diff --git a/JSTests/microbenchmarks/double-to-string.js b/JSTests/microbenchmarks/double-to-string.js new file mode 100644 index 0000000000000..e25fa6998df35 --- /dev/null +++ b/JSTests/microbenchmarks/double-to-string.js @@ -0,0 +1,7 @@ +function test(num) { + return num + 'px'; +} +noInline(test); + +for (var i = 0; i < 1e6; ++i) + test(i + 30.4); diff --git a/JSTests/microbenchmarks/js-map-get-int-no-dfg-no-inline.js b/JSTests/microbenchmarks/js-map-get-int-no-dfg-no-inline.js new file mode 100644 index 0000000000000..4dd9502c017c9 --- /dev/null +++ b/JSTests/microbenchmarks/js-map-get-int-no-dfg-no-inline.js @@ -0,0 +1,24 @@ +let m = new Map(); + +function testSet(m, k, v) { + m.set(k, v); +} +noDFG(testSet); +noInline(testSet); + +function testGet(m, k) { + m.get(k); +} +noDFG(testGet); +noInline(testGet); + +let count = 1e4; +for (let i = 0; i < count; ++i) { + testSet(m, i, i); +} + +for (let i = 0; i < count; ++i) { + for (let j = 0; j < 20; j++) { + testGet(m, i, i); + } +} \ No newline at end of file diff --git a/JSTests/microbenchmarks/js-map-get-int.js b/JSTests/microbenchmarks/js-map-get-int.js new file mode 100644 index 0000000000000..9928776705bd8 --- /dev/null +++ b/JSTests/microbenchmarks/js-map-get-int.js @@ -0,0 +1,21 @@ +let m = new Map(); + +function testSet(m, k, v) { + m.set(k, v); +} + +function testGet(m, k) { + m.get(k); +} + +let count = 1e4; +for (let i = 0; i < count; ++i) { + testSet(m, i, i); +} + +for (let i = 0; i < count; ++i) { + for (let j = 0; j < 20; j++) { + testGet(m, i, i); + } +} + diff --git a/JSTests/microbenchmarks/js-map-get-string-no-dfg-no-inline.js b/JSTests/microbenchmarks/js-map-get-string-no-dfg-no-inline.js new file mode 100644 index 0000000000000..c853f874c82ad --- /dev/null +++ b/JSTests/microbenchmarks/js-map-get-string-no-dfg-no-inline.js @@ -0,0 +1,27 @@ +let m = new Map(); + +function testSet(m, k, v) { + m.set(k, v); +} +noDFG(testSet); +noInline(testSet); + +function testGet(m, k) { + m.get(k); +} +noDFG(testGet); +noInline(testGet); + +let count = 1e4; +for (let i = 0; i < count; ++i) { + let s = i.toString(); + testSet(m, s, s); +} + +for (let i = 0; i < count; ++i) { + let s = i.toString(); + for (let j = 0; j < 20; j++) { + testGet(m, s); + } +} + diff --git a/JSTests/microbenchmarks/js-map-get-string.js b/JSTests/microbenchmarks/js-map-get-string.js new file mode 100644 index 0000000000000..1786e2e9c0f88 --- /dev/null +++ b/JSTests/microbenchmarks/js-map-get-string.js @@ -0,0 +1,22 @@ +let m = new Map(); + +function testSet(m, k, v) { + m.set(k, v); +} + +function testGet(m, k) { + m.get(k); +} + +let count = 1e4; +for (let i = 0; i < count; ++i) { + let s = i.toString(); + testSet(m, s, s); +} + +for (let i = 0; i < count; ++i) { + let s = i.toString(); + for (let j = 0; j < 20; j++) { + testGet(m, s); + } +} \ No newline at end of file diff --git a/JSTests/microbenchmarks/js-map-set-int-no-dfg-no-inline.js b/JSTests/microbenchmarks/js-map-set-int-no-dfg-no-inline.js new file mode 100644 index 0000000000000..dacec966a85c2 --- /dev/null +++ b/JSTests/microbenchmarks/js-map-set-int-no-dfg-no-inline.js @@ -0,0 +1,12 @@ +let m = new Map(); + +function testSet(m, k, v) { + m.set(k, v); +} +noDFG(testSet); +noInline(testSet); + +let count = 1e4; +for (let i = 0; i < count; ++i) { + testSet(m, i, i); +} diff --git a/JSTests/microbenchmarks/js-map-set-int.js b/JSTests/microbenchmarks/js-map-set-int.js new file mode 100644 index 0000000000000..5c306d1f335e4 --- /dev/null +++ b/JSTests/microbenchmarks/js-map-set-int.js @@ -0,0 +1,10 @@ +let m = new Map(); + +function testSet(m, k, v) { + m.set(k, v); +} + +let count = 1e4; +for (let i = 0; i < count; ++i) { + testSet(m, i, i); +} diff --git a/JSTests/microbenchmarks/js-map-set-string-no-dfg-no-inline.js b/JSTests/microbenchmarks/js-map-set-string-no-dfg-no-inline.js new file mode 100644 index 0000000000000..12f321047cc04 --- /dev/null +++ b/JSTests/microbenchmarks/js-map-set-string-no-dfg-no-inline.js @@ -0,0 +1,13 @@ +let m = new Map(); + +function testSet(m, k, v) { + m.set(k, v); +} +noDFG(testSet); +noInline(testSet); + +let count = 1e4; +for (let i = 0; i < count; ++i) { + let s = i.toString(); + testSet(m, s, s); +} diff --git a/JSTests/microbenchmarks/js-map-set-string.js b/JSTests/microbenchmarks/js-map-set-string.js new file mode 100644 index 0000000000000..2a4b4252fe26f --- /dev/null +++ b/JSTests/microbenchmarks/js-map-set-string.js @@ -0,0 +1,11 @@ +let m = new Map(); + +function testSet(m, k, v) { + m.set(k, v); +} + +let count = 1e4; +for (let i = 0; i < count; ++i) { + let s = i.toString(); + testSet(m, s, s); +} diff --git a/JSTests/microbenchmarks/to-hex.js b/JSTests/microbenchmarks/to-hex.js new file mode 100644 index 0000000000000..4db16e3a0906e --- /dev/null +++ b/JSTests/microbenchmarks/to-hex.js @@ -0,0 +1,14 @@ +//@ requireOptions("--useUint8ArrayBase64Methods=1") + +function test(buffer) +{ + return buffer.toHex(); +} +noInline(test); + +let buffer = new Uint8Array(16 * 1024); +for (let i = 0; i < buffer.length; ++i) + buffer[i] = i & 0xff; + +for (let i = 0; i < 1e4; ++i) + test(buffer); diff --git a/JSTests/stress/double-to-string.js b/JSTests/stress/double-to-string.js new file mode 100644 index 0000000000000..a412dff3d8ee2 --- /dev/null +++ b/JSTests/stress/double-to-string.js @@ -0,0 +1,12 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error('bad value: ' + actual); +} + +function test(num) { + return num + 'px'; +} +noInline(test); + +for (var i = 0; i < 1e6; ++i) + shouldBe(test(i + 30.4), (i + 30.4) + 'px'); diff --git a/JSTests/stress/map-iterators-next-2.js b/JSTests/stress/map-iterators-next-2.js new file mode 100644 index 0000000000000..654403fd43bd9 --- /dev/null +++ b/JSTests/stress/map-iterators-next-2.js @@ -0,0 +1,110 @@ +function assert(b) { + if (!b) + throw new Error("Bad result!"); +} +noInline(assert); + +let item1 = [1, 2]; +let item2 = [3, 4]; + +{ + let map = new Map(); + let iterator = map[Symbol.iterator](); + map.set(item1[0], item1[1]); + let element = iterator.next(); + + assert(element.done == false); + assert(element.value[0] == item1[0]); +} + +{ + let map = new Map([item1]); + let iterator = map[Symbol.iterator](); + map.set(item2[0], item2[1]); + let element = iterator.next(); + assert(element.done == false); + assert(element.value[0] == item1[0]); + + element = iterator.next(); + assert(element.done == false); + assert(element.value[0] == item2[0]); +} + +{ + let map = new Map([item1]); + let iterator = map[Symbol.iterator](); + map.delete(item1[0]); + + let element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); + + element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); +} + +{ + let map = new Map([item1, item2]); + let iterator = map[Symbol.iterator](); + map.delete(item2[0]); + + let element = iterator.next(); + assert(element.done == false); + assert(element.value[0] == item1[0]); + + element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); + + element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); +} + +{ + let map = new Map([item1, item2]); + let iterator = map[Symbol.iterator](); + + let element = iterator.next(); + assert(element.done == false); + assert(element.value[0] == item1[0]); + + element = iterator.next(); + assert(element.done == false); + assert(element.value[0] == item2[0]); + + element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); +} + +{ + let map = new Map([item1]); + let iterator = map[Symbol.iterator](); + map.clear(); + + let element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); + + element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); + + map.set(item1[0], item1[1]); + element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); +} + +{ + let map = new Map([item1]); + let iterator = map[Symbol.iterator](); + map.clear(); + + map.set(item1[0], item1[1]); + element = iterator.next(); + assert(element.done == false); + assert(element.value[0] == item1[0]); +} diff --git a/JSTests/stress/set-iterators-next-2.js b/JSTests/stress/set-iterators-next-2.js new file mode 100644 index 0000000000000..cbb35f8668ce2 --- /dev/null +++ b/JSTests/stress/set-iterators-next-2.js @@ -0,0 +1,110 @@ +function assert(b) { + if (!b) + throw new Error("Bad result!"); +} +noInline(assert); + +let item1 = 1; +let item2 = 2; + +{ + let set = new Set(); + let iterator = set[Symbol.iterator](); + set.add(item1); + let element = iterator.next(); + + assert(element.done == false); + assert(element.value == item1); +} + +{ + let set = new Set([item1]); + let iterator = set[Symbol.iterator](); + set.add(item2); + let element = iterator.next(); + assert(element.done == false); + assert(element.value == item1); + + element = iterator.next(); + assert(element.done == false); + assert(element.value == item2); +} + +{ + let set = new Set([item1]); + let iterator = set[Symbol.iterator](); + set.delete(item1); + + let element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); + + element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); +} + +{ + let set = new Set([item1, item2]); + let iterator = set[Symbol.iterator](); + set.delete(item2); + + let element = iterator.next(); + assert(element.done == false); + assert(element.value == item1); + + element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); + + element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); +} + +{ + let set = new Set([item1, item2]); + let iterator = set[Symbol.iterator](); + + let element = iterator.next(); + assert(element.done == false); + assert(element.value == item1); + + element = iterator.next(); + assert(element.done == false); + assert(element.value == item2); + + element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); +} + +{ + let set = new Set([item1]); + let iterator = set[Symbol.iterator](); + set.clear(); + + let element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); + + element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); + + set.add(item1); + element = iterator.next(); + assert(element.done == true); + assert(element.value == undefined); +} + +{ + let set = new Set([item1]); + let iterator = set[Symbol.iterator](); + set.clear(); + + set.add(item1); + element = iterator.next(); + assert(element.done == false); + assert(element.value == item1); +} diff --git a/JSTests/stress/uint8array-toBase64.js b/JSTests/stress/uint8array-toBase64.js new file mode 100644 index 0000000000000..0cc55819d5824 --- /dev/null +++ b/JSTests/stress/uint8array-toBase64.js @@ -0,0 +1,258 @@ +//@ requireOptions("--useUint8ArrayBase64Methods=1") + +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error(`FAIL: expected '${expected}' actual '${actual}'`); +} + +shouldBe((new Uint8Array([])).toBase64(), ""); +shouldBe((new Uint8Array([0])).toBase64(), "AA=="); +shouldBe((new Uint8Array([1])).toBase64(), "AQ=="); +shouldBe((new Uint8Array([128])).toBase64(), "gA=="); +shouldBe((new Uint8Array([254])).toBase64(), "/g=="); +shouldBe((new Uint8Array([255])).toBase64(), "/w=="); +shouldBe((new Uint8Array([0, 1])).toBase64(), "AAE="); +shouldBe((new Uint8Array([254, 255])).toBase64(), "/v8="); +shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64(), "AAGA/v8="); + +shouldBe((new Uint8Array([])).toBase64({}), ""); +shouldBe((new Uint8Array([0])).toBase64({}), "AA=="); +shouldBe((new Uint8Array([1])).toBase64({}), "AQ=="); +shouldBe((new Uint8Array([128])).toBase64({}), "gA=="); +shouldBe((new Uint8Array([254])).toBase64({}), "/g=="); +shouldBe((new Uint8Array([255])).toBase64({}), "/w=="); +shouldBe((new Uint8Array([0, 1])).toBase64({}), "AAE="); +shouldBe((new Uint8Array([254, 255])).toBase64({}), "/v8="); +shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({}), "AAGA/v8="); + +for (let omitPadding of [undefined, null, false, 0, ""]) { + shouldBe((new Uint8Array([])).toBase64({omitPadding}), ""); + shouldBe((new Uint8Array([0])).toBase64({omitPadding}), "AA=="); + shouldBe((new Uint8Array([1])).toBase64({omitPadding}), "AQ=="); + shouldBe((new Uint8Array([128])).toBase64({omitPadding}), "gA=="); + shouldBe((new Uint8Array([254])).toBase64({omitPadding}), "/g=="); + shouldBe((new Uint8Array([255])).toBase64({omitPadding}), "/w=="); + shouldBe((new Uint8Array([0, 1])).toBase64({omitPadding}), "AAE="); + shouldBe((new Uint8Array([254, 255])).toBase64({omitPadding}), "/v8="); + shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({omitPadding}), "AAGA/v8="); + + shouldBe((new Uint8Array([])).toBase64({get omitPadding() { return omitPadding; }}), ""); + shouldBe((new Uint8Array([0])).toBase64({get omitPadding() { return omitPadding; }}), "AA=="); + shouldBe((new Uint8Array([1])).toBase64({get omitPadding() { return omitPadding; }}), "AQ=="); + shouldBe((new Uint8Array([128])).toBase64({get omitPadding() { return omitPadding; }}), "gA=="); + shouldBe((new Uint8Array([254])).toBase64({get omitPadding() { return omitPadding; }}), "/g=="); + shouldBe((new Uint8Array([255])).toBase64({get omitPadding() { return omitPadding; }}), "/w=="); + shouldBe((new Uint8Array([0, 1])).toBase64({get omitPadding() { return omitPadding; }}), "AAE="); + shouldBe((new Uint8Array([254, 255])).toBase64({get omitPadding() { return omitPadding; }}), "/v8="); + shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({get omitPadding() { return omitPadding; }}), "AAGA/v8="); +} + +for (let omitPadding of [true, 42, "test", [], {}]) { + shouldBe((new Uint8Array([])).toBase64({omitPadding}), ""); + shouldBe((new Uint8Array([0])).toBase64({omitPadding}), "AA"); + shouldBe((new Uint8Array([1])).toBase64({omitPadding}), "AQ"); + shouldBe((new Uint8Array([128])).toBase64({omitPadding}), "gA"); + shouldBe((new Uint8Array([254])).toBase64({omitPadding}), "/g"); + shouldBe((new Uint8Array([255])).toBase64({omitPadding}), "/w"); + shouldBe((new Uint8Array([0, 1])).toBase64({omitPadding}), "AAE"); + shouldBe((new Uint8Array([254, 255])).toBase64({omitPadding}), "/v8"); + shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({omitPadding}), "AAGA/v8"); + + shouldBe((new Uint8Array([])).toBase64({get omitPadding() { return omitPadding; }}), ""); + shouldBe((new Uint8Array([0])).toBase64({get omitPadding() { return omitPadding; }}), "AA"); + shouldBe((new Uint8Array([1])).toBase64({get omitPadding() { return omitPadding; }}), "AQ"); + shouldBe((new Uint8Array([128])).toBase64({get omitPadding() { return omitPadding; }}), "gA"); + shouldBe((new Uint8Array([254])).toBase64({get omitPadding() { return omitPadding; }}), "/g"); + shouldBe((new Uint8Array([255])).toBase64({get omitPadding() { return omitPadding; }}), "/w"); + shouldBe((new Uint8Array([0, 1])).toBase64({get omitPadding() { return omitPadding; }}), "AAE"); + shouldBe((new Uint8Array([254, 255])).toBase64({get omitPadding() { return omitPadding; }}), "/v8"); + shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({get omitPadding() { return omitPadding; }}), "AAGA/v8"); +} + +for (let alphabet of [undefined, "base64"]) { + shouldBe((new Uint8Array([])).toBase64({alphabet}), ""); + shouldBe((new Uint8Array([0])).toBase64({alphabet}), "AA=="); + shouldBe((new Uint8Array([1])).toBase64({alphabet}), "AQ=="); + shouldBe((new Uint8Array([128])).toBase64({alphabet}), "gA=="); + shouldBe((new Uint8Array([254])).toBase64({alphabet}), "/g=="); + shouldBe((new Uint8Array([255])).toBase64({alphabet}), "/w=="); + shouldBe((new Uint8Array([0, 1])).toBase64({alphabet}), "AAE="); + shouldBe((new Uint8Array([254, 255])).toBase64({alphabet}), "/v8="); + shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({alphabet}), "AAGA/v8="); + + shouldBe((new Uint8Array([])).toBase64({get alphabet() { return alphabet; }}), ""); + shouldBe((new Uint8Array([0])).toBase64({get alphabet() { return alphabet; }}), "AA=="); + shouldBe((new Uint8Array([1])).toBase64({get alphabet() { return alphabet; }}), "AQ=="); + shouldBe((new Uint8Array([128])).toBase64({get alphabet() { return alphabet; }}), "gA=="); + shouldBe((new Uint8Array([254])).toBase64({get alphabet() { return alphabet; }}), "/g=="); + shouldBe((new Uint8Array([255])).toBase64({get alphabet() { return alphabet; }}), "/w=="); + shouldBe((new Uint8Array([0, 1])).toBase64({get alphabet() { return alphabet; }}), "AAE="); + shouldBe((new Uint8Array([254, 255])).toBase64({get alphabet() { return alphabet; }}), "/v8="); + shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({get alphabet() { return alphabet; }}), "AAGA/v8="); + + for (let omitPadding of [undefined, null, false, 0, ""]) { + shouldBe((new Uint8Array([])).toBase64({alphabet, omitPadding}), ""); + shouldBe((new Uint8Array([0])).toBase64({alphabet, omitPadding}), "AA=="); + shouldBe((new Uint8Array([1])).toBase64({alphabet, omitPadding}), "AQ=="); + shouldBe((new Uint8Array([128])).toBase64({alphabet, omitPadding}), "gA=="); + shouldBe((new Uint8Array([254])).toBase64({alphabet, omitPadding}), "/g=="); + shouldBe((new Uint8Array([255])).toBase64({alphabet, omitPadding}), "/w=="); + shouldBe((new Uint8Array([0, 1])).toBase64({alphabet, omitPadding}), "AAE="); + shouldBe((new Uint8Array([254, 255])).toBase64({alphabet, omitPadding}), "/v8="); + shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({alphabet, omitPadding}), "AAGA/v8="); + + shouldBe((new Uint8Array([])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), ""); + shouldBe((new Uint8Array([0])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "AA=="); + shouldBe((new Uint8Array([1])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "AQ=="); + shouldBe((new Uint8Array([128])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "gA=="); + shouldBe((new Uint8Array([254])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "/g=="); + shouldBe((new Uint8Array([255])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "/w=="); + shouldBe((new Uint8Array([0, 1])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "AAE="); + shouldBe((new Uint8Array([254, 255])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "/v8="); + shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "AAGA/v8="); + } + + for (let omitPadding of [true, 42, "test", [], {}]) { + shouldBe((new Uint8Array([])).toBase64({alphabet, omitPadding}), ""); + shouldBe((new Uint8Array([0])).toBase64({alphabet, omitPadding}), "AA"); + shouldBe((new Uint8Array([1])).toBase64({alphabet, omitPadding}), "AQ"); + shouldBe((new Uint8Array([128])).toBase64({alphabet, omitPadding}), "gA"); + shouldBe((new Uint8Array([254])).toBase64({alphabet, omitPadding}), "/g"); + shouldBe((new Uint8Array([255])).toBase64({alphabet, omitPadding}), "/w"); + shouldBe((new Uint8Array([0, 1])).toBase64({alphabet, omitPadding}), "AAE"); + shouldBe((new Uint8Array([254, 255])).toBase64({alphabet, omitPadding}), "/v8"); + shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({alphabet, omitPadding}), "AAGA/v8"); + + shouldBe((new Uint8Array([])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), ""); + shouldBe((new Uint8Array([0])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "AA"); + shouldBe((new Uint8Array([1])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "AQ"); + shouldBe((new Uint8Array([128])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "gA"); + shouldBe((new Uint8Array([254])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "/g"); + shouldBe((new Uint8Array([255])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "/w"); + shouldBe((new Uint8Array([0, 1])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "AAE"); + shouldBe((new Uint8Array([254, 255])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "/v8"); + shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({get alphabet() { return alphabet; }, get omitPadding() { return omitPadding; }}), "AAGA/v8"); + } +} + +shouldBe((new Uint8Array([])).toBase64({alphabet: "base64url"}), ""); +shouldBe((new Uint8Array([0])).toBase64({alphabet: "base64url"}), "AA=="); +shouldBe((new Uint8Array([1])).toBase64({alphabet: "base64url"}), "AQ=="); +shouldBe((new Uint8Array([128])).toBase64({alphabet: "base64url"}), "gA=="); +shouldBe((new Uint8Array([254])).toBase64({alphabet: "base64url"}), "_g=="); +shouldBe((new Uint8Array([255])).toBase64({alphabet: "base64url"}), "_w=="); +shouldBe((new Uint8Array([0, 1])).toBase64({alphabet: "base64url"}), "AAE="); +shouldBe((new Uint8Array([254, 255])).toBase64({alphabet: "base64url"}), "_v8="); +shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({alphabet: "base64url"}), "AAGA_v8="); + +shouldBe((new Uint8Array([])).toBase64({get alphabet() { return "base64url"; }}), ""); +shouldBe((new Uint8Array([0])).toBase64({get alphabet() { return "base64url"; }}), "AA=="); +shouldBe((new Uint8Array([1])).toBase64({get alphabet() { return "base64url"; }}), "AQ=="); +shouldBe((new Uint8Array([128])).toBase64({get alphabet() { return "base64url"; }}), "gA=="); +shouldBe((new Uint8Array([254])).toBase64({get alphabet() { return "base64url"; }}), "_g=="); +shouldBe((new Uint8Array([255])).toBase64({get alphabet() { return "base64url"; }}), "_w=="); +shouldBe((new Uint8Array([0, 1])).toBase64({get alphabet() { return "base64url"; }}), "AAE="); +shouldBe((new Uint8Array([254, 255])).toBase64({get alphabet() { return "base64url"; }}), "_v8="); +shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({get alphabet() { return "base64url"; }}), "AAGA_v8="); + +for (let omitPadding of [undefined, null, false, 0, ""]) { + shouldBe((new Uint8Array([])).toBase64({alphabet: "base64url", omitPadding}), ""); + shouldBe((new Uint8Array([0])).toBase64({alphabet: "base64url", omitPadding}), "AA=="); + shouldBe((new Uint8Array([1])).toBase64({alphabet: "base64url", omitPadding}), "AQ=="); + shouldBe((new Uint8Array([128])).toBase64({alphabet: "base64url", omitPadding}), "gA=="); + shouldBe((new Uint8Array([254])).toBase64({alphabet: "base64url", omitPadding}), "_g=="); + shouldBe((new Uint8Array([255])).toBase64({alphabet: "base64url", omitPadding}), "_w=="); + shouldBe((new Uint8Array([0, 1])).toBase64({alphabet: "base64url", omitPadding}), "AAE="); + shouldBe((new Uint8Array([254, 255])).toBase64({alphabet: "base64url", omitPadding}), "_v8="); + shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({alphabet: "base64url", omitPadding}), "AAGA_v8="); + + shouldBe((new Uint8Array([])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), ""); + shouldBe((new Uint8Array([0])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "AA=="); + shouldBe((new Uint8Array([1])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "AQ=="); + shouldBe((new Uint8Array([128])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "gA=="); + shouldBe((new Uint8Array([254])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "_g=="); + shouldBe((new Uint8Array([255])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "_w=="); + shouldBe((new Uint8Array([0, 1])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "AAE="); + shouldBe((new Uint8Array([254, 255])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "_v8="); + shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "AAGA_v8="); +} + +for (let omitPadding of [true, 42, "test", [], {}]) { + shouldBe((new Uint8Array([])).toBase64({alphabet: "base64url", omitPadding}), ""); + shouldBe((new Uint8Array([0])).toBase64({alphabet: "base64url", omitPadding}), "AA"); + shouldBe((new Uint8Array([1])).toBase64({alphabet: "base64url", omitPadding}), "AQ"); + shouldBe((new Uint8Array([128])).toBase64({alphabet: "base64url", omitPadding}), "gA"); + shouldBe((new Uint8Array([254])).toBase64({alphabet: "base64url", omitPadding}), "_g"); + shouldBe((new Uint8Array([255])).toBase64({alphabet: "base64url", omitPadding}), "_w"); + shouldBe((new Uint8Array([0, 1])).toBase64({alphabet: "base64url", omitPadding}), "AAE"); + shouldBe((new Uint8Array([254, 255])).toBase64({alphabet: "base64url", omitPadding}), "_v8"); + shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({alphabet: "base64url", omitPadding}), "AAGA_v8"); + + shouldBe((new Uint8Array([])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), ""); + shouldBe((new Uint8Array([0])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "AA"); + shouldBe((new Uint8Array([1])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "AQ"); + shouldBe((new Uint8Array([128])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "gA"); + shouldBe((new Uint8Array([254])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "_g"); + shouldBe((new Uint8Array([255])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "_w"); + shouldBe((new Uint8Array([0, 1])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "AAE"); + shouldBe((new Uint8Array([254, 255])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "_v8"); + shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toBase64({get alphabet() { return "base64url"; }, get omitPadding() { return omitPadding; }}), "AAGA_v8"); +} + +try { + let uint8array = new Uint8Array; + $.detachArrayBuffer(uint8array.buffer); + uint8array.toBase64(); +} catch (e) { + shouldBe(e instanceof TypeError, true); +} + +for (let alphabet of [undefined, "base64", "base64url"]) { + try { + let uint8array = new Uint8Array; + uint8array.toBase64({ + get alphabet() { + $.detachArrayBuffer(uint8array.buffer); + return alphabet; + }, + }); + } catch (e) { + shouldBe(e instanceof TypeError, true); + } + + try { + (new Uint8Array).toBase64({ + alphabet: { + toString() { + return alphabet; + }, + }, + }); + } catch (e) { + shouldBe(e instanceof TypeError, true); + } +} + +for (let options of [undefined, null, false, true, 42, "test", []]) { + try { + (new Uint8Array).toBase64(options); + } catch (e) { + shouldBe(e instanceof TypeError, true); + } +} + +for (let alphabet of [null, false, true, 42, "invalid", {}, []]) { + try { + (new Uint8Array).toBase64({alphabet}); + } catch (e) { + shouldBe(e instanceof TypeError, true); + } + + try { + (new Uint8Array).toBase64({ + get alphabet() { return alphabet; }, + }); + } catch (e) { + shouldBe(e instanceof TypeError, true); + } +} diff --git a/JSTests/stress/uint8array-toHex.js b/JSTests/stress/uint8array-toHex.js new file mode 100644 index 0000000000000..6a6fdabf1aecb --- /dev/null +++ b/JSTests/stress/uint8array-toHex.js @@ -0,0 +1,43 @@ +//@ requireOptions("--useUint8ArrayBase64Methods=1") + +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error(`FAIL: expected '${expected}' actual '${actual}'`); +} + +shouldBe((new Uint8Array([])).toHex(), ""); +shouldBe((new Uint8Array([0])).toHex(), "00"); +shouldBe((new Uint8Array([1])).toHex(), "01"); +shouldBe((new Uint8Array([128])).toHex(), "80"); +shouldBe((new Uint8Array([254])).toHex(), "fe"); +shouldBe((new Uint8Array([255])).toHex(), "ff"); +shouldBe((new Uint8Array([0, 1])).toHex(), "0001"); +shouldBe((new Uint8Array([254, 255])).toHex(), "feff"); +shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toHex(), "000180feff"); + +{ + let expected = '' + let buffer = new Uint8Array(16 * 1024); + for (let i = 0; i < buffer.length; ++i) { + buffer[i] = i & 0xff; + expected += (i & 0xff).toString(16).padStart(2, '0'); + } + shouldBe(buffer.toHex(), expected); +} +{ + let expected = '' + let buffer = new Uint8Array(15); + for (let i = 0; i < buffer.length; ++i) { + buffer[i] = i & 0xff; + expected += (i & 0xff).toString(16).padStart(2, '0'); + } + shouldBe(buffer.toHex(), expected); +} + +try { + let uint8array = new Uint8Array; + $.detachArrayBuffer(uint8array.buffer); + uint8array.toHex(); +} catch (e) { + shouldBe(e instanceof TypeError, true); +} diff --git a/JSTests/test262/expectations.yaml b/JSTests/test262/expectations.yaml index eef4ff6f5d9a9..ecb2363f32c67 100644 --- a/JSTests/test262/expectations.yaml +++ b/JSTests/test262/expectations.yaml @@ -895,9 +895,6 @@ test/built-ins/TypedArray/prototype/includes/index-compared-against-initial-leng test/built-ins/TypedArrayConstructors/ctors/object-arg/iterated-array-changed-by-tonumber.js: default: 'Test262Error: Expected SameValue(«NaN», «2») to be true (Testing with Float64Array.)' strict mode: 'Test262Error: Expected SameValue(«NaN», «2») to be true (Testing with Float64Array.)' -test/built-ins/TypedArrayConstructors/ctors/object-arg/iterated-array-with-modified-array-iterator.js: - default: 'Test262Error: Expected SameValue(«1», «4») to be true (Testing with Float64Array.)' - strict mode: 'Test262Error: Expected SameValue(«1», «4») to be true (Testing with Float64Array.)' test/harness/temporalHelpers-sample-time-zones.js: default: "TypeError: realTz.getOffsetNanosecondsFor is not a function. (In 'realTz.getOffsetNanosecondsFor(shiftInstant)', 'realTz.getOffsetNanosecondsFor' is undefined)" strict mode: "TypeError: realTz.getOffsetNanosecondsFor is not a function. (In 'realTz.getOffsetNanosecondsFor(shiftInstant)', 'realTz.getOffsetNanosecondsFor' is undefined)" @@ -949,6 +946,9 @@ test/intl402/Locale/prototype/firstDayOfWeek/valid-options.js: test/intl402/Locale/prototype/getWeekInfo/firstDay-by-option.js: default: 'Test262Error: new Intl.Locale("en", { firstDayOfWeek: mon }).getWeekInfo().firstDay returns "1" Expected SameValue(«7», «1») to be true' strict mode: 'Test262Error: new Intl.Locale("en", { firstDayOfWeek: mon }).getWeekInfo().firstDay returns "1" Expected SameValue(«7», «1») to be true' +test/intl402/NumberFormat/prototype/format/useGrouping-extended-en-IN.js: + default: 'Test262Error: notation: "compact" Expected SameValue(«1K», «1T») to be true' + strict mode: 'Test262Error: notation: "compact" Expected SameValue(«1K», «1T») to be true' test/intl402/Temporal/Duration/compare/relativeto-sub-minute-offset.js: default: 'RangeError: Cannot compare a duration of years, months, or weeks without a relativeTo option' strict mode: 'RangeError: Cannot compare a duration of years, months, or weeks without a relativeTo option' @@ -1109,8 +1109,6 @@ test/language/expressions/yield/star-rhs-iter-rtrn-res-done-no-value.js: test/language/expressions/yield/star-rhs-iter-thrw-res-done-no-value.js: default: 'Test262Error: access count (second iteration) Expected SameValue(«1», «0») to be true' strict mode: 'Test262Error: access count (second iteration) Expected SameValue(«1», «0») to be true' -test/language/global-code/script-decl-lex-var-declared-via-eval.js: - default: "SyntaxError: Can't create duplicate variable: 'test262Var'" test/language/identifier-resolution/assign-to-global-undefined.js: strict mode: Expected uncaught exception with name 'ReferenceError' but none was thrown test/language/import/import-assertions/json-extensibility-array.js: diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations index ec6a397daf9ef..f3747a183c7ae 100644 --- a/LayoutTests/TestExpectations +++ b/LayoutTests/TestExpectations @@ -2120,6 +2120,7 @@ fast/webgpu/regression/repro_275108.html [ Pass Failure Timeout ] [ Debug ] fast/webgpu/nocrash [ Skip ] [ Release ] fast/webgpu/nocrash [ Pass Failure Timeout ] [ Debug ] fast/webgpu/regression/repro_275624.html [ Skip ] +[ Debug ] fast/webgpu/texture-supports-blending.html [ Pass Crash ] # Imported W3C HTML/DOM ref tests that are failing. imported/w3c/web-platform-tests/html/dom/elements/global-attributes/dir_auto-textarea-script-N-between-Rs.html [ ImageOnlyFailure ] @@ -4976,6 +4977,7 @@ http/tests/media/video-canplaythrough-webm.html [ Skip ] media/media-session/mock-coordinator.html [ Skip ] media/track/track-description-cue.html [ Skip ] media/track/track-extended-descriptions.html [ Skip ] +fast/canvas/canvas-drawImage-hdr-video.html [ Skip ] # Soon Cocoa-only (currently only macOS) fast/forms/switch/ [ Skip ] @@ -7043,6 +7045,7 @@ imported/w3c/web-platform-tests/css/css-view-transitions/scroller-child.html [ I imported/w3c/web-platform-tests/css/css-view-transitions/scroller.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-view-transitions/iframe-and-main-frame-transition-old-main-new-iframe.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-view-transitions/iframe-and-main-frame-transition-old-main-old-iframe.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-view-transitions/paint-holding-in-iframe.html [ ImageOnlyFailure ] # Timeouts imported/w3c/web-platform-tests/css/css-view-transitions/iframe-transition.sub.html [ Skip ] @@ -7542,11 +7545,6 @@ imported/w3c/web-platform-tests/svg/painting/reftests/markers-orient-002.svg [ I imported/w3c/web-platform-tests/svg/painting/reftests/paint-context-001.svg [ ImageOnlyFailure ] imported/w3c/web-platform-tests/svg/painting/reftests/paint-context-002.svg [ ImageOnlyFailure ] -# tests added with webkit.org/b/272414 -webkit.org/b/272415 imported/w3c/web-platform-tests/svg/path/property/marker-path.svg [ ImageOnlyFailure ] -webkit.org/b/272416 imported/w3c/web-platform-tests/svg/path/property/mpath.svg [ ImageOnlyFailure ] -webkit.org/b/272417 imported/w3c/web-platform-tests/svg/path/property/priority.svg [ ImageOnlyFailure ] - # re-import css/css-align WPT failure webkit.org/b/271692 imported/w3c/web-platform-tests/css/css-align/blocks/align-content-block-break-overflow-020.html [ ImageOnlyFailure ] diff --git a/LayoutTests/accessibility/mac/textmarker-routines.html b/LayoutTests/accessibility/mac/textmarker-routines.html index 2b14708cd7773..94a51ffb705aa 100644 --- a/LayoutTests/accessibility/mac/textmarker-routines.html +++ b/LayoutTests/accessibility/mac/textmarker-routines.html @@ -13,7 +13,6 @@
- - - diff --git a/LayoutTests/compositing/shared-backing/update-backing-sharing-on-geometry-change-expected.html b/LayoutTests/compositing/shared-backing/update-backing-sharing-on-geometry-change-expected.html new file mode 100644 index 0000000000000..3aa68957d7f90 --- /dev/null +++ b/LayoutTests/compositing/shared-backing/update-backing-sharing-on-geometry-change-expected.html @@ -0,0 +1,87 @@ + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +

When the red square overlaps the yellow box, the inside box should be green.

+ + \ No newline at end of file diff --git a/LayoutTests/compositing/shared-backing/update-backing-sharing-on-geometry-change.html b/LayoutTests/compositing/shared-backing/update-backing-sharing-on-geometry-change.html new file mode 100644 index 0000000000000..9ba7cfd1f9c46 --- /dev/null +++ b/LayoutTests/compositing/shared-backing/update-backing-sharing-on-geometry-change.html @@ -0,0 +1,97 @@ + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +

When the red square overlaps the yellow box, the inside box should be green.

+ + \ No newline at end of file diff --git a/LayoutTests/compositing/shared-backing/update-backing-sharing-on-size-change-expected.html b/LayoutTests/compositing/shared-backing/update-backing-sharing-on-size-change-expected.html new file mode 100644 index 0000000000000..cbe69e339c93e --- /dev/null +++ b/LayoutTests/compositing/shared-backing/update-backing-sharing-on-size-change-expected.html @@ -0,0 +1,5 @@ + + +class3 diff --git a/LayoutTests/compositing/shared-backing/update-backing-sharing-on-size-change.html b/LayoutTests/compositing/shared-backing/update-backing-sharing-on-size-change.html new file mode 100644 index 0000000000000..22bc76abf2047 --- /dev/null +++ b/LayoutTests/compositing/shared-backing/update-backing-sharing-on-size-change.html @@ -0,0 +1,18 @@ + + + + +class3 + + +* +
+ diff --git a/LayoutTests/dom/html/level2/html/HTMLTableCellElement21.js b/LayoutTests/dom/html/level2/html/HTMLTableCellElement21.js index 480f2a46aa54e..5e39d024105aa 100644 --- a/LayoutTests/dom/html/level2/html/HTMLTableCellElement21.js +++ b/LayoutTests/dom/html/level2/html/HTMLTableCellElement21.js @@ -75,7 +75,7 @@ function loadComplete() { /** * - The noWrap attribute supresses word wrapping. + The noWrap attribute suppresses word wrapping. Retrieve the noWrap attribute of the second TH Element and examine its value. diff --git a/LayoutTests/dom/html/level2/html/HTMLTableCellElement22.js b/LayoutTests/dom/html/level2/html/HTMLTableCellElement22.js index 1a17786b3d917..2553437ca2415 100644 --- a/LayoutTests/dom/html/level2/html/HTMLTableCellElement22.js +++ b/LayoutTests/dom/html/level2/html/HTMLTableCellElement22.js @@ -75,7 +75,7 @@ function loadComplete() { /** * - The noWrap attribute supresses word wrapping. + The noWrap attribute suppresses word wrapping. Retrieve the noWrap attribute of the second TD Element and examine its value. diff --git a/LayoutTests/dom/xhtml/level2/html/HTMLTableCellElement21.js b/LayoutTests/dom/xhtml/level2/html/HTMLTableCellElement21.js index 480f2a46aa54e..5e39d024105aa 100644 --- a/LayoutTests/dom/xhtml/level2/html/HTMLTableCellElement21.js +++ b/LayoutTests/dom/xhtml/level2/html/HTMLTableCellElement21.js @@ -75,7 +75,7 @@ function loadComplete() { /** * - The noWrap attribute supresses word wrapping. + The noWrap attribute suppresses word wrapping. Retrieve the noWrap attribute of the second TH Element and examine its value. diff --git a/LayoutTests/dom/xhtml/level2/html/HTMLTableCellElement22.js b/LayoutTests/dom/xhtml/level2/html/HTMLTableCellElement22.js index 1a17786b3d917..2553437ca2415 100644 --- a/LayoutTests/dom/xhtml/level2/html/HTMLTableCellElement22.js +++ b/LayoutTests/dom/xhtml/level2/html/HTMLTableCellElement22.js @@ -75,7 +75,7 @@ function loadComplete() { /** * - The noWrap attribute supresses word wrapping. + The noWrap attribute suppresses word wrapping. Retrieve the noWrap attribute of the second TD Element and examine its value. diff --git a/LayoutTests/fast/canvas/canvas-drawImage-hdr-video-expected.txt b/LayoutTests/fast/canvas/canvas-drawImage-hdr-video-expected.txt index 4dea411731e39..0537eec9e99d0 100644 --- a/LayoutTests/fast/canvas/canvas-drawImage-hdr-video-expected.txt +++ b/LayoutTests/fast/canvas/canvas-drawImage-hdr-video-expected.txt @@ -4,7 +4,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE PASS pixelColor(1920 ,540)[0] is within 5 of 0 -PASS pixelColor(1920 ,540)[1] is within 5 of 174 +PASS pixelColor(1920 ,540)[1] is within 5 of 206 PASS pixelColor(1920 ,540)[2] is within 5 of 0 PASS pixelColor(1920 ,540)[3] is within 5 of 255 PASS successfullyParsed is true diff --git a/LayoutTests/fast/canvas/canvas-drawImage-hdr-video.html b/LayoutTests/fast/canvas/canvas-drawImage-hdr-video.html index 7d9be6c7edbd3..632173fb8f979 100644 --- a/LayoutTests/fast/canvas/canvas-drawImage-hdr-video.html +++ b/LayoutTests/fast/canvas/canvas-drawImage-hdr-video.html @@ -25,7 +25,7 @@ let sigma = 5; let pixel = "pixelColor(" + x + " ," + y + ")"; - let expectedColor = [0, 174, 0, 255]; + let expectedColor = [0, 206, 0, 255]; shouldBeCloseTo(pixel + "[0]", expectedColor[0], sigma); shouldBeCloseTo(pixel + "[1]", expectedColor[1], sigma); diff --git a/LayoutTests/fast/css/style-invalidation-inline-csstext.html b/LayoutTests/fast/css/style-invalidation-inline-csstext.html index 3eaafc7068078..2c3dc599b4c49 100644 --- a/LayoutTests/fast/css/style-invalidation-inline-csstext.html +++ b/LayoutTests/fast/css/style-invalidation-inline-csstext.html @@ -14,7 +14,7 @@ testDiv.style.cssText = cssText; - assert_equals(internals.styleChangeType(testDiv), shouldInvalidate ? "InlineStyleChange" : "NoStyleChange"); + assert_equals(internals.styleChangeType(testDiv), shouldInvalidate ? "InlineStyleInvalid" : "NoStyleChange"); assert_equals(internals.styleChangeType(testDivChild), "NoStyleChange"); }, name); } diff --git a/LayoutTests/fast/dynamic/block-inside-span-gets-removed-expected.html b/LayoutTests/fast/dynamic/block-inside-span-gets-removed-expected.html new file mode 100644 index 0000000000000..7bf3f4852ba5b --- /dev/null +++ b/LayoutTests/fast/dynamic/block-inside-span-gets-removed-expected.html @@ -0,0 +1,11 @@ + +
X X
+
X X
+
X X
+
X X
+
X X
diff --git a/LayoutTests/fast/dynamic/block-inside-span-gets-removed.html b/LayoutTests/fast/dynamic/block-inside-span-gets-removed.html new file mode 100644 index 0000000000000..edd09e6d77047 --- /dev/null +++ b/LayoutTests/fast/dynamic/block-inside-span-gets-removed.html @@ -0,0 +1,47 @@ + +
+ X +
Y
+
+
+
+ +
+
+ X +
Y
+
+
+ +
+ X +
Y
+
+
+
+
+ +
+ X + +
Y
+
+
+
+ +
+ +
Y
+
X +
+ diff --git a/LayoutTests/fast/dynamic/out-of-flow-video-assert-at-attach-expected.txt b/LayoutTests/fast/dynamic/out-of-flow-video-assert-at-attach-expected.txt new file mode 100644 index 0000000000000..e424d7759b32f --- /dev/null +++ b/LayoutTests/fast/dynamic/out-of-flow-video-assert-at-attach-expected.txt @@ -0,0 +1,2 @@ +PASS if no assert in debug. + diff --git a/LayoutTests/fast/dynamic/out-of-flow-video-assert-at-attach.html b/LayoutTests/fast/dynamic/out-of-flow-video-assert-at-attach.html new file mode 100644 index 0000000000000..81f8852d076a8 --- /dev/null +++ b/LayoutTests/fast/dynamic/out-of-flow-video-assert-at-attach.html @@ -0,0 +1,22 @@ + +PASS if no assert in debug. +
+ diff --git a/LayoutTests/fast/events/popup-blocking-click-in-iframe-expected.txt b/LayoutTests/fast/events/popup-blocking-click-in-iframe-expected.txt index fe229cd1d4199..c1a0c8163368e 100644 --- a/LayoutTests/fast/events/popup-blocking-click-in-iframe-expected.txt +++ b/LayoutTests/fast/events/popup-blocking-click-in-iframe-expected.txt @@ -1,5 +1,5 @@ -This tests that popup blocking does not supress windows opened in an iframe if the event handler is a function from an enclosing frame. +This tests that popup blocking does not suppress windows opened in an iframe if the event handler is a function from an enclosing frame. To run manually click the link in the iframe above with popup blocking enabled. diff --git a/LayoutTests/fast/events/popup-blocking-click-in-iframe.html b/LayoutTests/fast/events/popup-blocking-click-in-iframe.html index 96d19630d7ae9..28ea935e4dd29 100644 --- a/LayoutTests/fast/events/popup-blocking-click-in-iframe.html +++ b/LayoutTests/fast/events/popup-blocking-click-in-iframe.html @@ -50,7 +50,7 @@ -

This tests that popup blocking does not supress windows opened in an iframe if the event handler is a function from an enclosing frame.

+

This tests that popup blocking does not suppress windows opened in an iframe if the event handler is a function from an enclosing frame.

To run manually click the link in the iframe above with popup blocking enabled.

diff --git a/LayoutTests/fast/html/request-video-frame-callback-does-not-leak-expected.txt b/LayoutTests/fast/html/request-video-frame-callback-does-not-leak-expected.txt new file mode 100644 index 0000000000000..49429a10e30a2 --- /dev/null +++ b/LayoutTests/fast/html/request-video-frame-callback-does-not-leak-expected.txt @@ -0,0 +1,10 @@ +Tests that HTMLVideoElement.requestVideoFrameCallback does not leak the document object. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS The iframe document didn't leak. +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/fast/html/request-video-frame-callback-does-not-leak.html b/LayoutTests/fast/html/request-video-frame-callback-does-not-leak.html new file mode 100644 index 0000000000000..5f1f3461bcd45 --- /dev/null +++ b/LayoutTests/fast/html/request-video-frame-callback-does-not-leak.html @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/LayoutTests/fast/html/resources/request-video-frame-callback.html b/LayoutTests/fast/html/resources/request-video-frame-callback.html new file mode 100644 index 0000000000000..818a2c71cc0c2 --- /dev/null +++ b/LayoutTests/fast/html/resources/request-video-frame-callback.html @@ -0,0 +1,16 @@ + + + + + + + diff --git a/LayoutTests/fast/html/trusted-types-default-policy-gc-expected.txt b/LayoutTests/fast/html/trusted-types-default-policy-gc-expected.txt new file mode 100644 index 0000000000000..7ef22e9a431ad --- /dev/null +++ b/LayoutTests/fast/html/trusted-types-default-policy-gc-expected.txt @@ -0,0 +1 @@ +PASS diff --git a/LayoutTests/fast/html/trusted-types-default-policy-gc.html b/LayoutTests/fast/html/trusted-types-default-policy-gc.html new file mode 100644 index 0000000000000..a647db23d9303 --- /dev/null +++ b/LayoutTests/fast/html/trusted-types-default-policy-gc.html @@ -0,0 +1,20 @@ + + + + + Trusted Types default policy shouldn't be garbage collected + + + +
+ + + diff --git a/LayoutTests/fast/inline/positioned-content-inside-inline-box-expected.txt b/LayoutTests/fast/inline/positioned-content-inside-inline-box-expected.txt new file mode 100644 index 0000000000000..7e5579f72082f --- /dev/null +++ b/LayoutTests/fast/inline/positioned-content-inside-inline-box-expected.txt @@ -0,0 +1,5 @@ +XX XX +XX +XX +XX XX XX XX +PASS diff --git a/LayoutTests/fast/inline/positioned-content-inside-inline-box.html b/LayoutTests/fast/inline/positioned-content-inside-inline-box.html new file mode 100644 index 0000000000000..ced4a65125f69 --- /dev/null +++ b/LayoutTests/fast/inline/positioned-content-inside-inline-box.html @@ -0,0 +1,55 @@ + + + + +
+ XX + + + XX + + + + +
XX
+
+
+ + + XX + + + +
XX +
+ + + XX + + + + XX + + + + XX + + +
+ + \ No newline at end of file diff --git a/LayoutTests/fast/mediastream/MediaStreamTrack-getCapabilities-expected.txt b/LayoutTests/fast/mediastream/MediaStreamTrack-getCapabilities-expected.txt index 09870f88e246d..0f806bd765737 100644 --- a/LayoutTests/fast/mediastream/MediaStreamTrack-getCapabilities-expected.txt +++ b/LayoutTests/fast/mediastream/MediaStreamTrack-getCapabilities-expected.txt @@ -11,6 +11,7 @@ video track capabilities: capabilities.frameRate = { max: 30, min: 1 } capabilities.groupId = capabilities.height = { max: 1440, min: 1 } + capabilities.powerEfficient = [ false ] capabilities.width = { max: 2560, min: 1 } audio track capabilities: @@ -29,6 +30,7 @@ video track capabilities: capabilities.frameRate = { max: 120, min: 1 } capabilities.groupId = capabilities.height = { max: 2160, min: 1 } + capabilities.powerEfficient = [ false, true ] capabilities.torch = true capabilities.whiteBalanceMode = [ manual, single-shot, continuous ] capabilities.width = { max: 3840, min: 1 } @@ -50,6 +52,7 @@ video track capabilities: capabilities.frameRate = { max: 120, min: 1 } capabilities.groupId = capabilities.height = { max: 2160, min: 1 } + capabilities.powerEfficient = [ false, true ] capabilities.torch = true capabilities.whiteBalanceMode = [ manual, single-shot, continuous ] capabilities.width = { max: 3840, min: 1 } diff --git a/LayoutTests/fast/mediastream/MediaStreamTrack-getSettings-expected.txt b/LayoutTests/fast/mediastream/MediaStreamTrack-getSettings-expected.txt index ec1cf1954e14d..11c656d996051 100644 --- a/LayoutTests/fast/mediastream/MediaStreamTrack-getSettings-expected.txt +++ b/LayoutTests/fast/mediastream/MediaStreamTrack-getSettings-expected.txt @@ -11,6 +11,7 @@ video track settings: settings.frameRate = 30 settings.groupId = settings.height = 480 + settings.powerEfficient = false settings.width = 640 audio track settings: @@ -29,6 +30,7 @@ PASS "facingMode" in track.getCapabilities() is true PASS "frameRate" in track.getCapabilities() is true PASS "groupId" in track.getCapabilities() is true PASS "height" in track.getCapabilities() is true +PASS "powerEfficient" in track.getCapabilities() is true PASS "width" in track.getCapabilities() is true PASS "deviceId" in track.getCapabilities() is true PASS "echoCancellation" in track.getCapabilities() is true diff --git a/LayoutTests/fast/mediastream/camera-powerEfficient-track-expected.txt b/LayoutTests/fast/mediastream/camera-powerEfficient-track-expected.txt index c0666a119915e..27272076ffb6c 100644 --- a/LayoutTests/fast/mediastream/camera-powerEfficient-track-expected.txt +++ b/LayoutTests/fast/mediastream/camera-powerEfficient-track-expected.txt @@ -1,5 +1,6 @@ +PASS Selecting a non power efficient source PASS Selecting a non power efficient preset PASS Selecting a power efficient preset PASS Selecting a power efficient preset and check clones diff --git a/LayoutTests/fast/mediastream/camera-powerEfficient-track.html b/LayoutTests/fast/mediastream/camera-powerEfficient-track.html index ba2b253fddbca..c6dbd7a955feb 100644 --- a/LayoutTests/fast/mediastream/camera-powerEfficient-track.html +++ b/LayoutTests/fast/mediastream/camera-powerEfficient-track.html @@ -8,6 +8,18 @@ diff --git a/LayoutTests/fast/repaint/selection-gap-flipped-absolute-child-expected.txt b/LayoutTests/fast/repaint/selection-gap-flipped-absolute-child-expected.txt index 979efa39e550c..7b5e3d1bf38dc 100644 --- a/LayoutTests/fast/repaint/selection-gap-flipped-absolute-child-expected.txt +++ b/LayoutTests/fast/repaint/selection-gap-flipped-absolute-child-expected.txt @@ -2,4 +2,4 @@ Bug 111000: Selection gaps don't repaint correctly with transforms This tests that absolute elements that get flipped are invalidated correctly. The box will be competely green if the selected area was invalidated correctly. -(repaint rects (rect 0 0 100 100) ) +(repaint rects (rect -82 0 100 100) ) diff --git a/LayoutTests/fast/table/table-hittest-with-vertical-content-and-overflow-expected.txt b/LayoutTests/fast/table/table-hittest-with-vertical-content-and-overflow-expected.txt new file mode 100644 index 0000000000000..bd663d8c03f5e --- /dev/null +++ b/LayoutTests/fast/table/table-hittest-with-vertical-content-and-overflow-expected.txt @@ -0,0 +1,6 @@ +not ruby +ruby 1annotation +not ruby +ruby 2annotation +pass +Hover over "ruby 2" and try selecting it. diff --git a/LayoutTests/fast/table/table-hittest-with-vertical-content-and-overflow.html b/LayoutTests/fast/table/table-hittest-with-vertical-content-and-overflow.html new file mode 100644 index 0000000000000..92bf3074465fd --- /dev/null +++ b/LayoutTests/fast/table/table-hittest-with-vertical-content-and-overflow.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + +
not ruby
ruby 1annotation
not ruby
ruby 2annotation
+

+Hover over "ruby 2" and try selecting it.
+
\ No newline at end of file
diff --git a/LayoutTests/fast/webgpu/nocrash/fuzz-276279-expected.txt b/LayoutTests/fast/webgpu/nocrash/fuzz-276279-expected.txt
new file mode 100644
index 0000000000000..654ddf7f17efa
--- /dev/null
+++ b/LayoutTests/fast/webgpu/nocrash/fuzz-276279-expected.txt
@@ -0,0 +1 @@
+This test passes if it does not crash.
diff --git a/LayoutTests/fast/webgpu/nocrash/fuzz-276279.html b/LayoutTests/fast/webgpu/nocrash/fuzz-276279.html
new file mode 100644
index 0000000000000..8679a5d458c4c
--- /dev/null
+++ b/LayoutTests/fast/webgpu/nocrash/fuzz-276279.html
@@ -0,0 +1,29330 @@
+
+
diff --git a/LayoutTests/fast/webgpu/regression/repro_130656572-expected.txt b/LayoutTests/fast/webgpu/regression/repro_130656572-expected.txt
new file mode 100644
index 0000000000000..fc4f4b236f1e3
--- /dev/null
+++ b/LayoutTests/fast/webgpu/regression/repro_130656572-expected.txt
@@ -0,0 +1,3 @@
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+PASS output[0] is 0
+
diff --git a/LayoutTests/fast/webgpu/regression/repro_130656572.html b/LayoutTests/fast/webgpu/regression/repro_130656572.html
new file mode 100644
index 0000000000000..c6ecc772e9862
--- /dev/null
+++ b/LayoutTests/fast/webgpu/regression/repro_130656572.html
@@ -0,0 +1,97 @@
+
+
diff --git a/LayoutTests/fast/xsl/mozilla-tests.xsl b/LayoutTests/fast/xsl/mozilla-tests.xsl
index b10646d8165be..ee5306aa0f63c 100644
--- a/LayoutTests/fast/xsl/mozilla-tests.xsl
+++ b/LayoutTests/fast/xsl/mozilla-tests.xsl
@@ -69,7 +69,7 @@
 
 
 
-
+
 
 
 
diff --git a/LayoutTests/http/tests/geolocation/geolocation-get-current-position-does-not-leak.https-expected.txt b/LayoutTests/http/tests/geolocation/geolocation-get-current-position-does-not-leak.https-expected.txt
new file mode 100644
index 0000000000000..f3b2c09310883
--- /dev/null
+++ b/LayoutTests/http/tests/geolocation/geolocation-get-current-position-does-not-leak.https-expected.txt
@@ -0,0 +1,10 @@
+Tests that navigator.geolocation.getCurrentPosition() does not leak the document object.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS The iframe document didn't leak.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/geolocation/geolocation-get-current-position-does-not-leak.https.html b/LayoutTests/http/tests/geolocation/geolocation-get-current-position-does-not-leak.https.html
new file mode 100644
index 0000000000000..1e21de85bbbc4
--- /dev/null
+++ b/LayoutTests/http/tests/geolocation/geolocation-get-current-position-does-not-leak.https.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/geolocation/geolocation-watch-position-does-not-leak.https-expected.txt b/LayoutTests/http/tests/geolocation/geolocation-watch-position-does-not-leak.https-expected.txt
new file mode 100644
index 0000000000000..ad3a8d39bba55
--- /dev/null
+++ b/LayoutTests/http/tests/geolocation/geolocation-watch-position-does-not-leak.https-expected.txt
@@ -0,0 +1,10 @@
+Tests that navigator.geolocation.watchPosition() does not leak the document object.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS The iframe document didn't leak.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/geolocation/geolocation-watch-position-does-not-leak.https.html b/LayoutTests/http/tests/geolocation/geolocation-watch-position-does-not-leak.https.html
new file mode 100644
index 0000000000000..94de0b6bc641c
--- /dev/null
+++ b/LayoutTests/http/tests/geolocation/geolocation-watch-position-does-not-leak.https.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/geolocation/resources/geolocation-get-position-callback.html b/LayoutTests/http/tests/geolocation/resources/geolocation-get-position-callback.html
new file mode 100644
index 0000000000000..0d150260886a2
--- /dev/null
+++ b/LayoutTests/http/tests/geolocation/resources/geolocation-get-position-callback.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/geolocation/resources/geolocation-watch-position-callback.html b/LayoutTests/http/tests/geolocation/resources/geolocation-watch-position-callback.html
new file mode 100644
index 0000000000000..924e482ee91aa
--- /dev/null
+++ b/LayoutTests/http/tests/geolocation/resources/geolocation-watch-position-callback.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/mime/html-with-nosniff-html-expected.txt b/LayoutTests/http/tests/mime/html-with-nosniff-html-expected.txt
deleted file mode 100644
index cee72acd987b3..0000000000000
--- a/LayoutTests/http/tests/mime/html-with-nosniff-html-expected.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-nosniff-html.html has MIME type application/octet-stream
-
diff --git a/LayoutTests/http/tests/mime/html-with-nosniff-html.html b/LayoutTests/http/tests/mime/html-with-nosniff-html.html
deleted file mode 100644
index f808fe74c404b..0000000000000
--- a/LayoutTests/http/tests/mime/html-with-nosniff-html.html
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
diff --git a/LayoutTests/http/tests/mime/resources/.htaccess b/LayoutTests/http/tests/mime/resources/.htaccess
index 8db245f913e67..8c651044dc8c8 100644
--- a/LayoutTests/http/tests/mime/resources/.htaccess
+++ b/LayoutTests/http/tests/mime/resources/.htaccess
@@ -1,7 +1,3 @@
-
-Header always set X-Content-Type-Options "nosniff"
-Header always set Content-Type ""
-
 
 ForceType "text/plain"
 
diff --git a/LayoutTests/http/tests/mime/resources/nosniff-html.html b/LayoutTests/http/tests/mime/resources/nosniff-html.html
deleted file mode 100644
index ae8fee51175e7..0000000000000
--- a/LayoutTests/http/tests/mime/resources/nosniff-html.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/LayoutTests/http/tests/resources/document-leak-test.js b/LayoutTests/http/tests/resources/document-leak-test.js
new file mode 100644
index 0000000000000..392f4451cc624
--- /dev/null
+++ b/LayoutTests/http/tests/resources/document-leak-test.js
@@ -0,0 +1,70 @@
+jsTestIsAsync = true;
+
+if (!(window.testRunner && window.internals)) {
+    testFailed("Test requires both testRunner and internals");
+    finishJSTest();
+}
+
+var allFrames;
+var maxFailCount = 0;
+
+function createFrames(framesToCreate)
+{
+    if (typeof framesToCreate !== "number")
+        throw TypeError("framesToCreate must be a number.");
+
+    allFrames = new Array(framesToCreate);
+    maxFailCount = framesToCreate;
+
+    for (let i = 0; i < allFrames.length; ++i) {
+        let frame = document.createElement("iframe");
+        document.body.appendChild(frame);
+        allFrames[i] = frame;
+    }
+}
+
+function iframeForMessage(message)
+{
+    return allFrames.find(frame => frame.contentWindow === message.source);
+}
+
+var failCount = 0;
+function iframeLeaked()
+{
+    if (++failCount >= maxFailCount) {
+        testFailed("All iframe documents leaked.");
+        finishJSTest();
+    }
+}
+
+function iframeSentMessage(message)
+{
+    let iframe = iframeForMessage(message);
+    let frameDocumentID = internals.documentIdentifier(iframe.contentWindow.document);
+    let checkCount = 0;
+
+    iframe.addEventListener("load", () => {
+        let handle = setInterval(() => {
+            gc();
+            if (!internals.isDocumentAlive(frameDocumentID)) {
+                clearInterval(handle);
+                testPassed("The iframe document didn't leak.");
+                finishJSTest();
+            }
+
+            if (++checkCount > 5) {
+                clearInterval(handle);
+                iframeLeaked();
+            }
+        }, 10);
+    }, { once: true });
+
+    iframe.src = "about:blank";
+}
+
+function runDocumentLeakTest(options)
+{
+    createFrames(options.framesToCreate);
+    window.addEventListener("message", message => iframeSentMessage(message));
+    allFrames.forEach(iframe => iframe.src = options.frameURL);
+}
diff --git a/LayoutTests/http/tests/ssl/applepay/ApplePayError-expected.txt b/LayoutTests/http/tests/ssl/applepay/ApplePayError-expected.txt
index f4c230089e52b..0d30710d94de3 100644
--- a/LayoutTests/http/tests/ssl/applepay/ApplePayError-expected.txt
+++ b/LayoutTests/http/tests/ssl/applepay/ApplePayError-expected.txt
@@ -9,10 +9,10 @@ SETUP:
 PASS new ApplePayError() threw exception TypeError: Not enough arguments.
 
 SETUP:
-PASS new ApplePayError([]) threw exception TypeError: Argument 1 ('errorCode') to the ApplePayError constructor must be one of: "unknown", "shippingContactInvalid", "billingContactInvalid", "addressUnserviceable", "couponCodeInvalid", "couponCodeExpired".
+PASS new ApplePayError([]) threw exception TypeError: Argument 1 ('errorCode') to the ApplePayError constructor must be one of: "unknown", "shippingContactInvalid", "billingContactInvalid", "addressUnserviceable", "couponCodeInvalid", "couponCodeExpired", "unsupportedCard", "recipientContactInvalid".
 
 SETUP:
-PASS new ApplePayError('') threw exception TypeError: Argument 1 ('errorCode') to the ApplePayError constructor must be one of: "unknown", "shippingContactInvalid", "billingContactInvalid", "addressUnserviceable", "couponCodeInvalid", "couponCodeExpired".
+PASS new ApplePayError('') threw exception TypeError: Argument 1 ('errorCode') to the ApplePayError constructor must be one of: "unknown", "shippingContactInvalid", "billingContactInvalid", "addressUnserviceable", "couponCodeInvalid", "couponCodeExpired", "unsupportedCard", "recipientContactInvalid".
 
 SETUP:
 PASS new ApplePayError('unknown') did not throw exception.
diff --git a/LayoutTests/http/tests/ssl/applepay/ApplePaySession-expected.txt b/LayoutTests/http/tests/ssl/applepay/ApplePaySession-expected.txt
index 208f3bff1ee39..e6e9a53fb171f 100644
--- a/LayoutTests/http/tests/ssl/applepay/ApplePaySession-expected.txt
+++ b/LayoutTests/http/tests/ssl/applepay/ApplePaySession-expected.txt
@@ -152,9 +152,6 @@ PASS new ApplePaySession(2, request) threw exception TypeError: "amount" is not
 SETUP: request = validRequest(); request.total = { label: 'label', amount: '-10.00' };
 PASS new ApplePaySession(2, request) threw exception TypeError: Total amount must not be negative..
 
-SETUP: request = validRequest(); request.total = { label: 'label', amount: '10000000000.00' };
-PASS new ApplePaySession(2, request) threw exception TypeError: Total amount is too big..
-
 SETUP: request = validRequest(); request.total = { label: 'label', amount: '10.00', type: 'invalid' };
 PASS new ApplePaySession(2, request) threw exception TypeError: Type error.
 
diff --git a/LayoutTests/http/tests/ssl/applepay/ApplePaySession.html b/LayoutTests/http/tests/ssl/applepay/ApplePaySession.html
index 201e7d3c6c07d..8ddac5f2a2a7b 100644
--- a/LayoutTests/http/tests/ssl/applepay/ApplePaySession.html
+++ b/LayoutTests/http/tests/ssl/applepay/ApplePaySession.html
@@ -110,7 +110,6 @@
     logAndShouldThrow("request = validRequest(); request.total = { label: 'label' };", "new ApplePaySession(2, request)")
     logAndShouldThrow("request = validRequest(); request.total = { label: 'label', amount: 'amount' };", "new ApplePaySession(2, request)")
     logAndShouldThrow("request = validRequest(); request.total = { label: 'label', amount: '-10.00' };", "new ApplePaySession(2, request)")
-    logAndShouldThrow("request = validRequest(); request.total = { label: 'label', amount: '10000000000.00' };", "new ApplePaySession(2, request)")
     logAndShouldThrow("request = validRequest(); request.total = { label: 'label', amount: '10.00', type: 'invalid' };", "new ApplePaySession(2, request)")
     logAndShouldNotThrow("request = validRequest(); request.total = { label: 'label', amount: '10.00', type: 'pending' };", "new ApplePaySession(2, request)")
     
diff --git a/LayoutTests/http/tests/ssl/applepay/PaymentRequest.https-expected.txt b/LayoutTests/http/tests/ssl/applepay/PaymentRequest.https-expected.txt
index ff225dc8951cf..04804f14613c9 100644
--- a/LayoutTests/http/tests/ssl/applepay/PaymentRequest.https-expected.txt
+++ b/LayoutTests/http/tests/ssl/applepay/PaymentRequest.https-expected.txt
@@ -205,8 +205,6 @@ PASS new PaymentRequest([validPaymentMethod()], paymentDetails) threw exception
 SETUP: paymentDetails = validPaymentDetails(); paymentDetails.total = { label: 'label', amount: { currency: 'USD', value:'-10.00'} };
 PASS new PaymentRequest([validPaymentMethod()], paymentDetails) threw exception TypeError: Total currency values cannot be negative..
 
-SETUP: paymentDetails = validPaymentDetails(); paymentDetails.total = { label: 'label', amount: { currency: 'USD', value: '10000000000.00' } }; request = new PaymentRequest([validPaymentMethod()], paymentDetails)
-PASS request.show() rejected promise  with TypeError: Total amount is too big..
 
 
 Testing PaymentDetails.displayItems
diff --git a/LayoutTests/http/tests/ssl/applepay/PaymentRequest.https.html b/LayoutTests/http/tests/ssl/applepay/PaymentRequest.https.html
index 256ebbffc3236..9e46172908c53 100644
--- a/LayoutTests/http/tests/ssl/applepay/PaymentRequest.https.html
+++ b/LayoutTests/http/tests/ssl/applepay/PaymentRequest.https.html
@@ -147,7 +147,6 @@
     await logAndShouldThrow("paymentDetails = validPaymentDetails(); paymentDetails.total = { label: 'label', amount: 'amount' };", "new PaymentRequest([validPaymentMethod()], paymentDetails)")
     await logAndShouldThrow("paymentDetails = validPaymentDetails(); paymentDetails.total = { label: 'label', amount: { currency: '', value: '0' } };", "new PaymentRequest([validPaymentMethod()], paymentDetails)")
     await logAndShouldThrow("paymentDetails = validPaymentDetails(); paymentDetails.total = { label: 'label', amount: { currency: 'USD', value:'-10.00'} };", "new PaymentRequest([validPaymentMethod()], paymentDetails)")
-    await logAndShouldReject("paymentDetails = validPaymentDetails(); paymentDetails.total = { label: 'label', amount: { currency: 'USD', value: '10000000000.00' } }; request = new PaymentRequest([validPaymentMethod()], paymentDetails)", "request.show()")
     debug("")
     debug("")
 
diff --git a/LayoutTests/http/tests/webcodecs/audio-encoder-callbacks-do-not-leak-expected.txt b/LayoutTests/http/tests/webcodecs/audio-encoder-callbacks-do-not-leak-expected.txt
new file mode 100644
index 0000000000000..eb8a8b20993ce
--- /dev/null
+++ b/LayoutTests/http/tests/webcodecs/audio-encoder-callbacks-do-not-leak-expected.txt
@@ -0,0 +1,10 @@
+Tests that AudioEncoder callbacks do not leak the document object.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS The iframe document didn't leak.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/webcodecs/audio-encoder-callbacks-do-not-leak.html b/LayoutTests/http/tests/webcodecs/audio-encoder-callbacks-do-not-leak.html
new file mode 100644
index 0000000000000..0235ae6cce797
--- /dev/null
+++ b/LayoutTests/http/tests/webcodecs/audio-encoder-callbacks-do-not-leak.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/webcodecs/resources/audio-encoder-callbacks-frame.html b/LayoutTests/http/tests/webcodecs/resources/audio-encoder-callbacks-frame.html
new file mode 100644
index 0000000000000..63c21b5223646
--- /dev/null
+++ b/LayoutTests/http/tests/webcodecs/resources/audio-encoder-callbacks-frame.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/webcodecs/resources/video-decoder-callbacks-frame.html b/LayoutTests/http/tests/webcodecs/resources/video-decoder-callbacks-frame.html
new file mode 100644
index 0000000000000..c039cfacb554e
--- /dev/null
+++ b/LayoutTests/http/tests/webcodecs/resources/video-decoder-callbacks-frame.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/webcodecs/resources/video-encoder-callbacks-frame.html b/LayoutTests/http/tests/webcodecs/resources/video-encoder-callbacks-frame.html
new file mode 100644
index 0000000000000..e40b024c0e52f
--- /dev/null
+++ b/LayoutTests/http/tests/webcodecs/resources/video-encoder-callbacks-frame.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/webcodecs/video-decoder-callbacks-do-not-leak-expected.txt b/LayoutTests/http/tests/webcodecs/video-decoder-callbacks-do-not-leak-expected.txt
new file mode 100644
index 0000000000000..bbee38ded1de7
--- /dev/null
+++ b/LayoutTests/http/tests/webcodecs/video-decoder-callbacks-do-not-leak-expected.txt
@@ -0,0 +1,10 @@
+Tests that VideoDecoder error callback does not leak the document object.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS The iframe document didn't leak.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/webcodecs/video-decoder-callbacks-do-not-leak.html b/LayoutTests/http/tests/webcodecs/video-decoder-callbacks-do-not-leak.html
new file mode 100644
index 0000000000000..d7726379d4e84
--- /dev/null
+++ b/LayoutTests/http/tests/webcodecs/video-decoder-callbacks-do-not-leak.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/webcodecs/video-encoder-callbacks-do-not-leak-expected.txt b/LayoutTests/http/tests/webcodecs/video-encoder-callbacks-do-not-leak-expected.txt
new file mode 100644
index 0000000000000..9364b97ad8dad
--- /dev/null
+++ b/LayoutTests/http/tests/webcodecs/video-encoder-callbacks-do-not-leak-expected.txt
@@ -0,0 +1,10 @@
+Tests that VideoEncoder callbacks do not leak the document object.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS The iframe document didn't leak.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/webcodecs/video-encoder-callbacks-do-not-leak.html b/LayoutTests/http/tests/webcodecs/video-encoder-callbacks-do-not-leak.html
new file mode 100644
index 0000000000000..06e891796aa38
--- /dev/null
+++ b/LayoutTests/http/tests/webcodecs/video-encoder-callbacks-do-not-leak.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LayoutTests/http/wpt/webcodecs/encode-bitrate-change-expected.txt b/LayoutTests/http/wpt/webcodecs/encode-bitrate-change-expected.txt
new file mode 100644
index 0000000000000..e2292747a79e0
--- /dev/null
+++ b/LayoutTests/http/wpt/webcodecs/encode-bitrate-change-expected.txt
@@ -0,0 +1,8 @@
+
+PASS VP8 - bitrate
+PASS VP8 - framerate
+PASS VP9 - bitrate
+PASS VP9 - framerate
+PASS H.264 - bitrate
+PASS H.264 - framerate
+
diff --git a/LayoutTests/http/wpt/webcodecs/encode-bitrate-change.html b/LayoutTests/http/wpt/webcodecs/encode-bitrate-change.html
new file mode 100644
index 0000000000000..c23c52cc7a01c
--- /dev/null
+++ b/LayoutTests/http/wpt/webcodecs/encode-bitrate-change.html
@@ -0,0 +1,99 @@
+
+
+
+ + +
+ + + + diff --git a/LayoutTests/imported/w3c/resources/resource-files.json b/LayoutTests/imported/w3c/resources/resource-files.json index 3f55b36fb7667..48a481a6a57be 100644 --- a/LayoutTests/imported/w3c/resources/resource-files.json +++ b/LayoutTests/imported/w3c/resources/resource-files.json @@ -4465,6 +4465,7 @@ "web-platform-tests/css/css-view-transitions/capture-with-visibility-mixed-descendants-ref.html", "web-platform-tests/css/css-view-transitions/class-specificity-ref.html", "web-platform-tests/css/css-view-transitions/clip-path-larger-than-border-box-on-child-of-named-element-ref.html", + "web-platform-tests/css/css-view-transitions/content-escapes-clip-with-abspos-child-ref.html", "web-platform-tests/css/css-view-transitions/content-object-fit-fill-ref.html", "web-platform-tests/css/css-view-transitions/content-object-fit-none-ref.html", "web-platform-tests/css/css-view-transitions/content-smaller-than-box-size-ref.html", @@ -4508,6 +4509,7 @@ "web-platform-tests/css/css-view-transitions/iframe-transition-ref.html", "web-platform-tests/css/css-view-transitions/inline-child-with-filter-ref.html", "web-platform-tests/css/css-view-transitions/inline-element-size-ref.html", + "web-platform-tests/css/css-view-transitions/inline-with-offset-from-containing-block-clipped-ref.html", "web-platform-tests/css/css-view-transitions/inline-with-offset-from-containing-block-ref.html", "web-platform-tests/css/css-view-transitions/intrinsic-aspect-ratio-ref.html", "web-platform-tests/css/css-view-transitions/japanese-tag-ref.html", @@ -4543,6 +4545,7 @@ "web-platform-tests/css/css-view-transitions/navigation/root-element-transition-ref.html", "web-platform-tests/css/css-view-transitions/navigation/transition-to-prerender-ref.html", "web-platform-tests/css/css-view-transitions/new-and-old-sizes-match-ref.html", + "web-platform-tests/css/css-view-transitions/new-content-ancestor-clipped-2-ref.html", "web-platform-tests/css/css-view-transitions/new-content-ancestor-clipped-ref.html", "web-platform-tests/css/css-view-transitions/new-content-captures-clip-path-ref.html", "web-platform-tests/css/css-view-transitions/new-content-captures-different-size-ref.html", @@ -4554,6 +4557,7 @@ "web-platform-tests/css/css-view-transitions/new-content-changes-overflow-ref.html", "web-platform-tests/css/css-view-transitions/new-content-container-writing-modes-ref.html", "web-platform-tests/css/css-view-transitions/new-content-element-writing-modes-ref.html", + "web-platform-tests/css/css-view-transitions/new-content-flat-transform-ancestor-ref.html", "web-platform-tests/css/css-view-transitions/new-content-from-root-display-none-ref.html", "web-platform-tests/css/css-view-transitions/new-content-has-scrollbars-ref.html", "web-platform-tests/css/css-view-transitions/new-content-is-empty-div-ref.html", @@ -4562,9 +4566,12 @@ "web-platform-tests/css/css-view-transitions/new-content-object-view-box-clip-path-reference-ref.html", "web-platform-tests/css/css-view-transitions/new-content-object-view-box-overflow-clipped-ref.html", "web-platform-tests/css/css-view-transitions/new-content-object-view-box-overflow-ref.html", + "web-platform-tests/css/css-view-transitions/new-content-preserve-3d-ancestor-ref.html", "web-platform-tests/css/css-view-transitions/new-content-scaling-ref.html", + "web-platform-tests/css/css-view-transitions/new-content-transform-position-fixed-ref.html", "web-platform-tests/css/css-view-transitions/new-element-on-start-ref.html", "web-platform-tests/css/css-view-transitions/new-root-vertical-writing-mode-ref.html", + "web-platform-tests/css/css-view-transitions/no-painting-while-render-blocked-ref.html", "web-platform-tests/css/css-view-transitions/no-root-capture-ref.html", "web-platform-tests/css/css-view-transitions/no-white-flash-before-activation-ref.html", "web-platform-tests/css/css-view-transitions/nothing-captured-ref.html", @@ -4583,10 +4590,12 @@ "web-platform-tests/css/css-view-transitions/old-content-object-view-box-clip-path-reference-ref.html", "web-platform-tests/css/css-view-transitions/old-content-object-view-box-overflow-ref.html", "web-platform-tests/css/css-view-transitions/old-root-vertical-writing-mode-ref.html", + "web-platform-tests/css/css-view-transitions/paint-holding-in-iframe-ref.html", "web-platform-tests/css/css-view-transitions/pseudo-element-overflow-hidden-ref.html", "web-platform-tests/css/css-view-transitions/pseudo-element-preserve-3d-ref.html", "web-platform-tests/css/css-view-transitions/pseudo-rendering-invalidation-ref.html", "web-platform-tests/css/css-view-transitions/pseudo-with-classes-ref.html", + "web-platform-tests/css/css-view-transitions/reset-state-after-scrolled-view-transition-ref.html", "web-platform-tests/css/css-view-transitions/root-captured-as-different-tag-ref.html", "web-platform-tests/css/css-view-transitions/root-scrollbar-with-fixed-background-ref.html", "web-platform-tests/css/css-view-transitions/root-style-change-during-animation-ref.html", @@ -4604,6 +4613,7 @@ "web-platform-tests/css/css-view-transitions/shared-transition-author-style.manual.html", "web-platform-tests/css/css-view-transitions/shared-transition-half.manual.html", "web-platform-tests/css/css-view-transitions/shared-transition-shapes.manual.html", + "web-platform-tests/css/css-view-transitions/sibling-frames-transition-ref.html", "web-platform-tests/css/css-view-transitions/snapshot-containing-block-absolute-ref.html", "web-platform-tests/css/css-view-transitions/snapshot-containing-block-includes-scrollbar-gutter-ref.html", "web-platform-tests/css/css-view-transitions/snapshot-containing-block-static-ref.html", @@ -4615,6 +4625,7 @@ "web-platform-tests/css/css-view-transitions/support/iframe-scrollbar-child.html", "web-platform-tests/css/css-view-transitions/support/transition-in-empty-iframe-child.html", "web-platform-tests/css/css-view-transitions/transform-origin-view-transition-group-ref.html", + "web-platform-tests/css/css-view-transitions/transformed-element-scroll-transform-ref.html", "web-platform-tests/css/css-view-transitions/transition-in-empty-iframe-ref.html", "web-platform-tests/css/css-view-transitions/view-transition-name-is-backdrop-filter-root-ref.html", "web-platform-tests/css/css-view-transitions/view-transition-name-is-grouping-ref.html", diff --git a/LayoutTests/imported/w3c/web-platform-tests/WebIDL/ecmascript-binding/attributes-accessors-unique-function-objects-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/WebIDL/ecmascript-binding/attributes-accessors-unique-function-objects-expected.txt index 4a11d0256366b..450f50f5851a9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/WebIDL/ecmascript-binding/attributes-accessors-unique-function-objects-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/WebIDL/ecmascript-binding/attributes-accessors-unique-function-objects-expected.txt @@ -1,4 +1,4 @@ -CONSOLE MESSAGE: window.styleMedia is deprecated draft version of window.matchMedia API that is not implemented in Firefox and will be removed from the web platform in future. +CONSOLE MESSAGE: window.styleMedia is a deprecated draft version of window.matchMedia API, and it will be removed in the future. PASS For attributes, each copy of the accessor property has distinct built-in function objects for its getters and setters. diff --git a/LayoutTests/imported/w3c/web-platform-tests/content-security-policy/reporting/report-clips-sample.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/content-security-policy/reporting/report-clips-sample.https-expected.txt index fdd50a55cef16..f35970ad3d590 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/content-security-policy/reporting/report-clips-sample.https-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/content-security-policy/reporting/report-clips-sample.https-expected.txt @@ -1,9 +1,7 @@ -FAIL Unsafe eval violation sample is clipped to 40 characters. assert_throws_js: function "_ => { - eval("evil = '1234567890123456789012345678901234567890';"); - }" did not throw +PASS Unsafe eval violation sample is clipped to 40 characters. FAIL Function constructor - the other kind of eval - is clipped. assert_throws_js: function "_ => { new Function("a", "b", "return '1234567890123456789012345678901234567890';"); }" did not throw -FAIL Trusted Types violation sample is clipped to 40 characters excluded the sink name. assert_equals: expected "Element innerHTML|1234567890123456789012345678901234567890" but got "Element innerHTML|1234567890123456789012" +PASS Trusted Types violation sample is clipped to 40 characters excluded the sink name. diff --git a/LayoutTests/imported/w3c/web-platform-tests/content-security-policy/reporting/report-clips-sample.https.html b/LayoutTests/imported/w3c/web-platform-tests/content-security-policy/reporting/report-clips-sample.https.html index 696a27ba75691..961e0065919cd 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/content-security-policy/reporting/report-clips-sample.https.html +++ b/LayoutTests/imported/w3c/web-platform-tests/content-security-policy/reporting/report-clips-sample.https.html @@ -1,4 +1,4 @@ - + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/abspos/position-absolute-012-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/abspos/position-absolute-012-expected.txt index fcbc500c1a38d..d756c45927fab 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/abspos/position-absolute-012-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/abspos/position-absolute-012-expected.txt @@ -31,102 +31,38 @@ PASS .flexbox 29 PASS .flexbox 30 PASS .flexbox 31 PASS .flexbox 32 -FAIL .flexbox 33 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 34 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 35 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 36 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 37 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 38 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 39 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 40 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 41 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 42 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 43 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 44 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 45 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 46 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 47 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 48 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 49 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 50 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 51 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 52 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 53 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 54 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 55 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 56 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 57 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 58 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 59 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 60 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 61 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 62 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 63 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 64 assert_equals: -
-offsetLeft expected 0 but got 50 +PASS .flexbox 33 +PASS .flexbox 34 +PASS .flexbox 35 +PASS .flexbox 36 +PASS .flexbox 37 +PASS .flexbox 38 +PASS .flexbox 39 +PASS .flexbox 40 +PASS .flexbox 41 +PASS .flexbox 42 +PASS .flexbox 43 +PASS .flexbox 44 +PASS .flexbox 45 +PASS .flexbox 46 +PASS .flexbox 47 +PASS .flexbox 48 +PASS .flexbox 49 +PASS .flexbox 50 +PASS .flexbox 51 +PASS .flexbox 52 +PASS .flexbox 53 +PASS .flexbox 54 +PASS .flexbox 55 +PASS .flexbox 56 +PASS .flexbox 57 +PASS .flexbox 58 +PASS .flexbox 59 +PASS .flexbox 60 +PASS .flexbox 61 +PASS .flexbox 62 +PASS .flexbox 63 +PASS .flexbox 64 PASS .flexbox 65 PASS .flexbox 66 PASS .flexbox 67 diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/abspos/position-absolute-013-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/abspos/position-absolute-013-expected.txt index 98b951cd31a1b..900ba908878a3 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/abspos/position-absolute-013-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/abspos/position-absolute-013-expected.txt @@ -143,438 +143,150 @@ PASS .flexbox 141 PASS .flexbox 142 PASS .flexbox 143 PASS .flexbox 144 -FAIL .flexbox 145 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 146 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 147 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 148 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 149 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 150 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 151 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 152 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 153 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 154 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 155 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 156 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 157 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 158 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 159 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 160 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 161 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 162 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 163 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 164 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 165 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 166 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 167 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 168 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 169 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 170 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 171 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 172 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 173 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 174 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 175 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 176 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 177 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 178 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 179 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 180 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 181 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 182 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 183 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 184 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 185 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 186 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 187 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 188 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 189 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 190 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 191 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 192 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 193 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 194 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 195 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 196 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 197 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 198 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 199 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 200 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 201 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 202 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 203 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 204 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 205 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 206 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 207 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 208 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 209 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 210 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 211 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 212 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 213 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 214 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 215 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 216 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 217 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 218 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 219 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 220 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 221 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 222 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 223 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 224 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 225 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 226 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 227 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 228 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 229 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 230 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 231 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 232 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 233 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 234 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 235 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 236 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 237 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 238 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 239 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 240 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 241 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 242 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 243 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 244 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 245 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 246 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 247 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 248 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 249 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 250 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 251 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 252 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 253 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 254 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 255 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 256 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 257 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 258 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 259 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 260 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 261 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 262 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 263 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 264 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 265 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 266 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 267 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 268 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 269 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 270 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 271 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 272 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 273 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 274 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 275 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 276 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 277 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 278 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 279 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 280 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 281 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 282 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 283 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 284 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 285 assert_equals: -
-offsetLeft expected 0 but got 50 -FAIL .flexbox 286 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 287 assert_equals: -
-offsetLeft expected 50 but got 0 -FAIL .flexbox 288 assert_equals: -
-offsetLeft expected 50 but got 0 +PASS .flexbox 145 +PASS .flexbox 146 +PASS .flexbox 147 +PASS .flexbox 148 +PASS .flexbox 149 +PASS .flexbox 150 +PASS .flexbox 151 +PASS .flexbox 152 +PASS .flexbox 153 +PASS .flexbox 154 +PASS .flexbox 155 +PASS .flexbox 156 +PASS .flexbox 157 +PASS .flexbox 158 +PASS .flexbox 159 +PASS .flexbox 160 +PASS .flexbox 161 +PASS .flexbox 162 +PASS .flexbox 163 +PASS .flexbox 164 +PASS .flexbox 165 +PASS .flexbox 166 +PASS .flexbox 167 +PASS .flexbox 168 +PASS .flexbox 169 +PASS .flexbox 170 +PASS .flexbox 171 +PASS .flexbox 172 +PASS .flexbox 173 +PASS .flexbox 174 +PASS .flexbox 175 +PASS .flexbox 176 +PASS .flexbox 177 +PASS .flexbox 178 +PASS .flexbox 179 +PASS .flexbox 180 +PASS .flexbox 181 +PASS .flexbox 182 +PASS .flexbox 183 +PASS .flexbox 184 +PASS .flexbox 185 +PASS .flexbox 186 +PASS .flexbox 187 +PASS .flexbox 188 +PASS .flexbox 189 +PASS .flexbox 190 +PASS .flexbox 191 +PASS .flexbox 192 +PASS .flexbox 193 +PASS .flexbox 194 +PASS .flexbox 195 +PASS .flexbox 196 +PASS .flexbox 197 +PASS .flexbox 198 +PASS .flexbox 199 +PASS .flexbox 200 +PASS .flexbox 201 +PASS .flexbox 202 +PASS .flexbox 203 +PASS .flexbox 204 +PASS .flexbox 205 +PASS .flexbox 206 +PASS .flexbox 207 +PASS .flexbox 208 +PASS .flexbox 209 +PASS .flexbox 210 +PASS .flexbox 211 +PASS .flexbox 212 +PASS .flexbox 213 +PASS .flexbox 214 +PASS .flexbox 215 +PASS .flexbox 216 +PASS .flexbox 217 +PASS .flexbox 218 +PASS .flexbox 219 +PASS .flexbox 220 +PASS .flexbox 221 +PASS .flexbox 222 +PASS .flexbox 223 +PASS .flexbox 224 +PASS .flexbox 225 +PASS .flexbox 226 +PASS .flexbox 227 +PASS .flexbox 228 +PASS .flexbox 229 +PASS .flexbox 230 +PASS .flexbox 231 +PASS .flexbox 232 +PASS .flexbox 233 +PASS .flexbox 234 +PASS .flexbox 235 +PASS .flexbox 236 +PASS .flexbox 237 +PASS .flexbox 238 +PASS .flexbox 239 +PASS .flexbox 240 +PASS .flexbox 241 +PASS .flexbox 242 +PASS .flexbox 243 +PASS .flexbox 244 +PASS .flexbox 245 +PASS .flexbox 246 +PASS .flexbox 247 +PASS .flexbox 248 +PASS .flexbox 249 +PASS .flexbox 250 +PASS .flexbox 251 +PASS .flexbox 252 +PASS .flexbox 253 +PASS .flexbox 254 +PASS .flexbox 255 +PASS .flexbox 256 +PASS .flexbox 257 +PASS .flexbox 258 +PASS .flexbox 259 +PASS .flexbox 260 +PASS .flexbox 261 +PASS .flexbox 262 +PASS .flexbox 263 +PASS .flexbox 264 +PASS .flexbox 265 +PASS .flexbox 266 +PASS .flexbox 267 +PASS .flexbox 268 +PASS .flexbox 269 +PASS .flexbox 270 +PASS .flexbox 271 +PASS .flexbox 272 +PASS .flexbox 273 +PASS .flexbox 274 +PASS .flexbox 275 +PASS .flexbox 276 +PASS .flexbox 277 +PASS .flexbox 278 +PASS .flexbox 279 +PASS .flexbox 280 +PASS .flexbox 281 +PASS .flexbox 282 +PASS .flexbox 283 +PASS .flexbox 284 +PASS .flexbox 285 +PASS .flexbox 286 +PASS .flexbox 287 +PASS .flexbox 288 PASS .flexbox 289 PASS .flexbox 290 PASS .flexbox 291 diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/flex-item-compressible-001-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/flex-item-compressible-001-expected.txt index deff9a11bee41..c04d550da660e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/flex-item-compressible-001-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/flex-item-compressible-001-expected.txt @@ -30,18 +30,8 @@ PASS .flexbox 7 PASS .flexbox 8 PASS .flexbox 9 PASS .flexbox 10 -FAIL .flexbox 11 assert_equals: -
-
- -
-width expected 140 but got 100 -FAIL .flexbox 12 assert_equals: -
-
- -
-width expected 140 but got 100 +PASS .flexbox 11 +PASS .flexbox 12 PASS .flexbox 13 PASS .flexbox 14 PASS .flexbox 15 diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/flex-item-compressible-002-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/flex-item-compressible-002-expected.txt index f256663b25605..eea9efcc8808f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/flex-item-compressible-002-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/flex-item-compressible-002-expected.txt @@ -20,78 +20,18 @@ Test3: "height: calc(140px + 100%)" -FAIL .flexbox 1 assert_equals: -
-
- -
-height expected 100 but got 40 -FAIL .flexbox 2 assert_equals: -
-
- -
-height expected 100 but got 40 -FAIL .flexbox 3 assert_equals: -
-
- -
-height expected 140 but got 40 -FAIL .flexbox 4 assert_equals: -
-
- -
-height expected 140 but got 40 -FAIL .flexbox 5 assert_equals: -
-
- -
-height expected 140 but got 40 -FAIL .flexbox 6 assert_equals: -
-
- -
-height expected 100 but got 40 -FAIL .flexbox 7 assert_equals: -
-
- -
-height expected 100 but got 40 -FAIL .flexbox 8 assert_equals: -
-
- -
-height expected 140 but got 40 -FAIL .flexbox 9 assert_equals: -
-
- -
-height expected 140 but got 40 -FAIL .flexbox 10 assert_equals: -
-
- -
-height expected 140 but got 40 -FAIL .flexbox 11 assert_equals: -
-
- -
-height expected 140 but got 100 -FAIL .flexbox 12 assert_equals: -
-
- -
-height expected 140 but got 129 +PASS .flexbox 1 +PASS .flexbox 2 +PASS .flexbox 3 +PASS .flexbox 4 +PASS .flexbox 5 +PASS .flexbox 6 +PASS .flexbox 7 +PASS .flexbox 8 +PASS .flexbox 9 +PASS .flexbox 10 +PASS .flexbox 11 +PASS .flexbox 12 PASS .flexbox 13 PASS .flexbox 14 PASS .flexbox 15 diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/text-box-trim/inheritance-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/text-box-trim/inheritance-expected.txt index b3413eba99a32..192d0914bc346 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/text-box-trim/inheritance-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-inline/text-box-trim/inheritance-expected.txt @@ -1,6 +1,6 @@ PASS Property text-box-edge has initial value leading -FAIL Property text-box-edge inherits assert_equals: expected "text" but got "leading" +PASS Property text-box-edge inherits PASS Property text-box-trim has initial value none PASS Property text-box-trim does not inherit diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/charset/page-windows-1252-http-windows-1251-css-utf8-bom-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/charset/page-windows-1252-http-windows-1251-css-utf8-bom-expected.txt new file mode 100644 index 0000000000000..422052c84632c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/charset/page-windows-1252-http-windows-1251-css-utf8-bom-expected.txt @@ -0,0 +1,3 @@ + +PASS CSS charset: page windows-1252, CSS-HTTP windows-1251, CSS UTF-8 BOM + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/charset/page-windows-1252-http-windows-1251-css-utf8-bom.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/charset/page-windows-1252-http-windows-1251-css-utf8-bom.html new file mode 100644 index 0000000000000..6a65942e16ef3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/charset/page-windows-1252-http-windows-1251-css-utf8-bom.html @@ -0,0 +1,17 @@ + +CSS charset: page windows-1252, CSS-HTTP windows-1251, CSS UTF-8 BOM + + + + + +
+
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/charset/support/utf8-bom-http-windows-1251.css b/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/charset/support/utf8-bom-http-windows-1251.css new file mode 100644 index 0000000000000..1dbf5cf2fceeb --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/charset/support/utf8-bom-http-windows-1251.css @@ -0,0 +1 @@ +# { visibility:hidden } \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/custom-property-rule-ambiguity.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/custom-property-rule-ambiguity.html index 50728bc94c258..04f908acde2e7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/custom-property-rule-ambiguity.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/custom-property-rule-ambiguity.html @@ -47,10 +47,10 @@ assert_equals(rules[0].selectorText, 'div'); let div = rules[0]; let x = div.style.getPropertyValue('--x'); - assert_equals(x, 'hover { }\n .b { }'); + assert_equals(x.trim(), 'hover { }\n .b { }'); let childRules = div.cssRules; assert_equals(childRules.length, 1); - assert_equals(childRules[0].selectorText, '.a'); + assert_equals(childRules[0].selectorText, '& .a'); }, 'Nested rule that looks like a custom property declaration'); @@ -79,6 +79,6 @@ // So in other words, there should be no nested '.b.' child rule here. let childRules = div.cssRules; assert_equals(childRules.length, 1); - assert_equals(childRules[0].selectorText, '.a'); + assert_equals(childRules[0].selectorText, '& .a'); }, 'Nested rule that looks like an invalid custom property declaration'); diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/serialize-escape-identifiers-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/serialize-escape-identifiers-expected.txt new file mode 100644 index 0000000000000..9482f348393fa --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/serialize-escape-identifiers-expected.txt @@ -0,0 +1,3 @@ + +FAIL Rules must be the same after serialization round-trip, even with escaped characters assert_equals: expected "@import url(\"abc\") layer(\\{\\});\n@font-feature-values \"abc{}oops\" { }\n@font-palette-values --abc{}oops { }\n@keyframes abc{}oops { \n}\n@layer abc\\;oops\\!;" but got "@import url(\"abc\") layer(\\{\\});\n@font-feature-values \"abc{}oops\" { }\n@font-palette-values --abc { }\noops { }\n@keyframes abc { \n}\noops { }\n@layer abc\\;oops\\!;" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/serialize-escape-identifiers.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/serialize-escape-identifiers.html new file mode 100644 index 0000000000000..90476d6eca0ac --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/serialize-escape-identifiers.html @@ -0,0 +1,28 @@ + + + + Properly escape CSS identifiers + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/w3c-import.log index 726b8bb4e2a2d..1fee5627e647a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/w3c-import.log +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/w3c-import.log @@ -33,6 +33,7 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/missing-semicolon.html /LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/non-ascii-codepoints.html /LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/serialize-consecutive-tokens.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/serialize-escape-identifiers.html /LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/trailing-braces.html /LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/unclosed-constructs.html /LayoutTests/imported/w3c/web-platform-tests/css/css-syntax/unclosed-url-at-eof.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/transparent-accent-color-001-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/transparent-accent-color-001-expected.html new file mode 100644 index 0000000000000..91a9600e180cd --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/transparent-accent-color-001-expected.html @@ -0,0 +1,63 @@ + +CSS Basic User Interface Test: transparent accent color + + + + +

Test passes if in each box below, you see a pair of identically colored check-boxes. + +

+
+ + +
+ +
+ + +
+ +
+ + +
+ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/transparent-accent-color-001.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/transparent-accent-color-001.html new file mode 100644 index 0000000000000..5a5ad4f0a41dc --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/transparent-accent-color-001.html @@ -0,0 +1,66 @@ + +CSS Basic User Interface Test: transparent accent color + + + + + + + +

Test passes if in each box below, you see a pair of identically colored check-boxes. + +

+
+ + +
+ +
+ + +
+ +
+ + +
+ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/transparent-accent-color-002-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/transparent-accent-color-002-expected.html new file mode 100644 index 0000000000000..db70dede94533 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/transparent-accent-color-002-expected.html @@ -0,0 +1,63 @@ + +CSS Basic User Interface Test: transparent accent color + + + + +

Test passes if in each box below, you see a pair of identically colored check-boxes. + +

+
+ + +
+ +
+ + +
+ +
+ + +
+ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/transparent-accent-color-002.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/transparent-accent-color-002.html new file mode 100644 index 0000000000000..9bc5022c562d9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-ui/transparent-accent-color-002.html @@ -0,0 +1,66 @@ + +CSS Basic User Interface Test: transparent accent color + + + + + + + +

Test passes if in each box below, you see a pair of identically colored check-boxes. + +

+
+ + +
+ +
+ + +
+ +
+ + +
+ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/content-escapes-clip-with-abspos-child-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/content-escapes-clip-with-abspos-child-ref.html new file mode 100644 index 0000000000000..1e22d6f66d742 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/content-escapes-clip-with-abspos-child-ref.html @@ -0,0 +1,27 @@ + +View transitions: view-transition-name element, when captured escapes its clips even if it has an abspos child (ref) + + + + + + +
+
+
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/dialog-in-rtl-iframe-old-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/dialog-in-rtl-iframe-old-expected.html deleted file mode 100644 index 9d70d30bc7cda..0000000000000 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/dialog-in-rtl-iframe-old-expected.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - View transitions: Dialog element in RTL scrollable iframe - - - - - - - diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/dialog-in-rtl-iframe-old-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/dialog-in-rtl-iframe-old-ref.html deleted file mode 100644 index 9d70d30bc7cda..0000000000000 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/dialog-in-rtl-iframe-old-ref.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - View transitions: Dialog element in RTL scrollable iframe - - - - - - - diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/dialog-in-rtl-iframe-old.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/dialog-in-rtl-iframe-old.html deleted file mode 100644 index 8dbf6d322caf1..0000000000000 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/dialog-in-rtl-iframe-old.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - View transitions: Dialog element in RTL scrollable iframe (old image) - - - - - - - - - - - - - diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-ancestor-clipped-2.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-ancestor-clipped-2.html index 158ebec612693..3ce02097476ec 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-ancestor-clipped-2.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-ancestor-clipped-2.html @@ -36,7 +36,6 @@
+ + + + +
+
+
+
+
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-inline-with-offset-from-containing-block-clipped.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-inline-with-offset-from-containing-block-clipped.html index 2b122d0a74620..955377c1086de 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-inline-with-offset-from-containing-block-clipped.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-inline-with-offset-from-containing-block-clipped.html @@ -32,7 +32,7 @@
-      +     
+ + + + +
+
+
+
+
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-inline-with-offset-from-containing-block-clipped.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-inline-with-offset-from-containing-block-clipped.html index 7f35d8599b371..3566edf957854 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-inline-with-offset-from-containing-block-clipped.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-inline-with-offset-from-containing-block-clipped.html @@ -32,7 +32,7 @@
-      +     
+ + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-scrollbar-with-fixed-background-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-scrollbar-with-fixed-background-ref.html index ae1ff38f2f63d..bc2c5862bf534 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-scrollbar-with-fixed-background-ref.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-scrollbar-with-fixed-background-ref.html @@ -1,4 +1,5 @@ - + + View transitions: capture root element with scrollbar (ref) @@ -20,7 +21,7 @@
-
+
+ + + + + + + + + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-name-overridden-by-important-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-name-overridden-by-important-expected.html new file mode 100644 index 0000000000000..62419b07ee148 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-name-overridden-by-important-expected.html @@ -0,0 +1,14 @@ + + +View transitions: view-transition-names are tree scoped (ref) + + + + +
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-name-overridden-by-important.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-name-overridden-by-important.html new file mode 100644 index 0000000000000..139fbd01b76cf --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-name-overridden-by-important.html @@ -0,0 +1,59 @@ + + +View transitions: shadow parts should give precedence to !important + + + + + +
+ + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-name.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-name.html new file mode 100644 index 0000000000000..192fbca20c992 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-name.html @@ -0,0 +1,51 @@ + + +View transitions: shadow parts + + + + + + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/sibling-frames-transition-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/sibling-frames-transition-expected.html new file mode 100644 index 0000000000000..437c730b9e941 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/sibling-frames-transition-expected.html @@ -0,0 +1,42 @@ + +View transitions: main frame and sibling frames transition at the same time (ref) + + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/sibling-frames-transition-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/sibling-frames-transition-ref.html new file mode 100644 index 0000000000000..437c730b9e941 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/sibling-frames-transition-ref.html @@ -0,0 +1,42 @@ + +View transitions: main frame and sibling frames transition at the same time (ref) + + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/sibling-frames-transition.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/sibling-frames-transition.html new file mode 100644 index 0000000000000..b4814c5ca81b7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/sibling-frames-transition.html @@ -0,0 +1,101 @@ + + +View transitions: main frame and sibling frames transition at the same time + + + + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/support/dialog-in-rtl-iframe-child-old.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/support/dialog-in-rtl-iframe-child-old.html deleted file mode 100644 index e399a0dfb7f7a..0000000000000 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/support/dialog-in-rtl-iframe-child-old.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/w3c-import.log index 219b77150686a..9307d604f2a0b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/w3c-import.log +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/w3c-import.log @@ -21,6 +21,8 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/3d-transform-outgoing-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/3d-transform-outgoing.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/WEB_FEATURES.yml +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/active-view-transition-pseudo-class-match-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/active-view-transition-pseudo-class-match.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/animating-new-content-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/animating-new-content-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/animating-new-content-subset-expected.html @@ -59,6 +61,7 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/clip-path-larger-than-border-box-on-child-of-named-element-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/clip-path-larger-than-border-box-on-child-of-named-element-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/clip-path-larger-than-border-box-on-child-of-named-element.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/content-escapes-clip-with-abspos-child-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/content-object-fit-fill-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/content-object-fit-none-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/content-smaller-than-box-size-expected.html @@ -155,6 +158,7 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/fragmented-during-transition-skips.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/get-computed-style-crash.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/group-animation-for-root-transition.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/hit-test-pseudo-element-element-from-point.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/hit-test-unpainted-element-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/hit-test-unpainted-element-from-point.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/hit-test-unpainted-element-ref.html @@ -196,6 +200,7 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/inline-element-size-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/inline-element-size-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/inline-element-size.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/inline-with-offset-from-containing-block-clipped-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/inline-with-offset-from-containing-block-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/inline-with-offset-from-containing-block-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/inline-with-offset-from-containing-block.html @@ -273,6 +278,9 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-and-old-sizes-match-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-and-old-sizes-match-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-and-old-sizes-match.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-ancestor-clipped-2-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-ancestor-clipped-2-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-ancestor-clipped-2.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-ancestor-clipped-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-ancestor-clipped-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-ancestor-clipped.html @@ -306,12 +314,19 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-element-writing-modes-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-element-writing-modes-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-element-writing-modes.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-escapes-clip-with-abspos-child-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-escapes-clip-with-abspos-child.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-flat-transform-ancestor-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-flat-transform-ancestor-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-flat-transform-ancestor.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-from-root-display-none-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-from-root-display-none-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-from-root-display-none.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-has-scrollbars-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-has-scrollbars-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-has-scrollbars.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-inline-with-offset-from-containing-block-clipped-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-inline-with-offset-from-containing-block-clipped.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-intrinsic-aspect-ratio-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-intrinsic-aspect-ratio.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-is-empty-div-expected.html @@ -336,9 +351,17 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-object-view-box-overflow-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-object-view-box-overflow-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-object-view-box-overflow.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-preserve-3d-ancestor-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-preserve-3d-ancestor-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-preserve-3d-ancestor.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-root-scrollbar-with-fixed-background-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-root-scrollbar-with-fixed-background.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-scaling-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-scaling-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-scaling.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-transform-position-fixed-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-transform-position-fixed-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-transform-position-fixed.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-with-object-view-box-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-with-object-view-box.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/new-content-with-overflow-expected.html @@ -354,6 +377,9 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/no-crash-set-exception.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/no-crash-view-transition-in-massive-iframe.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/no-css-animation-while-render-blocked.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/no-painting-while-render-blocked-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/no-painting-while-render-blocked-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/no-painting-while-render-blocked.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/no-raf-while-render-blocked.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/no-root-capture-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/no-root-capture-ref.html @@ -390,9 +416,13 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-element-writing-modes-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-element-writing-modes-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-element-writing-modes.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-escapes-clip-with-abspos-child-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-escapes-clip-with-abspos-child.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-has-scrollbars-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-has-scrollbars-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-has-scrollbars.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-inline-with-offset-from-containing-block-clipped-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-inline-with-offset-from-containing-block-clipped.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-intrinsic-aspect-ratio-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-intrinsic-aspect-ratio.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-is-empty-div-expected.html @@ -414,6 +444,8 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-object-view-box-overflow-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-object-view-box-overflow-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-object-view-box-overflow.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-root-scrollbar-with-fixed-background-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-root-scrollbar-with-fixed-background.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-with-object-view-box-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-with-object-view-box.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/old-content-with-overflow-expected.html @@ -430,6 +462,9 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/only-child-old.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/only-child-on-root-element-with-view-transition.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/only-child-view-transition.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/paint-holding-in-iframe-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/paint-holding-in-iframe-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/paint-holding-in-iframe.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/paused-animation-at-end.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/pseudo-computed-style-stays-in-sync-with-new-element.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/pseudo-element-animations.html @@ -475,15 +510,16 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/pseudo-with-classes-view-transition-image-pair-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/pseudo-with-classes-view-transition-image-pair.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/ready_resolves_after_dom_before_raf.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/reset-state-after-scrolled-view-transition-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/reset-state-after-scrolled-view-transition-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/reset-state-after-scrolled-view-transition.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-captured-as-different-tag-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-captured-as-different-tag-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-captured-as-different-tag.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-element-cv-hidden-crash.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-element-display-none-crash.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-element-display-none-during-transition-crash.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-scrollbar-with-fixed-background-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-scrollbar-with-fixed-background-ref.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-scrollbar-with-fixed-background.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-style-change-during-animation-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-style-change-during-animation-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/root-style-change-during-animation.html @@ -520,9 +556,18 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/set-universal-specificity-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/set-universal-specificity-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/set-universal-specificity.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-name-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-name-nested-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-name-nested.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-name-overridden-by-important-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-name-overridden-by-important.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-name.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shared-transition-author-style.manual.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shared-transition-half.manual.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/shared-transition-shapes.manual.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/sibling-frames-transition-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/sibling-frames-transition-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/sibling-frames-transition.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/snapshot-containing-block-absolute-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/snapshot-containing-block-absolute-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/snapshot-containing-block-absolute.html @@ -546,6 +591,9 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transform-origin-view-transition-group-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transform-origin-view-transition-group-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transform-origin-view-transition-group.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transformed-element-scroll-transform-expected.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transformed-element-scroll-transform-ref.html +/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transformed-element-scroll-transform.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transition-in-empty-iframe-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transition-in-empty-iframe-ref.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transition-in-empty-iframe.html @@ -588,8 +636,6 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/view-transition-types-reserved.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/view-transition-types-stay-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/view-transition-types-stay.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/view-transition-types-universal-match-expected.html -/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/view-transition-types-universal-match.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/web-animation-pseudo-incorrect-name.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/web-animations-api-expected.html /LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/web-animations-api-parse-pseudo-argument-expected.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/mathml/presentation-markup/fractions/frac-1-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/mathml/presentation-markup/fractions/frac-1-expected.txt index ff8216d15acb0..a9386f7874f54 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/mathml/presentation-markup/fractions/frac-1-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/mathml/presentation-markup/fractions/frac-1-expected.txt @@ -3,7 +3,7 @@ PASS Fraction axis is aligned on the math axis PASS Vertical positions of numerator and denominator PASS Horizontal alignments of numerator and denominator PASS Dimension of mfrac elements -FAIL Preferred width of mfrac elements assert_approx_equals: Should be the maximum preferred width of numerator/denominator. expected 32 +/- 1 but got 30 +PASS Preferred width of mfrac elements diff --git a/LayoutTests/imported/w3c/web-platform-tests/mathml/presentation-markup/mrow/no-spacing-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/mathml/presentation-markup/mrow/no-spacing-expected.txt index bfb51c406473c..a59ce07cb287a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/mathml/presentation-markup/mrow/no-spacing-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/mathml/presentation-markup/mrow/no-spacing-expected.txt @@ -1,5 +1,5 @@ -FAIL Spacing inside . assert_less_than_equal: expected a number less than or equal to 100 but got 225 +FAIL Spacing inside . assert_less_than_equal: expected a number less than or equal to 100 but got 227 PASS Spacing around . FAIL Spacing inside . assert_less_than_equal: expected a number less than or equal to 100 but got 455 PASS Spacing around . diff --git a/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/not-participating-to-parent-layout-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/not-participating-to-parent-layout-expected.txt index 99ad70ad97ffa..962b9e32a03e6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/not-participating-to-parent-layout-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/not-participating-to-parent-layout-expected.txt @@ -24,9 +24,9 @@ PASS merror layout is not affected by children with "position: fixed" style PASS mfrac preferred width calculation is not affected by children with "display: none" style PASS mfrac layout is not affected by children with "display: none" style PASS mfrac preferred width calculation is not affected by children with "position: absolute" style -FAIL mfrac layout is not affected by children with "position: absolute" style assert_approx_equals: block size expected 18.71875 +/- 1 but got 0 +FAIL mfrac layout is not affected by children with "position: absolute" style assert_approx_equals: inline size expected 2 +/- 1 but got 0 PASS mfrac preferred width calculation is not affected by children with "position: fixed" style -FAIL mfrac layout is not affected by children with "position: fixed" style assert_approx_equals: block size expected 18.71875 +/- 1 but got 0 +FAIL mfrac layout is not affected by children with "position: fixed" style assert_approx_equals: inline size expected 2 +/- 1 but got 0 PASS mi preferred width calculation is not affected by children with "display: none" style PASS mi layout is not affected by children with "display: none" style PASS mi preferred width calculation is not affected by children with "position: absolute" style diff --git a/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/border-002-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/border-002-expected.txt index 2500da6dae28b..0dad84711814c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/border-002-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/border-002-expected.txt @@ -1,51 +1,51 @@ PASS Border properties on maction PASS Border properties on maction (rtl) -FAIL Border properties on menclose assert_approx_equals: bottom border expected 60 +/- 1 but got -3.96875 -FAIL Border properties on menclose (rtl) assert_approx_equals: bottom border expected 60 +/- 1 but got -3.96875 +PASS Border properties on menclose +PASS Border properties on menclose (rtl) PASS Border properties on merror PASS Border properties on merror (rtl) -FAIL Border properties on mfrac assert_approx_equals: left border expected 30 +/- 1 but got 0 -FAIL Border properties on mfrac (rtl) assert_approx_equals: left border expected 30 +/- 1 but got 0 +PASS Border properties on mfrac +PASS Border properties on mfrac (rtl) PASS Border properties on mi PASS Border properties on mi (rtl) -FAIL Border properties on mmultiscripts assert_approx_equals: left border expected 30 +/- 1 but got 0 -FAIL Border properties on mmultiscripts (rtl) assert_approx_equals: left border expected 30 +/- 1 but got 0 +PASS Border properties on mmultiscripts +PASS Border properties on mmultiscripts (rtl) PASS Border properties on mn PASS Border properties on mn (rtl) PASS Border properties on mo PASS Border properties on mo (rtl) -FAIL Border properties on mover assert_approx_equals: left border expected 30 +/- 1 but got 0 -FAIL Border properties on mover (rtl) assert_approx_equals: left border expected 30 +/- 1 but got 0 -FAIL Border properties on mpadded assert_approx_equals: bottom border expected 60 +/- 1 but got 0 -FAIL Border properties on mpadded (rtl) assert_approx_equals: bottom border expected 60 +/- 1 but got 0 +PASS Border properties on mover +PASS Border properties on mover (rtl) +PASS Border properties on mpadded +PASS Border properties on mpadded (rtl) PASS Border properties on mphantom PASS Border properties on mphantom (rtl) -FAIL Border properties on mroot assert_approx_equals: left border expected 30 +/- 1 but got 0 -FAIL Border properties on mroot (rtl) assert_approx_equals: left border expected 30 +/- 1 but got 0 +PASS Border properties on mroot +PASS Border properties on mroot (rtl) PASS Border properties on mrow PASS Border properties on mrow (rtl) PASS Border properties on ms PASS Border properties on ms (rtl) -FAIL Border properties on mspace assert_approx_equals: left/right border expected 70 +/- 1 but got 0 -FAIL Border properties on msqrt assert_approx_equals: bottom border expected 60 +/- 1 but got -8.21875 -FAIL Border properties on msqrt (rtl) assert_approx_equals: bottom border expected 60 +/- 1 but got -8.21875 +PASS Border properties on mspace +PASS Border properties on msqrt +PASS Border properties on msqrt (rtl) PASS Border properties on mstyle PASS Border properties on mstyle (rtl) -FAIL Border properties on msub assert_approx_equals: left border expected 30 +/- 1 but got 0 -FAIL Border properties on msub (rtl) assert_approx_equals: left border expected 30 +/- 1 but got 0 -FAIL Border properties on msubsup assert_approx_equals: left border expected 30 +/- 1 but got 0 -FAIL Border properties on msubsup (rtl) assert_approx_equals: left border expected 30 +/- 1 but got 0 -FAIL Border properties on msup assert_approx_equals: left border expected 30 +/- 1 but got 0 -FAIL Border properties on msup (rtl) assert_approx_equals: left border expected 30 +/- 1 but got 0 +PASS Border properties on msub +PASS Border properties on msub (rtl) +PASS Border properties on msubsup +PASS Border properties on msubsup (rtl) +PASS Border properties on msup +PASS Border properties on msup (rtl) PASS Border properties on mtable PASS Border properties on mtable (rtl) PASS Border properties on mtext PASS Border properties on mtext (rtl) -FAIL Border properties on munder assert_approx_equals: left border expected 30 +/- 1 but got 0 -FAIL Border properties on munder (rtl) assert_approx_equals: left border expected 30 +/- 1 but got 0 -FAIL Border properties on munderover assert_approx_equals: left border expected 30 +/- 1 but got 0 -FAIL Border properties on munderover (rtl) assert_approx_equals: left border expected 30 +/- 1 but got 0 +PASS Border properties on munder +PASS Border properties on munder (rtl) +PASS Border properties on munderover +PASS Border properties on munderover (rtl) PASS Border properties on semantics PASS Border properties on semantics (rtl) diff --git a/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/margin-001-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/margin-001-expected.txt index f33793b7c88e2..9765d8ab3f189 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/margin-001-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/margin-001-expected.txt @@ -1,10 +1,10 @@ -FAIL Margin properties on mrow assert_approx_equals: top margin expected 40 +/- 1 but got 80 -FAIL Margin properties on mrow (rtl) assert_approx_equals: top margin expected 40 +/- 1 but got 80 -FAIL Margin properties on mrow (shorthand) assert_approx_equals: top margin expected 20 +/- 1 but got 40 -FAIL Margin properties on mrow (logical) assert_approx_equals: top margin expected 40 +/- 1 but got 80 -FAIL Margin properties on mrow (logical, rtl) assert_approx_equals: top margin expected 40 +/- 1 but got 80 -FAIL Margin properties on mrow (logical, shorthand) assert_approx_equals: top margin expected 30 +/- 1 but got 60 +PASS Margin properties on mrow +PASS Margin properties on mrow (rtl) +PASS Margin properties on mrow (shorthand) +PASS Margin properties on mrow (logical) +PASS Margin properties on mrow (logical, rtl) +PASS Margin properties on mrow (logical, shorthand) diff --git a/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/margin-002-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/margin-002-expected.txt index fd47a6ef2ad38..fd795ff531229 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/margin-002-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/margin-002-expected.txt @@ -1,75 +1,75 @@ -FAIL Margin properties on maction assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on maction (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on maction (no margin-collapsing) assert_approx_equals: top margin expected 100 +/- 1 but got 200 -FAIL Margin properties on menclose assert_approx_equals: left margin expected 30 +/- 1 but got 0 -FAIL Margin properties on menclose (rtl) assert_approx_equals: left margin expected 30 +/- 1 but got 0 -FAIL Margin properties on menclose (no margin-collapsing) assert_approx_equals: left margin expected 60 +/- 1 but got 30 -FAIL Margin properties on merror assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on merror (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on merror (no margin-collapsing) assert_approx_equals: top margin expected 100 +/- 1 but got 200 -FAIL Margin properties on mfrac assert_approx_equals: left margin expected 30 +/- 1 but got 0 -FAIL Margin properties on mfrac (rtl) assert_approx_equals: left margin expected 30 +/- 1 but got 0 -FAIL Margin properties on mfrac (no margin-collapsing) assert_approx_equals: left margin expected 60 +/- 1 but got 30 -FAIL Margin properties on mi assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on mi (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on mi (no margin-collapsing) assert_approx_equals: top margin expected 100 +/- 1 but got 200 -FAIL Margin properties on mmultiscripts assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on mmultiscripts (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on mmultiscripts (no margin-collapsing) assert_approx_equals: bottom margin expected 120 +/- 1 but got 10 -FAIL Margin properties on mn assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on mn (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on mn (no margin-collapsing) assert_approx_equals: top margin expected 100 +/- 1 but got 200 -FAIL Margin properties on mo assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on mo (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on mo (no margin-collapsing) assert_approx_equals: top margin expected 100 +/- 1 but got 200 -FAIL Margin properties on mover assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on mover (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on mover (no margin-collapsing) assert_approx_equals: bottom margin expected 120 +/- 1 but got 10 -FAIL Margin properties on mpadded assert_approx_equals: left margin expected 30 +/- 1 but got 0 -FAIL Margin properties on mpadded (rtl) assert_approx_equals: left margin expected 30 +/- 1 but got 0 -FAIL Margin properties on mpadded (no margin-collapsing) assert_approx_equals: left margin expected 60 +/- 1 but got 30 -FAIL Margin properties on mphantom assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on mphantom (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on mphantom (no margin-collapsing) assert_approx_equals: top margin expected 100 +/- 1 but got 200 -FAIL Margin properties on mroot assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on mroot (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on mroot (no margin-collapsing) assert_approx_equals: bottom margin expected 120 +/- 1 but got 10 -FAIL Margin properties on mrow assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on mrow (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on mrow (no margin-collapsing) assert_approx_equals: top margin expected 100 +/- 1 but got 200 -FAIL Margin properties on ms assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on ms (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on ms (no margin-collapsing) assert_approx_equals: top margin expected 100 +/- 1 but got 200 -FAIL Margin properties on mspace assert_approx_equals: left/right margin expected 70 +/- 1 but got 0 -FAIL Margin properties on msqrt assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on msqrt (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on msqrt (no margin-collapsing) assert_approx_equals: bottom margin expected 120 +/- 1 but got 10 -FAIL Margin properties on mstyle assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on mstyle (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on mstyle (no margin-collapsing) assert_approx_equals: top margin expected 100 +/- 1 but got 200 -FAIL Margin properties on msub assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on msub (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on msub (no margin-collapsing) assert_approx_equals: bottom margin expected 120 +/- 1 but got 10 -FAIL Margin properties on msubsup assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on msubsup (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on msubsup (no margin-collapsing) assert_approx_equals: bottom margin expected 120 +/- 1 but got 10 -FAIL Margin properties on msup assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on msup (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on msup (no margin-collapsing) assert_approx_equals: bottom margin expected 120 +/- 1 but got 10 +FAIL Margin properties on maction assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on maction (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on maction (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on menclose assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on menclose (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on menclose (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on merror assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on merror (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on merror (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on mfrac assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mfrac (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mfrac (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on mi assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mi (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mi (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on mmultiscripts assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mmultiscripts (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mmultiscripts (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on mn assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mn (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mn (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on mo assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mo (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mo (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on mover assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mover (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mover (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on mpadded assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mpadded (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mpadded (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on mphantom assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mphantom (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mphantom (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on mroot assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mroot (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mroot (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on mrow assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mrow (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mrow (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on ms assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on ms (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on ms (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on mspace assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on msqrt assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on msqrt (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on msqrt (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on mstyle assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mstyle (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mstyle (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on msub assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on msub (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on msub (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on msubsup assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on msubsup (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on msubsup (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on msup assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on msup (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on msup (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 PASS Margin properties on mtable PASS Margin properties on mtable (rtl) -FAIL Margin properties on mtable (no margin-collapsing) assert_approx_equals: top margin expected 100 +/- 1 but got 150 -FAIL Margin properties on mtext assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on mtext (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on mtext (no margin-collapsing) assert_approx_equals: top margin expected 100 +/- 1 but got 200 -FAIL Margin properties on munder assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on munder (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on munder (no margin-collapsing) assert_approx_equals: bottom margin expected 120 +/- 1 but got 10 -FAIL Margin properties on munderover assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on munderover (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 0 -FAIL Margin properties on munderover (no margin-collapsing) assert_approx_equals: bottom margin expected 120 +/- 1 but got 10 -FAIL Margin properties on semantics assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on semantics (rtl) assert_approx_equals: top margin expected 50 +/- 1 but got 100 -FAIL Margin properties on semantics (no margin-collapsing) assert_approx_equals: top margin expected 100 +/- 1 but got 200 +FAIL Margin properties on mtable (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 70 +FAIL Margin properties on mtext assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mtext (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on mtext (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on munder assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on munder (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on munder (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on munderover assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on munderover (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on munderover (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 +FAIL Margin properties on semantics assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on semantics (rtl) assert_approx_equals: preferred width expected 70 +/- 1 but got 0 +FAIL Margin properties on semantics (no margin-collapsing) assert_approx_equals: preferred width expected 140 +/- 1 but got 0 diff --git a/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/margin-003-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/margin-003-expected.txt index 01872e3c429b8..2b220a486ec33 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/margin-003-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/margin-003-expected.txt @@ -1,20 +1,20 @@ -FAIL Margin properties on the children of menclose assert_approx_equals: inline size expected 56.15625 +/- 1 but got 31.15625 -FAIL Margin properties on the children of merror assert_approx_equals: inline size expected 47 +/- 1 but got 22 -FAIL Margin properties on the children of mfrac assert_approx_equals: inline size expected 45 +/- 1 but got 20 -FAIL Margin properties on the children of mmultiscripts assert_approx_equals: inline size expected 90.625 +/- 1 but got 40.625 -FAIL Margin properties on the children of mover assert_approx_equals: inline size expected 45 +/- 1 but got 20 -FAIL Margin properties on the children of mpadded assert_approx_equals: inline size expected 45 +/- 1 but got 20 -FAIL Margin properties on the children of mphantom assert_approx_equals: inline size expected 45 +/- 1 but got 20 -FAIL Margin properties on the children of mroot assert_approx_equals: inline size expected 105.0625 +/- 1 but got 55.0625 -FAIL Margin properties on the children of mrow assert_approx_equals: inline size expected 45 +/- 1 but got 20 -FAIL Margin properties on the children of msqrt assert_approx_equals: inline size expected 64.390625 +/- 1 but got 39.390625 -FAIL Margin properties on the children of mstyle assert_approx_equals: inline size expected 45 +/- 1 but got 20 -FAIL Margin properties on the children of msub assert_approx_equals: inline size expected 90.625 +/- 1 but got 40.625 -FAIL Margin properties on the children of msubsup assert_approx_equals: inline size expected 90.625 +/- 1 but got 40.625 -FAIL Margin properties on the children of msup assert_approx_equals: inline size expected 90.625 +/- 1 but got 40.625 -FAIL Margin properties on the children of munder assert_approx_equals: inline size expected 45 +/- 1 but got 20 -FAIL Margin properties on the children of munderover assert_approx_equals: inline size expected 45 +/- 1 but got 20 +FAIL Margin properties on the children of menclose assert_approx_equals: preferred width expected 58 +/- 1 but got 33 +FAIL Margin properties on the children of merror assert_approx_equals: preferred width expected 49 +/- 1 but got 24 +FAIL Margin properties on the children of mfrac assert_approx_equals: preferred width expected 49 +/- 1 but got 24 +FAIL Margin properties on the children of mmultiscripts assert_approx_equals: preferred width expected 93 +/- 1 but got 43 +FAIL Margin properties on the children of mover assert_approx_equals: preferred width expected 47 +/- 1 but got 22 +FAIL Margin properties on the children of mpadded assert_approx_equals: preferred width expected 47 +/- 1 but got 22 +FAIL Margin properties on the children of mphantom assert_approx_equals: preferred width expected 47 +/- 1 but got 22 +FAIL Margin properties on the children of mroot assert_approx_equals: inline size expected 105.0625 +/- 1 but got 80.0625 +FAIL Margin properties on the children of mrow assert_approx_equals: preferred width expected 47 +/- 1 but got 22 +FAIL Margin properties on the children of msqrt assert_approx_equals: preferred width expected 66 +/- 1 but got 41 +FAIL Margin properties on the children of mstyle assert_approx_equals: preferred width expected 47 +/- 1 but got 22 +FAIL Margin properties on the children of msub assert_approx_equals: preferred width expected 93 +/- 1 but got 43 +FAIL Margin properties on the children of msubsup assert_approx_equals: preferred width expected 93 +/- 1 but got 43 +FAIL Margin properties on the children of msup assert_approx_equals: preferred width expected 93 +/- 1 but got 43 +FAIL Margin properties on the children of munder assert_approx_equals: preferred width expected 47 +/- 1 but got 22 +FAIL Margin properties on the children of munderover assert_approx_equals: preferred width expected 47 +/- 1 but got 22 diff --git a/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/padding-002-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/padding-002-expected.txt index 172ba71695fcb..7371693571ff1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/padding-002-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/padding-002-expected.txt @@ -1,51 +1,51 @@ PASS Padding properties on maction PASS Padding properties on maction (rtl) -FAIL Padding properties on menclose assert_approx_equals: bottom padding expected 60 +/- 1 but got -4.1875 -FAIL Padding properties on menclose (rtl) assert_approx_equals: bottom padding expected 60 +/- 1 but got -4.1875 +PASS Padding properties on menclose +PASS Padding properties on menclose (rtl) PASS Padding properties on merror PASS Padding properties on merror (rtl) -FAIL Padding properties on mfrac assert_approx_equals: left padding expected 30 +/- 1 but got 0 -FAIL Padding properties on mfrac (rtl) assert_approx_equals: left padding expected 30 +/- 1 but got 0 +PASS Padding properties on mfrac +PASS Padding properties on mfrac (rtl) FAIL Padding properties on mi assert_approx_equals: right padding expected 40 +/- 1 but got 38.406253814697266 FAIL Padding properties on mi (rtl) assert_approx_equals: right padding expected 40 +/- 1 but got 38.406253814697266 -FAIL Padding properties on mmultiscripts assert_approx_equals: left padding expected 30 +/- 1 but got 0 -FAIL Padding properties on mmultiscripts (rtl) assert_approx_equals: left padding expected 30 +/- 1 but got 0 +PASS Padding properties on mmultiscripts +PASS Padding properties on mmultiscripts (rtl) PASS Padding properties on mn PASS Padding properties on mn (rtl) PASS Padding properties on mo PASS Padding properties on mo (rtl) -FAIL Padding properties on mover assert_approx_equals: left padding expected 30 +/- 1 but got 0 -FAIL Padding properties on mover (rtl) assert_approx_equals: left padding expected 30 +/- 1 but got 0 -FAIL Padding properties on mpadded assert_approx_equals: bottom padding expected 60 +/- 1 but got -0.21875 -FAIL Padding properties on mpadded (rtl) assert_approx_equals: bottom padding expected 60 +/- 1 but got -0.21875 +PASS Padding properties on mover +PASS Padding properties on mover +PASS Padding properties on mpadded +PASS Padding properties on mpadded (rtl) PASS Padding properties on mphantom PASS Padding properties on mphantom (rtl) -FAIL Padding properties on mroot assert_approx_equals: left padding expected 30 +/- 1 but got 0 -FAIL Padding properties on mroot (rtl) assert_approx_equals: left padding expected 30 +/- 1 but got 0 +PASS Padding properties on mroot +PASS Padding properties on mroot (rtl) PASS Padding properties on mrow PASS Padding properties on mrow (rtl) PASS Padding properties on ms PASS Padding properties on ms (rtl) -FAIL Padding properties on mspace assert_approx_equals: left/right padding expected 70 +/- 1 but got 0 -FAIL Padding properties on msqrt assert_approx_equals: bottom padding expected 60 +/- 1 but got -10.71875 -FAIL Padding properties on msqrt (rtl) assert_approx_equals: bottom padding expected 60 +/- 1 but got -10.71875 +PASS Padding properties on mspace +PASS Padding properties on msqrt +PASS Padding properties on msqrt (rtl) PASS Padding properties on mstyle PASS Padding properties on mstyle (rtl) -FAIL Padding properties on msub assert_approx_equals: left padding expected 30 +/- 1 but got 0 -FAIL Padding properties on msub (rtl) assert_approx_equals: left padding expected 30 +/- 1 but got 0 -FAIL Padding properties on msubsup assert_approx_equals: left padding expected 30 +/- 1 but got 0 -FAIL Padding properties on msubsup (rtl) assert_approx_equals: left padding expected 30 +/- 1 but got 0 -FAIL Padding properties on msup assert_approx_equals: left padding expected 30 +/- 1 but got 0 -FAIL Padding properties on msup (rtl) assert_approx_equals: left padding expected 30 +/- 1 but got 0 +PASS Padding properties on msub +PASS Padding properties on msub (rtl) +PASS Padding properties on msubsup +PASS Padding properties on msubsup (rtl) +PASS Padding properties on msup +PASS Padding properties on msup (rtl) PASS Padding properties on mtable PASS Padding properties on mtable (rtl) PASS Padding properties on mtext PASS Padding properties on mtext (rtl) -FAIL Padding properties on munder assert_approx_equals: left padding expected 30 +/- 1 but got 0 -FAIL Padding properties on munder (rtl) assert_approx_equals: left padding expected 30 +/- 1 but got 0 -FAIL Padding properties on munderover assert_approx_equals: left padding expected 30 +/- 1 but got 0 -FAIL Padding properties on munderover (rtl) assert_approx_equals: left padding expected 30 +/- 1 but got 0 +PASS Padding properties on munder +PASS Padding properties on munder (rtl) +PASS Padding properties on munderover +PASS Padding properties on munderover (rtl) PASS Padding properties on semantics PASS Padding properties on semantics (rtl) diff --git a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigate-event/intercept-detach-multiple-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigate-event/intercept-detach-multiple-expected.txt index 35ea968bf7cad..8511256f901da 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigate-event/intercept-detach-multiple-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigate-event/intercept-detach-multiple-expected.txt @@ -1,3 +1,3 @@ -FAIL event.intercept() throws if used on an event from a detached iframe assert_true: expected true got false +PASS event.intercept() throws if used on an event from a detached iframe diff --git a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigate-event/signal-abort-window-stop-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigate-event/signal-abort-window-stop-expected.txt index cadbe355e3cb3..ecff97a618ce6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigate-event/signal-abort-window-stop-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigate-event/signal-abort-window-stop-expected.txt @@ -1,3 +1,3 @@ -FAIL window.stop() signals event.signal assert_true: expected true got false +PASS window.stop() signals event.signal diff --git a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-204-205-download-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-204-205-download-expected.txt index 6eaafcb42d44d..33109532fba68 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-204-205-download-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-204-205-download-expected.txt @@ -1,5 +1,5 @@ -FAIL navigate() promises to 204s never settle assert_equals: expected 1 but got 2 -FAIL navigate() promises to 205s never settle assert_equals: expected 1 but got 2 -FAIL navigate() promises to Content-Disposition: attachment responses never settle assert_equals: expected 1 but got 2 +FAIL navigate() promises to 204s never settle assert_equals: expected "http://localhost:8800/common/blank.html" but got "http://localhost:8800/common/blank.html?pipe=status(204)" +FAIL navigate() promises to 205s never settle assert_equals: expected "http://localhost:8800/common/blank.html" but got "http://localhost:8800/common/blank.html?pipe=status(205)" +PASS navigate() promises to Content-Disposition: attachment responses never settle diff --git a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-file-url-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-file-url-expected.txt index c5d16e7e2e57b..f85f794614ce5 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-file-url-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-file-url-expected.txt @@ -1,6 +1,4 @@ CONSOLE MESSAGE: Not allowed to load local resource: / -Harness Error (TIMEOUT), message = null - -TIMEOUT navigate() to a file: URL Test timed out +PASS navigate() to a file: URL diff --git a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-pagehide-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-pagehide-expected.txt index 3140174613ab5..4e6e1e176fbb1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-pagehide-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-pagehide-expected.txt @@ -1,6 +1,11 @@ +CONSOLE MESSAGE: Unhandled Promise Rejection: Error: assert_throws_dom: function "() => { throw committedReason; }" threw object "AbortError: Navigation aborted" that is not a DOMException InvalidStateError: property "code" is equal to 20, expect... -Harness Error (TIMEOUT), message = null +Harness Error (FAIL), message = Unhandled rejection: assert_throws_dom: function "() => { throw committedReason; }" threw object "AbortError: Navigation aborted" that is not a DOMException InvalidStateError: property "code" is equal to 20, expected 11 + +TIMEOUT navigate() inside onpagehide Test timed out + +Harness Error (FAIL), message = Unhandled rejection: assert_throws_dom: function "() => { throw committedReason; }" threw object "AbortError: Navigation aborted" that is not a DOMException InvalidStateError: property "code" is equal to 20, expected 11 TIMEOUT navigate() inside onpagehide Test timed out diff --git a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-pagehide-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-pagehide-expected.txt index bdda83ecb97e0..b5ac032ad7355 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-pagehide-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-pagehide-expected.txt @@ -1,11 +1,4 @@ -CONSOLE MESSAGE: Unhandled Promise Rejection: Error: assert_equals: expected 1 but got 2 -Harness Error (FAIL), message = Unhandled rejection: assert_equals: expected 1 but got 2 - -TIMEOUT navigate() with an invalid URL inside onpagehide throws "SyntaxError", not "InvalidStateError" Test timed out - -Harness Error (FAIL), message = Unhandled rejection: assert_equals: expected 1 but got 2 - -TIMEOUT navigate() with an invalid URL inside onpagehide throws "SyntaxError", not "InvalidStateError" Test timed out +PASS navigate() with an invalid URL inside onpagehide throws "SyntaxError", not "InvalidStateError" diff --git a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-pagehide-unserializablestate-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-pagehide-unserializablestate-expected.txt index 414dc687d51b0..4bd33baeac377 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-pagehide-unserializablestate-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-pagehide-unserializablestate-expected.txt @@ -1,11 +1,4 @@ -CONSOLE MESSAGE: Unhandled Promise Rejection: Error: assert_equals: expected 1 but got 2 -Harness Error (FAIL), message = Unhandled rejection: assert_equals: expected 1 but got 2 - -TIMEOUT navigate() with an unserializable state inside onpagehide throws "DataCloneError", not "InvalidStateError" Test timed out - -Harness Error (FAIL), message = Unhandled rejection: assert_equals: expected 1 but got 2 - -TIMEOUT navigate() with an unserializable state inside onpagehide throws "DataCloneError", not "InvalidStateError" Test timed out +PASS navigate() with an unserializable state inside onpagehide throws "DataCloneError", not "InvalidStateError" diff --git a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/ordering-and-transition/navigate-cross-document-double-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/ordering-and-transition/navigate-cross-document-double-expected.txt index 01f6a6613912b..2236f9b982e44 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/ordering-and-transition/navigate-cross-document-double-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/ordering-and-transition/navigate-cross-document-double-expected.txt @@ -1,4 +1,4 @@ -FAIL event and promise ordering when navigate() is called to a cross-document destination, interrupting another navigate() to a cross-document destination assert_array_equals: expected property 2 to be "navigateerror" but got "navigate" (expected array ["navigate", "AbortSignal abort", "navigateerror", "navigate", "committed rejected 1", "finished rejected 1", "promise microtask"] got ["navigate", "AbortSignal abort", "navigate", "navigate", "AbortSignal abort", "navigate", "promise microtask"]) +FAIL event and promise ordering when navigate() is called to a cross-document destination, interrupting another navigate() to a cross-document destination assert_array_equals: lengths differ, expected array ["navigate", "AbortSignal abort", "navigateerror", "navigate", "committed rejected 1", "finished rejected 1", "promise microtask"] length 7, got ["navigate", "AbortSignal abort", "navigate", "promise microtask"] length 4 diff --git a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/ordering-and-transition/navigate-cross-document-event-order-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/ordering-and-transition/navigate-cross-document-event-order-expected.txt index 39a1e1f175077..9fc8ae41f2eca 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/navigation-api/ordering-and-transition/navigate-cross-document-event-order-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/navigation-api/ordering-and-transition/navigate-cross-document-event-order-expected.txt @@ -1,4 +1,4 @@ -FAIL navigate() event ordering for cross-document navigation assert_array_equals: lengths differ, expected array ["onnavigate", "onunload", "readystateinteractive", "domcontentloaded", "readystatecomplete", "onload", "onpageshow"] length 7, got ["onnavigate", "onnavigate", "onunload", "readystateinteractive", "domcontentloaded", "readystatecomplete", "onload", "onpageshow"] length 8 +PASS navigate() event ordering for cross-document navigation diff --git a/LayoutTests/imported/w3c/web-platform-tests/svg/path/property/getComputedStyle-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/svg/path/property/getComputedStyle-expected.txt index f6fe2437d97ce..446420161ef81 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/svg/path/property/getComputedStyle-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/svg/path/property/getComputedStyle-expected.txt @@ -1,7 +1,7 @@ PASS d property of g0 should be none. PASS d property of p1 should be none. -FAIL d property of p2 should be path("M 10 2 H 20"). assert_equals: expected "path(\"M 10 2 H 20\")" but got "none" +PASS d property of p2 should be path("M 10 2 H 20"). PASS d property of p3 should be path("M 10 3 H 30"). PASS d property of p4 should be path("M 10 4 H 40"). PASS d property of g5 should be path("M 10 5 H 50"). diff --git a/LayoutTests/imported/w3c/web-platform-tests/svg/path/property/test_style_flush_on_dom_api_with_d_property-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/svg/path/property/test_style_flush_on_dom_api_with_d_property-expected.txt index c6a491c7b7170..39de3a4e2e0ff 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/svg/path/property/test_style_flush_on_dom_api_with_d_property-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/svg/path/property/test_style_flush_on_dom_api_with_d_property-expected.txt @@ -1,6 +1,6 @@ -FAIL getTotalLength() with d property assert_equals: the total length expected 10 but got 0 -FAIL getPointAtLength() with d property assert_equals: x-axis position expected 10 but got 0 -FAIL isPointInFill() with d property assert_equals: expected true but got false -FAIL isPointInStroke() with d property assert_equals: expected true but got false +PASS getTotalLength() with d property +PASS getPointAtLength() with d property +PASS isPointInFill() with d property +PASS isPointInStroke() with d property diff --git a/LayoutTests/imported/w3c/web-platform-tests/svg/styling/presentation-attributes-relevant-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/svg/styling/presentation-attributes-relevant-expected.txt index 326aa27e8c09c..38cdc1bc224bd 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/svg/styling/presentation-attributes-relevant-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/svg/styling/presentation-attributes-relevant-expected.txt @@ -11,7 +11,7 @@ PASS cx presentation attribute supported on a relevant element PASS cy presentation attribute supported on a relevant element PASS direction presentation attribute supported on a relevant element PASS display presentation attribute supported on a relevant element -FAIL d presentation attribute supported on a relevant element assert_true: Presentation attribute d="M0,0 L1,1" should be supported on path element expected true got false +PASS d presentation attribute supported on a relevant element PASS dominant-baseline presentation attribute supported on a relevant element PASS fill presentation attribute supported on a relevant element PASS fill-opacity presentation attribute supported on a relevant element diff --git a/LayoutTests/imported/w3c/web-platform-tests/svg/styling/presentation-attributes-special-cases-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/svg/styling/presentation-attributes-special-cases-expected.txt index 255d781a3ac79..a8a633b4fc92d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/svg/styling/presentation-attributes-special-cases-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/svg/styling/presentation-attributes-special-cases-expected.txt @@ -10,7 +10,7 @@ PASS x, y, width, and height presentation attributes supported on use element PASS r presentation attribute supported on circle element PASS rx and ry presentation attributes supported on ellipse element PASS rx and ry presentation attributes supported on rect element -FAIL d presentation attribute supported on path element assert_true: Presentation attribute d="M0,0 L1,1" should be supported on path element expected true got false +PASS d presentation attribute supported on path element FAIL cx and cy presentation attributes not supported on other elements assert_false: Presentation attribute cx="1" should not be supported on g element expected false got true FAIL x, y, width, and height presentation attributes not supported on other elements assert_false: Presentation attribute x="1" should not be supported on g element expected false got true FAIL r presentation attribute not supported on other elements assert_false: Presentation attribute r="1" should not be supported on g element expected false got true diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/certs/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/certs/w3c-import.log new file mode 100644 index 0000000000000..d8719114f1f23 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/certs/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/certs/README.md +/LayoutTests/imported/w3c/web-platform-tests/tools/certs/cacert.key +/LayoutTests/imported/w3c/web-platform-tests/tools/certs/cacert.pem +/LayoutTests/imported/w3c/web-platform-tests/tools/certs/config.json +/LayoutTests/imported/w3c/web-platform-tests/tools/certs/web-platform.test.key +/LayoutTests/imported/w3c/web-platform-tests/tools/certs/web-platform.test.pem diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/install_chrome.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/install_chrome.yml index 7599321be2487..2dde99286cb84 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/install_chrome.yml +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/install_chrome.yml @@ -1,11 +1,11 @@ steps: # The conflicting google-chrome and chromedriver casks are first uninstalled. -# The raw google-chrome-dev cask URL is used to bypass caching. +# The raw google-chrome@dev.rb cask URL is used to bypass caching. - script: | set -eux -o pipefail HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --cask google-chrome || true HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --cask chromedriver || true - curl https://raw.githubusercontent.com/Homebrew/homebrew-cask-versions/master/Casks/google-chrome-dev.rb > google-chrome-dev.rb - HOMEBREW_NO_AUTO_UPDATE=1 brew install --cask google-chrome-dev.rb + curl https://raw.githubusercontent.com/Homebrew/homebrew-cask/HEAD/Casks/g/google-chrome@dev.rb > google-chrome@dev.rb + HOMEBREW_NO_AUTO_UPDATE=1 brew install --cask google-chrome@dev.rb displayName: 'Install Chrome Dev' condition: and(succeeded(), eq(variables['Agent.OS'], 'Darwin')) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/install_firefox.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/install_firefox.yml index 73af59766553f..d43e28b274f1c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/install_firefox.yml +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/install_firefox.yml @@ -1,9 +1,8 @@ steps: -# This is equivalent to `Homebrew/homebrew-cask-versions/firefox-nightly`, -# but the raw URL is used to bypass caching. +# The raw firefox@nightly.rb cask URL is used to bypass caching. - script: | set -eux -o pipefail - curl https://raw.githubusercontent.com/Homebrew/homebrew-cask-versions/master/Casks/firefox-nightly.rb > firefox-nightly.rb - HOMEBREW_NO_AUTO_UPDATE=1 brew install --cask firefox-nightly.rb + curl https://raw.githubusercontent.com/Homebrew/homebrew-cask/HEAD/Casks/f/firefox@nightly.rb > firefox@nightly.rb + HOMEBREW_NO_AUTO_UPDATE=1 brew install --cask firefox@nightly.rb displayName: 'Install Firefox Nightly' condition: and(succeeded(), eq(variables['Agent.OS'], 'Darwin')) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/w3c-import.log new file mode 100644 index 0000000000000..83394c43440e0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/w3c-import.log @@ -0,0 +1,35 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/README.md +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/affected_tests.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/checkout.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/color_profile.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/com.apple.SafariTechnologyPreview.plist +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/fyi_hook.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/install_certs.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/install_chrome.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/install_edge.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/install_firefox.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/install_fonts.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/install_safari.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/pip_install.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/publish_logs.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/sysdiagnose.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/system_info.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/tox_pytest.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/update_hosts.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/azure/update_manifest.yml diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/jobs.py b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/jobs.py index 44de9fe1ad078..fe8eaae069eb3 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/jobs.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/jobs.py @@ -23,7 +23,7 @@ ] # Rules are just regex on the path, with a leading ! indicating a regex that must not -# match for the job. Paths should be kept in sync with update-built-tests.sh. +# match for the job. Paths should be kept in sync with scripts in update_built.py. job_path_map = { "affected_tests": [".*/.*", "!resources/(?!idlharness.js)"] + EXCLUDES, "stability": [".*/.*", "!resources/.*"] + EXCLUDES, @@ -32,10 +32,11 @@ "resources_unittest": ["resources/", "tools/"], "tools_unittest": ["tools/"], "wptrunner_unittest": ["tools/"], - "update_built": ["update-built-tests\\.sh", - "conformance-checkers/", + "update_built": ["conformance-checkers/", + "css/css-images/", "css/css-ui/", "css/css-writing-modes/", + "fetch/metadata/", "html/", "infrastructure/", "mimesniff/"], diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/manifest_build.py b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/manifest_build.py index 6d93a35258398..15b8679f00942 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/manifest_build.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/manifest_build.py @@ -9,6 +9,9 @@ import requests +from pathlib import Path + + here = os.path.abspath(os.path.dirname(__file__)) wpt_root = os.path.abspath(os.path.join(here, os.pardir, os.pardir)) @@ -39,6 +42,10 @@ def create_manifest(path): run(["./wpt", "manifest", "-p", path]) +def create_web_feature_manifest(path): + run(["./wpt", "web-features-manifest", "-p", path]) + + def compress_manifest(path): for args in [["gzip", "-k", "-f", "--best"], ["bzip2", "-k", "-f", "--best"], @@ -108,7 +115,20 @@ def get_pr(owner, repo, sha): return pr["number"] -def create_release(manifest_path, owner, repo, sha, tag, body): +def get_file_upload_details(manifest_path, sha): + """ + For a given file, generate details used to upload to GitHub. + """ + path = Path(manifest_path) + stem = path.stem + extension = path.suffix + upload_filename_prefix = f"{stem}-{sha}{extension}" + upload_label_prefix = path.name + upload_desc = f"{stem.title()} upload" + return upload_filename_prefix, upload_label_prefix, upload_desc + + +def create_release(manifest_file_paths, owner, repo, sha, tag, body): logger.info(f"Creating a release for tag='{tag}', target_commitish='{sha}'") create_url = f"https://api.github.com/repos/{owner}/{repo}/releases" create_data = {"tag_name": tag, @@ -124,20 +144,22 @@ def create_release(manifest_path, owner, repo, sha, tag, body): upload_url = create_resp["upload_url"].split("{", 1)[0] upload_exts = [".gz", ".bz2", ".zst"] - for upload_ext in upload_exts: - upload_filename = f"MANIFEST-{sha}.json{upload_ext}" - params = {"name": upload_filename, - "label": "MANIFEST.json%s" % upload_ext} + for manifest_path in manifest_file_paths: + upload_filename_prefix, upload_label_prefix, upload_desc = get_file_upload_details(manifest_path, sha) + for upload_ext in upload_exts: + upload_filename = f"{upload_filename_prefix}{upload_ext}" + params = {"name": upload_filename, + "label": f"{upload_label_prefix}{upload_ext}"} - with open(f"{manifest_path}{upload_ext}", "rb") as f: - upload_data = f.read() + with open(f"{manifest_path}{upload_ext}", "rb") as f: + upload_data = f.read() - logger.info("Uploading %s bytes" % len(upload_data)) + logger.info("Uploading %s bytes" % len(upload_data)) - upload_resp = request(upload_url, "Manifest upload", data=upload_data, params=params, - headers={'Content-Type': 'application/octet-stream'}) - if not upload_resp: - return False + upload_resp = request(upload_url, upload_desc, data=upload_data, params=params, + headers={'Content-Type': 'application/octet-stream'}) + if not upload_resp: + return False release_id = create_resp["id"] edit_url = f"https://api.github.com/repos/{owner}/{repo}/releases/{release_id}" @@ -169,10 +191,13 @@ def main(): dry_run = should_dry_run() manifest_path = os.path.join(tempfile.mkdtemp(), "MANIFEST.json") + web_features_manifest_path = os.path.join(tempfile.mkdtemp(), "WEB_FEATURES_MANIFEST.json") create_manifest(manifest_path) + create_web_feature_manifest(web_features_manifest_path) compress_manifest(manifest_path) + compress_manifest(web_features_manifest_path) owner, repo = os.environ["GITHUB_REPOSITORY"].split("/", 1) @@ -188,7 +213,8 @@ def main(): return Status.FAIL tag_name = "merge_pr_%s" % pr - if not create_release(manifest_path, owner, repo, head_rev, tag_name, body): + manifest_paths = [manifest_path, web_features_manifest_path] + if not create_release(manifest_paths, owner, repo, head_rev, tag_name, body): return Status.FAIL return Status.SUCCESS diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/requirements_build.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/requirements_build.txt index e0443762c3c17..e74e4e5302de9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/requirements_build.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/requirements_build.txt @@ -1,5 +1,5 @@ -cairocffi==1.6.1 -fonttools==4.39.4 +cairocffi==1.7.0 +fonttools==4.51.0 genshi==0.7.7 -jinja2==3.1.2 +jinja2==3.1.4 pyyaml==6.0.1 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/requirements_macos_color_profile.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/requirements_macos_color_profile.txt index 7505a98d9f3c2..cd3e785aa4796 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/requirements_macos_color_profile.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/requirements_macos_color_profile.txt @@ -1,4 +1,4 @@ -pyobjc-core==9.2 +pyobjc-core==10.3.1 pyobjc-framework-Cocoa==9.2 pyobjc-framework-ColorSync==9.2 pyobjc-framework-Quartz==9.2 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/requirements_tc.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/requirements_tc.txt index ce121e561f4a1..a9128c02eb93c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/requirements_tc.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/requirements_tc.txt @@ -1,4 +1,4 @@ -pygithub==2.1.1 +pygithub==2.3.0 pyyaml==6.0.1 -requests==2.31.0 -taskcluster==59.2.0 +requests==2.32.3 +taskcluster==64.2.8 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/run_tc.py b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/run_tc.py index b91825497d499..446afadac8886 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/run_tc.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/run_tc.py @@ -92,7 +92,7 @@ def get_parser(): help="Browsers that will be used in the job") p.add_argument("--channel", default=None, - choices=["experimental", "dev", "nightly", "beta", "stable"], + choices=["experimental", "canary", "dev", "nightly", "beta", "stable"], help="Chrome browser channel") p.add_argument("--xvfb", action="store_true", @@ -148,6 +148,9 @@ def start_dbus(): def install_chrome(channel): + if channel == "canary": + # Chrome for Testing Canary is installed via --install-browser + return if channel in ("experimental", "dev"): deb_archive = "google-chrome-unstable_current_amd64.deb" elif channel == "beta": diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/taskcluster-run.py b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/taskcluster-run.py index 2917d8f3402e2..fe9538e3168b0 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/taskcluster-run.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/taskcluster-run.py @@ -23,10 +23,7 @@ def get_browser_args(product, channel, artifact_path): return ["--install-browser", "--processes=12"] if product == "chrome" or product == "chromium": # Taskcluster machines do not have GPUs, so use software rendering via --enable-swiftshader. - args = ["--enable-swiftshader"] - if channel == "nightly": - args.extend(["--install-browser", "--install-webdriver"]) - return args + return ["--enable-swiftshader", "--install-browser", "--install-webdriver"] if product == "webkitgtk_minibrowser": # Using 4 parallel jobs gives 4x speed-up even on a 1-core machine and doesn't cause extra timeouts. # See: https://github.com/web-platform-tests/wpt/issues/38723#issuecomment-1470938179 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tasks/test.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tasks/test.yml index 79d510c671e2a..a9ca07c6cebb8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tasks/test.yml +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tasks/test.yml @@ -4,7 +4,7 @@ components: workerType: ci schedulerId: taskcluster-github deadline: "24 hours" - image: webplatformtests/wpt:0.56 + image: webplatformtests/wpt:0.57 maxRunTime: 7200 artifacts: public/results: @@ -115,25 +115,24 @@ components: chunks-override: testharness: 24 - tox-python3_7: + tox-python3_8: env: - TOXENV: py37 + TOXENV: py38 PY_COLORS: "0" install: - - python3.7 - - python3.7-distutils - - python3.7-dev - - python3.7-venv + - python3.8 + - python3.8-distutils + - python3.8-dev + - python3.8-venv - tox-python3_11: + tox-python3_12: env: - TOXENV: py311 + TOXENV: py312 PY_COLORS: "0" install: - - python3.11 - - python3.11-distutils - - python3.11-dev - - python3.11-venv + - python3.12 + - python3.12-dev + - python3.12-venv tests-affected: options: browser: @@ -186,6 +185,12 @@ tasks: use: - trigger-daily - trigger-push + - vars: + browser: chrome + channel: canary + use: + - trigger-master + - trigger-push - vars: browser: chrome channel: dev @@ -226,7 +231,7 @@ tasks: browser: servo channel: nightly use: - - trigger-weekly + - trigger-daily - trigger-push - vars: browser: firefox_android @@ -280,6 +285,12 @@ tasks: use: - trigger-daily - trigger-push + - vars: + browser: chrome + channel: canary + use: + - trigger-master + - trigger-push - vars: browser: chrome channel: dev @@ -426,13 +437,13 @@ tasks: - update_built command: "./tools/ci/ci_built_diff.sh" - - tools/ unittests (Python 3.7): + - tools/ unittests (Python 3.8): description: >- - Unit tests for tools running under Python 3.7, excluding wptrunner + Unit tests for tools running under Python 3.8, excluding wptrunner use: - wpt-base - trigger-pr - - tox-python3_7 + - tox-python3_8 command: ./tools/ci/ci_tools_unittest.sh env: HYPOTHESIS_PROFILE: ci @@ -440,13 +451,13 @@ tasks: run-job: - tools_unittest - - tools/ unittests (Python 3.11): + - tools/ unittests (Python 3.12): description: >- - Unit tests for tools running under Python 3.11, excluding wptrunner + Unit tests for tools running under Python 3.12, excluding wptrunner use: - wpt-base - trigger-pr - - tox-python3_11 + - tox-python3_12 command: ./tools/ci/ci_tools_unittest.sh env: HYPOTHESIS_PROFILE: ci @@ -454,13 +465,13 @@ tasks: run-job: - tools_unittest - - tools/ integration tests (Python 3.7): + - tools/ integration tests (Python 3.8): description: >- - Integration tests for tools running under Python 3.7 + Integration tests for tools running under Python 3.8 use: - wpt-base - trigger-pr - - tox-python3_7 + - tox-python3_8 command: ./tools/ci/ci_tools_integration_test.sh install: - libnss3-tools @@ -476,13 +487,13 @@ tasks: run-job: - wpt_integration - - tools/ integration tests (Python 3.11): + - tools/ integration tests (Python 3.12): description: >- - Integration tests for tools running under Python 3.11 + Integration tests for tools running under Python 3.12 use: - wpt-base - trigger-pr - - tox-python3_11 + - tox-python3_12 command: ./tools/ci/ci_tools_integration_test.sh install: - libnss3-tools @@ -498,13 +509,13 @@ tasks: run-job: - wpt_integration - - resources/ tests (Python 3.7): + - resources/ tests (Python 3.8): description: >- - Tests for testharness.js and other files in resources/ under Python 3.7 + Tests for testharness.js and other files in resources/ under Python 3.8 use: - wpt-base - trigger-pr - - tox-python3_7 + - tox-python3_8 command: ./tools/ci/ci_resources_unittest.sh install: - libnss3-tools @@ -517,13 +528,13 @@ tasks: run-job: - resources_unittest - - resources/ tests (Python 3.11): + - resources/ tests (Python 3.12): description: >- - Tests for testharness.js and other files in resources/ under Python 3.11 + Tests for testharness.js and other files in resources/ under Python 3.12 use: - wpt-base - trigger-pr - - tox-python3_11 + - tox-python3_12 command: ./tools/ci/ci_resources_unittest.sh install: - libnss3-tools diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tasks/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tasks/w3c-import.log new file mode 100644 index 0000000000000..7d23c251b690e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tasks/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tasks/test.yml diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/testdata/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/testdata/w3c-import.log new file mode 100644 index 0000000000000..916efdc9e1ebb --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/testdata/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/testdata/epochs_daily_push_event.json +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/testdata/master_push_event.json +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/testdata/pr_event.json +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/testdata/pr_event_tests_affected.json diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tests/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tests/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tests/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tests/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tests/test_valid.py b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tests/test_valid.py index 3a4ed62c3a160..dd8d732654252 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tests/test_valid.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tests/test_valid.py @@ -137,6 +137,22 @@ def test_verify_payload(): 'wpt-firefox-nightly-testharness-14', 'wpt-firefox-nightly-testharness-15', 'wpt-firefox-nightly-testharness-16', + 'wpt-chrome-canary-testharness-1', + 'wpt-chrome-canary-testharness-2', + 'wpt-chrome-canary-testharness-3', + 'wpt-chrome-canary-testharness-4', + 'wpt-chrome-canary-testharness-5', + 'wpt-chrome-canary-testharness-6', + 'wpt-chrome-canary-testharness-7', + 'wpt-chrome-canary-testharness-8', + 'wpt-chrome-canary-testharness-9', + 'wpt-chrome-canary-testharness-10', + 'wpt-chrome-canary-testharness-11', + 'wpt-chrome-canary-testharness-12', + 'wpt-chrome-canary-testharness-13', + 'wpt-chrome-canary-testharness-14', + 'wpt-chrome-canary-testharness-15', + 'wpt-chrome-canary-testharness-16', 'wpt-chrome-dev-testharness-1', 'wpt-chrome-dev-testharness-2', 'wpt-chrome-dev-testharness-3', @@ -159,6 +175,12 @@ def test_verify_payload(): 'wpt-firefox-nightly-reftest-4', 'wpt-firefox-nightly-reftest-5', 'wpt-firefox-nightly-reftest-6', + 'wpt-chrome-canary-reftest-1', + 'wpt-chrome-canary-reftest-2', + 'wpt-chrome-canary-reftest-3', + 'wpt-chrome-canary-reftest-4', + 'wpt-chrome-canary-reftest-5', + 'wpt-chrome-canary-reftest-6', 'wpt-chrome-dev-reftest-1', 'wpt-chrome-dev-reftest-2', 'wpt-chrome-dev-reftest-3', @@ -167,21 +189,25 @@ def test_verify_payload(): 'wpt-chrome-dev-reftest-6', 'wpt-firefox-nightly-wdspec-1', 'wpt-firefox-nightly-wdspec-2', + 'wpt-chrome-canary-wdspec-1', + 'wpt-chrome-canary-wdspec-2', 'wpt-chrome-dev-wdspec-1', 'wpt-chrome-dev-wdspec-2', 'wpt-firefox-nightly-crashtest-1', + 'wpt-chrome-canary-crashtest-1', 'wpt-chrome-dev-crashtest-1', 'wpt-firefox-nightly-print-reftest-1', + 'wpt-chrome-canary-print-reftest-1', 'wpt-chrome-dev-print-reftest-1', 'lint']), ("pr_event.json", True, {".taskcluster.yml", ".travis.yml", "tools/ci/start.sh"}, ['lint', - 'tools/ unittests (Python 3.7)', - 'tools/ unittests (Python 3.11)', - 'tools/ integration tests (Python 3.7)', - 'tools/ integration tests (Python 3.11)', - 'resources/ tests (Python 3.7)', - 'resources/ tests (Python 3.11)', + 'tools/ unittests (Python 3.8)', + 'tools/ unittests (Python 3.12)', + 'tools/ integration tests (Python 3.8)', + 'tools/ integration tests (Python 3.12)', + 'resources/ tests (Python 3.8)', + 'resources/ tests (Python 3.12)', 'download-firefox-nightly', 'infrastructure/ tests', 'sink-task']), @@ -198,8 +224,8 @@ def test_verify_payload(): 'sink-task']), ("pr_event_tests_affected.json", True, {"resources/testharness.js"}, ['lint', - 'resources/ tests (Python 3.7)', - 'resources/ tests (Python 3.11)', + 'resources/ tests (Python 3.8)', + 'resources/ tests (Python 3.12)', 'download-firefox-nightly', 'infrastructure/ tests', 'sink-task']), @@ -269,6 +295,22 @@ def test_verify_payload(): 'wpt-webkitgtk_minibrowser-nightly-testharness-14', 'wpt-webkitgtk_minibrowser-nightly-testharness-15', 'wpt-webkitgtk_minibrowser-nightly-testharness-16', + 'wpt-servo-nightly-testharness-1', + 'wpt-servo-nightly-testharness-2', + 'wpt-servo-nightly-testharness-3', + 'wpt-servo-nightly-testharness-4', + 'wpt-servo-nightly-testharness-5', + 'wpt-servo-nightly-testharness-6', + 'wpt-servo-nightly-testharness-7', + 'wpt-servo-nightly-testharness-8', + 'wpt-servo-nightly-testharness-9', + 'wpt-servo-nightly-testharness-10', + 'wpt-servo-nightly-testharness-11', + 'wpt-servo-nightly-testharness-12', + 'wpt-servo-nightly-testharness-13', + 'wpt-servo-nightly-testharness-14', + 'wpt-servo-nightly-testharness-15', + 'wpt-servo-nightly-testharness-16', 'wpt-firefox_android-nightly-testharness-1', 'wpt-firefox_android-nightly-testharness-2', 'wpt-firefox_android-nightly-testharness-3', @@ -317,6 +359,12 @@ def test_verify_payload(): 'wpt-webkitgtk_minibrowser-nightly-reftest-4', 'wpt-webkitgtk_minibrowser-nightly-reftest-5', 'wpt-webkitgtk_minibrowser-nightly-reftest-6', + 'wpt-servo-nightly-reftest-1', + 'wpt-servo-nightly-reftest-2', + 'wpt-servo-nightly-reftest-3', + 'wpt-servo-nightly-reftest-4', + 'wpt-servo-nightly-reftest-5', + 'wpt-servo-nightly-reftest-6', 'wpt-firefox_android-nightly-reftest-1', 'wpt-firefox_android-nightly-reftest-2', 'wpt-firefox_android-nightly-reftest-3', @@ -331,12 +379,15 @@ def test_verify_payload(): 'wpt-chrome-stable-wdspec-2', 'wpt-webkitgtk_minibrowser-nightly-wdspec-1', 'wpt-webkitgtk_minibrowser-nightly-wdspec-2', + 'wpt-servo-nightly-wdspec-1', + 'wpt-servo-nightly-wdspec-2', 'wpt-firefox_android-nightly-wdspec-1', 'wpt-firefox_android-nightly-wdspec-2', 'wpt-firefox-stable-crashtest-1', 'wpt-chromium-nightly-crashtest-1', 'wpt-chrome-stable-crashtest-1', 'wpt-webkitgtk_minibrowser-nightly-crashtest-1', + 'wpt-servo-nightly-crashtest-1', 'wpt-firefox_android-nightly-crashtest-1', 'wpt-firefox-stable-print-reftest-1', 'wpt-chromium-nightly-print-reftest-1', diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tests/w3c-import.log new file mode 100644 index 0000000000000..4058efe2ddb50 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tests/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tests/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tests/test_decision.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tests/test_taskgraph.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/tests/test_valid.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/w3c-import.log new file mode 100644 index 0000000000000..b9234c6ad17ec --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/README.md +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/decision.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/download.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/github_checks_output.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/sink_task.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tc/taskgraph.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tests/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tests/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tests/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tests/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tests/w3c-import.log new file mode 100644 index 0000000000000..5b97cb0781def --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tests/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tests/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/tests/test_jobs.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/update_built.py b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/update_built.py index 8e0f18589de5c..929b09f9fe42d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/update_built.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/update_built.py @@ -9,6 +9,7 @@ wpt_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) +# These paths should be kept in sync with job_path_map in jobs.py. scripts = { "canvas": ["html/canvas/tools/gentest.py"], "conformance-checkers": ["conformance-checkers/tools/dl.py", diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/ci/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/w3c-import.log new file mode 100644 index 0000000000000..90cbeca92ac8f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/ci/w3c-import.log @@ -0,0 +1,37 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/ci_built_diff.sh +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/ci_resources_unittest.sh +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/ci_tools_integration_test.sh +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/ci_tools_unittest.sh +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/ci_wptrunner_infrastructure.sh +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/commands.json +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/epochs_update.sh +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/interfaces_update.sh +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/jobs.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/macos_color_profile.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/make_hosts_file.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/manifest_build.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/regen_certs.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/requirements_build.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/requirements_macos_color_profile.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/requirements_tc.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/run_tc.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/taskcluster-run.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/update_built.py +/LayoutTests/imported/w3c/web-platform-tests/tools/ci/website_build.sh diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/conftest.py index 8d1f585b0d8c3..56521819507c1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/conftest.py @@ -17,7 +17,7 @@ "default" if impl != "PyPy" else "pypy")) -def pytest_ignore_collect(collection_path, path, config): +def pytest_ignore_collect(collection_path, config): # ignore directories which have their own tox.ini assert collection_path != config.rootpath if (collection_path / "tox.ini").is_file(): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/docker/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/docker/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/docker/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/docker/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/docker/requirements.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/docker/requirements.txt index 332fdbdeb1b12..b275ccfd8f150 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/docker/requirements.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/docker/requirements.txt @@ -1,2 +1,2 @@ pyyaml==6.0.1 -requests==2.31.0 +requests==2.32.3 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/docker/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/docker/w3c-import.log new file mode 100644 index 0000000000000..8931127cd5a53 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/docker/w3c-import.log @@ -0,0 +1,25 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/docker/Dockerfile +/LayoutTests/imported/w3c/web-platform-tests/tools/docker/README.md +/LayoutTests/imported/w3c/web-platform-tests/tools/docker/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/docker/commands.json +/LayoutTests/imported/w3c/web-platform-tests/tools/docker/frontend.py +/LayoutTests/imported/w3c/web-platform-tests/tools/docker/requirements.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/docker/retry.py +/LayoutTests/imported/w3c/web-platform-tests/tools/docker/seccomp.json +/LayoutTests/imported/w3c/web-platform-tests/tools/docker/start.sh diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/tests/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/tests/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/tests/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/tests/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/tests/w3c-import.log new file mode 100644 index 0000000000000..c2b80402cffb2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/tests/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/tests/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/tests/test_gitignore.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/w3c-import.log new file mode 100644 index 0000000000000..3f5cce28ff796 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/gitignore/gitignore.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/lint/lint.py b/LayoutTests/imported/w3c/web-platform-tests/tools/lint/lint.py index cf164b6820f53..32bc7ece7b12f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/lint/lint.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/lint/lint.py @@ -394,10 +394,12 @@ def check_parsed(repo_root: Text, path: Text, f: IO[bytes]) -> List[rules.Error] if source_file.root is None: return [rules.ParseFailed.error(path)] - if source_file.type == "manual" and not source_file.name_is_manual: + test_type = source_file.type + + if test_type == "manual" and not source_file.name_is_manual: errors.append(rules.ContentManual.error(path)) - if source_file.type == "visual" and not source_file.name_is_visual: + if test_type == "visual" and not source_file.name_is_visual: errors.append(rules.ContentVisual.error(path)) about_blank_parts = urlsplit("about:blank") @@ -428,6 +430,10 @@ def check_parsed(repo_root: Text, path: Text, f: IO[bytes]) -> List[rules.Error] errors.append(rules.NonexistentRef.error(path, (reference_rel, href))) + if source_file.reftest_nodes: + if test_type not in ("print-reftest", "reftest"): + errors.append(rules.ReferenceInOtherType.error(path, (test_type,))) + if len(source_file.timeout_nodes) > 1: errors.append(rules.MultipleTimeout.error(path)) @@ -450,7 +456,6 @@ def check_parsed(repo_root: Text, path: Text, f: IO[bytes]) -> List[rules.Error] testharnessreport_nodes: List[ElementTree.Element] = [] if source_file.testharness_nodes: - test_type = source_file.manifest_items()[0] if test_type not in ("testharness", "manual"): errors.append(rules.TestharnessInOtherType.error(path, (test_type,))) if len(source_file.testharness_nodes) > 1: @@ -470,6 +475,9 @@ def check_parsed(repo_root: Text, path: Text, f: IO[bytes]) -> List[rules.Error] testdriver_vendor_nodes: List[ElementTree.Element] = [] if source_file.testdriver_nodes: + if test_type != "testharness": + errors.append(rules.TestdriverInUnsupportedType.error(path, (test_type,))) + if len(source_file.testdriver_nodes) > 1: errors.append(rules.MultipleTestdriver.error(path)) @@ -603,7 +611,7 @@ def check_global_metadata(value: bytes) -> Iterable[Tuple[Type[rules.Rule], Tupl def check_script_metadata(repo_root: Text, path: Text, f: IO[bytes]) -> List[rules.Error]: - if path.endswith((".worker.js", ".any.js")): + if path.endswith((".window.js", ".worker.js", ".any.js")): meta_re = js_meta_re broken_metadata = broken_js_metadata elif path.endswith(".py"): @@ -614,7 +622,7 @@ def check_script_metadata(repo_root: Text, path: Text, f: IO[bytes]) -> List[rul done = False errors = [] - for idx, line in enumerate(f): + for line_no, line in enumerate(f, 1): assert isinstance(line, bytes), line m = meta_re.match(line) @@ -622,29 +630,32 @@ def check_script_metadata(repo_root: Text, path: Text, f: IO[bytes]) -> List[rul key, value = m.groups() if key == b"global": for rule_class, context in check_global_metadata(value): - errors.append(rule_class.error(path, context, idx + 1)) + errors.append(rule_class.error(path, context, line_no)) elif key == b"timeout": if value != b"long": errors.append(rules.UnknownTimeoutMetadata.error(path, - line_no=idx + 1)) + line_no=line_no)) elif key == b"variant": if is_variant_malformed(value.decode()): value = f"{path} `META: variant=...` value" - errors.append(rules.MalformedVariant.error(path, (value,), idx + 1)) - elif key not in (b"title", b"script", b"quic"): - errors.append(rules.UnknownMetadata.error(path, - line_no=idx + 1)) + errors.append(rules.MalformedVariant.error(path, (value,), line_no)) + elif key == b"script": + if value == b"/resources/testharness.js": + errors.append(rules.MultipleTestharness.error(path, line_no=line_no)) + elif value == b"/resources/testharnessreport.js": + errors.append(rules.MultipleTestharnessReport.error(path, line_no=line_no)) + elif key not in (b"title", b"quic"): + errors.append(rules.UnknownMetadata.error(path, line_no=line_no)) else: done = True if done: if meta_re.match(line): - errors.append(rules.StrayMetadata.error(path, line_no=idx + 1)) + errors.append(rules.StrayMetadata.error(path, line_no=line_no)) elif meta_re.search(line): - errors.append(rules.IndentedMetadata.error(path, - line_no=idx + 1)) + errors.append(rules.IndentedMetadata.error(path, line_no=line_no)) elif broken_metadata.search(line): - errors.append(rules.BrokenMetadata.error(path, line_no=idx + 1)) + errors.append(rules.BrokenMetadata.error(path, line_no=line_no)) return errors diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/lint/rules.py b/LayoutTests/imported/w3c/web-platform-tests/tools/lint/rules.py index 471ca06a49a5b..8dba7817a9e56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/lint/rules.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/lint/rules.py @@ -156,8 +156,10 @@ class MultipleTestharness(Rule): name = "MULTIPLE-TESTHARNESS" description = "More than one ` + + +""" + error_map = check_with_files(code) + + for (filename, (errors, kind)) in error_map.items(): + check_errors(errors) + + if kind in ["web-lax", "web-strict"]: + assert errors == [ + ( + "NON-EXISTENT-REF", + "Reference test with a non-existent 'match' relationship reference: " + "'test-ref.html'", + filename, + None, + ), + ( + "TESTDRIVER-IN-UNSUPPORTED-TYPE", + "testdriver.js included in a reftest test, which doesn't support " + "testdriver.js", + filename, + None, + ), + ] + elif kind == "python": + assert errors == [ + ("PARSE-FAILED", "Unable to parse file", filename, 2), + ] + + def test_early_testdriver_vendor(): code = b""" + + @@ -669,6 +707,41 @@ def test_late_timeout(): ] +def test_reference_in_non_reference(): + code = b""" + + + + + +""" + error_map = check_with_files(code) + + for (filename, (errors, kind)) in error_map.items(): + check_errors(errors) + + if kind in ["web-lax", "web-strict"]: + assert errors == [ + ( + "NON-EXISTENT-REF", + "Reference test with a non-existent 'match' relationship reference: " + "'test-ref.html'", + filename, + None, + ), + ( + "REFERENCE-IN-OTHER-TYPE", + "Reference link included in a testharness test", + filename, + None, + ), + ] + elif kind == "python": + assert errors == [ + ("PARSE-FAILED", "Unable to parse file", filename, 2), + ] + + def test_print_function(): error_map = check_with_files(b"def foo():\n print('function')\n") @@ -975,6 +1048,8 @@ def test_css_missing_file_tentative(): (b"""// META: foobar\n""", (1, "BROKEN-METADATA")), (b"""// META: foo=bar\n""", (1, "UNKNOWN-METADATA")), (b"""// META: timeout=bar\n""", (1, "UNKNOWN-TIMEOUT-METADATA")), + (b"""// META: script=/resources/testharness.js""", (1, "MULTIPLE-TESTHARNESS")), + (b"""// META: script=/resources/testharnessreport.js""", (1, "MULTIPLE-TESTHARNESSREPORT")), ]) def test_script_metadata(filename, input, error): errors = check_file_contents("", filename, io.BytesIO(input)) @@ -991,6 +1066,10 @@ def test_script_metadata(filename, input, error): "MALFORMED-VARIANT": ( f"{filename} `META: variant=...` value must be a non empty " "string and start with '?' or '#'"), + "MULTIPLE-TESTHARNESS": ( + "More than one ` + +%(script)s +
+ +""" + + class AnyHtmlHandler(HtmlWrapperHandler): global_type = "window" path_replace = [(".any.html", ".any.js")] @@ -424,6 +438,17 @@ class ShadowRealmHandler(HtmlWrapperHandler): (async function() { const r = new ShadowRealm(); r.evaluate("globalThis.self = globalThis; undefined;"); + r.evaluate(`func => { + globalThis.fetch_json = (resource) => { + const thenMethod = func(resource); + return new Promise((resolve, reject) => thenMethod((s) => resolve(JSON.parse(s)), reject)); + }; + }`)((resource) => function (resolve, reject) { + fetch(resource).then(res => res.text(), String).then(resolve, reject); + }); + r.evaluate(`s => { + globalThis.location = { search: s }; + }`)(location.search); await new Promise(r.evaluate(` (resolve, reject) => { (async () => { @@ -566,6 +591,7 @@ def add_mount_point(self, url_base, path): ("GET", "*.any.serviceworker.html", ServiceWorkersHandler), ("GET", "*.any.serviceworker-module.html", ServiceWorkerModulesHandler), ("GET", "*.any.shadowrealm.html", ShadowRealmHandler), + ("GET", "*.any.window-module.html", WindowModulesHandler), ("GET", "*.any.worker.js", ClassicWorkerHandler), ("GET", "*.any.worker-module.js", ModuleWorkerHandler), ("GET", "*.asis", handlers.AsIsHandler), @@ -573,7 +599,9 @@ def add_mount_point(self, url_base, path): ("*", "/.well-known/attribution-reporting/debug/report-event-attribution", handlers.PythonScriptHandler), ("*", "/.well-known/attribution-reporting/report-aggregate-attribution", handlers.PythonScriptHandler), ("*", "/.well-known/attribution-reporting/debug/report-aggregate-attribution", handlers.PythonScriptHandler), + ("*", "/.well-known/attribution-reporting/debug/report-aggregate-debug", handlers.PythonScriptHandler), ("*", "/.well-known/attribution-reporting/debug/verbose", handlers.PythonScriptHandler), + ("GET", "/.well-known/interest-group/permissions/", handlers.PythonScriptHandler), ("*", "/.well-known/private-aggregation/*", handlers.PythonScriptHandler), ("*", "/.well-known/web-identity", handlers.PythonScriptHandler), ("*", "*.py", handlers.PythonScriptHandler), @@ -652,7 +680,7 @@ def create_daemon(self, init_func, host, port, paths, routes, bind_address, try: self.daemon = init_func(logger, host, port, paths, routes, bind_address, config, **kwargs) except OSError: - logger.critical("Socket error on port %s" % port, file=sys.stderr) + logger.critical("Socket error on port %s" % port) raise except Exception: logger.critical(traceback.format_exc()) @@ -802,7 +830,8 @@ def start_http_server(logger, host, port, paths, routes, bind_address, config, * key_file=None, certificate=None, latency=kwargs.get("latency")) - except Exception: + except Exception as error: + logger.critical(f"start_http_server: Caught exception from wptserve.WebTestHttpd: {error}") startup_failed(logger) @@ -820,7 +849,8 @@ def start_https_server(logger, host, port, paths, routes, bind_address, config, certificate=config.ssl_config["cert_path"], encrypt_after_connect=config.ssl_config["encrypt_after_connect"], latency=kwargs.get("latency")) - except Exception: + except Exception as error: + logger.critical(f"start_https_server: Caught exception from wptserve.WebTestHttpd: {error}") startup_failed(logger) @@ -841,7 +871,8 @@ def start_http2_server(logger, host, port, paths, routes, bind_address, config, encrypt_after_connect=config.ssl_config["encrypt_after_connect"], latency=kwargs.get("latency"), http2=True) - except Exception: + except Exception as error: + logger.critical(f"start_http2_server: Caught exception from wptserve.WebTestHttpd: {error}") startup_failed(logger) @@ -869,7 +900,7 @@ def __init__(self, host, port, doc_root, handlers_root, bind_address, ssl_config # TODO: Fix the logging configuration in WebSockets processes # see https://github.com/web-platform-tests/wpt/issues/22719 logger.critical("Failed to start websocket server on port %s, " - "is something already using that port?" % port, file=sys.stderr) + "is something already using that port?" % port) raise OSError() assert all(item == ports[0] for item in ports) self.port = ports[0] @@ -908,7 +939,8 @@ def start_ws_server(logger, host, port, paths, routes, bind_address, config, **k config.paths["ws_doc_root"], bind_address, ssl_config=None) - except Exception: + except Exception as error: + logger.critical(f"start_ws_server: Caught exception from WebSocketDomain: {error}") startup_failed(logger) @@ -920,7 +952,8 @@ def start_wss_server(logger, host, port, paths, routes, bind_address, config, ** config.paths["ws_doc_root"], bind_address, config.ssl_config) - except Exception: + except Exception as error: + logger.critical(f"start_wss_server: Caught exception from WebSocketDomain: {error}") startup_failed(logger) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/serve/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/serve/w3c-import.log new file mode 100644 index 0000000000000..d9b356e9e6ca1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/serve/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/serve/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/serve/commands.json +/LayoutTests/imported/w3c/web-platform-tests/tools/serve/serve.py +/LayoutTests/imported/w3c/web-platform-tests/tools/serve/test_functional.py +/LayoutTests/imported/w3c/web-platform-tests/tools/serve/test_serve.py +/LayoutTests/imported/w3c/web-platform-tests/tools/serve/wave.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/atomicwrites/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/atomicwrites/w3c-import.log new file mode 100644 index 0000000000000..7e3762aa56c53 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/atomicwrites/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/atomicwrites/__init__.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/docs/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/docs/w3c-import.log new file mode 100644 index 0000000000000..fbccbb2d4dbd9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/docs/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/docs/Makefile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/docs/conf.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/docs/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/docs/make.bat diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/w3c-import.log new file mode 100644 index 0000000000000..ada0fc07335e6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/w3c-import.log @@ -0,0 +1,25 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/CONTRIBUTING.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/MANIFEST.in +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/Makefile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/appveyor.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/setup.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/setup.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/atomicwrites/tox.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/.github/CONTRIBUTING.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/.github/CONTRIBUTING.md index bbdc20f193242..a7e5b014d3388 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/.github/CONTRIBUTING.md +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/.github/CONTRIBUTING.md @@ -1,6 +1,6 @@ # How To Contribute -First off, thank you for considering contributing to `attrs`! +Thank you for considering contributing to *attrs*! It's people like *you* who make it such a great tool for everyone. This document intends to make contribution more accessible by codifying tribal knowledge and expectations. @@ -16,7 +16,7 @@ Please report any harm to [Hynek Schlawack] in any way you find appropriate. In case you'd like to help out but don't want to deal with GitHub, there's a great opportunity: help your fellow developers on [Stack Overflow](https://stackoverflow.com/questions/tagged/python-attrs)! -The official tag is `python-attrs` and helping out in support frees us up to improve `attrs` instead! +The official tag is `python-attrs` and helping out in support frees us up to improve *attrs* instead! ## Workflow @@ -24,22 +24,151 @@ The official tag is `python-attrs` and helping out in support frees us up to imp - No contribution is too small! Please submit as many fixes for typos and grammar bloopers as you can! - Try to limit each pull request to *one* change only. -- Since we squash on merge, it's up to you how you handle updates to the main branch. - Whether you prefer to rebase on main or merge main into your branch, do whatever is more comfortable for you. +- Since we squash on merge, it's up to you how you handle updates to the `main` branch. + Whether you prefer to rebase on `main` or merge `main` into your branch, do whatever is more comfortable for you. - *Always* add tests and docs for your code. - This is a hard rule; patches with missing tests or documentation can't be merged. + This is a hard rule; patches with missing tests or documentation won't be merged. - Make sure your changes pass our [CI]. You won't get any feedback until it's green unless you ask for it. - For the CI to pass, the coverage must be 100%. If you have problems to test something, open anyway and ask for advice. In some situations, we may agree to add an `# pragma: no cover`. - Once you've addressed review feedback, make sure to bump the pull request with a short note, so we know you're done. -- Don’t break backwards compatibility. +- Don’t break backwards-compatibility. + + +## Local Development Environment + +You can (and should) run our test suite using [*tox*]. +However, you’ll probably want a more traditional environment as well. + +First, create a [virtual environment](https://virtualenv.pypa.io/) so you don't break your system-wide Python installation. +We recommend using the Python version from the `.python-version-default` file in project's root directory. + +If you're using [*direnv*](https://direnv.net), you can automate the creation of a virtual environment with the correct Python version by adding the following `.envrc` to the project root after you've cloned it to your computer: + +```bash +layout python python$(cat .python-version-default) +``` + +If you're using tools that understand `.python-version` files like [*pyenv*](https://github.com/pyenv/pyenv) does, you can make it a link to the `.python-version-default` file. + +--- + +Then, [fork](https://github.com/python-attrs/attrs/fork) the repository on GitHub. + +Clone the fork to your computer: + +```console +$ git clone git@github.com:/attrs.git +``` + +Or if you prefer to use Git via HTTPS: + +```console +$ git clone https://github.com//attrs.git +``` + +Then add the *attrs* repository as *upstream* remote: + +```console +$ git remote add -t main -m main --tags upstream https://github.com/python-attrs/attrs.git +``` + +The next step is to sync your local copy with the upstream repository: + +```console +$ git fetch upstream +``` + +This is important to obtain eventually missing tags, which are needed to install the development version later on. +See [#1104](https://github.com/python-attrs/attrs/issues/1104) for more information. + +Change into the newly created directory and after activating a virtual environment install an editable version of *attrs* along with its tests and docs requirements: + +```console +$ cd attrs +$ python -m pip install --upgrade pip wheel # PLEASE don't skip this step +$ python -m pip install -e '.[dev]' +``` + +At this point, + +```console +$ python -m pytest +``` + +should work and pass. +You can *significantly* speed up the test suite by passing `-n auto` to *pytest* which activates [*pytest-xdist*](https://github.com/pytest-dev/pytest-xdist) and takes advantage of all your CPU cores. + +For documentation, you can use: + +```console +$ tox run -e docs-serve +``` + +This will build the documentation, and then watch for changes and rebuild it whenever you save a file. + +To just build the documentation and run doctests, use: + +```console +$ tox run -e docs +``` + +You will find the built documentation in `docs/_build/html`. + + +--- + +To file a pull request, create a new branch on top of the upstream repository's `main` branch: + +```console +$ git fetch upstream +$ git checkout -b my_topical_branch upstream/main +``` + +Make your changes, push them to your fork (the remote *origin*): + +```console +$ git push -u origin +``` + +and publish the PR in GitHub's web interface! + +After your pull request is merged and the branch is no longer needed, delete it: + +```console +$ git checkout main +$ git push --delete origin my_topical_branch && git branch -D my_topical_branch +``` + +Before starting to work on your next pull request, run the following command to sync your local repository with the remote *upstream*: + +```console +$ git fetch upstream -u main:main +``` + +--- + +To avoid committing code that violates our style guide, we strongly advise you to install [*pre-commit*] and its hooks: + +```console +$ pre-commit install +``` + +This is not strictly necessary, because our [*tox*] file contains an environment that runs: + +```console +$ pre-commit run --all-files +``` + +and our CI has integration with [pre-commit.ci](https://pre-commit.ci). +But it's way more comfortable to run it locally and *git* catching avoidable errors. ## Code -- Obey [PEP 8](https://www.python.org/dev/peps/pep-0008/) and [PEP 257](https://www.python.org/dev/peps/pep-0257/). +- Obey [PEP 8](https://peps.python.org/pep-0008/) and [PEP 257](https://peps.python.org/pep-0257/). We use the `"""`-on-separate-lines style for docstrings: ```python @@ -53,8 +182,8 @@ The official tag is `python-attrs` and helping out in support frees us up to imp """ ``` - If you add or change public APIs, tag the docstring using `.. versionadded:: 16.0.0 WHAT` or `.. versionchanged:: 16.2.0 WHAT`. -- We use [*isort*](https://github.com/PyCQA/isort) to sort our imports, and we use [*Black*](https://github.com/psf/black) with line length of 79 characters to format our code. - As long as you run our full [*tox*] suite before committing, or install our [*pre-commit*] hooks (ideally you'll do both – see [*Local Development Environment*](#local-development-environment) below), you won't have to spend any time on formatting your code at all. +- We use [Ruff](https://github.com/astral-sh/ruff) to sort our imports, and we use [Black](https://github.com/psf/black) with line length of 79 characters to format our code. + As long as you run our full [*tox*] suite before committing, or install our [*pre-commit*] hooks (ideally you'll do both – see [*Local Development Environment*](#local-development-environment) above), you won't have to spend any time on formatting your code at all. If you don't, [CI] will catch it for you – but that seems like a waste of your time! @@ -71,7 +200,7 @@ The official tag is `python-attrs` and helping out in support frees us up to imp - To run the test suite, all you need is a recent [*tox*]. It will ensure the test suite runs with all dependencies against all Python versions just as it will in our [CI]. - If you lack some Python versions, you can can always limit the environments like `tox -e py27,py38`, or make it a non-failure using `tox --skip-missing-interpreters`. + If you lack some Python versions, you can can always limit the environments like `tox run -e py38,py39`, or make it a non-failure using `tox run --skip-missing-interpreters`. In that case you should look into [*asdf*](https://asdf-vm.com) or [*pyenv*](https://github.com/pyenv/pyenv), which make it very easy to install many different Python versions in parallel. - Write [good test docstrings](https://jml.io/pages/test-docstrings.html). @@ -81,7 +210,7 @@ The official tag is `python-attrs` and helping out in support frees us up to imp ## Documentation -- Use [semantic newlines] in [*reStructuredText*] files (files ending in `.rst`): +- Use [semantic newlines] in [reStructuredText](https://www.sphinx-doc.org/en/stable/usage/restructuredtext/basics.html) and [Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) files (files ending in `.rst` and `.md`): ```rst This is a sentence. @@ -103,23 +232,23 @@ The official tag is `python-attrs` and helping out in support frees us up to imp First line of new section. ``` -- If you add a new feature, demonstrate its awesomeness on the [examples page](https://github.com/python-attrs/attrs/blob/main/docs/examples.rst)! +- If you add a new feature, demonstrate its awesomeness on the [examples page](https://github.com/python-attrs/attrs/blob/main/docs/examples.md)! ### Changelog If your change is noteworthy, there needs to be a changelog entry so our users can learn about it! -To avoid merge conflicts, we use the [*towncrier*](https://pypi.org/project/towncrier) package to manage our changelog. -*towncrier* uses independent files for each pull request – so called *news fragments* – instead of one monolithic changelog file. -On release, those news fragments are compiled into our [`CHANGELOG.rst`](https://github.com/python-attrs/attrs/blob/main/CHANGELOG.rst). +To avoid merge conflicts, we use the [*Towncrier*](https://pypi.org/project/towncrier) package to manage our changelog. +*towncrier* uses independent *Markdown* files for each pull request – so called *news fragments* – instead of one monolithic changelog file. +On release, those news fragments are compiled into our [`CHANGELOG.md`](https://github.com/python-attrs/attrs/blob/main/CHANGELOG.md). -You don't need to install *towncrier* yourself, you just have to abide by a few simple rules: +You don't need to install *Towncrier* yourself, you just have to abide by a few simple rules: -- For each pull request, add a new file into `changelog.d` with a filename adhering to the `pr#.(change|deprecation|breaking).rst` schema: - For example, `changelog.d/42.change.rst` for a non-breaking change that is proposed in pull request #42. +- For each pull request, add a new file into `changelog.d` with a filename adhering to the `pr#.(change|deprecation|breaking).md` schema: + For example, `changelog.d/42.change.md` for a non-breaking change that is proposed in pull request #42. - As with other docs, please use [semantic newlines] within news fragments. -- Wrap symbols like modules, functions, or classes into double backticks so they are rendered in a `monospace font`. +- Wrap symbols like modules, functions, or classes into backticks so they are rendered in a `monospace font`. - Wrap arguments into asterisks like in docstrings: `Added new argument *an_argument*.` - If you mention functions or other callables, add parentheses at the end of their names: @@ -131,90 +260,30 @@ You don't need to install *towncrier* yourself, you just have to abide by a few + Added `attrs.validators.func()`. + `attrs.func()` now doesn't crash the Large Hadron Collider anymore when passed the *foobar* argument. - If you want to reference multiple issues, copy the news fragment to another filename. - *towncrier* will merge all news fragments with identical contents into one entry with multiple links to the respective pull requests. + *Towncrier* will merge all news fragments with identical contents into one entry with multiple links to the respective pull requests. Example entries: - ```rst - Added ``attrs.validators.func()``. + ```md + Added `attrs.validators.func()`. The feature really *is* awesome. ``` or: - ```rst - ``attrs.func()`` now doesn't crash the Large Hadron Collider anymore when passed the *foobar* argument. + ```md + `attrs.func()` now doesn't crash the Large Hadron Collider anymore when passed the *foobar* argument. The bug really *was* nasty. ``` --- -``tox -e changelog`` will render the current changelog to the terminal if you have any doubts. - - -## Local Development Environment - -You can (and should) run our test suite using [*tox*]. -However, you’ll probably want a more traditional environment as well. -We highly recommend to develop using the latest Python release because we try to take advantage of modern features whenever possible. - -First create a [virtual environment](https://virtualenv.pypa.io/) so you don't break your system-wide Python installation. -It’s out of scope for this document to list all the ways to manage virtual environments in Python, but if you don’t already have a pet way, take some time to look at tools like [*direnv*](https://hynek.me/til/python-project-local-venvs/), [*virtualfish*](https://virtualfish.readthedocs.io/), and [*virtualenvwrapper*](https://virtualenvwrapper.readthedocs.io/). - -Next, get an up to date checkout of the `attrs` repository: - -```console -$ git clone git@github.com:python-attrs/attrs.git -``` - -or if you want to use git via `https`: - -```console -$ git clone https://github.com/python-attrs/attrs.git -``` - -Change into the newly created directory and **after activating your virtual environment** install an editable version of `attrs` along with its tests and docs requirements: - -```console -$ cd attrs -$ pip install --upgrade pip setuptools # PLEASE don't skip this step -$ pip install -e '.[dev]' -``` - -At this point, - -```console -$ python -m pytest -``` - -should work and pass, as should: - -```console -$ cd docs -$ make html -``` - -The built documentation can then be found in `docs/_build/html/`. - -To avoid committing code that violates our style guide, we strongly advise you to install [*pre-commit*] [^dev] hooks: - -```console -$ pre-commit install -``` - -You can also run them anytime (as our tox does) using: - -```console -$ pre-commit run --all-files -``` - -[^dev]: *pre-commit* should have been installed into your virtualenv automatically when you ran `pip install -e '.[dev]'` above. - If *pre-commit* is missing, your probably need to run `pip install -e '.[dev]'` again. +`tox run -e changelog` will render the current changelog to the terminal if you have any doubts. ## Governance -`attrs` is maintained by [team of volunteers](https://github.com/python-attrs) that is always open to new members that share our vision of a fast, lean, and magic-free library that empowers programmers to write better code with less effort. +*attrs* is maintained by [team of volunteers](https://github.com/python-attrs) that is always open to new members that share our vision of a fast, lean, and magic-free library that empowers programmers to write better code with less effort. If you'd like to join, just get a pull request merged and ask to be added in the very same pull request! **The simple rule is that everyone is welcome to review/merge pull requests of others but nobody is allowed to merge their own code.** @@ -225,6 +294,5 @@ If you'd like to join, just get a pull request merged and ask to be added in the [CI]: https://github.com/python-attrs/attrs/actions?query=workflow%3ACI [Hynek Schlawack]: https://hynek.me/about/ [*pre-commit*]: https://pre-commit.com/ -[*tox*]: https://https://tox.wiki/ +[*tox*]: https://tox.wiki/ [semantic newlines]: https://rhodesmill.org/brandon/2012/one-sentence-per-line/ -[*reStructuredText*]: https://www.sphinx-doc.org/en/stable/usage/restructuredtext/basics.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/.github/FUNDING.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/.github/FUNDING.yml index ef4f21216256d..7c250da1e51bb 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/.github/FUNDING.yml +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/.github/FUNDING.yml @@ -1,5 +1,3 @@ --- - github: hynek -ko_fi: the_hynek tidelift: "pypi/attrs" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/.github/PULL_REQUEST_TEMPLATE.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/.github/PULL_REQUEST_TEMPLATE.md index 88f6415e96cfb..e84b6c86ac84f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/.github/PULL_REQUEST_TEMPLATE.md +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/.github/PULL_REQUEST_TEMPLATE.md @@ -14,6 +14,9 @@ If an item doesn't apply to your pull request, **check it anyway** to make it ap If your pull request is a documentation fix or a trivial typo, feel free to delete the whole thing. --> +- [ ] Do **not** open pull requests from your `main` branch – **use a separate branch**! + - There's a ton of footguns waiting if you don't heed this warning. You can still go back to your project, create a branch from your main branch, push it, and open the pull request from the new branch. + - This is not a pre-requisite for your your pull request to be accepted, but **you have been warned**. - [ ] Added **tests** for changed code. Our CI fails if coverage is not 100%. - [ ] New features have been added to our [Hypothesis testing strategy](https://github.com/python-attrs/attrs/blob/main/tests/strategies.py). @@ -24,9 +27,13 @@ If your pull request is a documentation fix or a trivial typo, feel free to dele - [ ] New functions/classes have to be added to `docs/api.rst` by hand. - [ ] Changes to the signature of `@attr.s()` have to be added by hand too. - [ ] Changed/added classes/methods/functions have appropriate `versionadded`, `versionchanged`, or `deprecated` [directives](http://www.sphinx-doc.org/en/stable/markup/para.html#directive-versionadded). - Find the appropriate next version in our [``__init__.py``](https://github.com/python-attrs/attrs/blob/main/src/attr/__init__.py) file. -- [ ] Documentation in `.rst` files is written using [semantic newlines](https://rhodesmill.org/brandon/2012/one-sentence-per-line/). + The next version is the second number in the current release + 1. + The first number represents the current year. + So if the current version on PyPI is 22.2.0, the next version is gonna be 22.3.0. + If the next version is the first in the new year, it'll be 23.1.0. +- [ ] Documentation in `.rst` and `.md` files is written using [semantic newlines](https://rhodesmill.org/brandon/2012/one-sentence-per-line/). - [ ] Changes (and possible deprecations) have news fragments in [`changelog.d`](https://github.com/python-attrs/attrs/blob/main/changelog.d). +- [ ] Consider granting [push permissions to the PR branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork), so maintainers can fix minor issues themselves without pestering you. + +## [23.2.0](https://github.com/python-attrs/attrs/tree/23.2.0) - 2023-12-31 + +### Changes + +- The type annotation for `attrs.resolve_types()` is now correct. + [#1141](https://github.com/python-attrs/attrs/issues/1141) +- Type stubs now use `typing.dataclass_transform` to decorate dataclass-like decorators, instead of the non-standard `__dataclass_transform__` special form, which is only supported by Pyright. + [#1158](https://github.com/python-attrs/attrs/issues/1158) +- Fixed serialization of namedtuple fields using `attrs.asdict/astuple()` with `retain_collection_types=True`. + [#1165](https://github.com/python-attrs/attrs/issues/1165) +- `attrs.AttrsInstance` is now a `typing.Protocol` in both type hints and code. + This allows you to subclass it along with another `Protocol`. + [#1172](https://github.com/python-attrs/attrs/issues/1172) +- If *attrs* detects that `__attrs_pre_init__` accepts more than just `self`, it will call it with the same arguments as `__init__` was called. + This allows you to, for example, pass arguments to `super().__init__()`. + [#1187](https://github.com/python-attrs/attrs/issues/1187) +- Slotted classes now transform `functools.cached_property` decorated methods to support equivalent semantics. + [#1200](https://github.com/python-attrs/attrs/issues/1200) +- Added *class_body* argument to `attrs.make_class()` to provide additional attributes for newly created classes. + It is, for example, now possible to attach methods. + [#1203](https://github.com/python-attrs/attrs/issues/1203) + +## [23.1.0](https://github.com/python-attrs/attrs/tree/23.1.0) - 2023-04-16 + +### Backwards-incompatible Changes + +- Python 3.6 has been dropped and packaging switched to static package data using [Hatch](https://hatch.pypa.io/latest/). + [#993](https://github.com/python-attrs/attrs/issues/993) + + +### Deprecations + +- The support for *zope-interface* via the `attrs.validators.provides` validator is now deprecated and will be removed in, or after, April 2024. + + The presence of a C-based package in our developement dependencies has caused headaches and we're not under the impression it's used a lot. + + Let us know if you're using it and we might publish it as a separate package. + [#1120](https://github.com/python-attrs/attrs/issues/1120) + + +### Changes + +- `attrs.filters.exclude()` and `attrs.filters.include()` now support the passing of attribute names as strings. + [#1068](https://github.com/python-attrs/attrs/issues/1068) +- `attrs.has()` and `attrs.fields()` now handle generic classes correctly. + [#1079](https://github.com/python-attrs/attrs/issues/1079) +- Fix frozen exception classes when raised within e.g. `contextlib.contextmanager`, which mutates their `__traceback__` attributes. + [#1081](https://github.com/python-attrs/attrs/issues/1081) +- `@frozen` now works with type checkers that implement [PEP-681](https://peps.python.org/pep-0681/) (ex. [pyright](https://github.com/microsoft/pyright/)). + [#1084](https://github.com/python-attrs/attrs/issues/1084) +- Restored ability to unpickle instances pickled before 22.2.0. + [#1085](https://github.com/python-attrs/attrs/issues/1085) +- `attrs.asdict()`'s and `attrs.astuple()`'s type stubs now accept the `attrs.AttrsInstance` protocol. + [#1090](https://github.com/python-attrs/attrs/issues/1090) +- Fix slots class cellvar updating closure in CPython 3.8+ even when `__code__` introspection is unavailable. + [#1092](https://github.com/python-attrs/attrs/issues/1092) +- `attrs.resolve_types()` can now pass `include_extras` to `typing.get_type_hints()` on Python 3.9+, and does so by default. + [#1099](https://github.com/python-attrs/attrs/issues/1099) +- Added instructions for pull request workflow to `CONTRIBUTING.md`. + [#1105](https://github.com/python-attrs/attrs/issues/1105) +- Added *type* parameter to `attrs.field()` function for use with `attrs.make_class()`. + + Please note that type checkers ignore type metadata passed into `make_class()`, but it can be useful if you're wrapping _attrs_. + [#1107](https://github.com/python-attrs/attrs/issues/1107) +- It is now possible for `attrs.evolve()` (and `attr.evolve()`) to change fields named `inst` if the instance is passed as a positional argument. + + Passing the instance using the `inst` keyword argument is now deprecated and will be removed in, or after, April 2024. + [#1117](https://github.com/python-attrs/attrs/issues/1117) +- `attrs.validators.optional()` now also accepts a tuple of validators (in addition to lists of validators). + [#1122](https://github.com/python-attrs/attrs/issues/1122) + + +## [22.2.0](https://github.com/python-attrs/attrs/tree/22.2.0) - 2022-12-21 + +### Backwards-incompatible Changes + +- Python 3.5 is not supported anymore. + [#988](https://github.com/python-attrs/attrs/issues/988) + + +### Deprecations + +- Python 3.6 is now deprecated and support will be removed in the next release. + [#1017](https://github.com/python-attrs/attrs/issues/1017) + + +### Changes + +- `attrs.field()` now supports an *alias* option for explicit `__init__` argument names. + + Get `__init__` signatures matching any taste, peculiar or plain! + The [PEP 681 compatible](https://peps.python.org/pep-0681/#field-specifier-parameters) *alias* option can be use to override private attribute name mangling, or add other arbitrary field argument name overrides. + [#950](https://github.com/python-attrs/attrs/issues/950) +- `attrs.NOTHING` is now an enum value, making it possible to use with e.g. [`typing.Literal`](https://docs.python.org/3/library/typing.html#typing.Literal). + [#983](https://github.com/python-attrs/attrs/issues/983) +- Added missing re-import of `attr.AttrsInstance` to the `attrs` namespace. + [#987](https://github.com/python-attrs/attrs/issues/987) +- Fix slight performance regression in classes with custom `__setattr__` and speedup even more. + [#991](https://github.com/python-attrs/attrs/issues/991) +- Class-creation performance improvements by switching performance-sensitive templating operations to f-strings. + + You can expect an improvement of about 5% -- even for very simple classes. + [#995](https://github.com/python-attrs/attrs/issues/995) +- `attrs.has()` is now a [`TypeGuard`](https://docs.python.org/3/library/typing.html#typing.TypeGuard) for `AttrsInstance`. + That means that type checkers know a class is an instance of an `attrs` class if you check it using `attrs.has()` (or `attr.has()`) first. + [#997](https://github.com/python-attrs/attrs/issues/997) +- Made `attrs.AttrsInstance` stub available at runtime and fixed type errors related to the usage of `attrs.AttrsInstance` in *Pyright*. + [#999](https://github.com/python-attrs/attrs/issues/999) +- On Python 3.10 and later, call [`abc.update_abstractmethods()`](https://docs.python.org/3/library/abc.html#abc.update_abstractmethods) on dict classes after creation. + This improves the detection of abstractness. + [#1001](https://github.com/python-attrs/attrs/issues/1001) +- *attrs*'s pickling methods now use dicts instead of tuples. + That is safer and more robust across different versions of a class. + [#1009](https://github.com/python-attrs/attrs/issues/1009) +- Added `attrs.validators.not_(wrapped_validator)` to logically invert *wrapped_validator* by accepting only values where *wrapped_validator* rejects the value with a `ValueError` or `TypeError` (by default, exception types configurable). + [#1010](https://github.com/python-attrs/attrs/issues/1010) +- The type stubs for `attrs.cmp_using()` now have default values. + [#1027](https://github.com/python-attrs/attrs/issues/1027) +- To conform with [PEP 681](https://peps.python.org/pep-0681/), `attr.s()` and `attrs.define()` now accept *unsafe_hash* in addition to *hash*. + [#1065](https://github.com/python-attrs/attrs/issues/1065) + + +## [22.1.0](https://github.com/python-attrs/attrs/tree/22.1.0) - 2022-07-28 + +### Backwards-incompatible Changes + +- Python 2.7 is not supported anymore. + + Dealing with Python 2.7 tooling has become too difficult for a volunteer-run project. + + We have supported Python 2 more than 2 years after it was officially discontinued and feel that we have paid our dues. + All version up to 21.4.0 from December 2021 remain fully functional, of course. + [#936](https://github.com/python-attrs/attrs/issues/936) + +- The deprecated `cmp` attribute of `attrs.Attribute` has been removed. + This does not affect the *cmp* argument to `attr.s` that can be used as a shortcut to set *eq* and *order* at the same time. + [#939](https://github.com/python-attrs/attrs/issues/939) + + +### Changes + +- Instantiation of frozen slotted classes is now faster. + [#898](https://github.com/python-attrs/attrs/issues/898) +- If an `eq` key is defined, it is also used before hashing the attribute. + [#909](https://github.com/python-attrs/attrs/issues/909) +- Added `attrs.validators.min_len()`. + [#916](https://github.com/python-attrs/attrs/issues/916) +- `attrs.validators.deep_iterable()`'s *member_validator* argument now also accepts a list of validators and wraps them in an `attrs.validators.and_()`. + [#925](https://github.com/python-attrs/attrs/issues/925) +- Added missing type stub re-imports for `attrs.converters` and `attrs.filters`. + [#931](https://github.com/python-attrs/attrs/issues/931) +- Added missing stub for `attr(s).cmp_using()`. + [#949](https://github.com/python-attrs/attrs/issues/949) +- `attrs.validators._in()`'s `ValueError` is not missing the attribute, expected options, and the value it got anymore. + [#951](https://github.com/python-attrs/attrs/issues/951) +- Python 3.11 is now officially supported. + [#969](https://github.com/python-attrs/attrs/issues/969) + + +## [21.4.0](https://github.com/python-attrs/attrs/tree/21.4.0) - 2021-12-29 + +### Changes + +- Fixed the test suite on PyPy3.8 where `cloudpickle` does not work. + [#892](https://github.com/python-attrs/attrs/issues/892) +- Fixed `coverage report` for projects that use `attrs` and don't set a `--source`. + [#895](https://github.com/python-attrs/attrs/issues/895), + [#896](https://github.com/python-attrs/attrs/issues/896) + + +## [21.3.0](https://github.com/python-attrs/attrs/tree/21.3.0) - 2021-12-28 + +### Backward-incompatible Changes + +- When using `@define`, converters are now run by default when setting an attribute on an instance -- additionally to validators. + I.e. the new default is `on_setattr=[attrs.setters.convert, attrs.setters.validate]`. + + This is unfortunately a breaking change, but it was an oversight, impossible to raise a `DeprecationWarning` about, and it's better to fix it now while the APIs are very fresh with few users. + [#835](https://github.com/python-attrs/attrs/issues/835), + [#886](https://github.com/python-attrs/attrs/issues/886) + +- `import attrs` has finally landed! + As of this release, you can finally import `attrs` using its proper name. + + Not all names from the `attr` namespace have been transferred; most notably `attr.s` and `attr.ib` are missing. + See `attrs.define` and `attrs.field` if you haven't seen our next-generation APIs yet. + A more elaborate explanation can be found [On The Core API Names](https://www.attrs.org/en/latest/names.html) + + This feature is at least for one release **provisional**. + We don't *plan* on changing anything, but such a big change is unlikely to go perfectly on the first strike. + + The API docs have been mostly updated, but it will be an ongoing effort to change everything to the new APIs. + Please note that we have **not** moved -- or even removed -- anything from `attr`! + + Please do report any bugs or documentation inconsistencies! + [#887](https://github.com/python-attrs/attrs/issues/887) + + +### Changes + +- `attr.asdict(retain_collection_types=False)` (default) dumps collection-esque keys as tuples. + [#646](https://github.com/python-attrs/attrs/issues/646), + [#888](https://github.com/python-attrs/attrs/issues/888) +- `__match_args__` are now generated to support Python 3.10's + [Structural Pattern Matching](https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching). + This can be controlled by the `match_args` argument to the class decorators on Python 3.10 and later. + On older versions, it is never added and the argument is ignored. + [#815](https://github.com/python-attrs/attrs/issues/815) +- If the class-level *on_setattr* is set to `attrs.setters.validate` (default in `@define` and `@mutable`) but no field defines a validator, pretend that it's not set. + [#817](https://github.com/python-attrs/attrs/issues/817) +- The generated `__repr__` is significantly faster on Pythons with f-strings. + [#819](https://github.com/python-attrs/attrs/issues/819) +- Attributes transformed via `field_transformer` are wrapped with `AttrsClass` again. + [#824](https://github.com/python-attrs/attrs/issues/824) +- Generated source code is now cached more efficiently for identical classes. + [#828](https://github.com/python-attrs/attrs/issues/828) +- Added `attrs.converters.to_bool()`. + [#830](https://github.com/python-attrs/attrs/issues/830) +- `attrs.resolve_types()` now resolves types of subclasses after the parents are resolved. + [#842](https://github.com/python-attrs/attrs/issues/842) + [#843](https://github.com/python-attrs/attrs/issues/843) +- Added new validators: `lt(val)` (\< val), `le(va)` (≤ val), `ge(val)` (≥ val), `gt(val)` (> val), and `maxlen(n)`. + [#845](https://github.com/python-attrs/attrs/issues/845) +- `attrs` classes are now fully compatible with [cloudpickle](https://github.com/cloudpipe/cloudpickle) (no need to disable `repr` anymore). + [#857](https://github.com/python-attrs/attrs/issues/857) +- Added new context manager `attrs.validators.disabled()` and functions `attrs.validators.(set|get)_disabled()`. + They deprecate `attrs.(set|get)_run_validators()`. + All functions are interoperable and modify the same internal state. + They are not – and never were – thread-safe, though. + [#859](https://github.com/python-attrs/attrs/issues/859) +- `attrs.validators.matches_re()` now accepts pre-compiled regular expressions in addition to pattern strings. + [#877](https://github.com/python-attrs/attrs/issues/877) + +--- + +## [21.2.0](https://github.com/python-attrs/attrs/tree/21.2.0) - 2021-05-07 + +### Backward-incompatible Changes + +- We had to revert the recursive feature for `attr.evolve()` because it broke some use-cases -- sorry! + [#806](https://github.com/python-attrs/attrs/issues/806) +- Python 3.4 is now blocked using packaging metadata because `attrs` can't be imported on it anymore. + To ensure that 3.4 users can keep installing `attrs` easily, we will [yank](https://pypi.org/help/#yanked) 21.1.0 from PyPI. + This has **no** consequences if you pin `attrs` to 21.1.0. + [#807](https://github.com/python-attrs/attrs/issues/807) + + +## [21.1.0](https://github.com/python-attrs/attrs/tree/21.1.0) - 2021-05-06 + +### Deprecations + +- The long-awaited, much-talked-about, little-delivered `import attrs` is finally upon us! + + Since the NG APIs have now been proclaimed stable, the **next** release of `attrs` will allow you to actually `import attrs`. + We're taking this opportunity to replace some defaults in our APIs that made sense in 2015, but don't in 2021. + + So please, if you have any pet peeves about defaults in `attrs`'s APIs, *now* is the time to air your grievances in #487! + We're not gonna get such a chance for a second time, without breaking our backward-compatibility guarantees, or long deprecation cycles. + Therefore, speak now or forever hold you peace! + [#487](https://github.com/python-attrs/attrs/issues/487) + +- The *cmp* argument to `attr.s()` and `attr.ib()` has been **undeprecated** + It will continue to be supported as syntactic sugar to set *eq* and *order* in one go. + + I'm terribly sorry for the hassle around this argument! + The reason we're bringing it back is it's usefulness regarding customization of equality/ordering. + + The `cmp` attribute and argument on `attr.Attribute` remains deprecated and will be removed later this year. + [#773](https://github.com/python-attrs/attrs/issues/773) + + +### Changes + +- It's now possible to customize the behavior of `eq` and `order` by passing in a callable. + [#435](https://github.com/python-attrs/attrs/issues/435), + [#627](https://github.com/python-attrs/attrs/issues/627) + +- The instant favorite next-generation APIs are not provisional anymore! + + They are also officially supported by Mypy as of their [0.800 release](https://mypy-lang.blogspot.com/2021/01/mypy-0800-released.html). + + We hope the next release will already contain an (additional) importable package called `attrs`. + [#668](https://github.com/python-attrs/attrs/issues/668), + [#786](https://github.com/python-attrs/attrs/issues/786) + +- If an attribute defines a converter, the type of its parameter is used as type annotation for its corresponding `__init__` parameter. + + If an `attr.converters.pipe` is used, the first one's is used. + [#710](https://github.com/python-attrs/attrs/issues/710) + +- Fixed the creation of an extra slot for an `attr.ib` when the parent class already has a slot with the same name. + [#718](https://github.com/python-attrs/attrs/issues/718) + +- `__attrs__init__()` will now be injected if `init=False`, or if `auto_detect=True` and a user-defined `__init__()` exists. + + This enables users to do "pre-init" work in their `__init__()` (such as `super().__init__()`). + + `__init__()` can then delegate constructor argument processing to `self.__attrs_init__(*args, **kwargs)`. + [#731](https://github.com/python-attrs/attrs/issues/731) + +- `bool(attr.NOTHING)` is now `False`. + [#732](https://github.com/python-attrs/attrs/issues/732) + +- It's now possible to use `super()` inside of properties of slotted classes. + [#747](https://github.com/python-attrs/attrs/issues/747) + +- Allow for a `__attrs_pre_init__()` method that -- if defined -- will get called at the beginning of the `attrs`-generated `__init__()` method. + [#750](https://github.com/python-attrs/attrs/issues/750) + +- Added forgotten `attr.Attribute.evolve()` to type stubs. + [#752](https://github.com/python-attrs/attrs/issues/752) + +- `attrs.evolve()` now works recursively with nested `attrs` classes. + [#759](https://github.com/python-attrs/attrs/issues/759) + +- Python 3.10 is now officially supported. + [#763](https://github.com/python-attrs/attrs/issues/763) + +- `attr.resolve_types()` now takes an optional *attrib* argument to work inside a `field_transformer`. + [#774](https://github.com/python-attrs/attrs/issues/774) + +- `ClassVar`s are now also detected if they come from [typing-extensions](https://pypi.org/project/typing-extensions/). + [#782](https://github.com/python-attrs/attrs/issues/782) + +- To make it easier to customize attribute comparison (#435), we have added the `attr.cmp_with()` helper. + + See the [new docs on comparison](https://www.attrs.org/en/stable/comparison.html) for more details. + [#787](https://github.com/python-attrs/attrs/issues/787) + +- Added **provisional** support for static typing in `pyright` via [PEP 681](https://peps.python.org/pep-0681/). + Both the `pyright` specification and `attrs` implementation may change in future versions of both projects. + + Your constructive feedback is welcome in both [attrs#795](https://github.com/python-attrs/attrs/issues/795) and [pyright#1782](https://github.com/microsoft/pyright/discussions/1782). + [#796](https://github.com/python-attrs/attrs/issues/796) + + +## [20.3.0](https://github.com/python-attrs/attrs/tree/20.3.0) - 2020-11-05 + +### Backward-incompatible Changes + +- `attr.define()`, `attr.frozen()`, `attr.mutable()`, and `attr.field()` remain **provisional**. + + This release does **not** change anything about them and they are already used widely in production though. + + If you wish to use them together with mypy, you can simply drop [this plugin](https://gist.github.com/hynek/1e3844d0c99e479e716169034b5fa963#file-attrs_ng_plugin-py) into your project. + + Feel free to provide feedback to them in the linked issue #668. + + We will release the `attrs` namespace once we have the feeling that the APIs have properly settled. + [#668](https://github.com/python-attrs/attrs/issues/668) + +### Changes + +- `attr.s()` now has a *field_transformer* hook that is called for all `Attribute`s and returns a (modified or updated) list of `Attribute` instances. + `attr.asdict()` has a *value_serializer* hook that can change the way values are converted. + Both hooks are meant to help with data (de-)serialization workflows. + [#653](https://github.com/python-attrs/attrs/issues/653) +- `kw_only=True` now works on Python 2. + [#700](https://github.com/python-attrs/attrs/issues/700) +- `raise from` now works on frozen classes on PyPy. + [#703](https://github.com/python-attrs/attrs/issues/703), + [#712](https://github.com/python-attrs/attrs/issues/712) +- `attr.asdict()` and `attr.astuple()` now treat `frozenset`s like `set`s with regards to the *retain_collection_types* argument. + [#704](https://github.com/python-attrs/attrs/issues/704) +- The type stubs for `attr.s()` and `attr.make_class()` are not missing the *collect_by_mro* argument anymore. + [#711](https://github.com/python-attrs/attrs/issues/711) + +--- + +## [20.2.0](https://github.com/python-attrs/attrs/tree/20.2.0) - 2020-09-05 + +### Backward-incompatible Changes + +- `attr.define()`, `attr.frozen()`, `attr.mutable()`, and `attr.field()` remain **provisional**. + + This release fixes a bunch of bugs and ergonomics but they remain mostly unchanged. + + If you wish to use them together with mypy, you can simply drop [this plugin](https://gist.github.com/hynek/1e3844d0c99e479e716169034b5fa963#file-attrs_ng_plugin-py) into your project. + + Feel free to provide feedback to them in the linked issue #668. + + We will release the `attrs` namespace once we have the feeling that the APIs have properly settled. + [#668](https://github.com/python-attrs/attrs/issues/668) + +### Changes + +- `attr.define()` et al now correctly detect `__eq__` and `__ne__`. + [#671](https://github.com/python-attrs/attrs/issues/671) + +- `attr.define()` et al's hybrid behavior now also works correctly when arguments are passed. + [#675](https://github.com/python-attrs/attrs/issues/675) + +- It's possible to define custom `__setattr__` methods on slotted classes again. + [#681](https://github.com/python-attrs/attrs/issues/681) + +- In 20.1.0 we introduced the `inherited` attribute on the `attr.Attribute` class to differentiate attributes that have been inherited and those that have been defined directly on the class. + + It has shown to be problematic to involve that attribute when comparing instances of `attr.Attribute` though, because when sub-classing, attributes from base classes are suddenly not equal to themselves in a super class. + + Therefore the `inherited` attribute will now be ignored when hashing and comparing instances of `attr.Attribute`. + [#684](https://github.com/python-attrs/attrs/issues/684) + +- `zope.interface` is now a "soft dependency" when running the test suite; if `zope.interface` is not installed when running the test suite, the interface-related tests will be automatically skipped. + [#685](https://github.com/python-attrs/attrs/issues/685) + +- The ergonomics of creating frozen classes using `@define(frozen=True)` and sub-classing frozen classes has been improved: + you don't have to set `on_setattr=None` anymore. + [#687](https://github.com/python-attrs/attrs/issues/687) + +--- + +## [20.1.0](https://github.com/python-attrs/attrs/tree/20.1.0) - 2020-08-20 + +### Backward-incompatible Changes + +- Python 3.4 is not supported anymore. + It has been unsupported by the Python core team for a while now, its PyPI downloads are negligible, and our CI provider removed it as a supported option. + + It's very unlikely that `attrs` will break under 3.4 anytime soon, which is why we do *not* block its installation on Python 3.4. + But we don't test it anymore and will block it once someone reports breakage. + [#608](https://github.com/python-attrs/attrs/issues/608) + +### Deprecations + +- Less of a deprecation and more of a heads up: the next release of `attrs` will introduce an `attrs` namespace. + That means that you'll finally be able to run `import attrs` with new functions that aren't cute abbreviations and that will carry better defaults. + + This should not break any of your code, because project-local packages have priority before installed ones. + If this is a problem for you for some reason, please report it to our bug tracker and we'll figure something out. + + The old `attr` namespace isn't going anywhere and its defaults are not changing – this is a purely additive measure. + Please check out the linked issue for more details. + + These new APIs have been added *provisionally* as part of #666 so you can try them out today and provide feedback. + Learn more in the [API docs](https://www.attrs.org/en/stable/api.html). + [#408](https://github.com/python-attrs/attrs/issues/408) + +### Changes + +- Added `attr.resolve_types()`. + It ensures that all forward-references and types in string form are resolved into concrete types. + + You need this only if you need concrete types at runtime. + That means that if you only use types for static type checking, you do **not** need this function. + [#288](https://github.com/python-attrs/attrs/issues/288), + [#302](https://github.com/python-attrs/attrs/issues/302) + +- Added `@attr.s(collect_by_mro=False)` argument that if set to `True` fixes the collection of attributes from base classes. + + It's only necessary for certain cases of multiple-inheritance but is kept off for now for backward-compatibility reasons. + It will be turned on by default in the future. + + As a side-effect, `attr.Attribute` now *always* has an `inherited` attribute indicating whether an attribute on a class was directly defined or inherited. + [#428](https://github.com/python-attrs/attrs/issues/428), + [#635](https://github.com/python-attrs/attrs/issues/635) + +- On Python 3, all generated methods now have a docstring explaining that they have been created by `attrs`. + [#506](https://github.com/python-attrs/attrs/issues/506) + +- It is now possible to prevent `attrs` from auto-generating the `__setstate__` and `__getstate__` methods that are required for pickling of slotted classes. + + Either pass `@attr.s(getstate_setstate=False)` or pass `@attr.s(auto_detect=True)` and implement them yourself: + if `attrs` finds either of the two methods directly on the decorated class, it assumes implicitly `getstate_setstate=False` (and implements neither). + + This option works with dict classes but should never be necessary. + [#512](https://github.com/python-attrs/attrs/issues/512), + [#513](https://github.com/python-attrs/attrs/issues/513), + [#642](https://github.com/python-attrs/attrs/issues/642) + +- Fixed a `ValueError: Cell is empty` bug that could happen in some rare edge cases. + [#590](https://github.com/python-attrs/attrs/issues/590) + +- `attrs` can now automatically detect your own implementations and infer `init=False`, `repr=False`, `eq=False`, `order=False`, and `hash=False` if you set `@attr.s(auto_detect=True)`. + `attrs` will ignore inherited methods. + If the argument implies more than one method (e.g. `eq=True` creates both `__eq__` and `__ne__`), it's enough for *one* of them to exist and `attrs` will create *neither*. + + This feature requires Python 3. + [#607](https://github.com/python-attrs/attrs/issues/607) + +- Added `attr.converters.pipe()`. + The feature allows combining multiple conversion callbacks into one by piping the value through all of them, and retuning the last result. + + As part of this feature, we had to relax the type information for converter callables. + [#618](https://github.com/python-attrs/attrs/issues/618) + +- Fixed serialization behavior of non-slots classes with `cache_hash=True`. + The hash cache will be cleared on operations which make "deep copies" of instances of classes with hash caching, + though the cache will not be cleared with shallow copies like those made by `copy.copy()`. + + Previously, `copy.deepcopy()` or serialization and deserialization with `pickle` would result in an un-initialized object. + + This change also allows the creation of `cache_hash=True` classes with a custom `__setstate__`, + which was previously forbidden ([#494](https://github.com/python-attrs/attrs/issues/494)). + [#620](https://github.com/python-attrs/attrs/issues/620) + +- It is now possible to specify hooks that are called whenever an attribute is set **after** a class has been instantiated. + + You can pass `on_setattr` both to `@attr.s()` to set the default for all attributes on a class, and to `@attr.ib()` to overwrite it for individual attributes. + + `attrs` also comes with a new module `attr.setters` that brings helpers that run validators, converters, or allow to freeze a subset of attributes. + [#645](https://github.com/python-attrs/attrs/issues/645), + [#660](https://github.com/python-attrs/attrs/issues/660) + +- **Provisional** APIs called `attr.define()`, `attr.mutable()`, and `attr.frozen()` have been added. + + They are only available on Python 3.6 and later, and call `attr.s()` with different default values. + + If nothing comes up, they will become the official way for creating classes in 20.2.0 (see above). + + **Please note** that it may take some time until mypy – and other tools that have dedicated support for `attrs` – recognize these new APIs. + Please **do not** open issues on our bug tracker, there is nothing we can do about it. + [#666](https://github.com/python-attrs/attrs/issues/666) + +- We have also provisionally added `attr.field()` that supplants `attr.ib()`. + It also requires at least Python 3.6 and is keyword-only. + Other than that, it only dropped a few arguments, but changed no defaults. + + As with `attr.s()`: `attr.ib()` is not going anywhere. + [#669](https://github.com/python-attrs/attrs/issues/669) + +--- + +## [19.3.0](https://github.com/python-attrs/attrs/tree/19.3.0) - 2019-10-15 + +### Changes + +- Fixed `auto_attribs` usage when default values cannot be compared directly with `==`, such as `numpy` arrays. + [#585](https://github.com/python-attrs/attrs/issues/585) + +--- + +## [19.2.0](https://github.com/python-attrs/attrs/tree/19.2.0) - 2019-10-01 + +### Backward-incompatible Changes + +- Removed deprecated `Attribute` attribute `convert` per scheduled removal on 2019/1. + This planned deprecation is tracked in issue [#307](https://github.com/python-attrs/attrs/issues/307). + [#504](https://github.com/python-attrs/attrs/issues/504) + +- `__lt__`, `__le__`, `__gt__`, and `__ge__` do not consider subclasses comparable anymore. + + This has been deprecated since 18.2.0 and was raising a `DeprecationWarning` for over a year. + [#570](https://github.com/python-attrs/attrs/issues/570) + +### Deprecations + +- The `cmp` argument to `attr.s()` and `attr.ib()` is now deprecated. + + Please use `eq` to add equality methods (`__eq__` and `__ne__`) and `order` to add ordering methods (`__lt__`, `__le__`, `__gt__`, and `__ge__`) instead – just like with [dataclasses](https://docs.python.org/3/library/dataclasses.html). + + Both are effectively `True` by default but it's enough to set `eq=False` to disable both at once. + Passing `eq=False, order=True` explicitly will raise a `ValueError` though. + + Since this is arguably a deeper backward-compatibility break, it will have an extended deprecation period until 2021-06-01. + After that day, the `cmp` argument will be removed. + + `attr.Attribute` also isn't orderable anymore. + [#574](https://github.com/python-attrs/attrs/issues/574) + +### Changes + +- Updated `attr.validators.__all__` to include new validators added in [#425]. + [#517](https://github.com/python-attrs/attrs/issues/517) +- Slotted classes now use a pure Python mechanism to rewrite the `__class__` cell when rebuilding the class, so `super()` works even on environments where `ctypes` is not installed. + [#522](https://github.com/python-attrs/attrs/issues/522) +- When collecting attributes using `@attr.s(auto_attribs=True)`, attributes with a default of `None` are now deleted too. + [#523](https://github.com/python-attrs/attrs/issues/523), + [#556](https://github.com/python-attrs/attrs/issues/556) +- Fixed `attr.validators.deep_iterable()` and `attr.validators.deep_mapping()` type stubs. + [#533](https://github.com/python-attrs/attrs/issues/533) +- `attr.validators.is_callable()` validator now raises an exception `attr.exceptions.NotCallableError`, a subclass of `TypeError`, informing the received value. + [#536](https://github.com/python-attrs/attrs/issues/536) +- `@attr.s(auto_exc=True)` now generates classes that are hashable by ID, as the documentation always claimed it would. + [#543](https://github.com/python-attrs/attrs/issues/543), + [#563](https://github.com/python-attrs/attrs/issues/563) +- Added `attr.validators.matches_re()` that checks string attributes whether they match a regular expression. + [#552](https://github.com/python-attrs/attrs/issues/552) +- Keyword-only attributes (`kw_only=True`) and attributes that are excluded from the `attrs`'s `__init__` (`init=False`) now can appear before mandatory attributes. + [#559](https://github.com/python-attrs/attrs/issues/559) +- The fake filename for generated methods is now more stable. + It won't change when you restart the process. + [#560](https://github.com/python-attrs/attrs/issues/560) +- The value passed to `@attr.ib(repr=…)` can now be either a boolean (as before) or a callable. + That callable must return a string and is then used for formatting the attribute by the generated `__repr__()` method. + [#568](https://github.com/python-attrs/attrs/issues/568) +- Added `attr.__version_info__` that can be used to reliably check the version of `attrs` and write forward- and backward-compatible code. + Please check out the [section on deprecated APIs](https://www.attrs.org/en/stable/api-attr.html#deprecated-apis) on how to use it. + [#580](https://github.com/python-attrs/attrs/issues/580) + +> + +--- + +## [19.1.0](https://github.com/python-attrs/attrs/tree/19.1.0) - 2019-03-03 + +### Backward-incompatible Changes + +- Fixed a bug where deserialized objects with `cache_hash=True` could have incorrect hash code values. + This change breaks classes with `cache_hash=True` when a custom `__setstate__` is present. + An exception will be thrown when applying the `attrs` annotation to such a class. + This limitation is tracked in issue [#494](https://github.com/python-attrs/attrs/issues/494). + [#482](https://github.com/python-attrs/attrs/issues/482) + +### Changes + +- Add `is_callable`, `deep_iterable`, and `deep_mapping` validators. + + - `is_callable`: validates that a value is callable + - `deep_iterable`: Allows recursion down into an iterable, + applying another validator to every member in the iterable + as well as applying an optional validator to the iterable itself. + - `deep_mapping`: Allows recursion down into the items in a mapping object, + applying a key validator and a value validator to the key and value in every item. + Also applies an optional validator to the mapping object itself. + + You can find them in the `attr.validators` package. + [#425] + +- Fixed stub files to prevent errors raised by mypy's `disallow_any_generics = True` option. + [#443](https://github.com/python-attrs/attrs/issues/443) + +- Attributes with `init=False` now can follow after `kw_only=True` attributes. + [#450](https://github.com/python-attrs/attrs/issues/450) + +- `attrs` now has first class support for defining exception classes. + + If you define a class using `@attr.s(auto_exc=True)` and subclass an exception, the class will behave like a well-behaved exception class including an appropriate `__str__` method, and all attributes additionally available in an `args` attribute. + [#500](https://github.com/python-attrs/attrs/issues/500) + +- Clarified documentation for hashing to warn that hashable objects should be deeply immutable (in their usage, even if this is not enforced). + [#503](https://github.com/python-attrs/attrs/issues/503) + +--- + +## [18.2.0](https://github.com/python-attrs/attrs/tree/18.2.0) - 2018-09-01 + +### Deprecations + +- Comparing subclasses using `<`, `>`, `<=`, and `>=` is now deprecated. + The docs always claimed that instances are only compared if the types are identical, so this is a first step to conform to the docs. + + Equality operators (`==` and `!=`) were always strict in this regard. + [#394](https://github.com/python-attrs/attrs/issues/394) + +### Changes + +- `attrs` now ships its own [PEP 484](https://peps.python.org/pep-0484/) type hints. + Together with [mypy](http://mypy-lang.org)'s `attrs` plugin, you've got all you need for writing statically typed code in both Python 2 and 3! + + At that occasion, we've also added [narrative docs](https://www.attrs.org/en/stable/types.html) about type annotations in `attrs`. + [#238](https://github.com/python-attrs/attrs/issues/238) + +- Added *kw_only* arguments to `attr.ib` and `attr.s`, and a corresponding *kw_only* attribute to `attr.Attribute`. + This change makes it possible to have a generated `__init__` with keyword-only arguments on Python 3, relaxing the required ordering of default and non-default valued attributes. + [#281](https://github.com/python-attrs/attrs/issues/281), + [#411](https://github.com/python-attrs/attrs/issues/411) + +- The test suite now runs with `hypothesis.HealthCheck.too_slow` disabled to prevent CI breakage on slower computers. + [#364](https://github.com/python-attrs/attrs/issues/364), + [#396](https://github.com/python-attrs/attrs/issues/396) + +- `attr.validators.in_()` now raises a `ValueError` with a useful message even if the options are a string and the value is not a string. + [#383](https://github.com/python-attrs/attrs/issues/383) + +- `attr.asdict()` now properly handles deeply nested lists and dictionaries. + [#395](https://github.com/python-attrs/attrs/issues/395) + +- Added `attr.converters.default_if_none()` that allows to replace `None` values in attributes. + For example `attr.ib(converter=default_if_none(""))` replaces `None` by empty strings. + [#400](https://github.com/python-attrs/attrs/issues/400), + [#414](https://github.com/python-attrs/attrs/issues/414) + +- Fixed a reference leak where the original class would remain live after being replaced when `slots=True` is set. + [#407](https://github.com/python-attrs/attrs/issues/407) + +- Slotted classes can now be made weakly referenceable by passing `@attr.s(weakref_slot=True)`. + [#420](https://github.com/python-attrs/attrs/issues/420) + +- Added *cache_hash* option to `@attr.s` which causes the hash code to be computed once and stored on the object. + [#426](https://github.com/python-attrs/attrs/issues/426) + +- Attributes can be named `property` and `itemgetter` now. + [#430](https://github.com/python-attrs/attrs/issues/430) + +- It is now possible to override a base class' class variable using only class annotations. + [#431](https://github.com/python-attrs/attrs/issues/431) + +--- + +## [18.1.0](https://github.com/python-attrs/attrs/tree/18.1.0) - 2018-05-03 + +### Changes + +- `x=X(); x.cycle = x; repr(x)` will no longer raise a `RecursionError`, and will instead show as `X(x=...)`. + + [#95](https://github.com/python-attrs/attrs/issues/95) + +- `attr.ib(factory=f)` is now syntactic sugar for the common case of `attr.ib(default=attr.Factory(f))`. + + [#178](https://github.com/python-attrs/attrs/issues/178), + [#356](https://github.com/python-attrs/attrs/issues/356) + +- Added `attr.field_dict()` to return an ordered dictionary of `attrs` attributes for a class, whose keys are the attribute names. + + [#290](https://github.com/python-attrs/attrs/issues/290), + [#349](https://github.com/python-attrs/attrs/issues/349) + +- The order of attributes that are passed into `attr.make_class()` or the *these* argument of `@attr.s()` is now retained if the dictionary is ordered (i.e. `dict` on Python 3.6 and later, `collections.OrderedDict` otherwise). + + Before, the order was always determined by the order in which the attributes have been defined which may not be desirable when creating classes programmatically. + + [#300](https://github.com/python-attrs/attrs/issues/300), + [#339](https://github.com/python-attrs/attrs/issues/339), + [#343](https://github.com/python-attrs/attrs/issues/343) + +- In slotted classes, `__getstate__` and `__setstate__` now ignore the `__weakref__` attribute. + + [#311](https://github.com/python-attrs/attrs/issues/311), + [#326](https://github.com/python-attrs/attrs/issues/326) + +- Setting the cell type is now completely best effort. + This fixes `attrs` on Jython. + + We cannot make any guarantees regarding Jython though, because our test suite cannot run due to dependency incompatibilities. + + [#321](https://github.com/python-attrs/attrs/issues/321), + [#334](https://github.com/python-attrs/attrs/issues/334) + +- If `attr.s` is passed a *these* argument, it will no longer attempt to remove attributes with the same name from the class body. + + [#322](https://github.com/python-attrs/attrs/issues/322), + [#323](https://github.com/python-attrs/attrs/issues/323) + +- The hash of `attr.NOTHING` is now vegan and faster on 32bit Python builds. + + [#331](https://github.com/python-attrs/attrs/issues/331), + [#332](https://github.com/python-attrs/attrs/issues/332) + +- The overhead of instantiating frozen dict classes is virtually eliminated. + [#336](https://github.com/python-attrs/attrs/issues/336) + +- Generated `__init__` methods now have an `__annotations__` attribute derived from the types of the fields. + + [#363](https://github.com/python-attrs/attrs/issues/363) + +- We have restructured the documentation a bit to account for `attrs`' growth in scope. + Instead of putting everything into the [examples](https://www.attrs.org/en/stable/examples.html) page, we have started to extract narrative chapters. + + So far, we've added chapters on [initialization](https://www.attrs.org/en/stable/init.html) and [hashing](https://www.attrs.org/en/stable/hashing.html). + + Expect more to come! + + [#369](https://github.com/python-attrs/attrs/issues/369), + [#370](https://github.com/python-attrs/attrs/issues/370) + +--- + +## [17.4.0](https://github.com/python-attrs/attrs/tree/17.4.0) - 2017-12-30 + +### Backward-incompatible Changes + +- The traversal of MROs when using multiple inheritance was backward: + If you defined a class `C` that subclasses `A` and `B` like `C(A, B)`, `attrs` would have collected the attributes from `B` *before* those of `A`. + + This is now fixed and means that in classes that employ multiple inheritance, the output of `__repr__` and the order of positional arguments in `__init__` changes. + Because of the nature of this bug, a proper deprecation cycle was unfortunately impossible. + + Generally speaking, it's advisable to prefer `kwargs`-based initialization anyways – *especially* if you employ multiple inheritance and diamond-shaped hierarchies. + + [#298](https://github.com/python-attrs/attrs/issues/298), + [#299](https://github.com/python-attrs/attrs/issues/299), + [#304](https://github.com/python-attrs/attrs/issues/304) + +- The `__repr__` set by `attrs` no longer produces an `AttributeError` when the instance is missing some of the specified attributes (either through deleting or after using `init=False` on some attributes). + + This can break code that relied on `repr(attr_cls_instance)` raising `AttributeError` to check if any `attrs`-specified members were unset. + + If you were using this, you can implement a custom method for checking this: + + ``` + def has_unset_members(self): + for field in attr.fields(type(self)): + try: + getattr(self, field.name) + except AttributeError: + return True + return False + ``` + + [#308](https://github.com/python-attrs/attrs/issues/308) + +### Deprecations + +- The `attr.ib(convert=callable)` option is now deprecated in favor of `attr.ib(converter=callable)`. + + This is done to achieve consistency with other noun-based arguments like *validator*. + + *convert* will keep working until at least January 2019 while raising a `DeprecationWarning`. + + [#307](https://github.com/python-attrs/attrs/issues/307) + +### Changes + +- Generated `__hash__` methods now hash the class type along with the attribute values. + Until now the hashes of two classes with the same values were identical which was a bug. + + The generated method is also *much* faster now. + + [#261](https://github.com/python-attrs/attrs/issues/261), + [#295](https://github.com/python-attrs/attrs/issues/295), + [#296](https://github.com/python-attrs/attrs/issues/296) + +- `attr.ib`’s *metadata* argument now defaults to a unique empty `dict` instance instead of sharing a common empty `dict` for all. + The singleton empty `dict` is still enforced. + + [#280](https://github.com/python-attrs/attrs/issues/280) + +- `ctypes` is optional now however if it's missing, a bare `super()` will not work in slotted classes. + This should only happen in special environments like Google App Engine. + + [#284](https://github.com/python-attrs/attrs/issues/284), + [#286](https://github.com/python-attrs/attrs/issues/286) + +- The attribute redefinition feature introduced in 17.3.0 now takes into account if an attribute is redefined via multiple inheritance. + In that case, the definition that is closer to the base of the class hierarchy wins. + + [#285](https://github.com/python-attrs/attrs/issues/285), + [#287](https://github.com/python-attrs/attrs/issues/287) + +- Subclasses of `auto_attribs=True` can be empty now. + + [#291](https://github.com/python-attrs/attrs/issues/291), + [#292](https://github.com/python-attrs/attrs/issues/292) + +- Equality tests are *much* faster now. + + [#306](https://github.com/python-attrs/attrs/issues/306) + +- All generated methods now have correct `__module__`, `__name__`, and (on Python 3) `__qualname__` attributes. + + [#309](https://github.com/python-attrs/attrs/issues/309) + +--- + +## [17.3.0](https://github.com/python-attrs/attrs/tree/17.3.0) - 2017-11-08 + +### Backward-incompatible Changes + +- Attributes are no longer defined on the class body. + + This means that if you define a class `C` with an attribute `x`, the class will *not* have an attribute `x` for introspection. + Instead of `C.x`, use `attr.fields(C).x` or look at `C.__attrs_attrs__`. + The old behavior has been deprecated since version 16.1. + ([#253](https://github.com/python-attrs/attrs/issues/253)) + +### Changes + +- `super()` and `__class__` now work with slotted classes on Python 3. + ([#102](https://github.com/python-attrs/attrs/issues/102), [#226](https://github.com/python-attrs/attrs/issues/226), [#269](https://github.com/python-attrs/attrs/issues/269), [#270](https://github.com/python-attrs/attrs/issues/270), [#272](https://github.com/python-attrs/attrs/issues/272)) + +- Added *type* argument to `attr.ib()` and corresponding `type` attribute to `attr.Attribute`. + + This change paves the way for automatic type checking and serialization (though as of this release `attrs` does not make use of it). + In Python 3.6 or higher, the value of `attr.Attribute.type` can alternately be set using variable type annotations + (see [PEP 526](https://peps.python.org/pep-0526/)). + ([#151](https://github.com/python-attrs/attrs/issues/151), [#214](https://github.com/python-attrs/attrs/issues/214), [#215](https://github.com/python-attrs/attrs/issues/215), [#239](https://github.com/python-attrs/attrs/issues/239)) + +- The combination of `str=True` and `slots=True` now works on Python 2. + ([#198](https://github.com/python-attrs/attrs/issues/198)) + +- `attr.Factory` is hashable again. + ([#204](https://github.com/python-attrs/attrs/issues/204)) + +- Subclasses now can overwrite attribute definitions of their base classes. + + That means that you can -- for example -- change the default value for an attribute by redefining it. + ([#221](https://github.com/python-attrs/attrs/issues/221), [#229](https://github.com/python-attrs/attrs/issues/229)) + +- Added new option *auto_attribs* to `@attr.s` that allows to collect annotated fields without setting them to `attr.ib()`. + + Setting a field to an `attr.ib()` is still possible to supply options like validators. + Setting it to any other value is treated like it was passed as `attr.ib(default=value)` -- passing an instance of `attr.Factory` also works as expected. + ([#262](https://github.com/python-attrs/attrs/issues/262), [#277](https://github.com/python-attrs/attrs/issues/277)) + +- Instances of classes created using `attr.make_class()` can now be pickled. + ([#282](https://github.com/python-attrs/attrs/issues/282)) + +--- + +## [17.2.0](https://github.com/python-attrs/attrs/tree/17.2.0) - 2017-05-24 + +### Changes: + +- Validators are hashable again. + Note that validators may become frozen in the future, pending availability of no-overhead frozen classes. + [#192](https://github.com/python-attrs/attrs/issues/192) + +--- + +## [17.1.0](https://github.com/python-attrs/attrs/tree/17.1.0) - 2017-05-16 + +To encourage more participation, the project has also been moved into a [dedicated GitHub organization](https://github.com/python-attrs/) and everyone is most welcome to join! + +`attrs` also has a logo now! + +```{image} https://www.attrs.org/en/latest/_static/attrs_logo.png +:alt: attrs logo +``` + +### Backward-incompatible Changes: + +- `attrs` will set the `__hash__()` method to `None` by default now. + The way hashes were handled before was in conflict with [Python's specification](https://docs.python.org/3/reference/datamodel.html#object.__hash__). + This *may* break some software although this breakage is most likely just surfacing of latent bugs. + You can always make `attrs` create the `__hash__()` method using `@attr.s(hash=True)`. + See [#136] for the rationale of this change. + + :::{warning} + Please *do not* upgrade blindly and *do* test your software! + *Especially* if you use instances as dict keys or put them into sets! + ::: + +- Correspondingly, `attr.ib`'s *hash* argument is `None` by default too and mirrors the *cmp* argument as it should. + +### Deprecations: + +- `attr.assoc()` is now deprecated in favor of `attr.evolve()` and will stop working in 2018. + +### Changes: + +- Fix default hashing behavior. + Now *hash* mirrors the value of *cmp* and classes are unhashable by default. + [#136] + [#142](https://github.com/python-attrs/attrs/issues/142) +- Added `attr.evolve()` that, given an instance of an `attrs` class and field changes as keyword arguments, will instantiate a copy of the given instance with the changes applied. + `evolve()` replaces `assoc()`, which is now deprecated. + `evolve()` is significantly faster than `assoc()`, and requires the class have an initializer that can take the field values as keyword arguments (like `attrs` itself can generate). + [#116](https://github.com/python-attrs/attrs/issues/116) + [#124](https://github.com/python-attrs/attrs/pull/124) + [#135](https://github.com/python-attrs/attrs/pull/135) +- `FrozenInstanceError` is now raised when trying to delete an attribute from a frozen class. + [#118](https://github.com/python-attrs/attrs/pull/118) +- Frozen-ness of classes is now inherited. + [#128](https://github.com/python-attrs/attrs/pull/128) +- `__attrs_post_init__()` is now run if validation is disabled. + [#130](https://github.com/python-attrs/attrs/pull/130) +- Added `attr.validators.in_(options)` that, given the allowed `options`, checks whether the attribute value is in it. + This can be used to check constants, enums, mappings, etc. + [#181](https://github.com/python-attrs/attrs/pull/181) +- Added `attr.validators.and_()` that composes multiple validators into one. + [#161](https://github.com/python-attrs/attrs/issues/161) +- For convenience, the *validator* argument of `@attr.s` now can take a list of validators that are wrapped using `and_()`. + [#138](https://github.com/python-attrs/attrs/issues/138) +- Accordingly, `attr.validators.optional()` now can take a list of validators too. + [#161](https://github.com/python-attrs/attrs/issues/161) +- Validators can now be defined conveniently inline by using the attribute as a decorator. + Check out the [validator examples](https://www.attrs.org/en/stable/init.html#decorator) to see it in action! + [#143](https://github.com/python-attrs/attrs/issues/143) +- `attr.Factory()` now has a *takes_self* argument that makes the initializer to pass the partially initialized instance into the factory. + In other words you can define attribute defaults based on other attributes. + [#165] + [#189](https://github.com/python-attrs/attrs/issues/189) +- Default factories can now also be defined inline using decorators. + They are *always* passed the partially initialized instance. + [#165] +- Conversion can now be made optional using `attr.converters.optional()`. + [#105](https://github.com/python-attrs/attrs/issues/105) + [#173](https://github.com/python-attrs/attrs/pull/173) +- `attr.make_class()` now accepts the keyword argument `bases` which allows for subclassing. + [#152](https://github.com/python-attrs/attrs/pull/152) +- Metaclasses are now preserved with `slots=True`. + [#155](https://github.com/python-attrs/attrs/pull/155) + +--- + +## [16.3.0](https://github.com/python-attrs/attrs/tree/16.3.0) - 2016-11-24 + +### Changes: + +- Attributes now can have user-defined metadata which greatly improves `attrs`'s extensibility. + [#96](https://github.com/python-attrs/attrs/pull/96) + +- Allow for a `__attrs_post_init__()` method that -- if defined -- will get called at the end of the `attrs`-generated `__init__()` method. + [#111](https://github.com/python-attrs/attrs/pull/111) + +- Added `@attr.s(str=True)` that will optionally create a `__str__()` method that is identical to `__repr__()`. + This is mainly useful with `Exception`s and other classes that rely on a useful `__str__()` implementation but overwrite the default one through a poor own one. + Default Python class behavior is to use `__repr__()` as `__str__()` anyways. + + If you tried using `attrs` with `Exception`s and were puzzled by the tracebacks: this option is for you. + +- `__name__` is no longer overwritten with `__qualname__` for `attr.s(slots=True)` classes. + [#99](https://github.com/python-attrs/attrs/issues/99) + +--- + +## [16.2.0](https://github.com/python-attrs/attrs/tree/16.2.0) - 2016-09-17 + +### Changes: + +- Added `attr.astuple()` that -- similarly to `attr.asdict()` -- returns the instance as a tuple. + [#77](https://github.com/python-attrs/attrs/issues/77) +- Converters now work with frozen classes. + [#76](https://github.com/python-attrs/attrs/issues/76) +- Instantiation of `attrs` classes with converters is now significantly faster. + [#80](https://github.com/python-attrs/attrs/pull/80) +- Pickling now works with slotted classes. + [#81](https://github.com/python-attrs/attrs/issues/81) +- `attr.assoc()` now works with slotted classes. + [#84](https://github.com/python-attrs/attrs/issues/84) +- The tuple returned by `attr.fields()` now also allows to access the `Attribute` instances by name. + Yes, we've subclassed `tuple` so you don't have to! + Therefore `attr.fields(C).x` is equivalent to the deprecated `C.x` and works with slotted classes. + [#88](https://github.com/python-attrs/attrs/issues/88) + +--- + +## [16.1.0](https://github.com/python-attrs/attrs/tree/16.1.0) - 2016-08-30 + +### Backward-incompatible Changes: + +- All instances where function arguments were called `cl` have been changed to the more Pythonic `cls`. + Since it was always the first argument, it's doubtful anyone ever called those function with in the keyword form. + If so, sorry for any breakage but there's no practical deprecation path to solve this ugly wart. + +### Deprecations: + +- Accessing `Attribute` instances on class objects is now deprecated and will stop working in 2017. + If you need introspection please use the `__attrs_attrs__` attribute or the `attr.fields()` function that carry them too. + In the future, the attributes that are defined on the class body and are usually overwritten in your `__init__` method are simply removed after `@attr.s` has been applied. + + This will remove the confusing error message if you write your own `__init__` and forget to initialize some attribute. + Instead you will get a straightforward `AttributeError`. + In other words: decorated classes will work more like plain Python classes which was always `attrs`'s goal. + +- The serious-business aliases `attr.attributes` and `attr.attr` have been deprecated in favor of `attr.attrs` and `attr.attrib` which are much more consistent and frankly obvious in hindsight. + They will be purged from documentation immediately but there are no plans to actually remove them. + +### Changes: + +- `attr.asdict()`'s `dict_factory` arguments is now propagated on recursion. + [#45](https://github.com/python-attrs/attrs/issues/45) +- `attr.asdict()`, `attr.has()` and `attr.fields()` are significantly faster. + [#48](https://github.com/python-attrs/attrs/issues/48) + [#51](https://github.com/python-attrs/attrs/issues/51) +- Add `attr.attrs` and `attr.attrib` as a more consistent aliases for `attr.s` and `attr.ib`. +- Add *frozen* option to `attr.s` that will make instances best-effort immutable. + [#60](https://github.com/python-attrs/attrs/issues/60) +- `attr.asdict()` now takes `retain_collection_types` as an argument. + If `True`, it does not convert attributes of type `tuple` or `set` to `list`. + [#69](https://github.com/python-attrs/attrs/issues/69) + +--- + +## [16.0.0](https://github.com/python-attrs/attrs/tree/16.0.0) - 2016-05-23 + +### Backward-incompatible Changes: + +- Python 3.3 and 2.6 are no longer supported. + They may work by chance but any effort to keep them working has ceased. + + The last Python 2.6 release was on October 29, 2013 and is no longer supported by the CPython core team. + Major Python packages like Django and Twisted dropped Python 2.6 a while ago already. + + Python 3.3 never had a significant user base and wasn't part of any distribution's LTS release. + +### Changes: + +- `__slots__` have arrived! + Classes now can automatically be [slotted](https://docs.python.org/3/reference/datamodel.html#slots)-style (and save your precious memory) just by passing `slots=True`. + [#35](https://github.com/python-attrs/attrs/issues/35) +- Allow the case of initializing attributes that are set to `init=False`. + This allows for clean initializer parameter lists while being able to initialize attributes to default values. + [#32](https://github.com/python-attrs/attrs/issues/32) +- `attr.asdict()` can now produce arbitrary mappings instead of Python `dict`s when provided with a `dict_factory` argument. + [#40](https://github.com/python-attrs/attrs/issues/40) +- Multiple performance improvements. + +--- + +## [15.2.0](https://github.com/python-attrs/attrs/tree/15.2.0) - 2015-12-08 + +### Changes: + +- Added a `convert` argument to `attr.ib`, which allows specifying a function to run on arguments. + This allows for simple type conversions, e.g. with `attr.ib(convert=int)`. + [#26](https://github.com/python-attrs/attrs/issues/26) +- Speed up object creation when attribute validators are used. + [#28](https://github.com/python-attrs/attrs/issues/28) + +--- + +## [15.1.0](https://github.com/python-attrs/attrs/tree/15.1.0) - 2015-08-20 + +### Changes: + +- Added `attr.validators.optional()` that wraps other validators allowing attributes to be `None`. + [#16](https://github.com/python-attrs/attrs/issues/16) +- Multi-level inheritance now works. + [#24](https://github.com/python-attrs/attrs/issues/24) +- `__repr__()` now works with non-redecorated subclasses. + [#20](https://github.com/python-attrs/attrs/issues/20) + +--- + +## [15.0.0](https://github.com/python-attrs/attrs/tree/15.0.0) - 2015-04-15 + +### Changes: + +Initial release. + +[#136]: https://github.com/python-attrs/attrs/issues/136 +[#165]: https://github.com/python-attrs/attrs/issues/165 +[#425]: https://github.com/python-attrs/attrs/issues/425 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/CITATION.cff b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/CITATION.cff new file mode 100644 index 0000000000000..83718ad889d76 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/CITATION.cff @@ -0,0 +1,9 @@ +cff-version: 1.2.0 +message: If you use this software, please cite it as below. +title: attrs +type: software +authors: + - given-names: Hynek + family-names: Schlawack + email: hs@ox.cx +doi: 10.5281/zenodo.6925130 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/LICENSE b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/LICENSE index 7ae3df930976b..2bd6453d255e1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/LICENSE +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Hynek Schlawack +Copyright (c) 2015 Hynek Schlawack and the attrs contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/README.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/README.md new file mode 100644 index 0000000000000..6ef8c0204eaca --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/README.md @@ -0,0 +1,133 @@ +

+ + + + attrs + + +

+ +

+ Documentation + License: MIT + + + Downloads per month + DOI +

+ + + +*attrs* is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka [dunder methods](https://www.attrs.org/en/latest/glossary.html#term-dunder-methods)). +[Trusted by NASA](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/personalizing-your-profile#list-of-qualifying-repositories-for-mars-2020-helicopter-contributor-achievement) for Mars missions since 2020! + +Its main goal is to help you to write **concise** and **correct** software without slowing down your code. + + +## Sponsors + +*attrs* would not be possible without our [amazing sponsors](https://github.com/sponsors/hynek). +Especially those generously supporting us at the *The Organization* tier and higher: + +

+ + + +

+ +

+ Please consider joining them to help make attrs’s maintenance more sustainable! +

+ + + +## Example + +*attrs* gives you a class decorator and a way to declaratively define the attributes on that class: + + + +```pycon +>>> from attrs import asdict, define, make_class, Factory + +>>> @define +... class SomeClass: +... a_number: int = 42 +... list_of_numbers: list[int] = Factory(list) +... +... def hard_math(self, another_number): +... return self.a_number + sum(self.list_of_numbers) * another_number + + +>>> sc = SomeClass(1, [1, 2, 3]) +>>> sc +SomeClass(a_number=1, list_of_numbers=[1, 2, 3]) + +>>> sc.hard_math(3) +19 +>>> sc == SomeClass(1, [1, 2, 3]) +True +>>> sc != SomeClass(2, [3, 2, 1]) +True + +>>> asdict(sc) +{'a_number': 1, 'list_of_numbers': [1, 2, 3]} + +>>> SomeClass() +SomeClass(a_number=42, list_of_numbers=[]) + +>>> C = make_class("C", ["a", "b"]) +>>> C("foo", "bar") +C(a='foo', b='bar') +``` + +After *declaring* your attributes, *attrs* gives you: + +- a concise and explicit overview of the class's attributes, +- a nice human-readable `__repr__`, +- equality-checking methods, +- an initializer, +- and much more, + +*without* writing dull boilerplate code again and again and *without* runtime performance penalties. + +**Hate type annotations**!? +No problem! +Types are entirely **optional** with *attrs*. +Simply assign `attrs.field()` to the attributes instead of annotating them with types. + +--- + +This example uses *attrs*'s modern APIs that have been introduced in version 20.1.0, and the *attrs* package import name that has been added in version 21.3.0. +The classic APIs (`@attr.s`, `attr.ib`, plus their serious-business aliases) and the `attr` package import name will remain **indefinitely**. + +Please check out [*On The Core API Names*](https://www.attrs.org/en/latest/names.html) for a more in-depth explanation. + + +## Data Classes + +On the tin, *attrs* might remind you of `dataclasses` (and indeed, `dataclasses` [are a descendant](https://hynek.me/articles/import-attrs/) of *attrs*). +In practice it does a lot more and is more flexible. +For instance it allows you to define [special handling of NumPy arrays for equality checks](https://www.attrs.org/en/stable/comparison.html#customization), allows more ways to [plug into the initialization process](https://www.attrs.org/en/stable/init.html#hooking-yourself-into-initialization), and allows for stepping through the generated methods using a debugger. + +For more details, please refer to our [comparison page](https://www.attrs.org/en/stable/why.html#data-classes). + + +## Project Information + +- [**Changelog**](https://www.attrs.org/en/stable/changelog.html) +- [**Documentation**](https://www.attrs.org/) +- [**PyPI**](https://pypi.org/project/attrs/) +- [**Source Code**](https://github.com/python-attrs/attrs) +- [**Contributing**](https://github.com/python-attrs/attrs/blob/main/.github/CONTRIBUTING.md) +- [**Third-party Extensions**](https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs) +- **Get Help**: please use the `python-attrs` tag on [StackOverflow](https://stackoverflow.com/questions/tagged/python-attrs) + + +### *attrs* for Enterprise + +Available as part of the Tidelift Subscription. + +The maintainers of *attrs* and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. +Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. +[Learn more.](https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/changelog.d/towncrier_template.md.jinja b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/changelog.d/towncrier_template.md.jinja new file mode 100644 index 0000000000000..d9ae7c10efc16 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/changelog.d/towncrier_template.md.jinja @@ -0,0 +1,28 @@ +{%- if versiondata["version"] == "main" -%} +## Changes for the Upcoming Release + +:::{warning} +These changes reflect the current [development progress](https://github.com/python-attrs/attrs/tree/main) and have **not** been part of a PyPI release yet. +::: +{% else -%} +## [{{ versiondata["version"] }}](https://github.com/python-attrs/attrs/tree/{{ versiondata["version"] }}) - {{ versiondata["date"] }} +{%- endif %} + +{% for section, _ in sections.items() %} +{% if sections[section] %} +{% for category, val in definitions.items() if category in sections[section] %} + +### {{ definitions[category]['name'] }} + +{% for text, values in sections[section][category].items() %} +- {{ text }} + {{ values|join(',\n ') }} +{% endfor %} + +{% endfor %} +{% else %} +No significant changes. + + +{% endif %} +{% endfor %} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/changelog.d/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/changelog.d/w3c-import.log new file mode 100644 index 0000000000000..1f1cf380e116a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/changelog.d/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/changelog.d/towncrier_template.md.jinja diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/conftest.py index 0d539a115c77d..144e5f3e19f8f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/conftest.py @@ -1,10 +1,20 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function +import pytest from hypothesis import HealthCheck, settings -from attr._compat import PY36, PY310 +from attr._compat import PY310 + + +@pytest.fixture(name="slots", params=(True, False)) +def _slots(request): + return request.param + + +@pytest.fixture(name="frozen", params=(True, False)) +def _frozen(request): + return request.param def pytest_configure(config): @@ -16,14 +26,5 @@ def pytest_configure(config): collect_ignore = [] -if not PY36: - collect_ignore.extend( - [ - "tests/test_annotations.py", - "tests/test_hooks.py", - "tests/test_init_subclass.py", - "tests/test_next_gen.py", - ] - ) if not PY310: collect_ignore.extend(["tests/test_pattern_matching.py"]) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/custom.css b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/custom.css new file mode 100644 index 0000000000000..72083fee48232 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/custom.css @@ -0,0 +1,10 @@ +@import url('https://rsms.me/inter/inter.css'); +@import url('https://assets.hynek.me/css/bm.css'); + + +:root { + font-feature-settings: 'liga' 1, 'calt' 1; /* fix for Chrome */ +} +@supports (font-variation-settings: normal) { + :root { font-family: InterVariable, sans-serif; } +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/docset-icon.png b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/docset-icon.png new file mode 100644 index 0000000000000..d9886f1f6f6bc Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/docset-icon.png differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/docset-icon@2x.png b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/docset-icon@2x.png new file mode 100644 index 0000000000000..5551b42bd92cb Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/docset-icon@2x.png differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/social card.afdesign b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/social card.afdesign new file mode 100644 index 0000000000000..06fa6696059d1 Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/social card.afdesign differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/social card.png b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/social card.png new file mode 100644 index 0000000000000..46af5dcfd691c Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/social card.png differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/sponsors/FilePreviews.svg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/sponsors/FilePreviews.svg new file mode 100644 index 0000000000000..2fff3aa3aeaef --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/sponsors/FilePreviews.svg @@ -0,0 +1 @@ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/sponsors/Tidelift.svg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/sponsors/Tidelift.svg new file mode 100644 index 0000000000000..8b4da42cb1572 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/sponsors/Tidelift.svg @@ -0,0 +1 @@ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/sponsors/Variomedia.svg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/sponsors/Variomedia.svg new file mode 100644 index 0000000000000..90e750d257456 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/sponsors/Variomedia.svg @@ -0,0 +1 @@ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/sponsors/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/sponsors/w3c-import.log new file mode 100644 index 0000000000000..e7c13b51fc33b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/sponsors/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/sponsors/FilePreviews.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/sponsors/Tidelift.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/sponsors/Variomedia.svg diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/w3c-import.log new file mode 100644 index 0000000000000..c562e0e4df477 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/w3c-import.log @@ -0,0 +1,25 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/attrs_logo.png +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/attrs_logo.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/attrs_logo_white.png +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/attrs_logo_white.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/custom.css +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/docset-icon.png +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/docset-icon@2x.png +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/social card.afdesign +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/_static/social card.png diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/api-attr.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/api-attr.rst new file mode 100644 index 0000000000000..1c1c3edb3fcd6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/api-attr.rst @@ -0,0 +1,242 @@ +API Reference for the ``attr`` Namespace +======================================== + +.. module:: attr + + +Core +---- + +.. autofunction:: attr.s(these=None, repr_ns=None, repr=None, cmp=None, hash=None, init=None, slots=False, frozen=False, weakref_slot=True, str=False, auto_attribs=False, kw_only=False, cache_hash=False, auto_exc=False, eq=None, order=None, auto_detect=False, collect_by_mro=False, getstate_setstate=None, on_setattr=None, field_transformer=None, match_args=True, unsafe_hash=None) + + .. note:: + + *attrs* also comes with a serious-business alias ``attr.attrs``. + + For example: + + .. doctest:: + + >>> import attr + >>> @attr.s + ... class C: + ... _private = attr.ib() + >>> C(private=42) + C(_private=42) + >>> class D: + ... def __init__(self, x): + ... self.x = x + >>> D(1) + + >>> D = attr.s(these={"x": attr.ib()}, init=False)(D) + >>> D(1) + D(x=1) + >>> @attr.s(auto_exc=True) + ... class Error(Exception): + ... x = attr.ib() + ... y = attr.ib(default=42, init=False) + >>> Error("foo") + Error(x='foo', y=42) + >>> raise Error("foo") + Traceback (most recent call last): + ... + Error: ('foo', 42) + >>> raise ValueError("foo", 42) # for comparison + Traceback (most recent call last): + ... + ValueError: ('foo', 42) + + +.. autofunction:: attr.ib + + .. note:: + + *attrs* also comes with a serious-business alias ``attr.attrib``. + + The object returned by `attr.ib` also allows for setting the default and the validator using decorators: + + .. doctest:: + + >>> @attr.s + ... class C: + ... x = attr.ib() + ... y = attr.ib() + ... @x.validator + ... def _any_name_except_a_name_of_an_attribute(self, attribute, value): + ... if value < 0: + ... raise ValueError("x must be positive") + ... @y.default + ... def _any_name_except_a_name_of_an_attribute(self): + ... return self.x + 1 + >>> C(1) + C(x=1, y=2) + >>> C(-1) + Traceback (most recent call last): + ... + ValueError: x must be positive + + +.. function:: define + + Same as `attrs.define`. + +.. function:: mutable + + Same as `attrs.mutable`. + +.. function:: frozen + + Same as `attrs.frozen`. + +.. function:: field + + Same as `attrs.field`. + +.. class:: Attribute + + Same as `attrs.Attribute`. + +.. function:: make_class + + Same as `attrs.make_class`. + +.. autoclass:: Factory + :noindex: + + Same as `attrs.Factory`. + + +.. data:: NOTHING + + Same as `attrs.NOTHING`. + + +Exceptions +---------- + +.. module:: attr.exceptions + +All exceptions are available from both ``attr.exceptions`` and `attrs.exceptions` (it's the same module in a different namespace). + +Please refer to `attrs.exceptions` for details. + + +Helpers +------- + +.. currentmodule:: attr + +.. function:: cmp_using + + Same as `attrs.cmp_using`. + +.. function:: fields + + Same as `attrs.fields`. + +.. function:: fields_dict + + Same as `attr.fields_dict`. + +.. function:: has + + Same as `attrs.has`. + +.. function:: resolve_types + + Same as `attrs.resolve_types`. + +.. autofunction:: asdict +.. autofunction:: astuple + +.. module:: attr.filters + +.. function:: include + + Same as `attrs.filters.include`. + +.. function:: exclude + + Same as `attrs.filters.exclude`. + +See :func:`attrs.asdict` for examples. + +All objects from `attrs.filters` are also available in ``attr.filters``. + +---- + +.. currentmodule:: attr + +.. function:: evolve + + Same as `attrs.evolve`. + +.. function:: validate + + Same as `attrs.validate`. + + +Validators +---------- + +.. module:: attr.validators + +All objects from `attrs.validators` are also available in ``attr.validators``. +Please refer to the former for details. + + +Converters +---------- + +.. module:: attr.converters + +All objects from `attrs.converters` are also available from ``attr.converters``. +Please refer to the former for details. + + +Setters +------- + +.. module:: attr.setters + +All objects from `attrs.setters` are also available in ``attr.setters``. +Please refer to the former for details. + + +Deprecated APIs +--------------- + +.. currentmodule:: attr + +To help you write backward compatible code that doesn't throw warnings on modern releases, the ``attr`` module has an ``__version_info__`` attribute as of version 19.2.0. +It behaves similarly to `sys.version_info` and is an instance of `attr.VersionInfo`: + +.. autoclass:: VersionInfo + + With its help you can write code like this: + + >>> if getattr(attr, "__version_info__", (0,)) >= (19, 2): + ... cmp_off = {"eq": False} + ... else: + ... cmp_off = {"cmp": False} + >>> cmp_off == {"eq": False} + True + >>> @attr.s(**cmp_off) + ... class C: + ... pass + + +---- + +.. autofunction:: assoc + +Before *attrs* got `attrs.validators.set_disabled` and `attrs.validators.set_disabled`, it had the following APIs to globally enable and disable validators. +They won't be removed, but are discouraged to use: + +.. autofunction:: set_run_validators +.. autofunction:: get_run_validators + +---- + +The serious-business aliases used to be called ``attr.attributes`` and ``attr.attr``. +There are no plans to remove them but they shouldn't be used in new code. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/api.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/api.rst index 02aed52ad5dfe..d55f2539ea5da 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/api.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/api.rst @@ -1,83 +1,62 @@ API Reference ============= -.. currentmodule:: attr +.. module:: attrs -``attrs`` works by decorating a class using `attrs.define` or `attr.s` and then optionally defining attributes on the class using `attrs.field`, `attr.ib`, or a type annotation. +*attrs* works by decorating a class using `attrs.define` or `attr.s` and then defining attributes on the class using `attrs.field`, `attr.ib`, or type annotations. -If you're confused by the many names, please check out `names` for clarification. +What follows is the API explanation, if you'd like a more hands-on tutorial, have a look at `examples`. -What follows is the API explanation, if you'd like a more hands-on introduction, have a look at `examples`. +If you're confused by the many names, please check out `names` for clarification, but the `TL;DR `_ is that as of version 21.3.0, *attrs* consists of **two** top-level package names: -As of version 21.3.0, ``attrs`` consists of **two** to-level package names: - -- The classic ``attr`` that powered the venerable `attr.s` and `attr.ib` -- The modern ``attrs`` that only contains most modern APIs and relies on `attrs.define` and `attrs.field` to define your classes. +- The classic ``attr`` that powers the venerable `attr.s` and `attr.ib`. +- The newer ``attrs`` that only contains most modern APIs and relies on `attrs.define` and `attrs.field` to define your classes. Additionally it offers some ``attr`` APIs with nicer defaults (e.g. `attrs.asdict`). - Using this namespace requires Python 3.6 or later. -The ``attrs`` namespace is built *on top of* ``attr`` which will *never* go away. +The ``attrs`` namespace is built *on top of* ``attr`` -- which will *never* go away -- and is just as stable, since it doesn't constitute a rewrite. +To keep repetition low and this document at a reasonable size, the ``attr`` namespace is `documented on a separate page `, though. Core ---- -.. note:: - - Please note that the ``attrs`` namespace has been added in version 21.3.0. - Most of the objects are simply re-imported from ``attr``. - Therefore if a class, method, or function claims that it has been added in an older version, it is only available in the ``attr`` namespace. - .. autodata:: attrs.NOTHING + :no-value: .. autofunction:: attrs.define -.. function:: attrs.mutable(same_as_define) +.. function:: mutable(same_as_define) - Alias for `attrs.define`. + Same as `attrs.define`. .. versionadded:: 20.1.0 -.. function:: attrs.frozen(same_as_define) +.. function:: frozen(same_as_define) Behaves the same as `attrs.define` but sets *frozen=True* and *on_setattr=None*. .. versionadded:: 20.1.0 -.. autofunction:: attrs.field - -.. function:: define - - Old import path for `attrs.define`. - -.. function:: mutable - - Old import path for `attrs.mutable`. - -.. function:: frozen - - Old import path for `attrs.frozen`. +.. autofunction:: field -.. function:: field - - Old import path for `attrs.field`. - -.. autoclass:: attrs.Attribute +.. autoclass:: Attribute :members: evolve For example: .. doctest:: - >>> import attr - >>> @attr.s - ... class C(object): - ... x = attr.ib() - >>> attr.fields(C).x - Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None) + >>> import attrs + >>> from attrs import define, field + + >>> @define + ... class C: + ... x = field() + >>> attrs.fields(C).x + Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='x') -.. autofunction:: attrs.make_class +.. autofunction:: make_class This is handy if you want to programmatically create classes. @@ -85,25 +64,27 @@ Core .. doctest:: - >>> C1 = attr.make_class("C1", ["x", "y"]) + >>> C1 = attrs.make_class("C1", ["x", "y"]) >>> C1(1, 2) C1(x=1, y=2) - >>> C2 = attr.make_class("C2", {"x": attr.ib(default=42), - ... "y": attr.ib(default=attr.Factory(list))}) + >>> C2 = attrs.make_class("C2", { + ... "x": field(default=42), + ... "y": field(factory=list) + ... }) >>> C2() C2(x=42, y=[]) -.. autoclass:: attrs.Factory +.. autoclass:: Factory For example: .. doctest:: - >>> @attr.s - ... class C(object): - ... x = attr.ib(default=attr.Factory(list)) - ... y = attr.ib(default=attr.Factory( + >>> @define + ... class C: + ... x = field(default=attrs.Factory(list)) + ... y = field(default=attrs.Factory( ... lambda self: set(self.x), ... takes_self=True) ... ) @@ -113,86 +94,11 @@ Core C(x=[1, 2, 3], y={1, 2, 3}) -Classic -~~~~~~~ - -.. data:: attr.NOTHING - - Same as `attrs.NOTHING`. - -.. autofunction:: attr.s(these=None, repr_ns=None, repr=None, cmp=None, hash=None, init=None, slots=False, frozen=False, weakref_slot=True, str=False, auto_attribs=False, kw_only=False, cache_hash=False, auto_exc=False, eq=None, order=None, auto_detect=False, collect_by_mro=False, getstate_setstate=None, on_setattr=None, field_transformer=None, match_args=True) - - .. note:: - - ``attrs`` also comes with a serious business alias ``attr.attrs``. - - For example: - - .. doctest:: - - >>> import attr - >>> @attr.s - ... class C(object): - ... _private = attr.ib() - >>> C(private=42) - C(_private=42) - >>> class D(object): - ... def __init__(self, x): - ... self.x = x - >>> D(1) - - >>> D = attr.s(these={"x": attr.ib()}, init=False)(D) - >>> D(1) - D(x=1) - >>> @attr.s(auto_exc=True) - ... class Error(Exception): - ... x = attr.ib() - ... y = attr.ib(default=42, init=False) - >>> Error("foo") - Error(x='foo', y=42) - >>> raise Error("foo") - Traceback (most recent call last): - ... - Error: ('foo', 42) - >>> raise ValueError("foo", 42) # for comparison - Traceback (most recent call last): - ... - ValueError: ('foo', 42) - - -.. autofunction:: attr.ib - - .. note:: - - ``attrs`` also comes with a serious business alias ``attr.attrib``. - - The object returned by `attr.ib` also allows for setting the default and the validator using decorators: - - .. doctest:: - - >>> @attr.s - ... class C(object): - ... x = attr.ib() - ... y = attr.ib() - ... @x.validator - ... def _any_name_except_a_name_of_an_attribute(self, attribute, value): - ... if value < 0: - ... raise ValueError("x must be positive") - ... @y.default - ... def _any_name_except_a_name_of_an_attribute(self): - ... return self.x + 1 - >>> C(1) - C(x=1, y=2) - >>> C(-1) - Traceback (most recent call last): - ... - ValueError: x must be positive - - - Exceptions ---------- +.. module:: attrs.exceptions + All exceptions are available from both ``attr.exceptions`` and ``attrs.exceptions`` and are the same thing. That means that it doesn't matter from from which namespace they've been raised and/or caught: @@ -205,15 +111,15 @@ That means that it doesn't matter from from which namespace they've been raised ... print("this works!") this works! -.. autoexception:: attrs.exceptions.PythonTooOldError -.. autoexception:: attrs.exceptions.FrozenError -.. autoexception:: attrs.exceptions.FrozenInstanceError -.. autoexception:: attrs.exceptions.FrozenAttributeError -.. autoexception:: attrs.exceptions.AttrsAttributeNotFoundError -.. autoexception:: attrs.exceptions.NotAnAttrsClassError -.. autoexception:: attrs.exceptions.DefaultAlreadySetError -.. autoexception:: attrs.exceptions.UnannotatedAttributeError -.. autoexception:: attrs.exceptions.NotCallableError +.. autoexception:: PythonTooOldError +.. autoexception:: FrozenError +.. autoexception:: FrozenInstanceError +.. autoexception:: FrozenAttributeError +.. autoexception:: AttrsAttributeNotFoundError +.. autoexception:: NotAnAttrsClassError +.. autoexception:: DefaultAlreadySetError +.. autoexception:: NotCallableError +.. autoexception:: UnannotatedAttributeError For example:: @@ -228,12 +134,11 @@ That means that it doesn't matter from from which namespace they've been raised Helpers ------- -``attrs`` comes with a bunch of helper methods that make working with it easier: +*attrs* comes with a bunch of helper methods that make working with it easier: -.. autofunction:: attrs.cmp_using -.. function:: attr.cmp_using +.. currentmodule:: attrs - Same as `attrs.cmp_using`. +.. autofunction:: attrs.cmp_using .. autofunction:: attrs.fields @@ -241,21 +146,17 @@ Helpers .. doctest:: - >>> @attr.s - ... class C(object): - ... x = attr.ib() - ... y = attr.ib() + >>> @define + ... class C: + ... x = field() + ... y = field() >>> attrs.fields(C) - (Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None)) + (Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='x'), Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y')) >>> attrs.fields(C)[1] - Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None) + Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y') >>> attrs.fields(C).y is attrs.fields(C)[1] True -.. function:: attr.fields - - Same as `attrs.fields`. - .. autofunction:: attrs.fields_dict For example: @@ -263,20 +164,16 @@ Helpers .. doctest:: >>> @attr.s - ... class C(object): + ... class C: ... x = attr.ib() ... y = attr.ib() >>> attrs.fields_dict(C) - {'x': Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), 'y': Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None)} + {'x': Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='x'), 'y': Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y')} >>> attr.fields_dict(C)['y'] - Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None) + Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y') >>> attrs.fields_dict(C)['y'] is attrs.fields(C).y True -.. function:: attr.fields_dict - - Same as `attrs.fields_dict`. - .. autofunction:: attrs.has For example: @@ -284,17 +181,13 @@ Helpers .. doctest:: >>> @attr.s - ... class C(object): + ... class C: ... pass >>> attr.has(C) True >>> attr.has(object) False -.. function:: attr.has - - Same as `attrs.has`. - .. autofunction:: attrs.resolve_types For example: @@ -302,12 +195,12 @@ Helpers .. doctest:: >>> import typing - >>> @attrs.define + >>> @define ... class A: ... a: typing.List['A'] ... b: 'B' ... - >>> @attrs.define + >>> @define ... class B: ... a: A ... @@ -322,68 +215,55 @@ Helpers >>> attrs.fields(A).b.type -.. function:: attr.resolve_types - - Same as `attrs.resolve_types`. - .. autofunction:: attrs.asdict For example: .. doctest:: - >>> @attrs.define + >>> @define ... class C: ... x: int ... y: int >>> attrs.asdict(C(1, C(2, 3))) {'x': 1, 'y': {'x': 2, 'y': 3}} -.. autofunction:: attr.asdict - .. autofunction:: attrs.astuple For example: .. doctest:: - >>> @attrs.define + >>> @define ... class C: - ... x = attr.field() - ... y = attr.field() + ... x = field() + ... y = field() >>> attrs.astuple(C(1,2)) (1, 2) -.. autofunction:: attr.astuple - - -``attrs`` includes some handy helpers for filtering the attributes in `attrs.asdict` and `attrs.astuple`: - -.. autofunction:: attrs.filters.include - -.. autofunction:: attrs.filters.exclude +.. module:: attrs.filters -.. function:: attr.filters.include +*attrs* includes helpers for filtering the attributes in `attrs.asdict` and `attrs.astuple`: - Same as `attrs.filters.include`. +.. autofunction:: include -.. function:: attr.filters.exclude - - Same as `attrs.filters.exclude`. +.. autofunction:: exclude See :func:`attrs.asdict` for examples. -All objects from ``attrs.filters`` are also available from ``attr.filters``. +All objects from ``attrs.filters`` are also available from ``attr.filters`` (it's the same module in a different namespace). ---- +.. currentmodule:: attrs + .. autofunction:: attrs.evolve For example: .. doctest:: - >>> @attrs.define + >>> @define ... class C: ... x: int ... y: int @@ -403,19 +283,15 @@ All objects from ``attrs.filters`` are also available from ``attr.filters``. * attributes with ``init=False`` can't be set with ``evolve``. * the usual ``__init__`` validators will validate the new values. -.. function:: attr.evolve - - Same as `attrs.evolve`. - .. autofunction:: attrs.validate For example: .. doctest:: - >>> @attrs.define(on_setattr=attrs.setters.NO_OP) + >>> @define(on_setattr=attrs.setters.NO_OP) ... class C: - ... x = attrs.field(validator=attrs.validators.instance_of(int)) + ... x = field(validator=attrs.validators.instance_of(int)) >>> i = C(1) >>> i.x = "1" >>> attrs.validate(i) @@ -423,26 +299,16 @@ All objects from ``attrs.filters`` are also available from ``attr.filters``. ... TypeError: ("'x' must be (got '1' that is a ).", ...) -.. function:: attr.validate - Same as `attrs.validate`. - - -Validators can be globally disabled if you want to run them only in development and tests but not in production because you fear their performance impact: - -.. autofunction:: set_run_validators - -.. autofunction:: get_run_validators - - -.. _api_validators: +.. _api-validators: Validators ---------- -``attrs`` comes with some common validators in the ``attrs.validators`` module. -All objects from ``attrs.converters`` are also available from ``attr.converters``. +.. module:: attrs.validators +*attrs* comes with some common validators in the ``attrs.validators`` module. +All objects from ``attrs.validators`` are also available from ``attr.validators`` (it's the same module in a different namespace). .. autofunction:: attrs.validators.lt @@ -450,9 +316,9 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` .. doctest:: - >>> @attrs.define + >>> @define ... class C: - ... x = attrs.field(validator=attrs.validators.lt(42)) + ... x = field(validator=attrs.validators.lt(42)) >>> C(41) C(x=41) >>> C(42) @@ -466,9 +332,9 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` .. doctest:: - >>> @attrs.define - ... class C(object): - ... x = attrs.field(validator=attr.validators.le(42)) + >>> @define + ... class C: + ... x = field(validator=attrs.validators.le(42)) >>> C(42) C(x=42) >>> C(43) @@ -482,7 +348,7 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` .. doctest:: - >>> @attrs.define + >>> @define ... class C: ... x = attrs.field(validator=attrs.validators.ge(42)) >>> C(42) @@ -498,9 +364,9 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` .. doctest:: - >>> @attrs.define + >>> @define ... class C: - ... x = attr.field(validator=attrs.validators.gt(42)) + ... x = field(validator=attrs.validators.gt(42)) >>> C(43) C(x=43) >>> C(42) @@ -514,9 +380,9 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` .. doctest:: - >>> @attrs.define + >>> @define ... class C: - ... x = attrs.field(validator=attrs.validators.max_len(4)) + ... x = field(validator=attrs.validators.max_len(4)) >>> C("spam") C(x='spam') >>> C("bacon") @@ -524,15 +390,31 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` ... ValueError: ("Length of 'x' must be <= 4: 5") +.. autofunction:: attrs.validators.min_len + + For example: + + .. doctest:: + + >>> @define + ... class C: + ... x = field(validator=attrs.validators.min_len(1)) + >>> C("bacon") + C(x='bacon') + >>> C("") + Traceback (most recent call last): + ... + ValueError: ("Length of 'x' must be => 1: 0") + .. autofunction:: attrs.validators.instance_of For example: .. doctest:: - >>> @attrs.define + >>> @define ... class C: - ... x = attrs.field(validator=attrs.validators.instance_of(int)) + ... x = field(validator=attrs.validators.instance_of(int)) >>> C(42) C(x=42) >>> C("42") @@ -550,24 +432,24 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` .. doctest:: - >>> import enum - >>> class State(enum.Enum): - ... ON = "on" - ... OFF = "off" - >>> @attrs.define - ... class C: - ... state = attrs.field(validator=attrs.validators.in_(State)) - ... val = attrs.field(validator=attrs.validators.in_([1, 2, 3])) - >>> C(State.ON, 1) - C(state=, val=1) - >>> C("on", 1) - Traceback (most recent call last): - ... - ValueError: 'state' must be in (got 'on') - >>> C(State.ON, 4) - Traceback (most recent call last): - ... - ValueError: 'val' must be in [1, 2, 3] (got 4) + >>> import enum + >>> class State(enum.Enum): + ... ON = "on" + ... OFF = "off" + >>> @define + ... class C: + ... state = field(validator=attrs.validators.in_(State)) + ... val = field(validator=attrs.validators.in_([1, 2, 3])) + >>> C(State.ON, 1) + C(state=, val=1) + >>> C("On", 1) + Traceback (most recent call last): + ... + ValueError: 'state' must be in (got 'On'), Attribute(name='state', default=NOTHING, validator=>, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), , 'on') + >>> C(State.ON, 4) + Traceback (most recent call last): + ... + ValueError: 'val' must be in [1, 2, 3] (got 4), Attribute(name='val', default=NOTHING, validator=, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), [1, 2, 3], 4) .. autofunction:: attrs.validators.provides @@ -577,8 +459,34 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` Thus the following two statements are equivalent:: - x = attrs.field(validator=attrs.validators.and_(v1, v2, v3)) - x = attrs.field(validator=[v1, v2, v3]) + x = field(validator=attrs.validators.and_(v1, v2, v3)) + x = field(validator=[v1, v2, v3]) + +.. autofunction:: attrs.validators.not_ + + For example: + + .. doctest:: + + >>> reserved_names = {"id", "time", "source"} + >>> @define + ... class Measurement: + ... tags = field( + ... validator=attrs.validators.deep_mapping( + ... key_validator=attrs.validators.not_( + ... attrs.validators.in_(reserved_names), + ... msg="reserved tag key", + ... ), + ... value_validator=attrs.validators.instance_of((str, int)), + ... ) + ... ) + >>> Measurement(tags={"source": "universe"}) + Traceback (most recent call last): + ... + ValueError: ("reserved tag key", Attribute(name='tags', default=NOTHING, validator=, capturing (, )>, type=None, kw_only=False), , {'source_': 'universe'}, (, )) + >>> Measurement(tags={"source_": "universe"}) + Measurement(tags={'source_': 'universe'}) + .. autofunction:: attrs.validators.optional @@ -586,9 +494,12 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` .. doctest:: - >>> @attrs.define + >>> @define ... class C: - ... x = attrs.field(validator=attrs.validators.optional(attr.validators.instance_of(int))) + ... x = field( + ... validator=attrs.validators.optional( + ... attrs.validators.instance_of(int) + ... )) >>> C(42) C(x=42) >>> C("42") @@ -605,9 +516,9 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` .. doctest:: - >>> @attrs.define + >>> @define ... class C: - ... x = attrs.field(validator=attrs.validators.is_callable()) + ... x = field(validator=attrs.validators.is_callable()) >>> C(isinstance) C(x=) >>> C("not a callable") @@ -622,10 +533,10 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` .. doctest:: - >>> @attrs.define + >>> @define ... class User: - ... email = attrs.field(validator=attrs.validators.matches_re( - ... "(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)")) + ... email = field(validator=attrs.validators.matches_re( + ... r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)")) >>> User(email="user@example.com") User(email='user@example.com') >>> User(email="user@example.com@test.com") @@ -640,11 +551,11 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` .. doctest:: - >>> @attrs.define + >>> @define ... class C: - ... x = attrs.field(validator=attrs.validators.deep_iterable( - ... member_validator=attrs.validators.instance_of(int), - ... iterable_validator=attrs.validators.instance_of(list) + ... x = field(validator=attrs.validators.deep_iterable( + ... member_validator=attrs.validators.instance_of(int), + ... iterable_validator=attrs.validators.instance_of(list) ... )) >>> C(x=[1, 2, 3]) C(x=[1, 2, 3]) @@ -664,12 +575,12 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` .. doctest:: - >>> @attrs.define + >>> @define ... class C: - ... x = attrs.field(validator=attrs.validators.deep_mapping( - ... key_validator=attrs.validators.instance_of(str), - ... value_validator=attrs.validators.instance_of(int), - ... mapping_validator=attrs.validators.instance_of(dict) + ... x = field(validator=attrs.validators.deep_mapping( + ... key_validator=attrs.validators.instance_of(str), + ... value_validator=attrs.validators.instance_of(int), + ... mapping_validator=attrs.validators.instance_of(dict) ... )) >>> C(x={"a": 1, "b": 2}) C(x={'a': 1, 'b': 2}) @@ -698,16 +609,18 @@ Validators can be both globally and locally disabled: Converters ---------- -All objects from ``attrs.converters`` are also available from ``attr.converters``. +.. module:: attrs.converters + +All objects from ``attrs.converters`` are also available from ``attr.converters`` (it's the same module in a different namespace). .. autofunction:: attrs.converters.pipe - For convenience, it's also possible to pass a list to `attr.ib`'s converter argument. + For convenience, it's also possible to pass a list to `attrs.field` / `attr.ib`'s converter arguments. Thus the following two statements are equivalent:: - x = attr.ib(converter=attr.converter.pipe(c1, c2, c3)) - x = attr.ib(converter=[c1, c2, c3]) + x = attrs.field(converter=attrs.converter.pipe(c1, c2, c3)) + x = attrs.field(converter=[c1, c2, c3]) .. autofunction:: attrs.converters.optional @@ -715,9 +628,9 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` .. doctest:: - >>> @attr.s - ... class C(object): - ... x = attr.ib(converter=attr.converters.optional(int)) + >>> @define + ... class C: + ... x = field(converter=attrs.converters.optional(int)) >>> C(None) C(x=None) >>> C(42) @@ -730,10 +643,10 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` .. doctest:: - >>> @attr.s - ... class C(object): - ... x = attr.ib( - ... converter=attr.converters.default_if_none("") + >>> @define + ... class C: + ... x = field( + ... converter=attrs.converters.default_if_none("") ... ) >>> C(None) C(x='') @@ -745,10 +658,10 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` .. doctest:: - >>> @attr.s - ... class C(object): - ... x = attr.ib( - ... converter=attr.converters.to_bool + >>> @define + ... class C: + ... x = field( + ... converter=attrs.converters.to_bool ... ) >>> C("yes") C(x=True) @@ -766,23 +679,32 @@ All objects from ``attrs.converters`` are also available from ``attr.converters` Setters ------- +.. module:: attrs.setters + These are helpers that you can use together with `attrs.define`'s and `attrs.fields`'s ``on_setattr`` arguments. -All setters in ``attrs.setters`` are also available from ``attr.setters``. +All setters in ``attrs.setters`` are also available from ``attr.setters`` (it's the same module in a different namespace). + +.. autofunction:: frozen +.. autofunction:: validate +.. autofunction:: convert +.. autofunction:: pipe -.. autofunction:: attrs.setters.frozen -.. autofunction:: attrs.setters.validate -.. autofunction:: attrs.setters.convert -.. autofunction:: attrs.setters.pipe -.. autodata:: attrs.setters.NO_OP +.. data:: NO_OP + + Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. + + Does not work in `attrs.setters.pipe` or within lists. + + .. versionadded:: 20.1.0 For example, only ``x`` is frozen here: .. doctest:: - >>> @attrs.define(on_setattr=attr.setters.frozen) + >>> @define(on_setattr=attr.setters.frozen) ... class C: - ... x = attr.field() - ... y = attr.field(on_setattr=attr.setters.NO_OP) + ... x = field() + ... y = field(on_setattr=attr.setters.NO_OP) >>> c = C(1, 2) >>> c.y = 3 >>> c.y @@ -793,34 +715,3 @@ All setters in ``attrs.setters`` are also available from ``attr.setters``. attrs.exceptions.FrozenAttributeError: () N.B. Please use `attrs.define`'s *frozen* argument (or `attrs.frozen`) to freeze whole classes; it is more efficient. - - -Deprecated APIs ---------------- - -.. _version-info: - -To help you write backward compatible code that doesn't throw warnings on modern releases, the ``attr`` module has an ``__version_info__`` attribute as of version 19.2.0. -It behaves similarly to `sys.version_info` and is an instance of `VersionInfo`: - -.. autoclass:: VersionInfo - - With its help you can write code like this: - - >>> if getattr(attr, "__version_info__", (0,)) >= (19, 2): - ... cmp_off = {"eq": False} - ... else: - ... cmp_off = {"cmp": False} - >>> cmp_off == {"eq": False} - True - >>> @attr.s(**cmp_off) - ... class C(object): - ... pass - - ----- - -The serious business aliases used to be called ``attr.attributes`` and ``attr.attr``. -There are no plans to remove them but they shouldn't be used in new code. - -.. autofunction:: assoc diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/changelog.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/changelog.md new file mode 100644 index 0000000000000..3c8d4d8b35682 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/changelog.md @@ -0,0 +1,11 @@ +```{include} ../CHANGELOG.md +:end-before: Changes for the upcoming release can be found +``` + + ```{towncrier-draft-entries} +main + ``` + +```{include} ../CHANGELOG.md +:start-after: towncrier release notes start --> +``` diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/comparison.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/comparison.md new file mode 100644 index 0000000000000..79786e9e19ff2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/comparison.md @@ -0,0 +1,69 @@ +# Comparison + +By default, two instances of *attrs* classes are equal if they have the same type and all their fields are equal. +For that, *attrs* writes `__eq__` and `__ne__` methods for you. + +Additionally, if you pass `order=True`, *attrs* will also create a complete set of ordering methods: `__le__`, `__lt__`, `__ge__`, and `__gt__`. + +Both for equality and order, *attrs* will: + +- Check if the types of the instances you're comparing are equal, +- if so, create a tuple of all field values for each instance, +- and finally perform the desired comparison operation on those tuples. + +[^default]: That's the default if you use the {func}`attr.s` decorator, but not with {func}`~attrs.define`. + +(custom-comparison)= + +## Customization + +As with other features, you can exclude fields from being involved in comparison operations: + +```{doctest} +>>> from attrs import define, field +>>> @define +... class C: +... x: int +... y: int = field(eq=False) + +>>> C(1, 2) == C(1, 3) +True +``` + +Additionally you can also pass a *callable* instead of a bool to both *eq* and *order*. +It is then used as a key function like you may know from {func}`sorted`: + +```{doctest} +>>> @define +... class S: +... x: str = field(eq=str.lower) + +>>> S("foo") == S("FOO") +True + +>>> @define(order=True) +... class C: +... x: str = field(order=int) + +>>> C("10") > C("2") +True +``` + +This is especially useful when you have fields with objects that have atypical comparison properties. +Common examples of such objects are [NumPy arrays](https://github.com/python-attrs/attrs/issues/435). + +To save you unnecessary boilerplate, *attrs* comes with the {func}`attrs.cmp_using` helper to create such functions. +For NumPy arrays it would look like this: + +```python +import numpy + +@define +class C: + an_array = field(eq=attrs.cmp_using(eq=numpy.array_equal)) +``` + +:::{warning} +Please note that *eq* and *order* are set *independently*, because *order* is `False` by default in {func}`~attrs.define` (but not in {func}`attr.s`). +You can set both at once by using the *cmp* argument that we've undeprecated just for this use-case. +::: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/conf.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/conf.py index 0cc80be6a6857..b92354a6fd470 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/conf.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/conf.py @@ -1,6 +1,12 @@ # SPDX-License-Identifier: MIT from importlib import metadata +from pathlib import Path + + +# -- Path setup ----------------------------------------------------------- + +PROJECT_ROOT_DIR = Path(__file__).parents[1].resolve() # -- General configuration ------------------------------------------------ @@ -12,9 +18,8 @@ linkcheck_ignore = [ # We run into GitHub's rate limits. r"https://github.com/.*/(issues|pull)/\d+", - # It never finds the anchor even though it's there. - "https://github.com/microsoft/pyright/blob/main/specs/" - "dataclass_transforms.md#attrs", + # Rate limits and the latest tag is missing anyways on release. + "https://github.com/python-attrs/attrs/tree/.*", ] # In nitpick mode (-n), still ignore any of the following "broken" references @@ -30,13 +35,20 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + "myst_parser", "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.todo", "notfound.extension", + "sphinxcontrib.towncrier", ] +myst_enable_extensions = [ + "colon_fence", + "smartquotes", + "deflist", +] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -58,8 +70,11 @@ # The full version, including alpha/beta/rc tags. release = metadata.version("attrs") -# The short X.Y version. -version = release.rsplit(".", 1)[0] +if "dev" in release: + release = version = "UNRELEASED" +else: + # The short X.Y version. + version = release.rsplit(".", 1)[0] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -82,7 +97,14 @@ "sidebar_hide_name": True, "light_logo": "attrs_logo.svg", "dark_logo": "attrs_logo_white.svg", + "top_of_page_button": None, + "light_css_variables": { + "font-stack": "Inter,sans-serif", + "font-stack--monospace": "BerkeleyMono, MonoLisa, ui-monospace, " + "SFMono-Regular, Menlo, Consolas, Liberation Mono, monospace", + }, } +html_css_files = ["custom.css"] # The name of an image file (within the static path) to use as favicon of the @@ -147,9 +169,15 @@ epub_description = "Python Clases Without Boilerplate" -intersphinx_mapping = { - "https://docs.python.org/3": None, -} +intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} # Allow non-local URIs so we can have images in CHANGELOG etc. suppress_warnings = ["image.nonlocal_uri"] + + +# -- Options for sphinxcontrib.towncrier extension ------------------------ + +towncrier_draft_autoversion_mode = "draft" +towncrier_draft_include_empty = True +towncrier_draft_working_directory = PROJECT_ROOT_DIR +towncrier_draft_config_path = "pyproject.toml" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/examples.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/examples.md new file mode 100644 index 0000000000000..0f8301aa59700 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/examples.md @@ -0,0 +1,762 @@ +# *attrs* by Example + +## Basics + +The simplest possible usage is: + +```{doctest} +>>> from attrs import define, field +>>> @define +... class Empty: +... pass +>>> Empty() +Empty() +>>> Empty() == Empty() +True +>>> Empty() is Empty() +False +``` + +So in other words: *attrs* is useful even without actual attributes! + +But you'll usually want some data on your classes, so let's add some: + +```{doctest} +>>> @define +... class Coordinates: +... x: int +... y: int +``` + +By default, all features are added, so you immediately have a fully functional data class with a nice `repr` string and comparison methods. + +```{doctest} +>>> c1 = Coordinates(1, 2) +>>> c1 +Coordinates(x=1, y=2) +>>> c2 = Coordinates(x=2, y=1) +>>> c2 +Coordinates(x=2, y=1) +>>> c1 == c2 +False +``` + +As shown, the generated `__init__` method allows for both positional and keyword arguments. + +--- + +Unlike Data Classes, *attrs* doesn't force you to use type annotations. +So, the previous example could also have been written as: + +```{doctest} +>>> @define +... class Coordinates: +... x = field() +... y = field() +>>> Coordinates(1, 2) +Coordinates(x=1, y=2) +``` + +:::{caution} +If a class body contains a field that is defined using {func}`attrs.field` (or {func}`attr.ib`), but **lacks a type annotation**, *attrs* switches to a no-typing mode and ignores fields that have type annotations but are not defined using {func}`attrs.field` (or {func}`attr.ib`). +::: + +--- + +For private attributes, *attrs* will strip the leading underscores for keyword arguments: + +```{doctest} +>>> @define +... class C: +... _x: int +>>> C(x=1) +C(_x=1) +``` + +If you want to initialize your private attributes yourself, you can do that too: + +```{doctest} +>>> @define +... class C: +... _x: int = field(init=False, default=42) +>>> C() +C(_x=42) +>>> C(23) +Traceback (most recent call last): + ... +TypeError: __init__() takes exactly 1 argument (2 given) +``` + +If you prefer to expose your privates, you can use keyword argument aliases: + +```{doctest} +>>> @define +... class C: +... _x: int = field(alias="_x") +>>> C(_x=1) +C(_x=1) +``` + +An additional way of defining attributes is supported too. +This is useful in times when you want to enhance classes that are not yours (nice `__repr__` for Django models anyone?): + +```{doctest} +>>> class SomethingFromSomeoneElse: +... def __init__(self, x): +... self.x = x +>>> SomethingFromSomeoneElse = define( +... these={ +... "x": field() +... }, init=False)(SomethingFromSomeoneElse) +>>> SomethingFromSomeoneElse(1) +SomethingFromSomeoneElse(x=1) +``` + +[Subclassing is bad for you](https://www.youtube.com/watch?v=3MNVP9-hglc), but *attrs* will still do what you'd hope for: + +```{doctest} +>>> @define(slots=False) +... class A: +... a: int +... def get_a(self): +... return self.a +>>> @define(slots=False) +... class B: +... b: int +>>> @define(slots=False) +... class C(B, A): +... c: int +>>> i = C(1, 2, 3) +>>> i +C(a=1, b=2, c=3) +>>> i == C(1, 2, 3) +True +>>> i.get_a() +1 +``` + +{term}`Slotted classes `, which are the default for the new APIs, don't play well with multiple inheritance so we don't use them in the example. + +The order of the attributes is defined by the [MRO](https://www.python.org/download/releases/2.3/mro/). + + +### Keyword-only Attributes + +You can also add [keyword-only](https://docs.python.org/3/glossary.html#keyword-only-parameter) attributes: + +```{doctest} +>>> @define +... class A: +... a: int = field(kw_only=True) +>>> A() +Traceback (most recent call last): +... +TypeError: A() missing 1 required keyword-only argument: 'a' +>>> A(a=1) +A(a=1) +``` + +`kw_only` may also be specified at decorator level, and will apply to all attributes: + +```{doctest} +>>> @define(kw_only=True) +... class A: +... a: int +... b: int +>>> A(1, 2) +Traceback (most recent call last): +... +TypeError: __init__() takes 1 positional argument but 3 were given +>>> A(a=1, b=2) +A(a=1, b=2) +``` + +If you create an attribute with `init=False`, the `kw_only` argument is ignored. + +Keyword-only attributes allow subclasses to add attributes without default values, even if the base class defines attributes with default values: + +```{doctest} +>>> @define +... class A: +... a: int = 0 +>>> @define +... class B(A): +... b: int = field(kw_only=True) +>>> B(b=1) +B(a=0, b=1) +>>> B() +Traceback (most recent call last): +... +TypeError: B() missing 1 required keyword-only argument: 'b' +``` + +If you don't set `kw_only=True`, then there is no valid attribute ordering, and you'll get an error: + +```{doctest} +>>> @define +... class A: +... a: int = 0 +>>> @define +... class B(A): +... b: int +Traceback (most recent call last): +... +ValueError: No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: Attribute(name='b', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, converter=None, metadata=mappingproxy({}), type=int, kw_only=False) +``` + +(asdict)= + +## Converting to Collections Types + +When you have a class with data, it often is very convenient to transform that class into a {class}`dict` (for example if you want to serialize it to JSON): + +```{doctest} +>>> from attrs import asdict +>>> asdict(Coordinates(x=1, y=2)) +{'x': 1, 'y': 2} +``` + +Some fields cannot or should not be transformed. +For that, {func}`attrs.asdict` offers a callback that decides whether an attribute should be included: + +```{doctest} +>>> @define +... class User: +... email: str +... password: str + +>>> @define +... class UserList: +... users: list[User] + +>>> asdict(UserList([User("jane@doe.invalid", "s33kred"), +... User("joe@doe.invalid", "p4ssw0rd")]), +... filter=lambda attr, value: attr.name != "password") +{'users': [{'email': 'jane@doe.invalid'}, {'email': 'joe@doe.invalid'}]} +``` + +For the common case where you want to [`include`](attrs.filters.include) or [`exclude`](attrs.filters.exclude) certain types, string name or attributes, *attrs* ships with a few helpers: + +```{doctest} +>>> from attrs import asdict, filters, fields + +>>> @define +... class User: +... login: str +... password: str +... email: str +... id: int + +>>> asdict( +... User("jane", "s33kred", "jane@example.com", 42), +... filter=filters.exclude(fields(User).password, "email", int)) +{'login': 'jane'} + +>>> @define +... class C: +... x: str +... y: str +... z: int + +>>> asdict(C("foo", "2", 3), +... filter=filters.include(int, fields(C).x)) +{'x': 'foo', 'z': 3} + +>>> asdict(C("foo", "2", 3), +... filter=filters.include(fields(C).x, "z")) +{'x': 'foo', 'z': 3} +``` + +:::{note} +Though using string names directly is convenient, mistyping attribute names will silently do the wrong thing and neither Python nor your type checker can help you. +{func}`attrs.fields()` will raise an `AttributeError` when the field doesn't exist while literal string names won't. +Using {func}`attrs.fields()` to get attributes is worth being recommended in most cases. + +```{doctest} +>>> asdict( +... User("jane", "s33kred", "jane@example.com", 42), +... filter=filters.exclude("passwd") +... ) +{'login': 'jane', 'password': 's33kred', 'email': 'jane@example.com', 'id': 42} + +>>> asdict( +... User("jane", "s33kred", "jane@example.com", 42), +... filter=fields(User).passwd +... ) +Traceback (most recent call last): +... +AttributeError: 'UserAttributes' object has no attribute 'passwd'. Did you mean: 'password'? +``` +::: + +Other times, all you want is a tuple and *attrs* won't let you down: + +```{doctest} +>>> import sqlite3 +>>> from attrs import astuple + +>>> @define +... class Foo: +... a: int +... b: int + +>>> foo = Foo(2, 3) +>>> with sqlite3.connect(":memory:") as conn: +... c = conn.cursor() +... c.execute("CREATE TABLE foo (x INTEGER PRIMARY KEY ASC, y)") #doctest: +ELLIPSIS +... c.execute("INSERT INTO foo VALUES (?, ?)", astuple(foo)) #doctest: +ELLIPSIS +... foo2 = Foo(*c.execute("SELECT x, y FROM foo").fetchone()) + + +>>> foo == foo2 +True +``` + +For more advanced transformations and conversions, we recommend you look at a companion library (such as [*cattrs*](https://catt.rs/)). + + +## Defaults + +Sometimes you want to have default values for your initializer. +And sometimes you even want mutable objects as default values (ever accidentally used `def f(arg=[])`?). +*attrs* has you covered in both cases: + +```{doctest} +>>> import collections + +>>> @define +... class Connection: +... socket: int +... @classmethod +... def connect(cls, db_string): +... # ... connect somehow to db_string ... +... return cls(socket=42) + +>>> @define +... class ConnectionPool: +... db_string: str +... pool: collections.deque = Factory(collections.deque) +... debug: bool = False +... def get_connection(self): +... try: +... return self.pool.pop() +... except IndexError: +... if self.debug: +... print("New connection!") +... return Connection.connect(self.db_string) +... def free_connection(self, conn): +... if self.debug: +... print("Connection returned!") +... self.pool.appendleft(conn) +... +>>> cp = ConnectionPool("postgres://localhost") +>>> cp +ConnectionPool(db_string='postgres://localhost', pool=deque([]), debug=False) +>>> conn = cp.get_connection() +>>> conn +Connection(socket=42) +>>> cp.free_connection(conn) +>>> cp +ConnectionPool(db_string='postgres://localhost', pool=deque([Connection(socket=42)]), debug=False) +``` + +More information on why class methods for constructing objects are awesome can be found in this insightful [blog post](https://web.archive.org/web/20210130220433/http://as.ynchrono.us/2014/12/asynchronous-object-initialization.html). + +Default factories can also be set using the `factory` argument to {func}`~attrs.field`, and using a decorator. +The method receives the partially initialized instance which enables you to base a default value on other attributes: + +```{doctest} +>>> @define +... class C: +... x: int = 1 +... y: int = field() +... @y.default +... def _any_name_except_a_name_of_an_attribute(self): +... return self.x + 1 +... z: list = field(factory=list) +>>> C() +C(x=1, y=2, z=[]) +``` + +Please keep in mind that the decorator approach *only* works if the attribute in question has a {func}`~attrs.field` assigned to it. +As a result, annotating an attribute with a type is *not* enough if you use `@default`. + +(examples-validators)= + +## Validators + +Although your initializers should do as little as possible (ideally: just initialize your instance according to the arguments!), it can come in handy to do some kind of validation on the arguments. + +*attrs* offers two ways to define validators for each attribute and it's up to you to choose which one suits your style and project better. + +You can use a decorator: + +```{doctest} +>>> @define +... class C: +... x: int = field() +... @x.validator +... def check(self, attribute, value): +... if value > 42: +... raise ValueError("x must be smaller or equal to 42") +>>> C(42) +C(x=42) +>>> C(43) +Traceback (most recent call last): + ... +ValueError: x must be smaller or equal to 42 +``` + +...or a callable... + +```{doctest} +>>> from attrs import validators + +>>> def x_smaller_than_y(instance, attribute, value): +... if value >= instance.y: +... raise ValueError("'x' has to be smaller than 'y'!") +>>> @define +... class C: +... x: int = field(validator=[validators.instance_of(int), +... x_smaller_than_y]) +... y: int +>>> C(x=3, y=4) +C(x=3, y=4) +>>> C(x=4, y=3) +Traceback (most recent call last): + ... +ValueError: 'x' has to be smaller than 'y'! +``` + +...or both at once: + +```{doctest} +>>> @define +... class C: +... x: int = field(validator=validators.instance_of(int)) +... @x.validator +... def fits_byte(self, attribute, value): +... if not 0 <= value < 256: +... raise ValueError("value out of bounds") +>>> C(128) +C(x=128) +>>> C("128") +Traceback (most recent call last): + ... +TypeError: ("'x' must be (got '128' that is a ).", Attribute(name='x', default=NOTHING, validator=[>, ], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=int, converter=None, kw_only=False), , '128') +>>> C(256) +Traceback (most recent call last): + ... +ValueError: value out of bounds +``` + +Please note that the decorator approach only works if -- and only if! -- the attribute in question has a {func}`~attrs.field` assigned. +Therefore if you use `@validator`, it is *not* enough to annotate said attribute with a type. + +*attrs* ships with a bunch of validators, make sure to [check them out](api-validators) before writing your own: + +```{doctest} +>>> @define +... class C: +... x: int = field(validator=validators.instance_of(int)) +>>> C(42) +C(x=42) +>>> C("42") +Traceback (most recent call last): + ... +TypeError: ("'x' must be (got '42' that is a ).", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=>, type=None, kw_only=False), , '42') +``` + +If using the old-school {func}`attr.s` decorator, validators only run on initialization by default. +If using the newer {func}`attrs.define` and friends, validators run on initialization *and* on attribute setting. +This behavior can be changed using the *on_setattr* argument. + +Check out {ref}`validators` for more details. + + +## Conversion + +Attributes can have a `converter` function specified, which will be called with the attribute's passed-in value to get a new value to use. +This can be useful for doing type-conversions on values that you don't want to force your callers to do. + +```{doctest} +>>> @define +... class C: +... x: int = field(converter=int) +>>> o = C("1") +>>> o.x +1 +>>> o.x = "2" +>>> o.x +2 +``` + +If using the old-school {func}`attr.s` decorator, converters only run on initialization by default. +If using the newer {func}`attrs.define` and friends, converters run on initialization *and* on attribute setting. +This behavior can be changed using the *on_setattr* argument. + +Check out {ref}`converters` for more details. + +(metadata)= + +## Metadata + +All *attrs* attributes may include arbitrary metadata in the form of a read-only dictionary. + +```{doctest} +>>> from attrs import fields + +>>> @define +... class C: +... x = field(metadata={'my_metadata': 1}) +>>> fields(C).x.metadata +mappingproxy({'my_metadata': 1}) +>>> fields(C).x.metadata['my_metadata'] +1 +``` + +Metadata is not used by *attrs*, and is meant to enable rich functionality in third-party libraries. +The metadata dictionary follows the normal dictionary rules: +Keys need to be hashable, and both keys and values are recommended to be immutable. + +If you're the author of a third-party library with *attrs* integration, please see [*Extending Metadata*](extending-metadata). + + +## Types + +*attrs* also allows you to associate a type with an attribute using either the *type* argument to using {pep}`526`-annotations or {func}`attrs.field`/{func}`attr.ib`: + +```{doctest} +>>> @define +... class C: +... x: int +>>> fields(C).x.type + + +>>> import attr +>>> @attr.s +... class C: +... x = attr.ib(type=int) +>>> fields(C).x.type + +``` + +If you don't mind annotating *all* attributes, you can even drop the `attrs.field` and assign default values instead: + +```{doctest} +>>> import typing + +>>> @define +... class AutoC: +... cls_var: typing.ClassVar[int] = 5 # this one is ignored +... l: list[int] = Factory(list) +... x: int = 1 +... foo: str = "every attrib needs a type if auto_attribs=True" +... bar: typing.Any = None +>>> fields(AutoC).l.type +list[int] +>>> fields(AutoC).x.type + +>>> fields(AutoC).foo.type + +>>> fields(AutoC).bar.type +typing.Any +>>> AutoC() +AutoC(l=[], x=1, foo='every attrib needs a type if auto_attribs=True', bar=None) +>>> AutoC.cls_var +5 +``` + +The generated `__init__` method will have an attribute called `__annotations__` that contains this type information. + +If your annotations contain strings (e.g. forward references), +you can resolve these after all references have been defined by using {func}`attrs.resolve_types`. +This will replace the *type* attribute in the respective fields. + +```{doctest} +>>> from attrs import resolve_types + +>>> @define +... class A: +... a: 'list[A]' +... b: 'B' +... +>>> @define +... class B: +... a: A +... +>>> fields(A).a.type +'list[A]' +>>> fields(A).b.type +'B' +>>> resolve_types(A, globals(), locals()) + +>>> fields(A).a.type +list[A] +>>> fields(A).b.type + +``` + +:::{note} +If you find yourself using string type annotations to handle forward references, wrap the entire type annotation in quotes instead of only the type you need a forward reference to (so `'list[A]'` instead of `list['A']`). +This is a limitation of the Python typing system. +::: + +:::{warning} +*attrs* itself doesn't have any features that work on top of type metadata. +However it's useful for writing your own validators or serialization frameworks. +::: + + +## Slots + +{term}`Slotted classes ` have several advantages on CPython. +Defining `__slots__` by hand is tedious, in *attrs* it's just a matter of using {func}`attrs.define` or passing `slots=True` to {func}`attr.s`: + +```{doctest} +>>> @define +... class Coordinates: +... x: int +... y: int + +>>> import attr + +>>> @attr.s(slots=True) +... class Coordinates: +... x: int +... y: int +``` + +{func}`~attrs.define` sets `slots=True` by default. + + +## Immutability + +Sometimes you have instances that shouldn't be changed after instantiation. +Immutability is especially popular in functional programming and is generally a very good thing. +If you'd like to enforce it, *attrs* will try to help: + +```{doctest} +>>> from attrs import frozen + +>>> @frozen +... class C: +... x: int +>>> i = C(1) +>>> i.x = 2 +Traceback (most recent call last): + ... +attrs.exceptions.FrozenInstanceError: can't set attribute +>>> i.x +1 +``` + +Please note that true immutability is impossible in Python but it will [get](how-frozen) you 99% there. +By themselves, immutable classes are useful for long-lived objects that should never change; like configurations for example. + +In order to use them in regular program flow, you'll need a way to easily create new instances with changed attributes. +In Clojure that function is called [*assoc*](https://clojuredocs.org/clojure.core/assoc) and *attrs* shamelessly imitates it: {func}`attrs.evolve`: + +```{doctest} +>>> from attrs import evolve, frozen + +>>> @frozen +... class C: +... x: int +... y: int +>>> i1 = C(1, 2) +>>> i1 +C(x=1, y=2) +>>> i2 = evolve(i1, y=3) +>>> i2 +C(x=1, y=3) +>>> i1 == i2 +False +``` + + +## Other Goodies + +Sometimes you may want to create a class programmatically. +*attrs* gives you {func}`attrs.make_class` for that: + +```{doctest} +>>> from attrs import make_class +>>> @define +... class C1: +... x = field(type=int) +... y = field() +>>> C2 = make_class("C2", {"x": field(type=int), "y": field()}) +>>> fields(C1) == fields(C2) +True +>>> fields(C1).x.type + +``` + +You can still have power over the attributes if you pass a dictionary of name: {func}`~attrs.field` mappings and can pass the same arguments as you can to `@attrs.define`: + +```{doctest} +>>> C = make_class("C", {"x": field(default=42), +... "y": field(default=Factory(list))}, +... repr=False) +>>> i = C() +>>> i # no repr added! +<__main__.C object at ...> +>>> i.x +42 +>>> i.y +[] +``` + +If you need to dynamically make a class with {func}`~attrs.make_class` and it needs to be a subclass of something else than {class}`object`, use the `bases` argument: + +```{doctest} +>>> class D: +... def __eq__(self, other): +... return True # arbitrary example +>>> C = make_class("C", {}, bases=(D,), cmp=False) +>>> isinstance(C(), D) +True +``` + +Sometimes, you want to have your class's `__init__` method do more than just +the initialization, validation, etc. that gets done for you automatically when +using `@define`. +To do this, just define a `__attrs_post_init__` method in your class. +It will get called at the end of the generated `__init__` method. + +```{doctest} +>>> @define +... class C: +... x: int +... y: int +... z: int = field(init=False) +... +... def __attrs_post_init__(self): +... self.z = self.x + self.y +>>> obj = C(x=1, y=2) +>>> obj +C(x=1, y=2, z=3) +``` + +You can exclude single attributes from certain methods: + +```{doctest} +>>> @define +... class C: +... user: str +... password: str = field(repr=False) +>>> C("me", "s3kr3t") +C(user='me') +``` + +Alternatively, to influence how the generated `__repr__()` method formats a specific attribute, specify a custom callable to be used instead of the `repr()` built-in function: + +```{doctest} +>>> @define +... class C: +... user: str +... password: str = field(repr=lambda value: '***') +>>> C("me", "s3kr3t") +C(user='me', password=***) +``` diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/extending.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/extending.md new file mode 100644 index 0000000000000..c6cb5f574bc94 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/extending.md @@ -0,0 +1,314 @@ +# Extending + +Each *attrs*-decorated class has a `__attrs_attrs__` class attribute. +It's a tuple of {class}`attrs.Attribute` carrying metadata about each attribute. + +So it is fairly simple to build your own decorators on top of *attrs*: + +```{doctest} +>>> from attr import define +>>> def print_attrs(cls): +... print(cls.__attrs_attrs__) +... return cls +>>> @print_attrs +... @define +... class C: +... a: int +(Attribute(name='a', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='a'),) +``` + +:::{warning} +The {func}`attrs.define` / {func}`attr.s` decorator **must** be applied first because it puts `__attrs_attrs__` in place! +That means that is has to come *after* your decorator because: + +```python +@a +@b +def f(): + pass +``` + +is just [syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar) for: + +```python +def original_f(): + pass + +f = a(b(original_f)) +``` +::: + + +## Wrapping the Decorator + +A more elegant way can be to wrap *attrs* altogether and build a class [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) on top of it. + +An example for that is the package [*environ-config*](https://github.com/hynek/environ-config) that uses *attrs* under the hood to define environment-based configurations declaratively without exposing *attrs* APIs at all. + +Another common use case is to overwrite *attrs*'s defaults. + + +### Mypy + +Unfortunately, decorator wrapping currently [confuses](https://github.com/python/mypy/issues/5406) mypy's *attrs* plugin. +At the moment, the best workaround is to hold your nose, write a fake *Mypy* plugin, and mutate a bunch of global variables: + +```python +from mypy.plugin import Plugin +from mypy.plugins.attrs import ( + attr_attrib_makers, + attr_class_makers, + attr_dataclass_makers, +) + +# These work just like `attr.dataclass`. +attr_dataclass_makers.add("my_module.method_looks_like_attr_dataclass") + +# This works just like `attr.s`. +attr_class_makers.add("my_module.method_looks_like_attr_s") + +# These are our `attr.ib` makers. +attr_attrib_makers.add("my_module.method_looks_like_attrib") + +class MyPlugin(Plugin): + # Our plugin does nothing but it has to exist so this file gets loaded. + pass + + +def plugin(version): + return MyPlugin +``` + +Then tell *Mypy* about your plugin using your project's `mypy.ini`: + +```ini +[mypy] +plugins= +``` + +:::{warning} +Please note that it is currently *impossible* to let mypy know that you've changed defaults like *eq* or *order*. +You can only use this trick to tell *Mypy* that a class is actually an *attrs* class. +::: + + +### Pyright + +Generic decorator wrapping is supported in [*Pyright*](https://github.com/microsoft/pyright) via `typing.dataclass_transform` / {pep}`689`. + +For a custom wrapping of the form: + +``` +@typing.dataclass_transform(field_specifiers=(attr.attrib, attr.field)) +def custom_define(f): + return attr.define(f) +``` + +## Types + +*attrs* offers two ways of attaching type information to attributes: + +- {pep}`526` annotations, +- and the *type* argument to {func}`attr.ib`. + +This information is available to you: + +```{doctest} +>>> from attr import attrib, define, field, fields +>>> @define +... class C: +... x: int = field() +... y = attrib(type=str) +>>> fields(C).x.type + +>>> fields(C).y.type + +``` + +Currently, *attrs* doesn't do anything with this information but it's very useful if you'd like to write your own validators or serializers! + +(extending-metadata)= + +## Metadata + +If you're the author of a third-party library with *attrs* integration, you may want to take advantage of attribute metadata. + +Here are some tips for effective use of metadata: + +- Try making your metadata keys and values immutable. + This keeps the entire {class}`~attrs.Attribute` instances immutable too. + +- To avoid metadata key collisions, consider exposing your metadata keys from your modules.: + + ```python + from mylib import MY_METADATA_KEY + + @define + class C: + x = field(metadata={MY_METADATA_KEY: 1}) + ``` + + Metadata should be composable, so consider supporting this approach even if you decide implementing your metadata in one of the following ways. + +- Expose `field` wrappers for your specific metadata. + This is a more graceful approach if your users don't require metadata from other libraries. + + ```{doctest} + >>> from attrs import fields, NOTHING + >>> MY_TYPE_METADATA = '__my_type_metadata' + >>> + >>> def typed( + ... cls, default=NOTHING, validator=None, repr=True, + ... eq=True, order=None, hash=None, init=True, metadata=None, + ... converter=None + ... ): + ... metadata = metadata or {} + ... metadata[MY_TYPE_METADATA] = cls + ... return field( + ... default=default, validator=validator, repr=repr, + ... eq=eq, order=order, hash=hash, init=init, + ... metadata=metadata, converter=converter + ... ) + >>> + >>> @define + ... class C: + ... x: int = typed(int, default=1, init=False) + >>> fields(C).x.metadata[MY_TYPE_METADATA] + + ``` + +(transform-fields)= + +## Automatic Field Transformation and Modification + +*attrs* allows you to automatically modify or transform the class' fields while the class is being created. +You do this by passing a *field_transformer* hook to {func}`~attrs.define` (and friends). +Its main purpose is to automatically add converters to attributes based on their type to aid the development of API clients and other typed data loaders. + +This hook must have the following signature: + +```{eval-rst} +.. function:: your_hook(cls: type, fields: list[attrs.Attribute]) -> list[attrs.Attribute] + :noindex: +``` + +- *cls* is your class right *before* it is being converted into an attrs class. + This means it does not yet have the `__attrs_attrs__` attribute. +- *fields* is a list of all `attrs.Attribute` instances that will later be set to `__attrs_attrs__`. + You can modify these attributes any way you want: + You can add converters, change types, and even remove attributes completely or create new ones! + +For example, let's assume that you really don't like floats: + +```{doctest} +>>> def drop_floats(cls, fields): +... return [f for f in fields if f.type not in {float, 'float'}] +... +>>> @frozen(field_transformer=drop_floats) +... class Data: +... a: int +... b: float +... c: str +... +>>> Data(42, "spam") +Data(a=42, c='spam') +``` + +A more realistic example would be to automatically convert data that you, e.g., load from JSON: + +```{doctest} +>>> from datetime import datetime +>>> +>>> def auto_convert(cls, fields): +... results = [] +... for field in fields: +... if field.converter is not None: +... results.append(field) +... continue +... if field.type in {datetime, 'datetime'}: +... converter = (lambda d: datetime.fromisoformat(d) if isinstance(d, str) else d) +... else: +... converter = None +... results.append(field.evolve(converter=converter)) +... return results +... +>>> @frozen(field_transformer=auto_convert) +... class Data: +... a: int +... b: str +... c: datetime +... +>>> from_json = {"a": 3, "b": "spam", "c": "2020-05-04T13:37:00"} +>>> Data(**from_json) # **** +Data(a=3, b='spam', c=datetime.datetime(2020, 5, 4, 13, 37)) +``` + +Or, perhaps you would prefer to generate dataclass-compatible `__init__` signatures via a default field *alias*. +Note, *field_transformer* operates on {class}`attrs.Attribute` instances before the default private-attribute handling is applied so explicit user-provided aliases can be detected. + +```{doctest} +>>> def dataclass_names(cls, fields): +... return [ +... field.evolve(alias=field.name) +... if not field.alias +... else field +... for field in fields +... ] +... +>>> @frozen(field_transformer=dataclass_names) +... class Data: +... public: int +... _private: str +... explicit: str = field(alias="aliased_name") +... +>>> Data(public=42, _private="spam", aliased_name="yes") +Data(public=42, _private='spam', explicit='yes') +``` + +## Customize Value Serialization in `asdict()` + +*attrs* allows you to serialize instances of *attrs* classes to dicts using the {func}`attrs.asdict` function. +However, the result can not always be serialized since most data types will remain as they are: + +```{doctest} +>>> import json +>>> import datetime +>>> from attrs import asdict +>>> +>>> @frozen +... class Data: +... dt: datetime.datetime +... +>>> data = asdict(Data(datetime.datetime(2020, 5, 4, 13, 37))) +>>> data +{'dt': datetime.datetime(2020, 5, 4, 13, 37)} +>>> json.dumps(data) +Traceback (most recent call last): + ... +TypeError: Object of type datetime is not JSON serializable +``` + +To help you with this, {func}`~attrs.asdict` allows you to pass a *value_serializer* hook. +It has the signature + +```{eval-rst} +.. function:: your_hook(inst: type, field: attrs.Attribute, value: typing.Any) -> typing.Any + :noindex: +``` + +```{doctest} +>>> from attr import asdict +>>> def serialize(inst, field, value): +... if isinstance(value, datetime.datetime): +... return value.isoformat() +... return value +... +>>> data = asdict( +... Data(datetime.datetime(2020, 5, 4, 13, 37)), +... value_serializer=serialize, +... ) +>>> data +{'dt': '2020-05-04T13:37:00'} +>>> json.dumps(data) +'{"dt": "2020-05-04T13:37:00"}' +``` diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/glossary.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/glossary.md new file mode 100644 index 0000000000000..6b09a3ad4d12a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/glossary.md @@ -0,0 +1,108 @@ +# Glossary + +:::{glossary} +dunder methods + "Dunder" is a contraction of "double underscore". + + It's methods like `__init__` or `__eq__` that are sometimes also called *magic methods* or it's said that they implement an *object protocol*. + + In spoken form, you'd call `__init__` just "dunder init". + + Its first documented use is a [mailing list posting](https://mail.python.org/pipermail/python-list/2002-September/155836.html) by Mark Jackson from 2002. + +dict classes + A regular class whose attributes are stored in the {attr}`object.__dict__` attribute of every single instance. + This is quite wasteful especially for objects with very few data attributes and the space consumption can become significant when creating large numbers of instances. + + This is the type of class you get by default both with and without *attrs* (except with the next APIs {func}`attrs.define()`, [`attrs.mutable()`](attrs.mutable), and [`attrs.frozen()`](attrs.frozen)). + +slotted classes + A class whose instances have no {attr}`object.__dict__` attribute and [define](https://docs.python.org/3/reference/datamodel.html#slots) their attributes in a `object.__slots__` attribute instead. + In *attrs*, they are created by passing `slots=True` to `@attr.s` (and are on by default in {func}`attrs.define()`, [`attrs.mutable()`](attrs.mutable), and [`attrs.frozen()`](attrs.frozen)). + + Their main advantage is that they use less memory on CPython[^pypy] and are slightly faster. + + However, they also come with several possibly surprising gotchas: + + - Slotted classes don't allow for any other attribute to be set except for those defined in one of the class' hierarchies `__slots__`: + + ```{doctest} + >>> from attr import define + >>> @define + ... class Coordinates: + ... x: int + ... y: int + ... + >>> c = Coordinates(x=1, y=2) + >>> c.z = 3 + Traceback (most recent call last): + ... + AttributeError: 'Coordinates' object has no attribute 'z' + ``` + + - Slotted classes can inherit from other classes just like non-slotted classes, but some of the benefits of slotted classes are lost if you do that. + If you must inherit from other classes, try to inherit only from other slotted classes. + + - However, [it's not possible](https://docs.python.org/3/reference/datamodel.html#slots) to inherit from more than one class that has attributes in `__slots__` (you will get an `TypeError: multiple bases have instance lay-out conflict`). + + - It's not possible to monkeypatch methods on slotted classes. + This can feel limiting in test code, however the need to monkeypatch your own classes is usually a design smell. + + If you really need to monkeypatch an instance in your tests, but don't want to give up on the advantages of slotted classes in production code, you can always subclass a slotted class as a dict class with no further changes and all the limitations go away: + + ```{doctest} + >>> import unittest.mock + >>> @define + ... class Slotted: + ... x: int + ... + ... def method(self): + ... return self.x + >>> s = Slotted(42) + >>> s.method() + 42 + >>> with unittest.mock.patch.object(s, "method", return_value=23): + ... pass + Traceback (most recent call last): + ... + AttributeError: 'Slotted' object attribute 'method' is read-only + >>> @define(slots=False) + ... class Dicted(Slotted): + ... pass + >>> d = Dicted(42) + >>> d.method() + 42 + >>> with unittest.mock.patch.object(d, "method", return_value=23): + ... assert 23 == d.method() + ``` + + - Slotted classes must implement {meth}`__getstate__ ` and {meth}`__setstate__ ` to be serializable with {mod}`pickle` protocol 0 and 1. + Therefore, *attrs* creates these methods automatically for slotted classes. + + :::{note} + When decorating with `@attr.s(slots=True)` and the class already implements the {meth}`__getstate__ ` and {meth}`__setstate__ ` methods, they will be *overwritten* by *attrs* autogenerated implementation by default. + + This can be avoided by setting `@attr.s(getstate_setstate=False)` or by setting `@attr.s(auto_detect=True)`. + + {func}`~attrs.define` sets `auto_detect=True` by default. + ::: + + Also, [think twice](https://www.youtube.com/watch?v=7KnfGDajDQw) before using {mod}`pickle`. + + - Slotted classes are weak-referenceable by default. + This can be disabled in CPython by passing `weakref_slot=False` to `@attr.s` [^pypyweakref]. + + - Since it's currently impossible to make a class slotted after it's been created, *attrs* has to replace your class with a new one. + While it tries to do that as graciously as possible, certain metaclass features like {meth}`object.__init_subclass__` do not work with slotted classes. + + - The {attr}`class.__subclasses__` attribute needs a garbage collection run (which can be manually triggered using {func}`gc.collect`), for the original class to be removed. + See issue [#407](https://github.com/python-attrs/attrs/issues/407) for more details. + + - Pickling of slotted classes will fail if you define a class with missing attributes. + + This situation can occur if you define an `attrs.field(init=False)` and don't set the attribute by hand before pickling. +::: + +[^pypy]: On PyPy, there is no memory advantage in using slotted classes. + +[^pypyweakref]: On PyPy, slotted classes are naturally weak-referenceable so `weakref_slot=False` has no effect. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/hashing.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/hashing.md new file mode 100644 index 0000000000000..231d818af6a1c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/hashing.md @@ -0,0 +1,85 @@ +# Hashing + +## Hash Method Generation + +:::{warning} +The overarching theme is to never set the `@attrs.define(unsafe_hash=X)` parameter yourself. +Leave it at `None` which means that *attrs* will do the right thing for you, depending on the other parameters: + +- If you want to make objects hashable by value: use `@define(frozen=True)`. +- If you want hashing and equality by object identity: use `@define(eq=False)` + +Setting `unsafe_hash` yourself can have unexpected consequences so we recommend to tinker with it only if you know exactly what you're doing. +::: + +Under certain circumstances, it's necessary for objects to be *hashable*. +For example if you want to put them into a {class}`set` or if you want to use them as keys in a {class}`dict`. + +The *hash* of an object is an integer that represents the contents of an object. +It can be obtained by calling {func}`hash` on an object and is implemented by writing a `__hash__` method for your class. + +*attrs* will happily write a `__hash__` method for you [^fn1], however it will *not* do so by default. +Because according to the [definition](https://docs.python.org/3/glossary.html#term-hashable) from the official Python docs, the returned hash has to fulfill certain constraints: + +[^fn1]: The hash is computed by hashing a tuple that consists of a unique id for the class plus all attribute values. + +1. Two objects that are equal, **must** have the same hash. + This means that if `x == y`, it *must* follow that `hash(x) == hash(y)`. + + By default, Python classes are compared *and* hashed by their `id`. + That means that every instance of a class has a different hash, no matter what attributes it carries. + + It follows that the moment you (or *attrs*) change the way equality is handled by implementing `__eq__` which is based on attribute values, this constraint is broken. + For that reason Python 3 will make a class that has customized equality unhashable. + Python 2 on the other hand will happily let you shoot your foot off. + Unfortunately, *attrs* still mimics (otherwise unsupported) Python 2's behavior for backward-compatibility reasons if you set `unsafe_hash=False`. + + The *correct way* to achieve hashing by id is to set `@define(eq=False)`. + Setting `@define(unsafe_hash=False)` (which implies `eq=True`) is almost certainly a *bug*. + + :::{warning} + Be careful when subclassing! + Setting `eq=False` on a class whose base class has a non-default `__hash__` method will *not* make *attrs* remove that `__hash__` for you. + + It is part of *attrs*'s philosophy to only *add* to classes so you have the freedom to customize your classes as you wish. + So if you want to *get rid* of methods, you'll have to do it by hand. + + The easiest way to reset `__hash__` on a class is adding `__hash__ = object.__hash__` in the class body. + ::: + +2. If two objects are not equal, their hash **should** be different. + + While this isn't a requirement from a standpoint of correctness, sets and dicts become less effective if there are a lot of identical hashes. + The worst case is when all objects have the same hash which turns a set into a list. + +3. The hash of an object **must not** change. + + If you create a class with `@define(frozen=True)` this is fulfilled by definition, therefore *attrs* will write a `__hash__` function for you automatically. + You can also force it to write one with `unsafe_hash=True` but then it's *your* responsibility to make sure that the object is not mutated. + + This point is the reason why mutable structures like lists, dictionaries, or sets aren't hashable while immutable ones like tuples or `frozenset`s are: + point 1 and 2 require that the hash changes with the contents but point 3 forbids it. + +For a more thorough explanation of this topic, please refer to this blog post: [*Python Hashes and Equality*](https://hynek.me/articles/hashes-and-equality/). + +:::{note} +Please note that the `unsafe_hash` argument's original name was `hash` but was changed to conform with {pep}`681` in 22.2.0. +The old argument name is still around and will **not** be removed -- but setting `unsafe_hash` takes precedence over `hash`. +The field-level argument is still called `hash` and will remain so. +::: + + +## Hashing and Mutability + +Changing any field involved in hash code computation after the first call to `__hash__` (typically this would be after its insertion into a hash-based collection) can result in silent bugs. +Therefore, it is strongly recommended that hashable classes be `frozen`. +Beware, however, that this is not a complete guarantee of safety: +if a field points to an object and that object is mutated, the hash code may change, but `frozen` will not protect you. + + +## Hash Code Caching + +Some objects have hash codes which are expensive to compute. +If such objects are to be stored in hash-based collections, it can be useful to compute the hash codes only once and then store the result on the object to make future hash code requests fast. +To enable caching of hash codes, pass `@define(cache_hash=True)`. +This may only be done if *attrs* is already generating a hash function for the object. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/how-does-it-work.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/how-does-it-work.md new file mode 100644 index 0000000000000..7acc8121322b2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/how-does-it-work.md @@ -0,0 +1,118 @@ +(how)= + +# How Does It Work? + +## Boilerplate + +*attrs* isn't the first library that aims to simplify class definition in Python. +But its **declarative** approach combined with **no runtime overhead** lets it stand out. + +Once you apply the `@attrs.define` (or `@attr.s`) decorator to a class, *attrs* searches the class object for instances of `attr.ib`s. +Internally they're a representation of the data passed into `attr.ib` along with a counter to preserve the order of the attributes. +Alternatively, it's possible to define them using {doc}`types`. + +In order to ensure that subclassing works as you'd expect it to work, *attrs* also walks the class hierarchy and collects the attributes of all base classes. +Please note that *attrs* does *not* call `super()` *ever*. +It will write {term}`dunder methods` to work on *all* of those attributes which also has performance benefits due to fewer function calls. + +Once *attrs* knows what attributes it has to work on, it writes the requested {term}`dunder methods` and -- depending on whether you wish to have a {term}`dict ` or {term}`slotted ` class -- creates a new class for you (`slots=True`) or attaches them to the original class (`slots=False`). +While creating new classes is more elegant, we've run into several edge cases surrounding metaclasses that make it impossible to go this route unconditionally. + +To be very clear: if you define a class with a single attribute without a default value, the generated `__init__` will look *exactly* how you'd expect: + +```{doctest} +>>> import inspect +>>> from attrs import define +>>> @define +... class C: +... x: int +>>> print(inspect.getsource(C.__init__)) +def __init__(self, x): + self.x = x + +``` + +No magic, no meta programming, no expensive introspection at runtime. + +--- + +Everything until this point happens exactly *once* when the class is defined. +As soon as a class is done, it's done. +And it's just a regular Python class like any other, except for a single `__attrs_attrs__` attribute that *attrs* uses internally. +Much of the information is accessible via {func}`attrs.fields` and other functions which can be used for introspection or for writing your own tools and decorators on top of *attrs* (like {func}`attrs.asdict`). + +And once you start instantiating your classes, *attrs* is out of your way completely. + +This **static** approach was very much a design goal of *attrs* and what I strongly believe makes it distinct. + +(how-frozen)= + +## Immutability + +In order to give you immutability, *attrs* will attach a `__setattr__` method to your class that raises an {class}`attrs.exceptions.FrozenInstanceError` whenever anyone tries to set an attribute. + +The same is true if you choose to freeze individual attributes using the {obj}`attrs.setters.frozen` *on_setattr* hook -- except that the exception becomes {class}`attrs.exceptions.FrozenAttributeError`. + +Both exceptions subclass {class}`attrs.exceptions.FrozenError`. + +--- + +Depending on whether a class is a dict class or a slotted class, *attrs* uses a different technique to circumvent that limitation in the `__init__` method. + +Once constructed, frozen instances don't differ in any way from regular ones except that you cannot change its attributes. + +### Dict Classes + +Dict classes -- i.e. regular classes -- simply assign the value directly into the class' eponymous `__dict__` (and there's nothing we can do to stop the user to do the same). + +The performance impact is negligible. + +### Slotted Classes + +Slotted classes are more complicated. +Here it uses (an aggressively cached) {meth}`object.__setattr__` to set your attributes. +This is (still) slower than a plain assignment: + +```none +$ pyperf timeit --rigorous \ + -s "import attr; C = attr.make_class('C', ['x', 'y', 'z'], slots=True)" \ + "C(1, 2, 3)" +......................................... +Mean +- std dev: 228 ns +- 18 ns + +$ pyperf timeit --rigorous \ + -s "import attr; C = attr.make_class('C', ['x', 'y', 'z'], slots=True, frozen=True)" \ + "C(1, 2, 3)" +......................................... +Mean +- std dev: 425 ns +- 16 ns +``` + +So on a laptop computer the difference is about 200 nanoseconds (1 second is 1,000,000,000 nanoseconds). +It's certainly something you'll feel in a hot loop but shouldn't matter in normal code. +Pick what's more important to you. + +### Summary + +You should avoid instantiating lots of frozen slotted classes (i.e. `@frozen`) in performance-critical code. + +Frozen dict classes have barely a performance impact, unfrozen slotted classes are even *faster* than unfrozen dict classes (i.e. regular classes). + + +(how-slotted-cached_property)= + +## Cached Properties on Slotted Classes. + +By default, the standard library `functools.cached_property` decorator does not work on slotted classes, +because it requires a `__dict__` to store the cached value. +This could be surprising when uses *attrs*, as makes using slotted classes so easy, +so attrs will convert `functools.cached_property` decorated methods, when constructing slotted classes. + +Getting this working is achieved by: +* Adding names to `__slots__` for the wrapped methods. +* Adding a `__getattr__` method to set values on the wrapped methods. + +For most users this should mean that it works transparently. + +Note that the implementation does not guarantee that the wrapped method is called +only once in multi-threaded usage. This matches the implementation of `cached_property` +in python v3.12. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/index.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/index.md new file mode 100644 index 0000000000000..ad92b5a398257 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/index.md @@ -0,0 +1,89 @@ +# *attrs*: Classes Without Boilerplate + +Release **{sub-ref}`release`** ([What's new?](changelog.md)) + +```{include} ../README.md +:start-after: 'teaser-begin -->' +:end-before: '' +:end-before: '## Project Information' +``` + + +## Philosophy + +**It's about regular classes.** + +: *attrs* is for creating well-behaved classes with a type, attributes, methods, and everything that comes with a class. + It can be used for data-only containers like `namedtuple`s or `types.SimpleNamespace` but they're just a sub-genre of what *attrs* is good for. + + +**The class belongs to the users.** + +: You define a class and *attrs* adds static methods to that class based on the attributes you declare. + The end. + It doesn't add metaclasses. + It doesn't add classes you've never heard of to your inheritance tree. + An *attrs* class in runtime is indistinguishable from a regular class: because it *is* a regular class with a few boilerplate-y methods attached. + + +**Be light on API impact.** + +: As convenient as it seems at first, *attrs* will *not* tack on any methods to your classes except for the {term}`dunder ones `. + Hence all the useful [tools](helpers) that come with *attrs* live in functions that operate on top of instances. + Since they take an *attrs* instance as their first argument, you can attach them to your classes with one line of code. + + +**Performance matters.** + +: *attrs* runtime impact is very close to zero because all the work is done when the class is defined. + Once you're instantiating it, *attrs* is out of the picture completely. + + +**No surprises.** + +: *attrs* creates classes that arguably work the way a Python beginner would reasonably expect them to work. + It doesn't try to guess what you mean because explicit is better than implicit. + It doesn't try to be clever because software shouldn't be clever. + +Check out {doc}`how-does-it-work` if you'd like to know how it achieves all of the above. + + +## What *attrs* Is Not + +*attrs* does *not* invent some kind of magic system that pulls classes out of its hat using meta classes, runtime introspection, and shaky interdependencies. + +All *attrs* does is: + +1. Take your declaration, +2. write {term}`dunder methods` based on that information, +3. and attach them to your class. + +It does *nothing* dynamic at runtime, hence zero runtime overhead. +It's still *your* class. +Do with it as you please. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/types.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/types.md new file mode 100644 index 0000000000000..5ab7146f6e9bb --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/types.md @@ -0,0 +1,111 @@ +# Type Annotations + +*attrs* comes with first-class support for type annotations for both {pep}`526` and legacy syntax. + +However they will forever remain *optional*, therefore the example from the README could also be written as: + +```{doctest} +>>> from attrs import define, field + +>>> @define +... class SomeClass: +... a_number = field(default=42) +... list_of_numbers = field(factory=list) + +>>> sc = SomeClass(1, [1, 2, 3]) +>>> sc +SomeClass(a_number=1, list_of_numbers=[1, 2, 3]) +``` + +You can choose freely between the approaches, but please remember that if you choose to use type annotations, you **must** annotate **all** attributes! + +:::{caution} +If you define a class with a {func}`attrs.field` that **lacks** a type annotation, *attrs* will **ignore** other fields that have a type annotation, but are not defined using {func}`attrs.field`: + +```{doctest} +>>> @define +... class SomeClass: +... a_number = field(default=42) +... another_number: int = 23 +>>> SomeClass() +SomeClass(a_number=42) +``` +::: + +Even when going all-in on type annotations, you will need {func}`attrs.field` for some advanced features though. + +One of those features are the decorator-based features like defaults. +It's important to remember that *attrs* doesn't do any magic behind your back. +All the decorators are implemented using an object that is returned by the call to {func}`attrs.field`. + +Attributes that only carry a class annotation do not have that object so trying to call a method on it will inevitably fail. + +--- + +Please note that types -- regardless how added -- are *only metadata* that can be queried from the class and they aren't used for anything out of the box! + +Because Python does not allow references to a class object before the class is defined, +types may be defined as string literals, so-called *forward references* ({pep}`526`). +You can enable this automatically for a whole module by using `from __future__ import annotations` ({pep}`563`) as of Python 3.7. +In this case *attrs* simply puts these string literals into the `type` attributes. +If you need to resolve these to real types, you can call {func}`attrs.resolve_types` which will update the attribute in place. + +In practice though, types show their biggest usefulness in combination with tools like [*Mypy*], [*pytype*], or [*Pyright*] that have dedicated support for *attrs* classes. + +The addition of static types is certainly one of the most exciting features in the Python ecosystem and helps you write *correct* and *verified self-documenting* code. + +If you don't know where to start, Carl Meyer gave a great talk on [*Type-checked Python in the Real World*](https://www.youtube.com/watch?v=pMgmKJyWKn8) at PyCon US 2018 that will help you to get started in no time. + + +## Mypy + +While having a nice syntax for type metadata is great, it's even greater that [*Mypy*] as of 0.570 ships with a dedicated *attrs* plugin which allows you to statically check your code. + +Imagine you add another line that tries to instantiate the defined class using `SomeClass("23")`. +Mypy will catch that error for you: + +```console +$ mypy t.py +t.py:12: error: Argument 1 to "SomeClass" has incompatible type "str"; expected "int" +``` + +This happens *without* running your code! + +And it also works with *both* Python 2-style annotation styles. +To *Mypy*, this code is equivalent to the one above: + +```python +@attr.s +class SomeClass: + a_number = attr.ib(default=42) # type: int + list_of_numbers = attr.ib(factory=list, type=list[int]) +``` + + +## Pyright + +*attrs* provides support for [*Pyright*] through the `dataclass_transform` / {pep}`681` specification. +This provides static type inference for a subset of *attrs* equivalent to standard-library {mod}`dataclasses`, +and requires explicit type annotations using the {func}`attrs.define` or `@attr.s(auto_attribs=True)` API. + +Given the following definition, *Pyright* will generate static type signatures for `SomeClass` attribute access, `__init__`, `__eq__`, and comparison methods: + +``` +@attr.define +class SomeClass: + a_number: int = 42 + list_of_numbers: list[int] = attr.field(factory=list) +``` + +:::{warning} +The *Pyright* inferred types are a tiny subset of those supported by *Mypy*, including: + +- The `attrs.frozen` decorator is not typed with frozen attributes, which are properly typed via `attrs.define(frozen=True)`. + +Your constructive feedback is welcome in both [attrs#795](https://github.com/python-attrs/attrs/issues/795) and [pyright#1782](https://github.com/microsoft/pyright/discussions/1782). +Generally speaking, the decision on improving *attrs* support in *Pyright* is entirely Microsoft's prerogative, though. +::: + +[*Mypy*]: http://mypy-lang.org +[*Pyright*]: https://github.com/microsoft/pyright +[*pytype*]: https://google.github.io/pytype/ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/w3c-import.log new file mode 100644 index 0000000000000..4472a58771c38 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/w3c-import.log @@ -0,0 +1,34 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/Makefile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/api-attr.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/api.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/changelog.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/comparison.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/conf.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/examples.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/extending.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/glossary.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/hashing.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/how-does-it-work.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/index.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/init.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/license.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/names.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/overview.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/types.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/why.md diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/why.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/why.md new file mode 100644 index 0000000000000..eeba9db5857f0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/docs/why.md @@ -0,0 +1,300 @@ +# Why not… + +If you'd like third party's account why *attrs* is great, have a look at Glyph's [*The One Python Library Everyone Needs*](https://glyph.twistedmatrix.com/2016/08/attrs.html). +It predates type annotations and hence Data Classes, but it masterfully illustrates the appeal of class-building packages. + + +## … Data Classes? + +{pep}`557` added Data Classes to [Python 3.7](https://docs.python.org/3.7/whatsnew/3.7.html#dataclasses) that resemble *attrs* in many ways. + +They are the result of the Python community's [wish](https://mail.python.org/pipermail/python-ideas/2017-May/045618.html) to have an easier way to write classes in the standard library that doesn't carry the problems of `namedtuple`s. +To that end, *attrs* and its developers were involved in the PEP process and while we may disagree with some minor decisions that have been made, it's a fine library and if it stops you from abusing `namedtuple`s, they are a huge win. + +Nevertheless, there are still reasons to prefer *attrs* over Data Classes. +Whether they're relevant to *you* depends on your circumstances: + +- Data Classes are *intentionally* less powerful than *attrs*. + There is a long list of features that were sacrificed for the sake of simplicity and while the most obvious ones are validators, converters, {ref}`equality customization `, or {doc}`extensibility ` in general, it permeates throughout all APIs. + + On the other hand, Data Classes currently do not offer any significant feature that *attrs* doesn't already have. + +- *attrs* supports all mainstream Python versions including PyPy. + +- *attrs* doesn't force type annotations on you if you don't like them. + +- But since it **also** supports typing, it's the best way to embrace type hints *gradually*, too. + +- While Data Classes are implementing features from *attrs* every now and then, their presence is dependent on the Python version, not the package version. + For example, support for `__slots__` has only been added in Python 3.10, but it doesn’t do cell rewriting and therefore doesn’t support bare calls to `super()`. + This may or may not be fixed in later Python releases, but handling all these differences is especially painful for PyPI packages that support multiple Python versions. + And of course, this includes possible implementation bugs. + +- *attrs* can and will move faster. + We are not bound to any release schedules and we have a clear deprecation policy. + + One of the [reasons](https://peps.python.org/pep-0557/#why-not-just-use-attrs) to not vendor *attrs* in the standard library was to not impede *attrs*'s future development. + +One way to think about *attrs* vs Data Classes is that *attrs* is a fully-fledged toolkit to write powerful classes while Data Classes are an easy way to get a class with some attributes. +Basically what *attrs* was in 2015. + + +## … Pydantic? + +Pydantic is first and foremost a *data validation & type coercion library*. +As such, it is a capable complement to class building libraries like *attrs* (or Data Classes!) for parsing and validating untrusted data. + +However, as convenient as it might be, using it for your business or data layer [is problematic in several ways](https://threeofwands.com/why-i-use-attrs-instead-of-pydantic/): +Is it really necessary to re-validate all your objects while reading them from a trusted database? +In the parlance of [*Form, Command, and Model Validation*](https://verraes.net/2015/02/form-command-model-validation/), Pydantic is the right tool for *Commands*. + +[*Separation of concerns*](https://en.wikipedia.org/wiki/Separation_of_concerns) feels tedious at times, but it's one of those things that you get to appreciate once you've shot your own foot often enough. + +*attrs* emphatically does **not** try to be a validation library, but a toolkit to write well-behaved classes like you would write yourself. +If you'd like a powerful library for structuring, unstructuring, and validating data, have a look at [*cattrs*](https://catt.rs/) which is an official member of the *attrs* family. + + +## … namedtuples? + +{obj}`collections.namedtuple`s are tuples with names, not classes.[^history] +Since writing classes is tiresome in Python, every now and then someone discovers all the typing they could save and gets really excited. +However, that convenience comes at a price. + +The most obvious difference between `namedtuple`s and *attrs*-based classes is that the latter are type-sensitive: + +```{doctest} +>>> import attrs +>>> C1 = attrs.make_class("C1", ["a"]) +>>> C2 = attrs.make_class("C2", ["a"]) +>>> i1 = C1(1) +>>> i2 = C2(1) +>>> i1.a == i2.a +True +>>> i1 == i2 +False +``` + +…while a `namedtuple` is *intentionally* [behaving like a tuple](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences) which means the type of a tuple is *ignored*: + +```{doctest} +>>> from collections import namedtuple +>>> NT1 = namedtuple("NT1", "a") +>>> NT2 = namedtuple("NT2", "b") +>>> t1 = NT1(1) +>>> t2 = NT2(1) +>>> t1 == t2 == (1,) +True +``` + +Other often surprising behaviors include: + +- Since they are a subclass of tuples, `namedtuple`s have a length and are both iterable and indexable. + That's not what you'd expect from a class and is likely to shadow subtle typo bugs. + +- Iterability also implies that it's easy to accidentally unpack a `namedtuple` which leads to hard-to-find bugs.[^iter] + +- `namedtuple`s have their methods *on your instances* whether you like it or not.[^pollution] + +- `namedtuple`s are *always* immutable. + Not only does that mean that you can't decide for yourself whether your instances should be immutable or not, it also means that if you want to influence your class' initialization (validation? default values?), you have to implement {meth}`__new__() ` which is a particularly hacky and error-prone requirement for a very common problem.[^immutable] + +- To attach methods to a `namedtuple` you have to subclass it. + And if you follow the standard library documentation's recommendation of: + + ``` + class Point(namedtuple('Point', ['x', 'y'])): + # ... + ``` + + you end up with a class that has *two* `Point`s in its {attr}`__mro__ `: `[, , , ]`. + + That's not only confusing, it also has very practical consequences: + for example if you create documentation that includes class hierarchies like [*Sphinx*'s autodoc](https://www.sphinx-doc.org/en/stable/usage/extensions/autodoc.html) with `show-inheritance`. + Again: common problem, hacky solution with confusing fallout. + +All these things make `namedtuple`s a particularly poor choice for public APIs because all your objects are irrevocably tainted. +With *attrs* your users won't notice a difference because it creates regular, well-behaved classes. + +:::{admonition} Summary +If you want a *tuple with names*, by all means: go for a `namedtuple`.[^perf] +But if you want a class with methods, you're doing yourself a disservice by relying on a pile of hacks that requires you to employ even more hacks as your requirements expand. + +Other than that, *attrs* also adds nifty features like validators, converters, and (mutable!) default values. +::: + +[^history]: The word is that `namedtuple`s were added to the Python standard library as a way to make tuples in return values more readable. + And indeed that is something you see throughout the standard library. + + Looking at what the makers of `namedtuple`s use it for themselves is a good guideline for deciding on your own use cases. + +[^pollution]: *attrs* only adds a single attribute: `__attrs_attrs__` for introspection. + All helpers are functions in the `attr` package. + Since they take the instance as first argument, you can easily attach them to your classes under a name of your own choice. + +[^iter]: {func}`attrs.astuple` can be used to get that behavior in *attrs* on *explicit demand*. + +[^immutable]: *attrs* offers *optional* immutability through the `frozen` keyword. + +[^perf]: Although *attrs* would serve you just as well! + Since both employ the same method of writing and compiling Python code for you, the performance penalty is negligible at worst and in some cases *attrs* is even faster if you use `slots=True` (which is generally a good idea anyway). + + +## … tuples? + +### Readability + +What makes more sense while debugging: + +``` +Point(x=1, y=2) +``` + +or: + +``` +(1, 2) +``` + +? + +Let's add even more ambiguity: + +``` +Customer(id=42, reseller=23, first_name="Jane", last_name="John") +``` + +or: + +``` +(42, 23, "Jane", "John") +``` + +? + +Why would you want to write `customer[2]` instead of `customer.first_name`? + +Don't get me started when you add nesting. +If you've never run into mysterious tuples you had no idea what the hell they meant while debugging, you're much smarter than yours truly. + +Using proper classes with names and types makes program code much more readable and [comprehensible](https://arxiv.org/pdf/1304.5257.pdf). +Especially when trying to grok a new piece of software or returning to old code after several months. + + +### Extendability + +Imagine you have a function that takes or returns a tuple. +Especially if you use tuple unpacking (eg. `x, y = get_point()`), adding additional data means that you have to change the invocation of that function *everywhere*. + +Adding an attribute to a class concerns only those who actually care about that attribute. + + +## … dicts? + +Dictionaries are not for fixed fields. + +If you have a dict, it maps something to something else. +You should be able to add and remove values. + +*attrs* lets you be specific about those expectations; a dictionary does not. +It gives you a named entity (the class) in your code, which lets you explain in other places whether you take a parameter of that class or return a value of that class. + +In other words: if your dict has a fixed and known set of keys, it is an object, not a hash. +So if you never iterate over the keys of a dict, you should use a proper class. + + +## … hand-written classes? + +While we're fans of all things artisanal, writing the same nine methods again and again doesn't qualify. +I usually manage to get some typos inside and there's simply more code that can break and thus has to be tested. + +To bring it into perspective, the equivalent of + +```{doctest} +>>> @attrs.define +... class SmartClass: +... a = attrs.field() +... b = attrs.field() +>>> SmartClass(1, 2) +SmartClass(a=1, b=2) +``` + +is roughly + +```{doctest} +>>> class ArtisanalClass: +... def __init__(self, a, b): +... self.a = a +... self.b = b +... +... def __repr__(self): +... return f"ArtisanalClass(a={self.a}, b={self.b})" +... +... def __eq__(self, other): +... if other.__class__ is self.__class__: +... return (self.a, self.b) == (other.a, other.b) +... else: +... return NotImplemented +... +... def __ne__(self, other): +... result = self.__eq__(other) +... if result is NotImplemented: +... return NotImplemented +... else: +... return not result +... +... def __lt__(self, other): +... if other.__class__ is self.__class__: +... return (self.a, self.b) < (other.a, other.b) +... else: +... return NotImplemented +... +... def __le__(self, other): +... if other.__class__ is self.__class__: +... return (self.a, self.b) <= (other.a, other.b) +... else: +... return NotImplemented +... +... def __gt__(self, other): +... if other.__class__ is self.__class__: +... return (self.a, self.b) > (other.a, other.b) +... else: +... return NotImplemented +... +... def __ge__(self, other): +... if other.__class__ is self.__class__: +... return (self.a, self.b) >= (other.a, other.b) +... else: +... return NotImplemented +... +... def __hash__(self): +... return hash((self.__class__, self.a, self.b)) +>>> ArtisanalClass(a=1, b=2) +ArtisanalClass(a=1, b=2) +``` + +which is quite a mouthful and it doesn't even use any of *attrs*'s more advanced features like validators or default values. +Also: no tests whatsoever. +And who will guarantee you, that you don't accidentally flip the `<` in your tenth implementation of `__gt__`? + +It also should be noted that *attrs* is not an all-or-nothing solution. +You can freely choose which features you want and disable those that you want more control over: + +```{doctest} +>>> @attrs.define +... class SmartClass: +... a: int +... b: int +... +... def __repr__(self): +... return "" % (self.a,) +>>> SmartClass(1, 2) + +``` + +:::{admonition} Summary +If you don't care and like typing, we're not gonna stop you. + +However it takes a lot of bias and determined rationalization to claim that *attrs* raises the mental burden on a project given how difficult it is to find the important bits in a hand-written class and how annoying it is to ensure you've copy-pasted your code correctly over all your classes. + +In any case, if you ever get sick of the repetitiveness and the drowning of important code in a sea of boilerplate, *attrs* will be waiting for you. +::: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/pyproject.toml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/pyproject.toml index 52c0e49ec2e4d..1c72fc26d6ab7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/pyproject.toml +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/pyproject.toml @@ -1,34 +1,157 @@ +# SPDX-License-Identifier: MIT + [build-system] -requires = ["setuptools>=40.6.0", "wheel"] -build-backend = "setuptools.build_meta" +requires = ["hatchling", "hatch-vcs", "hatch-fancy-pypi-readme>=23.2.0"] +build-backend = "hatchling.build" + + +[project] +name = "attrs" +authors = [{ name = "Hynek Schlawack", email = "hs@ox.cx" }] +license = "MIT" +requires-python = ">=3.7" +description = "Classes Without Boilerplate" +keywords = ["class", "attribute", "boilerplate"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Typing :: Typed", +] +dependencies = ["importlib_metadata;python_version<'3.8'"] +dynamic = ["version", "readme"] + +[project.optional-dependencies] +tests-mypy = [ + 'pytest-mypy-plugins; python_implementation == "CPython" and python_version >= "3.8"', + # Since the mypy error messages keep changing, we have to keep updating this + # pin. + 'mypy>=1.6; python_implementation == "CPython" and python_version >= "3.8"', +] +tests-no-zope = [ + # For regression test to ensure cloudpickle compat doesn't break. + 'cloudpickle; python_implementation == "CPython"', + "hypothesis", + "pympler", + # 4.3.0 dropped last use of `convert` + "pytest>=4.3.0", + "pytest-xdist[psutil]", + "attrs[tests-mypy]", +] +tests = ["attrs[tests-no-zope]", "zope.interface"] +cov = [ + "attrs[tests]", + # Ensure coverage is new enough for `source_pkgs`. + "coverage[toml]>=5.3", +] +docs = [ + "furo", + "myst-parser", + "sphinx", + "zope.interface", + "sphinx-notfound-page", + "sphinxcontrib-towncrier", + "towncrier", +] +dev = ["attrs[tests]", "pre-commit"] + +[project.urls] +Documentation = "https://www.attrs.org/" +Changelog = "https://www.attrs.org/en/stable/changelog.html" +GitHub = "https://github.com/python-attrs/attrs" +Funding = "https://github.com/sponsors/hynek" +Tidelift = "https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=pypi" + + +[tool.hatch.version] +source = "vcs" +raw-options = { local_scheme = "no-local-version" } + +[tool.hatch.build.targets.wheel] +packages = ["src/attr", "src/attrs"] + +[tool.hatch.metadata.hooks.fancy-pypi-readme] +content-type = "text/markdown" + +# PyPI doesn't support the tag. +[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] +text = """

+ + attrs + +

+""" + +[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] +path = "README.md" +start-after = "" + +[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] +text = """ + +## Release Information + +""" + +[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] +path = "CHANGELOG.md" +pattern = "\n(###.+?\n)## " + +[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] +text = """ + +--- + +[Full changelog](https://www.attrs.org/en/stable/changelog.html) +""" + +# Point sponsor image URLs to versions. +[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]] +pattern = '\/latest\/_static/sponsors' +replacement = '/$HFPR_VERSION/_static/sponsors' + + +[tool.pytest.ini_options] +addopts = ["-ra", "--strict-markers", "--strict-config"] +xfail_strict = true +testpaths = "tests" +filterwarnings = ["once::Warning", "ignore:::pympler[.*]"] [tool.coverage.run] parallel = true branch = true -source = ["attr", "attrs"] +source_pkgs = ["attr", "attrs"] [tool.coverage.paths] -source = ["src", ".tox/*/site-packages"] +source = ["src", ".tox/py*/**/site-packages"] [tool.coverage.report] show_missing = true +skip_covered = true exclude_lines = [ - "pragma: no cover", - # PyPy is unacceptably slow under coverage. - "if PYPY:", + "pragma: no cover", + # PyPy is unacceptably slow under coverage. + "if PYPY:", + # not meant to be executed + ': \.\.\.$', + '^ +\.\.\.$', ] [tool.black] line-length = 79 -extend-exclude = ''' -# Exclude pattern matching test till black gains Python 3.10 support -.*test_pattern_matching.* -''' [tool.interrogate] +omit-covered-files = true verbose = 2 fail-under = 100 whitelist-regex = ["test_.*"] @@ -38,34 +161,113 @@ whitelist-regex = ["test_.*"] toplevel = ["attr", "attrs"] -[tool.isort] -profile = "attrs" +[tool.ruff] +src = ["src", "tests", "conftest.py", "docs"] + +select = ["ALL"] +ignore = [ + "A001", # shadowing is fine + "A002", # shadowing is fine + "A003", # shadowing is fine + "ANN", # Mypy is better at this + "ARG", # unused arguments are normal when implementing interfaces + "COM", # Black takes care of our commas + "D", # We prefer our own docstring style. + "E501", # leave line-length enforcement to Black + "FBT", # we don't hate bool args around here + "FIX", # Yes, we want XXX as a marker. + "PLR0913", # yes, many arguments, but most have defaults + "PLR2004", # numbers are sometimes fine + "SLF001", # private members are accessed by friendly functions + "TCH", # TYPE_CHECKING blocks break autodocs + "TD", # we don't follow other people's todo style + "C901", # we're complex software + "PLR0911", # we're complex software + "PLR0912", # we're complex software + "PLR0915", # we're complex software + "PGH001", # eval FTW + "S307", # eval FTW + "N807", # we need to create functions that start with __ + "ERA001", # we need to keep around some notes + "RSE102", # I like empty parens on raised exceptions + "N", # we need more naming freedom + "UP031", # format() is slow as molasses; % and f'' FTW. + "PD", # we're not pandas + "PLW0603", # sometimes we need globals + "TRY301", # I'm sorry, but this makes not sense for us. +] + +[tool.ruff.per-file-ignores] +"**/test_*" = [ + "ARG005", # we need stub lambdas + "S", + "SIM300", # Yoda rocks in asserts + "SIM201", # sometimes we need to check `not ==` + "SIM202", # sometimes we need to check `not ==` + "PT005", # we always add underscores and explicit names + "PT011", # broad is fine + "TRY", # exception best practices don't matter in tests + "EM101", # no need for exception msg hygiene in tests + "B904", # exception best practices don't matter in tests + "B015", # pointless comparison in tests aren't pointless + "B018", # pointless expressions in tests aren't pointless + "PLR0124", # pointless comparison in tests aren't pointless + "DTZ", # datetime best practices don't matter in tests + "UP037", # we test some older syntaxes on purpose + "B017", # pytest.raises(Exception) is fine + "PT012", # sometimes we need more than a single stmt + "RUF012", # we don't do ClassVar annotations in tests +] + +"conftest.py" = [ + "PT005", # we always add underscores and explicit names +] + +"src/*/*.pyi" = ["ALL"] # TODO +"tests/test_annotations.py" = ["FA100"] +"tests/typing_example.py" = [ + "E741", # ambiguous variable names don't matter in type checks + "B018", # useless expressions aren't useless in type checks + "B015", # pointless comparison in type checks aren't pointless + "TRY301", # exception hygiene isn't important in type checks + "UP037", # we test some older syntaxes on purpose +] + +[tool.ruff.isort] +lines-between-types = 1 +lines-after-imports = 2 +known-first-party = ["attr", "attrs"] [tool.towncrier] - package = "attr" - package_dir = "src" - filename = "CHANGELOG.rst" - template = "changelog.d/towncrier_template.rst" - issue_format = "`#{issue} `_" - directory = "changelog.d" - title_format = "{version} ({project_date})" - underlines = ["-", "^"] - - [[tool.towncrier.section]] - path = "" - - [[tool.towncrier.type]] - directory = "breaking" - name = "Backward-incompatible Changes" - showcontent = true - - [[tool.towncrier.type]] - directory = "deprecation" - name = "Deprecations" - showcontent = true - - [[tool.towncrier.type]] - directory = "change" - name = "Changes" - showcontent = true +name = "attrs" +directory = "changelog.d" +filename = "CHANGELOG.md" +start_string = "\n" +template = "changelog.d/towncrier_template.md.jinja" +title_format = "" +issue_format = "[#{issue}](https://github.com/python-attrs/attrs/issues/{issue})" +underlines = ["", "", ""] + +[[tool.towncrier.section]] +path = "" + +[[tool.towncrier.type]] +directory = "breaking" +name = "Backwards-incompatible Changes" +showcontent = true + +[[tool.towncrier.type]] +directory = "deprecation" +name = "Deprecations" +showcontent = true + +[[tool.towncrier.type]] +directory = "change" +name = "Changes" +showcontent = true + + +[tool.mypy] +disallow_untyped_defs = true +check_untyped_defs = true diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/__init__.py index f95c96dd5795b..9226258a2d587 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/__init__.py @@ -1,13 +1,15 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function - -import sys +""" +Classes Without Boilerplate +""" from functools import partial +from typing import Callable from . import converters, exceptions, filters, setters, validators from ._cmp import cmp_using +from ._compat import Protocol from ._config import get_run_validators, set_run_validators from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types from ._make import ( @@ -21,31 +23,22 @@ make_class, validate, ) +from ._next_gen import define, field, frozen, mutable from ._version_info import VersionInfo -__version__ = "21.4.0" -__version_info__ = VersionInfo._from_version_string(__version__) - -__title__ = "attrs" -__description__ = "Classes Without Boilerplate" -__url__ = "https://www.attrs.org/" -__uri__ = __url__ -__doc__ = __description__ + " <" + __uri__ + ">" - -__author__ = "Hynek Schlawack" -__email__ = "hs@ox.cx" - -__license__ = "MIT" -__copyright__ = "Copyright (c) 2015 Hynek Schlawack" - - s = attributes = attrs ib = attr = attrib dataclass = partial(attrs, auto_attribs=True) # happy Easter ;) + +class AttrsInstance(Protocol): + pass + + __all__ = [ "Attribute", + "AttrsInstance", "Factory", "NOTHING", "asdict", @@ -57,15 +50,19 @@ "attrs", "cmp_using", "converters", + "define", "evolve", "exceptions", + "field", "fields", "fields_dict", "filters", + "frozen", "get_run_validators", "has", "ib", "make_class", + "mutable", "resolve_types", "s", "set_run_validators", @@ -74,7 +71,64 @@ "validators", ] -if sys.version_info[:2] >= (3, 6): - from ._next_gen import define, field, frozen, mutable # noqa: F401 - __all__.extend(("define", "field", "frozen", "mutable")) +def _make_getattr(mod_name: str) -> Callable: + """ + Create a metadata proxy for packaging information that uses *mod_name* in + its warnings and errors. + """ + + def __getattr__(name: str) -> str: + dunder_to_metadata = { + "__title__": "Name", + "__copyright__": "", + "__version__": "version", + "__version_info__": "version", + "__description__": "summary", + "__uri__": "", + "__url__": "", + "__author__": "", + "__email__": "", + "__license__": "license", + } + if name not in dunder_to_metadata: + msg = f"module {mod_name} has no attribute {name}" + raise AttributeError(msg) + + import sys + import warnings + + if sys.version_info < (3, 8): + from importlib_metadata import metadata + else: + from importlib.metadata import metadata + + if name not in ("__version__", "__version_info__"): + warnings.warn( + f"Accessing {mod_name}.{name} is deprecated and will be " + "removed in a future release. Use importlib.metadata directly " + "to query for attrs's packaging metadata.", + DeprecationWarning, + stacklevel=2, + ) + + meta = metadata("attrs") + if name == "__license__": + return "MIT" + if name == "__copyright__": + return "Copyright (c) 2015 Hynek Schlawack" + if name in ("__uri__", "__url__"): + return meta["Project-URL"].split(" ", 1)[-1] + if name == "__version_info__": + return VersionInfo._from_version_string(meta["version"]) + if name == "__author__": + return meta["Author-email"].rsplit(" ", 1)[0] + if name == "__email__": + return meta["Author-email"].rsplit("<", 1)[1][:-1] + + return meta[dunder_to_metadata[name]] + + return __getattr__ + + +__getattr__ = _make_getattr(__name__) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/__init__.pyi b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/__init__.pyi index c0a21265036a6..37a208732acf7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/__init__.pyi +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/__init__.pyi @@ -1,3 +1,4 @@ +import enum import sys from typing import ( @@ -8,6 +9,7 @@ from typing import ( List, Mapping, Optional, + Protocol, Sequence, Tuple, Type, @@ -22,8 +24,20 @@ from . import exceptions as exceptions from . import filters as filters from . import setters as setters from . import validators as validators +from ._cmp import cmp_using as cmp_using +from ._typing_compat import AttrsInstance_ from ._version_info import VersionInfo +if sys.version_info >= (3, 10): + from typing import TypeGuard +else: + from typing_extensions import TypeGuard + +if sys.version_info >= (3, 11): + from typing import dataclass_transform +else: + from typing_extensions import dataclass_transform + __version__: str __version_info__: VersionInfo __title__: str @@ -39,27 +53,33 @@ _T = TypeVar("_T") _C = TypeVar("_C", bound=type) _EqOrderType = Union[bool, Callable[[Any], Any]] -_ValidatorType = Callable[[Any, Attribute[_T], _T], Any] +_ValidatorType = Callable[[Any, "Attribute[_T]", _T], Any] _ConverterType = Callable[[Any], Any] -_FilterType = Callable[[Attribute[_T], _T], bool] +_FilterType = Callable[["Attribute[_T]", _T], bool] _ReprType = Callable[[Any], str] _ReprArgType = Union[bool, _ReprType] -_OnSetAttrType = Callable[[Any, Attribute[Any], Any], Any] +_OnSetAttrType = Callable[[Any, "Attribute[Any]", Any], Any] _OnSetAttrArgType = Union[ _OnSetAttrType, List[_OnSetAttrType], setters._NoOpType ] _FieldTransformer = Callable[ - [type, List[Attribute[Any]]], List[Attribute[Any]] + [type, List["Attribute[Any]"]], List["Attribute[Any]"] ] -_CompareWithType = Callable[[Any, Any], bool] # FIXME: in reality, if multiple validators are passed they must be in a list # or tuple, but those are invariant and so would prevent subtypes of # _ValidatorType from working when passed in a list or tuple. _ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]] -# _make -- +# We subclass this here to keep the protocol's qualified name clean. +class AttrsInstance(AttrsInstance_, Protocol): + pass -NOTHING: object +_A = TypeVar("_A", bound=type[AttrsInstance]) + +class _Nothing(enum.Enum): + NOTHING = enum.auto() + +NOTHING = _Nothing.NOTHING # NOTE: Factory lies about its return type to make this possible: # `x: List[int] # = Factory(list)` @@ -88,22 +108,6 @@ else: takes_self: bool = ..., ) -> _T: ... -# Static type inference support via __dataclass_transform__ implemented as per: -# https://github.com/microsoft/pyright/blob/1.1.135/specs/dataclass_transforms.md -# This annotation must be applied to all overloads of "define" and "attrs" -# -# NOTE: This is a typing construct and does not exist at runtime. Extensions -# wrapping attrs decorators should declare a separate __dataclass_transform__ -# signature in the extension module using the specification linked above to -# provide pyright support. -def __dataclass_transform__( - *, - eq_default: bool = True, - order_default: bool = False, - kw_only_default: bool = False, - field_descriptors: Tuple[Union[type, Callable[..., Any]], ...] = (()), -) -> Callable[[_T], _T]: ... - class Attribute(Generic[_T]): name: str default: Optional[_T] @@ -119,6 +123,8 @@ class Attribute(Generic[_T]): type: Optional[Type[_T]] kw_only: bool on_setattr: _OnSetAttrType + alias: Optional[str] + def evolve(self, **changes: Any) -> "Attribute[Any]": ... # NOTE: We had several choices for the annotation to use for type arg: @@ -161,6 +167,7 @@ def attrib( eq: Optional[_EqOrderType] = ..., order: Optional[_EqOrderType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., ) -> Any: ... # This form catches an explicit None or no default and infers the type from the @@ -181,6 +188,7 @@ def attrib( eq: Optional[_EqOrderType] = ..., order: Optional[_EqOrderType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., ) -> _T: ... # This form catches an explicit default argument. @@ -200,6 +208,7 @@ def attrib( eq: Optional[_EqOrderType] = ..., order: Optional[_EqOrderType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., ) -> _T: ... # This form covers type=non-Type: e.g. forward references (str), Any @@ -219,6 +228,7 @@ def attrib( eq: Optional[_EqOrderType] = ..., order: Optional[_EqOrderType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., ) -> Any: ... @overload def field( @@ -235,6 +245,8 @@ def field( eq: Optional[bool] = ..., order: Optional[bool] = ..., on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., + type: Optional[type] = ..., ) -> Any: ... # This form catches an explicit None or no default and infers the type from the @@ -254,6 +266,8 @@ def field( eq: Optional[_EqOrderType] = ..., order: Optional[_EqOrderType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., + type: Optional[type] = ..., ) -> _T: ... # This form catches an explicit default argument. @@ -272,6 +286,8 @@ def field( eq: Optional[_EqOrderType] = ..., order: Optional[_EqOrderType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., + type: Optional[type] = ..., ) -> _T: ... # This form covers type=non-Type: e.g. forward references (str), Any @@ -290,9 +306,11 @@ def field( eq: Optional[_EqOrderType] = ..., order: Optional[_EqOrderType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., + type: Optional[type] = ..., ) -> Any: ... @overload -@__dataclass_transform__(order_default=True, field_descriptors=(attrib, field)) +@dataclass_transform(order_default=True, field_specifiers=(attrib, field)) def attrs( maybe_cls: _C, these: Optional[Dict[str, Any]] = ..., @@ -317,9 +335,10 @@ def attrs( on_setattr: Optional[_OnSetAttrArgType] = ..., field_transformer: Optional[_FieldTransformer] = ..., match_args: bool = ..., + unsafe_hash: Optional[bool] = ..., ) -> _C: ... @overload -@__dataclass_transform__(order_default=True, field_descriptors=(attrib, field)) +@dataclass_transform(order_default=True, field_specifiers=(attrib, field)) def attrs( maybe_cls: None = ..., these: Optional[Dict[str, Any]] = ..., @@ -344,14 +363,16 @@ def attrs( on_setattr: Optional[_OnSetAttrArgType] = ..., field_transformer: Optional[_FieldTransformer] = ..., match_args: bool = ..., + unsafe_hash: Optional[bool] = ..., ) -> Callable[[_C], _C]: ... @overload -@__dataclass_transform__(field_descriptors=(attrib, field)) +@dataclass_transform(field_specifiers=(attrib, field)) def define( maybe_cls: _C, *, these: Optional[Dict[str, Any]] = ..., repr: bool = ..., + unsafe_hash: Optional[bool] = ..., hash: Optional[bool] = ..., init: bool = ..., slots: bool = ..., @@ -371,12 +392,13 @@ def define( match_args: bool = ..., ) -> _C: ... @overload -@__dataclass_transform__(field_descriptors=(attrib, field)) +@dataclass_transform(field_specifiers=(attrib, field)) def define( maybe_cls: None = ..., *, these: Optional[Dict[str, Any]] = ..., repr: bool = ..., + unsafe_hash: Optional[bool] = ..., hash: Optional[bool] = ..., init: bool = ..., slots: bool = ..., @@ -397,21 +419,69 @@ def define( ) -> Callable[[_C], _C]: ... mutable = define -frozen = define # they differ only in their defaults - -# TODO: add support for returning NamedTuple from the mypy plugin -class _Fields(Tuple[Attribute[Any], ...]): - def __getattr__(self, name: str) -> Attribute[Any]: ... -def fields(cls: type) -> _Fields: ... -def fields_dict(cls: type) -> Dict[str, Attribute[Any]]: ... -def validate(inst: Any) -> None: ... +@overload +@dataclass_transform(frozen_default=True, field_specifiers=(attrib, field)) +def frozen( + maybe_cls: _C, + *, + these: Optional[Dict[str, Any]] = ..., + repr: bool = ..., + unsafe_hash: Optional[bool] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + auto_detect: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., + match_args: bool = ..., +) -> _C: ... +@overload +@dataclass_transform(frozen_default=True, field_specifiers=(attrib, field)) +def frozen( + maybe_cls: None = ..., + *, + these: Optional[Dict[str, Any]] = ..., + repr: bool = ..., + unsafe_hash: Optional[bool] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + auto_detect: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., + match_args: bool = ..., +) -> Callable[[_C], _C]: ... +def fields(cls: Type[AttrsInstance]) -> Any: ... +def fields_dict(cls: Type[AttrsInstance]) -> Dict[str, Attribute[Any]]: ... +def validate(inst: AttrsInstance) -> None: ... def resolve_types( - cls: _C, + cls: _A, globalns: Optional[Dict[str, Any]] = ..., localns: Optional[Dict[str, Any]] = ..., attribs: Optional[List[Attribute[Any]]] = ..., -) -> _C: ... + include_extras: bool = ..., +) -> _A: ... # TODO: add support for returning a proper attrs class from the mypy plugin # we use Any instead of _CountingAttr so that e.g. `make_class('Foo', @@ -420,6 +490,7 @@ def make_class( name: str, attrs: Union[List[str], Tuple[str, ...], Dict[str, Any]], bases: Tuple[type, ...] = ..., + class_body: Optional[Dict[str, Any]] = ..., repr_ns: Optional[str] = ..., repr: bool = ..., cmp: Optional[_EqOrderType] = ..., @@ -449,7 +520,7 @@ def make_class( # https://github.com/python/typing/issues/253 # XXX: remember to fix attrs.asdict/astuple too! def asdict( - inst: Any, + inst: AttrsInstance, recurse: bool = ..., filter: Optional[_FilterType[Any]] = ..., dict_factory: Type[Mapping[Any, Any]] = ..., @@ -462,13 +533,13 @@ def asdict( # TODO: add support for returning NamedTuple from the mypy plugin def astuple( - inst: Any, + inst: AttrsInstance, recurse: bool = ..., filter: Optional[_FilterType[Any]] = ..., tuple_factory: Type[Sequence[Any]] = ..., retain_collection_types: bool = ..., ) -> Tuple[Any, ...]: ... -def has(cls: type) -> bool: ... +def has(cls: type) -> TypeGuard[Type[AttrsInstance]]: ... def assoc(inst: _T, **changes: Any) -> _T: ... def evolve(inst: _T, **changes: Any) -> _T: ... diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_cmp.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_cmp.py index 6cffa4dbabda2..a4a35e08fc9d9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_cmp.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_cmp.py @@ -1,10 +1,9 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function import functools +import types -from ._compat import new_class from ._make import _make_ne @@ -21,22 +20,22 @@ def cmp_using( class_name="Comparable", ): """ - Create a class that can be passed into `attr.ib`'s ``eq``, ``order``, and - ``cmp`` arguments to customize field comparison. - - The resulting class will have a full set of ordering methods if - at least one of ``{lt, le, gt, ge}`` and ``eq`` are provided. - - :param Optional[callable] eq: `callable` used to evaluate equality - of two objects. - :param Optional[callable] lt: `callable` used to evaluate whether - one object is less than another object. - :param Optional[callable] le: `callable` used to evaluate whether - one object is less than or equal to another object. - :param Optional[callable] gt: `callable` used to evaluate whether - one object is greater than another object. - :param Optional[callable] ge: `callable` used to evaluate whether - one object is greater than or equal to another object. + Create a class that can be passed into `attrs.field`'s ``eq``, ``order``, + and ``cmp`` arguments to customize field comparison. + + The resulting class will have a full set of ordering methods if at least + one of ``{lt, le, gt, ge}`` and ``eq`` are provided. + + :param Optional[callable] eq: `callable` used to evaluate equality of two + objects. + :param Optional[callable] lt: `callable` used to evaluate whether one + object is less than another object. + :param Optional[callable] le: `callable` used to evaluate whether one + object is less than or equal to another object. + :param Optional[callable] gt: `callable` used to evaluate whether one + object is greater than another object. + :param Optional[callable] ge: `callable` used to evaluate whether one + object is greater than or equal to another object. :param bool require_same_type: When `True`, equality and ordering methods will return `NotImplemented` if objects are not of the same type. @@ -80,7 +79,9 @@ def cmp_using( num_order_functions += 1 body["__ge__"] = _make_operator("ge", ge) - type_ = new_class(class_name, (object,), {}, lambda ns: ns.update(body)) + type_ = types.new_class( + class_name, (object,), {}, lambda ns: ns.update(body) + ) # Add same type requirement. if require_same_type: @@ -91,10 +92,8 @@ def cmp_using( if not has_eq_function: # functools.total_ordering requires __eq__ to be defined, # so raise early error here to keep a nice stack. - raise ValueError( - "eq must be define is order to complete ordering from " - "lt, le, gt, ge." - ) + msg = "eq must be define is order to complete ordering from lt, le, gt, ge." + raise ValueError(msg) type_ = functools.total_ordering(type_) return type_ @@ -129,9 +128,9 @@ def method(self, other): return result - method.__name__ = "__%s__" % (name,) - method.__doc__ = "Return a %s b. Computed by attrs." % ( - _operation_names[name], + method.__name__ = f"__{name}__" + method.__doc__ = ( + f"Return a {_operation_names[name]} b. Computed by attrs." ) return method @@ -141,10 +140,7 @@ def _is_comparable_to(self, other): """ Check whether `other` is comparable to `self`. """ - for func in self._requirements: - if not func(self, other): - return False - return True + return all(func(self, other) for func in self._requirements) def _check_same_type(self, other): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_cmp.pyi b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_cmp.pyi index e71aaff7a19b7..f3dcdc1a75414 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_cmp.pyi +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_cmp.pyi @@ -1,13 +1,13 @@ -from typing import Type +from typing import Any, Callable, Optional, Type -from . import _CompareWithType +_CompareWithType = Callable[[Any, Any], bool] def cmp_using( - eq: Optional[_CompareWithType], - lt: Optional[_CompareWithType], - le: Optional[_CompareWithType], - gt: Optional[_CompareWithType], - ge: Optional[_CompareWithType], - require_same_type: bool, - class_name: str, + eq: Optional[_CompareWithType] = ..., + lt: Optional[_CompareWithType] = ..., + le: Optional[_CompareWithType] = ..., + gt: Optional[_CompareWithType] = ..., + ge: Optional[_CompareWithType] = ..., + require_same_type: bool = ..., + class_name: str = ..., ) -> Type: ... diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_compat.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_compat.py index dc0cb02b6435b..46b05ca453773 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_compat.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_compat.py @@ -1,250 +1,69 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function - +import inspect import platform import sys import threading -import types -import warnings + +from collections.abc import Mapping, Sequence # noqa: F401 +from typing import _GenericAlias -PY2 = sys.version_info[0] == 2 PYPY = platform.python_implementation() == "PyPy" -PY36 = sys.version_info[:2] >= (3, 6) -HAS_F_STRINGS = PY36 +PY_3_8_PLUS = sys.version_info[:2] >= (3, 8) +PY_3_9_PLUS = sys.version_info[:2] >= (3, 9) PY310 = sys.version_info[:2] >= (3, 10) +PY_3_12_PLUS = sys.version_info[:2] >= (3, 12) -if PYPY or PY36: - ordered_dict = dict +if sys.version_info < (3, 8): + try: + from typing_extensions import Protocol + except ImportError: # pragma: no cover + Protocol = object else: - from collections import OrderedDict - - ordered_dict = OrderedDict - - -if PY2: - from collections import Mapping, Sequence + from typing import Protocol # noqa: F401 - from UserDict import IterableUserDict - # We 'bundle' isclass instead of using inspect as importing inspect is - # fairly expensive (order of 10-15 ms for a modern machine in 2016) - def isclass(klass): - return isinstance(klass, (type, types.ClassType)) - - def new_class(name, bases, kwds, exec_body): - """ - A minimal stub of types.new_class that we need for make_class. - """ - ns = {} - exec_body(ns) - - return type(name, bases, ns) +class _AnnotationExtractor: + """ + Extract type annotations from a callable, returning None whenever there + is none. + """ - # TYPE is used in exceptions, repr(int) is different on Python 2 and 3. - TYPE = "type" + __slots__ = ["sig"] - def iteritems(d): - return d.iteritems() + def __init__(self, callable): + try: + self.sig = inspect.signature(callable) + except (ValueError, TypeError): # inspect failed + self.sig = None - # Python 2 is bereft of a read-only dict proxy, so we make one! - class ReadOnlyDict(IterableUserDict): + def get_first_param_type(self): """ - Best-effort read-only dict wrapper. + Return the type annotation of the first argument if it's not empty. """ + if not self.sig: + return None - def __setitem__(self, key, val): - # We gently pretend we're a Python 3 mappingproxy. - raise TypeError( - "'mappingproxy' object does not support item assignment" - ) + params = list(self.sig.parameters.values()) + if params and params[0].annotation is not inspect.Parameter.empty: + return params[0].annotation - def update(self, _): - # We gently pretend we're a Python 3 mappingproxy. - raise AttributeError( - "'mappingproxy' object has no attribute 'update'" - ) + return None - def __delitem__(self, _): - # We gently pretend we're a Python 3 mappingproxy. - raise TypeError( - "'mappingproxy' object does not support item deletion" - ) - - def clear(self): - # We gently pretend we're a Python 3 mappingproxy. - raise AttributeError( - "'mappingproxy' object has no attribute 'clear'" - ) - - def pop(self, key, default=None): - # We gently pretend we're a Python 3 mappingproxy. - raise AttributeError( - "'mappingproxy' object has no attribute 'pop'" - ) - - def popitem(self): - # We gently pretend we're a Python 3 mappingproxy. - raise AttributeError( - "'mappingproxy' object has no attribute 'popitem'" - ) - - def setdefault(self, key, default=None): - # We gently pretend we're a Python 3 mappingproxy. - raise AttributeError( - "'mappingproxy' object has no attribute 'setdefault'" - ) - - def __repr__(self): - # Override to be identical to the Python 3 version. - return "mappingproxy(" + repr(self.data) + ")" - - def metadata_proxy(d): - res = ReadOnlyDict() - res.data.update(d) # We blocked update, so we have to do it like this. - return res - - def just_warn(*args, **kw): # pragma: no cover - """ - We only warn on Python 3 because we are not aware of any concrete - consequences of not setting the cell on Python 2. - """ - -else: # Python 3 and later. - from collections.abc import Mapping, Sequence # noqa - - def just_warn(*args, **kw): + def get_return_type(self): """ - We only warn on Python 3 because we are not aware of any concrete - consequences of not setting the cell on Python 2. + Return the return type if it's not empty. """ - warnings.warn( - "Running interpreter doesn't sufficiently support code object " - "introspection. Some features like bare super() or accessing " - "__class__ will not work with slotted classes.", - RuntimeWarning, - stacklevel=2, - ) - - def isclass(klass): - return isinstance(klass, type) + if ( + self.sig + and self.sig.return_annotation is not inspect.Signature.empty + ): + return self.sig.return_annotation - TYPE = "class" + return None - def iteritems(d): - return d.items() - - new_class = types.new_class - - def metadata_proxy(d): - return types.MappingProxyType(dict(d)) - - -def make_set_closure_cell(): - """Return a function of two arguments (cell, value) which sets - the value stored in the closure cell `cell` to `value`. - """ - # pypy makes this easy. (It also supports the logic below, but - # why not do the easy/fast thing?) - if PYPY: - - def set_closure_cell(cell, value): - cell.__setstate__((value,)) - - return set_closure_cell - - # Otherwise gotta do it the hard way. - - # Create a function that will set its first cellvar to `value`. - def set_first_cellvar_to(value): - x = value - return - - # This function will be eliminated as dead code, but - # not before its reference to `x` forces `x` to be - # represented as a closure cell rather than a local. - def force_x_to_be_a_cell(): # pragma: no cover - return x - - try: - # Extract the code object and make sure our assumptions about - # the closure behavior are correct. - if PY2: - co = set_first_cellvar_to.func_code - else: - co = set_first_cellvar_to.__code__ - if co.co_cellvars != ("x",) or co.co_freevars != (): - raise AssertionError # pragma: no cover - - # Convert this code object to a code object that sets the - # function's first _freevar_ (not cellvar) to the argument. - if sys.version_info >= (3, 8): - # CPython 3.8+ has an incompatible CodeType signature - # (added a posonlyargcount argument) but also added - # CodeType.replace() to do this without counting parameters. - set_first_freevar_code = co.replace( - co_cellvars=co.co_freevars, co_freevars=co.co_cellvars - ) - else: - args = [co.co_argcount] - if not PY2: - args.append(co.co_kwonlyargcount) - args.extend( - [ - co.co_nlocals, - co.co_stacksize, - co.co_flags, - co.co_code, - co.co_consts, - co.co_names, - co.co_varnames, - co.co_filename, - co.co_name, - co.co_firstlineno, - co.co_lnotab, - # These two arguments are reversed: - co.co_cellvars, - co.co_freevars, - ] - ) - set_first_freevar_code = types.CodeType(*args) - - def set_closure_cell(cell, value): - # Create a function using the set_first_freevar_code, - # whose first closure cell is `cell`. Calling it will - # change the value of that cell. - setter = types.FunctionType( - set_first_freevar_code, {}, "setter", (), (cell,) - ) - # And call it to set the cell. - setter(value) - - # Make sure it works on this interpreter: - def make_func_with_cell(): - x = None - - def func(): - return x # pragma: no cover - - return func - - if PY2: - cell = make_func_with_cell().func_closure[0] - else: - cell = make_func_with_cell().__closure__[0] - set_closure_cell(cell, 100) - if cell.cell_contents != 100: - raise AssertionError # pragma: no cover - - except Exception: - return just_warn - else: - return set_closure_cell - - -set_closure_cell = make_set_closure_cell() # Thread-local global to track attrs instances which are already being repr'd. # This is needed because there is no other (thread-safe) way to pass info @@ -259,3 +78,10 @@ def func(): # don't have a direct reference to the thread-local in their globals dict. # If they have such a reference, it breaks cloudpickle. repr_context = threading.local() + + +def get_generic_base(cl): + """If this is a generic class (A[str]), return the generic base for it.""" + if cl.__class__ is _GenericAlias: + return cl.__origin__ + return None diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_config.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_config.py index fc9be29d00812..9c245b1461abd 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_config.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_config.py @@ -1,8 +1,5 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function - - __all__ = ["set_run_validators", "get_run_validators"] _run_validators = True @@ -17,7 +14,8 @@ def set_run_validators(run): instead. """ if not isinstance(run, bool): - raise TypeError("'run' must be bool.") + msg = "'run' must be bool." + raise TypeError(msg) global _run_validators _run_validators = run diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_funcs.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_funcs.py index 4c90085a4013b..a888991d98fda 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_funcs.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_funcs.py @@ -1,10 +1,9 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function import copy -from ._compat import iteritems +from ._compat import PY_3_9_PLUS, get_generic_base from ._make import NOTHING, _obj_setattr, fields from .exceptions import AttrsAttributeNotFoundError @@ -18,13 +17,13 @@ def asdict( value_serializer=None, ): """ - Return the ``attrs`` attribute values of *inst* as a dict. + Return the *attrs* attribute values of *inst* as a dict. - Optionally recurse into other ``attrs``-decorated classes. + Optionally recurse into other *attrs*-decorated classes. - :param inst: Instance of an ``attrs``-decorated class. + :param inst: Instance of an *attrs*-decorated class. :param bool recurse: Recurse into classes that are also - ``attrs``-decorated. + *attrs*-decorated. :param callable filter: A callable whose return code determines whether an attribute or element is included (``True``) or dropped (``False``). Is called with the `attrs.Attribute` as the first argument and the @@ -42,7 +41,7 @@ def asdict( :rtype: return type of *dict_factory* - :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* class. .. versionadded:: 16.0.0 *dict_factory* @@ -73,19 +72,25 @@ def asdict( ) elif isinstance(v, (tuple, list, set, frozenset)): cf = v.__class__ if retain_collection_types is True else list - rv[a.name] = cf( - [ - _asdict_anything( - i, - is_key=False, - filter=filter, - dict_factory=dict_factory, - retain_collection_types=retain_collection_types, - value_serializer=value_serializer, - ) - for i in v - ] - ) + items = [ + _asdict_anything( + i, + is_key=False, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + for i in v + ] + try: + rv[a.name] = cf(items) + except TypeError: + if not issubclass(cf, tuple): + raise + # Workaround for TypeError: cf.__new__() missing 1 required + # positional argument (which appears, for a namedturle) + rv[a.name] = cf(*items) elif isinstance(v, dict): df = dict_factory rv[a.name] = df( @@ -107,7 +112,7 @@ def asdict( value_serializer=value_serializer, ), ) - for kk, vv in iteritems(v) + for kk, vv in v.items() ) else: rv[a.name] = v @@ -179,7 +184,7 @@ def _asdict_anything( value_serializer=value_serializer, ), ) - for kk, vv in iteritems(val) + for kk, vv in val.items() ) else: rv = val @@ -197,13 +202,13 @@ def astuple( retain_collection_types=False, ): """ - Return the ``attrs`` attribute values of *inst* as a tuple. + Return the *attrs* attribute values of *inst* as a tuple. - Optionally recurse into other ``attrs``-decorated classes. + Optionally recurse into other *attrs*-decorated classes. - :param inst: Instance of an ``attrs``-decorated class. + :param inst: Instance of an *attrs*-decorated class. :param bool recurse: Recurse into classes that are also - ``attrs``-decorated. + *attrs*-decorated. :param callable filter: A callable whose return code determines whether an attribute or element is included (``True``) or dropped (``False``). Is called with the `attrs.Attribute` as the first argument and the @@ -217,7 +222,7 @@ def astuple( :rtype: return type of *tuple_factory* - :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* class. .. versionadded:: 16.2.0 @@ -242,22 +247,26 @@ def astuple( ) elif isinstance(v, (tuple, list, set, frozenset)): cf = v.__class__ if retain is True else list - rv.append( - cf( - [ - astuple( - j, - recurse=True, - filter=filter, - tuple_factory=tuple_factory, - retain_collection_types=retain, - ) - if has(j.__class__) - else j - for j in v - ] + items = [ + astuple( + j, + recurse=True, + filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain, ) - ) + if has(j.__class__) + else j + for j in v + ] + try: + rv.append(cf(items)) + except TypeError: + if not issubclass(cf, tuple): + raise + # Workaround for TypeError: cf.__new__() missing 1 required + # positional argument (which appears, for a namedturle) + rv.append(cf(*items)) elif isinstance(v, dict): df = v.__class__ if retain is True else dict rv.append( @@ -278,7 +287,7 @@ def astuple( if has(vv.__class__) else vv, ) - for kk, vv in iteritems(v) + for kk, vv in v.items() ) ) else: @@ -291,28 +300,48 @@ def astuple( def has(cls): """ - Check whether *cls* is a class with ``attrs`` attributes. + Check whether *cls* is a class with *attrs* attributes. :param type cls: Class to introspect. :raise TypeError: If *cls* is not a class. :rtype: bool """ - return getattr(cls, "__attrs_attrs__", None) is not None + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is not None: + return True + + # No attrs, maybe it's a specialized generic (A[str])? + generic_base = get_generic_base(cls) + if generic_base is not None: + generic_attrs = getattr(generic_base, "__attrs_attrs__", None) + if generic_attrs is not None: + # Stick it on here for speed next time. + cls.__attrs_attrs__ = generic_attrs + return generic_attrs is not None + return False def assoc(inst, **changes): """ Copy *inst* and apply *changes*. - :param inst: Instance of a class with ``attrs`` attributes. + This is different from `evolve` that applies the changes to the arguments + that create the new instance. + + `evolve`'s behavior is preferable, but there are `edge cases`_ where it + doesn't work. Therefore `assoc` is deprecated, but will not be removed. + + .. _`edge cases`: https://github.com/python-attrs/attrs/issues/251 + + :param inst: Instance of a class with *attrs* attributes. :param changes: Keyword changes in the new copy. :return: A copy of inst with *changes* incorporated. - :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't - be found on *cls*. - :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + :raise attrs.exceptions.AttrsAttributeNotFoundError: If *attr_name* + couldn't be found on *cls*. + :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* class. .. deprecated:: 17.1.0 @@ -320,57 +349,79 @@ def assoc(inst, **changes): This function will not be removed du to the slightly different approach compared to `attrs.evolve`. """ - import warnings - - warnings.warn( - "assoc is deprecated and will be removed after 2018/01.", - DeprecationWarning, - stacklevel=2, - ) new = copy.copy(inst) attrs = fields(inst.__class__) - for k, v in iteritems(changes): + for k, v in changes.items(): a = getattr(attrs, k, NOTHING) if a is NOTHING: - raise AttrsAttributeNotFoundError( - "{k} is not an attrs attribute on {cl}.".format( - k=k, cl=new.__class__ - ) - ) + msg = f"{k} is not an attrs attribute on {new.__class__}." + raise AttrsAttributeNotFoundError(msg) _obj_setattr(new, k, v) return new -def evolve(inst, **changes): +def evolve(*args, **changes): """ - Create a new instance, based on *inst* with *changes* applied. + Create a new instance, based on the first positional argument with + *changes* applied. - :param inst: Instance of a class with ``attrs`` attributes. + :param inst: Instance of a class with *attrs* attributes. :param changes: Keyword changes in the new copy. :return: A copy of inst with *changes* incorporated. :raise TypeError: If *attr_name* couldn't be found in the class ``__init__``. - :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* class. - .. versionadded:: 17.1.0 + .. versionadded:: 17.1.0 + .. deprecated:: 23.1.0 + It is now deprecated to pass the instance using the keyword argument + *inst*. It will raise a warning until at least April 2024, after which + it will become an error. Always pass the instance as a positional + argument. """ + # Try to get instance by positional argument first. + # Use changes otherwise and warn it'll break. + if args: + try: + (inst,) = args + except ValueError: + msg = f"evolve() takes 1 positional argument, but {len(args)} were given" + raise TypeError(msg) from None + else: + try: + inst = changes.pop("inst") + except KeyError: + msg = "evolve() missing 1 required positional argument: 'inst'" + raise TypeError(msg) from None + + import warnings + + warnings.warn( + "Passing the instance per keyword argument is deprecated and " + "will stop working in, or after, April 2024.", + DeprecationWarning, + stacklevel=2, + ) + cls = inst.__class__ attrs = fields(cls) for a in attrs: if not a.init: continue attr_name = a.name # To deal with private attributes. - init_name = attr_name if attr_name[0] != "_" else attr_name[1:] + init_name = a.alias if init_name not in changes: changes[init_name] = getattr(inst, attr_name) return cls(**changes) -def resolve_types(cls, globalns=None, localns=None, attribs=None): +def resolve_types( + cls, globalns=None, localns=None, attribs=None, include_extras=True +): """ Resolve any strings and forward annotations in type annotations. @@ -389,10 +440,14 @@ def resolve_types(cls, globalns=None, localns=None, attribs=None): :param Optional[dict] localns: Dictionary containing local variables. :param Optional[list] attribs: List of attribs for the given class. This is necessary when calling from inside a ``field_transformer`` - since *cls* is not an ``attrs`` class yet. + since *cls* is not an *attrs* class yet. + :param bool include_extras: Resolve more accurately, if possible. + Pass ``include_extras`` to ``typing.get_hints``, if supported by the + typing module. On supported Python versions (3.9+), this resolves the + types more accurately. :raise TypeError: If *cls* is not a class. - :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* class and you didn't pass any attribs. :raise NameError: If types cannot be resolved because of missing variables. @@ -402,6 +457,7 @@ class and you didn't pass any attribs. .. versionadded:: 20.1.0 .. versionadded:: 21.1.0 *attribs* + .. versionadded:: 23.1.0 *include_extras* """ # Since calling get_type_hints is expensive we cache whether we've @@ -409,7 +465,12 @@ class and you didn't pass any attribs. if getattr(cls, "__attrs_types_resolved__", None) != cls: import typing - hints = typing.get_type_hints(cls, globalns=globalns, localns=localns) + kwargs = {"globalns": globalns, "localns": localns} + + if PY_3_9_PLUS: + kwargs["include_extras"] = include_extras + + hints = typing.get_type_hints(cls, **kwargs) for field in fields(cls) if attribs is None else attribs: if field.name in hints: # Since fields have been frozen we must work around it. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_make.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_make.py index d46f8a3e7a423..10b4eca779621 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_make.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_make.py @@ -1,12 +1,15 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function - +import contextlib import copy +import enum +import functools import inspect +import itertools import linecache import sys -import warnings +import types +import typing from operator import itemgetter @@ -14,37 +17,23 @@ # having the thread-local in the globals here. from . import _compat, _config, setters from ._compat import ( - HAS_F_STRINGS, - PY2, PY310, - PYPY, - isclass, - iteritems, - metadata_proxy, - new_class, - ordered_dict, - set_closure_cell, + PY_3_8_PLUS, + _AnnotationExtractor, + get_generic_base, ) from .exceptions import ( DefaultAlreadySetError, FrozenInstanceError, NotAnAttrsClassError, - PythonTooOldError, UnannotatedAttributeError, ) -if not PY2: - import typing - - # This is used at least twice, so cache it here. _obj_setattr = object.__setattr__ _init_converter_pat = "__attr_converter_%s" -_init_factory_pat = "__attr_factory_{}" -_tuple_property_pat = ( - " {attr_name} = _attrs_property(_attrs_itemgetter({index}))" -) +_init_factory_pat = "__attr_factory_%s" _classvar_prefixes = ( "typing.ClassVar", "t.ClassVar", @@ -56,7 +45,7 @@ # (when slots=True) _hash_cache_field = "_attrs_cached_hash" -_empty_metadata_singleton = metadata_proxy({}) +_empty_metadata_singleton = types.MappingProxyType({}) # Unique object for unequivocal getattr() defaults. _sentinel = object() @@ -64,21 +53,18 @@ _ng_default_on_setattr = setters.pipe(setters.convert, setters.validate) -class _Nothing(object): +class _Nothing(enum.Enum): """ - Sentinel class to indicate the lack of a value when ``None`` is ambiguous. + Sentinel to indicate the lack of a value when ``None`` is ambiguous. - ``_Nothing`` is a singleton. There is only ever one of it. + If extending attrs, you can use ``typing.Literal[NOTHING]`` to show + that a value may be ``NOTHING``. .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False. + .. versionchanged:: 22.2.0 ``NOTHING`` is now an ``enum.Enum`` variant. """ - _singleton = None - - def __new__(cls): - if _Nothing._singleton is None: - _Nothing._singleton = super(_Nothing, cls).__new__(cls) - return _Nothing._singleton + NOTHING = enum.auto() def __repr__(self): return "NOTHING" @@ -86,11 +72,8 @@ def __repr__(self): def __bool__(self): return False - def __len__(self): - return 0 # __bool__ for Python 2 - -NOTHING = _Nothing() +NOTHING = _Nothing.NOTHING """ Sentinel to indicate the lack of a value when ``None`` is ambiguous. """ @@ -108,17 +91,8 @@ class _CacheHashWrapper(int): See GH #613 for more details. """ - if PY2: - # For some reason `type(None)` isn't callable in Python 2, but we don't - # actually need a constructor for None objects, we just need any - # available function that returns None. - def __reduce__(self, _none_constructor=getattr, _args=(0, "", None)): - return _none_constructor, _args - - else: - - def __reduce__(self, _none_constructor=type(None), _args=()): - return _none_constructor, _args + def __reduce__(self, _none_constructor=type(None), _args=()): # noqa: B008 + return _none_constructor, _args def attrib( @@ -136,16 +110,20 @@ def attrib( eq=None, order=None, on_setattr=None, + alias=None, ): """ Create a new attribute on a class. .. warning:: - Does *not* do anything unless the class is also decorated with - `attr.s`! + Does *not* do anything unless the class is also decorated with `attr.s` + / `attrs.define` / and so on! - :param default: A value that is used if an ``attrs``-generated ``__init__`` + Please consider using `attrs.field` in new code (``attr.ib`` will *never* + go away, though). + + :param default: A value that is used if an *attrs*-generated ``__init__`` is used and no value is passed while instantiating or the attribute is excluded using ``init=False``. @@ -154,17 +132,17 @@ def attrib( or dicts). If a default is not set (or set manually to `attrs.NOTHING`), a value - *must* be supplied when instantiating; otherwise a `TypeError` - will be raised. + *must* be supplied when instantiating; otherwise a `TypeError` will be + raised. The default can also be set using decorator notation as shown below. - :type default: Any value + .. seealso:: `defaults` :param callable factory: Syntactic sugar for ``default=attr.Factory(factory)``. - :param validator: `callable` that is called by ``attrs``-generated + :param validator: `callable` that is called by *attrs*-generated ``__init__`` methods after the instance has been initialized. They receive the initialized instance, the :func:`~attrs.Attribute`, and the passed value. @@ -172,77 +150,90 @@ def attrib( The return value is *not* inspected so the validator has to throw an exception itself. - If a `list` is passed, its items are treated as validators and must - all pass. + If a `list` is passed, its items are treated as validators and must all + pass. Validators can be globally disabled and re-enabled using - `get_run_validators`. + `attrs.validators.get_disabled` / `attrs.validators.set_disabled`. The validator can also be set using decorator notation as shown below. + .. seealso:: :ref:`validators` + :type validator: `callable` or a `list` of `callable`\\ s. - :param repr: Include this attribute in the generated ``__repr__`` - method. If ``True``, include the attribute; if ``False``, omit it. By - default, the built-in ``repr()`` function is used. To override how the - attribute value is formatted, pass a ``callable`` that takes a single - value and returns a string. Note that the resulting string is used - as-is, i.e. it will be used directly *instead* of calling ``repr()`` - (the default). + :param repr: Include this attribute in the generated ``__repr__`` method. + If ``True``, include the attribute; if ``False``, omit it. By default, + the built-in ``repr()`` function is used. To override how the attribute + value is formatted, pass a ``callable`` that takes a single value and + returns a string. Note that the resulting string is used as-is, i.e. it + will be used directly *instead* of calling ``repr()`` (the default). :type repr: a `bool` or a `callable` to use a custom function. - :param eq: If ``True`` (default), include this attribute in the - generated ``__eq__`` and ``__ne__`` methods that check two instances - for equality. To override how the attribute value is compared, - pass a ``callable`` that takes a single value and returns the value - to be compared. + :param eq: If ``True`` (default), include this attribute in the generated + ``__eq__`` and ``__ne__`` methods that check two instances for + equality. To override how the attribute value is compared, pass a + ``callable`` that takes a single value and returns the value to be + compared. + + .. seealso:: `comparison` :type eq: a `bool` or a `callable`. :param order: If ``True`` (default), include this attributes in the - generated ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. - To override how the attribute value is ordered, - pass a ``callable`` that takes a single value and returns the value - to be ordered. + generated ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. To + override how the attribute value is ordered, pass a ``callable`` that + takes a single value and returns the value to be ordered. + + .. seealso:: `comparison` :type order: a `bool` or a `callable`. :param cmp: Setting *cmp* is equivalent to setting *eq* and *order* to the same value. Must not be mixed with *eq* or *order*. + + .. seealso:: `comparison` :type cmp: a `bool` or a `callable`. - :param Optional[bool] hash: Include this attribute in the generated + :param bool | None hash: Include this attribute in the generated ``__hash__`` method. If ``None`` (default), mirror *eq*'s value. This is the correct behavior according the Python spec. Setting this value to anything else than ``None`` is *discouraged*. + + .. seealso:: `hashing` :param bool init: Include this attribute in the generated ``__init__`` method. It is possible to set this to ``False`` and set a default value. In that case this attributed is unconditionally initialized with the specified default value or factory. - :param callable converter: `callable` that is called by - ``attrs``-generated ``__init__`` methods to convert attribute's value - to the desired format. It is given the passed-in value, and the - returned value will be used as the new value of the attribute. The - value is converted before being passed to the validator, if any. - :param metadata: An arbitrary mapping, to be used by third-party - components. See `extending_metadata`. - :param type: The type of the attribute. In Python 3.6 or greater, the - preferred method to specify the type is using a variable annotation - (see `PEP 526 `_). - This argument is provided for backward compatibility. - Regardless of the approach used, the type will be stored on - ``Attribute.type``. - - Please note that ``attrs`` doesn't do anything with this metadata by - itself. You can use it as part of your own code or for - `static type checking `. - :param kw_only: Make this attribute keyword-only (Python 3+) - in the generated ``__init__`` (if ``init`` is ``False``, this - parameter is ignored). + + .. seealso:: `init` + :param callable converter: `callable` that is called by *attrs*-generated + ``__init__`` methods to convert attribute's value to the desired + format. It is given the passed-in value, and the returned value will + be used as the new value of the attribute. The value is converted + before being passed to the validator, if any. + + .. seealso:: :ref:`converters` + :param dict | None metadata: An arbitrary mapping, to be used by + third-party components. See `extending-metadata`. + + :param type: The type of the attribute. Nowadays, the preferred method to + specify the type is using a variable annotation (see :pep:`526`). This + argument is provided for backward compatibility. Regardless of the + approach used, the type will be stored on ``Attribute.type``. + + Please note that *attrs* doesn't do anything with this metadata by + itself. You can use it as part of your own code or for `static type + checking `. + :param bool kw_only: Make this attribute keyword-only in the generated + ``__init__`` (if ``init`` is ``False``, this parameter is ignored). :param on_setattr: Allows to overwrite the *on_setattr* setting from `attr.s`. If left `None`, the *on_setattr* value from `attr.s` is used. Set to `attrs.setters.NO_OP` to run **no** `setattr` hooks for this attribute -- regardless of the setting in `attr.s`. :type on_setattr: `callable`, or a list of callables, or `None`, or `attrs.setters.NO_OP` + :param str | None alias: Override this attribute's parameter name in the + generated ``__init__`` method. If left `None`, default to ``name`` + stripped of leading underscores. See `private-attributes`. .. versionadded:: 15.2.0 *convert* .. versionadded:: 16.3.0 *metadata* @@ -265,24 +256,25 @@ def attrib( .. versionchanged:: 21.1.0 *eq*, *order*, and *cmp* also accept a custom callable .. versionchanged:: 21.1.0 *cmp* undeprecated + .. versionadded:: 22.2.0 *alias* """ eq, eq_key, order, order_key = _determine_attrib_eq_order( cmp, eq, order, True ) if hash is not None and hash is not True and hash is not False: - raise TypeError( - "Invalid value for hash. Must be True, False, or None." - ) + msg = "Invalid value for hash. Must be True, False, or None." + raise TypeError(msg) if factory is not None: if default is not NOTHING: - raise ValueError( - "The `default` and `factory` arguments are mutually " - "exclusive." + msg = ( + "The `default` and `factory` arguments are mutually exclusive." ) + raise ValueError(msg) if not callable(factory): - raise ValueError("The `factory` argument must be a callable.") + msg = "The `factory` argument must be a callable." + raise ValueError(msg) default = Factory(factory) if metadata is None: @@ -314,6 +306,7 @@ def attrib( order=order, order_key=order_key, on_setattr=on_setattr, + alias=alias, ) @@ -325,13 +318,11 @@ def _compile_and_eval(script, globs, locs=None, filename=""): eval(bytecode, globs, locs) -def _make_method(name, script, filename, globs=None): +def _make_method(name, script, filename, globs): """ Create the method with the script given and return the method object. """ locs = {} - if globs is None: - globs = {} # In order of debuggers like PDB being able to step through the code, # we add a fake linecache entry. @@ -347,9 +338,9 @@ def _make_method(name, script, filename, globs=None): old_val = linecache.cache.setdefault(filename, linecache_tuple) if old_val == linecache_tuple: break - else: - filename = "{}-{}>".format(base_filename[:-1], count) - count += 1 + + filename = f"{base_filename[:-1]}-{count}>" + count += 1 _compile_and_eval(script, globs, locs, filename) @@ -366,15 +357,15 @@ class MyClassAttributes(tuple): __slots__ = () x = property(itemgetter(0)) """ - attr_class_name = "{}Attributes".format(cls_name) + attr_class_name = f"{cls_name}Attributes" attr_class_template = [ - "class {}(tuple):".format(attr_class_name), + f"class {attr_class_name}(tuple):", " __slots__ = ()", ] if attr_names: for i, attr_name in enumerate(attr_names): attr_class_template.append( - _tuple_property_pat.format(index=i, attr_name=attr_name) + f" {attr_name} = _attrs_property(_attrs_itemgetter({i}))" ) else: attr_class_template.append(" pass") @@ -418,8 +409,6 @@ def _is_class_var(annot): def _has_own_attribute(cls, attrib_name): """ Check whether *cls* defines *attrib_name* (and doesn't just inherit it). - - Requires Python 3. """ attr = getattr(cls, attrib_name, _sentinel) if attr is _sentinel: @@ -443,13 +432,6 @@ def _get_annotations(cls): return {} -def _counter_getter(e): - """ - Key function for sorting to avoid re-creating a lambda for every class. - """ - return e[1].counter - - def _collect_base_attrs(cls, taken_attr_names): """ Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. @@ -463,7 +445,7 @@ def _collect_base_attrs(cls, taken_attr_names): if a.inherited or a.name in taken_attr_names: continue - a = a.evolve(inherited=True) + a = a.evolve(inherited=True) # noqa: PLW2901 base_attrs.append(a) base_attr_map[a.name] = base_cls @@ -501,7 +483,7 @@ def _collect_base_attrs_broken(cls, taken_attr_names): if a.name in taken_attr_names: continue - a = a.evolve(inherited=True) + a = a.evolve(inherited=True) # noqa: PLW2901 taken_attr_names.add(a.name) base_attrs.append(a) base_attr_map[a.name] = base_cls @@ -526,10 +508,7 @@ def _transform_attrs( anns = _get_annotations(cls) if these is not None: - ca_list = [(name, ca) for name, ca in iteritems(these)] - - if not isinstance(these, ordered_dict): - ca_list.sort(key=_counter_getter) + ca_list = list(these.items()) elif auto_attribs is True: ca_names = { name @@ -545,10 +524,7 @@ def _transform_attrs( a = cd.get(attr_name, NOTHING) if not isinstance(a, _CountingAttr): - if a is NOTHING: - a = attrib() - else: - a = attrib(default=a) + a = attrib() if a is NOTHING else attrib(default=a) ca_list.append((attr_name, a)) unannotated = ca_names - annot_names @@ -599,10 +575,8 @@ def _transform_attrs( had_default = False for a in (a for a in attrs if a.init is not False and a.kw_only is False): if had_default is True and a.default is NOTHING: - raise ValueError( - "No mandatory attributes allowed after an attribute with a " - "default value or factory. Attribute in question: %r" % (a,) - ) + msg = f"No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: {a!r}" + raise ValueError(msg) if had_default is False and a.default is not NOTHING: had_default = True @@ -610,6 +584,14 @@ def _transform_attrs( if field_transformer is not None: attrs = field_transformer(cls, attrs) + # Resolve default field alias after executing field_transformer. + # This allows field_transformer to differentiate between explicit vs + # default aliases and supply their own defaults. + attrs = [ + a.evolve(alias=_default_init_alias_for(a.name)) if not a.alias else a + for a in attrs + ] + # Create AttrsClass *after* applying the field_transformer since it may # add or remove attributes! attr_names = [a.name for a in attrs] @@ -618,28 +600,75 @@ def _transform_attrs( return _Attributes((AttrsClass(attrs), base_attrs, base_attr_map)) -if PYPY: +def _make_cached_property_getattr( + cached_properties, + original_getattr, + cls, +): + lines = [ + # Wrapped to get `__class__` into closure cell for super() + # (It will be replaced with the newly constructed class after construction). + "def wrapper():", + " __class__ = _cls", + " def __getattr__(self, item, cached_properties=cached_properties, original_getattr=original_getattr, _cached_setattr_get=_cached_setattr_get):", + " func = cached_properties.get(item)", + " if func is not None:", + " result = func(self)", + " _setter = _cached_setattr_get(self)", + " _setter(item, result)", + " return result", + ] + if original_getattr is not None: + lines.append( + " return original_getattr(self, item)", + ) + else: + lines.extend( + [ + " if hasattr(super(), '__getattr__'):", + " return super().__getattr__(item)", + " original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"", + " raise AttributeError(original_error)", + ] + ) + + lines.extend( + [ + " return __getattr__", + "__getattr__ = wrapper()", + ] + ) + + unique_filename = _generate_unique_filename(cls, "getattr") - def _frozen_setattrs(self, name, value): - """ - Attached to frozen classes as __setattr__. - """ - if isinstance(self, BaseException) and name in ( - "__cause__", - "__context__", - ): - BaseException.__setattr__(self, name, value) - return + glob = { + "cached_properties": cached_properties, + "_cached_setattr_get": _obj_setattr.__get__, + "_cls": cls, + "original_getattr": original_getattr, + } - raise FrozenInstanceError() + return _make_method( + "__getattr__", + "\n".join(lines), + unique_filename, + glob, + ) -else: - def _frozen_setattrs(self, name, value): - """ - Attached to frozen classes as __setattr__. - """ - raise FrozenInstanceError() +def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + if isinstance(self, BaseException) and name in ( + "__cause__", + "__context__", + "__traceback__", + ): + BaseException.__setattr__(self, name, value) + return + + raise FrozenInstanceError() def _frozen_delattrs(self, name): @@ -649,7 +678,7 @@ def _frozen_delattrs(self, name): raise FrozenInstanceError() -class _ClassBuilder(object): +class _ClassBuilder: """ Iteratively build *one* class. """ @@ -665,6 +694,7 @@ class _ClassBuilder(object): "_delete_attribs", "_frozen", "_has_pre_init", + "_pre_init_has_args", "_has_post_init", "_is_exc", "_on_setattr", @@ -703,7 +733,7 @@ def __init__( self._cls = cls self._cls_dict = dict(cls.__dict__) if slots else {} self._attrs = attrs - self._base_names = set(a.name for a in base_attrs) + self._base_names = {a.name for a in base_attrs} self._base_attr_map = base_map self._attr_names = tuple(a.name for a in attrs) self._slots = slots @@ -711,6 +741,13 @@ def __init__( self._weakref_slot = weakref_slot self._cache_hash = cache_hash self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False)) + self._pre_init_has_args = False + if self._has_pre_init: + # Check if the pre init method has more arguments than just `self` + # We want to pass arguments if pre init expects arguments + pre_init_func = cls.__attrs_pre_init__ + pre_init_signature = inspect.signature(pre_init_func) + self._pre_init_has_args = len(pre_init_signature.parameters) > 1 self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False)) self._delete_attribs = not bool(these) self._is_exc = is_exc @@ -760,17 +797,35 @@ def __init__( ) = self._make_getstate_setstate() def __repr__(self): - return "<_ClassBuilder(cls={cls})>".format(cls=self._cls.__name__) + return f"<_ClassBuilder(cls={self._cls.__name__})>" - def build_class(self): - """ - Finalize class based on the accumulated configuration. + if PY310: + import abc + + def build_class(self): + """ + Finalize class based on the accumulated configuration. + + Builder cannot be used after calling this method. + """ + if self._slots is True: + return self._create_slots_class() + + return self.abc.update_abstractmethods( + self._patch_original_class() + ) + + else: + + def build_class(self): + """ + Finalize class based on the accumulated configuration. + + Builder cannot be used after calling this method. + """ + if self._slots is True: + return self._create_slots_class() - Builder cannot be used after calling this method. - """ - if self._slots is True: - return self._create_slots_class() - else: return self._patch_original_class() def _patch_original_class(self): @@ -787,13 +842,11 @@ def _patch_original_class(self): name not in base_names and getattr(cls, name, _sentinel) is not _sentinel ): - try: + # An AttributeError can happen if a base class defines a + # class variable and we want to set an attribute with the + # same name by using only a type annotation. + with contextlib.suppress(AttributeError): delattr(cls, name) - except AttributeError: - # This can happen if a base class defines a class - # variable and we want to set an attribute with the - # same name by using only a type annotation. - pass # Attach our dunder methods. for name, value in self._cls_dict.items(): @@ -807,7 +860,7 @@ def _patch_original_class(self): cls.__attrs_own_setattr__ = False if not self._has_custom_setattr: - cls.__setattr__ = object.__setattr__ + cls.__setattr__ = _obj_setattr return cls @@ -817,8 +870,8 @@ def _create_slots_class(self): """ cd = { k: v - for k, v in iteritems(self._cls_dict) - if k not in tuple(self._attr_names) + ("__dict__", "__weakref__") + for k, v in self._cls_dict.items() + if k not in (*tuple(self._attr_names), "__dict__", "__weakref__") } # If our class doesn't have its own implementation of __setattr__ @@ -835,12 +888,12 @@ def _create_slots_class(self): if not self._has_custom_setattr: for base_cls in self._cls.__bases__: if base_cls.__dict__.get("__attrs_own_setattr__", False): - cd["__setattr__"] = object.__setattr__ + cd["__setattr__"] = _obj_setattr break # Traverse the MRO to collect existing slots # and check for an existing __weakref__. - existing_slots = dict() + existing_slots = {} weakref_inherited = False for base_cls in self._cls.__mro__[1:-1]: if base_cls.__dict__.get("__weakref__", None) is not None: @@ -863,38 +916,76 @@ def _create_slots_class(self): ): names += ("__weakref__",) + if PY_3_8_PLUS: + cached_properties = { + name: cached_property.func + for name, cached_property in cd.items() + if isinstance(cached_property, functools.cached_property) + } + else: + # `functools.cached_property` was introduced in 3.8. + # So can't be used before this. + cached_properties = {} + + # Collect methods with a `__class__` reference that are shadowed in the new class. + # To know to update them. + additional_closure_functions_to_update = [] + if cached_properties: + # Add cached properties to names for slotting. + names += tuple(cached_properties.keys()) + + for name in cached_properties: + # Clear out function from class to avoid clashing. + del cd[name] + + class_annotations = _get_annotations(self._cls) + for name, func in cached_properties.items(): + annotation = inspect.signature(func).return_annotation + if annotation is not inspect.Parameter.empty: + class_annotations[name] = annotation + + original_getattr = cd.get("__getattr__") + if original_getattr is not None: + additional_closure_functions_to_update.append(original_getattr) + + cd["__getattr__"] = _make_cached_property_getattr( + cached_properties, original_getattr, self._cls + ) + # We only add the names of attributes that aren't inherited. # Setting __slots__ to inherited attributes wastes memory. slot_names = [name for name in names if name not in base_names] + # There are slots for attributes from current class # that are defined in parent classes. - # As their descriptors may be overriden by a child class, + # As their descriptors may be overridden by a child class, # we collect them here and update the class dict reused_slots = { slot: slot_descriptor - for slot, slot_descriptor in iteritems(existing_slots) + for slot, slot_descriptor in existing_slots.items() if slot in slot_names } slot_names = [name for name in slot_names if name not in reused_slots] cd.update(reused_slots) if self._cache_hash: slot_names.append(_hash_cache_field) + cd["__slots__"] = tuple(slot_names) - qualname = getattr(self._cls, "__qualname__", None) - if qualname is not None: - cd["__qualname__"] = qualname + cd["__qualname__"] = self._cls.__qualname__ # Create new class based on old class and our methods. cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd) # The following is a fix for - # . On Python 3, - # if a method mentions `__class__` or uses the no-arg super(), the + # . + # If a method mentions `__class__` or uses the no-arg super(), the # compiler will bake a reference to the class in the method itself # as `method.__closure__`. Since we replace the class with a # clone, we rewrite these references so it keeps working. - for item in cls.__dict__.values(): + for item in itertools.chain( + cls.__dict__.values(), additional_closure_functions_to_update + ): if isinstance(item, (classmethod, staticmethod)): # Class- and staticmethods hide their functions inside. # These might need to be rewritten as well. @@ -911,12 +1002,12 @@ def _create_slots_class(self): for cell in closure_cells: try: match = cell.cell_contents is self._cls - except ValueError: # ValueError: Cell is empty + except ValueError: # noqa: PERF203 + # ValueError: Cell is empty pass else: if match: - set_closure_cell(cell, cls) - + cell.cell_contents = cls return cls def add_repr(self, ns): @@ -928,9 +1019,8 @@ def add_repr(self, ns): def add_str(self): repr = self._cls_dict.get("__repr__") if repr is None: - raise ValueError( - "__str__ can only be generated if a __repr__ exists." - ) + msg = "__str__ can only be generated if a __repr__ exists." + raise ValueError(msg) def __str__(self): return self.__repr__() @@ -951,7 +1041,7 @@ def slots_getstate(self): """ Automatically created by attrs. """ - return tuple(getattr(self, name) for name in state_attr_names) + return {name: getattr(self, name) for name in state_attr_names} hash_caching_enabled = self._cache_hash @@ -959,9 +1049,16 @@ def slots_setstate(self, state): """ Automatically created by attrs. """ - __bound_setattr = _obj_setattr.__get__(self, Attribute) - for name, value in zip(state_attr_names, state): - __bound_setattr(name, value) + __bound_setattr = _obj_setattr.__get__(self) + if isinstance(state, tuple): + # Backward compatibility with attrs instances pickled with + # attrs versions before v22.2.0 which stored tuples. + for name, value in zip(state_attr_names, state): + __bound_setattr(name, value) + else: + for name in state_attr_names: + if name in state: + __bound_setattr(name, state[name]) # The hash code cache is not included when the object is # serialized, but it still needs to be initialized to None to @@ -994,6 +1091,7 @@ def add_init(self): self._cls, self._attrs, self._has_pre_init, + self._pre_init_has_args, self._has_post_init, self._frozen, self._slots, @@ -1020,6 +1118,7 @@ def add_attrs_init(self): self._cls, self._attrs, self._has_pre_init, + self._pre_init_has_args, self._has_post_init, self._frozen, self._slots, @@ -1068,9 +1167,8 @@ def add_setattr(self): if self._has_custom_setattr: # We need to write a __setattr__ but there already is one! - raise ValueError( - "Can't combine custom __setattr__ with on_setattr hooks." - ) + msg = "Can't combine custom __setattr__ with on_setattr hooks." + raise ValueError(msg) # docstring comes from _add_method_dunders def __setattr__(self, name, val): @@ -1093,41 +1191,29 @@ def _add_method_dunders(self, method): """ Add __module__ and __qualname__ to a *method* if possible. """ - try: + with contextlib.suppress(AttributeError): method.__module__ = self._cls.__module__ - except AttributeError: - pass - try: - method.__qualname__ = ".".join( - (self._cls.__qualname__, method.__name__) - ) - except AttributeError: - pass + with contextlib.suppress(AttributeError): + method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}" - try: - method.__doc__ = "Method generated by attrs for class %s." % ( - self._cls.__qualname__, + with contextlib.suppress(AttributeError): + method.__doc__ = ( + "Method generated by attrs for class " + f"{self._cls.__qualname__}." ) - except AttributeError: - pass return method -_CMP_DEPRECATION = ( - "The usage of `cmp` is deprecated and will be removed on or after " - "2021-06-01. Please use `eq` and `order` instead." -) - - def _determine_attrs_eq_order(cmp, eq, order, default_eq): """ Validate the combination of *cmp*, *eq*, and *order*. Derive the effective values of eq and order. If *eq* is None, set it to *default_eq*. """ if cmp is not None and any((eq is not None, order is not None)): - raise ValueError("Don't mix `cmp` with `eq' and `order`.") + msg = "Don't mix `cmp` with `eq' and `order`." + raise ValueError(msg) # cmp takes precedence due to bw-compatibility. if cmp is not None: @@ -1142,7 +1228,8 @@ def _determine_attrs_eq_order(cmp, eq, order, default_eq): order = eq if eq is False and order is True: - raise ValueError("`order` can only be True if `eq` is True too.") + msg = "`order` can only be True if `eq` is True too." + raise ValueError(msg) return eq, order @@ -1153,7 +1240,8 @@ def _determine_attrib_eq_order(cmp, eq, order, default_eq): values of eq and order. If *eq* is None, set it to *default_eq*. """ if cmp is not None and any((eq is not None, order is not None)): - raise ValueError("Don't mix `cmp` with `eq' and `order`.") + msg = "Don't mix `cmp` with `eq' and `order`." + raise ValueError(msg) def decide_callable_or_boolean(value): """ @@ -1183,7 +1271,8 @@ def decide_callable_or_boolean(value): order, order_key = decide_callable_or_boolean(order) if eq is False and order is True: - raise ValueError("`order` can only be True if `eq` is True too.") + msg = "`order` can only be True if `eq` is True too." + raise ValueError(msg) return eq, eq_key, order, order_key @@ -1199,8 +1288,6 @@ def _determine_whether_to_implement( whose presence signal that the user has implemented it themselves. Return *default* if no reason for either for or against is found. - - auto_detect must be False on Python 2. """ if flag is True or flag is False: return flag @@ -1240,24 +1327,24 @@ def attrs( on_setattr=None, field_transformer=None, match_args=True, + unsafe_hash=None, ): r""" - A class decorator that adds `dunder - `_\ -methods according to the + A class decorator that adds :term:`dunder methods` according to the specified attributes using `attr.ib` or the *these* argument. - :param these: A dictionary of name to `attr.ib` mappings. This is - useful to avoid the definition of your attributes within the class body + Please consider using `attrs.define` / `attrs.frozen` in new code + (``attr.s`` will *never* go away, though). + + :param these: A dictionary of name to `attr.ib` mappings. This is useful + to avoid the definition of your attributes within the class body because you can't (e.g. if you want to add ``__repr__`` methods to Django models) or don't want to. - If *these* is not ``None``, ``attrs`` will *not* search the class body + If *these* is not ``None``, *attrs* will *not* search the class body for attributes and will *not* remove any attributes from it. - If *these* is an ordered dict (`dict` on Python 3.6+, - `collections.OrderedDict` otherwise), the order is deduced from - the order of the attributes inside *these*. Otherwise the order - of the definition of the attributes is used. + The order is deduced from the order of the attributes inside *these*. :type these: `dict` of `str` to `attr.ib` @@ -1270,79 +1357,89 @@ def attrs( arguments is implemented in the *current* class (i.e. it is *not* inherited from some base class). - So for example by implementing ``__eq__`` on a class yourself, - ``attrs`` will deduce ``eq=False`` and will create *neither* - ``__eq__`` *nor* ``__ne__`` (but Python classes come with a sensible - ``__ne__`` by default, so it *should* be enough to only implement - ``__eq__`` in most cases). + So for example by implementing ``__eq__`` on a class yourself, *attrs* + will deduce ``eq=False`` and will create *neither* ``__eq__`` *nor* + ``__ne__`` (but Python classes come with a sensible ``__ne__`` by + default, so it *should* be enough to only implement ``__eq__`` in most + cases). .. warning:: - If you prevent ``attrs`` from creating the ordering methods for you + If you prevent *attrs* from creating the ordering methods for you (``order=False``, e.g. by implementing ``__le__``), it becomes *your* responsibility to make sure its ordering is sound. The best way is to use the `functools.total_ordering` decorator. - Passing ``True`` or ``False`` to *init*, *repr*, *eq*, *order*, - *cmp*, or *hash* overrides whatever *auto_detect* would determine. - - *auto_detect* requires Python 3. Setting it ``True`` on Python 2 raises - an `attrs.exceptions.PythonTooOldError`. + Passing ``True`` or ``False`` to *init*, *repr*, *eq*, *order*, *cmp*, + or *hash* overrides whatever *auto_detect* would determine. :param bool repr: Create a ``__repr__`` method with a human readable - representation of ``attrs`` attributes.. + representation of *attrs* attributes.. :param bool str: Create a ``__str__`` method that is identical to - ``__repr__``. This is usually not necessary except for - `Exception`\ s. - :param Optional[bool] eq: If ``True`` or ``None`` (default), add ``__eq__`` + ``__repr__``. This is usually not necessary except for `Exception`\ s. + :param bool | None eq: If ``True`` or ``None`` (default), add ``__eq__`` and ``__ne__`` methods that check two instances for equality. - They compare the instances as if they were tuples of their ``attrs`` + They compare the instances as if they were tuples of their *attrs* attributes if and only if the types of both classes are *identical*! - :param Optional[bool] order: If ``True``, add ``__lt__``, ``__le__``, + + .. seealso:: `comparison` + :param bool | None order: If ``True``, add ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` methods that behave like *eq* above and allow instances to be ordered. If ``None`` (default) mirror value of *eq*. - :param Optional[bool] cmp: Setting *cmp* is equivalent to setting *eq* - and *order* to the same value. Must not be mixed with *eq* or *order*. - :param Optional[bool] hash: If ``None`` (default), the ``__hash__`` method - is generated according how *eq* and *frozen* are set. - 1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you. + .. seealso:: `comparison` + :param bool | None cmp: Setting *cmp* is equivalent to setting *eq* and + *order* to the same value. Must not be mixed with *eq* or *order*. + + .. seealso:: `comparison` + :param bool | None unsafe_hash: If ``None`` (default), the ``__hash__`` + method is generated according how *eq* and *frozen* are set. + + 1. If *both* are True, *attrs* will generate a ``__hash__`` for you. 2. If *eq* is True and *frozen* is False, ``__hash__`` will be set to None, marking it unhashable (which it is). 3. If *eq* is False, ``__hash__`` will be left untouched meaning the ``__hash__`` method of the base class will be used (if base class is ``object``, this means it will fall back to id-based hashing.). - Although not recommended, you can decide for yourself and force - ``attrs`` to create one (e.g. if the class is immutable even though you - didn't freeze it programmatically) by passing ``True`` or not. Both of - these cases are rather special and should be used carefully. - - See our documentation on `hashing`, Python's documentation on - `object.__hash__`, and the `GitHub issue that led to the default \ - behavior `_ for more - details. - :param bool init: Create a ``__init__`` method that initializes the - ``attrs`` attributes. Leading underscores are stripped for the argument - name. If a ``__attrs_pre_init__`` method exists on the class, it will - be called before the class is initialized. If a ``__attrs_post_init__`` - method exists on the class, it will be called after the class is fully + Although not recommended, you can decide for yourself and force *attrs* + to create one (e.g. if the class is immutable even though you didn't + freeze it programmatically) by passing ``True`` or not. Both of these + cases are rather special and should be used carefully. + + .. seealso:: + + - Our documentation on `hashing`, + - Python's documentation on `object.__hash__`, + - and the `GitHub issue that led to the default \ + behavior `_ for + more details. + + :param bool | None hash: Alias for *unsafe_hash*. *unsafe_hash* takes + precedence. + :param bool init: Create a ``__init__`` method that initializes the *attrs* + attributes. Leading underscores are stripped for the argument name. If + a ``__attrs_pre_init__`` method exists on the class, it will be called + before the class is initialized. If a ``__attrs_post_init__`` method + exists on the class, it will be called after the class is fully initialized. - If ``init`` is ``False``, an ``__attrs_init__`` method will be - injected instead. This allows you to define a custom ``__init__`` - method that can do pre-init work such as ``super().__init__()``, - and then call ``__attrs_init__()`` and ``__attrs_post_init__()``. - :param bool slots: Create a `slotted class ` that's more - memory-efficient. Slotted classes are generally superior to the default - dict classes, but have some gotchas you should know about, so we - encourage you to read the `glossary entry `. + If ``init`` is ``False``, an ``__attrs_init__`` method will be injected + instead. This allows you to define a custom ``__init__`` method that + can do pre-init work such as ``super().__init__()``, and then call + ``__attrs_init__()`` and ``__attrs_post_init__()``. + + .. seealso:: `init` + :param bool slots: Create a :term:`slotted class ` that's + more memory-efficient. Slotted classes are generally superior to the + default dict classes, but have some gotchas you should know about, so + we encourage you to read the :term:`glossary entry `. :param bool frozen: Make instances immutable after initialization. If someone attempts to modify a frozen instance, - `attr.exceptions.FrozenInstanceError` is raised. + `attrs.exceptions.FrozenInstanceError` is raised. .. note:: @@ -1357,21 +1454,21 @@ def attrs( 4. If a class is frozen, you cannot modify ``self`` in ``__attrs_post_init__`` or a self-written ``__init__``. You can - circumvent that limitation by using - ``object.__setattr__(self, "attribute_name", value)``. + circumvent that limitation by using ``object.__setattr__(self, + "attribute_name", value)``. 5. Subclasses of a frozen class are frozen too. :param bool weakref_slot: Make instances weak-referenceable. This has no effect unless ``slots`` is also enabled. - :param bool auto_attribs: If ``True``, collect `PEP 526`_-annotated - attributes (Python 3.6 and later only) from the class body. + :param bool auto_attribs: If ``True``, collect :pep:`526`-annotated + attributes from the class body. - In this case, you **must** annotate every field. If ``attrs`` - encounters a field that is set to an `attr.ib` but lacks a type - annotation, an `attr.exceptions.UnannotatedAttributeError` is - raised. Use ``field_name: typing.Any = attr.ib(...)`` if you don't - want to set a type. + In this case, you **must** annotate every field. If *attrs* encounters + a field that is set to an `attr.ib` but lacks a type annotation, an + `attr.exceptions.UnannotatedAttributeError` is raised. Use + ``field_name: typing.Any = attr.ib(...)`` if you don't want to set a + type. If you assign a value to those attributes (e.g. ``x: int = 42``), that value becomes the default value like if it were passed using @@ -1383,58 +1480,55 @@ def attrs( .. warning:: For features that use the attribute name to create decorators (e.g. - `validators `), you still *must* assign `attr.ib` to - them. Otherwise Python will either not find the name or try to use - the default value to call e.g. ``validator`` on it. + :ref:`validators `), you still *must* assign `attr.ib` + to them. Otherwise Python will either not find the name or try to + use the default value to call e.g. ``validator`` on it. These errors can be quite confusing and probably the most common bug report on our bug tracker. - .. _`PEP 526`: https://www.python.org/dev/peps/pep-0526/ - :param bool kw_only: Make all attributes keyword-only (Python 3+) - in the generated ``__init__`` (if ``init`` is ``False``, this - parameter is ignored). - :param bool cache_hash: Ensure that the object's hash code is computed - only once and stored on the object. If this is set to ``True``, - hashing must be either explicitly or implicitly enabled for this - class. If the hash code is cached, avoid any reassignments of - fields involved in hash code computation or mutations of the objects - those fields point to after object creation. If such changes occur, - the behavior of the object's hash code is undefined. - :param bool auto_exc: If the class subclasses `BaseException` - (which implicitly includes any subclass of any exception), the - following happens to behave like a well-behaved Python exceptions - class: + :param bool kw_only: Make all attributes keyword-only in the generated + ``__init__`` (if ``init`` is ``False``, this parameter is ignored). + :param bool cache_hash: Ensure that the object's hash code is computed only + once and stored on the object. If this is set to ``True``, hashing + must be either explicitly or implicitly enabled for this class. If the + hash code is cached, avoid any reassignments of fields involved in hash + code computation or mutations of the objects those fields point to + after object creation. If such changes occur, the behavior of the + object's hash code is undefined. + :param bool auto_exc: If the class subclasses `BaseException` (which + implicitly includes any subclass of any exception), the following + happens to behave like a well-behaved Python exceptions class: - the values for *eq*, *order*, and *hash* are ignored and the - instances compare and hash by the instance's ids (N.B. ``attrs`` will + instances compare and hash by the instance's ids (N.B. *attrs* will *not* remove existing implementations of ``__hash__`` or the equality methods. It just won't add own ones.), - all attributes that are either passed into ``__init__`` or have a default value are additionally available as a tuple in the ``args`` attribute, - the value of *str* is ignored leaving ``__str__`` to base classes. - :param bool collect_by_mro: Setting this to `True` fixes the way ``attrs`` + :param bool collect_by_mro: Setting this to `True` fixes the way *attrs* collects attributes from base classes. The default behavior is incorrect in certain cases of multiple inheritance. It should be on by default but is kept off for backward-compatibility. - See issue `#428 `_ for - more details. + .. seealso:: + Issue `#428 `_ - :param Optional[bool] getstate_setstate: + :param bool | None getstate_setstate: .. note:: This is usually only interesting for slotted classes and you should probably just set *auto_detect* to `True`. - If `True`, ``__getstate__`` and - ``__setstate__`` are generated and attached to the class. This is - necessary for slotted classes to be pickleable. If left `None`, it's - `True` by default for slotted classes and ``False`` for dict classes. + If `True`, ``__getstate__`` and ``__setstate__`` are generated and + attached to the class. This is necessary for slotted classes to be + pickleable. If left `None`, it's `True` by default for slotted classes + and ``False`` for dict classes. - If *auto_detect* is `True`, and *getstate_setstate* is left `None`, - and **either** ``__getstate__`` or ``__setstate__`` is detected directly - on the class (i.e. not inherited), it is set to `False` (this is usually + If *auto_detect* is `True`, and *getstate_setstate* is left `None`, and + **either** ``__getstate__`` or ``__setstate__`` is detected directly on + the class (i.e. not inherited), it is set to `False` (this is usually what you want). :param on_setattr: A callable that is run whenever the user attempts to set @@ -1448,19 +1542,22 @@ def attrs( If a list of callables is passed, they're automatically wrapped in an `attrs.setters.pipe`. + :type on_setattr: `callable`, or a list of callables, or `None`, or + `attrs.setters.NO_OP` - :param Optional[callable] field_transformer: - A function that is called with the original class object and all - fields right before ``attrs`` finalizes the class. You can use - this, e.g., to automatically add converters or validators to - fields based on their types. See `transform-fields` for more details. + :param callable | None field_transformer: + A function that is called with the original class object and all fields + right before *attrs* finalizes the class. You can use this, e.g., to + automatically add converters or validators to fields based on their + types. + + .. seealso:: `transform-fields` :param bool match_args: If `True` (default), set ``__match_args__`` on the class to support - `PEP 634 `_ (Structural - Pattern Matching). It is a tuple of all positional-only ``__init__`` - parameter names on Python 3.10 and later. Ignored on older Python - versions. + :pep:`634` (Structural Pattern Matching). It is a tuple of all + non-keyword-only ``__init__`` parameter names on Python 3.10 and later. + Ignored on older Python versions. .. versionadded:: 16.0.0 *slots* .. versionadded:: 16.1.0 *frozen* @@ -1496,23 +1593,19 @@ def attrs( .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__`` .. versionchanged:: 21.1.0 *cmp* undeprecated .. versionadded:: 21.3.0 *match_args* + .. versionadded:: 22.2.0 + *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance). """ - if auto_detect and PY2: - raise PythonTooOldError( - "auto_detect only works on Python 3 and later." - ) - eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None) - hash_ = hash # work around the lack of nonlocal + + # unsafe_hash takes precedence due to PEP 681. + if unsafe_hash is not None: + hash = unsafe_hash if isinstance(on_setattr, (list, tuple)): on_setattr = setters.pipe(*on_setattr) def wrap(cls): - - if getattr(cls, "__class__", None) is None: - raise TypeError("attrs only works with new-style classes.") - is_frozen = frozen or _has_frozen_base_class(cls) is_exc = auto_exc is True and issubclass(cls, BaseException) has_own_setattr = auto_detect and _has_own_attribute( @@ -1520,7 +1613,8 @@ def wrap(cls): ) if has_own_setattr and is_frozen: - raise ValueError("Can't freeze a class with a custom __setattr__.") + msg = "Can't freeze a class with a custom __setattr__." + raise ValueError(msg) builder = _ClassBuilder( cls, @@ -1563,28 +1657,25 @@ def wrap(cls): builder.add_setattr() + nonlocal hash if ( - hash_ is None + hash is None and auto_detect is True and _has_own_attribute(cls, "__hash__") ): hash = False - else: - hash = hash_ + if hash is not True and hash is not False and hash is not None: # Can't use `hash in` because 1 == True for example. - raise TypeError( - "Invalid value for hash. Must be True, False, or None." - ) - elif hash is False or (hash is None and eq is False) or is_exc: + msg = "Invalid value for hash. Must be True, False, or None." + raise TypeError(msg) + + if hash is False or (hash is None and eq is False) or is_exc: # Don't do anything. Should fall back to __object__'s __hash__ # which is by id. if cache_hash: - raise TypeError( - "Invalid value for cache_hash. To use hash caching," - " hashing must be either explicitly or implicitly " - "enabled." - ) + msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled." + raise TypeError(msg) elif hash is True or ( hash is None and eq is True and is_frozen is True ): @@ -1593,11 +1684,8 @@ def wrap(cls): else: # Raise TypeError on attempts to hash. if cache_hash: - raise TypeError( - "Invalid value for cache_hash. To use hash caching," - " hashing must be either explicitly or implicitly " - "enabled." - ) + msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled." + raise TypeError(msg) builder.make_unhashable() if _determine_whether_to_implement( @@ -1607,10 +1695,8 @@ def wrap(cls): else: builder.add_attrs_init() if cache_hash: - raise TypeError( - "Invalid value for cache_hash. To use hash caching," - " init must be True." - ) + msg = "Invalid value for cache_hash. To use hash caching, init must be True." + raise TypeError(msg) if ( PY310 @@ -1625,8 +1711,8 @@ def wrap(cls): # if it's used as `@attrs` but ``None`` if used as `@attrs()`. if maybe_cls is None: return wrap - else: - return wrap(maybe_cls) + + return wrap(maybe_cls) _attrs = attrs @@ -1636,39 +1722,22 @@ def wrap(cls): """ -if PY2: - - def _has_frozen_base_class(cls): - """ - Check whether *cls* has a frozen ancestor by looking at its - __setattr__. - """ - return ( - getattr(cls.__setattr__, "__module__", None) - == _frozen_setattrs.__module__ - and cls.__setattr__.__name__ == _frozen_setattrs.__name__ - ) - -else: - - def _has_frozen_base_class(cls): - """ - Check whether *cls* has a frozen ancestor by looking at its - __setattr__. - """ - return cls.__setattr__ == _frozen_setattrs +def _has_frozen_base_class(cls): + """ + Check whether *cls* has a frozen ancestor by looking at its + __setattr__. + """ + return cls.__setattr__ is _frozen_setattrs def _generate_unique_filename(cls, func_name): """ Create a "filename" suitable for a function being generated. """ - unique_filename = "".format( - func_name, - cls.__module__, - getattr(cls, "__qualname__", cls.__name__), + return ( + f"" ) - return unique_filename def _make_hash(cls, attrs, frozen, cache_hash): @@ -1680,6 +1749,8 @@ def _make_hash(cls, attrs, frozen, cache_hash): unique_filename = _generate_unique_filename(cls, "hash") type_hash = hash(unique_filename) + # If eq is custom generated, we need to include the functions in globs + globs = {} hash_def = "def __hash__(self" hash_func = "hash((" @@ -1687,13 +1758,9 @@ def _make_hash(cls, attrs, frozen, cache_hash): if not cache_hash: hash_def += "):" else: - if not PY2: - hash_def += ", *" + hash_def += ", *" - hash_def += ( - ", _cache_wrapper=" - + "__import__('attr._make')._make._CacheHashWrapper):" - ) + hash_def += ", _cache_wrapper=__import__('attr._make')._make._CacheHashWrapper):" hash_func = "_cache_wrapper(" + hash_func closing_braces += ")" @@ -1709,32 +1776,39 @@ def append_hash_computation_lines(prefix, indent): method_lines.extend( [ indent + prefix + hash_func, - indent + " %d," % (type_hash,), + indent + f" {type_hash},", ] ) for a in attrs: - method_lines.append(indent + " self.%s," % a.name) + if a.eq_key: + cmp_name = f"_{a.name}_key" + globs[cmp_name] = a.eq_key + method_lines.append( + indent + f" {cmp_name}(self.{a.name})," + ) + else: + method_lines.append(indent + f" self.{a.name},") method_lines.append(indent + " " + closing_braces) if cache_hash: - method_lines.append(tab + "if self.%s is None:" % _hash_cache_field) + method_lines.append(tab + f"if self.{_hash_cache_field} is None:") if frozen: append_hash_computation_lines( - "object.__setattr__(self, '%s', " % _hash_cache_field, tab * 2 + f"object.__setattr__(self, '{_hash_cache_field}', ", tab * 2 ) method_lines.append(tab * 2 + ")") # close __setattr__ else: append_hash_computation_lines( - "self.%s = " % _hash_cache_field, tab * 2 + f"self.{_hash_cache_field} = ", tab * 2 ) - method_lines.append(tab + "return self.%s" % _hash_cache_field) + method_lines.append(tab + f"return self.{_hash_cache_field}") else: append_hash_computation_lines("return ", tab) script = "\n".join(method_lines) - return _make_method("__hash__", script, unique_filename) + return _make_method("__hash__", script, unique_filename, globs) def _add_hash(cls, attrs): @@ -1785,29 +1859,17 @@ def _make_eq(cls, attrs): others = [" ) == ("] for a in attrs: if a.eq_key: - cmp_name = "_%s_key" % (a.name,) + cmp_name = f"_{a.name}_key" # Add the key function to the global namespace # of the evaluated function. globs[cmp_name] = a.eq_key - lines.append( - " %s(self.%s)," - % ( - cmp_name, - a.name, - ) - ) - others.append( - " %s(other.%s)," - % ( - cmp_name, - a.name, - ) - ) + lines.append(f" {cmp_name}(self.{a.name}),") + others.append(f" {cmp_name}(other.{a.name}),") else: - lines.append(" self.%s," % (a.name,)) - others.append(" other.%s," % (a.name,)) + lines.append(f" self.{a.name},") + others.append(f" other.{a.name},") - lines += others + [" )"] + lines += [*others, " )"] else: lines.append(" return True") @@ -1885,134 +1947,61 @@ def _add_eq(cls, attrs=None): return cls -if HAS_F_STRINGS: - - def _make_repr(attrs, ns, cls): - unique_filename = _generate_unique_filename(cls, "repr") - # Figure out which attributes to include, and which function to use to - # format them. The a.repr value can be either bool or a custom - # callable. - attr_names_with_reprs = tuple( - (a.name, (repr if a.repr is True else a.repr), a.init) - for a in attrs - if a.repr is not False +def _make_repr(attrs, ns, cls): + unique_filename = _generate_unique_filename(cls, "repr") + # Figure out which attributes to include, and which function to use to + # format them. The a.repr value can be either bool or a custom + # callable. + attr_names_with_reprs = tuple( + (a.name, (repr if a.repr is True else a.repr), a.init) + for a in attrs + if a.repr is not False + ) + globs = { + name + "_repr": r for name, r, _ in attr_names_with_reprs if r != repr + } + globs["_compat"] = _compat + globs["AttributeError"] = AttributeError + globs["NOTHING"] = NOTHING + attribute_fragments = [] + for name, r, i in attr_names_with_reprs: + accessor = ( + "self." + name if i else 'getattr(self, "' + name + '", NOTHING)' ) - globs = { - name + "_repr": r - for name, r, _ in attr_names_with_reprs - if r != repr - } - globs["_compat"] = _compat - globs["AttributeError"] = AttributeError - globs["NOTHING"] = NOTHING - attribute_fragments = [] - for name, r, i in attr_names_with_reprs: - accessor = ( - "self." + name - if i - else 'getattr(self, "' + name + '", NOTHING)' - ) - fragment = ( - "%s={%s!r}" % (name, accessor) - if r == repr - else "%s={%s_repr(%s)}" % (name, name, accessor) - ) - attribute_fragments.append(fragment) - repr_fragment = ", ".join(attribute_fragments) - - if ns is None: - cls_name_fragment = ( - '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}' - ) - else: - cls_name_fragment = ns + ".{self.__class__.__name__}" - - lines = [ - "def __repr__(self):", - " try:", - " already_repring = _compat.repr_context.already_repring", - " except AttributeError:", - " already_repring = {id(self),}", - " _compat.repr_context.already_repring = already_repring", - " else:", - " if id(self) in already_repring:", - " return '...'", - " else:", - " already_repring.add(id(self))", - " try:", - " return f'%s(%s)'" % (cls_name_fragment, repr_fragment), - " finally:", - " already_repring.remove(id(self))", - ] - - return _make_method( - "__repr__", "\n".join(lines), unique_filename, globs=globs + fragment = ( + "%s={%s!r}" % (name, accessor) + if r == repr + else "%s={%s_repr(%s)}" % (name, name, accessor) ) + attribute_fragments.append(fragment) + repr_fragment = ", ".join(attribute_fragments) -else: - - def _make_repr(attrs, ns, _): - """ - Make a repr method that includes relevant *attrs*, adding *ns* to the - full name. - """ - - # Figure out which attributes to include, and which function to use to - # format them. The a.repr value can be either bool or a custom - # callable. - attr_names_with_reprs = tuple( - (a.name, repr if a.repr is True else a.repr) - for a in attrs - if a.repr is not False - ) - - def __repr__(self): - """ - Automatically created by attrs. - """ - try: - already_repring = _compat.repr_context.already_repring - except AttributeError: - already_repring = set() - _compat.repr_context.already_repring = already_repring - - if id(self) in already_repring: - return "..." - real_cls = self.__class__ - if ns is None: - qualname = getattr(real_cls, "__qualname__", None) - if qualname is not None: # pragma: no cover - # This case only happens on Python 3.5 and 3.6. We exclude - # it from coverage, because we don't want to slow down our - # test suite by running them under coverage too for this - # one line. - class_name = qualname.rsplit(">.", 1)[-1] - else: - class_name = real_cls.__name__ - else: - class_name = ns + "." + real_cls.__name__ + if ns is None: + cls_name_fragment = '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}' + else: + cls_name_fragment = ns + ".{self.__class__.__name__}" - # Since 'self' remains on the stack (i.e.: strongly referenced) - # for the duration of this call, it's safe to depend on id(...) - # stability, and not need to track the instance and therefore - # worry about properties like weakref- or hash-ability. - already_repring.add(id(self)) - try: - result = [class_name, "("] - first = True - for name, attr_repr in attr_names_with_reprs: - if first: - first = False - else: - result.append(", ") - result.extend( - (name, "=", attr_repr(getattr(self, name, NOTHING))) - ) - return "".join(result) + ")" - finally: - already_repring.remove(id(self)) + lines = [ + "def __repr__(self):", + " try:", + " already_repring = _compat.repr_context.already_repring", + " except AttributeError:", + " already_repring = {id(self),}", + " _compat.repr_context.already_repring = already_repring", + " else:", + " if id(self) in already_repring:", + " return '...'", + " else:", + " already_repring.add(id(self))", + " try:", + f" return f'{cls_name_fragment}({repr_fragment})'", + " finally:", + " already_repring.remove(id(self))", + ] - return __repr__ + return _make_method( + "__repr__", "\n".join(lines), unique_filename, globs=globs + ) def _add_repr(cls, ns=None, attrs=None): @@ -2028,7 +2017,7 @@ def _add_repr(cls, ns=None, attrs=None): def fields(cls): """ - Return the tuple of ``attrs`` attributes for a class. + Return the tuple of *attrs* attributes for a class. The tuple also allows accessing the fields by their names (see below for examples). @@ -2036,50 +2025,61 @@ def fields(cls): :param type cls: Class to introspect. :raise TypeError: If *cls* is not a class. - :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* class. :rtype: tuple (with name accessors) of `attrs.Attribute` - .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields - by name. + .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields + by name. + .. versionchanged:: 23.1.0 Add support for generic classes. """ - if not isclass(cls): - raise TypeError("Passed object must be a class.") + generic_base = get_generic_base(cls) + + if generic_base is None and not isinstance(cls, type): + msg = "Passed object must be a class." + raise TypeError(msg) + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is None: - raise NotAnAttrsClassError( - "{cls!r} is not an attrs-decorated class.".format(cls=cls) - ) + if generic_base is not None: + attrs = getattr(generic_base, "__attrs_attrs__", None) + if attrs is not None: + # Even though this is global state, stick it on here to speed + # it up. We rely on `cls` being cached for this to be + # efficient. + cls.__attrs_attrs__ = attrs + return attrs + msg = f"{cls!r} is not an attrs-decorated class." + raise NotAnAttrsClassError(msg) + return attrs def fields_dict(cls): """ - Return an ordered dictionary of ``attrs`` attributes for a class, whose + Return an ordered dictionary of *attrs* attributes for a class, whose keys are the attribute names. :param type cls: Class to introspect. :raise TypeError: If *cls* is not a class. - :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + :raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* class. - :rtype: an ordered dict where keys are attribute names and values are - `attrs.Attribute`\\ s. This will be a `dict` if it's - naturally ordered like on Python 3.6+ or an - :class:`~collections.OrderedDict` otherwise. + :rtype: dict .. versionadded:: 18.1.0 """ - if not isclass(cls): - raise TypeError("Passed object must be a class.") + if not isinstance(cls, type): + msg = "Passed object must be a class." + raise TypeError(msg) attrs = getattr(cls, "__attrs_attrs__", None) if attrs is None: - raise NotAnAttrsClassError( - "{cls!r} is not an attrs-decorated class.".format(cls=cls) - ) - return ordered_dict(((a.name, a) for a in attrs)) + msg = f"{cls!r} is not an attrs-decorated class." + raise NotAnAttrsClassError(msg) + return {a.name: a for a in attrs} def validate(inst): @@ -2088,7 +2088,7 @@ def validate(inst): Leaves all exceptions through. - :param inst: Instance of a class with ``attrs`` attributes. + :param inst: Instance of a class with *attrs* attributes. """ if _config._run_validators is False: return @@ -2114,6 +2114,7 @@ def _make_init( cls, attrs, pre_init, + pre_init_has_args, post_init, frozen, slots, @@ -2128,7 +2129,8 @@ def _make_init( ) if frozen and has_cls_on_setattr: - raise ValueError("Frozen classes can't use on_setattr.") + msg = "Frozen classes can't use on_setattr." + raise ValueError(msg) needs_cached_setattr = cache_hash or frozen filtered_attrs = [] @@ -2142,7 +2144,8 @@ def _make_init( if a.on_setattr is not None: if frozen is True: - raise ValueError("Frozen classes can't use on_setattr.") + msg = "Frozen classes can't use on_setattr." + raise ValueError(msg) needs_cached_setattr = True elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP: @@ -2155,6 +2158,7 @@ def _make_init( frozen, slots, pre_init, + pre_init_has_args, post_init, cache_hash, base_attr_map, @@ -2172,7 +2176,7 @@ def _make_init( if needs_cached_setattr: # Save the lookup overhead in __init__ if we need to circumvent # setattr hooks. - globs["_cached_setattr"] = _obj_setattr + globs["_cached_setattr_get"] = _obj_setattr.__get__ init = _make_method( "__attrs_init__" if attrs_init else "__init__", @@ -2189,7 +2193,7 @@ def _setattr(attr_name, value_var, has_on_setattr): """ Use the cached object.setattr to set *attr_name* to *value_var*. """ - return "_setattr('%s', %s)" % (attr_name, value_var) + return f"_setattr('{attr_name}', {value_var})" def _setattr_with_converter(attr_name, value_var, has_on_setattr): @@ -2212,7 +2216,7 @@ def _assign(attr_name, value, has_on_setattr): if has_on_setattr: return _setattr(attr_name, value, True) - return "self.%s = %s" % (attr_name, value) + return f"self.{attr_name} = {value}" def _assign_with_converter(attr_name, value_var, has_on_setattr): @@ -2230,68 +2234,12 @@ def _assign_with_converter(attr_name, value_var, has_on_setattr): ) -if PY2: - - def _unpack_kw_only_py2(attr_name, default=None): - """ - Unpack *attr_name* from _kw_only dict. - """ - if default is not None: - arg_default = ", %s" % default - else: - arg_default = "" - return "%s = _kw_only.pop('%s'%s)" % ( - attr_name, - attr_name, - arg_default, - ) - - def _unpack_kw_only_lines_py2(kw_only_args): - """ - Unpack all *kw_only_args* from _kw_only dict and handle errors. - - Given a list of strings "{attr_name}" and "{attr_name}={default}" - generates list of lines of code that pop attrs from _kw_only dict and - raise TypeError similar to builtin if required attr is missing or - extra key is passed. - - >>> print("\n".join(_unpack_kw_only_lines_py2(["a", "b=42"]))) - try: - a = _kw_only.pop('a') - b = _kw_only.pop('b', 42) - except KeyError as _key_error: - raise TypeError( - ... - if _kw_only: - raise TypeError( - ... - """ - lines = ["try:"] - lines.extend( - " " + _unpack_kw_only_py2(*arg.split("=")) - for arg in kw_only_args - ) - lines += """\ -except KeyError as _key_error: - raise TypeError( - '__init__() missing required keyword-only argument: %s' % _key_error - ) -if _kw_only: - raise TypeError( - '__init__() got an unexpected keyword argument %r' - % next(iter(_kw_only)) - ) -""".split( - "\n" - ) - return lines - - def _attrs_to_init_script( attrs, frozen, slots, pre_init, + pre_init_has_args, post_init, cache_hash, base_attr_map, @@ -2317,7 +2265,7 @@ def _attrs_to_init_script( # Circumvent the __setattr__ descriptor to save one lookup per # assignment. # Note _setattr will be used again below if cache_hash is True - "_setattr = _cached_setattr.__get__(self, self.__class__)" + "_setattr = _cached_setattr_get(self)" ) if frozen is True: @@ -2335,7 +2283,7 @@ def fmt_setter(attr_name, value_var, has_on_setattr): if _is_slot_attr(attr_name, base_attr_map): return _setattr(attr_name, value_var, has_on_setattr) - return "_inst_dict['%s'] = %s" % (attr_name, value_var) + return f"_inst_dict['{attr_name}'] = {value_var}" def fmt_setter_with_converter( attr_name, value_var, has_on_setattr @@ -2373,22 +2321,21 @@ def fmt_setter_with_converter( has_on_setattr = a.on_setattr is not None or ( a.on_setattr is not setters.NO_OP and has_cls_on_setattr ) - arg_name = a.name.lstrip("_") + # a.alias is set to maybe-mangled attr_name in _ClassBuilder if not + # explicitly provided + arg_name = a.alias has_factory = isinstance(a.default, Factory) - if has_factory and a.default.takes_self: - maybe_self = "self" - else: - maybe_self = "" + maybe_self = "self" if has_factory and a.default.takes_self else "" if a.init is False: if has_factory: - init_factory_name = _init_factory_pat.format(a.name) + init_factory_name = _init_factory_pat % (a.name,) if a.converter is not None: lines.append( fmt_setter_with_converter( attr_name, - init_factory_name + "(%s)" % (maybe_self,), + init_factory_name + f"({maybe_self})", has_on_setattr, ) ) @@ -2398,32 +2345,31 @@ def fmt_setter_with_converter( lines.append( fmt_setter( attr_name, - init_factory_name + "(%s)" % (maybe_self,), + init_factory_name + f"({maybe_self})", has_on_setattr, ) ) names_for_globals[init_factory_name] = a.default.factory - else: - if a.converter is not None: - lines.append( - fmt_setter_with_converter( - attr_name, - "attr_dict['%s'].default" % (attr_name,), - has_on_setattr, - ) + elif a.converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, + f"attr_dict['{attr_name}'].default", + has_on_setattr, ) - conv_name = _init_converter_pat % (a.name,) - names_for_globals[conv_name] = a.converter - else: - lines.append( - fmt_setter( - attr_name, - "attr_dict['%s'].default" % (attr_name,), - has_on_setattr, - ) + ) + conv_name = _init_converter_pat % (a.name,) + names_for_globals[conv_name] = a.converter + else: + lines.append( + fmt_setter( + attr_name, + f"attr_dict['{attr_name}'].default", + has_on_setattr, ) + ) elif a.default is not NOTHING and not has_factory: - arg = "%s=attr_dict['%s'].default" % (arg_name, attr_name) + arg = f"{arg_name}=attr_dict['{attr_name}'].default" if a.kw_only: kw_only_args.append(arg) else: @@ -2442,14 +2388,14 @@ def fmt_setter_with_converter( lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) elif has_factory: - arg = "%s=NOTHING" % (arg_name,) + arg = f"{arg_name}=NOTHING" if a.kw_only: kw_only_args.append(arg) else: args.append(arg) - lines.append("if %s is not NOTHING:" % (arg_name,)) + lines.append(f"if {arg_name} is not NOTHING:") - init_factory_name = _init_factory_pat.format(a.name) + init_factory_name = _init_factory_pat % (a.name,) if a.converter is not None: lines.append( " " @@ -2504,21 +2450,11 @@ def fmt_setter_with_converter( if a.init is True: if a.type is not None and a.converter is None: annotations[arg_name] = a.type - elif a.converter is not None and not PY2: + elif a.converter is not None: # Try to get the type from the converter. - sig = None - try: - sig = inspect.signature(a.converter) - except (ValueError, TypeError): # inspect failed - pass - if sig: - sig_params = list(sig.parameters.values()) - if ( - sig_params - and sig_params[0].annotation - is not inspect.Parameter.empty - ): - annotations[arg_name] = sig_params[0].annotation + t = _AnnotationExtractor(a.converter).get_first_param_type() + if t: + annotations[arg_name] = t if attrs_to_validate: # we can skip this if there are no validators. names_for_globals["_config"] = _config @@ -2526,23 +2462,21 @@ def fmt_setter_with_converter( for a in attrs_to_validate: val_name = "__attr_validator_" + a.name attr_name = "__attr_" + a.name - lines.append( - " %s(self, %s, self.%s)" % (val_name, attr_name, a.name) - ) + lines.append(f" {val_name}(self, {attr_name}, self.{a.name})") names_for_globals[val_name] = a.validator names_for_globals[attr_name] = a if post_init: lines.append("self.__attrs_post_init__()") - # because this is set only after __attrs_post_init is called, a crash + # because this is set only after __attrs_post_init__ is called, a crash # will result if post-init tries to access the hash code. This seemed # preferable to setting this beforehand, in which case alteration to # field values during post-init combined with post-init accessing the # hash code would result in silent bugs. if cache_hash: if frozen: - if slots: + if slots: # noqa: SIM108 # if frozen and slots, then _setattr defined above init_hash_cache = "_setattr('%s', %s)" else: @@ -2555,44 +2489,67 @@ def fmt_setter_with_converter( # For exceptions we rely on BaseException.__init__ for proper # initialization. if is_exc: - vals = ",".join("self." + a.name for a in attrs if a.init) + vals = ",".join(f"self.{a.name}" for a in attrs if a.init) - lines.append("BaseException.__init__(self, %s)" % (vals,)) + lines.append(f"BaseException.__init__(self, {vals})") args = ", ".join(args) + pre_init_args = args if kw_only_args: - if PY2: - lines = _unpack_kw_only_lines_py2(kw_only_args) + lines + args += "%s*, %s" % ( + ", " if args else "", # leading comma + ", ".join(kw_only_args), # kw_only args + ) + pre_init_kw_only_args = ", ".join( + ["%s=%s" % (kw_arg, kw_arg) for kw_arg in kw_only_args] + ) + pre_init_args += ( + ", " if pre_init_args else "" + ) # handle only kwargs and no regular args + pre_init_args += pre_init_kw_only_args + + if pre_init and pre_init_has_args: + # If pre init method has arguments, pass same arguments as `__init__` + lines[0] = "self.__attrs_pre_init__(%s)" % pre_init_args - args += "%s**_kw_only" % (", " if args else "",) # leading comma - else: - args += "%s*, %s" % ( - ", " if args else "", # leading comma - ", ".join(kw_only_args), # kw_only args - ) return ( - """\ -def {init_name}(self, {args}): - {lines} -""".format( - init_name=("__attrs_init__" if attrs_init else "__init__"), - args=args, - lines="\n ".join(lines) if lines else "pass", + "def %s(self, %s):\n %s\n" + % ( + ("__attrs_init__" if attrs_init else "__init__"), + args, + "\n ".join(lines) if lines else "pass", ), names_for_globals, annotations, ) -class Attribute(object): +def _default_init_alias_for(name: str) -> str: + """ + The default __init__ parameter name for a field. + + This performs private-name adjustment via leading-unscore stripping, + and is the default value of Attribute.alias if not provided. + """ + + return name.lstrip("_") + + +class Attribute: """ *Read-only* representation of an attribute. + .. warning:: + + You should never instantiate this class yourself. + The class has *all* arguments of `attr.ib` (except for ``factory`` which is only syntactic sugar for ``default=Factory(...)`` plus the following: - ``name`` (`str`): The name of the attribute. + - ``alias`` (`str`): The __init__ parameter name of the attribute, after + any explicit overrides and default private-attribute-name handling. - ``inherited`` (`bool`): Whether or not that attribute has been inherited from a base class. - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The callables @@ -2608,12 +2565,16 @@ class Attribute(object): - Validators get them passed as the first argument. - The :ref:`field transformer ` hook receives a list of them. + - The ``alias`` property exposes the __init__ parameter name of the field, + with any overrides and default private-attribute handling applied. + .. versionadded:: 20.1.0 *inherited* .. versionadded:: 20.1.0 *on_setattr* .. versionchanged:: 20.2.0 *inherited* is not taken into account for equality checks and hashing anymore. .. versionadded:: 21.1.0 *eq_key* and *order_key* + .. versionadded:: 22.2.0 *alias* For the full version history of the fields, see `attr.ib`. """ @@ -2635,6 +2596,7 @@ class Attribute(object): "kw_only", "inherited", "on_setattr", + "alias", ) def __init__( @@ -2656,13 +2618,14 @@ def __init__( order=None, order_key=None, on_setattr=None, + alias=None, ): eq, eq_key, order, order_key = _determine_attrib_eq_order( cmp, eq_key or eq, order_key or order, True ) # Cache this descriptor here to speed things up later. - bound_setattr = _obj_setattr.__get__(self, Attribute) + bound_setattr = _obj_setattr.__get__(self) # Despite the big red warning, people *do* instantiate `Attribute` # themselves. @@ -2680,7 +2643,7 @@ def __init__( bound_setattr( "metadata", ( - metadata_proxy(metadata) + types.MappingProxyType(dict(metadata)) # Shallow copy if metadata else _empty_metadata_singleton ), @@ -2689,6 +2652,7 @@ def __init__( bound_setattr("kw_only", kw_only) bound_setattr("inherited", inherited) bound_setattr("on_setattr", on_setattr) + bound_setattr("alias", alias) def __setattr__(self, name, value): raise FrozenInstanceError() @@ -2699,9 +2663,8 @@ def from_counting_attr(cls, name, ca, type=None): if type is None: type = ca.type elif ca.type is not None: - raise ValueError( - "Type annotation and type argument cannot both be present" - ) + msg = "Type annotation and type argument cannot both be present" + raise ValueError(msg) inst_dict = { k: getattr(ca, k) for k in Attribute.__slots__ @@ -2721,25 +2684,16 @@ def from_counting_attr(cls, name, ca, type=None): type=type, cmp=None, inherited=False, - **inst_dict + **inst_dict, ) - @property - def cmp(self): - """ - Simulate the presence of a cmp attribute and warn. - """ - warnings.warn(_CMP_DEPRECATION, DeprecationWarning, stacklevel=2) - - return self.eq and self.order - - # Don't use attr.evolve since fields(Attribute) doesn't work + # Don't use attrs.evolve since fields(Attribute) doesn't work def evolve(self, **changes): """ Copy *self* and apply *changes*. - This works similarly to `attr.evolve` but that function does not work - with ``Attribute``. + This works similarly to `attrs.evolve` but that function does not work + with `Attribute`. It is mainly meant to be used for `transform-fields`. @@ -2768,14 +2722,14 @@ def __setstate__(self, state): self._setattrs(zip(self.__slots__, state)) def _setattrs(self, name_values_pairs): - bound_setattr = _obj_setattr.__get__(self, Attribute) + bound_setattr = _obj_setattr.__get__(self) for name, value in name_values_pairs: if name != "metadata": bound_setattr(name, value) else: bound_setattr( name, - metadata_proxy(value) + types.MappingProxyType(dict(value)) if value else _empty_metadata_singleton, ) @@ -2793,6 +2747,7 @@ def _setattrs(self, name_values_pairs): hash=(name != "metadata"), init=True, inherited=False, + alias=_default_init_alias_for(name), ) for name in Attribute.__slots__ ] @@ -2806,7 +2761,7 @@ def _setattrs(self, name_values_pairs): ) -class _CountingAttr(object): +class _CountingAttr: """ Intermediate representation of attributes that uses a counter to preserve the order in which the attributes have been defined. @@ -2831,37 +2786,42 @@ class _CountingAttr(object): "type", "kw_only", "on_setattr", + "alias", ) - __attrs_attrs__ = tuple( - Attribute( - name=name, - default=NOTHING, - validator=None, - repr=True, - cmp=None, - hash=True, - init=True, - kw_only=False, - eq=True, - eq_key=None, - order=False, - order_key=None, - inherited=False, - on_setattr=None, - ) - for name in ( - "counter", - "_default", - "repr", - "eq", - "order", - "hash", - "init", - "on_setattr", - ) - ) + ( + __attrs_attrs__ = ( + *tuple( + Attribute( + name=name, + alias=_default_init_alias_for(name), + default=NOTHING, + validator=None, + repr=True, + cmp=None, + hash=True, + init=True, + kw_only=False, + eq=True, + eq_key=None, + order=False, + order_key=None, + inherited=False, + on_setattr=None, + ) + for name in ( + "counter", + "_default", + "repr", + "eq", + "order", + "hash", + "init", + "on_setattr", + "alias", + ) + ), Attribute( name="metadata", + alias="metadata", default=None, validator=None, repr=True, @@ -2896,6 +2856,7 @@ def __init__( order, order_key, on_setattr, + alias, ): _CountingAttr.cls_counter += 1 self.counter = _CountingAttr.cls_counter @@ -2913,6 +2874,7 @@ def __init__( self.type = type self.kw_only = kw_only self.on_setattr = on_setattr + self.alias = alias def validator(self, meth): """ @@ -2949,7 +2911,7 @@ def default(self, meth): _CountingAttr = _add_eq(_add_repr(_CountingAttr)) -class Factory(object): +class Factory: """ Stores a factory callable. @@ -2967,10 +2929,6 @@ class Factory(object): __slots__ = ("factory", "takes_self") def __init__(self, factory, takes_self=False): - """ - `Factory` is part of the default machinery so if we want a default - value here, we have to implement it ourselves. - """ self.factory = factory self.takes_self = takes_self @@ -3007,23 +2965,26 @@ def __setstate__(self, state): Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f) -def make_class(name, attrs, bases=(object,), **attributes_arguments): - """ +def make_class( + name, attrs, bases=(object,), class_body=None, **attributes_arguments +): + r""" A quick way to create a new class called *name* with *attrs*. :param str name: The name for the new class. :param attrs: A list of names or a dictionary of mappings of names to - attributes. + `attr.ib`\ s / `attrs.field`\ s. - If *attrs* is a list or an ordered dict (`dict` on Python 3.6+, - `collections.OrderedDict` otherwise), the order is deduced from - the order of the names or attributes inside *attrs*. Otherwise the - order of the definition of the attributes is used. + The order is deduced from the order of the names or attributes inside + *attrs*. Otherwise the order of the definition of the attributes is + used. :type attrs: `list` or `dict` :param tuple bases: Classes that the new class will subclass. + :param dict class_body: An optional dictionary of class attributes for the new class. + :param attributes_arguments: Passed unmodified to `attr.s`. :return: A new class with *attrs*. @@ -3031,19 +2992,23 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): .. versionadded:: 17.1.0 *bases* .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained. + .. versionchanged:: 23.2.0 *class_body* """ if isinstance(attrs, dict): cls_dict = attrs elif isinstance(attrs, (list, tuple)): - cls_dict = dict((a, attrib()) for a in attrs) + cls_dict = {a: attrib() for a in attrs} else: - raise TypeError("attrs argument must be a dict or a list.") + msg = "attrs argument must be a dict or a list." + raise TypeError(msg) pre_init = cls_dict.pop("__attrs_pre_init__", None) post_init = cls_dict.pop("__attrs_post_init__", None) user_init = cls_dict.pop("__init__", None) body = {} + if class_body is not None: + body.update(class_body) if pre_init is not None: body["__attrs_pre_init__"] = pre_init if post_init is not None: @@ -3051,18 +3016,16 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): if user_init is not None: body["__init__"] = user_init - type_ = new_class(name, bases, {}, lambda ns: ns.update(body)) + type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body)) # For pickling to work, the __module__ variable needs to be set to the # frame where the class is created. Bypass this step in environments where # sys._getframe is not defined (Jython for example) or sys._getframe is not # defined for arguments greater than 0 (IronPython). - try: + with contextlib.suppress(AttributeError, ValueError): type_.__module__ = sys._getframe(1).f_globals.get( "__name__", "__main__" ) - except (AttributeError, ValueError): - pass # We do it here for proper warnings with meaningful stacklevel. cmp = attributes_arguments.pop("cmp", None) @@ -3084,7 +3047,7 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): @attrs(slots=True, hash=True) -class _AndValidator(object): +class _AndValidator: """ Compose many validators to a single one. """ @@ -3138,36 +3101,19 @@ def pipe_converter(val): return val - if not PY2: - if not converters: - # If the converter list is empty, pipe_converter is the identity. - A = typing.TypeVar("A") - pipe_converter.__annotations__ = {"val": A, "return": A} - else: - # Get parameter type. - sig = None - try: - sig = inspect.signature(converters[0]) - except (ValueError, TypeError): # inspect failed - pass - if sig: - params = list(sig.parameters.values()) - if ( - params - and params[0].annotation is not inspect.Parameter.empty - ): - pipe_converter.__annotations__["val"] = params[ - 0 - ].annotation - # Get return type. - sig = None - try: - sig = inspect.signature(converters[-1]) - except (ValueError, TypeError): # inspect failed - pass - if sig and sig.return_annotation is not inspect.Signature().empty: - pipe_converter.__annotations__[ - "return" - ] = sig.return_annotation + if not converters: + # If the converter list is empty, pipe_converter is the identity. + A = typing.TypeVar("A") + pipe_converter.__annotations__ = {"val": A, "return": A} + else: + # Get parameter type from first converter. + t = _AnnotationExtractor(converters[0]).get_first_param_type() + if t: + pipe_converter.__annotations__["val"] = t + + # Get return type from last converter. + rt = _AnnotationExtractor(converters[-1]).get_return_type() + if rt: + pipe_converter.__annotations__["return"] = rt return pipe_converter diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_next_gen.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_next_gen.py index 068253688caf8..1fb9f259b53b8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_next_gen.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_next_gen.py @@ -1,8 +1,8 @@ # SPDX-License-Identifier: MIT """ -These are Python 3.6+-only and keyword-only APIs that call `attr.s` and -`attr.ib` with different default values. +These are keyword-only APIs that call `attr.s` and `attr.ib` with different +default values. """ @@ -26,6 +26,7 @@ def define( *, these=None, repr=None, + unsafe_hash=None, hash=None, init=None, slots=True, @@ -45,20 +46,24 @@ def define( match_args=True, ): r""" - Define an ``attrs`` class. + Define an *attrs* class. Differences to the classic `attr.s` that it uses underneath: - - Automatically detect whether or not *auto_attribs* should be `True` - (c.f. *auto_attribs* parameter). - - If *frozen* is `False`, run converters and validators when setting an - attribute by default. - - *slots=True* (see :term:`slotted classes` for potentially surprising - behaviors) + - Automatically detect whether or not *auto_attribs* should be `True` (c.f. + *auto_attribs* parameter). + - Converters and validators run when attributes are set by default -- if + *frozen* is `False`. + - *slots=True* + + .. caution:: + + Usually this has only upsides and few visible effects in everyday + programming. But it *can* lead to some surprising behaviors, so please + make sure to read :term:`slotted classes`. - *auto_exc=True* - *auto_detect=True* - *order=False* - - *match_args=True* - Some options that were only relevant on Python 2 or were kept around for backwards-compatibility have been removed. @@ -77,6 +82,8 @@ def define( .. versionadded:: 20.1.0 .. versionchanged:: 21.3.0 Converters are also run ``on_setattr``. + .. versionadded:: 22.2.0 + *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance). """ def do_it(cls, auto_attribs): @@ -85,6 +92,7 @@ def do_it(cls, auto_attribs): these=these, repr=repr, hash=hash, + unsafe_hash=unsafe_hash, init=init, slots=slots, frozen=frozen, @@ -123,10 +131,8 @@ def wrap(cls): for base_cls in cls.__bases__: if base_cls.__setattr__ is _frozen_setattrs: if had_on_setattr: - raise ValueError( - "Frozen classes can't use on_setattr " - "(frozen-ness was inherited)." - ) + msg = "Frozen classes can't use on_setattr (frozen-ness was inherited)." + raise ValueError(msg) on_setattr = setters.NO_OP break @@ -143,8 +149,8 @@ def wrap(cls): # if it's used as `@attrs` but ``None`` if used as `@attrs()`. if maybe_cls is None: return wrap - else: - return wrap(maybe_cls) + + return wrap(maybe_cls) mutable = define @@ -159,17 +165,22 @@ def field( hash=None, init=True, metadata=None, + type=None, converter=None, factory=None, kw_only=False, eq=None, order=None, on_setattr=None, + alias=None, ): """ Identical to `attr.ib`, except keyword-only and with some arguments removed. + .. versionadded:: 23.1.0 + The *type* parameter has been re-added; mostly for `attrs.make_class`. + Please note that type checkers ignore this metadata. .. versionadded:: 20.1.0 """ return attrib( @@ -179,12 +190,14 @@ def field( hash=hash, init=init, metadata=metadata, + type=type, converter=converter, factory=factory, kw_only=kw_only, eq=eq, order=order, on_setattr=on_setattr, + alias=alias, ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_typing_compat.pyi b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_typing_compat.pyi new file mode 100644 index 0000000000000..ca7b71e906a28 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_typing_compat.pyi @@ -0,0 +1,15 @@ +from typing import Any, ClassVar, Protocol + +# MYPY is a special constant in mypy which works the same way as `TYPE_CHECKING`. +MYPY = False + +if MYPY: + # A protocol to be able to statically accept an attrs class. + class AttrsInstance_(Protocol): + __attrs_attrs__: ClassVar[Any] + +else: + # For type checkers without plug-in support use an empty protocol that + # will (hopefully) be combined into a union. + class AttrsInstance_(Protocol): + pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_version_info.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_version_info.py index cdaeec37a1ad9..51a1312f9759f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_version_info.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_version_info.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function from functools import total_ordering @@ -10,7 +9,7 @@ @total_ordering @attrs(eq=False, order=False, slots=True, frozen=True) -class VersionInfo(object): +class VersionInfo: """ A version object that can be compared to tuple of length 1--4: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/converters.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/converters.py index 1fb6c05d7bb88..2bf4c902a66fa 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/converters.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/converters.py @@ -4,15 +4,11 @@ Commonly useful converters. """ -from __future__ import absolute_import, division, print_function - -from ._compat import PY2 -from ._make import NOTHING, Factory, pipe +import typing -if not PY2: - import inspect - import typing +from ._compat import _AnnotationExtractor +from ._make import NOTHING, Factory, pipe __all__ = [ @@ -42,22 +38,15 @@ def optional_converter(val): return None return converter(val) - if not PY2: - sig = None - try: - sig = inspect.signature(converter) - except (ValueError, TypeError): # inspect failed - pass - if sig: - params = list(sig.parameters.values()) - if params and params[0].annotation is not inspect.Parameter.empty: - optional_converter.__annotations__["val"] = typing.Optional[ - params[0].annotation - ] - if sig.return_annotation is not inspect.Signature.empty: - optional_converter.__annotations__["return"] = typing.Optional[ - sig.return_annotation - ] + xtr = _AnnotationExtractor(converter) + + t = xtr.get_first_param_type() + if t: + optional_converter.__annotations__["val"] = typing.Optional[t] + + rt = xtr.get_return_type() + if rt: + optional_converter.__annotations__["return"] = typing.Optional[rt] return optional_converter @@ -81,21 +70,20 @@ def default_if_none(default=NOTHING, factory=None): .. versionadded:: 18.2.0 """ if default is NOTHING and factory is None: - raise TypeError("Must pass either `default` or `factory`.") + msg = "Must pass either `default` or `factory`." + raise TypeError(msg) if default is not NOTHING and factory is not None: - raise TypeError( - "Must pass either `default` or `factory` but not both." - ) + msg = "Must pass either `default` or `factory` but not both." + raise TypeError(msg) if factory is not None: default = Factory(factory) if isinstance(default, Factory): if default.takes_self: - raise ValueError( - "`takes_self` is not supported by default_if_none." - ) + msg = "`takes_self` is not supported by default_if_none." + raise ValueError(msg) def default_if_none_converter(val): if val is not None: @@ -152,4 +140,5 @@ def to_bool(val): except TypeError: # Raised when "val" is not hashable (e.g., lists) pass - raise ValueError("Cannot convert value to bool: {}".format(val)) + msg = f"Cannot convert value to bool: {val}" + raise ValueError(msg) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/converters.pyi b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/converters.pyi index 0f58088a37be3..5abb49f6d5a8c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/converters.pyi +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/converters.pyi @@ -1,4 +1,4 @@ -from typing import Callable, Optional, TypeVar, overload +from typing import Callable, TypeVar, overload from . import _ConverterType diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/exceptions.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/exceptions.py index b2f1edc32a941..3b7abb8154108 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/exceptions.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/exceptions.py @@ -1,6 +1,8 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function +from __future__ import annotations + +from typing import ClassVar class FrozenError(AttributeError): @@ -15,7 +17,7 @@ class FrozenError(AttributeError): """ msg = "can't set attribute" - args = [msg] + args: ClassVar[tuple[str]] = [msg] class FrozenInstanceError(FrozenError): @@ -36,7 +38,7 @@ class FrozenAttributeError(FrozenError): class AttrsAttributeNotFoundError(ValueError): """ - An ``attrs`` function couldn't find an attribute that the user asked for. + An *attrs* function couldn't find an attribute that the user asked for. .. versionadded:: 16.2.0 """ @@ -44,7 +46,7 @@ class AttrsAttributeNotFoundError(ValueError): class NotAnAttrsClassError(ValueError): """ - A non-``attrs`` class has been passed into an ``attrs`` function. + A non-*attrs* class has been passed into an *attrs* function. .. versionadded:: 16.2.0 """ @@ -52,7 +54,7 @@ class NotAnAttrsClassError(ValueError): class DefaultAlreadySetError(RuntimeError): """ - A default has been set using ``attr.ib()`` and is attempted to be reset + A default has been set when defining the field and is attempted to be reset using the decorator. .. versionadded:: 17.1.0 @@ -61,8 +63,7 @@ class DefaultAlreadySetError(RuntimeError): class UnannotatedAttributeError(RuntimeError): """ - A class with ``auto_attribs=True`` has an ``attr.ib()`` without a type - annotation. + A class with ``auto_attribs=True`` has a field without a type annotation. .. versionadded:: 17.3.0 """ @@ -70,7 +71,7 @@ class UnannotatedAttributeError(RuntimeError): class PythonTooOldError(RuntimeError): """ - It was attempted to use an ``attrs`` feature that requires a newer Python + It was attempted to use an *attrs* feature that requires a newer Python version. .. versionadded:: 18.2.0 @@ -79,8 +80,8 @@ class PythonTooOldError(RuntimeError): class NotCallableError(TypeError): """ - A ``attr.ib()`` requiring a callable has been set with a value - that is not callable. + A field requiring a callable has been set with a value that is not + callable. .. versionadded:: 19.2.0 """ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/filters.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/filters.py index a1978a87755ba..a1e40c98db853 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/filters.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/filters.py @@ -4,9 +4,6 @@ Commonly useful filters for `attr.asdict`. """ -from __future__ import absolute_import, division, print_function - -from ._compat import isclass from ._make import Attribute @@ -15,7 +12,8 @@ def _split_what(what): Returns a tuple of `frozenset`s of classes and attributes. """ return ( - frozenset(cls for cls in what if isclass(cls)), + frozenset(cls for cls in what if isinstance(cls, type)), + frozenset(cls for cls in what if isinstance(cls, str)), frozenset(cls for cls in what if isinstance(cls, Attribute)), ) @@ -25,14 +23,21 @@ def include(*what): Include *what*. :param what: What to include. - :type what: `list` of `type` or `attrs.Attribute`\\ s + :type what: `list` of classes `type`, field names `str` or + `attrs.Attribute`\\ s :rtype: `callable` + + .. versionchanged:: 23.1.0 Accept strings with field names. """ - cls, attrs = _split_what(what) + cls, names, attrs = _split_what(what) def include_(attribute, value): - return value.__class__ in cls or attribute in attrs + return ( + value.__class__ in cls + or attribute.name in names + or attribute in attrs + ) return include_ @@ -42,13 +47,20 @@ def exclude(*what): Exclude *what*. :param what: What to exclude. - :type what: `list` of classes or `attrs.Attribute`\\ s. + :type what: `list` of classes `type`, field names `str` or + `attrs.Attribute`\\ s. :rtype: `callable` + + .. versionchanged:: 23.3.0 Accept field name string as input argument """ - cls, attrs = _split_what(what) + cls, names, attrs = _split_what(what) def exclude_(attribute, value): - return value.__class__ not in cls and attribute not in attrs + return not ( + value.__class__ in cls + or attribute.name in names + or attribute in attrs + ) return exclude_ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/filters.pyi b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/filters.pyi index 993866865eab7..8a02fa0fc0631 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/filters.pyi +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/filters.pyi @@ -2,5 +2,5 @@ from typing import Any, Union from . import Attribute, _FilterType -def include(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ... -def exclude(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ... +def include(*what: Union[type, str, Attribute[Any]]) -> _FilterType[Any]: ... +def exclude(*what: Union[type, str, Attribute[Any]]) -> _FilterType[Any]: ... diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/setters.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/setters.py index b1cbb5d83e744..12ed6750df35b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/setters.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/setters.py @@ -4,7 +4,6 @@ Commonly used hooks for on_setattr. """ -from __future__ import absolute_import, division, print_function from . import _config from .exceptions import FrozenAttributeError @@ -69,11 +68,6 @@ def convert(instance, attrib, new_value): return new_value +# Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. +# autodata stopped working, so the docstring is inlined in the API docs. NO_OP = object() -""" -Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. - -Does not work in `pipe` or within lists. - -.. versionadded:: 20.1.0 -""" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/setters.pyi b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/setters.pyi index 3f5603c2b0cb7..72f7ce4761c34 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/setters.pyi +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/setters.pyi @@ -1,4 +1,4 @@ -from typing import Any, NewType, NoReturn, TypeVar, cast +from typing import Any, NewType, NoReturn, TypeVar from . import Attribute, _OnSetAttrType diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/validators.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/validators.py index 0b0c8342f2528..34d6b761d3785 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/validators.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/validators.py @@ -4,24 +4,19 @@ Commonly useful validators. """ -from __future__ import absolute_import, division, print_function import operator import re from contextlib import contextmanager +from re import Pattern from ._config import get_run_validators, set_run_validators from ._make import _AndValidator, and_, attrib, attrs +from .converters import default_if_none from .exceptions import NotCallableError -try: - Pattern = re.Pattern -except AttributeError: # Python <3.7 lacks a Pattern type. - Pattern = type(re.compile("")) - - __all__ = [ "and_", "deep_iterable", @@ -37,6 +32,8 @@ "lt", "matches_re", "max_len", + "min_len", + "not_", "optional", "provides", "set_disabled", @@ -92,7 +89,7 @@ def disabled(): @attrs(repr=False, slots=True, hash=True) -class _InstanceOfValidator(object): +class _InstanceOfValidator: type = attrib() def __call__(self, inst, attr, value): @@ -100,23 +97,21 @@ def __call__(self, inst, attr, value): We use a callable class to be able to change the ``__repr__``. """ if not isinstance(value, self.type): + msg = "'{name}' must be {type!r} (got {value!r} that is a {actual!r}).".format( + name=attr.name, + type=self.type, + actual=value.__class__, + value=value, + ) raise TypeError( - "'{name}' must be {type!r} (got {value!r} that is a " - "{actual!r}).".format( - name=attr.name, - type=self.type, - actual=value.__class__, - value=value, - ), + msg, attr, self.type, value, ) def __repr__(self): - return "".format( - type=self.type - ) + return f"" def instance_of(type): @@ -126,7 +121,7 @@ def instance_of(type): `isinstance` therefore it's also valid to pass a tuple of types). :param type: The type to check for. - :type type: type or tuple of types + :type type: type or tuple of type :raises TypeError: With a human readable error message, the attribute (of type `attrs.Attribute`), the expected type, and the value it @@ -136,7 +131,7 @@ def instance_of(type): @attrs(repr=False, frozen=True, slots=True) -class _MatchesReValidator(object): +class _MatchesReValidator: pattern = attrib() match_func = attrib() @@ -145,20 +140,18 @@ def __call__(self, inst, attr, value): We use a callable class to be able to change the ``__repr__``. """ if not self.match_func(value): + msg = "'{name}' must match regex {pattern!r} ({value!r} doesn't)".format( + name=attr.name, pattern=self.pattern.pattern, value=value + ) raise ValueError( - "'{name}' must match regex {pattern!r}" - " ({value!r} doesn't)".format( - name=attr.name, pattern=self.pattern.pattern, value=value - ), + msg, attr, self.pattern, value, ) def __repr__(self): - return "".format( - pattern=self.pattern - ) + return f"" def matches_re(regex, flags=0, func=None): @@ -169,34 +162,27 @@ def matches_re(regex, flags=0, func=None): :param regex: a regex string or precompiled pattern to match against :param int flags: flags that will be passed to the underlying re function (default 0) - :param callable func: which underlying `re` function to call (options - are `re.fullmatch`, `re.search`, `re.match`, default - is ``None`` which means either `re.fullmatch` or an emulation of - it on Python 2). For performance reasons, they won't be used directly - but on a pre-`re.compile`\ ed pattern. + :param callable func: which underlying `re` function to call. Valid options + are `re.fullmatch`, `re.search`, and `re.match`; the default ``None`` + means `re.fullmatch`. For performance reasons, the pattern is always + precompiled using `re.compile`. .. versionadded:: 19.2.0 .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern. """ - fullmatch = getattr(re, "fullmatch", None) - valid_funcs = (fullmatch, None, re.search, re.match) + valid_funcs = (re.fullmatch, None, re.search, re.match) if func not in valid_funcs: - raise ValueError( - "'func' must be one of {}.".format( - ", ".join( - sorted( - e and e.__name__ or "None" for e in set(valid_funcs) - ) - ) + msg = "'func' must be one of {}.".format( + ", ".join( + sorted(e and e.__name__ or "None" for e in set(valid_funcs)) ) ) + raise ValueError(msg) if isinstance(regex, Pattern): if flags: - raise TypeError( - "'flags' can only be used with a string pattern; " - "pass flags to re.compile() instead" - ) + msg = "'flags' can only be used with a string pattern; pass flags to re.compile() instead" + raise TypeError(msg) pattern = regex else: pattern = re.compile(regex, flags) @@ -205,19 +191,14 @@ def matches_re(regex, flags=0, func=None): match_func = pattern.match elif func is re.search: match_func = pattern.search - elif fullmatch: + else: match_func = pattern.fullmatch - else: # Python 2 fullmatch emulation (https://bugs.python.org/issue16203) - pattern = re.compile( - r"(?:{})\Z".format(pattern.pattern), pattern.flags - ) - match_func = pattern.match return _MatchesReValidator(pattern, match_func) @attrs(repr=False, slots=True, hash=True) -class _ProvidesValidator(object): +class _ProvidesValidator: interface = attrib() def __call__(self, inst, attr, value): @@ -225,20 +206,18 @@ def __call__(self, inst, attr, value): We use a callable class to be able to change the ``__repr__``. """ if not self.interface.providedBy(value): + msg = "'{name}' must provide {interface!r} which {value!r} doesn't.".format( + name=attr.name, interface=self.interface, value=value + ) raise TypeError( - "'{name}' must provide {interface!r} which {value!r} " - "doesn't.".format( - name=attr.name, interface=self.interface, value=value - ), + msg, attr, self.interface, value, ) def __repr__(self): - return "".format( - interface=self.interface - ) + return f"" def provides(interface): @@ -254,12 +233,22 @@ def provides(interface): :raises TypeError: With a human readable error message, the attribute (of type `attrs.Attribute`), the expected interface, and the value it got. + + .. deprecated:: 23.1.0 """ + import warnings + + warnings.warn( + "attrs's zope-interface support is deprecated and will be removed in, " + "or after, April 2024.", + DeprecationWarning, + stacklevel=2, + ) return _ProvidesValidator(interface) @attrs(repr=False, slots=True, hash=True) -class _OptionalValidator(object): +class _OptionalValidator: validator = attrib() def __call__(self, inst, attr, value): @@ -269,9 +258,7 @@ def __call__(self, inst, attr, value): self.validator(inst, attr, value) def __repr__(self): - return "".format( - what=repr(self.validator) - ) + return f"" def optional(validator): @@ -280,20 +267,21 @@ def optional(validator): which can be set to ``None`` in addition to satisfying the requirements of the sub-validator. - :param validator: A validator (or a list of validators) that is used for - non-``None`` values. - :type validator: callable or `list` of callables. + :param Callable | tuple[Callable] | list[Callable] validator: A validator + (or validators) that is used for non-``None`` values. .. versionadded:: 15.1.0 .. versionchanged:: 17.1.0 *validator* can be a list of validators. + .. versionchanged:: 23.1.0 *validator* can also be a tuple of validators. """ - if isinstance(validator, list): + if isinstance(validator, (list, tuple)): return _OptionalValidator(_AndValidator(validator)) + return _OptionalValidator(validator) @attrs(repr=False, slots=True, hash=True) -class _InValidator(object): +class _InValidator: options = attrib() def __call__(self, inst, attr, value): @@ -303,16 +291,16 @@ def __call__(self, inst, attr, value): in_options = False if not in_options: + msg = f"'{attr.name}' must be in {self.options!r} (got {value!r})" raise ValueError( - "'{name}' must be in {options!r} (got {value!r})".format( - name=attr.name, options=self.options, value=value - ) + msg, + attr, + self.options, + value, ) def __repr__(self): - return "".format( - options=self.options - ) + return f"" def in_(options): @@ -329,12 +317,16 @@ def in_(options): got. .. versionadded:: 17.1.0 + .. versionchanged:: 22.1.0 + The ValueError was incomplete until now and only contained the human + readable error message. Now it contains all the information that has + been promised since 17.1.0. """ return _InValidator(options) @attrs(repr=False, slots=False, hash=True) -class _IsCallableValidator(object): +class _IsCallableValidator: def __call__(self, inst, attr, value): """ We use a callable class to be able to change the ``__repr__``. @@ -357,13 +349,13 @@ def __repr__(self): def is_callable(): """ - A validator that raises a `attr.exceptions.NotCallableError` if the + A validator that raises a `attrs.exceptions.NotCallableError` if the initializer is called with a value for this particular attribute that is not callable. .. versionadded:: 19.1.0 - :raises `attr.exceptions.NotCallableError`: With a human readable error + :raises attrs.exceptions.NotCallableError: With a human readable error message containing the attribute (`attrs.Attribute`) name, and the value it got. """ @@ -371,7 +363,7 @@ def is_callable(): @attrs(repr=False, slots=True, hash=True) -class _DeepIterable(object): +class _DeepIterable: member_validator = attrib(validator=is_callable()) iterable_validator = attrib( default=None, validator=optional(is_callable()) @@ -391,14 +383,11 @@ def __repr__(self): iterable_identifier = ( "" if self.iterable_validator is None - else " {iterable!r}".format(iterable=self.iterable_validator) + else f" {self.iterable_validator!r}" ) return ( - "" - ).format( - iterable_identifier=iterable_identifier, - member=self.member_validator, + f"" ) @@ -406,7 +395,7 @@ def deep_iterable(member_validator, iterable_validator=None): """ A validator that performs deep validation of an iterable. - :param member_validator: Validator to apply to iterable members + :param member_validator: Validator(s) to apply to iterable members :param iterable_validator: Validator to apply to iterable itself (optional) @@ -414,11 +403,13 @@ def deep_iterable(member_validator, iterable_validator=None): :raises TypeError: if any sub-validators fail """ + if isinstance(member_validator, (list, tuple)): + member_validator = and_(*member_validator) return _DeepIterable(member_validator, iterable_validator) @attrs(repr=False, slots=True, hash=True) -class _DeepMapping(object): +class _DeepMapping: key_validator = attrib(validator=is_callable()) value_validator = attrib(validator=is_callable()) mapping_validator = attrib(default=None, validator=optional(is_callable())) @@ -457,7 +448,7 @@ def deep_mapping(key_validator, value_validator, mapping_validator=None): @attrs(repr=False, frozen=True, slots=True) -class _NumberValidator(object): +class _NumberValidator: bound = attrib() compare_op = attrib() compare_func = attrib() @@ -467,19 +458,11 @@ def __call__(self, inst, attr, value): We use a callable class to be able to change the ``__repr__``. """ if not self.compare_func(value, self.bound): - raise ValueError( - "'{name}' must be {op} {bound}: {value}".format( - name=attr.name, - op=self.compare_op, - bound=self.bound, - value=value, - ) - ) + msg = f"'{attr.name}' must be {self.compare_op} {self.bound}: {value}" + raise ValueError(msg) def __repr__(self): - return "".format( - op=self.compare_op, bound=self.bound - ) + return f"" def lt(val): @@ -531,7 +514,7 @@ def gt(val): @attrs(repr=False, frozen=True, slots=True) -class _MaxLengthValidator(object): +class _MaxLengthValidator: max_length = attrib() def __call__(self, inst, attr, value): @@ -539,14 +522,11 @@ def __call__(self, inst, attr, value): We use a callable class to be able to change the ``__repr__``. """ if len(value) > self.max_length: - raise ValueError( - "Length of '{name}' must be <= {max}: {len}".format( - name=attr.name, max=self.max_length, len=len(value) - ) - ) + msg = f"Length of '{attr.name}' must be <= {self.max_length}: {len(value)}" + raise ValueError(msg) def __repr__(self): - return "".format(max=self.max_length) + return f"" def max_len(length): @@ -559,3 +539,143 @@ def max_len(length): .. versionadded:: 21.3.0 """ return _MaxLengthValidator(length) + + +@attrs(repr=False, frozen=True, slots=True) +class _MinLengthValidator: + min_length = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if len(value) < self.min_length: + msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}" + raise ValueError(msg) + + def __repr__(self): + return f"" + + +def min_len(length): + """ + A validator that raises `ValueError` if the initializer is called + with a string or iterable that is shorter than *length*. + + :param int length: Minimum length of the string or iterable + + .. versionadded:: 22.1.0 + """ + return _MinLengthValidator(length) + + +@attrs(repr=False, slots=True, hash=True) +class _SubclassOfValidator: + type = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not issubclass(value, self.type): + msg = f"'{attr.name}' must be a subclass of {self.type!r} (got {value!r})." + raise TypeError( + msg, + attr, + self.type, + value, + ) + + def __repr__(self): + return f"" + + +def _subclass_of(type): + """ + A validator that raises a `TypeError` if the initializer is called + with a wrong type for this particular attribute (checks are performed using + `issubclass` therefore it's also valid to pass a tuple of types). + + :param type: The type to check for. + :type type: type or tuple of types + + :raises TypeError: With a human readable error message, the attribute + (of type `attrs.Attribute`), the expected type, and the value it + got. + """ + return _SubclassOfValidator(type) + + +@attrs(repr=False, slots=True, hash=True) +class _NotValidator: + validator = attrib() + msg = attrib( + converter=default_if_none( + "not_ validator child '{validator!r}' " + "did not raise a captured error" + ) + ) + exc_types = attrib( + validator=deep_iterable( + member_validator=_subclass_of(Exception), + iterable_validator=instance_of(tuple), + ), + ) + + def __call__(self, inst, attr, value): + try: + self.validator(inst, attr, value) + except self.exc_types: + pass # suppress error to invert validity + else: + raise ValueError( + self.msg.format( + validator=self.validator, + exc_types=self.exc_types, + ), + attr, + self.validator, + value, + self.exc_types, + ) + + def __repr__(self): + return ( + "" + ).format( + what=self.validator, + exc_types=self.exc_types, + ) + + +def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)): + """ + A validator that wraps and logically 'inverts' the validator passed to it. + It will raise a `ValueError` if the provided validator *doesn't* raise a + `ValueError` or `TypeError` (by default), and will suppress the exception + if the provided validator *does*. + + Intended to be used with existing validators to compose logic without + needing to create inverted variants, for example, ``not_(in_(...))``. + + :param validator: A validator to be logically inverted. + :param msg: Message to raise if validator fails. + Formatted with keys ``exc_types`` and ``validator``. + :type msg: str + :param exc_types: Exception type(s) to capture. + Other types raised by child validators will not be intercepted and + pass through. + + :raises ValueError: With a human readable error message, + the attribute (of type `attrs.Attribute`), + the validator that failed to raise an exception, + the value it got, + and the expected exception types. + + .. versionadded:: 22.2.0 + """ + try: + exc_types = tuple(exc_types) + except TypeError: + exc_types = (exc_types,) + return _NotValidator(validator, msg, exc_types) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/validators.pyi b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/validators.pyi index 5e00b8543397f..d194a75abcacf 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/validators.pyi +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/validators.pyi @@ -18,6 +18,7 @@ from typing import ( ) from . import _ValidatorType +from . import _ValidatorArgType _T = TypeVar("_T") _T1 = TypeVar("_T1") @@ -50,7 +51,9 @@ def instance_of( def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ... def provides(interface: Any) -> _ValidatorType[Any]: ... def optional( - validator: Union[_ValidatorType[_T], List[_ValidatorType[_T]]] + validator: Union[ + _ValidatorType[_T], List[_ValidatorType[_T]], Tuple[_ValidatorType[_T]] + ] ) -> _ValidatorType[Optional[_T]]: ... def in_(options: Container[_T]) -> _ValidatorType[_T]: ... def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... @@ -62,7 +65,7 @@ def matches_re( ] = ..., ) -> _ValidatorType[AnyStr]: ... def deep_iterable( - member_validator: _ValidatorType[_T], + member_validator: _ValidatorArgType[_T], iterable_validator: Optional[_ValidatorType[_I]] = ..., ) -> _ValidatorType[_I]: ... def deep_mapping( @@ -76,3 +79,10 @@ def le(val: _T) -> _ValidatorType[_T]: ... def ge(val: _T) -> _ValidatorType[_T]: ... def gt(val: _T) -> _ValidatorType[_T]: ... def max_len(length: int) -> _ValidatorType[_T]: ... +def min_len(length: int) -> _ValidatorType[_T]: ... +def not_( + validator: _ValidatorType[_T], + *, + msg: Optional[str] = None, + exc_types: Union[Type[Exception], Iterable[Type[Exception]]] = ..., +) -> _ValidatorType[_T]: ... diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/w3c-import.log new file mode 100644 index 0000000000000..648aced1a6f97 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/w3c-import.log @@ -0,0 +1,39 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/__init__.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_cmp.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_cmp.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_compat.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_config.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_funcs.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_make.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_next_gen.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_typing_compat.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_version_info.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/_version_info.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/converters.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/converters.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/exceptions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/exceptions.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/filters.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/filters.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/py.typed +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/setters.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/setters.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/validators.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attr/validators.pyi diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/__init__.py index a704b8b56bc0f..0c2481561a93a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/__init__.py @@ -3,17 +3,9 @@ from attr import ( NOTHING, Attribute, + AttrsInstance, Factory, - __author__, - __copyright__, - __description__, - __doc__, - __email__, - __license__, - __title__, - __url__, - __version__, - __version_info__, + _make_getattr, assoc, cmp_using, define, @@ -48,6 +40,7 @@ "assoc", "astuple", "Attribute", + "AttrsInstance", "cmp_using", "converters", "define", @@ -68,3 +61,5 @@ "validate", "validators", ] + +__getattr__ = _make_getattr(__name__) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/__init__.pyi b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/__init__.pyi index 7426fa5ddbf20..9372cfea16e89 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/__init__.pyi +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/__init__.pyi @@ -23,13 +23,17 @@ from attr import __version_info__ as __version_info__ from attr import _FilterType from attr import assoc as assoc from attr import Attribute as Attribute +from attr import AttrsInstance as AttrsInstance +from attr import cmp_using as cmp_using +from attr import converters as converters from attr import define as define from attr import evolve as evolve -from attr import Factory as Factory from attr import exceptions as exceptions +from attr import Factory as Factory from attr import field as field from attr import fields as fields from attr import fields_dict as fields_dict +from attr import filters as filters from attr import frozen as frozen from attr import has as has from attr import make_class as make_class @@ -42,7 +46,7 @@ from attr import validators as validators # TODO: see definition of attr.asdict/astuple def asdict( - inst: Any, + inst: AttrsInstance, recurse: bool = ..., filter: Optional[_FilterType[Any]] = ..., dict_factory: Type[Mapping[Any, Any]] = ..., @@ -55,7 +59,7 @@ def asdict( # TODO: add support for returning NamedTuple from the mypy plugin def astuple( - inst: Any, + inst: AttrsInstance, recurse: bool = ..., filter: Optional[_FilterType[Any]] = ..., tuple_factory: Type[Sequence[Any]] = ..., diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/converters.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/converters.py index edfa8d3c16ac8..7821f6c02cca8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/converters.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/converters.py @@ -1,3 +1,3 @@ # SPDX-License-Identifier: MIT -from attr.converters import * # noqa +from attr.converters import * # noqa: F403 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/exceptions.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/exceptions.py index bd9efed202ab1..3323f9d2112c5 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/exceptions.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/exceptions.py @@ -1,3 +1,3 @@ # SPDX-License-Identifier: MIT -from attr.exceptions import * # noqa +from attr.exceptions import * # noqa: F403 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/filters.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/filters.py index 52959005b088f..3080f48398e5e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/filters.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/filters.py @@ -1,3 +1,3 @@ # SPDX-License-Identifier: MIT -from attr.filters import * # noqa +from attr.filters import * # noqa: F403 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/setters.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/setters.py index 9b50770804e41..f3d73bb793dd4 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/setters.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/setters.py @@ -1,3 +1,3 @@ # SPDX-License-Identifier: MIT -from attr.setters import * # noqa +from attr.setters import * # noqa: F403 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/validators.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/validators.py index ab2c9b3024714..037e124f29f32 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/validators.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/validators.py @@ -1,3 +1,3 @@ # SPDX-License-Identifier: MIT -from attr.validators import * # noqa +from attr.validators import * # noqa: F403 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/w3c-import.log new file mode 100644 index 0000000000000..7488e74b58957 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/w3c-import.log @@ -0,0 +1,24 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/__init__.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/converters.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/exceptions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/filters.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/py.typed +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/setters.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/src/attrs/validators.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/attr_import_star.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/attr_import_star.py index eaec321bac435..bdc5c091b7f49 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/attr_import_star.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/attr_import_star.py @@ -1,8 +1,7 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import -from attr import * # noqa: F401,F403 +from attr import * # noqa: F403 # This is imported by test_import::test_from_attr_import_star; this must diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/dataclass_transform_example.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/dataclass_transform_example.py index 49e09061a8a2e..c65df14026da8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/dataclass_transform_example.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/dataclass_transform_example.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: MIT import attr +import attrs @attr.define() @@ -9,7 +10,7 @@ class Define: b: int -reveal_type(Define.__init__) # noqa +reveal_type(Define.__init__) # noqa: F821 @attr.define() @@ -18,10 +19,11 @@ class DefineConverter: with_converter: int = attr.field(converter=int) -reveal_type(DefineConverter.__init__) # noqa +reveal_type(DefineConverter.__init__) # noqa: F821 + +DefineConverter(with_converter=b"42") -# mypy plugin supports attr.frozen, pyright does not @attr.frozen() class Frozen: a: str @@ -30,10 +32,9 @@ class Frozen: d = Frozen("a") d.a = "new" -reveal_type(d.a) # noqa +reveal_type(d.a) # noqa: F821 -# but pyright supports attr.define(frozen) @attr.define(frozen=True) class FrozenDefine: a: str @@ -42,4 +43,21 @@ class FrozenDefine: d2 = FrozenDefine("a") d2.a = "new" -reveal_type(d2.a) # noqa +reveal_type(d2.a) # noqa: F821 + + +# Field-aliasing works +@attrs.define +class AliasedField: + _a: int = attrs.field(alias="_a") + + +af = AliasedField(42) + +reveal_type(af.__init__) # noqa: F821 + + +# unsafe_hash is accepted +@attrs.define(unsafe_hash=True) +class Hashable: + pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/strategies.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/strategies.py index 99f9f48536b9e..783058f837f6c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/strategies.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/strategies.py @@ -3,7 +3,7 @@ """ Testing strategies for Hypothesis-based tests. """ - +import functools import keyword import string @@ -13,6 +13,8 @@ import attr +from attr._compat import PY_3_8_PLUS + from .utils import make_class @@ -28,8 +30,7 @@ def gen_attr_names(): Some short strings (such as 'as') are keywords, so we skip them. """ lc = string.ascii_lowercase - for c in lc: - yield c + yield from lc for outer in lc: for inner in lc: res = outer + inner @@ -67,7 +68,7 @@ def _create_hyp_nested_strategy(draw, simple_class_strategy): lambda: OrderedDict([("cls", cls())]), ] factory = draw(st.sampled_from(factories)) - attrs = draw(list_of_attrs) + [attr.ib(default=attr.Factory(factory))] + attrs = [*draw(list_of_attrs), attr.ib(default=attr.Factory(factory))] return make_class("HypClass", dict(zip(gen_attr_names(), attrs))) @@ -112,13 +113,19 @@ def simple_attrs_with_metadata(draw): simple_attrs = simple_attrs_without_metadata | simple_attrs_with_metadata() + # Python functions support up to 255 arguments. list_of_attrs = st.lists(simple_attrs, max_size=3) @st.composite def simple_classes( - draw, slots=None, frozen=None, weakref_slot=None, private_attrs=None + draw, + slots=None, + frozen=None, + weakref_slot=None, + private_attrs=None, + cached_property=None, ): """ A strategy that generates classes with default non-attr attributes. @@ -158,6 +165,7 @@ class HypClass: pre_init_flag = draw(st.booleans()) post_init_flag = draw(st.booleans()) init_flag = draw(st.booleans()) + cached_property_flag = draw(st.booleans()) if pre_init_flag: @@ -180,9 +188,22 @@ def init(self, *args, **kwargs): cls_dict["__init__"] = init + bases = (object,) + if cached_property or ( + PY_3_8_PLUS and cached_property is None and cached_property_flag + ): + + class BaseWithCachedProperty: + @functools.cached_property + def _cached_property(self) -> int: + return 1 + + bases = (BaseWithCachedProperty,) + return make_class( "HypClass", cls_dict, + bases=bases, slots=slots_flag if slots is None else slots, frozen=frozen_flag if frozen is None else frozen, weakref_slot=weakref_flag if weakref_slot is None else weakref_slot, diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_3rd_party.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_3rd_party.py index 8866d7f6ef264..b2ce06c293bfb 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_3rd_party.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_3rd_party.py @@ -14,12 +14,12 @@ cloudpickle = pytest.importorskip("cloudpickle") -class TestCloudpickleCompat(object): +class TestCloudpickleCompat: """ Tests for compatibility with ``cloudpickle``. """ - @given(simple_classes()) + @given(simple_classes(cached_property=False)) def test_repr(self, cls): """ attrs instances can be pickled and un-pickled with cloudpickle. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_abc.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_abc.py new file mode 100644 index 0000000000000..a70b317a3cee1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_abc.py @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: MIT + +import abc +import inspect + +import pytest + +import attrs + +from attr._compat import PY310, PY_3_12_PLUS + + +@pytest.mark.skipif(not PY310, reason="abc.update_abstractmethods is 3.10+") +class TestUpdateAbstractMethods: + def test_abc_implementation(self, slots): + """ + If an attrs class implements an abstract method, it stops being + abstract. + """ + + class Ordered(abc.ABC): + @abc.abstractmethod + def __lt__(self, other): + pass + + @abc.abstractmethod + def __le__(self, other): + pass + + @attrs.define(order=True, slots=slots) + class Concrete(Ordered): + x: int + + assert not inspect.isabstract(Concrete) + assert Concrete(2) > Concrete(1) + + def test_remain_abstract(self, slots): + """ + If an attrs class inherits from an abstract class but doesn't implement + abstract methods, it remains abstract. + """ + + class A(abc.ABC): + @abc.abstractmethod + def foo(self): + pass + + @attrs.define(slots=slots) + class StillAbstract(A): + pass + + assert inspect.isabstract(StillAbstract) + expected_exception_message = ( + "^Can't instantiate abstract class StillAbstract without an " + "implementation for abstract method 'foo'$" + if PY_3_12_PLUS + else "class StillAbstract with abstract method foo" + ) + with pytest.raises(TypeError, match=expected_exception_message): + StillAbstract() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_annotations.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_annotations.py index a201ebf7fa689..d27d9e3743fe9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_annotations.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_annotations.py @@ -2,8 +2,6 @@ """ Tests for PEP-526 type annotations. - -Python 3.6+ only. """ import sys @@ -94,7 +92,10 @@ class C: assert 1 == len(attr.fields(C)) assert_init_annotations(C, x=typing.List[int]) - @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.skipif( + sys.version_info[:2] < (3, 11), + reason="Incompatible behavior on older Pythons", + ) def test_auto_attribs(self, slots): """ If *auto_attribs* is True, bare annotations are collected too. @@ -113,7 +114,7 @@ class C: i = C(42) assert "C(a=42, x=[], y=2, z=3, foo=None)" == repr(i) - attr_names = set(a.name for a in C.__attrs_attrs__) + attr_names = {a.name for a in C.__attrs_attrs__} assert "a" in attr_names # just double check that the set works assert "cls_var" not in attr_names @@ -149,10 +150,9 @@ class C: x=typing.List[int], y=int, z=int, - foo=typing.Optional[typing.Any], + foo=typing.Any, ) - @pytest.mark.parametrize("slots", [True, False]) def test_auto_attribs_unannotated(self, slots): """ Unannotated `attr.ib`s raise an error. @@ -170,7 +170,6 @@ class C: "The following `attr.ib`s lack a type annotation: v, y.", ) == e.value.args - @pytest.mark.parametrize("slots", [True, False]) def test_auto_attribs_subclassing(self, slots): """ Attributes from base classes are inherited, it doesn't matter if the @@ -251,7 +250,7 @@ class A: def test_nullary_converter(self): """ - A coverter with no arguments doesn't cause a crash. + A converter with no arguments doesn't cause a crash. """ def noop(): @@ -384,15 +383,15 @@ def noop(): assert attr.converters.optional(noop).__annotations__ == {} - @pytest.mark.xfail( - sys.version_info[:2] == (3, 6), reason="Does not work on 3.6." + @pytest.mark.skipif( + sys.version_info[:2] < (3, 11), + reason="Incompatible behavior on older Pythons", ) - @pytest.mark.parametrize("slots", [True, False]) def test_annotations_strings(self, slots): """ String annotations are passed into __init__ as is. - It fails on 3.6 due to a bug in Python. + The strings keep changing between releases. """ import typing as t @@ -417,10 +416,9 @@ class C: x=typing.List[int], y=int, z=int, - foo=typing.Optional[typing.Any], + foo=typing.Any, ) - @pytest.mark.parametrize("slots", [True, False]) def test_typing_extensions_classvar(self, slots): """ If ClassVar is coming from typing_extensions, it is recognized too. @@ -428,7 +426,7 @@ def test_typing_extensions_classvar(self, slots): @attr.s(auto_attribs=True, slots=slots) class C: - cls_var: "typing_extensions.ClassVar" = 23 # noqa + cls_var: "typing_extensions.ClassVar" = 23 # noqa: F821 assert_init_annotations(C) @@ -518,7 +516,34 @@ class C: assert str is attr.fields(C).y.type assert None is attr.fields(C).z.type - @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.skipif( + sys.version_info[:2] < (3, 9), + reason="Incompatible behavior on older Pythons", + ) + def test_extra_resolve(self): + """ + `get_type_hints` returns extra type hints. + """ + from typing import Annotated + + globals = {"Annotated": Annotated} + + @attr.define + class C: + x: 'Annotated[float, "test"]' + + attr.resolve_types(C, globals) + + assert attr.fields(C).x.type == Annotated[float, "test"] + + @attr.define + class D: + x: 'Annotated[float, "test"]' + + attr.resolve_types(D, globals, include_extras=False) + + assert attr.fields(D).x.type == float + def test_resolve_types_auto_attrib(self, slots): """ Types can be resolved even when strings are involved. @@ -538,7 +563,6 @@ class A: assert typing.List[int] == attr.fields(A).b.type assert typing.List[int] == attr.fields(A).c.type - @pytest.mark.parametrize("slots", [True, False]) def test_resolve_types_decorator(self, slots): """ Types can be resolved using it as a decorator. @@ -555,7 +579,6 @@ class A: assert typing.List[int] == attr.fields(A).b.type assert typing.List[int] == attr.fields(A).c.type - @pytest.mark.parametrize("slots", [True, False]) def test_self_reference(self, slots): """ References to self class using quotes can be resolved. @@ -564,14 +587,13 @@ def test_self_reference(self, slots): @attr.s(slots=slots, auto_attribs=True) class A: a: "A" - b: typing.Optional["A"] # noqa: will resolve below + b: typing.Optional["A"] # will resolve below -- noqa: F821 attr.resolve_types(A, globals(), locals()) assert A == attr.fields(A).a.type assert typing.Optional[A] == attr.fields(A).b.type - @pytest.mark.parametrize("slots", [True, False]) def test_forward_reference(self, slots): """ Forward references can be resolved. @@ -579,7 +601,7 @@ def test_forward_reference(self, slots): @attr.s(slots=slots, auto_attribs=True) class A: - a: typing.List["B"] # noqa: will resolve below + a: typing.List["B"] # will resolve below -- noqa: F821 @attr.s(slots=slots, auto_attribs=True) class B: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_cmp.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_cmp.py index ec2c6874899b7..07bfc5234ade5 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_cmp.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_cmp.py @@ -4,12 +4,10 @@ Tests for methods from `attrib._cmp`. """ -from __future__ import absolute_import, division, print_function import pytest from attr._cmp import cmp_using -from attr._compat import PY2 # Test parameters. @@ -57,7 +55,7 @@ cmp_ids = eq_ids + order_ids -class TestEqOrder(object): +class TestEqOrder: """ Tests for eq and order related methods. """ @@ -65,7 +63,9 @@ class TestEqOrder(object): ######### # eq ######### - @pytest.mark.parametrize("cls, requires_same_type", cmp_data, ids=cmp_ids) + @pytest.mark.parametrize( + ("cls", "requires_same_type"), cmp_data, ids=cmp_ids + ) def test_equal_same_type(self, cls, requires_same_type): """ Equal objects are detected as equal. @@ -73,7 +73,9 @@ def test_equal_same_type(self, cls, requires_same_type): assert cls(1) == cls(1) assert not (cls(1) != cls(1)) - @pytest.mark.parametrize("cls, requires_same_type", cmp_data, ids=cmp_ids) + @pytest.mark.parametrize( + ("cls", "requires_same_type"), cmp_data, ids=cmp_ids + ) def test_unequal_same_type(self, cls, requires_same_type): """ Unequal objects of correct type are detected as unequal. @@ -81,7 +83,9 @@ def test_unequal_same_type(self, cls, requires_same_type): assert cls(1) != cls(2) assert not (cls(1) == cls(2)) - @pytest.mark.parametrize("cls, requires_same_type", cmp_data, ids=cmp_ids) + @pytest.mark.parametrize( + ("cls", "requires_same_type"), cmp_data, ids=cmp_ids + ) def test_equal_different_type(self, cls, requires_same_type): """ Equal values of different types are detected appropriately. @@ -92,8 +96,9 @@ def test_equal_different_type(self, cls, requires_same_type): ######### # lt ######### - @pytest.mark.skipif(PY2, reason="PY2 does not raise TypeError") - @pytest.mark.parametrize("cls, requires_same_type", eq_data, ids=eq_ids) + @pytest.mark.parametrize( + ("cls", "requires_same_type"), eq_data, ids=eq_ids + ) def test_lt_unorderable(self, cls, requires_same_type): """ TypeError is raised if class does not implement __lt__. @@ -102,7 +107,7 @@ def test_lt_unorderable(self, cls, requires_same_type): cls(1) < cls(2) @pytest.mark.parametrize( - "cls, requires_same_type", order_data, ids=order_ids + ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_lt_same_type(self, cls, requires_same_type): """ @@ -112,7 +117,7 @@ def test_lt_same_type(self, cls, requires_same_type): assert not (cls(2) < cls(1)) @pytest.mark.parametrize( - "cls, requires_same_type", order_data, ids=order_ids + ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_not_lt_same_type(self, cls, requires_same_type): """ @@ -122,7 +127,7 @@ def test_not_lt_same_type(self, cls, requires_same_type): assert not (cls(1) >= cls(2)) @pytest.mark.parametrize( - "cls, requires_same_type", order_data, ids=order_ids + ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_lt_different_type(self, cls, requires_same_type): """ @@ -131,9 +136,8 @@ def test_lt_different_type(self, cls, requires_same_type): if requires_same_type: # Unlike __eq__, NotImplemented will cause an exception to be # raised from __lt__. - if not PY2: - with pytest.raises(TypeError): - cls(1) < cls(2.0) + with pytest.raises(TypeError): + cls(1) < cls(2.0) else: assert cls(1) < cls(2.0) assert not (cls(2) < cls(1.0)) @@ -141,8 +145,9 @@ def test_lt_different_type(self, cls, requires_same_type): ######### # le ######### - @pytest.mark.skipif(PY2, reason="PY2 does not raise TypeError") - @pytest.mark.parametrize("cls, requires_same_type", eq_data, ids=eq_ids) + @pytest.mark.parametrize( + ("cls", "requires_same_type"), eq_data, ids=eq_ids + ) def test_le_unorderable(self, cls, requires_same_type): """ TypeError is raised if class does not implement __le__. @@ -151,7 +156,7 @@ def test_le_unorderable(self, cls, requires_same_type): cls(1) <= cls(2) @pytest.mark.parametrize( - "cls, requires_same_type", order_data, ids=order_ids + ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_le_same_type(self, cls, requires_same_type): """ @@ -162,7 +167,7 @@ def test_le_same_type(self, cls, requires_same_type): assert not (cls(2) <= cls(1)) @pytest.mark.parametrize( - "cls, requires_same_type", order_data, ids=order_ids + ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_not_le_same_type(self, cls, requires_same_type): """ @@ -173,7 +178,7 @@ def test_not_le_same_type(self, cls, requires_same_type): assert not (cls(1) > cls(2)) @pytest.mark.parametrize( - "cls, requires_same_type", order_data, ids=order_ids + ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_le_different_type(self, cls, requires_same_type): """ @@ -182,9 +187,8 @@ def test_le_different_type(self, cls, requires_same_type): if requires_same_type: # Unlike __eq__, NotImplemented will cause an exception to be # raised from __le__. - if not PY2: - with pytest.raises(TypeError): - cls(1) <= cls(2.0) + with pytest.raises(TypeError): + cls(1) <= cls(2.0) else: assert cls(1) <= cls(2.0) assert cls(1) <= cls(1.0) @@ -193,8 +197,9 @@ def test_le_different_type(self, cls, requires_same_type): ######### # gt ######### - @pytest.mark.skipif(PY2, reason="PY2 does not raise TypeError") - @pytest.mark.parametrize("cls, requires_same_type", eq_data, ids=eq_ids) + @pytest.mark.parametrize( + ("cls", "requires_same_type"), eq_data, ids=eq_ids + ) def test_gt_unorderable(self, cls, requires_same_type): """ TypeError is raised if class does not implement __gt__. @@ -203,7 +208,7 @@ def test_gt_unorderable(self, cls, requires_same_type): cls(2) > cls(1) @pytest.mark.parametrize( - "cls, requires_same_type", order_data, ids=order_ids + ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_gt_same_type(self, cls, requires_same_type): """ @@ -213,7 +218,7 @@ def test_gt_same_type(self, cls, requires_same_type): assert not (cls(1) > cls(2)) @pytest.mark.parametrize( - "cls, requires_same_type", order_data, ids=order_ids + ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_not_gt_same_type(self, cls, requires_same_type): """ @@ -223,7 +228,7 @@ def test_not_gt_same_type(self, cls, requires_same_type): assert not (cls(2) <= cls(1)) @pytest.mark.parametrize( - "cls, requires_same_type", order_data, ids=order_ids + ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_gt_different_type(self, cls, requires_same_type): """ @@ -232,9 +237,8 @@ def test_gt_different_type(self, cls, requires_same_type): if requires_same_type: # Unlike __eq__, NotImplemented will cause an exception to be # raised from __gt__. - if not PY2: - with pytest.raises(TypeError): - cls(2) > cls(1.0) + with pytest.raises(TypeError): + cls(2) > cls(1.0) else: assert cls(2) > cls(1.0) assert not (cls(1) > cls(2.0)) @@ -242,8 +246,9 @@ def test_gt_different_type(self, cls, requires_same_type): ######### # ge ######### - @pytest.mark.skipif(PY2, reason="PY2 does not raise TypeError") - @pytest.mark.parametrize("cls, requires_same_type", eq_data, ids=eq_ids) + @pytest.mark.parametrize( + ("cls", "requires_same_type"), eq_data, ids=eq_ids + ) def test_ge_unorderable(self, cls, requires_same_type): """ TypeError is raised if class does not implement __ge__. @@ -252,7 +257,7 @@ def test_ge_unorderable(self, cls, requires_same_type): cls(2) >= cls(1) @pytest.mark.parametrize( - "cls, requires_same_type", order_data, ids=order_ids + ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_ge_same_type(self, cls, requires_same_type): """ @@ -263,7 +268,7 @@ def test_ge_same_type(self, cls, requires_same_type): assert not (cls(1) >= cls(2)) @pytest.mark.parametrize( - "cls, requires_same_type", order_data, ids=order_ids + ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_not_ge_same_type(self, cls, requires_same_type): """ @@ -274,7 +279,7 @@ def test_not_ge_same_type(self, cls, requires_same_type): assert not (cls(2) < cls(1)) @pytest.mark.parametrize( - "cls, requires_same_type", order_data, ids=order_ids + ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_ge_different_type(self, cls, requires_same_type): """ @@ -283,16 +288,15 @@ def test_ge_different_type(self, cls, requires_same_type): if requires_same_type: # Unlike __eq__, NotImplemented will cause an exception to be # raised from __ge__. - if not PY2: - with pytest.raises(TypeError): - cls(2) >= cls(1.0) + with pytest.raises(TypeError): + cls(2) >= cls(1.0) else: assert cls(2) >= cls(2.0) assert cls(2) >= cls(1.0) assert not (cls(1) >= cls(2.0)) -class TestDundersUnnamedClass(object): +class TestDundersUnnamedClass: """ Tests for dunder attributes of unnamed classes. """ @@ -304,8 +308,7 @@ def test_class(self): Class name and qualified name should be well behaved. """ assert self.cls.__name__ == "Comparable" - if not PY2: - assert self.cls.__qualname__ == "Comparable" + assert self.cls.__qualname__ == "Comparable" def test_eq(self): """ @@ -327,7 +330,7 @@ def test_ne(self): assert method.__name__ == "__ne__" -class TestTotalOrderingException(object): +class TestTotalOrderingException: """ Test for exceptions related to total ordering. """ @@ -345,7 +348,7 @@ def test_eq_must_specified(self): ) -class TestNotImplementedIsPropagated(object): +class TestNotImplementedIsPropagated: """ Test related to functions that return NotImplemented. """ @@ -361,7 +364,7 @@ def test_not_implemented_is_propagated(self): assert C(1) != C(1) -class TestDundersPartialOrdering(object): +class TestDundersPartialOrdering: """ Tests for dunder attributes of classes with partial ordering. """ @@ -373,8 +376,7 @@ def test_class(self): Class name and qualified name should be well behaved. """ assert self.cls.__name__ == "PartialOrderCSameType" - if not PY2: - assert self.cls.__qualname__ == "PartialOrderCSameType" + assert self.cls.__qualname__ == "PartialOrderCSameType" def test_eq(self): """ @@ -408,12 +410,9 @@ def test_le(self): __le__ docstring and qualified name should be well behaved. """ method = self.cls.__le__ - if PY2: - assert method.__doc__ == "x.__le__(y) <==> x<=y" - else: - assert method.__doc__.strip().startswith( - "Return a <= b. Computed by @total_ordering from" - ) + assert method.__doc__.strip().startswith( + "Return a <= b. Computed by @total_ordering from" + ) assert method.__name__ == "__le__" def test_gt(self): @@ -421,12 +420,9 @@ def test_gt(self): __gt__ docstring and qualified name should be well behaved. """ method = self.cls.__gt__ - if PY2: - assert method.__doc__ == "x.__gt__(y) <==> x>y" - else: - assert method.__doc__.strip().startswith( - "Return a > b. Computed by @total_ordering from" - ) + assert method.__doc__.strip().startswith( + "Return a > b. Computed by @total_ordering from" + ) assert method.__name__ == "__gt__" def test_ge(self): @@ -434,16 +430,13 @@ def test_ge(self): __ge__ docstring and qualified name should be well behaved. """ method = self.cls.__ge__ - if PY2: - assert method.__doc__ == "x.__ge__(y) <==> x>=y" - else: - assert method.__doc__.strip().startswith( - "Return a >= b. Computed by @total_ordering from" - ) + assert method.__doc__.strip().startswith( + "Return a >= b. Computed by @total_ordering from" + ) assert method.__name__ == "__ge__" -class TestDundersFullOrdering(object): +class TestDundersFullOrdering: """ Tests for dunder attributes of classes with full ordering. """ @@ -455,8 +448,7 @@ def test_class(self): Class name and qualified name should be well behaved. """ assert self.cls.__name__ == "FullOrderCSameType" - if not PY2: - assert self.cls.__qualname__ == "FullOrderCSameType" + assert self.cls.__qualname__ == "FullOrderCSameType" def test_eq(self): """ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_compat.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_compat.py index 464b492f0fac0..c8015b596e2b0 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_compat.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_compat.py @@ -1,18 +1,20 @@ # SPDX-License-Identifier: MIT +import types + import pytest -from attr._compat import metadata_proxy +import attr @pytest.fixture(name="mp") def _mp(): - return metadata_proxy({"x": 42, "y": "foo"}) + return types.MappingProxyType({"x": 42, "y": "foo"}) class TestMetadataProxy: """ - Ensure properties of metadata_proxy independently of hypothesis strategies. + Ensure properties of metadata proxy independently of hypothesis strategies. """ def test_repr(self, mp): @@ -50,3 +52,13 @@ def test_immutable(self, mp): with pytest.raises(AttributeError, match="no attribute 'setdefault'"): mp.setdefault("x") + + +def test_attrsinstance_subclass_protocol(): + """ + It's possible to subclass AttrsInstance and Protocol at once. + """ + + class Foo(attr.AttrsInstance, attr._compat.Protocol): + def attribute(self) -> int: + ... diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_config.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_config.py index bbf67564064dd..6c78fd295b538 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_config.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_config.py @@ -4,14 +4,13 @@ Tests for `attr._config`. """ -from __future__ import absolute_import, division, print_function import pytest from attr import _config -class TestConfig(object): +class TestConfig: def test_default(self): """ Run validators by default. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_converters.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_converters.py index d0fc723eb1b8e..7607e555066c7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_converters.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_converters.py @@ -4,7 +4,6 @@ Tests for `attr.converters`. """ -from __future__ import absolute_import import pytest @@ -14,7 +13,7 @@ from attr.converters import default_if_none, optional, pipe, to_bool -class TestOptional(object): +class TestOptional: """ Tests for `optional`. """ @@ -45,7 +44,7 @@ def test_fail(self): c("not_an_int") -class TestDefaultIfNone(object): +class TestDefaultIfNone: def test_missing_default(self): """ Raises TypeError if neither default nor factory have been passed. @@ -101,7 +100,7 @@ def test_none_factory(self): assert [] == c(None) -class TestPipe(object): +class TestPipe: def test_success(self): """ Succeeds if all wrapped converters succeed. @@ -130,15 +129,23 @@ def test_sugar(self): """ @attr.s - class C(object): + class C: a1 = attrib(default="True", converter=pipe(str, to_bool, bool)) a2 = attrib(default=True, converter=[str, to_bool, bool]) c = C() assert True is c.a1 is c.a2 + def test_empty(self): + """ + Empty pipe returns same value. + """ + o = object() + + assert o is pipe()(o) + -class TestToBool(object): +class TestToBool: def test_unhashable(self): """ Fails if value is unhashable. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_dunders.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_dunders.py index 186762eb0da6e..d0d289d84c9f1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_dunders.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_dunders.py @@ -4,9 +4,9 @@ Tests for dunder methods from `attrib._make`. """ -from __future__ import absolute_import, division, print_function import copy +import inspect import pickle import pytest @@ -22,7 +22,6 @@ _add_repr, _is_slot_cls, _make_init, - _Nothing, fields, make_class, ) @@ -40,25 +39,25 @@ @attr.s(eq=True) -class EqCallableC(object): +class EqCallableC: a = attr.ib(eq=str.lower, order=False) b = attr.ib(eq=True) @attr.s(eq=True, slots=True) -class EqCallableCSlots(object): +class EqCallableCSlots: a = attr.ib(eq=str.lower, order=False) b = attr.ib(eq=True) @attr.s(order=True) -class OrderCallableC(object): +class OrderCallableC: a = attr.ib(eq=True, order=str.lower) b = attr.ib(order=True) @attr.s(order=True, slots=True) -class OrderCallableCSlots(object): +class OrderCallableCSlots: a = attr.ib(eq=True, order=str.lower) b = attr.ib(order=True) @@ -86,10 +85,15 @@ def _add_init(cls, frozen): This function used to be part of _make. It wasn't used anymore however the tests for it are still useful to test the behavior of _make_init. """ + has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False)) + cls.__init__ = _make_init( cls, cls.__attrs_attrs__, - getattr(cls, "__attrs_pre_init__", False), + has_pre_init, + len(inspect.signature(cls.__attrs_pre_init__).parameters) > 1 + if has_pre_init + else False, getattr(cls, "__attrs_post_init__", False), frozen, _is_slot_cls(cls), @@ -102,14 +106,14 @@ def _add_init(cls, frozen): return cls -class InitC(object): +class InitC: __attrs_attrs__ = [simple_attr("a"), simple_attr("b")] InitC = _add_init(InitC, False) -class TestEqOrder(object): +class TestEqOrder: """ Tests for eq and order related methods. """ @@ -168,7 +172,7 @@ def test_unequal_different_class(self, cls): match. """ - class NotEqC(object): + class NotEqC: a = 1 b = 2 @@ -316,12 +320,11 @@ def test_ge_unordable(self, cls): assert NotImplemented == (cls(1, 2).__ge__(42)) -class TestAddRepr(object): +class TestAddRepr: """ Tests for `_add_repr`. """ - @pytest.mark.parametrize("slots", [True, False]) def test_repr(self, slots): """ If `repr` is False, ignore that attribute. @@ -349,7 +352,7 @@ def custom_repr(value): return "foo:" + str(value) @attr.s - class C(object): + class C: a = attr.ib(repr=custom_repr) assert "C(a=foo:1)" == repr(C(1)) @@ -361,7 +364,7 @@ def test_infinite_recursion(self): """ @attr.s - class Cycle(object): + class Cycle: value = attr.ib(default=7) cycle = attr.ib(default=None) @@ -376,7 +379,7 @@ def test_infinite_recursion_long_cycle(self): """ @attr.s - class LongCycle(object): + class LongCycle: value = attr.ib(default=14) cycle = attr.ib(default=None) @@ -391,7 +394,7 @@ def test_underscores(self): repr does not strip underscores. """ - class C(object): + class C: __attrs_attrs__ = [simple_attr("_x")] C = _add_repr(C) @@ -440,21 +443,21 @@ def test_str_no_repr(self): # these are for use in TestAddHash.test_cache_hash_serialization # they need to be out here so they can be un-pickled @attr.attrs(hash=True, cache_hash=False) -class HashCacheSerializationTestUncached(object): +class HashCacheSerializationTestUncached: foo_value = attr.ib() @attr.attrs(hash=True, cache_hash=True) -class HashCacheSerializationTestCached(object): +class HashCacheSerializationTestCached: foo_value = attr.ib() @attr.attrs(slots=True, hash=True, cache_hash=True) -class HashCacheSerializationTestCachedSlots(object): +class HashCacheSerializationTestCachedSlots: foo_value = attr.ib() -class IncrementingHasher(object): +class IncrementingHasher: def __init__(self): self.hash_value = 100 @@ -464,7 +467,7 @@ def __hash__(self): return rv -class TestAddHash(object): +class TestAddHash: """ Tests for `_add_hash`. """ @@ -647,21 +650,19 @@ def __hash__(self): assert 1 == cached_instance.hash_counter.times_hash_called @pytest.mark.parametrize("cache_hash", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) - @pytest.mark.parametrize("slots", [True, False]) def test_copy_hash_cleared(self, cache_hash, frozen, slots): """ Test that the default hash is recalculated after a copy operation. """ - kwargs = dict(frozen=frozen, slots=slots, cache_hash=cache_hash) + kwargs = {"frozen": frozen, "slots": slots, "cache_hash": cache_hash} # Give it an explicit hash if we don't have an implicit one if not frozen: kwargs["hash"] = True @attr.s(**kwargs) - class C(object): + class C: x = attr.ib() a = C(IncrementingHasher()) @@ -676,7 +677,7 @@ class C(object): assert orig_hash != hash(b) @pytest.mark.parametrize( - "klass,cached", + ("klass", "cached"), [ (HashCacheSerializationTestUncached, False), (HashCacheSerializationTestCached, True), @@ -702,7 +703,6 @@ def test_cache_hash_serialization_hash_cleared(self, klass, cached): assert original_hash != hash(obj_rt) - @pytest.mark.parametrize("frozen", [True, False]) def test_copy_two_arg_reduce(self, frozen): """ If __getstate__ returns None, the tuple returned by object.__reduce__ @@ -711,7 +711,7 @@ def test_copy_two_arg_reduce(self, frozen): """ @attr.s(frozen=frozen, cache_hash=True, hash=True) - class C(object): + class C: x = attr.ib() def __getstate__(self): @@ -729,7 +729,7 @@ def _roundtrip_pickle(self, obj): return pickle.loads(pickle_str) -class TestAddInit(object): +class TestAddInit: """ Tests for `_add_init`. """ @@ -802,7 +802,7 @@ def test_default(self): If a default value is present, it's used as fallback. """ - class C(object): + class C: __attrs_attrs__ = [ simple_attr(name="a", default=2), simple_attr(name="b", default="hallo"), @@ -820,10 +820,10 @@ def test_factory(self): If a default factory is present, it's used as fallback. """ - class D(object): + class D: pass - class C(object): + class C: __attrs_attrs__ = [ simple_attr(name="a", default=Factory(list)), simple_attr(name="b", default=Factory(D)), @@ -898,7 +898,7 @@ def test_underscores(self): underscores. """ - class C(object): + class C: __attrs_attrs__ = [simple_attr("_private")] C = _add_init(C, False) @@ -906,32 +906,32 @@ class C(object): assert 42 == i._private -class TestNothing(object): +class TestNothing: """ - Tests for `_Nothing`. + Tests for `NOTHING`. """ def test_copy(self): """ __copy__ returns the same object. """ - n = _Nothing() + n = NOTHING assert n is copy.copy(n) def test_deepcopy(self): """ __deepcopy__ returns the same object. """ - n = _Nothing() + n = NOTHING assert n is copy.deepcopy(n) def test_eq(self): """ All instances are equal. """ - assert _Nothing() == _Nothing() == NOTHING - assert not (_Nothing() != _Nothing()) - assert 1 != _Nothing() + assert NOTHING == NOTHING == NOTHING + assert not (NOTHING != NOTHING) + assert 1 != NOTHING def test_false(self): """ @@ -942,7 +942,7 @@ def test_false(self): @attr.s(hash=True, order=True) -class C(object): +class C: pass @@ -951,7 +951,7 @@ class C(object): @attr.s(hash=True, order=True) -class C(object): +class C: pass @@ -959,13 +959,13 @@ class C(object): @attr.s(hash=True, order=True) -class C(object): +class C: """A different class, to generate different methods.""" a = attr.ib() -class TestFilenames(object): +class TestFilenames: def test_filenames(self): """ The created dunder methods have a "consistent" filename. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_filters.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_filters.py index d1ec24dc6c2c0..6d237fdc3d13d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_filters.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_filters.py @@ -4,7 +4,6 @@ Tests for `attr.filters`. """ -from __future__ import absolute_import, division, print_function import pytest @@ -15,12 +14,12 @@ @attr.s -class C(object): +class C: a = attr.ib() b = attr.ib() -class TestSplitWhat(object): +class TestSplitWhat: """ Tests for `_split_what`. """ @@ -31,22 +30,27 @@ def test_splits(self): """ assert ( frozenset((int, str)), + frozenset(("abcd", "123")), frozenset((fields(C).a,)), - ) == _split_what((str, fields(C).a, int)) + ) == _split_what((str, "123", fields(C).a, int, "abcd")) -class TestInclude(object): +class TestInclude: """ Tests for `include`. """ @pytest.mark.parametrize( - "incl,value", + ("incl", "value"), [ ((int,), 42), ((str,), "hello"), ((str, fields(C).a), 42), ((str, fields(C).b), "hello"), + (("a",), 42), + (("a",), "hello"), + (("a", str), 42), + (("a", fields(C).b), "hello"), ], ) def test_allow(self, incl, value): @@ -57,12 +61,16 @@ def test_allow(self, incl, value): assert i(fields(C).a, value) is True @pytest.mark.parametrize( - "incl,value", + ("incl", "value"), [ ((str,), 42), ((int,), "hello"), ((str, fields(C).b), 42), ((int, fields(C).b), "hello"), + (("b",), 42), + (("b",), "hello"), + (("b", str), 42), + (("b", fields(C).b), "hello"), ], ) def test_drop_class(self, incl, value): @@ -73,18 +81,22 @@ def test_drop_class(self, incl, value): assert i(fields(C).a, value) is False -class TestExclude(object): +class TestExclude: """ Tests for `exclude`. """ @pytest.mark.parametrize( - "excl,value", + ("excl", "value"), [ ((str,), 42), ((int,), "hello"), ((str, fields(C).b), 42), ((int, fields(C).b), "hello"), + (("b",), 42), + (("b",), "hello"), + (("b", str), 42), + (("b", fields(C).b), "hello"), ], ) def test_allow(self, excl, value): @@ -95,12 +107,16 @@ def test_allow(self, excl, value): assert e(fields(C).a, value) is True @pytest.mark.parametrize( - "excl,value", + ("excl", "value"), [ ((int,), 42), ((str,), "hello"), ((str, fields(C).a), 42), ((str, fields(C).b), "hello"), + (("a",), 42), + (("a",), "hello"), + (("a", str), 42), + (("a", fields(C).b), "hello"), ], ) def test_drop_class(self, excl, value): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_funcs.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_funcs.py index 4490ed815ae3e..044aaab2c94a8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_funcs.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_funcs.py @@ -4,9 +4,10 @@ Tests for `attr._funcs`. """ -from __future__ import absolute_import, division, print_function +import re from collections import OrderedDict +from typing import Generic, NamedTuple, TypeVar import pytest @@ -16,7 +17,7 @@ import attr from attr import asdict, assoc, astuple, evolve, fields, has -from attr._compat import TYPE, Mapping, Sequence, ordered_dict +from attr._compat import Mapping, Sequence from attr.exceptions import AttrsAttributeNotFoundError from attr.validators import instance_of @@ -35,14 +36,14 @@ def _C(): import attr @attr.s - class C(object): + class C: x = attr.ib() y = attr.ib() return C -class TestAsDict(object): +class TestAsDict: """ Tests for `asdict`. """ @@ -197,7 +198,7 @@ def test_asdict_preserve_order(self, cls): Field order should be preserved when dumping to an ordered_dict. """ instance = cls() - dict_instance = asdict(instance, dict_factory=ordered_dict) + dict_instance = asdict(instance, dict_factory=dict) assert [a.name for a in fields(cls)] == list(dict_instance.keys()) @@ -207,7 +208,7 @@ def test_retain_keys_are_tuples(self): """ @attr.s - class A(object): + class A: a = attr.ib() instance = A({(1,): 1}) @@ -225,15 +226,61 @@ def test_tuple_keys(self): """ @attr.s - class A(object): + class A: a = attr.ib() instance = A({(1,): 1}) assert {"a": {(1,): 1}} == attr.asdict(instance) + def test_named_tuple_retain_type(self): + """ + Namedtuples can be serialized if retain_collection_types is True. + + See #1164 + """ + + class Coordinates(NamedTuple): + lat: float + lon: float + + @attr.s + class A: + coords: Coordinates = attr.ib() + + instance = A(Coordinates(50.419019, 30.516225)) + + assert {"coords": Coordinates(50.419019, 30.516225)} == attr.asdict( + instance, retain_collection_types=True + ) + + def test_type_error_with_retain_type(self): + """ + Serialization that fails with TypeError leaves the error through if + they're not tuples. + + See #1164 + """ -class TestAsTuple(object): + message = "__new__() missing 1 required positional argument (asdict)" + + class Coordinates(list): + def __init__(self, first, *rest): + if isinstance(first, list): + raise TypeError(message) + super().__init__([first, *rest]) + + @attr.s + class A: + coords: Coordinates = attr.ib() + + instance = A(Coordinates(50.419019, 30.516225)) + + with pytest.raises(TypeError, match=re.escape(message)): + attr.asdict(instance, retain_collection_types=True) + + +class TestAsTuple: """ Tests for `astuple`. """ @@ -390,8 +437,54 @@ def test_sets_no_retain(self, C, set_type): assert (1, [1, 2, 3]) == d + def test_named_tuple_retain_type(self): + """ + Namedtuples can be serialized if retain_collection_types is True. -class TestHas(object): + See #1164 + """ + + class Coordinates(NamedTuple): + lat: float + lon: float + + @attr.s + class A: + coords: Coordinates = attr.ib() + + instance = A(Coordinates(50.419019, 30.516225)) + + assert (Coordinates(50.419019, 30.516225),) == attr.astuple( + instance, retain_collection_types=True + ) + + def test_type_error_with_retain_type(self): + """ + Serialization that fails with TypeError leaves the error through if + they're not tuples. + + See #1164 + """ + + message = "__new__() missing 1 required positional argument (astuple)" + + class Coordinates(list): + def __init__(self, first, *rest): + if isinstance(first, list): + raise TypeError(message) + super().__init__([first, *rest]) + + @attr.s + class A: + coords: Coordinates = attr.ib() + + instance = A(Coordinates(50.419019, 30.516225)) + + with pytest.raises(TypeError, match=re.escape(message)): + attr.astuple(instance, retain_collection_types=True) + + +class TestHas: """ Tests for `has`. """ @@ -408,7 +501,7 @@ def test_positive_empty(self): """ @attr.s - class D(object): + class D: pass assert has(D) @@ -419,8 +512,39 @@ def test_negative(self): """ assert not has(object) + def test_generics(self): + """ + Works with generic classes. + """ + T = TypeVar("T") + + @attr.define + class A(Generic[T]): + a: T + + assert has(A) + + assert has(A[str]) + # Verify twice, since there's caching going on. + assert has(A[str]) + + def test_generics_negative(self): + """ + Returns `False` on non-decorated generic classes. + """ + T = TypeVar("T") + + class A(Generic[T]): + a: T + + assert not has(A) + + assert not has(A[str]) + # Verify twice, since there's caching going on. + assert not has(A[str]) + -class TestAssoc(object): +class TestAssoc: """ Tests for `assoc`. """ @@ -432,12 +556,11 @@ def test_empty(self, slots, frozen): """ @attr.s(slots=slots, frozen=frozen) - class C(object): + class C: pass i1 = C() - with pytest.deprecated_call(): - i2 = assoc(i1) + i2 = assoc(i1) assert i1 is not i2 assert i1 == i2 @@ -448,8 +571,7 @@ def test_no_changes(self, C): No changes means a verbatim copy. """ i1 = C() - with pytest.deprecated_call(): - i2 = assoc(i1) + i2 = assoc(i1) assert i1 is not i2 assert i1 == i2 @@ -466,8 +588,7 @@ def test_change(self, C, data): chosen_names = data.draw(st.sets(st.sampled_from(field_names))) change_dict = {name: data.draw(st.integers()) for name in chosen_names} - with pytest.deprecated_call(): - changed = assoc(original, **change_dict) + changed = assoc(original, **change_dict) for k, v in change_dict.items(): assert getattr(changed, k) == v @@ -484,9 +605,7 @@ def test_unknown(self, C): ) as e, pytest.deprecated_call(): assoc(C(), aaaa=2) - assert ( - "aaaa is not an attrs attribute on {cls!r}.".format(cls=C), - ) == e.value.args + assert (f"aaaa is not an attrs attribute on {C!r}.",) == e.value.args def test_frozen(self): """ @@ -494,29 +613,14 @@ def test_frozen(self): """ @attr.s(frozen=True) - class C(object): + class C: x = attr.ib() y = attr.ib() - with pytest.deprecated_call(): - assert C(3, 2) == assoc(C(1, 2), x=3) + assert C(3, 2) == assoc(C(1, 2), x=3) - def test_warning(self): - """ - DeprecationWarning points to the correct file. - """ - - @attr.s - class C(object): - x = attr.ib() - with pytest.warns(DeprecationWarning) as wi: - assert C(2) == assoc(C(1), x=2) - - assert __file__ == wi.list[0].filename - - -class TestEvolve(object): +class TestEvolve: """ Tests for `evolve`. """ @@ -528,7 +632,7 @@ def test_empty(self, slots, frozen): """ @attr.s(slots=slots, frozen=frozen) - class C(object): + class C: pass i1 = C() @@ -593,14 +697,14 @@ def test_validator_failure(self): """ @attr.s - class C(object): + class C: a = attr.ib(validator=instance_of(int)) with pytest.raises(TypeError) as e: evolve(C(a=1), a="some string") m = e.value.args[0] - assert m.startswith("'a' must be <{type} 'int'>".format(type=TYPE)) + assert m.startswith("'a' must be ") def test_private(self): """ @@ -608,7 +712,7 @@ def test_private(self): """ @attr.s - class C(object): + class C: _a = attr.ib() assert evolve(C(1), a=2)._a == 2 @@ -625,7 +729,7 @@ def test_non_init_attrs(self): """ @attr.s - class C(object): + class C: a = attr.ib() b = attr.ib(init=False, default=0) @@ -639,11 +743,11 @@ def test_regression_attrs_classes(self): """ @attr.s - class Cls1(object): + class Cls1: param1 = attr.ib() @attr.s - class Cls2(object): + class Cls2: param2 = attr.ib() obj2a = Cls2(param2="a") @@ -663,11 +767,11 @@ def test_dicts(self): """ @attr.s - class Cls1(object): + class Cls1: param1 = attr.ib() @attr.s - class Cls2(object): + class Cls2: param2 = attr.ib() obj2a = Cls2(param2="a") @@ -678,3 +782,47 @@ class Cls2(object): assert Cls1({"foo": 42, "param2": 42}) == attr.evolve( obj1a, param1=obj2b ) + + def test_inst_kw(self): + """ + If `inst` is passed per kw argument, a warning is raised. + See #1109 + """ + + @attr.s + class C: + pass + + with pytest.warns(DeprecationWarning) as wi: + evolve(inst=C()) + + assert __file__ == wi.list[0].filename + + def test_no_inst(self): + """ + Missing inst argument raises a TypeError like Python would. + """ + with pytest.raises(TypeError, match=r"evolve\(\) missing 1"): + evolve(x=1) + + def test_too_many_pos_args(self): + """ + More than one positional argument raises a TypeError like Python would. + """ + with pytest.raises( + TypeError, + match=r"evolve\(\) takes 1 positional argument, but 2 were given", + ): + evolve(1, 2) + + def test_can_change_inst(self): + """ + If the instance is passed by positional argument, a field named `inst` + can be changed. + """ + + @attr.define + class C: + inst: int + + assert C(42) == evolve(C(23), inst=42) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_functional.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_functional.py index 9b6a27e2f4d4e..341ee50a82ae6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_functional.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_functional.py @@ -4,7 +4,6 @@ End-to-end tests. """ -from __future__ import absolute_import, division, print_function import inspect import pickle @@ -12,28 +11,24 @@ from copy import deepcopy import pytest -import six -from hypothesis import assume, given +from hypothesis import given from hypothesis.strategies import booleans import attr -from attr._compat import PY2, PY36, TYPE from attr._make import NOTHING, Attribute from attr.exceptions import FrozenInstanceError -from .strategies import optional_bool - @attr.s -class C1(object): +class C1: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() @attr.s(slots=True) -class C1Slots(object): +class C1Slots: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() @@ -42,19 +37,19 @@ class C1Slots(object): @attr.s() -class C2(object): +class C2: x = attr.ib(default=foo) y = attr.ib(default=attr.Factory(list)) @attr.s(slots=True) -class C2Slots(object): +class C2Slots: x = attr.ib(default=foo) y = attr.ib(default=attr.Factory(list)) @attr.s -class Base(object): +class Base: x = attr.ib() def meth(self): @@ -62,7 +57,7 @@ def meth(self): @attr.s(slots=True) -class BaseSlots(object): +class BaseSlots: x = attr.ib() def meth(self): @@ -80,7 +75,7 @@ class SubSlots(BaseSlots): @attr.s(frozen=True, slots=True) -class Frozen(object): +class Frozen: x = attr.ib() @@ -90,7 +85,7 @@ class SubFrozen(Frozen): @attr.s(frozen=True, slots=False) -class FrozenNoSlots(object): +class FrozenNoSlots: x = attr.ib() @@ -99,21 +94,19 @@ class Meta(type): @attr.s -@six.add_metaclass(Meta) -class WithMeta(object): +class WithMeta(metaclass=Meta): pass @attr.s(slots=True) -@six.add_metaclass(Meta) -class WithMetaSlots(object): +class WithMetaSlots(metaclass=Meta): pass FromMakeClass = attr.make_class("FromMakeClass", ["x"]) -class TestFunctional(object): +class TestFunctional: """ Functional tests. """ @@ -126,6 +119,7 @@ def test_fields(self, cls): assert ( Attribute( name="x", + alias="x", default=foo, validator=None, repr=True, @@ -138,6 +132,7 @@ def test_fields(self, cls): ), Attribute( name="y", + alias="y", default=attr.Factory(list), validator=None, repr=True, @@ -167,8 +162,7 @@ def test_validator(self, cls): # Using C1 explicitly, since slotted classes don't support this. assert ( - "'x' must be <{type} 'int'> (got '1' that is a <{type} " - "'str'>).".format(type=TYPE), + "'x' must be (got '1' that is a ).", attr.fields(C1).x, int, "1", @@ -181,7 +175,7 @@ def test_renaming(self, slots): """ @attr.s(slots=slots) - class C3(object): + class C3: _x = attr.ib() assert "C3(_x=1)" == repr(C3(x=1)) @@ -196,6 +190,7 @@ def test_programmatic(self, slots, frozen): assert ( Attribute( name="a", + alias="a", default=NOTHING, validator=None, repr=True, @@ -208,6 +203,7 @@ def test_programmatic(self, slots, frozen): ), Attribute( name="b", + alias="b", default=NOTHING, validator=None, repr=True, @@ -231,9 +227,9 @@ def test_subclassing_with_extra_attrs(self, cls): assert i.x is i.meth() is obj assert i.y == 2 if cls is Sub: - assert "Sub(x={obj}, y=2)".format(obj=obj) == repr(i) + assert f"Sub(x={obj}, y=2)" == repr(i) else: - assert "SubSlots(x={obj}, y=2)".format(obj=obj) == repr(i) + assert f"SubSlots(x={obj}, y=2)" == repr(i) @pytest.mark.parametrize("base", [Base, BaseSlots]) def test_subclass_without_extra_attrs(self, base): @@ -248,7 +244,7 @@ class Sub2(base): obj = object() i = Sub2(x=obj) assert i.x is i.meth() is obj - assert "Sub2(x={obj})".format(obj=obj) == repr(i) + assert f"Sub2(x={obj})" == repr(i) @pytest.mark.parametrize( "frozen_class", @@ -317,10 +313,7 @@ def test_pickle_object(self, cls, protocol): """ Pickle object serialization works on all kinds of attrs classes. """ - if len(attr.fields(cls)) == 2: - obj = cls(123, 456) - else: - obj = cls(123) + obj = cls(123, 456) if len(attr.fields(cls)) == 2 else cls(123) assert repr(obj) == repr(pickle.loads(pickle.dumps(obj, protocol))) @@ -351,7 +344,7 @@ def test_default_decorator(self): """ @attr.s - class C(object): + class C: x = attr.ib(default=1) y = attr.ib() @@ -361,8 +354,6 @@ def compute(self): assert C(1, 2) == C() - @pytest.mark.parametrize("slots", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) @pytest.mark.parametrize("weakref_slot", [True, False]) def test_attrib_overwrite(self, slots, frozen, weakref_slot): """ @@ -380,7 +371,7 @@ def test_dict_patch_class(self): dict-classes are never replaced. """ - class C(object): + class C: x = attr.ib() C_new = attr.s(C) @@ -395,7 +386,7 @@ def test_hash_by_id(self): """ @attr.s(hash=False) - class HashByIDBackwardCompat(object): + class HashByIDBackwardCompat: x = attr.ib() assert hash(HashByIDBackwardCompat(1)) != hash( @@ -403,13 +394,13 @@ class HashByIDBackwardCompat(object): ) @attr.s(hash=False, eq=False) - class HashByID(object): + class HashByID: x = attr.ib() assert hash(HashByID(1)) != hash(HashByID(1)) @attr.s(hash=True) - class HashByValues(object): + class HashByValues: x = attr.ib() assert hash(HashByValues(1)) == hash(HashByValues(1)) @@ -420,37 +411,35 @@ def test_handles_different_defaults(self): """ @attr.s - class Unhashable(object): + class Unhashable: pass @attr.s - class C(object): + class C: x = attr.ib(default=Unhashable()) @attr.s class D(C): pass - @pytest.mark.parametrize("slots", [True, False]) def test_hash_false_eq_false(self, slots): """ hash=False and eq=False make a class hashable by ID. """ @attr.s(hash=False, eq=False, slots=slots) - class C(object): + class C: pass assert hash(C()) != hash(C()) - @pytest.mark.parametrize("slots", [True, False]) def test_eq_false(self, slots): """ eq=False makes a class hashable by ID. """ @attr.s(eq=False, slots=slots) - class C(object): + class C: pass # Ensure both objects live long enough such that their ids/hashes @@ -468,7 +457,7 @@ def test_overwrite_base(self): """ @attr.s - class C(object): + class C: c = attr.ib(default=100) x = attr.ib(default=1) b = attr.ib(default=23) @@ -515,7 +504,7 @@ def test_frozen_slots_combo( slots=base_slots, weakref_slot=base_weakref_slot, ) - class Base(object): + class Base: a = attr.ib(converter=int if base_converter else None) @attr.s( @@ -542,7 +531,7 @@ def test_tuple_class_aliasing(self): """ @attr.s - class C(object): + class C: property = attr.ib() itemgetter = attr.ib() x = attr.ib() @@ -551,8 +540,6 @@ class C(object): assert "itemgetter" == attr.fields(C).itemgetter.name assert "x" == attr.fields(C).x.name - @pytest.mark.parametrize("slots", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) def test_auto_exc(self, slots, frozen): """ Classes with auto_exc=True have a Exception-style __str__, compare and @@ -607,8 +594,6 @@ class FooError(Exception): deepcopy(e1) deepcopy(e2) - @pytest.mark.parametrize("slots", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) def test_auto_exc_one_attrib(self, slots, frozen): """ Having one attribute works with auto_exc=True. @@ -622,76 +607,39 @@ class FooError(Exception): FooError(1) - @pytest.mark.parametrize("slots", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) def test_eq_only(self, slots, frozen): """ Classes with order=False cannot be ordered. - - Python 3 throws a TypeError, in Python2 we have to check for the - absence. """ @attr.s(eq=True, order=False, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() - if not PY2: - possible_errors = ( - "unorderable types: C() < C()", - "'<' not supported between instances of 'C' and 'C'", - "unorderable types: C < C", # old PyPy 3 - ) + possible_errors = ( + "unorderable types: C() < C()", + "'<' not supported between instances of 'C' and 'C'", + "unorderable types: C < C", # old PyPy 3 + ) - with pytest.raises(TypeError) as ei: - C(5) < C(6) + with pytest.raises(TypeError) as ei: + C(5) < C(6) - assert ei.value.args[0] in possible_errors - else: - i = C(42) - for m in ("lt", "le", "gt", "ge"): - assert None is getattr(i, "__%s__" % (m,), None) - - @given(cmp=optional_bool, eq=optional_bool, order=optional_bool) - def test_cmp_deprecated_attribute(self, cmp, eq, order): - """ - Accessing Attribute.cmp raises a deprecation warning but returns True - if cmp is True, or eq and order are *both* effectively True. - """ - # These cases are invalid and raise a ValueError. - assume(cmp is None or (eq is None and order is None)) - assume(not (eq is False and order is True)) - - if cmp is not None: - rv = cmp - elif eq is True or eq is None: - rv = order is None or order is True - elif cmp is None and eq is None and order is None: - rv = True - elif cmp is None or eq is None: - rv = False - else: - pytest.fail( - "Unexpected state: cmp=%r eq=%r order=%r" % (cmp, eq, order) - ) + assert ei.value.args[0] in possible_errors - with pytest.deprecated_call() as dc: - - @attr.s - class C(object): - x = attr.ib(cmp=cmp, eq=eq, order=order) + @pytest.mark.parametrize("cmp", [True, False]) + def test_attrib_cmp_shortcut(self, slots, cmp): + """ + Setting cmp on `attr.ib`s sets both eq and order. + """ - assert rv == attr.fields(C).x.cmp + @attr.s(slots=slots) + class C: + x = attr.ib(cmp=cmp) - (w,) = dc.list + assert cmp is attr.fields(C).x.eq + assert cmp is attr.fields(C).x.order - assert ( - "The usage of `cmp` is deprecated and will be removed on or after " - "2021-06-01. Please use `eq` and `order` instead." - == w.message.args[0] - ) - - @pytest.mark.parametrize("slots", [True, False]) def test_no_setattr_if_validate_without_validators(self, slots): """ If a class has on_setattr=attr.setters.validate (former default in NG @@ -701,11 +649,11 @@ def test_no_setattr_if_validate_without_validators(self, slots): Regression test for #816. """ - @attr.s(on_setattr=attr.setters.validate) - class C(object): + @attr.s(on_setattr=attr.setters.validate, slots=slots) + class C: x = attr.ib() - @attr.s(on_setattr=attr.setters.validate) + @attr.s(on_setattr=attr.setters.validate, slots=slots) class D(C): y = attr.ib() @@ -716,18 +664,17 @@ class D(C): assert "self.y = y" in src assert object.__setattr__ == D.__setattr__ - @pytest.mark.parametrize("slots", [True, False]) def test_no_setattr_if_convert_without_converters(self, slots): """ If a class has on_setattr=attr.setters.convert but sets no validators, don't use the (slower) setattr in __init__. """ - @attr.s(on_setattr=attr.setters.convert) - class C(object): + @attr.s(on_setattr=attr.setters.convert, slots=slots) + class C: x = attr.ib() - @attr.s(on_setattr=attr.setters.convert) + @attr.s(on_setattr=attr.setters.convert, slots=slots) class D(C): y = attr.ib() @@ -738,8 +685,6 @@ class D(C): assert "self.y = y" in src assert object.__setattr__ == D.__setattr__ - @pytest.mark.skipif(not PY36, reason="NG APIs are 3.6+") - @pytest.mark.parametrize("slots", [True, False]) def test_no_setattr_with_ng_defaults(self, slots): """ If a class has the NG default on_setattr=[convert, validate] but sets @@ -747,8 +692,8 @@ def test_no_setattr_with_ng_defaults(self, slots): __init__. """ - @attr.define - class C(object): + @attr.define(slots=slots) + class C: x = attr.ib() src = inspect.getsource(C.__init__) @@ -757,7 +702,7 @@ class C(object): assert "self.x = x" in src assert object.__setattr__ == C.__setattr__ - @attr.define + @attr.define(slots=slots) class D(C): y = attr.ib() @@ -775,7 +720,7 @@ def test_on_setattr_detect_inherited_validators(self): """ @attr.s(on_setattr=attr.setters.validate) - class C(object): + class C: x = attr.ib(validator=42) @attr.s(on_setattr=attr.setters.validate) @@ -784,7 +729,18 @@ class D(C): src = inspect.getsource(D.__init__) - assert "_setattr = _cached_setattr" in src + assert "_setattr = _cached_setattr_get(self)" in src assert "_setattr('x', x)" in src assert "_setattr('y', y)" in src assert object.__setattr__ != D.__setattr__ + + def test_unsafe_hash(self, slots): + """ + attr.s(unsafe_hash=True) makes a class hashable. + """ + + @attr.s(slots=slots, unsafe_hash=True) + class Hashable: + pass + + assert hash(Hashable()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_hooks.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_hooks.py index 92fc2dcaab5f9..9c37a98cdc019 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_hooks.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_hooks.py @@ -1,7 +1,8 @@ # SPDX-License-Identifier: MIT +from __future__ import annotations + from datetime import datetime -from typing import Dict, List import attr @@ -99,6 +100,24 @@ class C: assert attr.asdict(C(1, 2)) == {"x": 1, "new": 2} + def test_hook_override_alias(self): + """ + It is possible to set field alias via hook + """ + + def use_dataclass_names(cls, attribs): + return [a.evolve(alias=a.name) for a in attribs] + + @attr.s(auto_attribs=True, field_transformer=use_dataclass_names) + class NameCase: + public: int + _private: int + __dunder__: int + + assert NameCase(public=1, _private=2, __dunder__=3) == NameCase( + 1, 2, 3 + ) + def test_hook_with_inheritance(self): """ The hook receives all fields from base classes. @@ -151,14 +170,14 @@ def hook(inst, a, v): @attr.dataclass class Child: x: datetime - y: List[datetime] + y: list[datetime] @attr.dataclass class Parent: a: Child - b: List[Child] - c: Dict[str, Child] - d: Dict[str, datetime] + b: list[Child] + c: dict[str, Child] + d: dict[str, datetime] inst = Parent( a=Child(1, [datetime(2020, 7, 1)]), @@ -192,8 +211,8 @@ class Child: @attr.dataclass class Parent: a: Child - b: List[Child] - c: Dict[str, Child] + b: list[Child] + c: dict[str, Child] inst = Parent(a=Child(1), b=[Child(2)], c={"spam": Child(3)}) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_import.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_import.py index 423124319c9b1..9e90a5c11e685 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_import.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_import.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: MIT -class TestImportStar(object): +class TestImportStar: def test_from_attr_import_star(self): """ import * from attr diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_init_subclass.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_init_subclass.py index 863e794377d87..cff4e948bcbe8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_init_subclass.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_init_subclass.py @@ -1,17 +1,12 @@ # SPDX-License-Identifier: MIT """ -Tests for `__init_subclass__` related tests. - -Python 3.6+ only. +Tests for `__init_subclass__` related functionality. """ -import pytest - import attr -@pytest.mark.parametrize("slots", [True, False]) def test_init_subclass_vanilla(slots): """ `super().__init_subclass__` can be used if the subclass is not an attrs @@ -46,3 +41,26 @@ class Attrs(Base, param="foo"): pass assert "foo" == Attrs().param + + +def test_init_subclass_slots_workaround(): + """ + `__init_subclass__` works with modern APIs if care is taken around classes + existing twice. + """ + subs = {} + + @attr.define + class Base: + def __init_subclass__(cls): + subs[cls.__qualname__] = cls + + @attr.define + class Sub1(Base): + x: int + + @attr.define + class Sub2(Base): + y: int + + assert (Sub1, Sub2) == tuple(subs.values()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_make.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_make.py index 729d3a71f0692..19f7a4cd412c9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_make.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_make.py @@ -4,7 +4,6 @@ Tests for `attr._make`. """ -from __future__ import absolute_import, division, print_function import copy import functools @@ -14,6 +13,7 @@ import sys from operator import attrgetter +from typing import Generic, TypeVar import pytest @@ -23,7 +23,7 @@ import attr from attr import _config -from attr._compat import PY2, PY310, ordered_dict +from attr._compat import PY310 from attr._make import ( Attribute, Factory, @@ -41,11 +41,7 @@ make_class, validate, ) -from attr.exceptions import ( - DefaultAlreadySetError, - NotAnAttrsClassError, - PythonTooOldError, -) +from attr.exceptions import DefaultAlreadySetError, NotAnAttrsClassError from .strategies import ( gen_attr_names, @@ -62,7 +58,20 @@ attrs_st = simple_attrs.map(lambda c: Attribute.from_counting_attr("name", c)) -class TestCountingAttr(object): +@pytest.fixture(name="with_and_without_validation", params=[True, False]) +def _with_and_without_validation(request): + """ + Run tests with and without validation enabled. + """ + attr.validators.set_disabled(request.param) + + try: + yield + finally: + attr.validators.set_disabled(False) + + +class TestCountingAttr: """ Tests for `attr`. """ @@ -151,7 +160,7 @@ def f(self): def make_tc(): - class TransformC(object): + class TransformC: z = attr.ib() y = attr.ib() x = attr.ib() @@ -160,7 +169,7 @@ class TransformC(object): return TransformC -class TestTransformAttrs(object): +class TestTransformAttrs: """ Tests for `_transform_attrs`. """ @@ -189,7 +198,7 @@ def test_empty(self): """ @attr.s - class C(object): + class C: pass assert _Attributes(((), [], {})) == _transform_attrs( @@ -215,7 +224,7 @@ def test_conflicting_defaults(self): mandatory attributes. """ - class C(object): + class C: x = attr.ib(default=None) y = attr.ib() @@ -228,20 +237,20 @@ class C(object): "eq=True, eq_key=None, order=True, order_key=None, " "hash=None, init=True, " "metadata=mappingproxy({}), type=None, converter=None, " - "kw_only=False, inherited=False, on_setattr=None)", + "kw_only=False, inherited=False, on_setattr=None, alias=None)", ) == e.value.args def test_kw_only(self): """ Converts all attributes, including base class' attributes, if `kw_only` is provided. Therefore, `kw_only` allows attributes with defaults to - preceed mandatory attributes. + precede mandatory attributes. Updates in the subclass *don't* affect the base class attributes. """ @attr.s - class B(object): + class B: b = attr.ib() for b_a in B.__attrs_attrs__: @@ -269,7 +278,7 @@ def test_these(self): If these is passed, use it and ignore body and base classes. """ - class Base(object): + class Base: z = attr.ib() class C(Base): @@ -288,7 +297,7 @@ def test_these_leave_body(self): """ @attr.s(init=False, these={"x": attr.ib()}) - class C(object): + class C: x = 5 assert 5 == C().x @@ -302,21 +311,21 @@ def test_these_ordered(self): b = attr.ib(default=2) a = attr.ib(default=1) - @attr.s(these=ordered_dict([("a", a), ("b", b)])) - class C(object): + @attr.s(these={"a": a, "b": b}) + class C: pass assert "C(a=1, b=2)" == repr(C()) def test_multiple_inheritance_old(self): """ - Old multiple inheritance attributre collection behavior is retained. + Old multiple inheritance attribute collection behavior is retained. See #285 """ @attr.s - class A(object): + class A: a1 = attr.ib(default="a1") a2 = attr.ib(default="a2") @@ -351,7 +360,7 @@ def test_overwrite_proper_mro(self): """ @attr.s(collect_by_mro=True) - class C(object): + class C: x = attr.ib(default=1) @attr.s(collect_by_mro=True) @@ -368,7 +377,7 @@ def test_multiple_inheritance_proper_mro(self): """ @attr.s - class A(object): + class A: a1 = attr.ib(default="a1") a2 = attr.ib(default="a2") @@ -405,8 +414,7 @@ def test_mro(self): """ @attr.s(collect_by_mro=True) - class A(object): - + class A: x = attr.ib(10) def xx(self): @@ -437,7 +445,7 @@ def test_inherited(self): """ @attr.s - class A(object): + class A: a = attr.ib() @attr.s @@ -461,31 +469,18 @@ class C(B): assert False is f(C).c.inherited -class TestAttributes(object): +class TestAttributes: """ Tests for the `attrs`/`attr.s` class decorator. """ - @pytest.mark.skipif(not PY2, reason="No old-style classes in Py3") - def test_catches_old_style(self): - """ - Raises TypeError on old-style classes. - """ - with pytest.raises(TypeError) as e: - - @attr.s - class C: - pass - - assert ("attrs only works with new-style classes.",) == e.value.args - def test_sets_attrs(self): """ Sets the `__attrs_attrs__` class attribute with a list of `Attribute`s. """ @attr.s - class C(object): + class C: x = attr.ib() assert "x" == C.__attrs_attrs__[0].name @@ -497,7 +492,7 @@ def test_empty(self): """ @attr.s - class C3(object): + class C3: pass assert "C3()" == repr(C3()) @@ -523,7 +518,7 @@ def test_adds_all_by_default(self, method_name): # overwritten afterwards. sentinel = object() - class C(object): + class C: x = attr.ib() setattr(C, method_name, sentinel) @@ -536,7 +531,7 @@ class C(object): assert meth is None @pytest.mark.parametrize( - "arg_name, method_name", + ("arg_name", "method_name"), [ ("repr", "__repr__"), ("eq", "__eq__"), @@ -564,7 +559,7 @@ def test_respects_add_arguments(self, arg_name, method_name): if arg_name == "eq": am_args["order"] = False - class C(object): + class C: x = attr.ib() setattr(C, method_name, sentinel) @@ -580,23 +575,22 @@ def test_respects_init_attrs_init(self, init): Otherwise, it does not. """ - class C(object): + class C: x = attr.ib() C = attr.s(init=init)(C) assert hasattr(C, "__attrs_init__") != init - @pytest.mark.skipif(PY2, reason="__qualname__ is PY3-only.") @given(slots_outer=booleans(), slots_inner=booleans()) def test_repr_qualname(self, slots_outer, slots_inner): """ - On Python 3, the name in repr is the __qualname__. + The name in repr is the __qualname__. """ @attr.s(slots=slots_outer) - class C(object): + class C: @attr.s(slots=slots_inner) - class D(object): + class D: pass assert "C.D()" == repr(C.D()) @@ -609,38 +603,36 @@ def test_repr_fake_qualname(self, slots_outer, slots_inner): """ @attr.s(slots=slots_outer) - class C(object): + class C: @attr.s(repr_ns="C", slots=slots_inner) - class D(object): + class D: pass assert "C.D()" == repr(C.D()) - @pytest.mark.skipif(PY2, reason="__qualname__ is PY3-only.") @given(slots_outer=booleans(), slots_inner=booleans()) def test_name_not_overridden(self, slots_outer, slots_inner): """ - On Python 3, __name__ is different from __qualname__. + __name__ is different from __qualname__. """ @attr.s(slots=slots_outer) - class C(object): + class C: @attr.s(slots=slots_inner) - class D(object): + class D: pass assert C.D.__name__ == "D" assert C.D.__qualname__ == C.__qualname__ + ".D" - @pytest.mark.parametrize("with_validation", [True, False]) - def test_pre_init(self, with_validation, monkeypatch): + @pytest.mark.usefixtures("with_and_without_validation") + def test_pre_init(self): """ Verify that __attrs_pre_init__ gets called if defined. """ - monkeypatch.setattr(_config, "_run_validators", with_validation) @attr.s - class C(object): + class C: def __attrs_pre_init__(self2): self2.z = 30 @@ -648,15 +640,68 @@ def __attrs_pre_init__(self2): assert 30 == getattr(c, "z", None) - @pytest.mark.parametrize("with_validation", [True, False]) - def test_post_init(self, with_validation, monkeypatch): + @pytest.mark.usefixtures("with_and_without_validation") + def test_pre_init_args(self): + """ + Verify that __attrs_pre_init__ gets called with extra args if defined. + """ + + @attr.s + class C: + x = attr.ib() + + def __attrs_pre_init__(self2, x): + self2.z = x + 1 + + c = C(x=10) + + assert 11 == getattr(c, "z", None) + + @pytest.mark.usefixtures("with_and_without_validation") + def test_pre_init_kwargs(self): + """ + Verify that __attrs_pre_init__ gets called with extra args and kwargs + if defined. + """ + + @attr.s + class C: + x = attr.ib() + y = attr.field(kw_only=True) + + def __attrs_pre_init__(self2, x, y): + self2.z = x + y + 1 + + c = C(10, y=11) + + assert 22 == getattr(c, "z", None) + + @pytest.mark.usefixtures("with_and_without_validation") + def test_pre_init_kwargs_only(self): + """ + Verify that __attrs_pre_init__ gets called with extra kwargs only if + defined. + """ + + @attr.s + class C: + y = attr.field(kw_only=True) + + def __attrs_pre_init__(self2, y): + self2.z = y + 1 + + c = C(y=11) + + assert 12 == getattr(c, "z", None) + + @pytest.mark.usefixtures("with_and_without_validation") + def test_post_init(self): """ Verify that __attrs_post_init__ gets called if defined. """ - monkeypatch.setattr(_config, "_run_validators", with_validation) @attr.s - class C(object): + class C: x = attr.ib() y = attr.ib() @@ -667,15 +712,14 @@ def __attrs_post_init__(self2): assert 30 == getattr(c, "z", None) - @pytest.mark.parametrize("with_validation", [True, False]) - def test_pre_post_init_order(self, with_validation, monkeypatch): + @pytest.mark.usefixtures("with_and_without_validation") + def test_pre_post_init_order(self): """ Verify that __attrs_post_init__ gets called if defined. """ - monkeypatch.setattr(_config, "_run_validators", with_validation) @attr.s - class C(object): + class C: x = attr.ib() def __attrs_pre_init__(self2): @@ -694,7 +738,7 @@ def test_types(self): """ @attr.s - class C(object): + class C: x = attr.ib(type=int) y = attr.ib(type=str) z = attr.ib() @@ -703,14 +747,13 @@ class C(object): assert str is fields(C).y.type assert None is fields(C).z.type - @pytest.mark.parametrize("slots", [True, False]) def test_clean_class(self, slots): """ Attribute definitions do not appear on the class body after @attr.s. """ @attr.s(slots=slots) - class C(object): + class C: x = attr.ib() x = getattr(C, "x", None) @@ -723,7 +766,7 @@ def test_factory_sugar(self): """ @attr.s - class C(object): + class C: x = attr.ib(factory=list) assert Factory(list) == attr.fields(C).x.default @@ -735,7 +778,7 @@ def test_sugar_factory_mutex(self): with pytest.raises(ValueError, match="mutually exclusive"): @attr.s - class C(object): + class C: x = attr.ib(factory=list, default=Factory(list)) def test_sugar_callable(self): @@ -746,7 +789,7 @@ def test_sugar_callable(self): with pytest.raises(ValueError, match="must be a callable"): @attr.s - class C(object): + class C: x = attr.ib(factory=Factory(list)) def test_inherited_does_not_affect_hashing_and_equality(self): @@ -756,7 +799,7 @@ def test_inherited_does_not_affect_hashing_and_equality(self): """ @attr.s - class BaseClass(object): + class BaseClass: x = attr.ib() @attr.s @@ -770,7 +813,7 @@ class SubClass(BaseClass): assert hash(ba) == hash(sa) -class TestKeywordOnlyAttributes(object): +class TestKeywordOnlyAttributes: """ Tests for keyword-only attributes. """ @@ -781,7 +824,7 @@ def test_adds_keyword_only_arguments(self): """ @attr.s - class C(object): + class C: a = attr.ib() b = attr.ib(default=2, kw_only=True) c = attr.ib(kw_only=True) @@ -800,7 +843,7 @@ def test_ignores_kw_only_when_init_is_false(self): """ @attr.s - class C(object): + class C: x = attr.ib(init=False, default=0, kw_only=True) y = attr.ib() @@ -816,20 +859,15 @@ def test_keyword_only_attributes_presence(self): """ @attr.s - class C(object): + class C: x = attr.ib(kw_only=True) with pytest.raises(TypeError) as e: C() - if PY2: - assert ( - "missing required keyword-only argument: 'x'" - ) in e.value.args[0] - else: - assert ( - "missing 1 required keyword-only argument: 'x'" - ) in e.value.args[0] + assert ( + "missing 1 required keyword-only argument: 'x'" + ) in e.value.args[0] def test_keyword_only_attributes_unexpected(self): """ @@ -837,7 +875,7 @@ def test_keyword_only_attributes_unexpected(self): """ @attr.s - class C(object): + class C: x = attr.ib(kw_only=True) with pytest.raises(TypeError) as e: @@ -854,7 +892,7 @@ def test_keyword_only_attributes_can_come_in_any_order(self): """ @attr.s - class C(object): + class C: a = attr.ib(kw_only=True) b = attr.ib(kw_only=True, default="b") c = attr.ib(kw_only=True) @@ -883,7 +921,7 @@ def test_keyword_only_attributes_allow_subclassing(self): """ @attr.s - class Base(object): + class Base: x = attr.ib(default=0) @attr.s @@ -902,7 +940,7 @@ def test_keyword_only_class_level(self): """ @attr.s(kw_only=True) - class C(object): + class C: x = attr.ib() y = attr.ib(kw_only=True) @@ -921,7 +959,7 @@ def test_keyword_only_class_level_subclassing(self): """ @attr.s - class Base(object): + class Base: x = attr.ib(default=0) @attr.s(kw_only=True) @@ -944,7 +982,7 @@ def test_init_false_attribute_after_keyword_attribute(self): """ @attr.s - class KwArgBeforeInitFalse(object): + class KwArgBeforeInitFalse: kwarg = attr.ib(kw_only=True) non_init_function_default = attr.ib(init=False) non_init_keyword_default = attr.ib( @@ -972,7 +1010,7 @@ def test_init_false_attribute_after_keyword_attribute_with_inheritance( """ @attr.s - class KwArgBeforeInitFalseParent(object): + class KwArgBeforeInitFalseParent: kwarg = attr.ib(kw_only=True) @attr.s @@ -993,34 +1031,14 @@ def _init_to_init(self): assert c.non_init_keyword_default == "default-by-keyword" -@pytest.mark.skipif(not PY2, reason="PY2-specific keyword-only error behavior") -class TestKeywordOnlyAttributesOnPy2(object): - """ - Tests for keyword-only attribute behavior on py2. - """ - - def test_no_init(self): - """ - Keyworld-only is a no-op, not any error, if ``init=false``. - """ - - @attr.s(kw_only=True, init=False) - class ClassLevel(object): - a = attr.ib() - - @attr.s(init=False) - class AttrLevel(object): - a = attr.ib(kw_only=True) - - @attr.s -class GC(object): +class GC: @attr.s - class D(object): + class D: pass -class TestMakeClass(object): +class TestMakeClass: """ Tests for `make_class`. """ @@ -1033,7 +1051,7 @@ def test_simple(self, ls): C1 = make_class("C1", ls(["a", "b"])) @attr.s - class C2(object): + class C2: a = attr.ib() b = attr.ib() @@ -1048,7 +1066,7 @@ def test_dict(self): ) @attr.s - class C2(object): + class C2: a = attr.ib(default=42) b = attr.ib(default=None) @@ -1076,7 +1094,7 @@ def test_bases(self): Parameter bases default to (object,) and subclasses correctly """ - class D(object): + class D: pass cls = make_class("C", {}) @@ -1088,7 +1106,18 @@ class D(object): assert D in cls.__mro__ assert isinstance(cls(), D) - @pytest.mark.parametrize("slots", [True, False]) + def test_additional_class_body(self): + """ + Additional class_body is added to newly created class. + """ + + def echo_func(cls, *args): + return args + + cls = make_class("C", {}, class_body={"echo": classmethod(echo_func)}) + + assert ("a", "b") == cls.echo("a", "b") + def test_clean_class(self, slots): """ Attribute definitions do not appear on the class body. @@ -1116,11 +1145,10 @@ def test_make_class_ordered(self): b = attr.ib(default=2) a = attr.ib(default=1) - C = attr.make_class("C", ordered_dict([("a", a), ("b", b)])) + C = attr.make_class("C", {"a": a, "b": b}) assert "C(a=1, b=2)" == repr(C()) - @pytest.mark.skipif(PY2, reason="Python 3-only") def test_generic_dynamic_class(self): """ make_class can create generic dynamic classes. @@ -1137,7 +1165,7 @@ def test_generic_dynamic_class(self): attr.make_class("test", {"id": attr.ib(type=str)}, (MyParent[int],)) -class TestFields(object): +class TestFields: """ Tests for `fields`. """ @@ -1154,13 +1182,29 @@ def test_instance(self, C): def test_handler_non_attrs_class(self): """ - Raises `ValueError` if passed a non-``attrs`` instance. + Raises `ValueError` if passed a non-*attrs* instance. """ with pytest.raises(NotAnAttrsClassError) as e: fields(object) assert ( - "{o!r} is not an attrs-decorated class.".format(o=object) + f"{object!r} is not an attrs-decorated class." + ) == e.value.args[0] + + def test_handler_non_attrs_generic_class(self): + """ + Raises `ValueError` if passed a non-*attrs* generic class. + """ + T = TypeVar("T") + + class B(Generic[T]): + pass + + with pytest.raises(NotAnAttrsClassError) as e: + fields(B[str]) + + assert ( + f"{B[str]!r} is not an attrs-decorated class." ) == e.value.args[0] @given(simple_classes()) @@ -1178,8 +1222,26 @@ def test_fields_properties(self, C): for attribute in fields(C): assert getattr(fields(C), attribute.name) is attribute + def test_generics(self): + """ + Fields work with generic classes. + """ + T = TypeVar("T") + + @attr.define + class A(Generic[T]): + a: T + + assert len(fields(A)) == 1 + assert fields(A).a.name == "a" + assert fields(A).a.default is attr.NOTHING + + assert len(fields(A[str])) == 1 + assert fields(A[str]).a.name == "a" + assert fields(A[str]).a.default is attr.NOTHING -class TestFieldsDict(object): + +class TestFieldsDict: """ Tests for `fields_dict`. """ @@ -1196,13 +1258,13 @@ def test_instance(self, C): def test_handler_non_attrs_class(self): """ - Raises `ValueError` if passed a non-``attrs`` instance. + Raises `ValueError` if passed a non-*attrs* instance. """ with pytest.raises(NotAnAttrsClassError) as e: fields_dict(object) assert ( - "{o!r} is not an attrs-decorated class.".format(o=object) + f"{object!r} is not an attrs-decorated class." ) == e.value.args[0] @given(simple_classes()) @@ -1212,12 +1274,12 @@ def test_fields_dict(self, C): """ d = fields_dict(C) - assert isinstance(d, ordered_dict) + assert isinstance(d, dict) assert list(fields(C)) == list(d.values()) - assert [a.name for a in fields(C)] == [field_name for field_name in d] + assert [a.name for a in fields(C)] == list(d) -class TestConverter(object): +class TestConverter: """ Tests for attribute conversion. """ @@ -1260,19 +1322,14 @@ def test_converter_factory_property(self, val, init): """ C = make_class( "C", - ordered_dict( - [ - ("y", attr.ib()), - ( - "x", - attr.ib( - init=init, - default=Factory(lambda: val), - converter=lambda v: v + 1, - ), - ), - ] - ), + { + "y": attr.ib(), + "x": attr.ib( + init=init, + default=Factory(lambda: val), + converter=lambda v: v + 1, + ), + }, ) c = C(2) @@ -1330,7 +1387,7 @@ def test_frozen(self): C("1") -class TestValidate(object): +class TestValidate: """ Tests for `validate`. """ @@ -1424,11 +1481,11 @@ def test_multiple_empty(self): # Hypothesis seems to cache values, so the lists of attributes come out # unsorted. sorted_lists_of_attrs = list_of_attrs.map( - lambda l: sorted(l, key=attrgetter("counter")) + lambda ln: sorted(ln, key=attrgetter("counter")) ) -class TestMetadata(object): +class TestMetadata: """ Tests for metadata handling. """ @@ -1471,8 +1528,8 @@ def test_metadata_immutability(self, C, string): a.metadata.setdefault(string, string) for k in a.metadata: - # For some reason, Python 3's MappingProxyType throws an - # IndexError for deletes on a large integer key. + # For some reason, MappingProxyType throws an IndexError for + # deletes on a large integer key. with pytest.raises((TypeError, IndexError)): del a.metadata[k] with pytest.raises(AttributeError): @@ -1523,7 +1580,7 @@ def test_metadata(self): assert md is a.metadata -class TestClassBuilder(object): +class TestClassBuilder: """ Tests for `_ClassBuilder`. """ @@ -1545,7 +1602,7 @@ def test_repr(self): repr of builder itself makes sense. """ - class C(object): + class C: pass b = _ClassBuilder( @@ -1572,7 +1629,7 @@ def test_returns_self(self): All methods return the builder for chaining. """ - class C(object): + class C: x = attr.ib() b = _ClassBuilder( @@ -1627,12 +1684,12 @@ def test_attaches_meta_dunders(self, meth_name): """ @attr.s(hash=True, str=True) - class C(object): + class C: def organic(self): pass @attr.s(hash=True, str=True) - class D(object): + class D: pass meth_C = getattr(C, meth_name) @@ -1640,11 +1697,10 @@ class D(object): assert meth_name == meth_C.__name__ == meth_D.__name__ assert C.organic.__module__ == meth_C.__module__ == meth_D.__module__ - if not PY2: - # This is assertion that would fail if a single __ne__ instance - # was reused across multiple _make_eq calls. - organic_prefix = C.organic.__qualname__.rsplit(".", 1)[0] - assert organic_prefix + "." + meth_name == meth_C.__qualname__ + # This is assertion that would fail if a single __ne__ instance + # was reused across multiple _make_eq calls. + organic_prefix = C.organic.__qualname__.rsplit(".", 1)[0] + assert organic_prefix + "." + meth_name == meth_C.__qualname__ def test_handles_missing_meta_on_class(self): """ @@ -1652,7 +1708,7 @@ def test_handles_missing_meta_on_class(self): either. """ - class C(object): + class C: pass b = _ClassBuilder( @@ -1691,7 +1747,7 @@ def test_weakref_setstate(self): """ @attr.s(slots=True) - class C(object): + class C: __weakref__ = attr.ib( init=False, hash=False, repr=False, eq=False, order=False ) @@ -1705,7 +1761,7 @@ def test_no_references_to_original(self): """ @attr.s(slots=True) - class C(object): + class C: pass @attr.s(slots=True) @@ -1748,7 +1804,7 @@ def test_copy(self, kwargs): """ @attr.s(eq=True, **kwargs) - class C(object): + class C: x = attr.ib() a = C(1) @@ -1763,7 +1819,7 @@ def test_copy_custom_setstate(self, kwargs): """ @attr.s(eq=True, **kwargs) - class C(object): + class C: x = attr.ib() def __getstate__(self): @@ -1779,6 +1835,107 @@ def __setstate__(self, state): assert actual == expected +class TestInitAlias: + """ + Tests for Attribute alias handling. + """ + + def test_default_and_specify(self): + """ + alias is present on the Attributes returned from attr.fields. + + If left unspecified, it defaults to standard private-attribute + handling. If specified, it passes through the explicit alias. + """ + + # alias is None by default on _CountingAttr + default_counting = attr.ib() + assert default_counting.alias is None + + override_counting = attr.ib(alias="specified") + assert override_counting.alias == "specified" + + @attr.s + class Cases: + public_default = attr.ib() + _private_default = attr.ib() + __dunder_default__ = attr.ib() + + public_override = attr.ib(alias="public") + _private_override = attr.ib(alias="_private") + __dunder_override__ = attr.ib(alias="__dunder__") + + cases = attr.fields_dict(Cases) + + # Default applies private-name mangling logic + assert cases["public_default"].name == "public_default" + assert cases["public_default"].alias == "public_default" + + assert cases["_private_default"].name == "_private_default" + assert cases["_private_default"].alias == "private_default" + + assert cases["__dunder_default__"].name == "__dunder_default__" + assert cases["__dunder_default__"].alias == "dunder_default__" + + # Override is passed through + assert cases["public_override"].name == "public_override" + assert cases["public_override"].alias == "public" + + assert cases["_private_override"].name == "_private_override" + assert cases["_private_override"].alias == "_private" + + assert cases["__dunder_override__"].name == "__dunder_override__" + assert cases["__dunder_override__"].alias == "__dunder__" + + # And aliases are applied to the __init__ signature + example = Cases( + public_default=1, + private_default=2, + dunder_default__=3, + public=4, + _private=5, + __dunder__=6, + ) + + assert example.public_default == 1 + assert example._private_default == 2 + assert example.__dunder_default__ == 3 + assert example.public_override == 4 + assert example._private_override == 5 + assert example.__dunder_override__ == 6 + + def test_evolve(self): + """ + attr.evolve uses Attribute.alias to determine parameter names. + """ + + @attr.s + class EvolveCase: + _override = attr.ib(alias="_override") + __mangled = attr.ib() + __dunder__ = attr.ib() + + org = EvolveCase(1, 2, 3) + + # Previous behavior of evolve as broken for double-underscore + # passthrough, and would raise here due to mis-mapping the __dunder__ + # alias + assert attr.evolve(org) == org + + # evolve uses the alias to match __init__ signature + assert attr.evolve( + org, + _override=0, + ) == EvolveCase(0, 2, 3) + + # and properly passes through dunders and mangles + assert attr.evolve( + org, + EvolveCase__mangled=4, + dunder__=5, + ) == EvolveCase(1, 4, 5) + + class TestMakeOrder: """ Tests for _make_order(). @@ -1788,11 +1945,11 @@ def test_subclasses_cannot_be_compared(self): """ Calling comparison methods on subclasses raises a TypeError. - We use the actual operation so we get an error raised on Python 3. + We use the actual operation so we get an error raised. """ @attr.s - class A(object): + class A: a = attr.ib() @attr.s @@ -1815,21 +1972,20 @@ class B(A): == a.__ge__(b) ) - if not PY2: - with pytest.raises(TypeError): - a <= b + with pytest.raises(TypeError): + a <= b - with pytest.raises(TypeError): - a >= b + with pytest.raises(TypeError): + a >= b - with pytest.raises(TypeError): - a < b + with pytest.raises(TypeError): + a < b - with pytest.raises(TypeError): - a > b + with pytest.raises(TypeError): + a > b -class TestDetermineAttrsEqOrder(object): +class TestDetermineAttrsEqOrder: def test_default(self): """ If all are set to None, set both eq and order to the passed default. @@ -1865,7 +2021,7 @@ def test_mix(self, cmp, eq, order): _determine_attrs_eq_order(cmp, eq, order, True) -class TestDetermineAttribEqOrder(object): +class TestDetermineAttribEqOrder: def test_default(self): """ If all are set to None, set both eq and order to the passed default. @@ -1953,37 +2109,25 @@ def test_docs(self, meth_name): """ @attr.s - class A(object): + class A: pass if hasattr(A, "__qualname__"): method = getattr(A, meth_name) - expected = "Method generated by attrs for class {}.".format( - A.__qualname__ - ) + expected = f"Method generated by attrs for class {A.__qualname__}." assert expected == method.__doc__ -@pytest.mark.skipif(not PY2, reason="Needs to be only caught on Python 2.") -def test_auto_detect_raises_on_py2(): - """ - Trying to pass auto_detect=True to attr.s raises PythonTooOldError. - """ - with pytest.raises(PythonTooOldError): - attr.s(auto_detect=True) - - -class BareC(object): +class BareC: pass -class BareSlottedC(object): +class BareSlottedC: __slots__ = () -@pytest.mark.skipif(PY2, reason="Auto-detection is Python 3-only.") class TestAutoDetect: - @pytest.mark.parametrize("C", (BareC, BareSlottedC)) + @pytest.mark.parametrize("C", [BareC, BareSlottedC]) def test_determine_detects_non_presence_correctly(self, C): """ On an empty class, nothing should be detected. @@ -2001,8 +2145,6 @@ def test_determine_detects_non_presence_correctly(self, C): C, None, True, ("__le__", "__lt__", "__ge__", "__gt__") ) - @pytest.mark.parametrize("slots", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) def test_make_all_by_default(self, slots, frozen): """ If nothing is there to be detected, imply init=True, repr=True, @@ -2010,7 +2152,7 @@ def test_make_all_by_default(self, slots, frozen): """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() i = C(1) @@ -2025,15 +2167,13 @@ class C(object): assert i.__ge__ is not o.__ge__ assert i.__gt__ is not o.__gt__ - @pytest.mark.parametrize("slots", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) def test_detect_auto_init(self, slots, frozen): """ If auto_detect=True and an __init__ exists, don't write one. """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class CI(object): + class CI: x = attr.ib() def __init__(self): @@ -2041,15 +2181,13 @@ def __init__(self): assert 42 == CI().x - @pytest.mark.parametrize("slots", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) def test_detect_auto_repr(self, slots, frozen): """ If auto_detect=True and an __repr__ exists, don't write one. """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __repr__(self): @@ -2057,15 +2195,32 @@ def __repr__(self): assert "hi" == repr(C(42)) - @pytest.mark.parametrize("slots", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) + def test_hash_uses_eq(self, slots, frozen): + """ + If eq is passed in, then __hash__ should use the eq callable + to generate the hash code. + """ + + @attr.s(slots=slots, frozen=frozen, hash=True) + class C: + x = attr.ib(eq=str) + + @attr.s(slots=slots, frozen=frozen, hash=True) + class D: + x = attr.ib() + + # These hashes should be the same because 1 is turned into + # string before hashing. + assert hash(C("1")) == hash(C(1)) + assert hash(D("1")) != hash(D(1)) + def test_detect_auto_hash(self, slots, frozen): """ If auto_detect=True and an __hash__ exists, don't write one. """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __hash__(self): @@ -2073,15 +2228,13 @@ def __hash__(self): assert 0xC0FFEE == hash(C(42)) - @pytest.mark.parametrize("slots", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) def test_detect_auto_eq(self, slots, frozen): """ If auto_detect=True and an __eq__ or an __ne__, exist, don't write one. """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __eq__(self, o): @@ -2091,7 +2244,7 @@ def __eq__(self, o): C(1) == C(1) @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class D(object): + class D: x = attr.ib() def __ne__(self, o): @@ -2100,8 +2253,6 @@ def __ne__(self, o): with pytest.raises(ValueError, match="worked"): D(1) != D(1) - @pytest.mark.parametrize("slots", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) def test_detect_auto_order(self, slots, frozen): """ If auto_detect=True and an __ge__, __gt__, __le__, or and __lt__ exist, @@ -2127,19 +2278,19 @@ def assert_none_set(cls, ex): assert_not_set(cls, ex, "__" + m + "__") @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class LE(object): + class LE: __le__ = 42 @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class LT(object): + class LT: __lt__ = 42 @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class GE(object): + class GE: __ge__ = 42 @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class GT(object): + class GT: __gt__ = 42 assert_none_set(LE, "__le__") @@ -2147,15 +2298,13 @@ class GT(object): assert_none_set(GE, "__ge__") assert_none_set(GT, "__gt__") - @pytest.mark.parametrize("slots", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) def test_override_init(self, slots, frozen): """ If init=True is passed, ignore __init__. """ @attr.s(init=True, auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __init__(self): @@ -2163,15 +2312,13 @@ def __init__(self): assert C(1) == C(1) - @pytest.mark.parametrize("slots", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) def test_override_repr(self, slots, frozen): """ If repr=True is passed, ignore __repr__. """ @attr.s(repr=True, auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __repr__(self): @@ -2179,15 +2326,13 @@ def __repr__(self): assert "C(x=1)" == repr(C(1)) - @pytest.mark.parametrize("slots", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) def test_override_hash(self, slots, frozen): """ If hash=True is passed, ignore __hash__. """ @attr.s(hash=True, auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __hash__(self): @@ -2195,15 +2340,13 @@ def __hash__(self): assert hash(C(1)) - @pytest.mark.parametrize("slots", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) def test_override_eq(self, slots, frozen): """ If eq=True is passed, ignore __eq__ and __ne__. """ @attr.s(eq=True, auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __eq__(self, o): @@ -2214,10 +2357,8 @@ def __ne__(self, o): assert C(1) == C(1) - @pytest.mark.parametrize("slots", [True, False]) - @pytest.mark.parametrize("frozen", [True, False]) @pytest.mark.parametrize( - "eq,order,cmp", + ("eq", "order", "cmp"), [ (True, None, None), (True, True, None), @@ -2243,7 +2384,7 @@ def meth(self, o): slots=slots, frozen=frozen, ) - class C(object): + class C: x = attr.ib() __le__ = __lt__ = __gt__ = __ge__ = meth @@ -2252,7 +2393,6 @@ class C(object): assert C(2) > C(1) assert C(2) >= C(1) - @pytest.mark.parametrize("slots", [True, False]) @pytest.mark.parametrize("first", [True, False]) def test_total_ordering(self, slots, first): """ @@ -2262,7 +2402,7 @@ def test_total_ordering(self, slots, first): Ensure the order doesn't matter. """ - class C(object): + class C: x = attr.ib() own_eq_called = attr.ib(default=False) own_le_called = attr.ib(default=False) @@ -2301,21 +2441,22 @@ def __le__(self, o): assert c1.own_eq_called - @pytest.mark.parametrize("slots", [True, False]) def test_detects_setstate_getstate(self, slots): """ __getstate__ and __setstate__ are not overwritten if either is present. """ @attr.s(slots=slots, auto_detect=True) - class C(object): + class C: def __getstate__(self): return ("hi",) - assert None is getattr(C(), "__setstate__", None) + assert getattr(object, "__setstate__", None) is getattr( + C, "__setstate__", None + ) @attr.s(slots=slots, auto_detect=True) - class C(object): + class C: called = attr.ib(False) def __setstate__(self, state): @@ -2328,7 +2469,9 @@ def __setstate__(self, state): i.__setstate__(()) assert True is i.called - assert None is getattr(C(), "__getstate__", None) + assert getattr(object, "__getstate__", None) is getattr( + C, "__getstate__", None + ) @pytest.mark.skipif(PY310, reason="Pre-3.10 only.") def test_match_args_pre_310(self): @@ -2337,14 +2480,14 @@ def test_match_args_pre_310(self): """ @attr.s - class C(object): + class C: a = attr.ib() assert None is getattr(C, "__match_args__", None) @pytest.mark.skipif(not PY310, reason="Structural pattern matching is 3.10+") -class TestMatchArgs(object): +class TestMatchArgs: """ Tests for match_args and __match_args__ generation. """ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_mypy.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_mypy.yml index ca17b0a662a91..0d0757233b4cf 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_mypy.yml +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_mypy.yml @@ -1,26 +1,26 @@ - case: attr_s_with_type_argument parametrized: - - val: 'a = attr.ib(type=int)' - - val: 'a: int = attr.ib()' + - val: "a = attr.ib(type=int)" + - val: "a: int = attr.ib()" main: | import attr @attr.s class C: {{ val }} - C() # E: Missing positional argument "a" in call to "C" + C() # E: Missing positional argument "a" in call to "C" [call-arg] C(1) C(a=1) - C(a="hi") # E: Argument "a" to "C" has incompatible type "str"; expected "int" + C(a="hi") # E: Argument "a" to "C" has incompatible type "str"; expected "int" [arg-type] - case: attr_s_with_type_annotations - main : | + main: | import attr @attr.s class C: a: int = attr.ib() - C() # E: Missing positional argument "a" in call to "C" + C() # E: Missing positional argument "a" in call to "C" [call-arg] C(1) C(a=1) - C(a="hi") # E: Argument "a" to "C" has incompatible type "str"; expected "int" + C(a="hi") # E: Argument "a" to "C" has incompatible type "str"; expected "int" [arg-type] - case: testAttrsSimple main: | @@ -39,9 +39,10 @@ A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) - A(1, [2], '3', 4, 5) # E: Too many arguments for "A" + A(1, [2], '3', 4, 5) # E: Too many arguments for "A" [call-arg] - case: testAttrsAnnotated + regex: true main: | import attr from typing import List, ClassVar @@ -53,13 +54,14 @@ _d: int = attr.ib(validator=None, default=18) E = 7 F: ClassVar[int] = 22 - reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[builtins.int], c: builtins.str =, d: builtins.int =) -> main.A" + reveal_type(A) # N: Revealed type is "def \(a: builtins\.int, b: builtins\.list\[builtins\.int\], c: builtins\.str =, d: builtins\.int =\) -> main\.A" A(1, [2]) A(1, [2], '3', 4) - A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "List[int]" # E: Argument 3 to "A" has incompatible type "int"; expected "str" - A(1, [2], '3', 4, 5) # E: Too many arguments for "A" + A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "[Ll]ist\[int\]" \[arg-type\] # E: Argument 3 to "A" has incompatible type "int"; expected "str" \[arg-type\] + A(1, [2], '3', 4, 5) # E: Too many arguments for "A" \[call-arg\] - case: testAttrsPython2Annotations + regex: true main: | import attr from typing import List, ClassVar @@ -71,13 +73,14 @@ _d = attr.ib(validator=None, default=18) # type: int E = 7 F: ClassVar[int] = 22 - reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[builtins.int], c: builtins.str =, d: builtins.int =) -> main.A" + reveal_type(A) # N: Revealed type is "def \(a: builtins\.int, b: builtins\.list\[builtins\.int\], c: builtins\.str =, d: builtins\.int =\) -> main\.A" A(1, [2]) A(1, [2], '3', 4) - A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "List[int]" # E: Argument 3 to "A" has incompatible type "int"; expected "str" - A(1, [2], '3', 4, 5) # E: Too many arguments for "A" + A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "[Ll]ist\[int\]" \[arg-type\] # E: Argument 3 to "A" has incompatible type "int"; expected "str" \[arg-type\] + A(1, [2], '3', 4, 5) # E: Too many arguments for "A" \[call-arg\] - case: testAttrsAutoAttribs + regex: true main: | import attr from typing import List, ClassVar @@ -89,23 +92,23 @@ _d: int = attr.ib(validator=None, default=18) E = 7 F: ClassVar[int] = 22 - reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[builtins.int], c: builtins.str =, d: builtins.int =) -> main.A" + reveal_type(A) # N: Revealed type is "def \(a: builtins\.int, b: builtins.list\[builtins.int\], c: builtins\.str =, d: builtins\.int =\) -> main\.A" A(1, [2]) A(1, [2], '3', 4) - A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "List[int]" # E: Argument 3 to "A" has incompatible type "int"; expected "str" - A(1, [2], '3', 4, 5) # E: Too many arguments for "A" + A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "[Ll]ist\[int\]" \[arg-type\] # E: Argument 3 to "A" has incompatible type "int"; expected "str" \[arg-type\] + A(1, [2], '3', 4, 5) # E: Too many arguments for "A" \[call-arg\] - case: testAttrsUntypedNoUntypedDefs mypy_config: | - disallow_untyped_defs = True + disallow_untyped_defs = True main: | import attr @attr.s class A: - a = attr.ib() # E: Need type annotation for "a" - _b = attr.ib() # E: Need type annotation for "_b" - c = attr.ib(18) # E: Need type annotation for "c" - _d = attr.ib(validator=None, default=18) # E: Need type annotation for "_d" + a = attr.ib() # E: Need type annotation for "a" [var-annotated] + _b = attr.ib() # E: Need type annotation for "_b" [var-annotated] + c = attr.ib(18) # E: Need type annotation for "c" [var-annotated] + _d = attr.ib(validator=None, default=18) # E: Need type annotation for "_d" [var-annotated] E = 18 - case: testAttrsWrongReturnValue @@ -115,24 +118,25 @@ class A: x: int = attr.ib(8) def foo(self) -> str: - return self.x # E: Incompatible return value type (got "int", expected "str") + return self.x # E: Incompatible return value type (got "int", expected "str") [return-value] @attr.s class B: x = attr.ib(8) # type: int def foo(self) -> str: - return self.x # E: Incompatible return value type (got "int", expected "str") + return self.x # E: Incompatible return value type (got "int", expected "str") [return-value] @attr.dataclass class C: x: int = 8 def foo(self) -> str: - return self.x # E: Incompatible return value type (got "int", expected "str") + return self.x # E: Incompatible return value type (got "int", expected "str") [return-value] @attr.s class D: x = attr.ib(8, type=int) def foo(self) -> str: - return self.x # E: Incompatible return value type (got "int", expected "str") + return self.x # E: Incompatible return value type (got "int", expected "str") [return-value] - case: testAttrsSeriousNames + regex: true main: | from attr import attrib, attrs from typing import List @@ -143,11 +147,11 @@ c = attrib(18) _d = attrib(validator=None, default=18) CLASS_VAR = 18 - reveal_type(A) # N: Revealed type is "def (a: Any, b: builtins.list[builtins.int], c: Any =, d: Any =) -> main.A" + reveal_type(A) # N: Revealed type is "def \(a: Any, b: builtins.list\[builtins.int\], c: Any =, d: Any =\) -> main\.A" A(1, [2]) A(1, [2], '3', 4) - A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "List[int]" - A(1, [2], '3', 4, 5) # E: Too many arguments for "A" + A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "[Ll]ist\[int\]" \[arg-type\] + A(1, [2], '3', 4, 5) # E: Too many arguments for "A" \[call-arg\] - case: testAttrsDefaultErrors main: | @@ -155,19 +159,19 @@ @attr.s class A: x = attr.ib(default=17) - y = attr.ib() # E: Non-default attributes not allowed after default attributes. + y = attr.ib() # E: Non-default attributes not allowed after default attributes. [misc] @attr.s(auto_attribs=True) class B: x: int = 17 - y: int # E: Non-default attributes not allowed after default attributes. + y: int # E: Non-default attributes not allowed after default attributes. [misc] @attr.s(auto_attribs=True) class C: x: int = attr.ib(default=17) - y: int # E: Non-default attributes not allowed after default attributes. + y: int # E: Non-default attributes not allowed after default attributes. [misc] @attr.s class D: x = attr.ib() - y = attr.ib() # E: Non-default attributes not allowed after default attributes. + y = attr.ib() # E: Non-default attributes not allowed after default attributes. [misc] @x.default def foo(self): @@ -177,9 +181,9 @@ main: | import attr x = True - @attr.s(cmp=x) # E: "cmp" argument must be True or False. + @attr.s(cmp=x) # E: "cmp" argument must be a True, False, or None literal [literal-required] class A: - a = attr.ib(init=x) # E: "init" argument must be True or False. + a = attr.ib(init=x) # E: "init" argument must be a True or False literal [literal-required] - case: testAttrsInitFalse main: | @@ -192,8 +196,8 @@ _d: int = attrib(validator=None, default=18) reveal_type(A) # N: Revealed type is "def () -> main.A" A() - A(1, [2]) # E: Too many arguments for "A" - A(1, [2], '3', 4) # E: Too many arguments for "A" + A(1, [2]) # E: Too many arguments for "A" [call-arg] + A(1, [2], '3', 4) # E: Too many arguments for "A" [call-arg] - case: testAttrsInitAttribFalse main: | @@ -205,16 +209,17 @@ reveal_type(A) # N: Revealed type is "def (b: Any) -> main.A" - case: testAttrsCmpTrue + regex: true main: | from attr import attrib, attrs @attrs(auto_attribs=True) class A: a: int - reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> main.A" - reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" - reveal_type(A.__le__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" - reveal_type(A.__gt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" - reveal_type(A.__ge__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" + reveal_type(A) # N: Revealed type is "def \(a: builtins.int\) -> main.A" + reveal_type(A.__lt__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" + reveal_type(A.__le__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" + reveal_type(A.__gt__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" + reveal_type(A.__ge__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" A(1) < A(2) A(1) <= A(2) @@ -223,17 +228,17 @@ A(1) == A(2) A(1) != A(2) - A(1) < 1 # E: Unsupported operand types for < ("A" and "int") - A(1) <= 1 # E: Unsupported operand types for <= ("A" and "int") - A(1) > 1 # E: Unsupported operand types for > ("A" and "int") - A(1) >= 1 # E: Unsupported operand types for >= ("A" and "int") + A(1) < 1 # E: Unsupported operand types for < \("A" and "int"\) \[operator\] + A(1) <= 1 # E: Unsupported operand types for <= \("A" and "int"\) \[operator\] + A(1) > 1 # E: Unsupported operand types for > \("A" and "int"\) \[operator\] + A(1) >= 1 # E: Unsupported operand types for >= \("A" and "int"\) \[operator\] A(1) == 1 A(1) != 1 - 1 < A(1) # E: Unsupported operand types for < ("int" and "A") - 1 <= A(1) # E: Unsupported operand types for <= ("int" and "A") - 1 > A(1) # E: Unsupported operand types for > ("int" and "A") - 1 >= A(1) # E: Unsupported operand types for >= ("int" and "A") + 1 < A(1) # E: Unsupported operand types for < \("int" and "A"\) \[operator\] + 1 <= A(1) # E: Unsupported operand types for <= \("int" and "A"\) \[operator\] + 1 > A(1) # E: Unsupported operand types for > \("int" and "A"\) \[operator\] + 1 >= A(1) # E: Unsupported operand types for >= \("int" and "A"\) \[operator\] 1 == A(1) 1 != A(1) @@ -247,24 +252,24 @@ reveal_type(A.__eq__) # N: Revealed type is "def (builtins.object, builtins.object) -> builtins.bool" reveal_type(A.__ne__) # N: Revealed type is "def (builtins.object, builtins.object) -> builtins.bool" - A(1) < A(2) # E: Unsupported left operand type for < ("A") - A(1) <= A(2) # E: Unsupported left operand type for <= ("A") - A(1) > A(2) # E: Unsupported left operand type for > ("A") - A(1) >= A(2) # E: Unsupported left operand type for >= ("A") + A(1) < A(2) # E: Unsupported left operand type for < ("A") [operator] + A(1) <= A(2) # E: Unsupported left operand type for <= ("A") [operator] + A(1) > A(2) # E: Unsupported left operand type for > ("A") [operator] + A(1) >= A(2) # E: Unsupported left operand type for >= ("A") [operator] A(1) == A(2) A(1) != A(2) - A(1) < 1 # E: Unsupported operand types for > ("int" and "A") - A(1) <= 1 # E: Unsupported operand types for >= ("int" and "A") - A(1) > 1 # E: Unsupported operand types for < ("int" and "A") - A(1) >= 1 # E: Unsupported operand types for <= ("int" and "A") + A(1) < 1 # E: Unsupported operand types for > ("int" and "A") [operator] + A(1) <= 1 # E: Unsupported operand types for >= ("int" and "A") [operator] + A(1) > 1 # E: Unsupported operand types for < ("int" and "A") [operator] + A(1) >= 1 # E: Unsupported operand types for <= ("int" and "A") [operator] A(1) == 1 A(1) != 1 - 1 < A(1) # E: Unsupported operand types for < ("int" and "A") - 1 <= A(1) # E: Unsupported operand types for <= ("int" and "A") - 1 > A(1) # E: Unsupported operand types for > ("int" and "A") - 1 >= A(1) # E: Unsupported operand types for >= ("int" and "A") + 1 < A(1) # E: Unsupported operand types for < ("int" and "A") [operator] + 1 <= A(1) # E: Unsupported operand types for <= ("int" and "A") [operator] + 1 > A(1) # E: Unsupported operand types for > ("int" and "A") [operator] + 1 >= A(1) # E: Unsupported operand types for >= ("int" and "A") [operator] 1 == A(1) 1 != A(1) @@ -276,24 +281,24 @@ a: int reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> main.A" - A(1) < A(2) # E: Unsupported left operand type for < ("A") - A(1) <= A(2) # E: Unsupported left operand type for <= ("A") - A(1) > A(2) # E: Unsupported left operand type for > ("A") - A(1) >= A(2) # E: Unsupported left operand type for >= ("A") + A(1) < A(2) # E: Unsupported left operand type for < ("A") [operator] + A(1) <= A(2) # E: Unsupported left operand type for <= ("A") [operator] + A(1) > A(2) # E: Unsupported left operand type for > ("A") [operator] + A(1) >= A(2) # E: Unsupported left operand type for >= ("A") [operator] A(1) == A(2) A(1) != A(2) - A(1) < 1 # E: Unsupported operand types for > ("int" and "A") - A(1) <= 1 # E: Unsupported operand types for >= ("int" and "A") - A(1) > 1 # E: Unsupported operand types for < ("int" and "A") - A(1) >= 1 # E: Unsupported operand types for <= ("int" and "A") + A(1) < 1 # E: Unsupported operand types for > ("int" and "A") [operator] + A(1) <= 1 # E: Unsupported operand types for >= ("int" and "A") [operator] + A(1) > 1 # E: Unsupported operand types for < ("int" and "A") [operator] + A(1) >= 1 # E: Unsupported operand types for <= ("int" and "A") [operator] A(1) == 1 A(1) != 1 - 1 < A(1) # E: Unsupported operand types for < ("int" and "A") - 1 <= A(1) # E: Unsupported operand types for <= ("int" and "A") - 1 > A(1) # E: Unsupported operand types for > ("int" and "A") - 1 >= A(1) # E: Unsupported operand types for >= ("int" and "A") + 1 < A(1) # E: Unsupported operand types for < ("int" and "A") [operator] + 1 <= A(1) # E: Unsupported operand types for <= ("int" and "A") [operator] + 1 > A(1) # E: Unsupported operand types for > ("int" and "A") [operator] + 1 >= A(1) # E: Unsupported operand types for >= ("int" and "A") [operator] 1 == A(1) 1 != A(1) @@ -308,15 +313,14 @@ class DeprecatedFalse: ... - @attrs(cmp=False, eq=True) # E: Don't mix "cmp" with "eq" and "order" + @attrs(cmp=False, eq=True) # E: Don't mix "cmp" with "eq" and "order" [misc] class Mixed: ... - @attrs(order=True, eq=False) # E: eq must be True if order is True + @attrs(order=True, eq=False) # E: eq must be True if order is True [misc] class Confused: ... - - case: testAttrsInheritance main: | import attr @@ -385,7 +389,7 @@ a = attr.ib() a = A(5) - a.a = 16 # E: Property "a" defined in "A" is read-only + a.a = 16 # E: Property "a" defined in "A" is read-only [misc] - case: testAttrsNextGenFrozen main: | from attr import frozen, field @@ -395,7 +399,7 @@ a = field() a = A(5) - a.a = 16 # E: Property "a" defined in "A" is read-only + a.a = 16 # E: Property "a" defined in "A" is read-only [misc] - case: testAttrsNextGenDetect main: | @@ -419,7 +423,6 @@ a: int b = field() - # TODO: Next Gen hasn't shipped with mypy yet so the following are wrong reveal_type(A) # N: Revealed type is "def (a: Any) -> main.A" reveal_type(B) # N: Revealed type is "def (a: builtins.int) -> main.B" reveal_type(C) # N: Revealed type is "def (a: builtins.int, b: Any) -> main.C" @@ -453,6 +456,7 @@ reveal_type(A) # N: Revealed type is "def (x: builtins.list[builtins.int], y: builtins.list[builtins.str]) -> main.A" - case: testAttrsGeneric + regex: true main: | from typing import TypeVar, Generic, List import attr @@ -466,16 +470,15 @@ def bar(self) -> T: return self.x[0] def problem(self) -> T: - return self.x # E: Incompatible return value type (got "List[T]", expected "T") - reveal_type(A) # N: Revealed type is "def [T] (x: builtins.list[T`1], y: T`1) -> main.A[T`1]" + return self.x # E: Incompatible return value type \(got "[Ll]ist\[T\]", expected "T"\) \[return-value\] + reveal_type(A) # N: Revealed type is "def \[T\] \(x: builtins\.list\[T`1\], y: T`1\) -> main.A\[T`1\]" a = A([1], 2) - reveal_type(a) # N: Revealed type is "main.A[builtins.int*]" - reveal_type(a.x) # N: Revealed type is "builtins.list[builtins.int*]" - reveal_type(a.y) # N: Revealed type is "builtins.int*" - - A(['str'], 7) # E: Cannot infer type argument 1 of "A" - A([1], '2') # E: Cannot infer type argument 1 of "A" + reveal_type(a) # N: Revealed type is "main\.A\[builtins.int\]" + reveal_type(a.x) # N: Revealed type is "builtins\.list\[builtins\.int\]" + reveal_type(a.y) # N: Revealed type is "builtins\.int" + A(['str'], 7) # E: Cannot infer type argument 1 of "A" \[misc\] + A([1], '2') # E: Cannot infer type argument 1 of "A" \[misc\] - case: testAttrsUntypedGenericInheritance main: | @@ -514,12 +517,12 @@ pass sub_int = Sub[int](attr=1) - reveal_type(sub_int) # N: Revealed type is "main.Sub[builtins.int*]" - reveal_type(sub_int.attr) # N: Revealed type is "builtins.int*" + reveal_type(sub_int) # N: Revealed type is "main.Sub[builtins.int]" + reveal_type(sub_int.attr) # N: Revealed type is "builtins.int" sub_str = Sub[str](attr='ok') - reveal_type(sub_str) # N: Revealed type is "main.Sub[builtins.str*]" - reveal_type(sub_str.attr) # N: Revealed type is "builtins.str*" + reveal_type(sub_str) # N: Revealed type is "main.Sub[builtins.str]" + reveal_type(sub_str.attr) # N: Revealed type is "builtins.str" - case: testAttrsGenericInheritance2 main: | @@ -646,7 +649,7 @@ return cls(6, 'hello') @classmethod def bad(cls) -> A: - return cls(17) # E: Missing positional argument "b" in call to "A" + return cls(17) # E: Missing positional argument "b" in call to "A" [call-arg] def foo(self) -> int: return self.a reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.str) -> main.A" @@ -688,7 +691,7 @@ main: | import attr @attr.s - class C(object): + class C: x: int = attr.ib(default=1) y: int = attr.ib() @y.default @@ -700,7 +703,7 @@ main: | import attr @attr.s - class C(object): + class C: x = attr.ib() @x.validator def check(self, attribute, value): @@ -764,8 +767,7 @@ return 'hello' - case: testAttrsUsingBadConverter - mypy_config: - strict_optional = False + regex: true main: | import attr from typing import overload @@ -785,15 +787,14 @@ bad_overloaded: int = attr.ib(converter=bad_overloaded_converter) reveal_type(A) out: | - main:15: error: Cannot determine __init__ type from converter - main:15: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], Any]" - main:16: error: Cannot determine __init__ type from converter - main:16: error: Argument "converter" has incompatible type overloaded function; expected "Callable[[Any], Any]" - main:17: note: Revealed type is "def (bad: Any, bad_overloaded: Any) -> main.A" + main:15: error: Cannot determine __init__ type from converter \[misc\] + main:15: error: Argument "converter" has incompatible type \"Callable\[\[\], str\]\"; expected (\"Callable\[\[Any\], Any\] \| None\"|\"Optional\[Callable\[\[Any\], Any\]\]\") \[arg-type\] + main:16: error: Cannot determine __init__ type from converter \[misc\] + main:16: error: Argument "converter" has incompatible type overloaded function; expected (\"Callable\[\[Any\], Any\] \| None\"|\"Optional\[Callable\[\[Any\], Any\]\]\") \[arg-type\] + main:17: note: Revealed type is "def (bad: Any, bad_overloaded: Any\) -> main.A" - case: testAttrsUsingBadConverterReprocess - mypy_config: - strict_optional = False + regex: true main: | import attr from typing import overload @@ -814,26 +815,26 @@ bad_overloaded: int = attr.ib(converter=bad_overloaded_converter) reveal_type(A) out: | - main:16: error: Cannot determine __init__ type from converter - main:16: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], Any]" - main:17: error: Cannot determine __init__ type from converter - main:17: error: Argument "converter" has incompatible type overloaded function; expected "Callable[[Any], Any]" - main:18: note: Revealed type is "def (bad: Any, bad_overloaded: Any) -> main.A" + main:16: error: Cannot determine __init__ type from converter \[misc\] + main:16: error: Argument \"converter\" has incompatible type \"Callable\[\[\], str\]\"; expected (\"Callable\[\[Any\], Any\] \| None\"|\"Optional\[Callable\[\[Any\], Any\]\]\") \[arg-type\] + main:17: error: Cannot determine __init__ type from converter \[misc\] + main:17: error: Argument "converter" has incompatible type overloaded function; expected (\"Callable\[\[Any\], Any\] \| None\"|\"Optional\[Callable\[\[Any\], Any\]\]\") \[arg-type\] + main:18: note: Revealed type is "def (bad: Any, bad_overloaded: Any\) -> main.A" - case: testAttrsUsingUnsupportedConverter main: | import attr class Thing: def do_it(self, int) -> str: - ... + return "" thing = Thing() def factory(default: int): - ... + return 1 @attr.s class C: - x: str = attr.ib(converter=thing.do_it) # E: Unsupported converter, only named functions and types are currently supported - y: str = attr.ib(converter=lambda x: x) # E: Unsupported converter, only named functions and types are currently supported - z: str = attr.ib(converter=factory(8)) # E: Unsupported converter, only named functions and types are currently supported + x: str = attr.ib(converter=thing.do_it) # E: Unsupported converter, only named functions, types and lambdas are currently supported [misc] + y: str = attr.ib(converter=lambda x: x) + z: str = attr.ib(converter=factory(8)) # E: Unsupported converter, only named functions, types and lambdas are currently supported [misc] reveal_type(C) # N: Revealed type is "def (x: Any, y: Any, z: Any) -> main.C" - case: testAttrsUsingConverterAndSubclass @@ -874,6 +875,7 @@ o = C(1, 2, "3") - case: testAttrsCmpWithSubclasses + regex: true main: | import attr @attr.s @@ -885,29 +887,29 @@ @attr.s class D(A): pass - reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" - reveal_type(B.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" - reveal_type(C.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" - reveal_type(D.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" + reveal_type(A.__lt__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" + reveal_type(B.__lt__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" + reveal_type(C.__lt__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" + reveal_type(D.__lt__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" A() < A() B() < B() - A() < B() # E: Unsupported operand types for < ("A" and "B") + A() < B() # E: Unsupported operand types for < \("A" and "B"\) \[operator\] C() > A() C() > B() C() > C() - C() > D() # E: Unsupported operand types for > ("C" and "D") + C() > D() # E: Unsupported operand types for > \("C" and "D"\) \[operator\] D() >= A() - D() >= B() # E: Unsupported operand types for >= ("D" and "B") - D() >= C() # E: Unsupported operand types for >= ("D" and "C") + D() >= B() # E: Unsupported operand types for >= \("D" and "B"\) \[operator\] + D() >= C() # E: Unsupported operand types for >= \("D" and "C"\) \[operator\] D() >= D() - A() <= 1 # E: Unsupported operand types for <= ("A" and "int") - B() <= 1 # E: Unsupported operand types for <= ("B" and "int") - C() <= 1 # E: Unsupported operand types for <= ("C" and "int") - D() <= 1 # E: Unsupported operand types for <= ("D" and "int") + A() <= 1 # E: Unsupported operand types for <= \("A" and "int"\) \[operator\] + B() <= 1 # E: Unsupported operand types for <= \("B" and "int"\) \[operator\] + C() <= 1 # E: Unsupported operand types for <= \("C" and "int"\) \[operator\] + D() <= 1 # E: Unsupported operand types for <= \("D" and "int"\) \[operator\] - case: testAttrsComplexSuperclass main: | @@ -938,16 +940,16 @@ import attr @attr.s class A: - x = y = z = attr.ib() # E: Too many names for one attribute + x = y = z = attr.ib() # E: Too many names for one attribute [misc] - case: testAttrsPrivateInit main: | import attr @attr.s - class C(object): + class C: _x = attr.ib(init=False, default=42) C() - C(_x=42) # E: Unexpected keyword argument "_x" for "C" + C(_x=42) # E: Unexpected keyword argument "_x" for "C" [call-arg] - case: testAttrsAutoMustBeAll main: | @@ -957,9 +959,9 @@ a: int b = 17 # The following forms are not allowed with auto_attribs=True - c = attr.ib() # E: Need type annotation for "c" - d, e = attr.ib(), attr.ib() # E: Need type annotation for "d" # E: Need type annotation for "e" - f = g = attr.ib() # E: Need type annotation for "f" # E: Need type annotation for "g" + c = attr.ib() # E: Need type annotation for "c" [var-annotated] + d, e = attr.ib(), attr.ib() # E: Need type annotation for "d" [var-annotated] # E: Need type annotation for "e" [var-annotated] + f = g = attr.ib() # E: Need type annotation for "f" [var-annotated] # E: Need type annotation for "g" [var-annotated] - case: testAttrsRepeatedName main: | @@ -974,38 +976,15 @@ class B: a: int = attr.ib(default=8) b: int = attr.ib() - a: int = attr.ib() # E: Name "a" already defined on line 10 + a: int = attr.ib() # E: Name "a" already defined on line 10 [no-redef] reveal_type(B) # N: Revealed type is "def (b: builtins.int, a: builtins.int) -> main.B" @attr.s(auto_attribs=True) class C: a: int = 8 b: int - a: int = attr.ib() # E: Name "a" already defined on line 16 + a: int = attr.ib() # E: Name "a" already defined on line 16 [no-redef] reveal_type(C) # N: Revealed type is "def (a: builtins.int, b: builtins.int) -> main.C" -- case: testAttrsNewStyleClassPy2 - mypy_config: - python_version = 2.7 - main: | - import attr - @attr.s - class Good(object): - pass - @attr.s - class Bad: # E: attrs only works with new-style classes - pass - skip: True # https://github.com/typeddjango/pytest-mypy-plugins/issues/47 - -- case: testAttrsAutoAttribsPy2 - mypy_config: | - python_version = 2.7 - main: | - import attr - @attr.s(auto_attribs=True) # E: auto_attribs is not supported in Python 2 - class A(object): - x = attr.ib() - skip: True # https://github.com/typeddjango/pytest-mypy-plugins/issues/47 - - case: testAttrsFrozenSubclass main: | import attr @@ -1034,19 +1013,19 @@ non_frozen_base = NonFrozenBase(1) non_frozen_base.a = 17 frozen_base = FrozenBase(1) - frozen_base.a = 17 # E: Property "a" defined in "FrozenBase" is read-only + frozen_base.a = 17 # E: Property "a" defined in "FrozenBase" is read-only [misc] a = FrozenNonFrozen(1, 2) - a.a = 17 # E: Property "a" defined in "FrozenNonFrozen" is read-only - a.b = 17 # E: Property "b" defined in "FrozenNonFrozen" is read-only + a.a = 17 # E: Property "a" defined in "FrozenNonFrozen" is read-only [misc] + a.b = 17 # E: Property "b" defined in "FrozenNonFrozen" is read-only [misc] b = FrozenFrozen(1, 2) - b.a = 17 # E: Property "a" defined in "FrozenFrozen" is read-only - b.b = 17 # E: Property "b" defined in "FrozenFrozen" is read-only + b.a = 17 # E: Property "a" defined in "FrozenFrozen" is read-only [misc] + b.b = 17 # E: Property "b" defined in "FrozenFrozen" is read-only [misc] c = NonFrozenFrozen(1, 2) - c.a = 17 # E: Property "a" defined in "NonFrozenFrozen" is read-only - c.b = 17 # E: Property "b" defined in "NonFrozenFrozen" is read-only + c.a = 17 # E: Property "a" defined in "NonFrozenFrozen" is read-only [misc] + c.b = 17 # E: Property "b" defined in "NonFrozenFrozen" is read-only [misc] - case: testAttrsCallableAttributes main: | from typing import Callable @@ -1088,17 +1067,18 @@ import attr @attr.s class A: - x: int = attr.ib(factory=int, default=7) # E: Can't pass both "default" and "factory". + x: int = attr.ib(factory=int, default=7) # E: Can't pass both "default" and "factory". [misc] - case: testAttrsFactoryBadReturn + regex: true main: | import attr def my_factory() -> int: return 7 @attr.s class A: - x: int = attr.ib(factory=list) # E: Incompatible types in assignment (expression has type "List[_T]", variable has type "int") - y: str = attr.ib(factory=my_factory) # E: Incompatible types in assignment (expression has type "int", variable has type "str") + x: int = attr.ib(factory=list) # E: Incompatible types in assignment \(expression has type "[Ll]ist\[.*\]", variable has type "int"\) \[assignment\] + y: str = attr.ib(factory=my_factory) # E: Incompatible types in assignment \(expression has type "int", variable has type "str"\) \[assignment\] - case: testAttrsDefaultAndInit main: | @@ -1110,7 +1090,7 @@ b = attr.ib() # Ok because previous attribute is init=False c = attr.ib(default=44) d = attr.ib(init=False) # Ok because this attribute is init=False - e = attr.ib() # E: Non-default attributes not allowed after default attributes. + e = attr.ib() # E: Non-default attributes not allowed after default attributes. [misc] - case: testAttrsOptionalConverter main: | @@ -1149,8 +1129,8 @@ @attr.s class A: a = attr.ib(kw_only=True) - A() # E: Missing named argument "a" for "A" - A(15) # E: Too many positional arguments for "A" + A() # E: Missing named argument "a" for "A" [call-arg] + A(15) # E: Too many positional arguments for "A" [misc] A(a=15) - case: testAttrsKwOnlyClass @@ -1160,7 +1140,7 @@ class A: a: int b: bool - A() # E: Missing named argument "a" for "A" # E: Missing named argument "b" for "A" + A() # E: Missing named argument "a" for "A" [call-arg] # E: Missing named argument "b" for "A" [call-arg] A(b=True, a=15) - case: testAttrsKwOnlyClassNoInit @@ -1192,7 +1172,6 @@ c = attr.ib(15) D(b=17) - - case: testAttrsKwOnlySubclass main: | import attr @@ -1218,19 +1197,6 @@ a = attr.ib(kw_only=True) b = attr.ib(15) -- case: testAttrsKwOnlyPy2 - mypy_config: - python_version=2.7 - main: | - import attr - @attr.s(kw_only=True) # E: kw_only is not supported in Python 2 - class A(object): - x = attr.ib() - @attr.s - class B(object): - x = attr.ib(kw_only=True) # E: kw_only is not supported in Python 2 - skip: True # https://github.com/typeddjango/pytest-mypy-plugins/issues/47 - - case: testAttrsDisallowUntypedWorksForward main: | # flags: --disallow-untyped-defs @@ -1247,14 +1213,13 @@ reveal_type(B) # N: Revealed type is "def (x: main.C) -> main.B" - case: testDisallowUntypedWorksForwardBad - mypy_config: - disallow_untyped_defs = True + mypy_config: disallow_untyped_defs = True main: | import attr @attr.s class B: - x = attr.ib() # E: Need type annotation for "x" + x = attr.ib() # E: Need type annotation for "x" [var-annotated] reveal_type(B) # N: Revealed type is "def (x: Any) -> main.B" @@ -1264,7 +1229,7 @@ import attr @attr.s - class C(object): + class C: x: int = attr.ib(default=1) y: int = attr.ib() @y.default @@ -1279,7 +1244,7 @@ import attr @attr.s - class C(object): + class C: x = attr.ib() @x.validator def check(self, attribute, value): @@ -1296,7 +1261,7 @@ @attr.s class C: - total = attr.ib(type=Bad) # E: Name "Bad" is not defined + total = attr.ib(type=Bad) # E: Name "Bad" is not defined [name-defined] - case: testTypeInAttrForwardInRuntime main: | @@ -1307,7 +1272,7 @@ total = attr.ib(type=Forward) reveal_type(C.total) # N: Revealed type is "main.Forward" - C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "Forward" + C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "Forward" [arg-type] class Forward: ... - case: testDefaultInAttrForward @@ -1318,11 +1283,11 @@ class C: total = attr.ib(default=func()) - def func() -> int: ... + def func() -> int: return 5 C() C(1) - C(1, 2) # E: Too many arguments for "C" + C(1, 2) # E: Too many arguments for "C" [call-arg] - case: testTypeInAttrUndefinedFrozen main: | @@ -1330,9 +1295,9 @@ @attr.s(frozen=True) class C: - total = attr.ib(type=Bad) # E: Name "Bad" is not defined + total = attr.ib(type=Bad) # E: Name "Bad" is not defined [name-defined] - C(0).total = 1 # E: Property "total" defined in "C" is read-only + C(0).total = 1 # E: Property "total" defined in "C" is read-only [misc] - case: testTypeInAttrDeferredStar main: | @@ -1349,8 +1314,8 @@ class C: total = attr.ib(type=int) - C() # E: Missing positional argument "total" in call to "C" - C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "int" + C() # E: Missing positional argument "total" in call to "C" [call-arg] + C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "int" [arg-type] - path: other.py content: | import lib @@ -1365,7 +1330,7 @@ from b import A1, A2 @attr.s - class Asdf(A1, A2): # E: Non-default attributes not allowed after default attributes. + class Asdf(A1, A2): # E: Non-default attributes not allowed after default attributes. [misc] pass - path: b.py content: | @@ -1393,3 +1358,57 @@ foo = x reveal_type(B) # N: Revealed type is "def (foo: builtins.int) -> main.B" + +- case: testFields + regex: true + main: | + from attrs import define, fields + + @define + class A: + a: int + b: str + + reveal_type(fields(A)) # N: Revealed type is "[Tt]uple\[attr.Attribute\[builtins.int\], attr.Attribute\[builtins.str\], fallback=main.A.__main_A_AttrsAttributes__\]" + +- case: testFieldsError + regex: true + main: | + from attrs import fields + + class A: + a: int + b: str + + fields(A) # E: Argument 1 to "fields" has incompatible type "[Tt]ype\[A\]"; expected "[Tt]ype\[AttrsInstance\]" \[arg-type\] + +- case: testAsDict + main: | + from attrs import asdict, define + + @define + class A: + a: int + + asdict(A(1)) + +- case: testAsDictError + main: | + from attrs import asdict + + class A: + a: int + + asdict(A()) # E: Argument 1 to "asdict" has incompatible type "A"; expected "AttrsInstance" [arg-type] + +- case: testHasTypeGuard + main: | + from attrs import define, has + + @define + class A: + pass + + reveal_type(A) # N: Revealed type is "def () -> main.A" + if has(A): + reveal_type(A) # N: Revealed type is "Type[attr.AttrsInstance]" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_next_gen.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_next_gen.py index 8395f9c028655..7d053d2143911 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_next_gen.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_next_gen.py @@ -1,11 +1,12 @@ # SPDX-License-Identifier: MIT """ -Python 3-only integration tests for provisional next generation APIs. +Integration tests for next-generation APIs. """ import re +from contextlib import contextmanager from functools import partial import pytest @@ -27,6 +28,16 @@ def test_simple(self): """ C("1", 2) + def test_field_type(self): + """ + Make class with attrs.field and type parameter. + """ + classFields = {"testint": attrs.field(type=int)} + + A = attrs.make_class("A", classFields) + + assert int == attrs.fields(A).testint.type + def test_no_slots(self): """ slots can be deactivated. @@ -38,7 +49,7 @@ class NoSlots: ns = NoSlots(1) - assert {"x": 1} == getattr(ns, "__dict__") + assert {"x": 1} == ns.__dict__ def test_validates(self): """ @@ -312,6 +323,38 @@ class MyException(Exception): assert "foo" == ei.value.x assert ei.value.__cause__ is None + @pytest.mark.parametrize( + "decorator", + [ + partial(_attr.s, frozen=True, slots=True, auto_exc=True), + attrs.frozen, + attrs.define, + attrs.mutable, + ], + ) + def test_setting_traceback_on_exception(self, decorator): + """ + contextlib.contextlib (re-)sets __traceback__ on raised exceptions. + + Ensure that works, as well as if done explicitly + """ + + @decorator + class MyException(Exception): + pass + + @contextmanager + def do_nothing(): + yield + + with do_nothing(), pytest.raises(MyException) as ei: + raise MyException() + + assert isinstance(ei.value, MyException) + + # this should not raise an exception either + ei.value.__traceback__ = ei.value.__traceback__ + def test_converts_and_validates_by_default(self): """ If no on_setattr is set, assume setters.convert, setters.validate. @@ -346,7 +389,6 @@ def test_mro_ng(self): @attrs.define class A: - x: int = 10 def xx(self): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_packaging.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_packaging.py new file mode 100644 index 0000000000000..046ae4c39dd06 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_packaging.py @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: MIT + +import sys + +import pytest + +import attr +import attrs + + +if sys.version_info < (3, 8): + import importlib_metadata as metadata +else: + from importlib import metadata + + +@pytest.fixture(name="mod", params=(attr, attrs)) +def _mod(request): + return request.param + + +class TestLegacyMetadataHack: + def test_title(self, mod): + """ + __title__ returns attrs. + """ + with pytest.deprecated_call() as ws: + assert "attrs" == mod.__title__ + + assert ( + f"Accessing {mod.__name__}.__title__ is deprecated" + in ws.list[0].message.args[0] + ) + + def test_copyright(self, mod): + """ + __copyright__ returns the correct blurp. + """ + with pytest.deprecated_call() as ws: + assert "Copyright (c) 2015 Hynek Schlawack" == mod.__copyright__ + + assert ( + f"Accessing {mod.__name__}.__copyright__ is deprecated" + in ws.list[0].message.args[0] + ) + + def test_version(self, mod, recwarn): + """ + __version__ returns the correct version and doesn't warn. + """ + assert metadata.version("attrs") == mod.__version__ + + assert [] == recwarn.list + + def test_description(self, mod): + """ + __description__ returns the correct description. + """ + with pytest.deprecated_call() as ws: + assert "Classes Without Boilerplate" == mod.__description__ + + assert ( + f"Accessing {mod.__name__}.__description__ is deprecated" + in ws.list[0].message.args[0] + ) + + @pytest.mark.parametrize("name", ["__uri__", "__url__"]) + def test_uri(self, mod, name): + """ + __uri__ & __url__ returns the correct project URL. + """ + with pytest.deprecated_call() as ws: + assert "https://www.attrs.org/" == getattr(mod, name) + + assert ( + f"Accessing {mod.__name__}.{name} is deprecated" + in ws.list[0].message.args[0] + ) + + def test_author(self, mod): + """ + __author__ returns Hynek. + """ + with pytest.deprecated_call() as ws: + assert "Hynek Schlawack" == mod.__author__ + + assert ( + f"Accessing {mod.__name__}.__author__ is deprecated" + in ws.list[0].message.args[0] + ) + + def test_email(self, mod): + """ + __email__ returns Hynek's email address. + """ + with pytest.deprecated_call() as ws: + assert "hs@ox.cx" == mod.__email__ + + assert ( + f"Accessing {mod.__name__}.__email__ is deprecated" + in ws.list[0].message.args[0] + ) + + def test_license(self, mod): + """ + __license__ returns MIT. + """ + with pytest.deprecated_call() as ws: + assert "MIT" == mod.__license__ + + assert ( + f"Accessing {mod.__name__}.__license__ is deprecated" + in ws.list[0].message.args[0] + ) + + def test_does_not_exist(self, mod): + """ + Asking for unsupported dunders raises an AttributeError. + """ + with pytest.raises( + AttributeError, + match=f"module {mod.__name__} has no attribute __yolo__", + ): + mod.__yolo__ + + def test_version_info(self, recwarn, mod): + """ + ___version_info__ is not deprected, therefore doesn't raise a warning + and parses correctly. + """ + assert isinstance(mod.__version_info__, attr.VersionInfo) + assert [] == recwarn.list diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_pattern_matching.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_pattern_matching.py index 590804a8a7ac4..3855d6a379c24 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_pattern_matching.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_pattern_matching.py @@ -19,7 +19,7 @@ def test_simple_match_case(self, dec): """ @dec - class C(object): + class C: a = attr.ib() assert ("a",) == C.__match_args__ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_pyright.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_pyright.py index c30dcc5cb1613..800d6099fab0f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_pyright.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_pyright.py @@ -1,47 +1,55 @@ # SPDX-License-Identifier: MIT +from __future__ import annotations + import json -import os.path import shutil import subprocess -import sys - -import pytest -import attr +from pathlib import Path +import pytest -if sys.version_info < (3, 6): - _found_pyright = False -else: - _found_pyright = shutil.which("pyright") +import attrs -@attr.s(frozen=True) -class PyrightDiagnostic(object): - severity = attr.ib() - message = attr.ib() +pytestmark = [ + pytest.mark.skipif( + shutil.which("pyright") is None, reason="Requires pyright." + ), +] -@pytest.mark.skipif(not _found_pyright, reason="Requires pyright.") -def test_pyright_baseline(): - """The __dataclass_transform__ decorator allows pyright to determine - attrs decorated class types. - """ +@attrs.frozen +class PyrightDiagnostic: + severity: str + message: str - test_file = os.path.dirname(__file__) + "/dataclass_transform_example.py" - pyright = subprocess.run( +def parse_pyright_output(test_file: Path) -> set[PyrightDiagnostic]: + pyright = subprocess.run( # noqa: PLW1510 ["pyright", "--outputjson", str(test_file)], capture_output=True ) + pyright_result = json.loads(pyright.stdout) - diagnostics = set( + return { PyrightDiagnostic(d["severity"], d["message"]) for d in pyright_result["generalDiagnostics"] - ) + } - # Expected diagnostics as per pyright 1.1.135 + +def test_pyright_baseline(): + """ + The typing.dataclass_transform decorator allows pyright to determine + attrs decorated class types. + """ + + test_file = Path(__file__).parent / "dataclass_transform_example.py" + + diagnostics = parse_pyright_output(test_file) + + # Expected diagnostics as per pyright 1.1.311 expected_diagnostics = { PyrightDiagnostic( severity="information", @@ -51,7 +59,13 @@ def test_pyright_baseline(): PyrightDiagnostic( severity="information", message='Type of "DefineConverter.__init__" is ' - '"(self: DefineConverter, with_converter: int) -> None"', + '"(self: DefineConverter, with_converter: str | Buffer | ' + 'SupportsInt | SupportsIndex | SupportsTrunc) -> None"', + ), + PyrightDiagnostic( + severity="error", + message='Cannot assign member "a" for type ' + '"Frozen"\n\xa0\xa0"Frozen" is frozen\n\xa0\xa0\xa0\xa0Member "__set__" is unknown', ), PyrightDiagnostic( severity="information", @@ -60,12 +74,45 @@ def test_pyright_baseline(): PyrightDiagnostic( severity="error", message='Cannot assign member "a" for type ' - '"FrozenDefine"\n\xa0\xa0"FrozenDefine" is frozen', + '"FrozenDefine"\n\xa0\xa0"FrozenDefine" is frozen\n\xa0\xa0\xa0\xa0' + 'Member "__set__" is unknown', ), PyrightDiagnostic( severity="information", message='Type of "d2.a" is "Literal[\'new\']"', ), + PyrightDiagnostic( + severity="information", + message='Type of "af.__init__" is "(_a: int) -> None"', + ), } + assert expected_diagnostics == diagnostics + + +def test_pyright_attrsinstance_compat(tmp_path): + """ + Test that `AttrsInstance` is compatible with Pyright. + """ + test_pyright_attrsinstance_compat_path = ( + tmp_path / "test_pyright_attrsinstance_compat.py" + ) + test_pyright_attrsinstance_compat_path.write_text( + """\ +import attrs + +# We can assign any old object to `AttrsInstance`. +foo: attrs.AttrsInstance = object() + +reveal_type(attrs.AttrsInstance) +""" + ) + + diagnostics = parse_pyright_output(test_pyright_attrsinstance_compat_path) + expected_diagnostics = { + PyrightDiagnostic( + severity="information", + message='Type of "attrs.AttrsInstance" is "type[AttrsInstance]"', + ), + } assert diagnostics == expected_diagnostics diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_setattr.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_setattr.py index aaedde5746489..c7b90daee68b9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_setattr.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_setattr.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function import pickle @@ -9,22 +8,21 @@ import attr from attr import setters -from attr._compat import PY2 from attr.exceptions import FrozenAttributeError from attr.validators import instance_of, matches_re @attr.s(frozen=True) -class Frozen(object): +class Frozen: x = attr.ib() @attr.s -class WithOnSetAttrHook(object): +class WithOnSetAttrHook: x = attr.ib(on_setattr=lambda *args: None) -class TestSetAttr(object): +class TestSetAttr: def test_change(self): """ The return value of a hook overwrites the value. But they are not run @@ -35,7 +33,7 @@ def hook(*a, **kw): return "hooked!" @attr.s - class Hooked(object): + class Hooked: x = attr.ib(on_setattr=hook) y = attr.ib() @@ -56,7 +54,7 @@ def test_frozen_attribute(self): """ @attr.s - class PartiallyFrozen(object): + class PartiallyFrozen: x = attr.ib(on_setattr=setters.frozen) y = attr.ib() @@ -81,7 +79,7 @@ def test_validator(self, on_setattr): """ @attr.s(on_setattr=on_setattr) - class ValidatedAttribute(object): + class ValidatedAttribute: x = attr.ib() y = attr.ib(validator=[instance_of(str), matches_re("foo.*qux")]) @@ -115,7 +113,7 @@ def test_pipe(self): s = [setters.convert, lambda _, __, nv: nv + 1] @attr.s - class Piped(object): + class Piped: x1 = attr.ib(converter=int, on_setattr=setters.pipe(*s)) x2 = attr.ib(converter=int, on_setattr=s) @@ -147,7 +145,7 @@ def test_no_validator_no_converter(self): """ @attr.s(on_setattr=[setters.convert, setters.validate]) - class C(object): + class C: x = attr.ib() c = C(1) @@ -162,7 +160,7 @@ def test_validate_respects_run_validators_config(self): """ @attr.s(on_setattr=setters.validate) - class C(object): + class C: x = attr.ib(validator=attr.validators.instance_of(int)) c = C(1) @@ -187,7 +185,7 @@ def test_frozen_on_setattr_class_is_caught(self): with pytest.raises(ValueError) as ei: @attr.s(frozen=True, on_setattr=setters.validate) - class C(object): + class C: x = attr.ib() assert "Frozen classes can't use on_setattr." == ei.value.args[0] @@ -200,12 +198,11 @@ def test_frozen_on_setattr_attribute_is_caught(self): with pytest.raises(ValueError) as ei: @attr.s(frozen=True) - class C(object): + class C: x = attr.ib(on_setattr=setters.validate) assert "Frozen classes can't use on_setattr." == ei.value.args[0] - @pytest.mark.parametrize("slots", [True, False]) def test_setattr_reset_if_no_custom_setattr(self, slots): """ If a class with an active setattr is subclassed and no new setattr @@ -218,29 +215,26 @@ def boom(*args): pytest.fail("Must not be called.") @attr.s - class Hooked(object): + class Hooked: x = attr.ib(on_setattr=boom) @attr.s(slots=slots) class NoHook(WithOnSetAttrHook): x = attr.ib() - if not PY2: - assert NoHook.__setattr__ == object.__setattr__ - + assert NoHook.__setattr__ == object.__setattr__ assert 1 == NoHook(1).x assert Hooked.__attrs_own_setattr__ assert not NoHook.__attrs_own_setattr__ assert WithOnSetAttrHook.__attrs_own_setattr__ - @pytest.mark.parametrize("slots", [True, False]) def test_setattr_inherited_do_not_reset(self, slots): """ If we inherit a __setattr__ that has been written by the user, we must not reset it unless necessary. """ - class A(object): + class A: """ Not an attrs class on purpose to prevent accidental resets that would render the asserts meaningless. @@ -261,7 +255,6 @@ class C(B): assert C.__setattr__ == A.__setattr__ - @pytest.mark.parametrize("slots", [True, False]) def test_pickling_retains_attrs_own(self, slots): """ Pickling/Unpickling does not lose ownership information about @@ -288,7 +281,7 @@ def test_slotted_class_can_have_custom_setattr(self): """ @attr.s(slots=True) - class A(object): + class A: def __setattr__(self, key, value): raise SystemError @@ -306,7 +299,7 @@ def test_slotted_confused(self): """ @attr.s(slots=True) - class A(object): + class A: x = attr.ib(on_setattr=setters.frozen) class B(A): @@ -318,14 +311,6 @@ class C(B): C(1).x = 2 - -@pytest.mark.skipif(PY2, reason="Python 3-only.") -class TestSetAttrNoPy2(object): - """ - __setattr__ tests for Py3+ to avoid the skip repetition. - """ - - @pytest.mark.parametrize("slots", [True, False]) def test_setattr_auto_detect_if_no_custom_setattr(self, slots): """ It's possible to remove the on_setattr hook from an attribute and @@ -345,7 +330,6 @@ def __setattr__(self, name, val): assert not RemoveNeedForOurSetAttr.__attrs_own_setattr__ assert 2 == i.x - @pytest.mark.parametrize("slots", [True, False]) def test_setattr_restore_respects_auto_detect(self, slots): """ If __setattr__ should be restored but the user supplied its own and @@ -359,7 +343,6 @@ def __setattr__(self, _, __): assert CustomSetAttr.__setattr__ != object.__setattr__ - @pytest.mark.parametrize("slots", [True, False]) def test_setattr_auto_detect_frozen(self, slots): """ frozen=True together with a detected custom __setattr__ are rejected. @@ -373,7 +356,6 @@ class CustomSetAttr(Frozen): def __setattr__(self, _, __): pass - @pytest.mark.parametrize("slots", [True, False]) def test_setattr_auto_detect_on_setattr(self, slots): """ on_setattr attributes together with a detected custom __setattr__ are @@ -385,7 +367,7 @@ def test_setattr_auto_detect_on_setattr(self, slots): ): @attr.s(auto_detect=True, slots=slots) - class HookAndCustomSetAttr(object): + class HookAndCustomSetAttr: x = attr.ib(on_setattr=lambda *args: None) def __setattr__(self, _, __): @@ -401,12 +383,12 @@ def test_setattr_inherited_do_not_reset_intermediate( A user-provided intermediate __setattr__ is not reset to object.__setattr__. - This only can work on Python 3+ with auto_detect activated, such that - attrs can know that there is a user-provided __setattr__. + This only can work with auto_detect activated, such that attrs can know + that there is a user-provided __setattr__. """ @attr.s(slots=a_slots) - class A(object): + class A: x = attr.ib(on_setattr=setters.frozen) @attr.s(slots=b_slots, auto_detect=True) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_slots.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_slots.py index baf9a40ddb88d..26365ab0d2bcd 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_slots.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_slots.py @@ -3,17 +3,18 @@ """ Unit tests for slots-related functionality. """ - +import functools import pickle -import sys -import types import weakref +from unittest import mock + import pytest import attr +import attrs -from attr._compat import PY2, PYPY, just_warn, make_set_closure_cell +from attr._compat import PY_3_8_PLUS, PYPY # Pympler doesn't work on PyPy. @@ -21,12 +22,12 @@ from pympler.asizeof import asizeof has_pympler = True -except BaseException: # Won't be an import error. +except BaseException: # Won't be an import error. # noqa: BLE001 has_pympler = False @attr.s -class C1(object): +class C1: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() @@ -41,18 +42,16 @@ def classmethod(cls): def staticmethod(): return "staticmethod" - if not PY2: + def my_class(self): + return __class__ - def my_class(self): - return __class__ - - def my_super(self): - """Just to test out the no-arg super.""" - return super().__repr__() + def my_super(self): + """Just to test out the no-arg super.""" + return super().__repr__() @attr.s(slots=True, hash=True) -class C1Slots(object): +class C1Slots: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() @@ -67,14 +66,12 @@ def classmethod(cls): def staticmethod(): return "staticmethod" - if not PY2: - - def my_class(self): - return __class__ + def my_class(self): + return __class__ - def my_super(self): - """Just to test out the no-arg super.""" - return super().__repr__() + def my_super(self): + """Just to test out the no-arg super.""" + return super().__repr__() def test_slots_being_used(): @@ -90,7 +87,7 @@ def test_slots_being_used(): assert "__dict__" in dir(non_slot_instance) assert "__slots__" not in dir(non_slot_instance) - assert set(["__weakref__", "x", "y"]) == set(slot_instance.__slots__) + assert {"__weakref__", "x", "y"} == set(slot_instance.__slots__) if has_pympler: assert asizeof(slot_instance) < asizeof(non_slot_instance) @@ -154,7 +151,7 @@ class C2Slots(C1): assert "clsmethod" == c2.classmethod() assert "staticmethod" == c2.staticmethod() - assert set(["z"]) == set(C2Slots.__slots__) + assert {"z"} == set(C2Slots.__slots__) c3 = C2Slots(x=1, y=3, z="test") @@ -178,7 +175,7 @@ def test_nonslots_these(): This will actually *replace* the class with another one, using slots. """ - class SimpleOrdinaryClass(object): + class SimpleOrdinaryClass: def __init__(self, x, y, z): self.x = x self.y = y @@ -213,7 +210,7 @@ def staticmethod(): assert "clsmethod" == c2.classmethod() assert "staticmethod" == c2.staticmethod() - assert set(["__weakref__", "x", "y", "z"]) == set(C2Slots.__slots__) + assert {"__weakref__", "x", "y", "z"} == set(C2Slots.__slots__) c3 = C2Slots(x=1, y=3, z="test") assert c3 > c2 @@ -245,7 +242,7 @@ class C2(C1): assert 2 == c2.y assert "test" == c2.z - assert set(["z"]) == set(C2Slots.__slots__) + assert {"z"} == set(C2Slots.__slots__) assert 1 == c2.method() assert "clsmethod" == c2.classmethod() @@ -275,7 +272,7 @@ def test_inheritance_from_slots_with_attribute_override(): Inheriting from a slotted class doesn't re-create existing slots """ - class HasXSlot(object): + class HasXSlot: __slots__ = ("x",) @attr.s(slots=True, hash=True) @@ -311,7 +308,7 @@ def test_inherited_slot_reuses_slot_descriptor(): We reuse slot descriptor for an attr.ib defined in a slotted attr.s """ - class HasXSlot(object): + class HasXSlot: __slots__ = ("x",) class OverridesX(HasXSlot): @@ -342,7 +339,7 @@ def test_bare_inheritance_from_slots(): @attr.s( init=False, eq=False, order=False, hash=False, repr=False, slots=True ) - class C1BareSlots(object): + class C1BareSlots: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() @@ -358,7 +355,7 @@ def staticmethod(): return "staticmethod" @attr.s(init=False, eq=False, order=False, hash=False, repr=False) - class C1Bare(object): + class C1Bare: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() @@ -409,8 +406,7 @@ class C2(C1Bare): assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2) -@pytest.mark.skipif(PY2, reason="closure cell rewriting is PY3-only.") -class TestClosureCellRewriting(object): +class TestClosureCellRewriting: def test_closure_cell_rewriting(self): """ Slotted classes support proper closure cell rewriting. @@ -457,7 +453,6 @@ def my_subclass(self): assert non_slot_instance.my_subclass() is C2 assert slot_instance.my_subclass() is C2Slots - @pytest.mark.parametrize("slots", [True, False]) def test_cls_static(self, slots): """ Slotted classes support proper closure cell rewriting for class- and @@ -482,36 +477,6 @@ def statmethod(): assert D.statmethod() is D - @pytest.mark.skipif(PYPY, reason="set_closure_cell always works on PyPy") - @pytest.mark.skipif( - sys.version_info >= (3, 8), - reason="can't break CodeType.replace() via monkeypatch", - ) - def test_code_hack_failure(self, monkeypatch): - """ - Keeps working if function/code object introspection doesn't work - on this (nonstandard) interpeter. - - A warning is emitted that points to the actual code. - """ - # This is a pretty good approximation of the behavior of - # the actual types.CodeType on Brython. - monkeypatch.setattr(types, "CodeType", lambda: None) - func = make_set_closure_cell() - - with pytest.warns(RuntimeWarning) as wr: - func() - - w = wr.pop() - assert __file__ == w.filename - assert ( - "Running interpreter doesn't sufficiently support code object " - "introspection. Some features like bare super() or accessing " - "__class__ will not work with slotted classes.", - ) == w.message.args - - assert just_warn is func - @pytest.mark.skipif(PYPY, reason="__slots__ only block weakref on CPython") def test_not_weakrefable(): @@ -520,7 +485,7 @@ def test_not_weakrefable(): """ @attr.s(slots=True, weakref_slot=False) - class C(object): + class C: pass c = C() @@ -538,7 +503,7 @@ def test_implicitly_weakrefable(): """ @attr.s(slots=True, weakref_slot=False) - class C(object): + class C: pass c = C() @@ -553,7 +518,7 @@ def test_weakrefable(): """ @attr.s(slots=True, weakref_slot=True) - class C(object): + class C: pass c = C() @@ -568,7 +533,7 @@ def test_weakref_does_not_add_a_field(): """ @attr.s(slots=True, weakref_slot=True) - class C(object): + class C: field = attr.ib() assert [f.name for f in attr.fields(C)] == ["field"] @@ -581,7 +546,7 @@ def tests_weakref_does_not_add_when_inheriting_with_weakref(): """ @attr.s(slots=True, weakref_slot=True) - class C(object): + class C: pass @attr.s(slots=True, weakref_slot=True) @@ -601,7 +566,7 @@ def tests_weakref_does_not_add_with_weakref_attribute(): """ @attr.s(slots=True, weakref_slot=True) - class C(object): + class C: __weakref__ = attr.ib( init=False, hash=False, repr=False, eq=False, order=False ) @@ -618,8 +583,8 @@ def test_slots_empty_cell(): closure cells are present with no contents in a `slots=True` class. (issue https://github.com/python-attrs/attrs/issues/589) - On Python 3, if a method mentions `__class__` or uses the no-arg `super()`, - the compiler will bake a reference to the class in the method itself as + If a method mentions `__class__` or uses the no-arg `super()`, the compiler + will bake a reference to the class in the method itself as `method.__closure__`. Since `attrs` replaces the class with a clone, `_ClassBuilder._create_slots_class(self)` will rewrite these references so it keeps working. This method was not properly covering the edge case where @@ -628,26 +593,26 @@ def test_slots_empty_cell(): """ @attr.s(slots=True) - class C(object): + class C: field = attr.ib() def f(self, a): - super(C, self).__init__() + super(C, self).__init__() # noqa: UP008 C(field=1) @attr.s(getstate_setstate=True) -class C2(object): +class C2: x = attr.ib() @attr.s(slots=True, getstate_setstate=True) -class C2Slots(object): +class C2Slots: x = attr.ib() -class TestPickle(object): +class TestPickle: @pytest.mark.parametrize("protocol", range(pickle.HIGHEST_PROTOCOL)) def test_pickleable_by_default(self, protocol): """ @@ -665,10 +630,12 @@ def test_no_getstate_setstate_for_dict_classes(self): As long as getstate_setstate is None, nothing is done to dict classes. """ - i = C1(1, 2) - - assert None is getattr(i, "__getstate__", None) - assert None is getattr(i, "__setstate__", None) + assert getattr(object, "__getstate__", None) is getattr( + C1, "__getstate__", None + ) + assert getattr(object, "__setstate__", None) is getattr( + C1, "__setstate__", None + ) def test_no_getstate_setstate_if_option_false(self): """ @@ -676,13 +643,15 @@ def test_no_getstate_setstate_if_option_false(self): """ @attr.s(slots=True, getstate_setstate=False) - class C(object): + class C: x = attr.ib() - i = C(42) - - assert None is getattr(i, "__getstate__", None) - assert None is getattr(i, "__setstate__", None) + assert getattr(object, "__getstate__", None) is getattr( + C, "__getstate__", None + ) + assert getattr(object, "__setstate__", None) is getattr( + C, "__setstate__", None + ) @pytest.mark.parametrize("cls", [C2(1), C2Slots(1)]) def test_getstate_set_state_force_true(self, cls): @@ -695,11 +664,11 @@ def test_getstate_set_state_force_true(self, cls): def test_slots_super_property_get(): """ - On Python 2/3: the `super(self.__class__, self)` works. + Both `super()` and `super(self.__class__, self)` work. """ @attr.s(slots=True) - class A(object): + class A: x = attr.ib() @property @@ -710,20 +679,27 @@ def f(self): class B(A): @property def f(self): - return super(B, self).f ** 2 + return super().f ** 2 + + @attr.s(slots=True) + class C(A): + @property + def f(self): + return super(C, self).f ** 2 # noqa: UP008 assert B(11).f == 121 assert B(17).f == 289 + assert C(11).f == 121 + assert C(17).f == 289 -@pytest.mark.skipif(PY2, reason="shortcut super() is PY3-only.") -def test_slots_super_property_get_shurtcut(): +def test_slots_super_property_get_shortcut(): """ - On Python 3, the `super()` shortcut is allowed. + The `super()` shortcut is allowed. """ @attr.s(slots=True) - class A(object): + class A: x = attr.ib() @property @@ -738,3 +714,438 @@ def f(self): assert B(11).f == 121 assert B(17).f == 289 + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_cached_property_allows_call(): + """ + cached_property in slotted class allows call. + """ + + @attr.s(slots=True) + class A: + x = attr.ib() + + @functools.cached_property + def f(self): + return self.x + + assert A(11).f == 11 + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_cached_property_class_does_not_have__dict__(): + """ + slotted class with cached property has no __dict__ attribute. + """ + + @attr.s(slots=True) + class A: + x = attr.ib() + + @functools.cached_property + def f(self): + return self.x + + assert set(A.__slots__) == {"x", "f", "__weakref__"} + assert "__dict__" not in dir(A) + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_cached_property_works_on_frozen_isntances(): + """ + Infers type of cached property. + """ + + @attrs.frozen(slots=True) + class A: + x: int + + @functools.cached_property + def f(self) -> int: + return self.x + + assert A(x=1).f == 1 + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_cached_property_infers_type(): + """ + Infers type of cached property. + """ + + @attrs.frozen(slots=True) + class A: + x: int + + @functools.cached_property + def f(self) -> int: + return self.x + + assert A.__annotations__ == {"x": int, "f": int} + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_cached_property_with_empty_getattr_raises_attribute_error_of_requested(): + """ + Ensures error information is not lost. + """ + + @attr.s(slots=True) + class A: + x = attr.ib() + + @functools.cached_property + def f(self): + return self.x + + a = A(1) + with pytest.raises( + AttributeError, match="'A' object has no attribute 'z'" + ): + a.z + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_cached_property_with_getattr_calls_getattr_for_missing_attributes(): + """ + Ensure __getattr__ implementation is maintained for non cached_properties. + """ + + @attr.s(slots=True) + class A: + x = attr.ib() + + @functools.cached_property + def f(self): + return self.x + + def __getattr__(self, item): + return item + + a = A(1) + assert a.f == 1 + assert a.z == "z" + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_getattr_in_superclass__is_called_for_missing_attributes_when_cached_property_present(): + """ + Ensure __getattr__ implementation is maintained in subclass. + """ + + @attr.s(slots=True) + class A: + x = attr.ib() + + def __getattr__(self, item): + return item + + @attr.s(slots=True) + class B(A): + @functools.cached_property + def f(self): + return self.x + + b = B(1) + assert b.f == 1 + assert b.z == "z" + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_getattr_in_subclass_gets_superclass_cached_property(): + """ + Ensure super() in __getattr__ is not broken through cached_property re-write. + """ + + @attr.s(slots=True) + class A: + x = attr.ib() + + @functools.cached_property + def f(self): + return self.x + + def __getattr__(self, item): + return item + + @attr.s(slots=True) + class B(A): + @functools.cached_property + def g(self): + return self.x + + def __getattr__(self, item): + return super().__getattr__(item) + + b = B(1) + assert b.f == 1 + assert b.z == "z" + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_sub_class_with_independent_cached_properties_both_work(): + """ + Subclassing shouldn't break cached properties. + """ + + @attr.s(slots=True) + class A: + x = attr.ib() + + @functools.cached_property + def f(self): + return self.x + + @attr.s(slots=True) + class B(A): + @functools.cached_property + def g(self): + return self.x * 2 + + assert B(1).f == 1 + assert B(1).g == 2 + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_with_multiple_cached_property_subclasses_works(): + """ + Multiple sub-classes shouldn't break cached properties. + """ + + @attr.s(slots=True) + class A: + x = attr.ib(kw_only=True) + + @functools.cached_property + def f(self): + return self.x + + @attr.s(slots=False) + class B: + @functools.cached_property + def g(self): + return self.x * 2 + + def __getattr__(self, item): + if hasattr(super(), "__getattr__"): + return super().__getattr__(item) + return item + + @attr.s(slots=True) + class AB(A, B): + pass + + ab = AB(x=1) + + assert ab.f == 1 + assert ab.g == 2 + assert ab.h == "h" + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_sub_class_avoids_duplicated_slots(): + """ + Duplicating the slots is a waste of memory. + """ + + @attr.s(slots=True) + class A: + x = attr.ib() + + @functools.cached_property + def f(self): + return self.x + + @attr.s(slots=True) + class B(A): + @functools.cached_property + def f(self): + return self.x * 2 + + assert B(1).f == 2 + assert B.__slots__ == () + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_sub_class_with_actual_slot(): + """ + A sub-class can have an explicit attrs field that replaces a cached property. + """ + + @attr.s(slots=True) + class A: # slots : (x, f) + x = attr.ib() + + @functools.cached_property + def f(self): + return self.x + + @attr.s(slots=True) + class B(A): + f: int = attr.ib() + + assert B(1, 2).f == 2 + assert B.__slots__ == () + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_cached_property_is_not_called_at_construction(): + """ + A cached property function should only be called at property access point. + """ + call_count = 0 + + @attr.s(slots=True) + class A: + x = attr.ib() + + @functools.cached_property + def f(self): + nonlocal call_count + call_count += 1 + return self.x + + A(1) + assert call_count == 0 + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_cached_property_repeat_call_only_once(): + """ + A cached property function should be called only once, on repeated attribute access. + """ + call_count = 0 + + @attr.s(slots=True) + class A: + x = attr.ib() + + @functools.cached_property + def f(self): + nonlocal call_count + call_count += 1 + return self.x + + obj = A(1) + obj.f + obj.f + assert call_count == 1 + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_cached_property_called_independent_across_instances(): + """ + A cached property value should be specific to the given instance. + """ + + @attr.s(slots=True) + class A: + x = attr.ib() + + @functools.cached_property + def f(self): + return self.x + + obj_1 = A(1) + obj_2 = A(2) + + assert obj_1.f == 1 + assert obj_2.f == 2 + + +@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+") +def test_slots_cached_properties_work_independently(): + """ + Multiple cached properties should work independently. + """ + + @attr.s(slots=True) + class A: + x = attr.ib() + + @functools.cached_property + def f_1(self): + return self.x + + @functools.cached_property + def f_2(self): + return self.x * 2 + + obj = A(1) + + assert obj.f_1 == 1 + assert obj.f_2 == 2 + + +@attr.s(slots=True) +class A: + x = attr.ib() + b = attr.ib() + c = attr.ib() + + +def test_slots_unpickle_after_attr_removed(): + """ + We don't assign attributes we don't have anymore if the class has + removed it. + """ + a = A(1, 2, 3) + a_pickled = pickle.dumps(a) + a_unpickled = pickle.loads(a_pickled) + assert a_unpickled == a + + @attr.s(slots=True) + class NEW_A: + x = attr.ib() + c = attr.ib() + + with mock.patch(f"{__name__}.A", NEW_A): + new_a = pickle.loads(a_pickled) + + assert new_a.x == 1 + assert new_a.c == 3 + assert not hasattr(new_a, "b") + + +def test_slots_unpickle_after_attr_added(frozen): + """ + We don't assign attribute we haven't had before if the class has one added. + """ + a = A(1, 2, 3) + a_pickled = pickle.dumps(a) + a_unpickled = pickle.loads(a_pickled) + + assert a_unpickled == a + + @attr.s(slots=True, frozen=frozen) + class NEW_A: + x = attr.ib() + b = attr.ib() + d = attr.ib() + c = attr.ib() + + with mock.patch(f"{__name__}.A", NEW_A): + new_a = pickle.loads(a_pickled) + + assert new_a.x == 1 + assert new_a.b == 2 + assert new_a.c == 3 + assert not hasattr(new_a, "d") + + +def test_slots_unpickle_is_backward_compatible(frozen): + """ + Ensure object pickled before v22.2.0 can still be unpickled. + """ + a = A(1, 2, 3) + + a_pickled = ( + b"\x80\x04\x95&\x00\x00\x00\x00\x00\x00\x00\x8c\x10" + + a.__module__.encode() + + b"\x94\x8c\x01A\x94\x93\x94)\x81\x94K\x01K\x02K\x03\x87\x94b." + ) + + a_unpickled = pickle.loads(a_pickled) + + assert a_unpickled == a diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_utils.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_utils.py new file mode 100644 index 0000000000000..92c04a1b503f4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_utils.py @@ -0,0 +1,19 @@ +from .utils import simple_class + + +class TestSimpleClass: + """ + Tests for the testing helper function `make_class`. + """ + + def test_returns_class(self): + """ + Returns a class object. + """ + assert type is simple_class().__class__ + + def test_returns_distinct_classes(self): + """ + Each call returns a completely new class. + """ + assert simple_class() is not simple_class() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_validators.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_validators.py index d7c6de8bad793..4327f825188d5 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_validators.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_validators.py @@ -4,7 +4,6 @@ Tests for `attr.validators`. """ -from __future__ import absolute_import, division, print_function import re @@ -14,8 +13,8 @@ from attr import _config, fields, has from attr import validators as validator_module -from attr._compat import PY2, TYPE from attr.validators import ( + _subclass_of, and_, deep_iterable, deep_mapping, @@ -28,6 +27,8 @@ lt, matches_re, max_len, + min_len, + not_, optional, provides, ) @@ -48,9 +49,9 @@ def zope_interface(): return zope.interface -class TestDisableValidators(object): +class TestDisableValidators: @pytest.fixture(autouse=True) - def reset_default(self): + def _reset_default(self): """ Make sure validators are always enabled after a test. """ @@ -63,8 +64,10 @@ def test_default(self): """ assert _config._run_validators is True - @pytest.mark.parametrize("value, expected", [(True, False), (False, True)]) - def test_set_validators_diabled(self, value, expected): + @pytest.mark.parametrize( + ("value", "expected"), [(True, False), (False, True)] + ) + def test_set_validators_disabled(self, value, expected): """ Sets `_run_validators`. """ @@ -72,7 +75,9 @@ def test_set_validators_diabled(self, value, expected): assert _config._run_validators is expected - @pytest.mark.parametrize("value, expected", [(True, False), (False, True)]) + @pytest.mark.parametrize( + ("value", "expected"), [(True, False), (False, True)] + ) def test_disabled(self, value, expected): """ Returns `_run_validators`. @@ -99,16 +104,15 @@ def test_disabled_ctx_with_errors(self): """ assert _config._run_validators is True - with pytest.raises(ValueError): - with validator_module.disabled(): - assert _config._run_validators is False + with pytest.raises(ValueError), validator_module.disabled(): + assert _config._run_validators is False - raise ValueError("haha!") + raise ValueError("haha!") assert _config._run_validators is True -class TestInstanceOf(object): +class TestInstanceOf: """ Tests for `instance_of`. """ @@ -143,8 +147,7 @@ def test_fail(self): with pytest.raises(TypeError) as e: v(None, a, "42") assert ( - "'test' must be <{type} 'int'> (got '42' that is a <{type} " - "'str'>).".format(type=TYPE), + "'test' must be (got '42' that is a ).", a, int, "42", @@ -155,12 +158,10 @@ def test_repr(self): Returned validator has a useful `__repr__`. """ v = instance_of(int) - assert ( - ">".format(type=TYPE) - ) == repr(v) + assert (">") == repr(v) -class TestMatchesRe(object): +class TestMatchesRe: """ Tests for `matches_re`. """ @@ -177,7 +178,7 @@ def test_match(self): """ @attr.s - class ReTester(object): + class ReTester: str_match = attr.ib(validator=matches_re("a|ab")) ReTester("ab") # shouldn't raise exceptions @@ -194,7 +195,7 @@ def test_flags(self): """ @attr.s - class MatchTester(object): + class MatchTester: val = attr.ib(validator=matches_re("a", re.IGNORECASE, re.match)) MatchTester("A1") # test flags and using re.match @@ -206,7 +207,7 @@ def test_precompiled_pattern(self): pattern = re.compile("a") @attr.s - class RePatternTester(object): + class RePatternTester: val = attr.ib(validator=matches_re(pattern)) RePatternTester("a") @@ -228,7 +229,7 @@ def test_different_func(self): """ @attr.s - class SearchTester(object): + class SearchTester: val = attr.ib(validator=matches_re("a", 0, re.search)) SearchTester("bab") # re.search will match @@ -240,16 +241,10 @@ def test_catches_invalid_func(self): with pytest.raises(ValueError) as ei: matches_re("a", 0, lambda: None) - if not PY2: - assert ( - "'func' must be one of None, fullmatch, match, search." - == ei.value.args[0] - ) - else: - assert ( - "'func' must be one of None, match, search." - == ei.value.args[0] - ) + assert ( + "'func' must be one of None, fullmatch, match, search." + == ei.value.args[0] + ) @pytest.mark.parametrize( "func", [None, getattr(re, "fullmatch", None), re.match, re.search] @@ -282,7 +277,7 @@ def always_fail(_, __, ___): 0 / 0 -class TestAnd(object): +class TestAnd: def test_in_all(self): """ Verify that this validator is in ``__all__``. @@ -312,7 +307,7 @@ def test_sugar(self): """ @attr.s - class C(object): + class C: a1 = attr.ib("a1", validator=and_(instance_of(int))) a2 = attr.ib("a2", validator=[instance_of(int)]) @@ -336,7 +331,7 @@ def f(): return IFoo -class TestProvides(object): +class TestProvides: """ Tests for `provides`. """ @@ -353,11 +348,13 @@ def test_success(self, zope_interface, ifoo): """ @zope_interface.implementer(ifoo) - class C(object): + class C: def f(self): pass - v = provides(ifoo) + with pytest.deprecated_call(): + v = provides(ifoo) + v(None, simple_attr("x"), C()) def test_fail(self, ifoo): @@ -367,13 +364,14 @@ def test_fail(self, ifoo): value = object() a = simple_attr("x") - v = provides(ifoo) + with pytest.deprecated_call(): + v = provides(ifoo) + with pytest.raises(TypeError) as e: v(None, a, value) + assert ( - "'x' must provide {interface!r} which {value!r} doesn't.".format( - interface=ifoo, value=value - ), + f"'x' must provide {ifoo!r} which {value!r} doesn't.", a, ifoo, value, @@ -383,18 +381,21 @@ def test_repr(self, ifoo): """ Returned validator has a useful `__repr__`. """ - v = provides(ifoo) - assert ( - "".format( - interface=ifoo - ) - ) == repr(v) + with pytest.deprecated_call(): + v = provides(ifoo) + + assert (f"") == repr(v) @pytest.mark.parametrize( - "validator", [instance_of(int), [always_pass, instance_of(int)]] + "validator", + [ + instance_of(int), + [always_pass, instance_of(int)], + (always_pass, instance_of(int)), + ], ) -class TestOptional(object): +class TestOptional: """ Tests for `optional`. """ @@ -428,8 +429,7 @@ def test_fail(self, validator): with pytest.raises(TypeError) as e: v(None, a, "42") assert ( - "'test' must be <{type} 'int'> (got '42' that is a <{type} " - "'str'>).".format(type=TYPE), + "'test' must be (got '42' that is a ).", a, int, "42", @@ -444,18 +444,23 @@ def test_repr(self, validator): if isinstance(validator, list): repr_s = ( ">]) or None>" - ).format(func=repr(always_pass), type=TYPE) + ">]) or None>" + ).format(func=repr(always_pass)) + elif isinstance(validator, tuple): + repr_s = ( + ">)) or None>" + ).format(func=repr(always_pass)) else: repr_s = ( "> or None>" - ).format(type=TYPE) + "> or None>" + ) assert repr_s == repr(v) -class TestIn_(object): +class TestIn_: """ Tests for `in_`. """ @@ -480,9 +485,16 @@ def test_fail(self): """ v = in_([1, 2, 3]) a = simple_attr("test") + with pytest.raises(ValueError) as e: v(None, a, None) - assert ("'test' must be in [1, 2, 3] (got None)",) == e.value.args + + assert ( + "'test' must be in [1, 2, 3] (got None)", + a, + [1, 2, 3], + None, + ) == e.value.args def test_fail_with_string(self): """ @@ -493,17 +505,38 @@ def test_fail_with_string(self): a = simple_attr("test") with pytest.raises(ValueError) as e: v(None, a, None) - assert ("'test' must be in 'abc' (got None)",) == e.value.args + assert ( + "'test' must be in 'abc' (got None)", + a, + "abc", + None, + ) == e.value.args def test_repr(self): """ Returned validator has a useful `__repr__`. """ v = in_([3, 4, 5]) - assert (("")) == repr(v) + assert ("") == repr(v) -class TestDeepIterable(object): +@pytest.fixture( + name="member_validator", + params=( + instance_of(int), + [always_pass, instance_of(int)], + (always_pass, instance_of(int)), + ), + scope="module", +) +def _member_validator(request): + """ + Provides sample `member_validator`s for some tests in `TestDeepIterable` + """ + return request.param + + +class TestDeepIterable: """ Tests for `deep_iterable`. """ @@ -514,34 +547,34 @@ def test_in_all(self): """ assert deep_iterable.__name__ in validator_module.__all__ - def test_success_member_only(self): + def test_success_member_only(self, member_validator): """ If the member validator succeeds and the iterable validator is not set, nothing happens. """ - member_validator = instance_of(int) v = deep_iterable(member_validator) a = simple_attr("test") v(None, a, [42]) - def test_success_member_and_iterable(self): + def test_success_member_and_iterable(self, member_validator): """ If both the member and iterable validators succeed, nothing happens. """ - member_validator = instance_of(int) iterable_validator = instance_of(list) v = deep_iterable(member_validator, iterable_validator) a = simple_attr("test") v(None, a, [42]) @pytest.mark.parametrize( - "member_validator, iterable_validator", - ( + ("member_validator", "iterable_validator"), + [ (instance_of(int), 42), (42, instance_of(list)), (42, 42), (42, None), - ), + ([instance_of(int), 42], 42), + ([42, instance_of(int)], 42), + ], ) def test_noncallable_validators( self, member_validator, iterable_validator @@ -552,8 +585,8 @@ def test_noncallable_validators( with pytest.raises(TypeError) as e: deep_iterable(member_validator, iterable_validator) value = 42 - message = "must be callable (got {value} that is a {type_}).".format( - value=value, type_=value.__class__ + message = ( + f"must be callable (got {value} that is a {value.__class__})." ) assert message in e.value.args[0] @@ -561,17 +594,16 @@ def test_noncallable_validators( assert message in e.value.msg assert value == e.value.value - def test_fail_invalid_member(self): + def test_fail_invalid_member(self, member_validator): """ Raise member validator error if an invalid member is found. """ - member_validator = instance_of(int) v = deep_iterable(member_validator) a = simple_attr("test") with pytest.raises(TypeError): v(None, a, [42, "42"]) - def test_fail_invalid_iterable(self): + def test_fail_invalid_iterable(self, member_validator): """ Raise iterable validator error if an invalid iterable is found. """ @@ -582,12 +614,11 @@ def test_fail_invalid_iterable(self): with pytest.raises(TypeError): v(None, a, [42]) - def test_fail_invalid_member_and_iterable(self): + def test_fail_invalid_member_and_iterable(self, member_validator): """ Raise iterable validator error if both the iterable and a member are invalid. """ - member_validator = instance_of(int) iterable_validator = instance_of(tuple) v = deep_iterable(member_validator, iterable_validator) a = simple_attr("test") @@ -600,14 +631,29 @@ def test_repr_member_only(self): when only member validator is set. """ member_validator = instance_of(int) - member_repr = ">".format( - type=TYPE + member_repr = ">" + v = deep_iterable(member_validator) + expected_repr = ( + f"" + ) + assert expected_repr == repr(v) + + def test_repr_member_only_sequence(self): + """ + Returned validator has a useful `__repr__` + when only member validator is set and the member validator is a list of + validators + """ + member_validator = [always_pass, instance_of(int)] + member_repr = ( + f"_AndValidator(_validators=({always_pass!r}, " + ">))" ) v = deep_iterable(member_validator) expected_repr = ( - "" - ).format(member_repr=member_repr) - assert ((expected_repr)) == repr(v) + f"" + ) + assert expected_repr == repr(v) def test_repr_member_and_iterable(self): """ @@ -615,22 +661,39 @@ def test_repr_member_and_iterable(self): and iterable validators are set. """ member_validator = instance_of(int) - member_repr = ">".format( - type=TYPE + member_repr = ">" + iterable_validator = instance_of(list) + iterable_repr = ">" + v = deep_iterable(member_validator, iterable_validator) + expected_repr = ( + "" + ) + assert expected_repr == repr(v) + + def test_repr_sequence_member_and_iterable(self): + """ + Returned validator has a useful `__repr__` when both member + and iterable validators are set and the member validator is a list of + validators + """ + member_validator = [always_pass, instance_of(int)] + member_repr = ( + f"_AndValidator(_validators=({always_pass!r}, " + ">))" ) iterable_validator = instance_of(list) - iterable_repr = ( - ">" - ).format(type=TYPE) + iterable_repr = ">" v = deep_iterable(member_validator, iterable_validator) expected_repr = ( "" - ).format(iterable_repr=iterable_repr, member_repr=member_repr) + f" {iterable_repr} iterables of {member_repr}>" + ) + assert expected_repr == repr(v) -class TestDeepMapping(object): +class TestDeepMapping: """ Tests for `deep_mapping`. """ @@ -652,14 +715,14 @@ def test_success(self): v(None, a, {"a": 6, "b": 7}) @pytest.mark.parametrize( - "key_validator, value_validator, mapping_validator", - ( + ("key_validator", "value_validator", "mapping_validator"), + [ (42, instance_of(int), None), (instance_of(str), 42, None), (instance_of(str), instance_of(int), 42), (42, 42, None), (42, 42, 42), - ), + ], ) def test_noncallable_validators( self, key_validator, value_validator, mapping_validator @@ -671,8 +734,8 @@ def test_noncallable_validators( deep_mapping(key_validator, value_validator, mapping_validator) value = 42 - message = "must be callable (got {value} that is a {type_}).".format( - value=value, type_=value.__class__ + message = ( + f"must be callable (got {value} that is a {value.__class__})." ) assert message in e.value.args[0] @@ -719,22 +782,18 @@ def test_repr(self): Returned validator has a useful `__repr__`. """ key_validator = instance_of(str) - key_repr = ">".format( - type=TYPE - ) + key_repr = ">" value_validator = instance_of(int) - value_repr = ">".format( - type=TYPE - ) + value_repr = ">" v = deep_mapping(key_validator, value_validator) expected_repr = ( "" - ).format(key_repr=key_repr, value_repr=value_repr) + f"{key_repr} to {value_repr}>" + ) assert expected_repr == repr(v) -class TestIsCallable(object): +class TestIsCallable: """ Tests for `is_callable`. """ @@ -803,7 +862,7 @@ def test_hashability(): class TestLtLeGeGt: """ - Tests for `max_len`. + Tests for `Lt, Le, Ge, Gt`. """ BOUND = 4 @@ -824,13 +883,13 @@ def test_retrieve_bound(self, v): """ @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=v(self.BOUND)) assert fields(Tester).value.validator.bound == self.BOUND @pytest.mark.parametrize( - "v, value", + ("v", "value"), [ (lt, 3), (le, 3), @@ -844,13 +903,13 @@ def test_check_valid(self, v, value): """Silent if value {op} bound.""" @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=v(self.BOUND)) Tester(value) # shouldn't raise exceptions @pytest.mark.parametrize( - "v, value", + ("v", "value"), [ (lt, 4), (le, 5), @@ -862,7 +921,7 @@ def test_check_invalid(self, v, value): """Raise ValueError if value {op} bound.""" @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=v(self.BOUND)) with pytest.raises(ValueError): @@ -874,9 +933,7 @@ def test_repr(self, v): __repr__ is meaningful. """ nv = v(23) - assert repr(nv) == "".format( - op=nv.compare_op, bound=23 - ) + assert repr(nv) == f"" class TestMaxLen: @@ -898,7 +955,7 @@ def test_retrieve_max_len(self): """ @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=max_len(self.MAX_LENGTH)) assert fields(Tester).value.validator.max_length == self.MAX_LENGTH @@ -921,7 +978,7 @@ def test_check_valid(self, value): """ @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=max_len(self.MAX_LENGTH)) Tester(value) # shouldn't raise exceptions @@ -939,7 +996,7 @@ def test_check_invalid(self, value): """ @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=max_len(self.MAX_LENGTH)) with pytest.raises(ValueError): @@ -950,3 +1007,344 @@ def test_repr(self): __repr__ is meaningful. """ assert repr(max_len(23)) == "" + + +class TestMinLen: + """ + Tests for `min_len`. + """ + + MIN_LENGTH = 2 + + def test_in_all(self): + """ + validator is in ``__all__``. + """ + assert min_len.__name__ in validator_module.__all__ + + def test_retrieve_min_len(self): + """ + The configured min. length can be extracted from the Attribute + """ + + @attr.s + class Tester: + value = attr.ib(validator=min_len(self.MIN_LENGTH)) + + assert fields(Tester).value.validator.min_length == self.MIN_LENGTH + + @pytest.mark.parametrize( + "value", + [ + "foo", + "spam", + list(range(MIN_LENGTH)), + {"spam": 3, "eggs": 4}, + ], + ) + def test_check_valid(self, value): + """ + Silent if len(value) => min_len. + Values can be strings and other iterables. + """ + + @attr.s + class Tester: + value = attr.ib(validator=min_len(self.MIN_LENGTH)) + + Tester(value) # shouldn't raise exceptions + + @pytest.mark.parametrize( + "value", + [ + "", + list(range(1)), + ], + ) + def test_check_invalid(self, value): + """ + Raise ValueError if len(value) < min_len. + """ + + @attr.s + class Tester: + value = attr.ib(validator=min_len(self.MIN_LENGTH)) + + with pytest.raises(ValueError): + Tester(value) + + def test_repr(self): + """ + __repr__ is meaningful. + """ + assert repr(min_len(23)) == "" + + +class TestSubclassOf: + """ + Tests for `_subclass_of`. + """ + + def test_success(self): + """ + Nothing happens if classes match. + """ + v = _subclass_of(int) + v(None, simple_attr("test"), int) + + def test_subclass(self): + """ + Subclasses are accepted too. + """ + v = _subclass_of(int) + # yep, bools are a subclass of int :( + v(None, simple_attr("test"), bool) + + def test_fail(self): + """ + Raises `TypeError` on wrong types. + """ + v = _subclass_of(int) + a = simple_attr("test") + with pytest.raises(TypeError) as e: + v(None, a, str) + assert ( + "'test' must be a subclass of (got ).", + a, + int, + str, + ) == e.value.args + + def test_repr(self): + """ + Returned validator has a useful `__repr__`. + """ + v = _subclass_of(int) + assert (">") == repr(v) + + +class TestNot_: + """ + Tests for `not_`. + """ + + DEFAULT_EXC_TYPES = (ValueError, TypeError) + + def test_not_all(self): + """ + The validator is in ``__all__``. + """ + assert not_.__name__ in validator_module.__all__ + + def test_repr(self): + """ + Returned validator has a useful `__repr__`. + """ + wrapped = in_([3, 4, 5]) + + v = not_(wrapped) + + assert ( + f"" + ) == repr(v) + + def test_success_because_fails(self): + """ + If the wrapped validator fails, we're happy. + """ + + def always_fails(inst, attr, value): + raise ValueError("always fails") + + v = not_(always_fails) + a = simple_attr("test") + input_value = 3 + + v(1, a, input_value) + + def test_fails_because_success(self): + """ + If the wrapped validator doesn't fail, not_ should fail. + """ + + def always_passes(inst, attr, value): + pass + + v = not_(always_passes) + a = simple_attr("test") + input_value = 3 + + with pytest.raises(ValueError) as e: + v(1, a, input_value) + + assert ( + ( + "not_ validator child '{!r}' did not raise a captured error" + ).format(always_passes), + a, + always_passes, + input_value, + self.DEFAULT_EXC_TYPES, + ) == e.value.args + + def test_composable_with_in_pass(self): + """ + Check something is ``not in`` something else. + """ + v = not_(in_("abc")) + a = simple_attr("test") + input_value = "d" + + v(None, a, input_value) + + def test_composable_with_in_fail(self): + """ + Check something is ``not in`` something else, but it is, so fail. + """ + wrapped = in_("abc") + v = not_(wrapped) + a = simple_attr("test") + input_value = "b" + + with pytest.raises(ValueError) as e: + v(None, a, input_value) + + assert ( + ( + "not_ validator child '{!r}' did not raise a captured error" + ).format(in_("abc")), + a, + wrapped, + input_value, + self.DEFAULT_EXC_TYPES, + ) == e.value.args + + def test_composable_with_matches_re_pass(self): + """ + Check something does not match a regex. + """ + v = not_(matches_re("[a-z]{3}")) + a = simple_attr("test") + input_value = "spam" + + v(None, a, input_value) + + def test_composable_with_matches_re_fail(self): + """ + Check something does not match a regex, but it does, so fail. + """ + wrapped = matches_re("[a-z]{3}") + v = not_(wrapped) + a = simple_attr("test") + input_value = "egg" + + with pytest.raises(ValueError) as e: + v(None, a, input_value) + + assert ( + ( + f"not_ validator child '{wrapped!r}' did not raise a captured error" + ), + a, + wrapped, + input_value, + self.DEFAULT_EXC_TYPES, + ) == e.value.args + + def test_composable_with_instance_of_pass(self): + """ + Check something is not a type. This validator raises a TypeError, + rather than a ValueError like the others. + """ + v = not_(instance_of((int, float))) + a = simple_attr("test") + + v(None, a, "spam") + + def test_composable_with_instance_of_fail(self): + """ + Check something is not a type, but it is, so fail. + """ + wrapped = instance_of((int, float)) + v = not_(wrapped) + a = simple_attr("test") + input_value = 2.718281828 + + with pytest.raises(ValueError) as e: + v(None, a, input_value) + + assert ( + ( + "not_ validator child '{!r}' did not raise a captured error" + ).format(instance_of((int, float))), + a, + wrapped, + input_value, + self.DEFAULT_EXC_TYPES, + ) == e.value.args + + def test_custom_capture_match(self): + """ + Match a custom exception provided to `not_` + """ + v = not_(in_("abc"), exc_types=ValueError) + a = simple_attr("test") + + v(None, a, "d") + + def test_custom_capture_miss(self): + """ + If the exception doesn't match, the underlying raise comes through + """ + + class MyError(Exception): + """:(""" + + wrapped = in_("abc") + v = not_(wrapped, exc_types=MyError) + a = simple_attr("test") + input_value = "d" + + with pytest.raises(ValueError) as e: + v(None, a, input_value) + + # get the underlying exception to compare + with pytest.raises(Exception) as e_from_wrapped: + wrapped(None, a, input_value) + assert e_from_wrapped.value.args == e.value.args + + def test_custom_msg(self): + """ + If provided, use the custom message in the raised error + """ + custom_msg = "custom message!" + wrapped = in_("abc") + v = not_(wrapped, msg=custom_msg) + a = simple_attr("test") + input_value = "a" + + with pytest.raises(ValueError) as e: + v(None, a, input_value) + + assert ( + custom_msg, + a, + wrapped, + input_value, + self.DEFAULT_EXC_TYPES, + ) == e.value.args + + def test_bad_exception_args(self): + """ + Malformed exception arguments + """ + wrapped = in_("abc") + + with pytest.raises(TypeError) as e: + not_(wrapped, exc_types=(str, int)) + + assert ( + "'exc_types' must be a subclass of " + "(got )." + ) == e.value.args[0] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_version_info.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_version_info.py index 41f75f47a6d87..5bd101bcce7d3 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_version_info.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_version_info.py @@ -1,11 +1,9 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function import pytest from attr import VersionInfo -from attr._compat import PY2 @pytest.fixture(name="vi") @@ -29,9 +27,6 @@ def test_suffix_is_preserved(self): == VersionInfo._from_version_string("19.2.0.dev0").releaselevel ) - @pytest.mark.skipif( - PY2, reason="Python 2 is too YOLO to care about comparability." - ) @pytest.mark.parametrize("other", [(), (19, 2, 0, "final", "garbage")]) def test_wrong_len(self, vi, other): """ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/typing_example.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/typing_example.py index a85c768c104ab..2124912c8d58e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/typing_example.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/typing_example.py @@ -1,8 +1,10 @@ # SPDX-License-Identifier: MIT +from __future__ import annotations + import re -from typing import Any, Dict, List, Tuple, Union +from typing import Any, Dict, List, Tuple import attr import attrs @@ -49,12 +51,12 @@ class CC: @attr.s class DD: - x: List[int] = attr.ib() + x: list[int] = attr.ib() @attr.s class EE: - y: "List[int]" = attr.ib() + y: "list[int]" = attr.ib() @attr.s @@ -119,6 +121,17 @@ class Error2(Exception): e.args str(e) +# Field aliases + + +@attrs.define +class AliasExample: + without_alias: int + _with_alias: int = attr.ib(alias="_with_alias") + + +attr.fields(AliasExample).without_alias.alias +attr.fields(AliasExample)._with_alias.alias # Converters # XXX: Currently converters can only be functions so none of this works @@ -165,7 +178,7 @@ class Validated: attr.validators.instance_of(C), attr.validators.instance_of(list) ), ) - a = attr.ib( + aa = attr.ib( type=Tuple[C], validator=attr.validators.deep_iterable( attr.validators.instance_of(C), attr.validators.instance_of(tuple) @@ -199,13 +212,40 @@ class Validated: # Test different forms of instance_of g: int = attr.ib(validator=attr.validators.instance_of(int)) h: int = attr.ib(validator=attr.validators.instance_of((int,))) - j: Union[int, str] = attr.ib( - validator=attr.validators.instance_of((int, str)) - ) - k: Union[int, str, C] = attr.ib( + j: int | str = attr.ib(validator=attr.validators.instance_of((int, str))) + k: int | str | C = attr.ib( validator=attrs.validators.instance_of((int, C, str)) ) + l: Any = attr.ib( + validator=attr.validators.not_(attr.validators.in_("abc")) + ) + m: Any = attr.ib( + validator=attr.validators.not_( + attr.validators.in_("abc"), exc_types=ValueError + ) + ) + n: Any = attr.ib( + validator=attr.validators.not_( + attr.validators.in_("abc"), exc_types=(ValueError,) + ) + ) + o: Any = attr.ib( + validator=attr.validators.not_(attr.validators.in_("abc"), msg="spam") + ) + p: Any = attr.ib( + validator=attr.validators.not_(attr.validators.in_("abc"), msg=None) + ) + q: Any = attr.ib( + validator=attrs.validators.optional(attrs.validators.instance_of(C)) + ) + r: Any = attr.ib( + validator=attrs.validators.optional([attrs.validators.instance_of(C)]) + ) + s: Any = attr.ib( + validator=attrs.validators.optional((attrs.validators.instance_of(C),)) + ) + @attr.define class Validated2: @@ -284,14 +324,14 @@ class ValidatedSetter2: # field_transformer -def ft_hook(cls: type, attribs: List[attr.Attribute]) -> List[attr.Attribute]: +def ft_hook(cls: type, attribs: list[attr.Attribute]) -> list[attr.Attribute]: return attribs # field_transformer def ft_hook2( - cls: type, attribs: List[attrs.Attribute] -) -> List[attrs.Attribute]: + cls: type, attribs: list[attrs.Attribute] +) -> list[attrs.Attribute]: return attribs @@ -355,16 +395,16 @@ class MRO: @attr.s class FactoryTest: - a: List[int] = attr.ib(default=attr.Factory(list)) - b: List[Any] = attr.ib(default=attr.Factory(list, False)) - c: List[int] = attr.ib(default=attr.Factory((lambda s: s.a), True)) + a: list[int] = attr.ib(default=attr.Factory(list)) + b: list[Any] = attr.ib(default=attr.Factory(list, False)) + c: list[int] = attr.ib(default=attr.Factory((lambda s: s.a), True)) @attrs.define class FactoryTest2: - a: List[int] = attrs.field(default=attrs.Factory(list)) - b: List[Any] = attrs.field(default=attrs.Factory(list, False)) - c: List[int] = attrs.field(default=attrs.Factory((lambda s: s.a), True)) + a: list[int] = attrs.field(default=attrs.Factory(list)) + b: list[Any] = attrs.field(default=attrs.Factory(list, False)) + c: list[int] = attrs.field(default=attrs.Factory((lambda s: s.a), True)) attrs.asdict(FactoryTest2()) @@ -394,27 +434,42 @@ class MatchArgs2: attrs.astuple(MatchArgs2(1, 2)) -def importing_from_attr() -> None: +def accessing_from_attr() -> None: """ Use a function to keep the ns clean. """ - from attr.converters import optional - from attr.exceptions import FrozenError - from attr.filters import include - from attr.setters import frozen - from attr.validators import and_ - - assert optional and FrozenError and include and frozen and and_ + attr.converters.optional + attr.exceptions.FrozenError + attr.filters.include + attr.filters.exclude + attr.setters.frozen + attr.validators.and_ + attr.cmp_using -def importing_from_attrs() -> None: +def accessing_from_attrs() -> None: """ Use a function to keep the ns clean. """ - from attrs.converters import optional - from attrs.exceptions import FrozenError - from attrs.filters import include - from attrs.setters import frozen - from attrs.validators import and_ + attrs.converters.optional + attrs.exceptions.FrozenError + attrs.filters.include + attrs.filters.exclude + attrs.setters.frozen + attrs.validators.and_ + attrs.cmp_using + + +foo = object +if attrs.has(foo) or attr.has(foo): + foo.__attrs_attrs__ + + +@attrs.define(unsafe_hash=True) +class Hashable: + pass + - assert optional and FrozenError and include and frozen and and_ +def test(cls: type) -> None: + if attr.has(cls): + attr.resolve_types(cls) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/utils.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/utils.py index a2fefbd6068b4..9e678f05f17c6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/utils.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/utils.py @@ -4,10 +4,9 @@ Common helper functions for tests. """ -from __future__ import absolute_import, division, print_function from attr import Attribute -from attr._make import NOTHING, make_class +from attr._make import NOTHING, _default_init_alias_for, make_class def simple_class( @@ -65,22 +64,5 @@ def simple_attr( converter=converter, kw_only=kw_only, inherited=inherited, + alias=_default_init_alias_for(name), ) - - -class TestSimpleClass(object): - """ - Tests for the testing helper function `make_class`. - """ - - def test_returns_class(self): - """ - Returns a class object. - """ - assert type is simple_class().__class__ - - def returns_distinct_classes(self): - """ - Each call returns a completely new class. - """ - assert simple_class() is not simple_class() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/w3c-import.log new file mode 100644 index 0000000000000..ca53f7573dc27 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/w3c-import.log @@ -0,0 +1,47 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/attr_import_star.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/dataclass_transform_example.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/strategies.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_3rd_party.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_abc.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_annotations.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_cmp.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_compat.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_config.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_converters.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_dunders.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_filters.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_funcs.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_functional.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_hooks.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_import.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_init_subclass.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_make.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_mypy.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_next_gen.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_packaging.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_pattern_matching.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_pyright.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_setattr.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_slots.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_utils.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_validators.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/test_version_info.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/typing_example.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tests/utils.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tox.ini b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tox.ini index ddcbc4dbbcd99..54724faaafbf6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tox.ini +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tox.ini @@ -1,129 +1,114 @@ -[pytest] -addopts = -ra -testpaths = tests -xfail_strict = true -filterwarnings = - once::Warning - ignore:::pympler[.*] - - -# Keep docs in sync with docs env and .readthedocs.yml. -[gh-actions] -python = - 2.7: py27 - 3.5: py35 - 3.6: py36 - 3.7: py37 - 3.8: py38, changelog - 3.9: py39, pyright - 3.10: py310, manifest, typing, docs - pypy-2: pypy - pypy-3: pypy3 - - [tox] -envlist = typing,pre-commit,py27,py35,py36,py37,py38,py39,py310,pypy,pypy3,pyright,manifest,docs,pypi-description,changelog,coverage-report -isolated_build = True +min_version = 4 +env_list = + pre-commit, + py3{7,8,9,10,11,12}-tests, + py3{8,9,10,11,12}-mypy, + pypy3, + pyright, + docs, + changelog, + coverage-report -[testenv:docs] -# Keep basepython in sync with gh-actions and .readthedocs.yml. -basepython = python3.10 -extras = docs -commands = - sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs docs/_build/html - sphinx-build -n -T -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html - python -m doctest README.rst +[testenv:.pkg] +pass_env = SETUPTOOLS_SCM_PRETEND_VERSION [testenv] -extras = tests -commands = python -m pytest {posargs} - - -[testenv:py27] -extras = tests -commands = coverage run -m pytest {posargs} - - -[testenv:py37] -extras = tests -commands = coverage run -m pytest {posargs} - +package = wheel +wheel_build_env = .pkg +pass_env = + FORCE_COLOR + NO_COLOR +extras = + tests: tests + mypy: tests-mypy +commands = + tests: pytest {posargs:-n auto} + mypy: mypy tests/typing_example.py + mypy: mypy src/attrs/__init__.pyi src/attr/__init__.pyi src/attr/_typing_compat.pyi src/attr/_version_info.pyi src/attr/converters.pyi src/attr/exceptions.pyi src/attr/filters.pyi src/attr/setters.pyi src/attr/validators.pyi -[testenv:py310] +[testenv:py3{7,10,12}-tests] +extras = cov # Python 3.6+ has a number of compile-time warnings on invalid string escapes. -# PYTHONWARNINGS=d and --no-compile below make them visible during the Tox run. -basepython = python3.10 -install_command = pip install --no-compile {opts} {packages} -setenv = +# PYTHONWARNINGS=d and --no-compile below make them visible during the tox run. +set_env = + COVERAGE_PROCESS_START={toxinidir}/pyproject.toml PYTHONWARNINGS=d -extras = tests -commands = coverage run -m pytest {posargs} +install_command = python -Im pip install --no-compile {opts} {packages} +commands_pre = python -c 'import pathlib; pathlib.Path("{env_site_packages_dir}/cov.pth").write_text("import coverage; coverage.process_startup()")' +commands = coverage run -m pytest {posargs:-n auto} [testenv:coverage-report] -basepython = python3.10 -depends = py27,py37,py310 +# Keep base_python in-sync with .python-version-default +base_python = py312 +depends = py3{7,10,11} skip_install = true -deps = coverage[toml]>=5.4 +deps = coverage[toml]>=5.3 commands = coverage combine coverage report -[testenv:pre-commit] -basepython = python3.10 -skip_install = true -deps = - pre-commit -passenv = HOMEPATH # needed on Windows +[testenv:docs] +# Keep base_python in-sync with ci.yml/docs and .readthedocs.yaml. +base_python = py312 +extras = docs commands = - pre-commit run --all-files + sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs docs/_build/html + sphinx-build -n -T -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html -[testenv:manifest] -basepython = python3.10 -deps = check-manifest -skip_install = true -commands = check-manifest +[testenv:docs-watch] +package = editable +base_python = {[testenv:docs]base_python} +extras = {[testenv:docs]extras} +deps = watchfiles +commands = + watchfiles \ + --ignore-paths docs/_build/ \ + 'sphinx-build -W -n --jobs auto -b html -d {envtmpdir}/doctrees docs docs/_build/html' \ + src \ + docs + + +[testenv:docs-linkcheck] +package = editable +base_python = {[testenv:docs]base_python} +extras = {[testenv:docs]extras} +commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs docs/_build/html -[testenv:pypi-description] -basepython = python3.8 +[testenv:pre-commit] skip_install = true -deps = - twine - pip >= 18.0.0 -commands = - pip wheel -w {envtmpdir}/build --no-deps . - twine check {envtmpdir}/build/* +deps = pre-commit +commands = pre-commit run --all-files [testenv:changelog] -basepython = python3.8 -deps = towncrier<21.3 +deps = towncrier skip_install = true -commands = towncrier --draft +commands = towncrier build --version main --draft -[testenv:typing] -basepython = python3.10 -deps = mypy>=0.902 -commands = - mypy src/attr/__init__.pyi src/attr/_version_info.pyi src/attr/converters.pyi src/attr/exceptions.pyi src/attr/filters.pyi src/attr/setters.pyi src/attr/validators.pyi - mypy tests/typing_example.py +[testenv:pyright] +extras = tests +deps = pyright +commands = pytest tests/test_pyright.py -vv -[testenv:pyright] -# Install and configure node and pyright -# This *could* be folded into a custom install_command -# Use nodeenv to configure node in the running tox virtual environment -# Seeing errors using "nodeenv -p" -# Use npm install -g to install "globally" into the virtual environment -basepython = python3.9 -deps = nodeenv +[testenv:docset] +deps = doc2dash +extras = docs +allowlist_externals = + rm + cp + tar commands = - nodeenv --prebuilt --node=lts --force {envdir} - npm install -g --no-package-lock --no-save pyright - pytest tests/test_pyright.py -vv + rm -rf attrs.docset attrs.tgz docs/_build + sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs docs/_build/html + doc2dash --index-page index.html --icon docs/_static/docset-icon.png --online-redirect-url https://www.attrs.org/en/latest/ docs/_build/html + cp docs/_static/docset-icon@2x.png attrs.docset/icon@2x.png + tar --exclude='.DS_Store' -cvzf attrs.tgz attrs.docset diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/w3c-import.log new file mode 100644 index 0000000000000..363d4d7d1ae64 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/CHANGELOG.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/CITATION.cff +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/README.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/pyproject.toml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/attrs/tox.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/certifi.egg-info/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/certifi.egg-info/w3c-import.log new file mode 100644 index 0000000000000..963ac02a117b6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/certifi.egg-info/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/certifi.egg-info/PKG-INFO +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/certifi.egg-info/SOURCES.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/certifi.egg-info/dependency_links.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/certifi.egg-info/not-zip-safe +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/certifi.egg-info/top_level.txt diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/certifi/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/certifi/w3c-import.log new file mode 100644 index 0000000000000..ae7ddab3cd42a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/certifi/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/certifi/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/certifi/__main__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/certifi/cacert.pem +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/certifi/core.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/w3c-import.log new file mode 100644 index 0000000000000..d4a1fbf76bc2b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/MANIFEST.in +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/PKG-INFO +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/setup.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/certifi/setup.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/CHANGES.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/CHANGES.rst new file mode 100644 index 0000000000000..ea8cbea8f7b95 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/CHANGES.rst @@ -0,0 +1,131 @@ +Version history +=============== + +This library adheres to `Semantic Versioning 2.0 `_. + +**1.2.1** + +- Updated the copying of ``__notes__`` to match CPython behavior (PR by CF Bolz-Tereick) +- Corrected the type annotation of the exception handler callback to accept a + ``BaseExceptionGroup`` instead of ``BaseException`` +- Fixed type errors on Python < 3.10 and the type annotation of ``suppress()`` + (PR by John Litborn) + +**1.2.0** + +- Added special monkeypatching if `Apport `_ has + overridden ``sys.excepthook`` so it will format exception groups correctly + (PR by John Litborn) +- Added a backport of ``contextlib.suppress()`` from Python 3.12.1 which also handles + suppressing exceptions inside exception groups +- Fixed bare ``raise`` in a handler reraising the original naked exception rather than + an exception group which is what is raised when you do a ``raise`` in an ``except*`` + handler + +**1.1.3** + +- ``catch()`` now raises a ``TypeError`` if passed an async exception handler instead of + just giving a ``RuntimeWarning`` about the coroutine never being awaited. (#66, PR by + John Litborn) +- Fixed plain ``raise`` statement in an exception handler callback to work like a + ``raise`` in an ``except*`` block +- Fixed new exception group not being chained to the original exception when raising an + exception group from exceptions raised in handler callbacks +- Fixed type annotations of the ``derive()``, ``subgroup()`` and ``split()`` methods to + match the ones in typeshed + +**1.1.2** + +- Changed handling of exceptions in exception group handler callbacks to not wrap a + single exception in an exception group, as per + `CPython issue 103590 `_ + +**1.1.1** + +- Worked around + `CPython issue #98778 `_, + ``urllib.error.HTTPError(..., fp=None)`` raises ``KeyError`` on unknown attribute + access, on affected Python versions. (PR by Zac Hatfield-Dodds) + +**1.1.0** + +- Backported upstream fix for gh-99553 (custom subclasses of ``BaseExceptionGroup`` that + also inherit from ``Exception`` should not be able to wrap base exceptions) +- Moved all initialization code to ``__new__()`` (thus matching Python 3.11 behavior) + +**1.0.4** + +- Fixed regression introduced in v1.0.3 where the code computing the suggestions would + assume that both the ``obj`` attribute of ``AttributeError`` is always available, even + though this is only true from Python 3.10 onwards + (#43; PR by Carl Friedrich Bolz-Tereick) + +**1.0.3** + +- Fixed monkey patching breaking suggestions (on a ``NameError`` or ``AttributeError``) + on Python 3.10 (#41; PR by Carl Friedrich Bolz-Tereick) + +**1.0.2** + +- Updated type annotations to match the ones in ``typeshed`` + +**1.0.1** + +- Fixed formatted traceback missing exceptions beyond 2 nesting levels of + ``__context__`` or ``__cause__`` + +**1.0.0** + +- Fixed + ``AttributeError: 'PatchedTracebackException' object has no attribute '__cause__'`` + on Python 3.10 (only) when a traceback is printed from an exception where an exception + group is set as the cause (#33) +- Fixed a loop in exception groups being rendered incorrectly (#35) +- Fixed the patched formatting functions (``format_exception()``etc.) not passing the + ``compact=True`` flag on Python 3.10 like the original functions do + +**1.0.0rc9** + +- Added custom versions of several ``traceback`` functions that work with exception + groups even if monkey patching was disabled or blocked + +**1.0.0rc8** + +- Don't monkey patch anything if ``sys.excepthook`` has been altered +- Fixed formatting of ``SyntaxError`` in the monkey patched + ``TracebackException.format_exception_only()`` method + +**1.0.0rc7** + +- **BACKWARDS INCOMPATIBLE** Changed ``catch()`` to not wrap an exception in an + exception group if only one exception arrived at ``catch()`` and it was not matched + with any handlers. This was to match the behavior of ``except*``. + +**1.0.0rc6** + +- **BACKWARDS INCOMPATIBLE** Changed ``catch()`` to match the behavior of ``except*``: + each handler will be called only once per key in the ``handlers`` dictionary, and with + an exception group as the argument. Handlers now also catch subclasses of the given + exception types, just like ``except*``. + +**1.0.0rc5** + +- Patch for ``traceback.TracebackException.format_exception_only()`` (PR by Zac Hatfield-Dodds) + +**1.0.0rc4** + +- Update `PEP 678`_ support to use ``.add_note()`` and ``__notes__`` (PR by Zac Hatfield-Dodds) + +**1.0.0rc3** + +- Added message about the number of sub-exceptions + +**1.0.0rc2** + +- Display and copy ``__note__`` (draft `PEP 678`_) if available (PR by Zac Hatfield-Dodds) + +.. _PEP 678: https://www.python.org/dev/peps/pep-0678/ + +**1.0.0rc1** + +- Initial release diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/LICENSE b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/LICENSE new file mode 100644 index 0000000000000..50d4fa5e68439 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/LICENSE @@ -0,0 +1,73 @@ +The MIT License (MIT) + +Copyright (c) 2022 Alex Grönholm + +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. + + +This project contains code copied from the Python standard library. +The following is the required license notice for those parts. + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/PKG-INFO b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/PKG-INFO new file mode 100644 index 0000000000000..2e8819b83b6cb --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/PKG-INFO @@ -0,0 +1,157 @@ +Metadata-Version: 2.1 +Name: exceptiongroup +Version: 1.2.1 +Summary: Backport of PEP 654 (exception groups) +Author-email: Alex Grönholm +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Typing :: Typed +Requires-Dist: pytest >= 6 ; extra == "test" +Project-URL: Changelog, https://github.com/agronholm/exceptiongroup/blob/main/CHANGES.rst +Project-URL: Issue Tracker, https://github.com/agronholm/exceptiongroup/issues +Project-URL: Source code, https://github.com/agronholm/exceptiongroup +Provides-Extra: test + +.. image:: https://github.com/agronholm/exceptiongroup/actions/workflows/test.yml/badge.svg + :target: https://github.com/agronholm/exceptiongroup/actions/workflows/test.yml + :alt: Build Status +.. image:: https://coveralls.io/repos/github/agronholm/exceptiongroup/badge.svg?branch=main + :target: https://coveralls.io/github/agronholm/exceptiongroup?branch=main + :alt: Code Coverage + +This is a backport of the ``BaseExceptionGroup`` and ``ExceptionGroup`` classes from +Python 3.11. + +It contains the following: + +* The ``exceptiongroup.BaseExceptionGroup`` and ``exceptiongroup.ExceptionGroup`` + classes +* A utility function (``exceptiongroup.catch()``) for catching exceptions possibly + nested in an exception group +* Patches to the ``TracebackException`` class that properly formats exception groups + (installed on import) +* An exception hook that handles formatting of exception groups through + ``TracebackException`` (installed on import) +* Special versions of some of the functions from the ``traceback`` module, modified to + correctly handle exception groups even when monkey patching is disabled, or blocked by + another custom exception hook: + + * ``traceback.format_exception()`` + * ``traceback.format_exception_only()`` + * ``traceback.print_exception()`` + * ``traceback.print_exc()`` +* A backported version of ``contextlib.suppress()`` from Python 3.12.1 which also + handles suppressing exceptions inside exception groups + +If this package is imported on Python 3.11 or later, the built-in implementations of the +exception group classes are used instead, ``TracebackException`` is not monkey patched +and the exception hook won't be installed. + +See the `standard library documentation`_ for more information on exception groups. + +.. _standard library documentation: https://docs.python.org/3/library/exceptions.html + +Catching exceptions +=================== + +Due to the lack of the ``except*`` syntax introduced by `PEP 654`_ in earlier Python +versions, you need to use ``exceptiongroup.catch()`` to catch exceptions that are +potentially nested inside an exception group. This function returns a context manager +that calls the given handler for any exceptions matching the sole argument. + +The argument to ``catch()`` must be a dict (or any ``Mapping``) where each key is either +an exception class or an iterable of exception classes. Each value must be a callable +that takes a single positional argument. The handler will be called at most once, with +an exception group as an argument which will contain all the exceptions that are any +of the given types, or their subclasses. The exception group may contain nested groups +containing more matching exceptions. + +Thus, the following Python 3.11+ code: + +.. code-block:: python + + try: + ... + except* (ValueError, KeyError) as excgroup: + for exc in excgroup.exceptions: + print('Caught exception:', type(exc)) + except* RuntimeError: + print('Caught runtime error') + +would be written with this backport like this: + +.. code-block:: python + + from exceptiongroup import BaseExceptionGroup, catch + + def value_key_err_handler(excgroup: BaseExceptionGroup) -> None: + for exc in excgroup.exceptions: + print('Caught exception:', type(exc)) + + def runtime_err_handler(exc: BaseExceptionGroup) -> None: + print('Caught runtime error') + + with catch({ + (ValueError, KeyError): value_key_err_handler, + RuntimeError: runtime_err_handler + }): + ... + +**NOTE**: Just like with ``except*``, you cannot handle ``BaseExceptionGroup`` or +``ExceptionGroup`` with ``catch()``. + +Suppressing exceptions +====================== + +This library contains a backport of the ``contextlib.suppress()`` context manager from +Python 3.12.1. It allows you to selectively ignore certain exceptions, even when they're +inside exception groups: + +.. code-block:: python + + from exceptiongroup import suppress + + with suppress(RuntimeError): + raise ExceptionGroup("", [RuntimeError("boo")]) + +Notes on monkey patching +======================== + +To make exception groups render properly when an unhandled exception group is being +printed out, this package does two things when it is imported on any Python version +earlier than 3.11: + +#. The ``traceback.TracebackException`` class is monkey patched to store extra + information about exception groups (in ``__init__()``) and properly format them (in + ``format()``) +#. An exception hook is installed at ``sys.excepthook``, provided that no other hook is + already present. This hook causes the exception to be formatted using + ``traceback.TracebackException`` rather than the built-in rendered. + +If ``sys.exceptionhook`` is found to be set to something else than the default when +``exceptiongroup`` is imported, no monkeypatching is done at all. + +To prevent the exception hook and patches from being installed, set the environment +variable ``EXCEPTIONGROUP_NO_PATCH`` to ``1``. + +Formatting exception groups +--------------------------- + +Normally, the monkey patching applied by this library on import will cause exception +groups to be printed properly in tracebacks. But in cases when the monkey patching is +blocked by a third party exception hook, or monkey patching is explicitly disabled, +you can still manually format exceptions using the special versions of the ``traceback`` +functions, like ``format_exception()``, listed at the top of this page. They work just +like their counterparts in the ``traceback`` module, except that they use a separately +patched subclass of ``TracebackException`` to perform the rendering. + +Particularly in cases where a library installs its own exception hook, it is recommended +to use these special versions to do the actual formatting of exceptions/tracebacks. + +.. _PEP 654: https://www.python.org/dev/peps/pep-0654/ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/README.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/README.rst new file mode 100644 index 0000000000000..d34937d576e47 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/README.rst @@ -0,0 +1,137 @@ +.. image:: https://github.com/agronholm/exceptiongroup/actions/workflows/test.yml/badge.svg + :target: https://github.com/agronholm/exceptiongroup/actions/workflows/test.yml + :alt: Build Status +.. image:: https://coveralls.io/repos/github/agronholm/exceptiongroup/badge.svg?branch=main + :target: https://coveralls.io/github/agronholm/exceptiongroup?branch=main + :alt: Code Coverage + +This is a backport of the ``BaseExceptionGroup`` and ``ExceptionGroup`` classes from +Python 3.11. + +It contains the following: + +* The ``exceptiongroup.BaseExceptionGroup`` and ``exceptiongroup.ExceptionGroup`` + classes +* A utility function (``exceptiongroup.catch()``) for catching exceptions possibly + nested in an exception group +* Patches to the ``TracebackException`` class that properly formats exception groups + (installed on import) +* An exception hook that handles formatting of exception groups through + ``TracebackException`` (installed on import) +* Special versions of some of the functions from the ``traceback`` module, modified to + correctly handle exception groups even when monkey patching is disabled, or blocked by + another custom exception hook: + + * ``traceback.format_exception()`` + * ``traceback.format_exception_only()`` + * ``traceback.print_exception()`` + * ``traceback.print_exc()`` +* A backported version of ``contextlib.suppress()`` from Python 3.12.1 which also + handles suppressing exceptions inside exception groups + +If this package is imported on Python 3.11 or later, the built-in implementations of the +exception group classes are used instead, ``TracebackException`` is not monkey patched +and the exception hook won't be installed. + +See the `standard library documentation`_ for more information on exception groups. + +.. _standard library documentation: https://docs.python.org/3/library/exceptions.html + +Catching exceptions +=================== + +Due to the lack of the ``except*`` syntax introduced by `PEP 654`_ in earlier Python +versions, you need to use ``exceptiongroup.catch()`` to catch exceptions that are +potentially nested inside an exception group. This function returns a context manager +that calls the given handler for any exceptions matching the sole argument. + +The argument to ``catch()`` must be a dict (or any ``Mapping``) where each key is either +an exception class or an iterable of exception classes. Each value must be a callable +that takes a single positional argument. The handler will be called at most once, with +an exception group as an argument which will contain all the exceptions that are any +of the given types, or their subclasses. The exception group may contain nested groups +containing more matching exceptions. + +Thus, the following Python 3.11+ code: + +.. code-block:: python + + try: + ... + except* (ValueError, KeyError) as excgroup: + for exc in excgroup.exceptions: + print('Caught exception:', type(exc)) + except* RuntimeError: + print('Caught runtime error') + +would be written with this backport like this: + +.. code-block:: python + + from exceptiongroup import BaseExceptionGroup, catch + + def value_key_err_handler(excgroup: BaseExceptionGroup) -> None: + for exc in excgroup.exceptions: + print('Caught exception:', type(exc)) + + def runtime_err_handler(exc: BaseExceptionGroup) -> None: + print('Caught runtime error') + + with catch({ + (ValueError, KeyError): value_key_err_handler, + RuntimeError: runtime_err_handler + }): + ... + +**NOTE**: Just like with ``except*``, you cannot handle ``BaseExceptionGroup`` or +``ExceptionGroup`` with ``catch()``. + +Suppressing exceptions +====================== + +This library contains a backport of the ``contextlib.suppress()`` context manager from +Python 3.12.1. It allows you to selectively ignore certain exceptions, even when they're +inside exception groups: + +.. code-block:: python + + from exceptiongroup import suppress + + with suppress(RuntimeError): + raise ExceptionGroup("", [RuntimeError("boo")]) + +Notes on monkey patching +======================== + +To make exception groups render properly when an unhandled exception group is being +printed out, this package does two things when it is imported on any Python version +earlier than 3.11: + +#. The ``traceback.TracebackException`` class is monkey patched to store extra + information about exception groups (in ``__init__()``) and properly format them (in + ``format()``) +#. An exception hook is installed at ``sys.excepthook``, provided that no other hook is + already present. This hook causes the exception to be formatted using + ``traceback.TracebackException`` rather than the built-in rendered. + +If ``sys.exceptionhook`` is found to be set to something else than the default when +``exceptiongroup`` is imported, no monkeypatching is done at all. + +To prevent the exception hook and patches from being installed, set the environment +variable ``EXCEPTIONGROUP_NO_PATCH`` to ``1``. + +Formatting exception groups +--------------------------- + +Normally, the monkey patching applied by this library on import will cause exception +groups to be printed properly in tracebacks. But in cases when the monkey patching is +blocked by a third party exception hook, or monkey patching is explicitly disabled, +you can still manually format exceptions using the special versions of the ``traceback`` +functions, like ``format_exception()``, listed at the top of this page. They work just +like their counterparts in the ``traceback`` module, except that they use a separately +patched subclass of ``TracebackException`` to perform the rendering. + +Particularly in cases where a library installs its own exception hook, it is recommended +to use these special versions to do the actual formatting of exceptions/tracebacks. + +.. _PEP 654: https://www.python.org/dev/peps/pep-0654/ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/pyproject.toml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/pyproject.toml new file mode 100644 index 0000000000000..aa47cdcec4c1d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/pyproject.toml @@ -0,0 +1,111 @@ +[build-system] +requires = ["flit_scm"] +build-backend = "flit_scm:buildapi" + +[project] +name = "exceptiongroup" +description = "Backport of PEP 654 (exception groups)" +readme = "README.rst" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Typing :: Typed" +] +authors = [{name = "Alex Grönholm", email = "alex.gronholm@nextday.fi"}] +license = {file = "LICENSE"} +requires-python = ">=3.7" +dynamic = ["version"] + +[project.urls] +Changelog = "https://github.com/agronholm/exceptiongroup/blob/main/CHANGES.rst" +"Source code" = "https://github.com/agronholm/exceptiongroup" +"Issue Tracker" = "https://github.com/agronholm/exceptiongroup/issues" + +[project.optional-dependencies] +test = [ + "pytest >= 6" +] + +[tool.flit.sdist] +include = [ + "CHANGES.rst", + "tests", +] +exclude = [ + ".github/*", + ".gitignore", + ".pre-commit-config.yaml" +] + +[tool.setuptools_scm] +version_scheme = "post-release" +local_scheme = "dirty-tag" +write_to = "src/exceptiongroup/_version.py" + +[tool.ruff.lint] +select = [ + "E", "F", "W", # default flake-8 + "I", # isort + "ISC", # flake8-implicit-str-concat + "PGH", # pygrep-hooks + "RUF100", # unused noqa (yesqa) + "UP", # pyupgrade +] + +[tool.ruff.lint.pyupgrade] +# Preserve types, even if a file imports `from __future__ import annotations`. +keep-runtime-typing = true + +[tool.ruff.lint.isort] +known-first-party = ["exceptiongroup"] + +[tool.pytest.ini_options] +addopts = "-rsx --tb=short --strict-config --strict-markers" +testpaths = ["tests"] + +[tool.coverage.run] +source = ["exceptiongroup"] +relative_files = true + +[tool.coverage.report] +exclude_also = [ + "if TYPE_CHECKING:", + "@overload", +] + +[tool.pyright] +# for type tests, the code itself isn't type checked in CI +reportUnnecessaryTypeIgnoreComment = true + +[tool.mypy] +# for type tests, the code itself isn't type checked in CI +warn_unused_ignores = true + +[tool.tox] +legacy_tox_ini = """ +[tox] +envlist = py37, py38, py39, py310, py311, py312, pypy3 +labels = + typing = py{310,311,312}-typing +skip_missing_interpreters = true +minversion = 4.0 + +[testenv] +extras = test +commands = python -m pytest {posargs} +package = editable +usedevelop = true + +[testenv:{py37-,py38-,py39-,py310-,py311-,py312-,}typing] +deps = + pyright + mypy +commands = + pyright --verifytypes exceptiongroup + pyright tests/check_types.py + mypy tests/check_types.py +usedevelop = true +""" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/__init__.py new file mode 100644 index 0000000000000..d8e36b2e65d11 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/__init__.py @@ -0,0 +1,46 @@ +__all__ = [ + "BaseExceptionGroup", + "ExceptionGroup", + "catch", + "format_exception", + "format_exception_only", + "print_exception", + "print_exc", + "suppress", +] + +import os +import sys + +from ._catch import catch +from ._version import version as __version__ # noqa: F401 + +if sys.version_info < (3, 11): + from ._exceptions import BaseExceptionGroup, ExceptionGroup + from ._formatting import ( + format_exception, + format_exception_only, + print_exc, + print_exception, + ) + + if os.getenv("EXCEPTIONGROUP_NO_PATCH") != "1": + from . import _formatting # noqa: F401 + + BaseExceptionGroup.__module__ = __name__ + ExceptionGroup.__module__ = __name__ +else: + from traceback import ( + format_exception, + format_exception_only, + print_exc, + print_exception, + ) + + BaseExceptionGroup = BaseExceptionGroup + ExceptionGroup = ExceptionGroup + +if sys.version_info < (3, 12, 1): + from ._suppress import suppress +else: + from contextlib import suppress diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_catch.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_catch.py new file mode 100644 index 0000000000000..0246568bd0501 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_catch.py @@ -0,0 +1,138 @@ +from __future__ import annotations + +import inspect +import sys +from collections.abc import Callable, Iterable, Mapping +from contextlib import AbstractContextManager +from types import TracebackType +from typing import TYPE_CHECKING, Any + +if sys.version_info < (3, 11): + from ._exceptions import BaseExceptionGroup + +if TYPE_CHECKING: + _Handler = Callable[[BaseExceptionGroup[Any]], Any] + + +class _Catcher: + def __init__(self, handler_map: Mapping[tuple[type[BaseException], ...], _Handler]): + self._handler_map = handler_map + + def __enter__(self) -> None: + pass + + def __exit__( + self, + etype: type[BaseException] | None, + exc: BaseException | None, + tb: TracebackType | None, + ) -> bool: + if exc is not None: + unhandled = self.handle_exception(exc) + if unhandled is exc: + return False + elif unhandled is None: + return True + else: + if isinstance(exc, BaseExceptionGroup): + try: + raise unhandled from exc.__cause__ + except BaseExceptionGroup: + # Change __context__ to __cause__ because Python 3.11 does this + # too + unhandled.__context__ = exc.__cause__ + raise + + raise unhandled from exc + + return False + + def handle_exception(self, exc: BaseException) -> BaseException | None: + excgroup: BaseExceptionGroup | None + if isinstance(exc, BaseExceptionGroup): + excgroup = exc + else: + excgroup = BaseExceptionGroup("", [exc]) + + new_exceptions: list[BaseException] = [] + for exc_types, handler in self._handler_map.items(): + matched, excgroup = excgroup.split(exc_types) + if matched: + try: + try: + raise matched + except BaseExceptionGroup: + result = handler(matched) + except BaseExceptionGroup as new_exc: + if new_exc is matched: + new_exceptions.append(new_exc) + else: + new_exceptions.extend(new_exc.exceptions) + except BaseException as new_exc: + new_exceptions.append(new_exc) + else: + if inspect.iscoroutine(result): + raise TypeError( + f"Error trying to handle {matched!r} with {handler!r}. " + "Exception handler must be a sync function." + ) from exc + + if not excgroup: + break + + if new_exceptions: + if len(new_exceptions) == 1: + return new_exceptions[0] + + return BaseExceptionGroup("", new_exceptions) + elif ( + excgroup and len(excgroup.exceptions) == 1 and excgroup.exceptions[0] is exc + ): + return exc + else: + return excgroup + + +def catch( + __handlers: Mapping[type[BaseException] | Iterable[type[BaseException]], _Handler], +) -> AbstractContextManager[None]: + if not isinstance(__handlers, Mapping): + raise TypeError("the argument must be a mapping") + + handler_map: dict[ + tuple[type[BaseException], ...], Callable[[BaseExceptionGroup]] + ] = {} + for type_or_iterable, handler in __handlers.items(): + iterable: tuple[type[BaseException]] + if isinstance(type_or_iterable, type) and issubclass( + type_or_iterable, BaseException + ): + iterable = (type_or_iterable,) + elif isinstance(type_or_iterable, Iterable): + iterable = tuple(type_or_iterable) + else: + raise TypeError( + "each key must be either an exception classes or an iterable thereof" + ) + + if not callable(handler): + raise TypeError("handlers must be callable") + + for exc_type in iterable: + if not isinstance(exc_type, type) or not issubclass( + exc_type, BaseException + ): + raise TypeError( + "each key must be either an exception classes or an iterable " + "thereof" + ) + + if issubclass(exc_type, BaseExceptionGroup): + raise TypeError( + "catching ExceptionGroup with catch() is not allowed. " + "Use except instead." + ) + + handler_map[iterable] = handler + + return _Catcher(handler_map) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_exceptions.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_exceptions.py new file mode 100644 index 0000000000000..a4a7acea822d4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_exceptions.py @@ -0,0 +1,321 @@ +from __future__ import annotations + +from collections.abc import Callable, Sequence +from functools import partial +from inspect import getmro, isclass +from typing import TYPE_CHECKING, Generic, Type, TypeVar, cast, overload + +_BaseExceptionT_co = TypeVar("_BaseExceptionT_co", bound=BaseException, covariant=True) +_BaseExceptionT = TypeVar("_BaseExceptionT", bound=BaseException) +_ExceptionT_co = TypeVar("_ExceptionT_co", bound=Exception, covariant=True) +_ExceptionT = TypeVar("_ExceptionT", bound=Exception) +# using typing.Self would require a typing_extensions dependency on py<3.11 +_ExceptionGroupSelf = TypeVar("_ExceptionGroupSelf", bound="ExceptionGroup") +_BaseExceptionGroupSelf = TypeVar("_BaseExceptionGroupSelf", bound="BaseExceptionGroup") + + +def check_direct_subclass( + exc: BaseException, parents: tuple[type[BaseException]] +) -> bool: + for cls in getmro(exc.__class__)[:-1]: + if cls in parents: + return True + + return False + + +def get_condition_filter( + condition: type[_BaseExceptionT] + | tuple[type[_BaseExceptionT], ...] + | Callable[[_BaseExceptionT_co], bool], +) -> Callable[[_BaseExceptionT_co], bool]: + if isclass(condition) and issubclass( + cast(Type[BaseException], condition), BaseException + ): + return partial(check_direct_subclass, parents=(condition,)) + elif isinstance(condition, tuple): + if all(isclass(x) and issubclass(x, BaseException) for x in condition): + return partial(check_direct_subclass, parents=condition) + elif callable(condition): + return cast("Callable[[BaseException], bool]", condition) + + raise TypeError("expected a function, exception type or tuple of exception types") + + +def _derive_and_copy_attributes(self, excs): + eg = self.derive(excs) + eg.__cause__ = self.__cause__ + eg.__context__ = self.__context__ + eg.__traceback__ = self.__traceback__ + if hasattr(self, "__notes__"): + # Create a new list so that add_note() only affects one exceptiongroup + eg.__notes__ = list(self.__notes__) + return eg + + +class BaseExceptionGroup(BaseException, Generic[_BaseExceptionT_co]): + """A combination of multiple unrelated exceptions.""" + + def __new__( + cls: type[_BaseExceptionGroupSelf], + __message: str, + __exceptions: Sequence[_BaseExceptionT_co], + ) -> _BaseExceptionGroupSelf: + if not isinstance(__message, str): + raise TypeError(f"argument 1 must be str, not {type(__message)}") + if not isinstance(__exceptions, Sequence): + raise TypeError("second argument (exceptions) must be a sequence") + if not __exceptions: + raise ValueError( + "second argument (exceptions) must be a non-empty sequence" + ) + + for i, exc in enumerate(__exceptions): + if not isinstance(exc, BaseException): + raise ValueError( + f"Item {i} of second argument (exceptions) is not an exception" + ) + + if cls is BaseExceptionGroup: + if all(isinstance(exc, Exception) for exc in __exceptions): + cls = ExceptionGroup + + if issubclass(cls, Exception): + for exc in __exceptions: + if not isinstance(exc, Exception): + if cls is ExceptionGroup: + raise TypeError( + "Cannot nest BaseExceptions in an ExceptionGroup" + ) + else: + raise TypeError( + f"Cannot nest BaseExceptions in {cls.__name__!r}" + ) + + instance = super().__new__(cls, __message, __exceptions) + instance._message = __message + instance._exceptions = __exceptions + return instance + + def add_note(self, note: str) -> None: + if not isinstance(note, str): + raise TypeError( + f"Expected a string, got note={note!r} (type {type(note).__name__})" + ) + + if not hasattr(self, "__notes__"): + self.__notes__: list[str] = [] + + self.__notes__.append(note) + + @property + def message(self) -> str: + return self._message + + @property + def exceptions( + self, + ) -> tuple[_BaseExceptionT_co | BaseExceptionGroup[_BaseExceptionT_co], ...]: + return tuple(self._exceptions) + + @overload + def subgroup( + self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...] + ) -> ExceptionGroup[_ExceptionT] | None: ... + + @overload + def subgroup( + self, __condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...] + ) -> BaseExceptionGroup[_BaseExceptionT] | None: ... + + @overload + def subgroup( + self, + __condition: Callable[[_BaseExceptionT_co | _BaseExceptionGroupSelf], bool], + ) -> BaseExceptionGroup[_BaseExceptionT_co] | None: ... + + def subgroup( + self, + __condition: type[_BaseExceptionT] + | tuple[type[_BaseExceptionT], ...] + | Callable[[_BaseExceptionT_co | _BaseExceptionGroupSelf], bool], + ) -> BaseExceptionGroup[_BaseExceptionT] | None: + condition = get_condition_filter(__condition) + modified = False + if condition(self): + return self + + exceptions: list[BaseException] = [] + for exc in self.exceptions: + if isinstance(exc, BaseExceptionGroup): + subgroup = exc.subgroup(__condition) + if subgroup is not None: + exceptions.append(subgroup) + + if subgroup is not exc: + modified = True + elif condition(exc): + exceptions.append(exc) + else: + modified = True + + if not modified: + return self + elif exceptions: + group = _derive_and_copy_attributes(self, exceptions) + return group + else: + return None + + @overload + def split( + self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...] + ) -> tuple[ + ExceptionGroup[_ExceptionT] | None, + BaseExceptionGroup[_BaseExceptionT_co] | None, + ]: ... + + @overload + def split( + self, __condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...] + ) -> tuple[ + BaseExceptionGroup[_BaseExceptionT] | None, + BaseExceptionGroup[_BaseExceptionT_co] | None, + ]: ... + + @overload + def split( + self, + __condition: Callable[[_BaseExceptionT_co | _BaseExceptionGroupSelf], bool], + ) -> tuple[ + BaseExceptionGroup[_BaseExceptionT_co] | None, + BaseExceptionGroup[_BaseExceptionT_co] | None, + ]: ... + + def split( + self, + __condition: type[_BaseExceptionT] + | tuple[type[_BaseExceptionT], ...] + | Callable[[_BaseExceptionT_co], bool], + ) -> ( + tuple[ + ExceptionGroup[_ExceptionT] | None, + BaseExceptionGroup[_BaseExceptionT_co] | None, + ] + | tuple[ + BaseExceptionGroup[_BaseExceptionT] | None, + BaseExceptionGroup[_BaseExceptionT_co] | None, + ] + | tuple[ + BaseExceptionGroup[_BaseExceptionT_co] | None, + BaseExceptionGroup[_BaseExceptionT_co] | None, + ] + ): + condition = get_condition_filter(__condition) + if condition(self): + return self, None + + matching_exceptions: list[BaseException] = [] + nonmatching_exceptions: list[BaseException] = [] + for exc in self.exceptions: + if isinstance(exc, BaseExceptionGroup): + matching, nonmatching = exc.split(condition) + if matching is not None: + matching_exceptions.append(matching) + + if nonmatching is not None: + nonmatching_exceptions.append(nonmatching) + elif condition(exc): + matching_exceptions.append(exc) + else: + nonmatching_exceptions.append(exc) + + matching_group: _BaseExceptionGroupSelf | None = None + if matching_exceptions: + matching_group = _derive_and_copy_attributes(self, matching_exceptions) + + nonmatching_group: _BaseExceptionGroupSelf | None = None + if nonmatching_exceptions: + nonmatching_group = _derive_and_copy_attributes( + self, nonmatching_exceptions + ) + + return matching_group, nonmatching_group + + @overload + def derive(self, __excs: Sequence[_ExceptionT]) -> ExceptionGroup[_ExceptionT]: ... + + @overload + def derive( + self, __excs: Sequence[_BaseExceptionT] + ) -> BaseExceptionGroup[_BaseExceptionT]: ... + + def derive( + self, __excs: Sequence[_BaseExceptionT] + ) -> BaseExceptionGroup[_BaseExceptionT]: + return BaseExceptionGroup(self.message, __excs) + + def __str__(self) -> str: + suffix = "" if len(self._exceptions) == 1 else "s" + return f"{self.message} ({len(self._exceptions)} sub-exception{suffix})" + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.message!r}, {self._exceptions!r})" + + +class ExceptionGroup(BaseExceptionGroup[_ExceptionT_co], Exception): + def __new__( + cls: type[_ExceptionGroupSelf], + __message: str, + __exceptions: Sequence[_ExceptionT_co], + ) -> _ExceptionGroupSelf: + return super().__new__(cls, __message, __exceptions) + + if TYPE_CHECKING: + + @property + def exceptions( + self, + ) -> tuple[_ExceptionT_co | ExceptionGroup[_ExceptionT_co], ...]: ... + + @overload # type: ignore[override] + def subgroup( + self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...] + ) -> ExceptionGroup[_ExceptionT] | None: ... + + @overload + def subgroup( + self, __condition: Callable[[_ExceptionT_co | _ExceptionGroupSelf], bool] + ) -> ExceptionGroup[_ExceptionT_co] | None: ... + + def subgroup( + self, + __condition: type[_ExceptionT] + | tuple[type[_ExceptionT], ...] + | Callable[[_ExceptionT_co], bool], + ) -> ExceptionGroup[_ExceptionT] | None: + return super().subgroup(__condition) + + @overload + def split( + self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...] + ) -> tuple[ + ExceptionGroup[_ExceptionT] | None, ExceptionGroup[_ExceptionT_co] | None + ]: ... + + @overload + def split( + self, __condition: Callable[[_ExceptionT_co | _ExceptionGroupSelf], bool] + ) -> tuple[ + ExceptionGroup[_ExceptionT_co] | None, ExceptionGroup[_ExceptionT_co] | None + ]: ... + + def split( + self: _ExceptionGroupSelf, + __condition: type[_ExceptionT] + | tuple[type[_ExceptionT], ...] + | Callable[[_ExceptionT_co], bool], + ) -> tuple[ + ExceptionGroup[_ExceptionT_co] | None, ExceptionGroup[_ExceptionT_co] | None + ]: + return super().split(__condition) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_formatting.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_formatting.py new file mode 100644 index 0000000000000..e3835e4145076 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_formatting.py @@ -0,0 +1,603 @@ +# traceback_exception_init() adapted from trio +# +# _ExceptionPrintContext and traceback_exception_format() copied from the standard +# library +from __future__ import annotations + +import collections.abc +import sys +import textwrap +import traceback +from functools import singledispatch +from types import TracebackType +from typing import Any, List, Optional + +from ._exceptions import BaseExceptionGroup + +max_group_width = 15 +max_group_depth = 10 +_cause_message = ( + "\nThe above exception was the direct cause of the following exception:\n\n" +) + +_context_message = ( + "\nDuring handling of the above exception, another exception occurred:\n\n" +) + + +def _format_final_exc_line(etype, value): + valuestr = _safe_string(value, "exception") + if value is None or not valuestr: + line = f"{etype}\n" + else: + line = f"{etype}: {valuestr}\n" + + return line + + +def _safe_string(value, what, func=str): + try: + return func(value) + except BaseException: + return f"<{what} {func.__name__}() failed>" + + +class _ExceptionPrintContext: + def __init__(self): + self.seen = set() + self.exception_group_depth = 0 + self.need_close = False + + def indent(self): + return " " * (2 * self.exception_group_depth) + + def emit(self, text_gen, margin_char=None): + if margin_char is None: + margin_char = "|" + indent_str = self.indent() + if self.exception_group_depth: + indent_str += margin_char + " " + + if isinstance(text_gen, str): + yield textwrap.indent(text_gen, indent_str, lambda line: True) + else: + for text in text_gen: + yield textwrap.indent(text, indent_str, lambda line: True) + + +def exceptiongroup_excepthook( + etype: type[BaseException], value: BaseException, tb: TracebackType | None +) -> None: + sys.stderr.write("".join(traceback.format_exception(etype, value, tb))) + + +class PatchedTracebackException(traceback.TracebackException): + def __init__( + self, + exc_type: type[BaseException], + exc_value: BaseException, + exc_traceback: TracebackType | None, + *, + limit: int | None = None, + lookup_lines: bool = True, + capture_locals: bool = False, + compact: bool = False, + _seen: set[int] | None = None, + ) -> None: + kwargs: dict[str, Any] = {} + if sys.version_info >= (3, 10): + kwargs["compact"] = compact + + is_recursive_call = _seen is not None + if _seen is None: + _seen = set() + _seen.add(id(exc_value)) + + self.stack = traceback.StackSummary.extract( + traceback.walk_tb(exc_traceback), + limit=limit, + lookup_lines=lookup_lines, + capture_locals=capture_locals, + ) + self.exc_type = exc_type + # Capture now to permit freeing resources: only complication is in the + # unofficial API _format_final_exc_line + self._str = _safe_string(exc_value, "exception") + try: + self.__notes__ = getattr(exc_value, "__notes__", None) + except KeyError: + # Workaround for https://github.com/python/cpython/issues/98778 on Python + # <= 3.9, and some 3.10 and 3.11 patch versions. + HTTPError = getattr(sys.modules.get("urllib.error", None), "HTTPError", ()) + if sys.version_info[:2] <= (3, 11) and isinstance(exc_value, HTTPError): + self.__notes__ = None + else: + raise + + if exc_type and issubclass(exc_type, SyntaxError): + # Handle SyntaxError's specially + self.filename = exc_value.filename + lno = exc_value.lineno + self.lineno = str(lno) if lno is not None else None + self.text = exc_value.text + self.offset = exc_value.offset + self.msg = exc_value.msg + if sys.version_info >= (3, 10): + end_lno = exc_value.end_lineno + self.end_lineno = str(end_lno) if end_lno is not None else None + self.end_offset = exc_value.end_offset + elif ( + exc_type + and issubclass(exc_type, (NameError, AttributeError)) + and getattr(exc_value, "name", None) is not None + ): + suggestion = _compute_suggestion_error(exc_value, exc_traceback) + if suggestion: + self._str += f". Did you mean: '{suggestion}'?" + + if lookup_lines: + # Force all lines in the stack to be loaded + for frame in self.stack: + frame.line + + self.__suppress_context__ = ( + exc_value.__suppress_context__ if exc_value is not None else False + ) + + # Convert __cause__ and __context__ to `TracebackExceptions`s, use a + # queue to avoid recursion (only the top-level call gets _seen == None) + if not is_recursive_call: + queue = [(self, exc_value)] + while queue: + te, e = queue.pop() + + if e and e.__cause__ is not None and id(e.__cause__) not in _seen: + cause = PatchedTracebackException( + type(e.__cause__), + e.__cause__, + e.__cause__.__traceback__, + limit=limit, + lookup_lines=lookup_lines, + capture_locals=capture_locals, + _seen=_seen, + ) + else: + cause = None + + if compact: + need_context = ( + cause is None and e is not None and not e.__suppress_context__ + ) + else: + need_context = True + if ( + e + and e.__context__ is not None + and need_context + and id(e.__context__) not in _seen + ): + context = PatchedTracebackException( + type(e.__context__), + e.__context__, + e.__context__.__traceback__, + limit=limit, + lookup_lines=lookup_lines, + capture_locals=capture_locals, + _seen=_seen, + ) + else: + context = None + + # Capture each of the exceptions in the ExceptionGroup along with each + # of their causes and contexts + if e and isinstance(e, BaseExceptionGroup): + exceptions = [] + for exc in e.exceptions: + texc = PatchedTracebackException( + type(exc), + exc, + exc.__traceback__, + lookup_lines=lookup_lines, + capture_locals=capture_locals, + _seen=_seen, + ) + exceptions.append(texc) + else: + exceptions = None + + te.__cause__ = cause + te.__context__ = context + te.exceptions = exceptions + if cause: + queue.append((te.__cause__, e.__cause__)) + if context: + queue.append((te.__context__, e.__context__)) + if exceptions: + queue.extend(zip(te.exceptions, e.exceptions)) + + def format(self, *, chain=True, _ctx=None): + if _ctx is None: + _ctx = _ExceptionPrintContext() + + output = [] + exc = self + if chain: + while exc: + if exc.__cause__ is not None: + chained_msg = _cause_message + chained_exc = exc.__cause__ + elif exc.__context__ is not None and not exc.__suppress_context__: + chained_msg = _context_message + chained_exc = exc.__context__ + else: + chained_msg = None + chained_exc = None + + output.append((chained_msg, exc)) + exc = chained_exc + else: + output.append((None, exc)) + + for msg, exc in reversed(output): + if msg is not None: + yield from _ctx.emit(msg) + if exc.exceptions is None: + if exc.stack: + yield from _ctx.emit("Traceback (most recent call last):\n") + yield from _ctx.emit(exc.stack.format()) + yield from _ctx.emit(exc.format_exception_only()) + elif _ctx.exception_group_depth > max_group_depth: + # exception group, but depth exceeds limit + yield from _ctx.emit(f"... (max_group_depth is {max_group_depth})\n") + else: + # format exception group + is_toplevel = _ctx.exception_group_depth == 0 + if is_toplevel: + _ctx.exception_group_depth += 1 + + if exc.stack: + yield from _ctx.emit( + "Exception Group Traceback (most recent call last):\n", + margin_char="+" if is_toplevel else None, + ) + yield from _ctx.emit(exc.stack.format()) + + yield from _ctx.emit(exc.format_exception_only()) + num_excs = len(exc.exceptions) + if num_excs <= max_group_width: + n = num_excs + else: + n = max_group_width + 1 + _ctx.need_close = False + for i in range(n): + last_exc = i == n - 1 + if last_exc: + # The closing frame may be added by a recursive call + _ctx.need_close = True + + if max_group_width is not None: + truncated = i >= max_group_width + else: + truncated = False + title = f"{i + 1}" if not truncated else "..." + yield ( + _ctx.indent() + + ("+-" if i == 0 else " ") + + f"+---------------- {title} ----------------\n" + ) + _ctx.exception_group_depth += 1 + if not truncated: + yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx) + else: + remaining = num_excs - max_group_width + plural = "s" if remaining > 1 else "" + yield from _ctx.emit( + f"and {remaining} more exception{plural}\n" + ) + + if last_exc and _ctx.need_close: + yield _ctx.indent() + "+------------------------------------\n" + _ctx.need_close = False + _ctx.exception_group_depth -= 1 + + if is_toplevel: + assert _ctx.exception_group_depth == 1 + _ctx.exception_group_depth = 0 + + def format_exception_only(self): + """Format the exception part of the traceback. + The return value is a generator of strings, each ending in a newline. + Normally, the generator emits a single string; however, for + SyntaxError exceptions, it emits 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 output. + """ + if self.exc_type is None: + yield traceback._format_final_exc_line(None, self._str) + return + + stype = self.exc_type.__qualname__ + smod = self.exc_type.__module__ + if smod not in ("__main__", "builtins"): + if not isinstance(smod, str): + smod = "" + stype = smod + "." + stype + + if not issubclass(self.exc_type, SyntaxError): + yield _format_final_exc_line(stype, self._str) + elif traceback_exception_format_syntax_error is not None: + yield from traceback_exception_format_syntax_error(self, stype) + else: + yield from traceback_exception_original_format_exception_only(self) + + if isinstance(self.__notes__, collections.abc.Sequence): + for note in self.__notes__: + note = _safe_string(note, "note") + yield from [line + "\n" for line in note.split("\n")] + elif self.__notes__ is not None: + yield _safe_string(self.__notes__, "__notes__", func=repr) + + +traceback_exception_original_format = traceback.TracebackException.format +traceback_exception_original_format_exception_only = ( + traceback.TracebackException.format_exception_only +) +traceback_exception_format_syntax_error = getattr( + traceback.TracebackException, "_format_syntax_error", None +) +if sys.excepthook is sys.__excepthook__: + traceback.TracebackException.__init__ = ( # type: ignore[assignment] + PatchedTracebackException.__init__ + ) + traceback.TracebackException.format = ( # type: ignore[assignment] + PatchedTracebackException.format + ) + traceback.TracebackException.format_exception_only = ( # type: ignore[assignment] + PatchedTracebackException.format_exception_only + ) + sys.excepthook = exceptiongroup_excepthook + +# Ubuntu's system Python has a sitecustomize.py file that imports +# apport_python_hook and replaces sys.excepthook. +# +# The custom hook captures the error for crash reporting, and then calls +# sys.__excepthook__ to actually print the error. +# +# We don't mind it capturing the error for crash reporting, but we want to +# take over printing the error. So we monkeypatch the apport_python_hook +# module so that instead of calling sys.__excepthook__, it calls our custom +# hook. +# +# More details: https://github.com/python-trio/trio/issues/1065 +if getattr(sys.excepthook, "__name__", None) in ( + "apport_excepthook", + # on ubuntu 22.10 the hook was renamed to partial_apport_excepthook + "partial_apport_excepthook", +): + # patch traceback like above + traceback.TracebackException.__init__ = ( # type: ignore[assignment] + PatchedTracebackException.__init__ + ) + traceback.TracebackException.format = ( # type: ignore[assignment] + PatchedTracebackException.format + ) + traceback.TracebackException.format_exception_only = ( # type: ignore[assignment] + PatchedTracebackException.format_exception_only + ) + + from types import ModuleType + + import apport_python_hook + + assert sys.excepthook is apport_python_hook.apport_excepthook + + # monkeypatch the sys module that apport has imported + fake_sys = ModuleType("exceptiongroup_fake_sys") + fake_sys.__dict__.update(sys.__dict__) + fake_sys.__excepthook__ = exceptiongroup_excepthook + apport_python_hook.sys = fake_sys + + +@singledispatch +def format_exception_only(__exc: BaseException) -> List[str]: + return list( + PatchedTracebackException( + type(__exc), __exc, None, compact=True + ).format_exception_only() + ) + + +@format_exception_only.register +def _(__exc: type, value: BaseException) -> List[str]: + return format_exception_only(value) + + +@singledispatch +def format_exception( + __exc: BaseException, + limit: Optional[int] = None, + chain: bool = True, +) -> List[str]: + return list( + PatchedTracebackException( + type(__exc), __exc, __exc.__traceback__, limit=limit, compact=True + ).format(chain=chain) + ) + + +@format_exception.register +def _( + __exc: type, + value: BaseException, + tb: TracebackType, + limit: Optional[int] = None, + chain: bool = True, +) -> List[str]: + return format_exception(value, limit, chain) + + +@singledispatch +def print_exception( + __exc: BaseException, + limit: Optional[int] = None, + file: Any = None, + chain: bool = True, +) -> None: + if file is None: + file = sys.stderr + + for line in PatchedTracebackException( + type(__exc), __exc, __exc.__traceback__, limit=limit + ).format(chain=chain): + print(line, file=file, end="") + + +@print_exception.register +def _( + __exc: type, + value: BaseException, + tb: TracebackType, + limit: Optional[int] = None, + file: Any = None, + chain: bool = True, +) -> None: + print_exception(value, limit, file, chain) + + +def print_exc( + limit: Optional[int] = None, + file: Any | None = None, + chain: bool = True, +) -> None: + value = sys.exc_info()[1] + print_exception(value, limit, file, chain) + + +# Python levenshtein edit distance code for NameError/AttributeError +# suggestions, backported from 3.12 + +_MAX_CANDIDATE_ITEMS = 750 +_MAX_STRING_SIZE = 40 +_MOVE_COST = 2 +_CASE_COST = 1 +_SENTINEL = object() + + +def _substitution_cost(ch_a, ch_b): + if ch_a == ch_b: + return 0 + if ch_a.lower() == ch_b.lower(): + return _CASE_COST + return _MOVE_COST + + +def _compute_suggestion_error(exc_value, tb): + wrong_name = getattr(exc_value, "name", None) + if wrong_name is None or not isinstance(wrong_name, str): + return None + if isinstance(exc_value, AttributeError): + obj = getattr(exc_value, "obj", _SENTINEL) + if obj is _SENTINEL: + return None + obj = exc_value.obj + try: + d = dir(obj) + except Exception: + return None + else: + assert isinstance(exc_value, NameError) + # find most recent frame + if tb is None: + return None + while tb.tb_next is not None: + tb = tb.tb_next + frame = tb.tb_frame + + d = list(frame.f_locals) + list(frame.f_globals) + list(frame.f_builtins) + if len(d) > _MAX_CANDIDATE_ITEMS: + return None + wrong_name_len = len(wrong_name) + if wrong_name_len > _MAX_STRING_SIZE: + return None + best_distance = wrong_name_len + suggestion = None + for possible_name in d: + if possible_name == wrong_name: + # A missing attribute is "found". Don't suggest it (see GH-88821). + continue + # No more than 1/3 of the involved characters should need changed. + max_distance = (len(possible_name) + wrong_name_len + 3) * _MOVE_COST // 6 + # Don't take matches we've already beaten. + max_distance = min(max_distance, best_distance - 1) + current_distance = _levenshtein_distance( + wrong_name, possible_name, max_distance + ) + if current_distance > max_distance: + continue + if not suggestion or current_distance < best_distance: + suggestion = possible_name + best_distance = current_distance + return suggestion + + +def _levenshtein_distance(a, b, max_cost): + # A Python implementation of Python/suggestions.c:levenshtein_distance. + + # Both strings are the same + if a == b: + return 0 + + # Trim away common affixes + pre = 0 + while a[pre:] and b[pre:] and a[pre] == b[pre]: + pre += 1 + a = a[pre:] + b = b[pre:] + post = 0 + while a[: post or None] and b[: post or None] and a[post - 1] == b[post - 1]: + post -= 1 + a = a[: post or None] + b = b[: post or None] + if not a or not b: + return _MOVE_COST * (len(a) + len(b)) + if len(a) > _MAX_STRING_SIZE or len(b) > _MAX_STRING_SIZE: + return max_cost + 1 + + # Prefer shorter buffer + if len(b) < len(a): + a, b = b, a + + # Quick fail when a match is impossible + if (len(b) - len(a)) * _MOVE_COST > max_cost: + return max_cost + 1 + + # Instead of producing the whole traditional len(a)-by-len(b) + # matrix, we can update just one row in place. + # Initialize the buffer row + row = list(range(_MOVE_COST, _MOVE_COST * (len(a) + 1), _MOVE_COST)) + + result = 0 + for bindex in range(len(b)): + bchar = b[bindex] + distance = result = bindex * _MOVE_COST + minimum = sys.maxsize + for index in range(len(a)): + # 1) Previous distance in this row is cost(b[:b_index], a[:index]) + substitute = distance + _substitution_cost(bchar, a[index]) + # 2) cost(b[:b_index], a[:index+1]) from previous row + distance = row[index] + # 3) existing result is cost(b[:b_index+1], a[index]) + + insert_delete = min(result, distance) + _MOVE_COST + result = min(insert_delete, substitute) + + # cost(b[:b_index+1], a[:index+1]) + row[index] = result + if result < minimum: + minimum = result + if minimum > max_cost: + # Everything in this row is too big, so bail early. + return max_cost + 1 + return result diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_suppress.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_suppress.py new file mode 100644 index 0000000000000..11467eeda9b31 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_suppress.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +import sys +from contextlib import AbstractContextManager +from types import TracebackType +from typing import TYPE_CHECKING, Optional, Type, cast + +if sys.version_info < (3, 11): + from ._exceptions import BaseExceptionGroup + +if TYPE_CHECKING: + # requires python 3.9 + BaseClass = AbstractContextManager[None] +else: + BaseClass = AbstractContextManager + + +class suppress(BaseClass): + """Backport of :class:`contextlib.suppress` from Python 3.12.1.""" + + def __init__(self, *exceptions: type[BaseException]): + self._exceptions = exceptions + + def __enter__(self) -> None: + pass + + def __exit__( + self, + exctype: Optional[Type[BaseException]], + excinst: Optional[BaseException], + exctb: Optional[TracebackType], + ) -> bool: + # Unlike isinstance and issubclass, CPython exception handling + # currently only looks at the concrete type hierarchy (ignoring + # the instance and subclass checking hooks). While Guido considers + # that a bug rather than a feature, it's a fairly hard one to fix + # due to various internal implementation details. suppress provides + # the simpler issubclass based semantics, rather than trying to + # exactly reproduce the limitations of the CPython interpreter. + # + # See http://bugs.python.org/issue12029 for more details + if exctype is None: + return False + + if issubclass(exctype, self._exceptions): + return True + + if issubclass(exctype, BaseExceptionGroup): + match, rest = cast(BaseExceptionGroup, excinst).split(self._exceptions) + if rest is None: + return True + + raise rest + + return False diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_version.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_version.py new file mode 100644 index 0000000000000..9e1bb0b65ea2f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_version.py @@ -0,0 +1,16 @@ +# file generated by setuptools_scm +# don't change, don't track in version control +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Tuple, Union + VERSION_TUPLE = Tuple[Union[int, str], ...] +else: + VERSION_TUPLE = object + +version: str +__version__: str +__version_tuple__: VERSION_TUPLE +version_tuple: VERSION_TUPLE + +__version__ = version = '1.2.1' +__version_tuple__ = version_tuple = (1, 2, 1) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/py.typed b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/py.typed new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/w3c-import.log new file mode 100644 index 0000000000000..fad94308e8cd7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_catch.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_exceptions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_formatting.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_suppress.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/_version.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/src/exceptiongroup/py.typed diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/__init__.py new file mode 100644 index 0000000000000..a6834b8285a56 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/apport_excepthook.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/apport_excepthook.py new file mode 100644 index 0000000000000..1e4a8f30deeac --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/apport_excepthook.py @@ -0,0 +1,13 @@ +# The apport_python_hook package is only installed as part of Ubuntu's system +# python, and not available in venvs. So before we can import it we have to +# make sure it's on sys.path. +import sys + +sys.path.append("/usr/lib/python3/dist-packages") +import apport_python_hook # unsorted import + +apport_python_hook.install() + +from exceptiongroup import ExceptionGroup # noqa: E402 # unsorted import + +raise ExceptionGroup("msg1", [KeyError("msg2"), ValueError("msg3")]) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/check_types.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/check_types.py new file mode 100644 index 0000000000000..f7a102d5d8ab2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/check_types.py @@ -0,0 +1,36 @@ +from typing_extensions import assert_type + +from exceptiongroup import BaseExceptionGroup, ExceptionGroup, catch, suppress + +# issue 117 +a = BaseExceptionGroup("", (KeyboardInterrupt(),)) +assert_type(a, BaseExceptionGroup[KeyboardInterrupt]) +b = BaseExceptionGroup("", (ValueError(),)) +assert_type(b, BaseExceptionGroup[ValueError]) +c = ExceptionGroup("", (ValueError(),)) +assert_type(c, ExceptionGroup[ValueError]) + +# expected type error when passing a BaseException to ExceptionGroup +ExceptionGroup("", (KeyboardInterrupt(),)) # type: ignore[type-var] + + +# code snippets from the README + + +def value_key_err_handler(excgroup: BaseExceptionGroup) -> None: + for exc in excgroup.exceptions: + print("Caught exception:", type(exc)) + + +def runtime_err_handler(exc: BaseExceptionGroup) -> None: + print("Caught runtime error") + + +with catch( + {(ValueError, KeyError): value_key_err_handler, RuntimeError: runtime_err_handler} +): + ... + + +with suppress(RuntimeError): + raise ExceptionGroup("", [RuntimeError("boo")]) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/conftest.py new file mode 100644 index 0000000000000..aeca72d3b5e6f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/conftest.py @@ -0,0 +1,4 @@ +import sys + +if sys.version_info < (3, 11): + collect_ignore_glob = ["*_py311.py"] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_apport_monkeypatching.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_apport_monkeypatching.py new file mode 100644 index 0000000000000..998554fde690d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_apport_monkeypatching.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +import os +import subprocess +import sys +from pathlib import Path + +import pytest + +import exceptiongroup + + +def run_script(name: str) -> subprocess.CompletedProcess[bytes]: + exceptiongroup_path = Path(exceptiongroup.__file__).parent.parent + script_path = Path(__file__).parent / name + + env = dict(os.environ) + print("parent PYTHONPATH:", env.get("PYTHONPATH")) + if "PYTHONPATH" in env: # pragma: no cover + pp = env["PYTHONPATH"].split(os.pathsep) + else: + pp = [] + + pp.insert(0, str(exceptiongroup_path)) + pp.insert(0, str(script_path.parent)) + env["PYTHONPATH"] = os.pathsep.join(pp) + print("subprocess PYTHONPATH:", env.get("PYTHONPATH")) + + cmd = [sys.executable, "-u", str(script_path)] + print("running:", cmd) + completed = subprocess.run( + cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) + print("process output:") + print(completed.stdout.decode("utf-8")) + return completed + + +@pytest.mark.skipif( + sys.version_info > (3, 11), + reason="No patching is done on Python >= 3.11", +) +@pytest.mark.skipif( + not Path("/usr/lib/python3/dist-packages/apport_python_hook.py").exists(), + reason="need Ubuntu with python3-apport installed", +) +def test_apport_excepthook_monkeypatch_interaction(): + completed = run_script("apport_excepthook.py") + stdout = completed.stdout.decode("utf-8") + file = Path(__file__).parent / "apport_excepthook.py" + assert stdout == ( + f"""\ + + Exception Group Traceback (most recent call last): + | File "{file}", line 13, in + | raise ExceptionGroup("msg1", [KeyError("msg2"), ValueError("msg3")]) + | exceptiongroup.ExceptionGroup: msg1 (2 sub-exceptions) + +-+---------------- 1 ---------------- + | KeyError: 'msg2' + +---------------- 2 ---------------- + | ValueError: msg3 + +------------------------------------ +""" + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_catch.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_catch.py new file mode 100644 index 0000000000000..1da749e23c6ed --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_catch.py @@ -0,0 +1,222 @@ +import pytest + +from exceptiongroup import BaseExceptionGroup, ExceptionGroup, catch + + +def test_bad_arg(): + with pytest.raises(TypeError, match="the argument must be a mapping"): + with catch(1): + pass + + +def test_bad_handler(): + with pytest.raises(TypeError, match="handlers must be callable"): + with catch({RuntimeError: None}): + pass + + +@pytest.mark.parametrize( + "exc_type", + [ + pytest.param(BaseExceptionGroup, id="naked_basegroup"), + pytest.param(ExceptionGroup, id="naked_group"), + pytest.param((ValueError, BaseExceptionGroup), id="iterable_basegroup"), + pytest.param((ValueError, ExceptionGroup), id="iterable_group"), + ], +) +def test_catch_exceptiongroup(exc_type): + with pytest.raises(TypeError, match="catching ExceptionGroup with catch"): + with catch({exc_type: (lambda e: True)}): + pass + + +def test_catch_ungrouped(): + value_type_errors = [] + zero_division_errors = [] + for exc in [ValueError("foo"), TypeError("bar"), ZeroDivisionError()]: + with catch( + { + (ValueError, TypeError): value_type_errors.append, + ZeroDivisionError: zero_division_errors.append, + } + ): + raise exc + + assert len(value_type_errors) == 2 + + assert isinstance(value_type_errors[0], ExceptionGroup) + assert len(value_type_errors[0].exceptions) == 1 + assert isinstance(value_type_errors[0].exceptions[0], ValueError) + + assert isinstance(value_type_errors[1], ExceptionGroup) + assert len(value_type_errors[1].exceptions) == 1 + assert isinstance(value_type_errors[1].exceptions[0], TypeError) + + assert len(zero_division_errors) == 1 + assert isinstance(zero_division_errors[0], ExceptionGroup) + assert isinstance(zero_division_errors[0].exceptions[0], ZeroDivisionError) + assert len(zero_division_errors[0].exceptions) == 1 + + +def test_catch_group(): + value_runtime_errors = [] + zero_division_errors = [] + with catch( + { + (ValueError, RuntimeError): value_runtime_errors.append, + ZeroDivisionError: zero_division_errors.append, + } + ): + raise ExceptionGroup( + "booboo", + [ + ValueError("foo"), + ValueError("bar"), + RuntimeError("bar"), + ZeroDivisionError(), + ], + ) + + assert len(value_runtime_errors) == 1 + assert isinstance(value_runtime_errors[0], ExceptionGroup) + exceptions = value_runtime_errors[0].exceptions + assert isinstance(exceptions[0], ValueError) + assert isinstance(exceptions[1], ValueError) + assert isinstance(exceptions[2], RuntimeError) + + assert len(zero_division_errors) == 1 + assert isinstance(zero_division_errors[0], ExceptionGroup) + exceptions = zero_division_errors[0].exceptions + assert isinstance(exceptions[0], ZeroDivisionError) + + +def test_catch_nested_group(): + value_runtime_errors = [] + zero_division_errors = [] + with catch( + { + (ValueError, RuntimeError): value_runtime_errors.append, + ZeroDivisionError: zero_division_errors.append, + } + ): + nested_group = ExceptionGroup( + "nested", [RuntimeError("bar"), ZeroDivisionError()] + ) + raise ExceptionGroup("booboo", [ValueError("foo"), nested_group]) + + assert len(value_runtime_errors) == 1 + exceptions = value_runtime_errors[0].exceptions + assert isinstance(exceptions[0], ValueError) + assert isinstance(exceptions[1], ExceptionGroup) + assert isinstance(exceptions[1].exceptions[0], RuntimeError) + + assert len(zero_division_errors) == 1 + assert isinstance(zero_division_errors[0], ExceptionGroup) + assert isinstance(zero_division_errors[0].exceptions[0], ExceptionGroup) + assert isinstance( + zero_division_errors[0].exceptions[0].exceptions[0], ZeroDivisionError + ) + + +def test_catch_no_match(): + try: + with catch({(ValueError, RuntimeError): (lambda e: None)}): + group = ExceptionGroup("booboo", [ZeroDivisionError()]) + raise group + except ExceptionGroup as exc: + assert exc is not group + else: + pytest.fail("Did not raise an ExceptionGroup") + + +def test_catch_single_no_match(): + try: + with catch({(ValueError, RuntimeError): (lambda e: None)}): + raise ZeroDivisionError + except ZeroDivisionError: + pass + else: + pytest.fail("Did not raise an ZeroDivisionError") + + +def test_catch_full_match(): + with catch({(ValueError, RuntimeError): (lambda e: None)}): + raise ExceptionGroup("booboo", [ValueError()]) + + +def test_catch_handler_raises(): + def handler(exc): + raise RuntimeError("new") + + with pytest.raises(RuntimeError, match="new") as exc: + with catch({(ValueError, ValueError): handler}): + excgrp = ExceptionGroup("booboo", [ValueError("bar")]) + raise excgrp + + context = exc.value.__context__ + assert isinstance(context, ExceptionGroup) + assert str(context) == "booboo (1 sub-exception)" + assert len(context.exceptions) == 1 + assert isinstance(context.exceptions[0], ValueError) + assert exc.value.__cause__ is None + + +def test_bare_raise_in_handler(): + """Test that a bare "raise" "middle" ecxeption group gets discarded.""" + + def handler(exc): + raise + + with pytest.raises(ExceptionGroup) as excgrp: + with catch({(ValueError,): handler, (RuntimeError,): lambda eg: None}): + try: + first_exc = RuntimeError("first") + raise first_exc + except RuntimeError as exc: + middle_exc = ExceptionGroup( + "bad", [ValueError(), ValueError(), TypeError()] + ) + raise middle_exc from exc + + assert len(excgrp.value.exceptions) == 2 + assert all(isinstance(exc, ValueError) for exc in excgrp.value.exceptions) + assert excgrp.value is not middle_exc + assert excgrp.value.__cause__ is first_exc + assert excgrp.value.__context__ is first_exc + + +def test_catch_subclass(): + lookup_errors = [] + with catch({LookupError: lookup_errors.append}): + raise KeyError("foo") + + assert len(lookup_errors) == 1 + assert isinstance(lookup_errors[0], ExceptionGroup) + exceptions = lookup_errors[0].exceptions + assert isinstance(exceptions[0], KeyError) + + +def test_async_handler(request): + async def handler(eg): + pass + + def delegate(eg): + coro = handler(eg) + request.addfinalizer(coro.close) + return coro + + with pytest.raises(TypeError, match="Exception handler must be a sync function."): + with catch({TypeError: delegate}): + raise ExceptionGroup("message", [TypeError("uh-oh")]) + + +def test_bare_reraise_from_naked_exception(): + def handler(eg): + raise + + with pytest.raises(ExceptionGroup) as excgrp, catch({Exception: handler}): + raise KeyError("foo") + + assert len(excgrp.value.exceptions) == 1 + assert isinstance(excgrp.value.exceptions[0], KeyError) + assert str(excgrp.value.exceptions[0]) == "'foo'" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_catch_py311.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_catch_py311.py new file mode 100644 index 0000000000000..2f12ac32a2836 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_catch_py311.py @@ -0,0 +1,190 @@ +import sys + +import pytest + +from exceptiongroup import ExceptionGroup + + +def test_catch_ungrouped(): + value_type_errors = [] + zero_division_errors = [] + for exc in [ValueError("foo"), TypeError("bar"), ZeroDivisionError()]: + try: + raise exc + except* (ValueError, TypeError) as e: + value_type_errors.append(e) + except* ZeroDivisionError as e: + zero_division_errors.append(e) + + assert len(value_type_errors) == 2 + + assert isinstance(value_type_errors[0], ExceptionGroup) + assert len(value_type_errors[0].exceptions) == 1 + assert isinstance(value_type_errors[0].exceptions[0], ValueError) + + assert isinstance(value_type_errors[1], ExceptionGroup) + assert len(value_type_errors[1].exceptions) == 1 + assert isinstance(value_type_errors[1].exceptions[0], TypeError) + + assert len(zero_division_errors) == 1 + assert isinstance(zero_division_errors[0], ExceptionGroup) + assert isinstance(zero_division_errors[0].exceptions[0], ZeroDivisionError) + assert len(zero_division_errors[0].exceptions) == 1 + + +def test_catch_group(): + value_runtime_errors = [] + zero_division_errors = [] + try: + raise ExceptionGroup( + "booboo", + [ + ValueError("foo"), + ValueError("bar"), + RuntimeError("bar"), + ZeroDivisionError(), + ], + ) + except* (ValueError, RuntimeError) as exc: + value_runtime_errors.append(exc) + except* ZeroDivisionError as exc: + zero_division_errors.append(exc) + + assert len(value_runtime_errors) == 1 + assert isinstance(value_runtime_errors[0], ExceptionGroup) + exceptions = value_runtime_errors[0].exceptions + assert isinstance(exceptions[0], ValueError) + assert isinstance(exceptions[1], ValueError) + assert isinstance(exceptions[2], RuntimeError) + + assert len(zero_division_errors) == 1 + assert isinstance(zero_division_errors[0], ExceptionGroup) + exceptions = zero_division_errors[0].exceptions + assert isinstance(exceptions[0], ZeroDivisionError) + + +def test_catch_nested_group(): + value_runtime_errors = [] + zero_division_errors = [] + try: + nested_group = ExceptionGroup( + "nested", [RuntimeError("bar"), ZeroDivisionError()] + ) + raise ExceptionGroup("booboo", [ValueError("foo"), nested_group]) + except* (ValueError, RuntimeError) as exc: + value_runtime_errors.append(exc) + except* ZeroDivisionError as exc: + zero_division_errors.append(exc) + + assert len(value_runtime_errors) == 1 + exceptions = value_runtime_errors[0].exceptions + assert isinstance(exceptions[0], ValueError) + assert isinstance(exceptions[1], ExceptionGroup) + assert isinstance(exceptions[1].exceptions[0], RuntimeError) + + assert len(zero_division_errors) == 1 + assert isinstance(zero_division_errors[0], ExceptionGroup) + assert isinstance(zero_division_errors[0].exceptions[0], ExceptionGroup) + assert isinstance( + zero_division_errors[0].exceptions[0].exceptions[0], ZeroDivisionError + ) + + +def test_catch_no_match(): + try: + try: + group = ExceptionGroup("booboo", [ZeroDivisionError()]) + raise group + except* (ValueError, RuntimeError): + pass + except ExceptionGroup as exc: + assert isinstance(exc.exceptions[0], ZeroDivisionError) + assert exc is not group + else: + pytest.fail("Did not raise an ExceptionGroup") + + +def test_catch_single_no_match(): + try: + try: + raise ZeroDivisionError + except* (ValueError, RuntimeError): + pass + except ZeroDivisionError: + pass + else: + pytest.fail("Did not raise an ZeroDivisionError") + + +def test_catch_full_match(): + try: + raise ExceptionGroup("booboo", [ValueError()]) + except* (ValueError, RuntimeError): + pass + + +@pytest.mark.skipif( + sys.version_info < (3, 11, 4), + reason="Behavior was changed in 3.11.4", +) +def test_catch_handler_raises(): + with pytest.raises(RuntimeError, match="new") as exc: + try: + excgrp = ExceptionGroup("booboo", [ValueError("bar")]) + raise excgrp + except* ValueError: + raise RuntimeError("new") + + context = exc.value.__context__ + assert isinstance(context, ExceptionGroup) + assert str(context) == "booboo (1 sub-exception)" + assert len(context.exceptions) == 1 + assert isinstance(context.exceptions[0], ValueError) + assert exc.value.__cause__ is None + + +def test_catch_subclass(): + lookup_errors = [] + try: + raise KeyError("foo") + except* LookupError as e: + lookup_errors.append(e) + + assert len(lookup_errors) == 1 + assert isinstance(lookup_errors[0], ExceptionGroup) + exceptions = lookup_errors[0].exceptions + assert isinstance(exceptions[0], KeyError) + + +def test_bare_raise_in_handler(): + """Test that the "middle" ecxeption group gets discarded.""" + with pytest.raises(ExceptionGroup) as excgrp: + try: + try: + first_exc = RuntimeError("first") + raise first_exc + except RuntimeError as exc: + middle_exc = ExceptionGroup( + "bad", [ValueError(), ValueError(), TypeError()] + ) + raise middle_exc from exc + except* ValueError: + raise + except* TypeError: + pass + + assert excgrp.value is not middle_exc + assert excgrp.value.__cause__ is first_exc + assert excgrp.value.__context__ is first_exc + + +def test_bare_reraise_from_naked_exception(): + with pytest.raises(ExceptionGroup) as excgrp: + try: + raise KeyError("foo") + except* KeyError: + raise + + assert len(excgrp.value.exceptions) == 1 + assert isinstance(excgrp.value.exceptions[0], KeyError) + assert str(excgrp.value.exceptions[0]) == "'foo'" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_exceptions.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_exceptions.py new file mode 100644 index 0000000000000..f77511ab61e27 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_exceptions.py @@ -0,0 +1,845 @@ +# Copied from the standard library +import collections.abc +import sys +import unittest + +import pytest + +from exceptiongroup import BaseExceptionGroup, ExceptionGroup + + +class TestExceptionGroupTypeHierarchy(unittest.TestCase): + def test_exception_group_types(self): + self.assertTrue(issubclass(ExceptionGroup, Exception)) + self.assertTrue(issubclass(ExceptionGroup, BaseExceptionGroup)) + self.assertTrue(issubclass(BaseExceptionGroup, BaseException)) + + def test_exception_group_is_generic_type(self): + E = OSError + self.assertEqual(ExceptionGroup[E].__origin__, ExceptionGroup) + self.assertEqual(BaseExceptionGroup[E].__origin__, BaseExceptionGroup) + + +class BadConstructorArgs(unittest.TestCase): + def test_bad_EG_construction__too_few_args(self): + if sys.version_info >= (3, 11): + MSG = ( + r"BaseExceptionGroup.__new__\(\) takes exactly 2 arguments \(1 given\)" + ) + else: + MSG = ( + r"__new__\(\) missing 1 required positional argument: " + r"'_ExceptionGroup__exceptions'" + ) + + with self.assertRaisesRegex(TypeError, MSG): + ExceptionGroup("no errors") + with self.assertRaisesRegex(TypeError, MSG): + ExceptionGroup([ValueError("no msg")]) + + def test_bad_EG_construction__too_many_args(self): + if sys.version_info >= (3, 11): + MSG = ( + r"BaseExceptionGroup.__new__\(\) takes exactly 2 arguments \(3 given\)" + ) + else: + MSG = r"__new__\(\) takes 3 positional arguments but 4 were given" + + with self.assertRaisesRegex(TypeError, MSG): + ExceptionGroup("eg", [ValueError("too")], [TypeError("many")]) + + def test_bad_EG_construction__bad_message(self): + MSG = "argument 1 must be str, not " + with self.assertRaisesRegex(TypeError, MSG): + ExceptionGroup(ValueError(12), SyntaxError("bad syntax")) + with self.assertRaisesRegex(TypeError, MSG): + ExceptionGroup(None, [ValueError(12)]) + + def test_bad_EG_construction__bad_excs_sequence(self): + MSG = r"second argument \(exceptions\) must be a sequence" + with self.assertRaisesRegex(TypeError, MSG): + ExceptionGroup("errors not sequence", {ValueError(42)}) + with self.assertRaisesRegex(TypeError, MSG): + ExceptionGroup("eg", None) + + MSG = r"second argument \(exceptions\) must be a non-empty sequence" + with self.assertRaisesRegex(ValueError, MSG): + ExceptionGroup("eg", []) + + def test_bad_EG_construction__nested_non_exceptions(self): + MSG = r"Item [0-9]+ of second argument \(exceptions\) is not an exception" + with self.assertRaisesRegex(ValueError, MSG): + ExceptionGroup("expect instance, not type", [OSError]) + with self.assertRaisesRegex(ValueError, MSG): + ExceptionGroup("bad error", ["not an exception"]) + + +class InstanceCreation(unittest.TestCase): + def test_EG_wraps_Exceptions__creates_EG(self): + excs = [ValueError(1), TypeError(2)] + self.assertIs(type(ExceptionGroup("eg", excs)), ExceptionGroup) + + def test_BEG_wraps_Exceptions__creates_EG(self): + excs = [ValueError(1), TypeError(2)] + self.assertIs(type(BaseExceptionGroup("beg", excs)), ExceptionGroup) + + def test_EG_wraps_BaseException__raises_TypeError(self): + MSG = "Cannot nest BaseExceptions in an ExceptionGroup" + with self.assertRaisesRegex(TypeError, MSG): + ExceptionGroup("eg", [ValueError(1), KeyboardInterrupt(2)]) + + def test_BEG_wraps_BaseException__creates_BEG(self): + beg = BaseExceptionGroup("beg", [ValueError(1), KeyboardInterrupt(2)]) + self.assertIs(type(beg), BaseExceptionGroup) + + def test_EG_subclass_wraps_non_base_exceptions(self): + class MyEG(ExceptionGroup): + pass + + self.assertIs(type(MyEG("eg", [ValueError(12), TypeError(42)])), MyEG) + + @pytest.mark.skipif( + sys.version_info[:3] == (3, 11, 0), + reason="Behavior was made stricter in 3.11.1", + ) + def test_EG_subclass_does_not_wrap_base_exceptions(self): + class MyEG(ExceptionGroup): + pass + + msg = "Cannot nest BaseExceptions in 'MyEG'" + with self.assertRaisesRegex(TypeError, msg): + MyEG("eg", [ValueError(12), KeyboardInterrupt(42)]) + + @pytest.mark.skipif( + sys.version_info[:3] == (3, 11, 0), + reason="Behavior was made stricter in 3.11.1", + ) + def test_BEG_and_E_subclass_does_not_wrap_base_exceptions(self): + class MyEG(BaseExceptionGroup, ValueError): + pass + + msg = "Cannot nest BaseExceptions in 'MyEG'" + with self.assertRaisesRegex(TypeError, msg): + MyEG("eg", [ValueError(12), KeyboardInterrupt(42)]) + + +def create_simple_eg(): + excs = [] + try: + try: + raise MemoryError("context and cause for ValueError(1)") + except MemoryError as e: + raise ValueError(1) from e + except ValueError as e: + excs.append(e) + + try: + try: + raise OSError("context for TypeError") + except OSError: + raise TypeError(int) + except TypeError as e: + excs.append(e) + + try: + try: + raise ImportError("context for ValueError(2)") + except ImportError: + raise ValueError(2) + except ValueError as e: + excs.append(e) + + try: + raise ExceptionGroup("simple eg", excs) + except ExceptionGroup as e: + return e + + +class ExceptionGroupFields(unittest.TestCase): + def test_basics_ExceptionGroup_fields(self): + eg = create_simple_eg() + + # check msg + self.assertEqual(eg.message, "simple eg") + self.assertEqual(eg.args[0], "simple eg") + + # check cause and context + self.assertIsInstance(eg.exceptions[0], ValueError) + self.assertIsInstance(eg.exceptions[0].__cause__, MemoryError) + self.assertIsInstance(eg.exceptions[0].__context__, MemoryError) + self.assertIsInstance(eg.exceptions[1], TypeError) + self.assertIsNone(eg.exceptions[1].__cause__) + self.assertIsInstance(eg.exceptions[1].__context__, OSError) + self.assertIsInstance(eg.exceptions[2], ValueError) + self.assertIsNone(eg.exceptions[2].__cause__) + self.assertIsInstance(eg.exceptions[2].__context__, ImportError) + + # check tracebacks + line0 = create_simple_eg.__code__.co_firstlineno + tb_linenos = [line0 + 27, [line0 + 6, line0 + 14, line0 + 22]] + self.assertEqual(eg.__traceback__.tb_lineno, tb_linenos[0]) + self.assertIsNone(eg.__traceback__.tb_next) + for i in range(3): + tb = eg.exceptions[i].__traceback__ + self.assertIsNone(tb.tb_next) + self.assertEqual(tb.tb_lineno, tb_linenos[1][i]) + + def test_fields_are_readonly(self): + eg = ExceptionGroup("eg", [TypeError(1), OSError(2)]) + + self.assertEqual(type(eg.exceptions), tuple) + + eg.message + with self.assertRaises(AttributeError): + eg.message = "new msg" + + eg.exceptions + with self.assertRaises(AttributeError): + eg.exceptions = [OSError("xyz")] + + def test_notes_is_list_of_strings_if_it_exists(self): + eg = create_simple_eg() + + note = "This is a happy note for the exception group" + self.assertFalse(hasattr(eg, "__notes__")) + eg.add_note(note) + self.assertEqual(eg.__notes__, [note]) + + def test_derive_doesn_copy_notes(self): + eg = create_simple_eg() + eg.add_note("hello") + assert eg.__notes__ == ["hello"] + eg2 = eg.derive([ValueError()]) + assert not hasattr(eg2, "__notes__") + + +class ExceptionGroupTestBase(unittest.TestCase): + def assertMatchesTemplate(self, exc, exc_type, template): + """Assert that the exception matches the template + + A template describes the shape of exc. If exc is a + leaf exception (i.e., not an exception group) then + template is an exception instance that has the + expected type and args value of exc. If exc is an + exception group, then template is a list of the + templates of its nested exceptions. + """ + if exc_type is not None: + self.assertIs(type(exc), exc_type) + + if isinstance(exc, BaseExceptionGroup): + self.assertIsInstance(template, collections.abc.Sequence) + self.assertEqual(len(exc.exceptions), len(template)) + for e, t in zip(exc.exceptions, template): + self.assertMatchesTemplate(e, None, t) + else: + self.assertIsInstance(template, BaseException) + self.assertEqual(type(exc), type(template)) + self.assertEqual(exc.args, template.args) + + +class ExceptionGroupSubgroupTests(ExceptionGroupTestBase): + def setUp(self): + self.eg = create_simple_eg() + self.eg_template = [ValueError(1), TypeError(int), ValueError(2)] + + def test_basics_subgroup_split__bad_arg_type(self): + bad_args = [ + "bad arg", + OSError("instance not type"), + [OSError, TypeError], + (OSError, 42), + ] + for arg in bad_args: + with self.assertRaises(TypeError): + self.eg.subgroup(arg) + with self.assertRaises(TypeError): + self.eg.split(arg) + + def test_basics_subgroup_by_type__passthrough(self): + eg = self.eg + # self.assertIs(eg, eg.subgroup(BaseException)) + # self.assertIs(eg, eg.subgroup(Exception)) + self.assertIs(eg, eg.subgroup(BaseExceptionGroup)) + self.assertIs(eg, eg.subgroup(ExceptionGroup)) + + def test_basics_subgroup_by_type__no_match(self): + self.assertIsNone(self.eg.subgroup(OSError)) + + def test_basics_subgroup_by_type__match(self): + eg = self.eg + testcases = [ + # (match_type, result_template) + (ValueError, [ValueError(1), ValueError(2)]), + (TypeError, [TypeError(int)]), + ((ValueError, TypeError), self.eg_template), + ] + + for match_type, template in testcases: + with self.subTest(match=match_type): + subeg = eg.subgroup(match_type) + self.assertEqual(subeg.message, eg.message) + self.assertMatchesTemplate(subeg, ExceptionGroup, template) + + def test_basics_subgroup_by_predicate__passthrough(self): + self.assertIs(self.eg, self.eg.subgroup(lambda e: True)) + + def test_basics_subgroup_by_predicate__no_match(self): + self.assertIsNone(self.eg.subgroup(lambda e: False)) + + def test_basics_subgroup_by_predicate__match(self): + eg = self.eg + testcases = [ + # (match_type, result_template) + (ValueError, [ValueError(1), ValueError(2)]), + (TypeError, [TypeError(int)]), + ((ValueError, TypeError), self.eg_template), + ] + + for match_type, template in testcases: + subeg = eg.subgroup(lambda e: isinstance(e, match_type)) + self.assertEqual(subeg.message, eg.message) + self.assertMatchesTemplate(subeg, ExceptionGroup, template) + + +class ExceptionGroupSplitTests(ExceptionGroupTestBase): + def setUp(self): + self.eg = create_simple_eg() + self.eg_template = [ValueError(1), TypeError(int), ValueError(2)] + + def test_basics_split_by_type__passthrough(self): + for E in [BaseException, Exception, BaseExceptionGroup, ExceptionGroup]: + match, rest = self.eg.split(E) + self.assertMatchesTemplate(match, ExceptionGroup, self.eg_template) + self.assertIsNone(rest) + + def test_basics_split_by_type__no_match(self): + match, rest = self.eg.split(OSError) + self.assertIsNone(match) + self.assertMatchesTemplate(rest, ExceptionGroup, self.eg_template) + + def test_basics_split_by_type__match(self): + eg = self.eg + VE = ValueError + TE = TypeError + testcases = [ + # (matcher, match_template, rest_template) + (VE, [VE(1), VE(2)], [TE(int)]), + (TE, [TE(int)], [VE(1), VE(2)]), + ((VE, TE), self.eg_template, None), + ((OSError, VE), [VE(1), VE(2)], [TE(int)]), + ] + + for match_type, match_template, rest_template in testcases: + match, rest = eg.split(match_type) + self.assertEqual(match.message, eg.message) + self.assertMatchesTemplate(match, ExceptionGroup, match_template) + if rest_template is not None: + self.assertEqual(rest.message, eg.message) + self.assertMatchesTemplate(rest, ExceptionGroup, rest_template) + else: + self.assertIsNone(rest) + + def test_basics_split_by_predicate__passthrough(self): + match, rest = self.eg.split(lambda e: True) + self.assertMatchesTemplate(match, ExceptionGroup, self.eg_template) + self.assertIsNone(rest) + + def test_basics_split_by_predicate__no_match(self): + match, rest = self.eg.split(lambda e: False) + self.assertIsNone(match) + self.assertMatchesTemplate(rest, ExceptionGroup, self.eg_template) + + def test_basics_split_by_predicate__match(self): + eg = self.eg + VE = ValueError + TE = TypeError + testcases = [ + # (matcher, match_template, rest_template) + (VE, [VE(1), VE(2)], [TE(int)]), + (TE, [TE(int)], [VE(1), VE(2)]), + ((VE, TE), self.eg_template, None), + ] + + for match_type, match_template, rest_template in testcases: + match, rest = eg.split(lambda e: isinstance(e, match_type)) + self.assertEqual(match.message, eg.message) + self.assertMatchesTemplate(match, ExceptionGroup, match_template) + if rest_template is not None: + self.assertEqual(rest.message, eg.message) + self.assertMatchesTemplate(rest, ExceptionGroup, rest_template) + + +class DeepRecursionInSplitAndSubgroup(unittest.TestCase): + def make_deep_eg(self): + e = TypeError(1) + for _ in range(10000): + e = ExceptionGroup("eg", [e]) + return e + + def test_deep_split(self): + e = self.make_deep_eg() + with self.assertRaises(RecursionError): + e.split(TypeError) + + def test_deep_subgroup(self): + e = self.make_deep_eg() + with self.assertRaises(RecursionError): + e.subgroup(TypeError) + + +def leaf_generator(exc, tbs=None): + if tbs is None: + tbs = [] + tbs.append(exc.__traceback__) + if isinstance(exc, BaseExceptionGroup): + for e in exc.exceptions: + yield from leaf_generator(e, tbs) + else: + # exc is a leaf exception and its traceback + # is the concatenation of the traceback + # segments in tbs + yield exc, tbs + tbs.pop() + + +class LeafGeneratorTest(unittest.TestCase): + # The leaf_generator is mentioned in PEP 654 as a suggestion + # on how to iterate over leaf nodes of an EG. Is is also + # used below as a test utility. So we test it here. + + def test_leaf_generator(self): + eg = create_simple_eg() + + self.assertSequenceEqual([e for e, _ in leaf_generator(eg)], eg.exceptions) + + for e, tbs in leaf_generator(eg): + self.assertSequenceEqual(tbs, [eg.__traceback__, e.__traceback__]) + + +def create_nested_eg(): + excs = [] + try: + try: + raise TypeError(bytes) + except TypeError as e: + raise ExceptionGroup("nested", [e]) + except ExceptionGroup as e: + excs.append(e) + + try: + try: + raise MemoryError("out of memory") + except MemoryError as e: + raise ValueError(1) from e + except ValueError as e: + excs.append(e) + + try: + raise ExceptionGroup("root", excs) + except ExceptionGroup as eg: + return eg + + +class NestedExceptionGroupBasicsTest(ExceptionGroupTestBase): + def test_nested_group_matches_template(self): + eg = create_nested_eg() + self.assertMatchesTemplate( + eg, ExceptionGroup, [[TypeError(bytes)], ValueError(1)] + ) + + def test_nested_group_chaining(self): + eg = create_nested_eg() + self.assertIsInstance(eg.exceptions[1].__context__, MemoryError) + self.assertIsInstance(eg.exceptions[1].__cause__, MemoryError) + self.assertIsInstance(eg.exceptions[0].__context__, TypeError) + + def test_nested_exception_group_tracebacks(self): + eg = create_nested_eg() + + line0 = create_nested_eg.__code__.co_firstlineno + for tb, expected in [ + (eg.__traceback__, line0 + 19), + (eg.exceptions[0].__traceback__, line0 + 6), + (eg.exceptions[1].__traceback__, line0 + 14), + (eg.exceptions[0].exceptions[0].__traceback__, line0 + 4), + ]: + self.assertEqual(tb.tb_lineno, expected) + self.assertIsNone(tb.tb_next) + + def test_iteration_full_tracebacks(self): + eg = create_nested_eg() + # check that iteration over leaves + # produces the expected tracebacks + self.assertEqual(len(list(leaf_generator(eg))), 2) + + line0 = create_nested_eg.__code__.co_firstlineno + expected_tbs = [[line0 + 19, line0 + 6, line0 + 4], [line0 + 19, line0 + 14]] + + for i, (_, tbs) in enumerate(leaf_generator(eg)): + self.assertSequenceEqual([tb.tb_lineno for tb in tbs], expected_tbs[i]) + + +class ExceptionGroupSplitTestBase(ExceptionGroupTestBase): + def split_exception_group(self, eg, types): + """Split an EG and do some sanity checks on the result""" + self.assertIsInstance(eg, BaseExceptionGroup) + + match, rest = eg.split(types) + sg = eg.subgroup(types) + + if match is not None: + self.assertIsInstance(match, BaseExceptionGroup) + for e, _ in leaf_generator(match): + self.assertIsInstance(e, types) + + self.assertIsNotNone(sg) + self.assertIsInstance(sg, BaseExceptionGroup) + for e, _ in leaf_generator(sg): + self.assertIsInstance(e, types) + + if rest is not None: + self.assertIsInstance(rest, BaseExceptionGroup) + + def leaves(exc): + return [] if exc is None else [e for e, _ in leaf_generator(exc)] + + # match and subgroup have the same leaves + self.assertSequenceEqual(leaves(match), leaves(sg)) + + match_leaves = leaves(match) + rest_leaves = leaves(rest) + # each leaf exception of eg is in exactly one of match and rest + self.assertEqual(len(leaves(eg)), len(leaves(match)) + len(leaves(rest))) + + for e in leaves(eg): + self.assertNotEqual(match and e in match_leaves, rest and e in rest_leaves) + + # message, cause and context, traceback and note equal to eg + for part in [match, rest, sg]: + if part is not None: + self.assertEqual(eg.message, part.message) + self.assertIs(eg.__cause__, part.__cause__) + self.assertIs(eg.__context__, part.__context__) + self.assertIs(eg.__traceback__, part.__traceback__) + self.assertEqual( + getattr(eg, "__notes__", None), + getattr(part, "__notes__", None), + ) + + def tbs_for_leaf(leaf, eg): + for e, tbs in leaf_generator(eg): + if e is leaf: + return tbs + + def tb_linenos(tbs): + return [tb.tb_lineno for tb in tbs if tb] + + # full tracebacks match + for part in [match, rest, sg]: + for e in leaves(part): + self.assertSequenceEqual( + tb_linenos(tbs_for_leaf(e, eg)), tb_linenos(tbs_for_leaf(e, part)) + ) + + return match, rest + + +class NestedExceptionGroupSplitTest(ExceptionGroupSplitTestBase): + def test_split_by_type(self): + class MyExceptionGroup(ExceptionGroup): + pass + + def raiseVE(v): + raise ValueError(v) + + def raiseTE(t): + raise TypeError(t) + + def nested_group(): + def level1(i): + excs = [] + for f, arg in [(raiseVE, i), (raiseTE, int), (raiseVE, i + 1)]: + try: + f(arg) + except Exception as e: + excs.append(e) + raise ExceptionGroup("msg1", excs) + + def level2(i): + excs = [] + for f, arg in [(level1, i), (level1, i + 1), (raiseVE, i + 2)]: + try: + f(arg) + except Exception as e: + excs.append(e) + raise MyExceptionGroup("msg2", excs) + + def level3(i): + excs = [] + for f, arg in [(level2, i + 1), (raiseVE, i + 2)]: + try: + f(arg) + except Exception as e: + excs.append(e) + raise ExceptionGroup("msg3", excs) + + level3(5) + + try: + nested_group() + except ExceptionGroup as e: + e.add_note(f"the note: {id(e)}") + eg = e + + eg_template = [ + [ + [ValueError(6), TypeError(int), ValueError(7)], + [ValueError(7), TypeError(int), ValueError(8)], + ValueError(8), + ], + ValueError(7), + ] + + valueErrors_template = [ + [ + [ValueError(6), ValueError(7)], + [ValueError(7), ValueError(8)], + ValueError(8), + ], + ValueError(7), + ] + + typeErrors_template = [[[TypeError(int)], [TypeError(int)]]] + + self.assertMatchesTemplate(eg, ExceptionGroup, eg_template) + + # Match Nothing + match, rest = self.split_exception_group(eg, SyntaxError) + self.assertIsNone(match) + self.assertMatchesTemplate(rest, ExceptionGroup, eg_template) + + # Match Everything + match, rest = self.split_exception_group(eg, BaseException) + self.assertMatchesTemplate(match, ExceptionGroup, eg_template) + self.assertIsNone(rest) + match, rest = self.split_exception_group(eg, (ValueError, TypeError)) + self.assertMatchesTemplate(match, ExceptionGroup, eg_template) + self.assertIsNone(rest) + + # Match ValueErrors + match, rest = self.split_exception_group(eg, ValueError) + self.assertMatchesTemplate(match, ExceptionGroup, valueErrors_template) + self.assertMatchesTemplate(rest, ExceptionGroup, typeErrors_template) + + # Match TypeErrors + match, rest = self.split_exception_group(eg, (TypeError, SyntaxError)) + self.assertMatchesTemplate(match, ExceptionGroup, typeErrors_template) + self.assertMatchesTemplate(rest, ExceptionGroup, valueErrors_template) + + # Match ExceptionGroup + match, rest = eg.split(ExceptionGroup) + self.assertIs(match, eg) + self.assertIsNone(rest) + + # Match MyExceptionGroup (ExceptionGroup subclass) + match, rest = eg.split(MyExceptionGroup) + self.assertMatchesTemplate(match, ExceptionGroup, [eg_template[0]]) + self.assertMatchesTemplate(rest, ExceptionGroup, [eg_template[1]]) + + def test_split_BaseExceptionGroup(self): + def exc(ex): + try: + raise ex + except BaseException as e: + return e + + try: + raise BaseExceptionGroup( + "beg", [exc(ValueError(1)), exc(KeyboardInterrupt(2))] + ) + except BaseExceptionGroup as e: + beg = e + + # Match Nothing + match, rest = self.split_exception_group(beg, TypeError) + self.assertIsNone(match) + self.assertMatchesTemplate( + rest, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)] + ) + + # Match Everything + match, rest = self.split_exception_group(beg, (ValueError, KeyboardInterrupt)) + self.assertMatchesTemplate( + match, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)] + ) + self.assertIsNone(rest) + + # Match ValueErrors + match, rest = self.split_exception_group(beg, ValueError) + self.assertMatchesTemplate(match, ExceptionGroup, [ValueError(1)]) + self.assertMatchesTemplate(rest, BaseExceptionGroup, [KeyboardInterrupt(2)]) + + # Match KeyboardInterrupts + match, rest = self.split_exception_group(beg, KeyboardInterrupt) + self.assertMatchesTemplate(match, BaseExceptionGroup, [KeyboardInterrupt(2)]) + self.assertMatchesTemplate(rest, ExceptionGroup, [ValueError(1)]) + + +class NestedExceptionGroupSubclassSplitTest(ExceptionGroupSplitTestBase): + def test_split_ExceptionGroup_subclass_no_derive_no_new_override(self): + class EG(ExceptionGroup): + pass + + try: + try: + try: + raise TypeError(2) + except TypeError as te: + raise EG("nested", [te]) + except EG as nested: + try: + raise ValueError(1) + except ValueError as ve: + raise EG("eg", [ve, nested]) + except EG as e: + eg = e + + self.assertMatchesTemplate(eg, EG, [ValueError(1), [TypeError(2)]]) + + # Match Nothing + match, rest = self.split_exception_group(eg, OSError) + self.assertIsNone(match) + self.assertMatchesTemplate( + rest, ExceptionGroup, [ValueError(1), [TypeError(2)]] + ) + + # Match Everything + match, rest = self.split_exception_group(eg, (ValueError, TypeError)) + self.assertMatchesTemplate( + match, ExceptionGroup, [ValueError(1), [TypeError(2)]] + ) + self.assertIsNone(rest) + + # Match ValueErrors + match, rest = self.split_exception_group(eg, ValueError) + self.assertMatchesTemplate(match, ExceptionGroup, [ValueError(1)]) + self.assertMatchesTemplate(rest, ExceptionGroup, [[TypeError(2)]]) + + # Match TypeErrors + match, rest = self.split_exception_group(eg, TypeError) + self.assertMatchesTemplate(match, ExceptionGroup, [[TypeError(2)]]) + self.assertMatchesTemplate(rest, ExceptionGroup, [ValueError(1)]) + + def test_split_BaseExceptionGroup_subclass_no_derive_new_override(self): + class EG(BaseExceptionGroup): + def __new__(cls, message, excs, unused): + # The "unused" arg is here to show that split() doesn't call + # the actual class constructor from the default derive() + # implementation (it would fail on unused arg if so because + # it assumes the BaseExceptionGroup.__new__ signature). + return super().__new__(cls, message, excs) + + try: + raise EG("eg", [ValueError(1), KeyboardInterrupt(2)], "unused") + except EG as e: + eg = e + + self.assertMatchesTemplate(eg, EG, [ValueError(1), KeyboardInterrupt(2)]) + + # Match Nothing + match, rest = self.split_exception_group(eg, OSError) + self.assertIsNone(match) + self.assertMatchesTemplate( + rest, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)] + ) + + # Match Everything + match, rest = self.split_exception_group(eg, (ValueError, KeyboardInterrupt)) + self.assertMatchesTemplate( + match, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)] + ) + self.assertIsNone(rest) + + # Match ValueErrors + match, rest = self.split_exception_group(eg, ValueError) + self.assertMatchesTemplate(match, ExceptionGroup, [ValueError(1)]) + self.assertMatchesTemplate(rest, BaseExceptionGroup, [KeyboardInterrupt(2)]) + + # Match KeyboardInterrupt + match, rest = self.split_exception_group(eg, KeyboardInterrupt) + self.assertMatchesTemplate(match, BaseExceptionGroup, [KeyboardInterrupt(2)]) + self.assertMatchesTemplate(rest, ExceptionGroup, [ValueError(1)]) + + def test_split_ExceptionGroup_subclass_derive_and_new_overrides(self): + class EG(ExceptionGroup): + def __new__(cls, message, excs, code): + obj = super().__new__(cls, message, excs) + obj.code = code + return obj + + def derive(self, excs): + return EG(self.message, excs, self.code) + + try: + try: + try: + raise TypeError(2) + except TypeError as te: + raise EG("nested", [te], 101) + except EG as nested: + try: + raise ValueError(1) + except ValueError as ve: + raise EG("eg", [ve, nested], 42) + except EG as e: + e.add_note("hello") + eg = e + + self.assertMatchesTemplate(eg, EG, [ValueError(1), [TypeError(2)]]) + + # Match Nothing + match, rest = self.split_exception_group(eg, OSError) + self.assertIsNone(match) + self.assertMatchesTemplate(rest, EG, [ValueError(1), [TypeError(2)]]) + self.assertEqual(rest.code, 42) + self.assertEqual(rest.exceptions[1].code, 101) + self.assertEqual(rest.__notes__, ["hello"]) + + # Match Everything + match, rest = self.split_exception_group(eg, (ValueError, TypeError)) + self.assertMatchesTemplate(match, EG, [ValueError(1), [TypeError(2)]]) + self.assertEqual(match.code, 42) + self.assertEqual(match.exceptions[1].code, 101) + self.assertEqual(match.__notes__, ["hello"]) + self.assertIsNone(rest) + + # Match ValueErrors + match, rest = self.split_exception_group(eg, ValueError) + self.assertMatchesTemplate(match, EG, [ValueError(1)]) + self.assertEqual(match.code, 42) + self.assertEqual(match.__notes__, ["hello"]) + self.assertMatchesTemplate(rest, EG, [[TypeError(2)]]) + self.assertEqual(rest.code, 42) + self.assertEqual(rest.exceptions[0].code, 101) + self.assertEqual(rest.__notes__, ["hello"]) + + # Match TypeErrors + match, rest = self.split_exception_group(eg, TypeError) + self.assertMatchesTemplate(match, EG, [[TypeError(2)]]) + self.assertEqual(match.code, 42) + self.assertEqual(match.exceptions[0].code, 101) + self.assertEqual(match.__notes__, ["hello"]) + self.assertMatchesTemplate(rest, EG, [ValueError(1)]) + self.assertEqual(rest.code, 42) + self.assertEqual(rest.__notes__, ["hello"]) + + +def test_repr(): + group = BaseExceptionGroup("foo", [ValueError(1), KeyboardInterrupt()]) + assert repr(group) == ( + "BaseExceptionGroup('foo', [ValueError(1), KeyboardInterrupt()])" + ) + + group = ExceptionGroup("foo", [ValueError(1), RuntimeError("bar")]) + assert repr(group) == "ExceptionGroup('foo', [ValueError(1), RuntimeError('bar')])" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_formatting.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_formatting.py new file mode 100644 index 0000000000000..f6b9bc2c455e5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_formatting.py @@ -0,0 +1,538 @@ +import sys +import traceback +from typing import NoReturn +from urllib.error import HTTPError + +import pytest +from _pytest.capture import CaptureFixture +from _pytest.fixtures import SubRequest +from _pytest.monkeypatch import MonkeyPatch + +from exceptiongroup import ExceptionGroup + + +def raise_excgroup() -> NoReturn: + exceptions = [] + try: + raise ValueError("foo") + except ValueError as exc: + exceptions.append(exc) + + try: + raise RuntimeError("bar") + except RuntimeError as exc: + exc.__notes__ = ["Note from bar handler"] + exceptions.append(exc) + + exc = ExceptionGroup("test message", exceptions) + exc.add_note("Displays notes attached to the group too") + raise exc + + +@pytest.fixture( + params=[ + pytest.param(True, id="patched"), + pytest.param( + False, + id="unpatched", + marks=[ + pytest.mark.skipif( + sys.version_info >= (3, 11), + reason="No patching is done on Python >= 3.11", + ) + ], + ), + ], +) +def patched(request: SubRequest) -> bool: + return request.param + + +@pytest.fixture( + params=[pytest.param(False, id="newstyle"), pytest.param(True, id="oldstyle")] +) +def old_argstyle(request: SubRequest) -> bool: + return request.param + + +def test_exceptionhook(capsys: CaptureFixture) -> None: + try: + raise_excgroup() + except ExceptionGroup as exc: + sys.excepthook(type(exc), exc, exc.__traceback__) + + local_lineno = test_exceptionhook.__code__.co_firstlineno + lineno = raise_excgroup.__code__.co_firstlineno + module_prefix = "" if sys.version_info >= (3, 11) else "exceptiongroup." + output = capsys.readouterr().err + assert output == ( + f"""\ + + Exception Group Traceback (most recent call last): + | File "{__file__}", line {local_lineno + 2}, in test_exceptionhook + | raise_excgroup() + | File "{__file__}", line {lineno + 15}, in raise_excgroup + | raise exc + | {module_prefix}ExceptionGroup: test message (2 sub-exceptions) + | Displays notes attached to the group too + +-+---------------- 1 ---------------- + | Traceback (most recent call last): + | File "{__file__}", line {lineno + 3}, in raise_excgroup + | raise ValueError("foo") + | ValueError: foo + +---------------- 2 ---------------- + | Traceback (most recent call last): + | File "{__file__}", line {lineno + 8}, in raise_excgroup + | raise RuntimeError("bar") + | RuntimeError: bar + | Note from bar handler + +------------------------------------ +""" + ) + + +def test_exceptiongroup_as_cause(capsys: CaptureFixture) -> None: + try: + raise Exception() from ExceptionGroup("", (Exception(),)) + except Exception as exc: + sys.excepthook(type(exc), exc, exc.__traceback__) + + lineno = test_exceptiongroup_as_cause.__code__.co_firstlineno + module_prefix = "" if sys.version_info >= (3, 11) else "exceptiongroup." + output = capsys.readouterr().err + assert output == ( + f"""\ + | {module_prefix}ExceptionGroup: (1 sub-exception) + +-+---------------- 1 ---------------- + | Exception + +------------------------------------ + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "{__file__}", line {lineno + 2}, in test_exceptiongroup_as_cause + raise Exception() from ExceptionGroup("", (Exception(),)) +Exception +""" + ) + + +def test_exceptiongroup_loop(capsys: CaptureFixture) -> None: + e0 = Exception("e0") + eg0 = ExceptionGroup("eg0", (e0,)) + eg1 = ExceptionGroup("eg1", (eg0,)) + + try: + raise eg0 from eg1 + except ExceptionGroup as exc: + sys.excepthook(type(exc), exc, exc.__traceback__) + + lineno = test_exceptiongroup_loop.__code__.co_firstlineno + 6 + module_prefix = "" if sys.version_info >= (3, 11) else "exceptiongroup." + output = capsys.readouterr().err + assert output == ( + f"""\ + | {module_prefix}ExceptionGroup: eg1 (1 sub-exception) + +-+---------------- 1 ---------------- + | Exception Group Traceback (most recent call last): + | File "{__file__}", line {lineno}, in test_exceptiongroup_loop + | raise eg0 from eg1 + | {module_prefix}ExceptionGroup: eg0 (1 sub-exception) + +-+---------------- 1 ---------------- + | Exception: e0 + +------------------------------------ + +The above exception was the direct cause of the following exception: + + + Exception Group Traceback (most recent call last): + | File "{__file__}", line {lineno}, in test_exceptiongroup_loop + | raise eg0 from eg1 + | {module_prefix}ExceptionGroup: eg0 (1 sub-exception) + +-+---------------- 1 ---------------- + | Exception: e0 + +------------------------------------ +""" + ) + + +def test_exceptionhook_format_exception_only(capsys: CaptureFixture) -> None: + try: + raise_excgroup() + except ExceptionGroup as exc: + sys.excepthook(type(exc), exc, exc.__traceback__) + + local_lineno = test_exceptionhook_format_exception_only.__code__.co_firstlineno + lineno = raise_excgroup.__code__.co_firstlineno + module_prefix = "" if sys.version_info >= (3, 11) else "exceptiongroup." + output = capsys.readouterr().err + assert output == ( + f"""\ + + Exception Group Traceback (most recent call last): + | File "{__file__}", line {local_lineno + 2}, in \ +test_exceptionhook_format_exception_only + | raise_excgroup() + | File "{__file__}", line {lineno + 15}, in raise_excgroup + | raise exc + | {module_prefix}ExceptionGroup: test message (2 sub-exceptions) + | Displays notes attached to the group too + +-+---------------- 1 ---------------- + | Traceback (most recent call last): + | File "{__file__}", line {lineno + 3}, in raise_excgroup + | raise ValueError("foo") + | ValueError: foo + +---------------- 2 ---------------- + | Traceback (most recent call last): + | File "{__file__}", line {lineno + 8}, in raise_excgroup + | raise RuntimeError("bar") + | RuntimeError: bar + | Note from bar handler + +------------------------------------ +""" + ) + + +def test_formatting_syntax_error(capsys: CaptureFixture) -> None: + try: + exec("//serser") + except SyntaxError as exc: + sys.excepthook(type(exc), exc, exc.__traceback__) + + if sys.version_info >= (3, 10): + underline = "\n ^^" + elif sys.version_info >= (3, 8): + underline = "\n ^" + else: + underline = "\n ^" + + lineno = test_formatting_syntax_error.__code__.co_firstlineno + output = capsys.readouterr().err + assert output == ( + f"""\ +Traceback (most recent call last): + File "{__file__}", line {lineno + 2}, \ +in test_formatting_syntax_error + exec("//serser") + File "", line 1 + //serser{underline} +SyntaxError: invalid syntax +""" + ) + + +def test_format_exception( + patched: bool, old_argstyle: bool, monkeypatch: MonkeyPatch +) -> None: + if not patched: + # Block monkey patching, then force the module to be re-imported + del sys.modules["traceback"] + del sys.modules["exceptiongroup"] + del sys.modules["exceptiongroup._formatting"] + monkeypatch.setattr(sys, "excepthook", lambda *args: sys.__excepthook__(*args)) + + from exceptiongroup import format_exception + + exceptions = [] + try: + raise ValueError("foo") + except ValueError as exc: + exceptions.append(exc) + + try: + raise RuntimeError("bar") + except RuntimeError as exc: + exc.__notes__ = ["Note from bar handler"] + exceptions.append(exc) + + try: + raise_excgroup() + except ExceptionGroup as exc: + if old_argstyle: + lines = format_exception(type(exc), exc, exc.__traceback__) + else: + lines = format_exception(exc) + + local_lineno = test_format_exception.__code__.co_firstlineno + lineno = raise_excgroup.__code__.co_firstlineno + assert isinstance(lines, list) + module_prefix = "" if sys.version_info >= (3, 11) else "exceptiongroup." + assert "".join(lines) == ( + f"""\ + + Exception Group Traceback (most recent call last): + | File "{__file__}", line {local_lineno + 25}, in test_format_exception + | raise_excgroup() + | File "{__file__}", line {lineno + 15}, in raise_excgroup + | raise exc + | {module_prefix}ExceptionGroup: test message (2 sub-exceptions) + | Displays notes attached to the group too + +-+---------------- 1 ---------------- + | Traceback (most recent call last): + | File "{__file__}", line {lineno + 3}, in raise_excgroup + | raise ValueError("foo") + | ValueError: foo + +---------------- 2 ---------------- + | Traceback (most recent call last): + | File "{__file__}", line {lineno + 8}, in raise_excgroup + | raise RuntimeError("bar") + | RuntimeError: bar + | Note from bar handler + +------------------------------------ +""" + ) + + +def test_format_nested(monkeypatch: MonkeyPatch) -> None: + if not patched: + # Block monkey patching, then force the module to be re-imported + del sys.modules["traceback"] + del sys.modules["exceptiongroup"] + del sys.modules["exceptiongroup._formatting"] + monkeypatch.setattr(sys, "excepthook", lambda *args: sys.__excepthook__(*args)) + + from exceptiongroup import format_exception + + def raise_exc(max_level: int, level: int = 1) -> NoReturn: + if level == max_level: + raise Exception(f"LEVEL_{level}") + else: + try: + raise_exc(max_level, level + 1) + except Exception: + raise Exception(f"LEVEL_{level}") + + try: + raise_exc(3) + except Exception as exc: + lines = format_exception(type(exc), exc, exc.__traceback__) + + local_lineno = test_format_nested.__code__.co_firstlineno + 20 + raise_exc_lineno1 = raise_exc.__code__.co_firstlineno + 2 + raise_exc_lineno2 = raise_exc.__code__.co_firstlineno + 5 + raise_exc_lineno3 = raise_exc.__code__.co_firstlineno + 7 + assert isinstance(lines, list) + assert "".join(lines) == ( + f"""\ +Traceback (most recent call last): + File "{__file__}", line {raise_exc_lineno2}, in raise_exc + raise_exc(max_level, level + 1) + File "{__file__}", line {raise_exc_lineno1}, in raise_exc + raise Exception(f"LEVEL_{{level}}") +Exception: LEVEL_3 + +During handling of the above exception, another exception occurred: + +Traceback (most recent call last): + File "{__file__}", line {raise_exc_lineno2}, in raise_exc + raise_exc(max_level, level + 1) + File "{__file__}", line {raise_exc_lineno3}, in raise_exc + raise Exception(f"LEVEL_{{level}}") +Exception: LEVEL_2 + +During handling of the above exception, another exception occurred: + +Traceback (most recent call last): + File "{__file__}", line {local_lineno}, in test_format_nested + raise_exc(3) + File "{__file__}", line {raise_exc_lineno3}, in raise_exc + raise Exception(f"LEVEL_{{level}}") +Exception: LEVEL_1 +""" + ) + + +def test_format_exception_only( + patched: bool, old_argstyle: bool, monkeypatch: MonkeyPatch +) -> None: + if not patched: + # Block monkey patching, then force the module to be re-imported + del sys.modules["traceback"] + del sys.modules["exceptiongroup"] + del sys.modules["exceptiongroup._formatting"] + monkeypatch.setattr(sys, "excepthook", lambda *args: sys.__excepthook__(*args)) + + from exceptiongroup import format_exception_only + + try: + raise_excgroup() + except ExceptionGroup as exc: + if old_argstyle: + output = format_exception_only(type(exc), exc) + else: + output = format_exception_only(exc) + + module_prefix = "" if sys.version_info >= (3, 11) else "exceptiongroup." + assert output == [ + f"{module_prefix}ExceptionGroup: test message (2 sub-exceptions)\n", + "Displays notes attached to the group too\n", + ] + + +def test_print_exception( + patched: bool, old_argstyle: bool, monkeypatch: MonkeyPatch, capsys: CaptureFixture +) -> None: + if not patched: + # Block monkey patching, then force the module to be re-imported + del sys.modules["traceback"] + del sys.modules["exceptiongroup"] + del sys.modules["exceptiongroup._formatting"] + monkeypatch.setattr(sys, "excepthook", lambda *args: sys.__excepthook__(*args)) + + from exceptiongroup import print_exception + + try: + raise_excgroup() + except ExceptionGroup as exc: + if old_argstyle: + print_exception(type(exc), exc, exc.__traceback__) + else: + print_exception(exc) + + local_lineno = test_print_exception.__code__.co_firstlineno + lineno = raise_excgroup.__code__.co_firstlineno + module_prefix = "" if sys.version_info >= (3, 11) else "exceptiongroup." + output = capsys.readouterr().err + assert output == ( + f"""\ + + Exception Group Traceback (most recent call last): + | File "{__file__}", line {local_lineno + 13}, in test_print_exception + | raise_excgroup() + | File "{__file__}", line {lineno + 15}, in raise_excgroup + | raise exc + | {module_prefix}ExceptionGroup: test message (2 sub-exceptions) + | Displays notes attached to the group too + +-+---------------- 1 ---------------- + | Traceback (most recent call last): + | File "{__file__}", line {lineno + 3}, in raise_excgroup + | raise ValueError("foo") + | ValueError: foo + +---------------- 2 ---------------- + | Traceback (most recent call last): + | File "{__file__}", line {lineno + 8}, in raise_excgroup + | raise RuntimeError("bar") + | RuntimeError: bar + | Note from bar handler + +------------------------------------ +""" + ) + + +def test_print_exc( + patched: bool, monkeypatch: MonkeyPatch, capsys: CaptureFixture +) -> None: + if not patched: + # Block monkey patching, then force the module to be re-imported + del sys.modules["traceback"] + del sys.modules["exceptiongroup"] + del sys.modules["exceptiongroup._formatting"] + monkeypatch.setattr(sys, "excepthook", lambda *args: sys.__excepthook__(*args)) + + from exceptiongroup import print_exc + + try: + raise_excgroup() + except ExceptionGroup: + print_exc() + local_lineno = test_print_exc.__code__.co_firstlineno + lineno = raise_excgroup.__code__.co_firstlineno + module_prefix = "" if sys.version_info >= (3, 11) else "exceptiongroup." + output = capsys.readouterr().err + assert output == ( + f"""\ + + Exception Group Traceback (most recent call last): + | File "{__file__}", line {local_lineno + 13}, in test_print_exc + | raise_excgroup() + | File "{__file__}", line {lineno + 15}, in raise_excgroup + | raise exc + | {module_prefix}ExceptionGroup: test message (2 sub-exceptions) + | Displays notes attached to the group too + +-+---------------- 1 ---------------- + | Traceback (most recent call last): + | File "{__file__}", line {lineno + 3}, in raise_excgroup + | raise ValueError("foo") + | ValueError: foo + +---------------- 2 ---------------- + | Traceback (most recent call last): + | File "{__file__}", line {lineno + 8}, in raise_excgroup + | raise RuntimeError("bar") + | RuntimeError: bar + | Note from bar handler + +------------------------------------ +""" + ) + + +@pytest.mark.skipif( + not hasattr(NameError, "name") or sys.version_info[:2] == (3, 11), + reason="only works if NameError exposes the missing name", +) +def test_nameerror_suggestions( + patched: bool, monkeypatch: MonkeyPatch, capsys: CaptureFixture +) -> None: + if not patched: + # Block monkey patching, then force the module to be re-imported + del sys.modules["traceback"] + del sys.modules["exceptiongroup"] + del sys.modules["exceptiongroup._formatting"] + monkeypatch.setattr(sys, "excepthook", lambda *args: sys.__excepthook__(*args)) + + from exceptiongroup import print_exc + + try: + folder + except NameError: + print_exc() + output = capsys.readouterr().err + assert "Did you mean" in output and "'filter'?" in output + + +@pytest.mark.skipif( + not hasattr(AttributeError, "name") or sys.version_info[:2] == (3, 11), + reason="only works if AttributeError exposes the missing name", +) +def test_nameerror_suggestions_in_group( + patched: bool, monkeypatch: MonkeyPatch, capsys: CaptureFixture +) -> None: + if not patched: + # Block monkey patching, then force the module to be re-imported + del sys.modules["traceback"] + del sys.modules["exceptiongroup"] + del sys.modules["exceptiongroup._formatting"] + monkeypatch.setattr(sys, "excepthook", lambda *args: sys.__excepthook__(*args)) + + from exceptiongroup import print_exception + + try: + [].attend + except AttributeError as e: + eg = ExceptionGroup("a", [e]) + print_exception(eg) + output = capsys.readouterr().err + assert "Did you mean" in output and "'append'?" in output + + +def test_bug_suggestions_attributeerror_no_obj( + patched: bool, monkeypatch: MonkeyPatch, capsys: CaptureFixture +) -> None: + if not patched: + # Block monkey patching, then force the module to be re-imported + del sys.modules["traceback"] + del sys.modules["exceptiongroup"] + del sys.modules["exceptiongroup._formatting"] + monkeypatch.setattr(sys, "excepthook", lambda *args: sys.__excepthook__(*args)) + + from exceptiongroup import print_exception + + class NamedAttributeError(AttributeError): + def __init__(self, name: str) -> None: + self.name: str = name + + try: + raise NamedAttributeError(name="mykey") + except AttributeError as e: + print_exception(e) # does not crash + output = capsys.readouterr().err + assert "NamedAttributeError" in output + + +def test_works_around_httperror_bug(): + # See https://github.com/python/cpython/issues/98778 in Python <= 3.9 + err = HTTPError("url", 405, "METHOD NOT ALLOWED", None, None) + traceback.TracebackException(type(err), err, None) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_suppress.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_suppress.py new file mode 100644 index 0000000000000..289bb33c0e67f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_suppress.py @@ -0,0 +1,16 @@ +import sys + +import pytest + +from exceptiongroup import suppress + +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup, ExceptionGroup + + +def test_suppress_exception(): + with pytest.raises(ExceptionGroup) as exc, suppress(SystemExit): + raise BaseExceptionGroup("", [SystemExit(1), RuntimeError("boo")]) + + assert len(exc.value.exceptions) == 1 + assert isinstance(exc.value.exceptions[0], RuntimeError) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/w3c-import.log new file mode 100644 index 0000000000000..269bce75e0e86 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/w3c-import.log @@ -0,0 +1,26 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/apport_excepthook.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/check_types.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_apport_monkeypatching.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_catch.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_catch_py311.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_exceptions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_formatting.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/tests/test_suppress.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/w3c-import.log new file mode 100644 index 0000000000000..73f90c322f765 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/CHANGES.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/PKG-INFO +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/exceptiongroup/pyproject.toml diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/.github/workflows/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/.github/workflows/w3c-import.log new file mode 100644 index 0000000000000..28003b8e38af1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/.github/workflows/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/.github/workflows/ci.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/.github/workflows/install_h2spec.sh diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/_static/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/_static/w3c-import.log new file mode 100644 index 0000000000000..0768e856b6b47 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/_static/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/_static/h2.connection.H2ConnectionStateMachine.dot.png +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/_static/h2.stream.H2StreamStateMachine.dot.png diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/w3c-import.log new file mode 100644 index 0000000000000..4256fa28e300e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/w3c-import.log @@ -0,0 +1,38 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/advanced-usage.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/api.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/asyncio-example.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/basic-usage.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/conf.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/curio-example.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/eventlet-example.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/examples.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/gevent-example.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/installation.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/low-level.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/negotiating-http2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/plain-sockets-example.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/release-notes.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/release-process.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/testimonials.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/tornado-example.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/twisted-example.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/twisted-head-example.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/twisted-post-example.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/source/wsgi-example.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/w3c-import.log new file mode 100644 index 0000000000000..a4ea5377524c6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/Makefile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/docs/make.bat diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/asyncio/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/asyncio/w3c-import.log new file mode 100644 index 0000000000000..7e8ae4a287e50 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/asyncio/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/asyncio/asyncio-server.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/asyncio/cert.crt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/asyncio/cert.key +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/asyncio/wsgi-server.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/curio/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/curio/w3c-import.log new file mode 100644 index 0000000000000..b2906b0931429 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/curio/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/curio/curio-server.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/curio/localhost.crt.pem +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/curio/localhost.key diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/eventlet/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/eventlet/w3c-import.log new file mode 100644 index 0000000000000..19f8224450bb5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/eventlet/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/eventlet/eventlet-server.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/eventlet/server.crt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/eventlet/server.key diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/fragments/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/fragments/w3c-import.log new file mode 100644 index 0000000000000..b3ee6f94b420a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/fragments/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/fragments/client_https_setup_fragment.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/fragments/client_upgrade_fragment.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/fragments/server_https_setup_fragment.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/fragments/server_upgrade_fragment.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/gevent/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/gevent/w3c-import.log new file mode 100644 index 0000000000000..0bbbcc13573be --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/gevent/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/gevent/gevent-server.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/gevent/localhost.crt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/gevent/localhost.key diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/plain_sockets/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/plain_sockets/w3c-import.log new file mode 100644 index 0000000000000..c280cab2bbf20 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/plain_sockets/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/plain_sockets/plain_sockets_client.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/tornado/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/tornado/w3c-import.log new file mode 100644 index 0000000000000..91eede98029ec --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/tornado/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/tornado/server.crt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/tornado/server.key +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/tornado/tornado-server.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/twisted/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/twisted/w3c-import.log new file mode 100644 index 0000000000000..eb0ff2ec1ef55 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/twisted/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/twisted/head_request.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/twisted/post_request.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/twisted/server.crt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/twisted/server.csr +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/twisted/server.key +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/examples/twisted/twisted-server.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/src/h2/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/src/h2/w3c-import.log new file mode 100644 index 0000000000000..ed14e9e529154 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/src/h2/w3c-import.log @@ -0,0 +1,27 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/src/h2/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/src/h2/config.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/src/h2/connection.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/src/h2/errors.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/src/h2/events.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/src/h2/exceptions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/src/h2/frame_buffer.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/src/h2/settings.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/src/h2/stream.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/src/h2/utilities.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/src/h2/windows.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/w3c-import.log new file mode 100644 index 0000000000000..10f1ea4e89880 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/w3c-import.log @@ -0,0 +1,44 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/coroutine_tests.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/h2spectest.sh +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/helpers.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_basic_logic.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_closed_streams.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_complex_logic.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_config.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_events.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_exceptions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_flow_control_window.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_h2_upgrade.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_head_request.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_header_indexing.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_informational_responses.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_interacting_stacks.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_invalid_content_lengths.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_invalid_frame_sequences.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_invalid_headers.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_priority.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_related_events.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_rfc7838.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_rfc8441.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_settings.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_state_machines.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_stream_reset.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/test/test_utility_functions.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/visualizer/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/visualizer/w3c-import.log new file mode 100644 index 0000000000000..84d3fff1b3292 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/visualizer/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/visualizer/NOTICES.visualizer +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/visualizer/visualize.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/w3c-import.log new file mode 100644 index 0000000000000..3d30981368a09 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/CHANGELOG.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/MANIFEST.in +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/setup.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/setup.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/h2/tox.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/.github/workflows/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/.github/workflows/w3c-import.log new file mode 100644 index 0000000000000..038b4780ca62b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/.github/workflows/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/.github/workflows/ci.yml diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/bench/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/bench/w3c-import.log new file mode 100644 index 0000000000000..9f019a0556964 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/bench/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/bench/test_hpack.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/source/security/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/source/security/w3c-import.log new file mode 100644 index 0000000000000..7fec879d25a00 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/source/security/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/source/security/CVE-2016-6581.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/source/security/index.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/source/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/source/w3c-import.log new file mode 100644 index 0000000000000..b0f0498b3f8e5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/source/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/source/api.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/source/conf.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/source/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/source/installation.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/w3c-import.log new file mode 100644 index 0000000000000..8b656a7ccd028 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/Makefile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/docs/make.bat diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/src/hpack/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/src/hpack/w3c-import.log new file mode 100644 index 0000000000000..3abcc5a4e5b93 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/src/hpack/w3c-import.log @@ -0,0 +1,24 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/src/hpack/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/src/hpack/exceptions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/src/hpack/hpack.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/src/hpack/huffman.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/src/hpack/huffman_constants.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/src/hpack/huffman_table.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/src/hpack/struct.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/src/hpack/table.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/w3c-import.log new file mode 100644 index 0000000000000..95e787e264515 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/w3c-import.log @@ -0,0 +1,48 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_00.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_01.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_02.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_03.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_04.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_05.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_06.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_07.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_08.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_09.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_10.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_11.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_12.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_13.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_14.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_15.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_16.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_17.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_18.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_19.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_20.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_21.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_22.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_23.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_24.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_25.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_26.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_27.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_28.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_29.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_30.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/go-hpack/story_31.json diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/w3c-import.log new file mode 100644 index 0000000000000..f83e3f0482191 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/w3c-import.log @@ -0,0 +1,48 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_00.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_01.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_02.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_03.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_04.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_05.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_06.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_07.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_08.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_09.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_10.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_11.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_12.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_13.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_14.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_15.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_16.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_17.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_18.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_19.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_20.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_21.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_22.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_23.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_24.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_25.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_26.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_27.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_28.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_29.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_30.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear-huffman/story_31.json diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/w3c-import.log new file mode 100644 index 0000000000000..4f9a2c077c292 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/w3c-import.log @@ -0,0 +1,48 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_00.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_01.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_02.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_03.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_04.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_05.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_06.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_07.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_08.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_09.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_10.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_11.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_12.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_13.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_14.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_15.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_16.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_17.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_18.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_19.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_20.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_21.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_22.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_23.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_24.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_25.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_26.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_27.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_28.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_29.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_30.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-linear/story_31.json diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/w3c-import.log new file mode 100644 index 0000000000000..7ed12d5d9d5a5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/w3c-import.log @@ -0,0 +1,48 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_00.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_01.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_02.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_03.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_04.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_05.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_06.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_07.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_08.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_09.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_10.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_11.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_12.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_13.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_14.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_15.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_16.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_17.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_18.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_19.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_20.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_21.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_22.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_23.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_24.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_25.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_26.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_27.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_28.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_29.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_30.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive-huffman/story_31.json diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/w3c-import.log new file mode 100644 index 0000000000000..fa727ebd83110 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/w3c-import.log @@ -0,0 +1,48 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_00.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_01.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_02.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_03.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_04.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_05.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_06.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_07.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_08.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_09.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_10.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_11.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_12.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_13.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_14.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_15.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_16.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_17.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_18.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_19.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_20.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_21.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_22.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_23.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_24.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_25.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_26.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_27.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_28.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_29.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_30.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-naive/story_31.json diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/w3c-import.log new file mode 100644 index 0000000000000..e021208b00278 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/w3c-import.log @@ -0,0 +1,48 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_00.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_01.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_02.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_03.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_04.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_05.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_06.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_07.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_08.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_09.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_10.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_11.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_12.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_13.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_14.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_15.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_16.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_17.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_18.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_19.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_20.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_21.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_22.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_23.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_24.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_25.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_26.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_27.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_28.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_29.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_30.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static-huffman/story_31.json diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/w3c-import.log new file mode 100644 index 0000000000000..062692c1f78fe --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/w3c-import.log @@ -0,0 +1,48 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_00.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_01.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_02.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_03.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_04.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_05.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_06.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_07.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_08.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_09.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_10.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_11.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_12.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_13.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_14.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_15.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_16.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_17.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_18.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_19.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_20.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_21.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_22.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_23.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_24.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_25.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_26.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_27.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_28.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_29.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_30.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/haskell-http2-static/story_31.json diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/w3c-import.log new file mode 100644 index 0000000000000..0a5e9183a7429 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/w3c-import.log @@ -0,0 +1,47 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_00.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_01.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_02.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_03.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_04.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_05.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_06.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_07.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_08.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_09.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_10.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_11.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_12.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_13.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_14.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_15.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_16.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_17.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_18.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_19.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_20.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_21.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_22.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_23.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_24.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_25.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_26.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_27.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_28.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_29.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-16384-4096/story_30.json diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/w3c-import.log new file mode 100644 index 0000000000000..f1af38abc95cc --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/w3c-import.log @@ -0,0 +1,47 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_00.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_01.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_02.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_03.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_04.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_05.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_06.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_07.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_08.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_09.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_10.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_11.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_12.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_13.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_14.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_15.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_16.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_17.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_18.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_19.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_20.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_21.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_22.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_23.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_24.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_25.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_26.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_27.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_28.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_29.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2-change-table-size/story_30.json diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/w3c-import.log new file mode 100644 index 0000000000000..f03bf250e923c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/w3c-import.log @@ -0,0 +1,48 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_00.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_01.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_02.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_03.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_04.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_05.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_06.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_07.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_08.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_09.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_10.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_11.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_12.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_13.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_14.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_15.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_16.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_17.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_18.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_19.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_20.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_21.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_22.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_23.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_24.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_25.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_26.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_27.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_28.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_29.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_30.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/nghttp2/story_31.json diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/w3c-import.log new file mode 100644 index 0000000000000..49b7e565e2c4d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/w3c-import.log @@ -0,0 +1,48 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_00.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_01.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_02.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_03.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_04.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_05.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_06.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_07.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_08.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_09.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_10.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_11.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_12.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_13.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_14.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_15.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_16.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_17.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_18.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_19.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_20.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_21.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_22.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_23.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_24.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_25.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_26.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_27.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_28.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_29.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_30.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/node-http2-hpack/story_31.json diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/w3c-import.log new file mode 100644 index 0000000000000..8f11c45a53591 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/w3c-import.log @@ -0,0 +1,48 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_00.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_01.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_02.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_03.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_04.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_05.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_06.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_07.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_08.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_09.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_10.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_11.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_12.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_13.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_14.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_15.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_16.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_17.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_18.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_19.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_20.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_21.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_22.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_23.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_24.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_25.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_26.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_27.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_28.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_29.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_30.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_fixtures/raw-data/story_31.json diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/w3c-import.log new file mode 100644 index 0000000000000..eea959e7661d3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_encode_decode.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_hpack.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_hpack_integration.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_huffman.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_struct.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/test/test_table.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/utils/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/utils/w3c-import.log new file mode 100644 index 0000000000000..dfee967144c06 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/utils/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/utils/create_test_output.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/w3c-import.log new file mode 100644 index 0000000000000..55eb9e91eb3c2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/w3c-import.log @@ -0,0 +1,26 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/CHANGELOG.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/CONTRIBUTORS.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/MANIFEST.in +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/Makefile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/setup.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/setup.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/tasks.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hpack/tox.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/filter-turbulence-invalid-001.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/filter-turbulence-invalid-001.html index 7400c8b37938f..122cc70f39793 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/filter-turbulence-invalid-001.html +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/filter-turbulence-invalid-001.html @@ -25,7 +25,7 @@ - + @@ -35,7 +35,7 @@ - + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/test-plan.src.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/test-plan.src.html index c29f268837dd8..644c10aa12205 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/test-plan.src.html +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/test-plan.src.html @@ -1490,7 +1490,7 @@

An element with isolation:isolate creates an isolated group for [P] - parent element with a property that creates a stacking context (e.g. position:fixed); the element background-color is other than transparent
[IN-P] - Intermediate child element between the parent [P] and the child [B]
The intermediate element has text content, default value for background-color and isolation:isolate set
- [B] - element with mix-blend-mode other than normal + [B] - element with mix-blend-mode other than normal The color of the child element [B] mixes with the color of the intermediate element [IN-P], where they overlap.
There is no blending between the color of the parent element [P] and the color of the blended element [B]. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/html5lib/html5lib/filters/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/html5lib/html5lib/filters/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/html5lib/html5lib/filters/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/html5lib/html5lib/filters/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/.github/workflows/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/.github/workflows/w3c-import.log new file mode 100644 index 0000000000000..b702d181582a4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/.github/workflows/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/.github/workflows/ci.yml diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/docs/source/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/docs/source/w3c-import.log new file mode 100644 index 0000000000000..4a9240e188c1e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/docs/source/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/docs/source/api.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/docs/source/conf.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/docs/source/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/docs/source/installation.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/docs/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/docs/w3c-import.log new file mode 100644 index 0000000000000..d88f1f8a0a6ea --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/docs/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/docs/Makefile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/docs/make.bat diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/src/hyperframe/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/src/hyperframe/w3c-import.log new file mode 100644 index 0000000000000..6cef8e4982c69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/src/hyperframe/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/src/hyperframe/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/src/hyperframe/exceptions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/src/hyperframe/flags.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/src/hyperframe/frame.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/src/hyperframe/py.typed diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/test/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/test/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/test/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/test/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/test/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/test/w3c-import.log new file mode 100644 index 0000000000000..8e610411076b7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/test/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/test/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/test/test_external_collection.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/test/test_flags.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/test/test_frames.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/w3c-import.log new file mode 100644 index 0000000000000..fea80c2dfb14d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/w3c-import.log @@ -0,0 +1,25 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/CHANGELOG.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/CONTRIBUTORS.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/MANIFEST.in +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/Makefile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/setup.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/setup.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/hyperframe/tox.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/.github/workflows/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/.github/workflows/w3c-import.log new file mode 100644 index 0000000000000..4d5c401635edc --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/.github/workflows/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/.github/workflows/main.yml diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/docs/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/docs/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/docs/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/docs/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/docs/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/docs/w3c-import.log new file mode 100644 index 0000000000000..5b001660055e5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/docs/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/docs/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/docs/changelog.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/docs/conf.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/docs/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/docs/using.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/importlib_metadata/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/importlib_metadata/w3c-import.log new file mode 100644 index 0000000000000..7c1c198824c9e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/importlib_metadata/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/importlib_metadata/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/importlib_metadata/_compat.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/prepare/example/example/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/prepare/example/example/w3c-import.log new file mode 100644 index 0000000000000..422d99f62862b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/prepare/example/example/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/prepare/example/example/__init__.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/prepare/example/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/prepare/example/w3c-import.log new file mode 100644 index 0000000000000..cc083274a133e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/prepare/example/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/prepare/example/setup.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/data/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/data/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/data/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/data/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/data/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/data/w3c-import.log new file mode 100644 index 0000000000000..b61e050bcc846 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/data/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/data/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/data/example-21.12-py3-none-any.whl +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/data/example-21.12-py3.6.egg diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/w3c-import.log new file mode 100644 index 0000000000000..ac37c49ab56b3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/fixtures.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/py39compat.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/test_api.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/test_integration.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/test_main.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tests/test_zip.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/w3c-import.log new file mode 100644 index 0000000000000..3307926bf5481 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/w3c-import.log @@ -0,0 +1,26 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/MANIFEST.in +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/codecov.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/coverage.ini +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/coverplug.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/pyproject.toml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/setup.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/setup.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/importlib_metadata/tox.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/src/iniconfig/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/src/iniconfig/w3c-import.log new file mode 100644 index 0000000000000..d54c8377e8518 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/src/iniconfig/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/src/iniconfig/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/src/iniconfig/__init__.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/src/iniconfig/py.typed diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/testing/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/testing/w3c-import.log new file mode 100644 index 0000000000000..d24d56e5c0189 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/testing/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/testing/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/testing/test_iniconfig.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/w3c-import.log new file mode 100644 index 0000000000000..7cd627a844f95 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/w3c-import.log @@ -0,0 +1,25 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/CHANGELOG +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/MANIFEST.in +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/README.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/example.ini +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/pyproject.toml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/setup.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/setup.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/iniconfig/tox.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/docs/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/docs/w3c-import.log new file mode 100644 index 0000000000000..c2dce884a83df --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/docs/w3c-import.log @@ -0,0 +1,24 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/docs/Makefile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/docs/api.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/docs/conf.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/docs/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/docs/license.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/docs/make.bat +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/docs/testing.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/docs/versions.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/tests/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/tests/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/tests/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/tests/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/tests/w3c-import.log new file mode 100644 index 0000000000000..a302a818b5805 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/tests/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/tests/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/tests/test_more.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/tests/test_recipes.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/w3c-import.log new file mode 100644 index 0000000000000..a7d79b68a5e4b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/more.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/more_itertools/recipes.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/w3c-import.log new file mode 100644 index 0000000000000..876731aed3e16 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/MANIFEST.in +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/setup.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/setup.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/more-itertools/tox.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/.github/workflows/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/.github/workflows/w3c-import.log new file mode 100644 index 0000000000000..6740f4ce8fa9b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/.github/workflows/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/.github/workflows/docs.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/.github/workflows/lint.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/.github/workflows/test.yml diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/development/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/development/w3c-import.log new file mode 100644 index 0000000000000..3a7c728ff556a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/development/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/development/getting-started.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/development/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/development/release-process.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/development/reviewing-patches.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/development/submitting-patches.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/w3c-import.log new file mode 100644 index 0000000000000..7b68529984366 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/w3c-import.log @@ -0,0 +1,28 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/Makefile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/changelog.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/conf.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/markers.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/requirements.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/requirements.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/security.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/specifiers.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/tags.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/utils.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/docs/version.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/packaging/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/packaging/w3c-import.log new file mode 100644 index 0000000000000..c8db1ead6c6f5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/packaging/w3c-import.log @@ -0,0 +1,28 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/packaging/__about__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/packaging/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/packaging/_manylinux.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/packaging/_musllinux.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/packaging/_structures.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/packaging/markers.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/packaging/py.typed +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/packaging/requirements.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/packaging/specifiers.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/packaging/tags.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/packaging/utils.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/packaging/version.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tasks/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tasks/w3c-import.log new file mode 100644 index 0000000000000..6619c0f2b4077 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tasks/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tasks/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tasks/check.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tasks/paths.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tasks/requirements.txt diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/manylinux/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/manylinux/w3c-import.log new file mode 100644 index 0000000000000..a329a955ba7c8 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/manylinux/w3c-import.log @@ -0,0 +1,27 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/manylinux/build.sh +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/manylinux/hello-world-armv7l-armel +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/manylinux/hello-world-armv7l-armhf +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/manylinux/hello-world-invalid-class +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/manylinux/hello-world-invalid-data +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/manylinux/hello-world-invalid-magic +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/manylinux/hello-world-s390x-s390x +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/manylinux/hello-world-too-short +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/manylinux/hello-world-x86_64-amd64 +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/manylinux/hello-world-x86_64-i386 +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/manylinux/hello-world-x86_64-x32 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/musllinux/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/musllinux/w3c-import.log new file mode 100644 index 0000000000000..b149a60115de0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/musllinux/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/musllinux/build.sh +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/musllinux/glibc-x86_64 +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/musllinux/musl-aarch64 +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/musllinux/musl-i386 +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/musllinux/musl-x86_64 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/w3c-import.log new file mode 100644 index 0000000000000..49e63afaadf82 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/w3c-import.log @@ -0,0 +1,27 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/hello-world.c +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/test_manylinux.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/test_markers.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/test_musllinux.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/test_requirements.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/test_specifiers.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/test_structures.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/test_tags.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/test_utils.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/tests/test_version.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/w3c-import.log new file mode 100644 index 0000000000000..1609a204d9d02 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/w3c-import.log @@ -0,0 +1,28 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/CHANGELOG.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/CONTRIBUTING.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/LICENSE.APACHE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/LICENSE.BSD +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/MANIFEST.in +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/mypy.ini +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/noxfile.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/pyproject.toml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/setup.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/packaging/setup.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pdf_js/pdf.worker.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pdf_js/pdf.worker.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pdf_js/pdf.worker.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pdf_js/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pdf_js/w3c-import.log new file mode 100644 index 0000000000000..7d41c7a28b444 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pdf_js/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pdf_js/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pdf_js/pdf.js +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pdf_js/pdf.worker.js diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/.github/FUNDING.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/.github/FUNDING.yml new file mode 100644 index 0000000000000..b4d3edff1f6a6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/.github/FUNDING.yml @@ -0,0 +1,5 @@ +# info: +# * 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/pluggy +open_collective: pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/.github/workflows/main.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/.github/workflows/main.yml index e1022ca96dd2f..d8c0b181a7c4a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/.github/workflows/main.yml +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/.github/workflows/main.yml @@ -19,62 +19,64 @@ jobs: fail-fast: false matrix: name: [ - "windows-py36", - "windows-py39", + "windows-py38", + "windows-py311", "windows-pypy3", - "ubuntu-py36", - "ubuntu-py36-pytestmain", - "ubuntu-py37", + "ubuntu-py38-pytestmain", "ubuntu-py38", "ubuntu-py39", + "ubuntu-py310", + "ubuntu-py311", + "ubuntu-py312", "ubuntu-pypy3", "ubuntu-benchmark", - - "linting", - "docs", ] include: - - name: "windows-py36" - python: "3.6" + - name: "windows-py38" + python: "3.8" os: windows-latest - tox_env: "py36" - - name: "windows-py39" - python: "3.9" + tox_env: "py38" + - name: "windows-py311" + python: "3.10" os: windows-latest - tox_env: "py39" + tox_env: "py311" - name: "windows-pypy3" - python: "pypy3" + python: "pypy3.9" os: windows-latest tox_env: "pypy3" - - name: "ubuntu-py36" - python: "3.6" - os: ubuntu-latest - tox_env: "py36" - use_coverage: true - - name: "ubuntu-py36-pytestmain" - python: "3.6" - os: ubuntu-latest - tox_env: "py36-pytestmain" - use_coverage: true - - name: "ubuntu-py37" - python: "3.7" - os: ubuntu-latest - tox_env: "py37" - use_coverage: true - name: "ubuntu-py38" python: "3.8" os: ubuntu-latest tox_env: "py38" use_coverage: true + - name: "ubuntu-py38-pytestmain" + python: "3.8" + os: ubuntu-latest + tox_env: "py38-pytestmain" + use_coverage: true - name: "ubuntu-py39" python: "3.9" os: ubuntu-latest tox_env: "py39" use_coverage: true + - name: "ubuntu-py310" + python: "3.10" + os: ubuntu-latest + tox_env: "py310" + - name: "ubuntu-py311" + python: "3.11" + os: ubuntu-latest + tox_env: "py311" + use_coverage: true + - name: "ubuntu-py312" + python: "3.12-dev" + os: ubuntu-latest + tox_env: "py312" + use_coverage: true - name: "ubuntu-pypy3" - python: "pypy3" + python: "pypy3.9" os: ubuntu-latest tox_env: "pypy3" use_coverage: true @@ -82,22 +84,14 @@ jobs: python: "3.8" os: ubuntu-latest tox_env: "benchmark" - - name: "linting" - python: "3.8" - os: ubuntu-latest - tox_env: "linting" - - name: "docs" - python: "3.8" - os: ubuntu-latest - tox_env: "docs" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} @@ -116,33 +110,36 @@ jobs: - name: Upload coverage if: matrix.use_coverage && github.repository == 'pytest-dev/pluggy' - env: - CODECOV_NAME: ${{ matrix.name }} - run: bash scripts/upload-coverage.sh -F GHA,${{ runner.os }} + uses: codecov/codecov-action@v4 + continue-on-error: true + with: + fail_ci_if_error: true + files: ./coverage.xml + verbose: true deploy: if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pluggy' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: - python-version: "3.8" + python-version: "3.11" - name: Install dependencies run: | python -m pip install --upgrade pip - pip install --upgrade wheel setuptools setuptools_scm + pip install --upgrade build - name: Build package - run: python setup.py sdist bdist_wheel + run: python -m build --sdist --wheel --outdir dist/ - name: Publish package - uses: pypa/gh-action-pypi-publish@v1.4.1 + uses: pypa/gh-action-pypi-publish@v1.8.6 with: user: __token__ password: ${{ secrets.pypi_token }} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/CHANGELOG.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/CHANGELOG.rst index 13a388c435a8c..a597dc370a6ae 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/CHANGELOG.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/CHANGELOG.rst @@ -2,8 +2,145 @@ Changelog ========= +Versions follow `Semantic Versioning `_ (``..``). + +.. + 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 +pluggy 1.5.0 (2024-04-19) +========================= + +Features +-------- + +- `#178 `_: Add support for deprecating specific hook parameters, or more generally, for issuing a warning whenever a hook implementation requests certain parameters. + + See :ref:`warn_on_impl` for details. + + + +Bug Fixes +--------- + +- `#481 `_: ``PluginManager.get_plugins()`` no longer returns ``None`` for blocked plugins. + + +pluggy 1.4.0 (2024-01-24) +========================= + +Features +-------- + +- `#463 `_: A warning :class:`~pluggy.PluggyTeardownRaisedWarning` is now issued when an old-style hookwrapper raises an exception during teardown. + See the warning documentation for more details. + +- `#471 `_: Add :func:`PluginManager.unblock ` method to unblock a plugin by plugin name. + +Bug Fixes +--------- + +- `#441 `_: Fix :func:`~pluggy.HookCaller.call_extra()` extra methods getting ordered before everything else in some circumstances. Regressed in pluggy 1.1.0. + +- `#438 `_: Fix plugins registering other plugins in a hook when the other plugins implement the same hook itself. Regressed in pluggy 1.1.0. + + +pluggy 1.3.0 (2023-08-26) +========================= + +Deprecations and Removals +------------------------- + +- `#426 `_: Python 3.7 is no longer supported. + + + +Features +-------- + +- `#428 `_: Pluggy now exposes its typings to static type checkers. + + As part of this, the following changes are made: + + - Renamed ``_Result`` to ``Result``, and exported as :class:`pluggy.Result`. + - Renamed ``_HookRelay`` to ``HookRelay``, and exported as :class:`pluggy.HookRelay`. + - Renamed ``_HookCaller`` to ``HookCaller``, and exported as :class:`pluggy.HookCaller`. + - Exported ``HookImpl`` as :class:`pluggy.HookImpl`. + - Renamed ``_HookImplOpts`` to ``HookimplOpts``, and exported as :class:`pluggy.HookimplOpts`. + - Renamed ``_HookSpecOpts`` to ``HookspecOpts``, and exported as :class:`pluggy.HookspecOpts`. + - Some fields and classes are marked ``Final`` and ``@final``. + - The :ref:`api-reference` is updated to clearly delineate pluggy's public API. + + Compatibility aliases are put in place for the renamed types. + We do not plan to remove the aliases, but we strongly recommend to only import from ``pluggy.*`` to ensure future compatibility. + + Please note that pluggy is currently unable to provide strong typing for hook calls, e.g. ``pm.hook.my_hook(...)``, + nor to statically check that a hook implementation matches the hook specification's type. + + +pluggy 1.2.0 (2023-06-21) +========================= + +Features +-------- + +- `#405 `_: The new-style hook wrappers, added in the yanked 1.1.0 release, now require an explicit ``wrapper=True`` designation in the ``@hookimpl()`` decorator. + + +pluggy 1.1.0 (YANKED) +===================== + +.. note:: + + This release was yanked because unfortunately the implicit new-style hook wrappers broke some downstream projects. + See `#403 `__ for more information. + This was rectified in the 1.2.0 release. + +Deprecations and Removals +------------------------- + +- `#364 `_: Python 3.6 is no longer supported. + + + +Features +-------- + +- `#260 `_: Added "new-style" hook wrappers, a simpler but equally powerful alternative to the existing ``hookwrapper=True`` wrappers. + + New-style wrappers are generator functions, similarly to ``hookwrapper``, but do away with the :class:`result ` object. + Instead, the return value is sent directly to the ``yield`` statement, or, if inner calls raised an exception, it is raised from the ``yield``. + The wrapper is expected to return a value or raise an exception, which will become the result of the hook call. + + New-style wrappers are fully interoperable with old-style wrappers. + We encourage users to use the new style, however we do not intend to deprecate the old style any time soon. + + See :ref:`hookwrappers` for the full documentation. + + +- `#364 `_: Python 3.11 and 3.12 are now officially supported. + + +- `#394 `_: Added the :meth:`~pluggy.Result.force_exception` method to ``_Result``. + + ``force_exception`` allows (old-style) hookwrappers to force an exception or override/adjust an existing exception of a hook invocation, + in a properly behaving manner. Using ``force_exception`` is preferred over raising an exception from the hookwrapper, + because raising an exception causes other hookwrappers to be skipped. + + pluggy 1.0.0 (2021-08-25) ========================= @@ -48,7 +185,7 @@ Features -------- - `#282 `_: When registering a hookimpl which is declared as ``hookwrapper=True`` but whose - function is not a generator function, a ``PluggyValidationError`` exception is + function is not a generator function, a :class:`~pluggy.PluginValidationError` exception is now raised. Previously this problem would cause an error only later, when calling the hook. @@ -58,7 +195,7 @@ Features .. code-block:: python - def my_hook_real_implementation(arg): + def my_hook_implementation(arg): print("before") yield print("after") @@ -79,6 +216,10 @@ Features - `#309 `_: Add official support for Python 3.9. +- `#251 `_: Add ``specname`` option to ``@hookimpl``. If ``specname`` is provided, it will be used + instead of the function name when matching this hook implementation to a hook specification during registration (allowing a plugin to register + a hook implementation that was not named the same thing as the corresponding ``@hookspec``). + pluggy 0.13.1 (2019-11-21) ========================== diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/README.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/README.rst index 3496617e1e879..7a58c1b9c8d77 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/README.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/README.rst @@ -99,3 +99,18 @@ Running this directly gets us:: http://doc.devpi.net .. _read the docs: https://pluggy.readthedocs.io/en/latest/ + + +Support pluggy +-------------- + +`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. + +``pluggy`` is part of the ``pytest-dev`` project, see more details in the `pytest collective`_. + +.. _Open Collective: https://opencollective.com +.. _pytest collective: https://opencollective.com/pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/RELEASING.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/RELEASING.rst index ee0d1331e0a46..3d6ba16c16b41 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/RELEASING.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/RELEASING.rst @@ -1,6 +1,10 @@ Release Procedure ----------------- +#. Dependening on the magnitude of the changes in the release, consider testing + some of the large downstream users of pluggy against the upcoming release. + You can do so using the scripts in the ``downstream/`` directory. + #. From a clean work tree, execute:: tox -e release -- VERSION diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/SECURITY.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/SECURITY.md new file mode 100644 index 0000000000000..6d3c7348e89c8 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/SECURITY.md @@ -0,0 +1,3 @@ +# Security + +Please report any security vulnerabilities at https://github.com/pytest-dev/pluggy/security. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/TIDELIFT.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/TIDELIFT.rst new file mode 100644 index 0000000000000..93af996b29614 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/TIDELIFT.rst @@ -0,0 +1,60 @@ +======== +Tidelift +======== + +pluggy is a member of `Tidelift`_. This document describes how the core team manages +Tidelift-related activities. + +What is it +========== + +Tidelift aims to make Open Source sustainable by offering subscriptions to companies which rely +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 +===== + +It was decided in the `mailing list`_ that the Tidelift contribution will be split evenly between +members of the `contributors team`_ interested in receiving funding. + +The current list of contributors receiving funding are: + +* `@nicoddemus`_ +* `@The-Compiler`_ +* `@RonnyPfannschmidt`_ + +Contributors interested in receiving a part of the funds just need to submit a PR adding their +name to the list. Contributors that want to stop receiving the funds should also submit a PR +in the same way. + +The PR should mention `@pytest-dev/tidelift-admins`_ so appropriate changes +can be made in the Tidelift platform. + +After the PR has been accepted and merged, the contributor should register in the `Tidelift`_ +platform and follow the instructions there, including signing an `agreement`_. + +Admins +====== + +A few people have admin access to the Tidelift dashboard to make changes. Those people +are part of the `@pytest-dev/tidelift-admins`_ team. + +`Core contributors`_ interested in helping out with Tidelift maintenance are welcome! We don't +expect much work here other than the occasional adding/removal of a contributor from receiving +funds. Just drop a line to one of the `@pytest-dev/tidelift-admins`_ or use the mailing list. + + +.. _`Tidelift`: https://tidelift.com +.. _`mailing list`: https://mail.python.org/pipermail/pytest-dev/2019-May/004716.html +.. _`contributors team`: https://github.com/orgs/pytest-dev/teams/contributors +.. _`core contributors`: https://github.com/orgs/pytest-dev/teams/core/members +.. _`@pytest-dev/tidelift-admins`: https://github.com/orgs/pytest-dev/teams/tidelift-admins/members +.. _`agreement`: https://tidelift.com/docs/lifting/agreement + +.. _`@nicoddemus`: https://github.com/nicoddemus +.. _`@The-Compiler`: https://github.com/The-Compiler +.. _`@RonnyPfannschmidt`: https://github.com/RonnyPfannschmidt diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/api_reference.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/api_reference.rst index d9552d44858ae..b14d725d94cb6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/api_reference.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/api_reference.rst @@ -1,19 +1,61 @@ :orphan: -Api Reference +.. _`api-reference`: + +API Reference ============= -.. automodule:: pluggy +.. autoclass:: pluggy.PluginManager + :members: + +.. autoclass:: pluggy.PluginValidationError + :show-inheritance: + :members: + +.. autodecorator:: pluggy.HookspecMarker + +.. autodecorator:: pluggy.HookimplMarker + +.. autoclass:: pluggy.HookRelay() + :members: + + .. data:: + + :type: HookCaller + + The caller for the hook with the given name. + +.. autoclass:: pluggy.HookCaller() + :members: + :special-members: __call__ + +.. autoclass:: pluggy.HookCallError() + :show-inheritance: :members: - :undoc-members: + +.. autoclass:: pluggy.Result() :show-inheritance: + :members: -.. autoclass:: pluggy._callers._Result -.. automethod:: pluggy._callers._Result.get_result -.. automethod:: pluggy._callers._Result.force_result +.. autoclass:: pluggy.HookImpl() + :members: -.. autoclass:: pluggy._hooks._HookCaller -.. automethod:: pluggy._hooks._HookCaller.call_extra -.. automethod:: pluggy._hooks._HookCaller.call_historic +.. autoclass:: pluggy.HookspecOpts() + :show-inheritance: + :members: -.. autoclass:: pluggy._hooks._HookRelay +.. autoclass:: pluggy.HookimplOpts() + :show-inheritance: + :members: + + +Warnings +-------- + +Custom warnings generated in some situations such as improper usage or deprecated features. + +.. autoclass:: pluggy.PluggyWarning() + :show-inheritance: + +.. autoclass:: pluggy.PluggyTeardownRaisedWarning() + :show-inheritance: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/conf.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/conf.py index f8e70c88bf3de..e5151c5a45ced 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/conf.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/conf.py @@ -1,9 +1,9 @@ -import sys +from importlib import metadata +from typing import TYPE_CHECKING -if sys.version_info >= (3, 8): - from importlib import metadata -else: - import importlib_metadata as metadata + +if TYPE_CHECKING: + import sphinx.application extensions = [ @@ -33,7 +33,7 @@ version = ".".join(release.split(".")[:2]) -language = None +language = "en" pygments_style = "sphinx" # html_logo = "_static/img/plug.png" @@ -46,8 +46,9 @@ "github_button": "true", "github_banner": "true", "github_type": "star", - "badge_branch": "master", + "badge_branch": "main", "page_width": "1080px", + "sidebar_width": "300px", "fixed_sidebar": "false", } html_sidebars = { @@ -59,6 +60,20 @@ # (source start file, name, description, authors, manual section). man_pages = [(master_doc, "pluggy", "pluggy Documentation", [author], 1)] +autodoc_member_order = "bysource" + +nitpicky = True +nitpick_ignore = { + # Don't want to expose this yet (see #428). + ("py:class", "pluggy._tracing.TagTracerSub"), + # Compat hack, don't want to expose it. + ("py:class", "pluggy._manager.DistFacade"), + # `types.ModuleType` turns into `module` but then fails to resolve... + ("py:class", "module"), + # Just a TypeVar. + ("py:obj", "pluggy._result.ResultType"), + ("py:class", "pluggy._result.ResultType"), +} # -- Options for Texinfo output ------------------------------------------- @@ -81,7 +96,36 @@ intersphinx_mapping = { "python": ("https://docs.python.org/3", None), "pytest": ("https://docs.pytest.org/en/latest", None), - "setuptools": ("https://setuptools.readthedocs.io/en/latest", None), - "tox": ("https://tox.readthedocs.io/en/latest", None), + "setuptools": ("https://setuptools.pypa.io/en/latest", None), + "tox": ("https://tox.wiki/en/latest", None), "devpi": ("https://devpi.net/docs/devpi/devpi/stable/+doc/", None), + "kedro": ("https://docs.kedro.org/en/latest/", None), } + + +def configure_logging(app: "sphinx.application.Sphinx") -> None: + """Configure Sphinx's WarningHandler to handle (expected) missing include.""" + import logging + + import sphinx.util.logging + + class WarnLogFilter(logging.Filter): + def filter(self, record: logging.LogRecord) -> bool: + """Ignore warnings about missing include with "only" directive. + + 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: + configure_logging(app) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample-spam/setup.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample-spam/setup.py index f81a8eb403879..557aa5c1f1358 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample-spam/setup.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample-spam/setup.py @@ -1,5 +1,6 @@ from setuptools import setup + setup( name="eggsample-spam", install_requires="eggsample", diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/__init__.py index 4dc4b36dec3ac..b2d9d8301facc 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/__init__.py @@ -1,4 +1,5 @@ import pluggy + hookimpl = pluggy.HookimplMarker("eggsample") """Marker to be imported and used in plugins (and for own implementations)""" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/hookspecs.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/hookspecs.py index 48866b2491287..4bab42281de96 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/hookspecs.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/hookspecs.py @@ -1,5 +1,6 @@ import pluggy + hookspec = pluggy.HookspecMarker("eggsample") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/host.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/host.py index ac1d33b453992..d18278792362f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/host.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/host.py @@ -1,9 +1,11 @@ import itertools import random +from eggsample import hookspecs +from eggsample import lib + import pluggy -from eggsample import hookspecs, lib condiments_tray = {"pickled walnuts": 13, "steak sauce": 4, "mushy peas": 2} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/setup.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/setup.py index 8b3facb3b6718..89e88ceca07c1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/setup.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/eggsample/setup.py @@ -1,4 +1,6 @@ -from setuptools import setup, find_packages +from setuptools import find_packages +from setuptools import setup + setup( name="eggsample", diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/toy-example.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/toy-example.py index 6d2086f9ba3bd..c7d361bc4dbea 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/toy-example.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/examples/toy-example.py @@ -1,5 +1,6 @@ import pluggy + hookspec = pluggy.HookspecMarker("myproject") hookimpl = pluggy.HookimplMarker("myproject") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/index.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/index.rst index eab08fcbbd616..b98c4956ba2e6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/index.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/index.rst @@ -6,7 +6,7 @@ What is it? *********** ``pluggy`` is the crystallized core of :ref:`plugin management and hook calling ` for :std:doc:`pytest `. -It enables `500+ plugins`_ to extend and customize ``pytest``'s default +It enables `1400+ plugins`_ to extend and customize ``pytest``'s default behaviour. Even ``pytest`` itself is composed as a set of ``pluggy`` plugins which are invoked in sequence according to a well defined set of protocols. @@ -162,6 +162,7 @@ documentation and source code: * :ref:`pytest ` * :std:doc:`tox ` * :std:doc:`devpi ` +* :std:doc:`kedro ` For more details and advanced usage please read on. @@ -224,6 +225,8 @@ which has been appropriately marked. *hookimpls* are loaded from a plugin using the :py:meth:`~pluggy.PluginManager.register()` method: +*hookimpls* must be hashable. + .. code-block:: python import sys @@ -281,19 +284,22 @@ example above). Note: there is *no* strict requirement that each *hookimpl* has a corresponding *hookspec* (see :ref:`enforcing spec validation `). -*new in version 0.13.2:* +*new in version 1.0.0:* To override the default behavior, a *hookimpl* may also be matched to a *hookspec* in the *host* program with a non-matching function name by using the ``specname`` option. Continuing the example above, the *hookimpl* function does not need to be named ``setup_project``, but if the argument ``specname="setup_project"`` is provided to the ``hookimpl`` decorator, it will -be matched and checked against the ``setup_project`` hookspec: +be matched and checked against the ``setup_project`` hookspec. + +This is useful for registering multiple implementations of the same plugin in +the same Python file, for example: .. code-block:: python @hookimpl(specname="setup_project") - def any_plugin_function(config, args): + def setup_1(config, args): """This hook is used to process the initial config and possibly input arguments. """ @@ -302,6 +308,16 @@ be matched and checked against the ``setup_project`` hookspec: return config + + @hookimpl(specname="setup_project") + def setup_2(config, args): + """Perform additional setup steps""" + # ... + return config + + +.. _callorder: + Call time order ^^^^^^^^^^^^^^^ By default hooks are :ref:`called ` in LIFO registered order, however, @@ -359,32 +375,108 @@ For another example see the :ref:`pytest:plugin-hookorder` section of the Wrappers ^^^^^^^^ -A *hookimpl* can be marked with a ``"hookwrapper"`` option which indicates that -the function will be called to *wrap* (or surround) all other normal *hookimpl* -calls. A *hookwrapper* can thus execute some code ahead and after the execution -of all corresponding non-wrappper *hookimpls*. -Much in the same way as a :py:func:`@contextlib.contextmanager `, *hookwrappers* must -be implemented as generator function with a single ``yield`` in its body: +.. note:: + This section describes "new-style hook wrappers", which were added in Pluggy + 1.1. For earlier versions, see the "old-style hook wrappers" section below. + + New-style hooks wrappers are declared with ``wrapper=True``, while + old-style hook wrappers are declared with ``hookwrapper=True``. + + The two styles are fully interoperable between plugins using different + styles. However within the same plugin we recommend using only one style, + for consistency. + +A *hookimpl* can be marked with the ``"wrapper"`` option, which indicates +that the function will be called to *wrap* (or surround) all other normal +*hookimpl* calls. A *hook wrapper* can thus execute some code ahead and after the +execution of all corresponding non-wrapper *hookimpls*. +Much in the same way as a :py:func:`@contextlib.contextmanager `, +*hook wrappers* must be implemented as generator function with a single ``yield`` in its body: .. code-block:: python - @hookimpl(hookwrapper=True) + @hookimpl(wrapper=True) def setup_project(config, args): """Wrap calls to ``setup_project()`` implementations which should return json encoded config options. """ + # get initial default config + defaults = config.tojson() + if config.debug: print("Pre-hook config is {}".format(config.tojson())) + # all corresponding hookimpls are invoked here + result = yield + + for item in result: + print("JSON config override is {}".format(item)) + + if config.debug: + print("Post-hook config is {}".format(config.tojson())) + + if config.use_defaults: + return defaults + else: + return result + +The generator is :py:meth:`sent ` the return value +of the hook thus far, or, if the previous calls raised an exception, it is +:py:meth:`thrown ` the exception. + +The function should do one of two things: + +- Return a value, which can be the same value as received from the ``yield``, or something else entirely. + +- Raise an exception. + +The return value or exception propagate to further hook wrappers, and finally +to the hook caller. + +Also see the :ref:`pytest:hookwrapper` section in the ``pytest`` docs. + +.. _old_style_hookwrappers: + +Old-style wrappers +^^^^^^^^^^^^^^^^^^ + +.. note:: + Prefer to use new-style hook wrappers, unless you need to support Pluggy + versions before 1.1. + +A *hookimpl* can be marked with the ``"hookwrapper"`` option, which indicates +that the function will be called to *wrap* (or surround) all other normal +*hookimpl* calls. A *hookwrapper* can thus execute some code ahead and after the +execution of all corresponding non-wrapper *hookimpls*. + +*hookwrappers* must be implemented as generator function with a single ``yield`` in its body: + + +.. code-block:: python + + @hookimpl(hookwrapper=True) + def setup_project(config, args): + """Wrap calls to ``setup_project()`` implementations which + should return json encoded config options. + """ # get initial default config defaults = config.tojson() + if config.debug: + print("Pre-hook config is {}".format(config.tojson())) + # all corresponding hookimpls are invoked here outcome = yield - for item in outcome.get_result(): + try: + result = outcome.get_result() + except BaseException as e: + outcome.force_exception(e) + return + + for item in result: print("JSON config override is {}".format(item)) if config.debug: @@ -393,17 +485,22 @@ be implemented as generator function with a single ``yield`` in its body: if config.use_defaults: outcome.force_result(defaults) -The generator is :py:meth:`sent ` a :py:class:`pluggy._callers._Result` object which can -be assigned in the ``yield`` expression and used to override or inspect -the final result(s) returned back to the caller using the -:py:meth:`~pluggy._callers._Result.force_result` or -:py:meth:`~pluggy._callers._Result.get_result` methods. +The generator is :py:meth:`sent ` a :py:class:`pluggy.Result` object which can +be assigned in the ``yield`` expression and used to inspect +the final result(s) or exceptions returned back to the caller using the +:py:meth:`~pluggy.Result.get_result` method, override the result +using the :py:meth:`~pluggy.Result.force_result`, or override +the exception using the :py:meth:`~pluggy.Result.force_exception` +method. .. note:: - Hook wrappers can **not** return results (as per generator function - semantics); they can only modify them using the ``_Result`` API. + Old-style hook wrappers can **not** return results; they can only modify + them using the :py:meth:`~pluggy.Result.force_result` API. -Also see the :ref:`pytest:hookwrapper` section in the ``pytest`` docs. + Old-style Hook wrappers should **not** raise exceptions; this will cause + further hookwrappers to be skipped. They should use + :py:meth:`~pluggy.Result.force_exception` to adjust the + exception. .. _specs: @@ -469,7 +566,7 @@ then make sure to mark those hooks as :ref:`optional `. Opt-in arguments ^^^^^^^^^^^^^^^^ To allow for *hookspecs* to evolve over the lifetime of a project, -*hookimpls* can accept **less** arguments then defined in the spec. +*hookimpls* can accept **less** arguments than defined in the spec. This allows for extending hook arguments (and thus semantics) without breaking existing *hookimpls*. @@ -511,7 +608,7 @@ First result only ^^^^^^^^^^^^^^^^^ A *hookspec* can be marked such that when the *hook* is called the call loop will only invoke up to the first *hookimpl* which returns a result other -then ``None``. +than ``None``. .. code-block:: python @@ -522,7 +619,7 @@ then ``None``. This can be useful for optimizing a call loop for which you are only interested in a single core *hookimpl*. An example is the :func:`~_pytest.hookspec.pytest_cmdline_main` central routine of ``pytest``. -Note that all ``hookwrappers`` are still invoked with the first result. +Note that all hook wrappers are still invoked with the first result. Also see the :ref:`pytest:firstresult` section in the ``pytest`` docs. @@ -531,7 +628,7 @@ Also see the :ref:`pytest:firstresult` section in the ``pytest`` docs. Historic hooks ^^^^^^^^^^^^^^ You can mark a *hookspec* as being *historic* meaning that the hook -can be called with :py:meth:`~pluggy._hooks._HookCaller.call_historic()` **before** +can be called with :py:meth:`~pluggy.HookCaller.call_historic()` **before** having been registered: .. code-block:: python @@ -549,22 +646,47 @@ dynamically loaded plugins. For more info see :ref:`call_historic`. +.. _warn_on_impl: + Warnings on hook implementation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ As projects evolve new hooks may be introduced and/or deprecated. -if a hookspec specifies a ``warn_on_impl``, pluggy will trigger it for any plugin implementing the hook. +If a hookspec specifies a ``warn_on_impl``, pluggy will trigger it for any plugin implementing the hook. + + +.. code-block:: python + + @hookspec( + warn_on_impl=DeprecationWarning("old_hook is deprecated and will be removed soon") + ) + def old_hook(): + pass + +If you don't want to deprecate implementing the entire hook, but just specific +parameters of it, you can specify ``warn_on_impl_args``, a dict mapping +parameter names to warnings. The warnings will trigger whenever any plugin +implements the hook requesting one of the specified parameters. .. code-block:: python @hookspec( - warn_on_impl=DeprecationWarning("oldhook is deprecated and will be removed soon") + warn_on_impl_args={ + "lousy_arg": DeprecationWarning( + "The lousy_arg parameter of refreshed_hook is deprecated and will be removed soon; " + "use awesome_arg instead" + ), + }, ) - def oldhook(): + def refreshed_hook(lousy_arg, awesome_arg): pass +.. versionadded:: 1.5 + The ``warn_on_impl_args`` parameter. + + .. _manage: The Plugin registry @@ -653,13 +775,13 @@ The core functionality of ``pluggy`` enables an extension provider to override function calls made at certain points throughout a program. A particular *hook* is invoked by calling an instance of -a :py:class:`pluggy._hooks._HookCaller` which in turn *loops* through the +a :py:class:`pluggy.HookCaller` which in turn *loops* through the ``1:N`` registered *hookimpls* and calls them in sequence. Every :py:class:`~pluggy.PluginManager` has a ``hook`` attribute -which is an instance of this :py:class:`pluggy._hooks._HookRelay`. -The :py:class:`~pluggy._hooks._HookRelay` itself contains references -(by hook name) to each registered *hookimpl*'s :py:class:`~pluggy._hooks._HookCaller` instance. +which is an instance of :py:class:`pluggy.HookRelay`. +The :py:class:`~pluggy.HookRelay` itself contains references +(by hook name) to each registered *hookimpl*'s :py:class:`~pluggy.HookCaller` instance. More practically you call a *hook* like so: @@ -675,7 +797,7 @@ More practically you call a *hook* like so: pm.add_hookspecs(mypluginspec) pm.register(myplugin) - # we invoke the _HookCaller and thus all underlying hookimpls + # we invoke the HookCaller and thus all underlying hookimpls result_list = pm.hook.myhook(config=config, args=sys.argv) Note that you **must** call hooks using keyword :std:term:`python:argument` syntax! @@ -723,7 +845,7 @@ Collecting results ------------------ By default calling a hook results in all underlying :ref:`hookimpls ` functions to be invoked in sequence via a loop. Any function -which returns a value other then a ``None`` result will have that result +which returns a value other than a ``None`` result will have that result appended to a :py:class:`list` which is returned by the call. The only exception to this behaviour is if the hook has been marked to return @@ -734,10 +856,9 @@ single value (which is not ``None``) will be returned. Exception handling ------------------ -If any *hookimpl* errors with an exception no further callbacks -are invoked and the exception is packaged up and delivered to -any :ref:`wrappers ` before being re-raised at the -hook invocation point: +If any *hookimpl* errors with an exception no further callbacks are invoked and +the exception is delivered to any :ref:`wrappers ` before being +re-raised at the hook invocation point: .. code-block:: python @@ -764,15 +885,14 @@ hook invocation point: return 3 - @hookimpl(hookwrapper=True) + @hookimpl(wrapper=True) def myhook(self, args): - outcome = yield - try: - outcome.get_result() - except RuntimeError: - # log the error details - print(outcome.excinfo) + return (yield) + except RuntimeError as exc: + # log runtime error details + print(exc) + raise pm = PluginManager("myproject") @@ -796,7 +916,7 @@ only useful if you expect that some *hookimpls* may be registered **after** the hook is initially invoked. Historic hooks must be :ref:`specially marked ` and called -using the :py:meth:`~pluggy._hooks._HookCaller.call_historic()` method: +using the :py:meth:`~pluggy.HookCaller.call_historic()` method: .. code-block:: python @@ -817,8 +937,8 @@ using the :py:meth:`~pluggy._hooks._HookCaller.call_historic()` method: # historic callback is invoked here pm.register(mylateplugin) -Note that if you :py:meth:`~pluggy._hooks._HookCaller.call_historic()` -the :py:class:`~pluggy._hooks._HookCaller` (and thus your calling code) +Note that if you :py:meth:`~pluggy.HookCaller.call_historic()` +the :py:class:`~pluggy.HookCaller` (and thus your calling code) can not receive results back from the underlying *hookimpl* functions. Instead you can provide a *callback* for processing results (like the ``callback`` function above) which will be called as each new plugin @@ -829,23 +949,28 @@ is registered. hooks since only the first registered plugin's hook(s) would ever be called. +.. _call_extra: + Calling with extras ------------------- You can call a hook with temporarily participating *implementation* functions (that aren't in the registry) using the -:py:meth:`pluggy._hooks._HookCaller.call_extra()` method. +:py:meth:`pluggy.HookCaller.call_extra()` method. Calling with a subset of registered plugins ------------------------------------------- You can make a call using a subset of plugins by asking the :py:class:`~pluggy.PluginManager` first for a -:py:class:`~pluggy._hooks._HookCaller` with those plugins removed +:py:class:`~pluggy.HookCaller` with those plugins removed using the :py:meth:`pluggy.PluginManager.subset_hook_caller()` method. -You then can use that :py:class:`_HookCaller ` -to make normal, :py:meth:`~pluggy._hooks._HookCaller.call_historic`, or -:py:meth:`~pluggy._hooks._HookCaller.call_extra` calls as necessary. +You then can use that :py:class:`~pluggy.HookCaller` +to make normal, :py:meth:`~pluggy.HookCaller.call_historic`, or +:py:meth:`~pluggy.HookCaller.call_extra` calls as necessary. + + +.. _tracing: Built-in tracing **************** @@ -877,11 +1002,11 @@ The expected signature and default implementations for these functions is: .. code-block:: python - def before(hook_name, methods, kwargs): + def before(hook_name, hook_impls, kwargs): pass - def after(outcome, hook_name, methods, kwargs): + def after(outcome, hook_name, hook_impls, kwargs): pass Public API @@ -939,13 +1064,13 @@ Table of contents .. _callbacks: https://en.wikipedia.org/wiki/Callback_(computer_programming) .. _tox test suite: - https://github.com/pytest-dev/pluggy/blob/master/tox.ini + https://github.com/pytest-dev/pluggy/blob/main/tox.ini .. _Semantic Versioning: https://semver.org/ .. _Python interpreters: - https://github.com/pytest-dev/pluggy/blob/master/tox.ini#L2 -.. _500+ plugins: - http://plugincompat.herokuapp.com/ + https://github.com/pytest-dev/pluggy/blob/main/tox.ini#L2 +.. _1400+ plugins: + https://docs.pytest.org/en/latest/reference/plugin_list.html .. _pre-commit: https://pre-commit.com/ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/requirements.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/requirements.txt new file mode 100644 index 0000000000000..7d0b87a35ece5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/docs/requirements.txt @@ -0,0 +1,4 @@ +# Higher bound for safety, can bump it if builds fine with new major versions. +sphinx>=6,<8 +pygments +towncrier diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/README.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/README.md new file mode 100644 index 0000000000000..ff420e7d9d941 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/README.md @@ -0,0 +1,2 @@ +This directory contains scripts for testing some downstream projects +against your current pluggy worktree. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/conda.sh b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/conda.sh new file mode 100644 index 0000000000000..685d08d41b541 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/conda.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -eux -o pipefail +if [[ ! -d conda ]]; then + git clone https://github.com/conda/conda +fi +pushd conda && trap popd EXIT +git pull +set +eu +source dev/start +set -eu +pip install -e ../../ +pytest -m "not integration and not installed" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/datasette.sh b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/datasette.sh new file mode 100644 index 0000000000000..7d3c5586b44c0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/datasette.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -eux -o pipefail +if [[ ! -d datasette ]]; then + git clone https://github.com/simonw/datasette +fi +pushd datasette && trap popd EXIT +git pull +python -m venv venv +venv/bin/pip install -e .[test] -e ../.. +venv/bin/pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/devpi.sh b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/devpi.sh new file mode 100644 index 0000000000000..7ef09c8da07e0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/devpi.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -eux -o pipefail +if [[ ! -d devpi ]]; then + git clone https://github.com/devpi/devpi +fi +pushd devpi && trap popd EXIT +git pull +python -m venv venv +venv/bin/pip install -r dev-requirements.txt -e ../.. +venv/bin/pytest common +venv/bin/pytest server +venv/bin/pytest client +venv/bin/pytest web diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/hatch.sh b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/hatch.sh new file mode 100644 index 0000000000000..933e0f637b7c9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/hatch.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -eux -o pipefail +if [[ ! -d hatch ]]; then + git clone https://github.com/pypa/hatch +fi +pushd hatch && trap popd EXIT +git pull +python -m venv venv +venv/bin/pip install -e . -e ./backend -e ../.. +venv/bin/hatch run dev diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/pytest.sh b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/pytest.sh new file mode 100644 index 0000000000000..5afc5612f0a4a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/pytest.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -eux -o pipefail +if [[ ! -d pytest ]]; then + git clone https://github.com/pytest-dev/pytest +fi +pushd pytest && trap popd EXIT +git pull +python -m venv venv +venv/bin/pip install -e .[testing] -e ../.. +venv/bin/pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/tox.sh b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/tox.sh new file mode 100644 index 0000000000000..79e12dfa2b132 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/downstream/tox.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -eux -o pipefail +if [[ ! -d tox ]]; then + git clone https://github.com/tox-dev/tox +fi +pushd tox && trap popd EXIT +python -m venv venv +venv/bin/pip install -e .[testing] -e ../.. +venv/bin/pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/pyproject.toml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/pyproject.toml index 15eba2689830e..e286825c4b463 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/pyproject.toml +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/pyproject.toml @@ -1,9 +1,24 @@ [build-system] requires = [ - "setuptools", - "setuptools-scm", - "wheel", + # sync with setup.py until we discard non-pep-517/518 + "setuptools>=45.0", + "setuptools-scm[toml]>=6.2.3", ] +build-backend = "setuptools.build_meta" + + +[tool.ruff.lint] +select = [ + "I", # isort +] + +[tool.ruff.lint.isort] +force-single-line = true +combine-as-imports = true +force-sort-within-sections = true +order-by-type = false +known-local-folder = ["pluggy"] +lines-after-imports = 2 [tool.setuptools_scm] write_to = "src/pluggy/_version.py" @@ -45,3 +60,25 @@ template = "changelog/_template.rst" directory = "trivial" name = "Trivial/Internal Changes" showcontent = true + +[tool.mypy] +mypy_path = "src" +check_untyped_defs = true +# Hopefully we can set this someday! +# disallow_any_expr = true +disallow_any_generics = true +disallow_any_unimported = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +ignore_missing_imports = true +implicit_reexport = false +no_implicit_optional = true +show_error_codes = true +strict_equality = true +strict_optional = true +warn_redundant_casts = true +warn_return_any = true +warn_unreachable = true +warn_unused_configs = true +warn_unused_ignores = true diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/scripts/release.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/scripts/release.py index e09b8c77b1163..879d35dfd4b70 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/scripts/release.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/scripts/release.py @@ -1,12 +1,15 @@ """ Release script. """ + import argparse -import sys from subprocess import check_call +import sys -from colorama import init, Fore -from git import Repo, Remote +from colorama import Fore +from colorama import init +from git import Remote +from git import Repo def create_branch(version): @@ -50,7 +53,7 @@ def changelog(version, write_out=False): else: addopts = ["--draft"] print(f"{Fore.CYAN}Generating CHANGELOG") - check_call(["towncrier", "--yes", "--version", version] + addopts) + check_call(["towncrier", "build", "--yes", "--version", version] + addopts) def main(): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/scripts/towncrier-draft-to-file.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/scripts/towncrier-draft-to-file.py new file mode 100644 index 0000000000000..a47caa8fa8db6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/scripts/towncrier-draft-to-file.py @@ -0,0 +1,16 @@ +from subprocess import call +import sys + + +def main(): + """ + Platform agnostic wrapper script for towncrier. + Fixes the issue (pytest#7251) where windows users are unable to natively + run tox -e docs to build pytest docs. + """ + with open("docs/_changelog_towncrier_draft.rst", "w") as draft_file: + return call(("towncrier", "--draft"), stdout=draft_file) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/setup.cfg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/setup.cfg index 7040bcb83bec5..1e34d36120366 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/setup.cfg +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/setup.cfg @@ -1,6 +1,3 @@ -[bdist_wheel] -universal=1 - [metadata] name = pluggy description = plugin and hook calling mechanisms for python @@ -25,17 +22,15 @@ classifiers = Programming Language :: Python :: Implementation :: PyPy Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 [options] packages = pluggy -install_requires = - importlib-metadata>=0.12;python_version<"3.8" -python_requires = >=3.6 +python_requires = >=3.8 package_dir = =src setup_requires = @@ -47,6 +42,8 @@ dev = testing = pytest pytest-benchmark +[options.package_data] +pluggy = py.typed [devpi:upload] formats=sdist.tgz,bdist_wheel diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/__init__.py index 979028f759f2f..36ce16806219d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/__init__.py @@ -6,13 +6,32 @@ __version__ = "unknown" __all__ = [ + "__version__", "PluginManager", "PluginValidationError", + "HookCaller", "HookCallError", + "HookspecOpts", + "HookimplOpts", + "HookImpl", + "HookRelay", "HookspecMarker", "HookimplMarker", + "Result", + "PluggyWarning", + "PluggyTeardownRaisedWarning", ] -from ._manager import PluginManager, PluginValidationError -from ._callers import HookCallError -from ._hooks import HookspecMarker, HookimplMarker +from ._hooks import HookCaller +from ._hooks import HookImpl +from ._hooks import HookimplMarker +from ._hooks import HookimplOpts +from ._hooks import HookRelay +from ._hooks import HookspecMarker +from ._hooks import HookspecOpts +from ._manager import PluginManager +from ._manager import PluginValidationError +from ._result import HookCallError +from ._result import Result +from ._warnings import PluggyTeardownRaisedWarning +from ._warnings import PluggyWarning diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_callers.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_callers.py index 7a16f3bdd40f4..d01f925cca20f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_callers.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_callers.py @@ -1,22 +1,72 @@ """ Call loop machinery """ -import sys -from ._result import HookCallError, _Result, _raise_wrapfail +from __future__ import annotations +from typing import cast +from typing import Generator +from typing import Mapping +from typing import NoReturn +from typing import Sequence +from typing import Tuple +from typing import Union +import warnings -def _multicall(hook_name, hook_impls, caller_kwargs, firstresult): +from ._hooks import HookImpl +from ._result import HookCallError +from ._result import Result +from ._warnings import PluggyTeardownRaisedWarning + + +# Need to distinguish between old- and new-style hook wrappers. +# Wrapping with a tuple is the fastest type-safe way I found to do it. +Teardown = Union[ + Tuple[Generator[None, Result[object], None], HookImpl], + Generator[None, object, object], +] + + +def _raise_wrapfail( + wrap_controller: ( + Generator[None, Result[object], None] | Generator[None, object, object] + ), + msg: str, +) -> NoReturn: + co = wrap_controller.gi_code + raise RuntimeError( + "wrap_controller at %r %s:%d %s" + % (co.co_name, co.co_filename, co.co_firstlineno, msg) + ) + + +def _warn_teardown_exception( + hook_name: str, hook_impl: HookImpl, e: BaseException +) -> None: + msg = "A plugin raised an exception during an old-style hookwrapper teardown.\n" + msg += f"Plugin: {hook_impl.plugin_name}, Hook: {hook_name}\n" + msg += f"{type(e).__name__}: {e}\n" + msg += "For more information see https://pluggy.readthedocs.io/en/stable/api_reference.html#pluggy.PluggyTeardownRaisedWarning" # noqa: E501 + warnings.warn(PluggyTeardownRaisedWarning(msg), stacklevel=5) + + +def _multicall( + hook_name: str, + hook_impls: Sequence[HookImpl], + caller_kwargs: Mapping[str, object], + firstresult: bool, +) -> object | list[object]: """Execute a call into multiple python functions/methods and return the result(s). - ``caller_kwargs`` comes from _HookCaller.__call__(). + ``caller_kwargs`` comes from HookCaller.__call__(). """ __tracebackhide__ = True - results = [] - excinfo = None + results: list[object] = [] + exception = None + only_new_style_wrappers = True try: # run impl and wrapper setup functions in a loop - teardowns = [] + teardowns: list[Teardown] = [] try: for hook_impl in reversed(hook_impls): try: @@ -29,32 +79,104 @@ def _multicall(hook_name, hook_impls, caller_kwargs, firstresult): ) if hook_impl.hookwrapper: + only_new_style_wrappers = False + try: + # If this cast is not valid, a type error is raised below, + # which is the desired response. + res = hook_impl.function(*args) + wrapper_gen = cast(Generator[None, Result[object], None], res) + next(wrapper_gen) # first yield + teardowns.append((wrapper_gen, hook_impl)) + except StopIteration: + _raise_wrapfail(wrapper_gen, "did not yield") + elif hook_impl.wrapper: try: - gen = hook_impl.function(*args) - next(gen) # first yield - teardowns.append(gen) + # If this cast is not valid, a type error is raised below, + # which is the desired response. + res = hook_impl.function(*args) + function_gen = cast(Generator[None, object, object], res) + next(function_gen) # first yield + teardowns.append(function_gen) except StopIteration: - _raise_wrapfail(gen, "did not yield") + _raise_wrapfail(function_gen, "did not yield") else: res = hook_impl.function(*args) if res is not None: results.append(res) if firstresult: # halt further impl calls break - except BaseException: - excinfo = sys.exc_info() + except BaseException as exc: + exception = exc finally: - if firstresult: # first result hooks return a single value - outcome = _Result(results[0] if results else None, excinfo) + # Fast path - only new-style wrappers, no Result. + if only_new_style_wrappers: + if firstresult: # first result hooks return a single value + result = results[0] if results else None + else: + result = results + + # run all wrapper post-yield blocks + for teardown in reversed(teardowns): + try: + if exception is not None: + teardown.throw(exception) # type: ignore[union-attr] + else: + teardown.send(result) # type: ignore[union-attr] + # Following is unreachable for a well behaved hook wrapper. + # Try to force finalizers otherwise postponed till GC action. + # Note: close() may raise if generator handles GeneratorExit. + teardown.close() # type: ignore[union-attr] + except StopIteration as si: + result = si.value + exception = None + continue + except BaseException as e: + exception = e + continue + _raise_wrapfail(teardown, "has second yield") # type: ignore[arg-type] + + if exception is not None: + raise exception.with_traceback(exception.__traceback__) + else: + return result + + # Slow path - need to support old-style wrappers. else: - outcome = _Result(results, excinfo) + if firstresult: # first result hooks return a single value + outcome: Result[object | list[object]] = Result( + results[0] if results else None, exception + ) + else: + outcome = Result(results, exception) - # run all wrapper post-yield blocks - for gen in reversed(teardowns): - try: - gen.send(outcome) - _raise_wrapfail(gen, "has second yield") - except StopIteration: - pass + # run all wrapper post-yield blocks + for teardown in reversed(teardowns): + if isinstance(teardown, tuple): + try: + teardown[0].send(outcome) + except StopIteration: + pass + except BaseException as e: + _warn_teardown_exception(hook_name, teardown[1], e) + raise + else: + _raise_wrapfail(teardown[0], "has second yield") + else: + try: + if outcome._exception is not None: + teardown.throw(outcome._exception) + else: + teardown.send(outcome._result) + # Following is unreachable for a well behaved hook wrapper. + # Try to force finalizers otherwise postponed till GC action. + # Note: close() may raise if generator handles GeneratorExit. + teardown.close() + except StopIteration as si: + outcome.force_result(si.value) + continue + except BaseException as e: + outcome.force_exception(e) + continue + _raise_wrapfail(teardown, "has second yield") - return outcome.get_result() + return outcome.get_result() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_hooks.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_hooks.py index 1e5fbb7595810..362d791823ee6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_hooks.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_hooks.py @@ -1,51 +1,160 @@ """ Internal hook annotation, representation and calling machinery. """ + +from __future__ import annotations + import inspect import sys +from types import ModuleType +from typing import AbstractSet +from typing import Any +from typing import Callable +from typing import Final +from typing import final +from typing import Generator +from typing import List +from typing import Mapping +from typing import Optional +from typing import overload +from typing import Sequence +from typing import Tuple +from typing import TYPE_CHECKING +from typing import TypedDict +from typing import TypeVar +from typing import Union import warnings - +from ._result import Result + + +_T = TypeVar("_T") +_F = TypeVar("_F", bound=Callable[..., object]) +_Namespace = Union[ModuleType, type] +_Plugin = object +_HookExec = Callable[ + [str, Sequence["HookImpl"], Mapping[str, object], bool], + Union[object, List[object]], +] +_HookImplFunction = Callable[..., Union[_T, Generator[None, Result[_T], None]]] + + +class HookspecOpts(TypedDict): + """Options for a hook specification.""" + + #: Whether the hook is :ref:`first result only `. + firstresult: bool + #: Whether the hook is :ref:`historic `. + historic: bool + #: Whether the hook :ref:`warns when implemented `. + warn_on_impl: Warning | None + #: Whether the hook warns when :ref:`certain arguments are requested + #: `. + #: + #: .. versionadded:: 1.5 + warn_on_impl_args: Mapping[str, Warning] | None + + +class HookimplOpts(TypedDict): + """Options for a hook implementation.""" + + #: Whether the hook implementation is a :ref:`wrapper `. + wrapper: bool + #: Whether the hook implementation is an :ref:`old-style wrapper + #: `. + hookwrapper: bool + #: Whether validation against a hook specification is :ref:`optional + #: `. + optionalhook: bool + #: Whether to try to order this hook implementation :ref:`first + #: `. + tryfirst: bool + #: Whether to try to order this hook implementation :ref:`last + #: `. + trylast: bool + #: The name of the hook specification to match, see :ref:`specname`. + specname: str | None + + +@final class HookspecMarker: - """Decorator helper class for marking functions as hook specifications. + """Decorator for marking functions as hook specifications. - You can instantiate it with a project_name to get a decorator. - Calling :py:meth:`.PluginManager.add_hookspecs` later will discover all marked functions - if the :py:class:`.PluginManager` uses the same project_name. + Instantiate it with a project_name to get a decorator. + Calling :meth:`PluginManager.add_hookspecs` later will discover all marked + functions if the :class:`PluginManager` uses the same project name. """ - def __init__(self, project_name): - self.project_name = project_name + __slots__ = ("project_name",) + + def __init__(self, project_name: str) -> None: + self.project_name: Final = project_name + @overload def __call__( - self, function=None, firstresult=False, historic=False, warn_on_impl=None - ): - """if passed a function, directly sets attributes on the function - which will make it discoverable to :py:meth:`.PluginManager.add_hookspecs`. - If passed no function, returns a decorator which can be applied to a function - later using the attributes supplied. + self, + function: _F, + firstresult: bool = False, + historic: bool = False, + warn_on_impl: Warning | None = None, + warn_on_impl_args: Mapping[str, Warning] | None = None, + ) -> _F: ... + + @overload # noqa: F811 + def __call__( # noqa: F811 + self, + function: None = ..., + firstresult: bool = ..., + historic: bool = ..., + warn_on_impl: Warning | None = ..., + warn_on_impl_args: Mapping[str, Warning] | None = ..., + ) -> Callable[[_F], _F]: ... + + def __call__( # noqa: F811 + self, + function: _F | None = None, + firstresult: bool = False, + historic: bool = False, + warn_on_impl: Warning | None = None, + warn_on_impl_args: Mapping[str, Warning] | None = None, + ) -> _F | Callable[[_F], _F]: + """If passed a function, directly sets attributes on the function + which will make it discoverable to :meth:`PluginManager.add_hookspecs`. + + If passed no function, returns a decorator which can be applied to a + function later using the attributes supplied. + + :param firstresult: + If ``True``, the 1:N hook call (N being the number of registered + hook implementation functions) will stop at I<=N when the I'th + function returns a non-``None`` result. See :ref:`firstresult`. + + :param historic: + If ``True``, every call to the hook will be memorized and replayed + on plugins registered after the call was made. See :ref:`historic`. - If ``firstresult`` is ``True`` the 1:N hook call (N being the number of registered - hook implementation functions) will stop at I<=N when the I'th function - returns a non-``None`` result. + :param warn_on_impl: + If given, every implementation of this hook will trigger the given + warning. See :ref:`warn_on_impl`. - If ``historic`` is ``True`` calls to a hook will be memorized and replayed - on later registered plugins. + :param warn_on_impl_args: + If given, every implementation of this hook which requests one of + the arguments in the dict will trigger the corresponding warning. + See :ref:`warn_on_impl`. + .. versionadded:: 1.5 """ - def setattr_hookspec_opts(func): + def setattr_hookspec_opts(func: _F) -> _F: if historic and firstresult: raise ValueError("cannot have a historic firstresult hook") - setattr( - func, - self.project_name + "_spec", - dict( - firstresult=firstresult, - historic=historic, - warn_on_impl=warn_on_impl, - ), - ) + opts: HookspecOpts = { + "firstresult": firstresult, + "historic": historic, + "warn_on_impl": warn_on_impl, + "warn_on_impl_args": warn_on_impl_args, + } + setattr(func, self.project_name + "_spec", opts) return func if function is not None: @@ -54,65 +163,115 @@ def setattr_hookspec_opts(func): return setattr_hookspec_opts +@final class HookimplMarker: - """Decorator helper class for marking functions as hook implementations. + """Decorator for marking functions as hook implementations. - You can instantiate with a ``project_name`` to get a decorator. - Calling :py:meth:`.PluginManager.register` later will discover all marked functions - if the :py:class:`.PluginManager` uses the same project_name. + Instantiate it with a ``project_name`` to get a decorator. + Calling :meth:`PluginManager.register` later will discover all marked + functions if the :class:`PluginManager` uses the same project name. """ - def __init__(self, project_name): - self.project_name = project_name + __slots__ = ("project_name",) + + def __init__(self, project_name: str) -> None: + self.project_name: Final = project_name + @overload def __call__( self, - function=None, - hookwrapper=False, - optionalhook=False, - tryfirst=False, - trylast=False, - specname=None, - ): - - """if passed a function, directly sets attributes on the function - which will make it discoverable to :py:meth:`.PluginManager.register`. + function: _F, + hookwrapper: bool = ..., + optionalhook: bool = ..., + tryfirst: bool = ..., + trylast: bool = ..., + specname: str | None = ..., + wrapper: bool = ..., + ) -> _F: ... + + @overload # noqa: F811 + def __call__( # noqa: F811 + self, + function: None = ..., + hookwrapper: bool = ..., + optionalhook: bool = ..., + tryfirst: bool = ..., + trylast: bool = ..., + specname: str | None = ..., + wrapper: bool = ..., + ) -> Callable[[_F], _F]: ... + + def __call__( # noqa: F811 + self, + function: _F | None = None, + hookwrapper: bool = False, + optionalhook: bool = False, + tryfirst: bool = False, + trylast: bool = False, + specname: str | None = None, + wrapper: bool = False, + ) -> _F | Callable[[_F], _F]: + """If passed a function, directly sets attributes on the function + which will make it discoverable to :meth:`PluginManager.register`. + If passed no function, returns a decorator which can be applied to a function later using the attributes supplied. - If ``optionalhook`` is ``True`` a missing matching hook specification will not result - in an error (by default it is an error if no matching spec is found). - - If ``tryfirst`` is ``True`` this hook implementation will run as early as possible - in the chain of N hook implementations for a specification. - - If ``trylast`` is ``True`` this hook implementation will run as late as possible - in the chain of N hook implementations. - - If ``hookwrapper`` is ``True`` the hook implementations needs to execute exactly - one ``yield``. The code before the ``yield`` is run early before any non-hookwrapper - function is run. The code after the ``yield`` is run after all non-hookwrapper - function have run. The ``yield`` receives a :py:class:`.callers._Result` object - representing the exception or result outcome of the inner calls (including other - hookwrapper calls). - - If ``specname`` is provided, it will be used instead of the function name when - matching this hook implementation to a hook specification during registration. - + :param optionalhook: + If ``True``, a missing matching hook specification will not result + in an error (by default it is an error if no matching spec is + found). See :ref:`optionalhook`. + + :param tryfirst: + If ``True``, this hook implementation will run as early as possible + in the chain of N hook implementations for a specification. See + :ref:`callorder`. + + :param trylast: + If ``True``, this hook implementation will run as late as possible + in the chain of N hook implementations for a specification. See + :ref:`callorder`. + + :param wrapper: + If ``True`` ("new-style hook wrapper"), the hook implementation + needs to execute exactly one ``yield``. The code before the + ``yield`` is run early before any non-hook-wrapper function is run. + The code after the ``yield`` is run after all non-hook-wrapper + functions have run. The ``yield`` receives the result value of the + inner calls, or raises the exception of inner calls (including + earlier hook wrapper calls). The return value of the function + becomes the return value of the hook, and a raised exception becomes + the exception of the hook. See :ref:`hookwrapper`. + + :param hookwrapper: + If ``True`` ("old-style hook wrapper"), the hook implementation + needs to execute exactly one ``yield``. The code before the + ``yield`` is run early before any non-hook-wrapper function is run. + The code after the ``yield`` is run after all non-hook-wrapper + function have run The ``yield`` receives a :class:`Result` object + representing the exception or result outcome of the inner calls + (including earlier hook wrapper calls). This option is mutually + exclusive with ``wrapper``. See :ref:`old_style_hookwrapper`. + + :param specname: + If provided, the given name will be used instead of the function + name when matching this hook implementation to a hook specification + during registration. See :ref:`specname`. + + .. versionadded:: 1.2.0 + The ``wrapper`` parameter. """ - def setattr_hookimpl_opts(func): - setattr( - func, - self.project_name + "_impl", - dict( - hookwrapper=hookwrapper, - optionalhook=optionalhook, - tryfirst=tryfirst, - trylast=trylast, - specname=specname, - ), - ) + def setattr_hookimpl_opts(func: _F) -> _F: + opts: HookimplOpts = { + "wrapper": wrapper, + "hookwrapper": hookwrapper, + "optionalhook": optionalhook, + "tryfirst": tryfirst, + "trylast": trylast, + "specname": specname, + } + setattr(func, self.project_name + "_impl", opts) return func if function is None: @@ -121,9 +280,10 @@ def setattr_hookimpl_opts(func): return setattr_hookimpl_opts(function) -def normalize_hookimpl_opts(opts): +def normalize_hookimpl_opts(opts: HookimplOpts) -> None: opts.setdefault("tryfirst", False) opts.setdefault("trylast", False) + opts.setdefault("wrapper", False) opts.setdefault("hookwrapper", False) opts.setdefault("optionalhook", False) opts.setdefault("specname", None) @@ -132,7 +292,7 @@ def normalize_hookimpl_opts(opts): _PYPY = hasattr(sys, "pypy_version_info") -def varnames(func): +def varnames(func: object) -> tuple[tuple[str, ...], tuple[str, ...]]: """Return tuple of positional and keywrord argument names for a function, method, class or callable. @@ -150,12 +310,33 @@ def varnames(func): except Exception: return (), () - try: # func MUST be a function or method here or we won't parse any args - spec = inspect.getfullargspec(func) + try: + # func MUST be a function or method here or we won't parse any args. + sig = inspect.signature( + func.__func__ if inspect.ismethod(func) else func # type:ignore[arg-type] + ) except TypeError: return (), () - args, defaults = tuple(spec.args), spec.defaults + _valid_param_kinds = ( + inspect.Parameter.POSITIONAL_ONLY, + inspect.Parameter.POSITIONAL_OR_KEYWORD, + ) + _valid_params = { + name: param + for name, param in sig.parameters.items() + if param.kind in _valid_param_kinds + } + args = tuple(_valid_params) + defaults = ( + tuple( + param.default + for param in _valid_params.values() + if param.default is not param.empty + ) + or None + ) + if defaults: index = -len(defaults) args, kwargs = args[:index], tuple(args[index:]) @@ -164,162 +345,371 @@ def varnames(func): # strip any implicit instance arg # pypy3 uses "obj" instead of "self" for default dunder methods - implicit_names = ("self",) if not _PYPY else ("self", "obj") + if not _PYPY: + implicit_names: tuple[str, ...] = ("self",) + else: + implicit_names = ("self", "obj") if args: - if inspect.ismethod(func) or ( - "." in getattr(func, "__qualname__", ()) and args[0] in implicit_names - ): + qualname: str = getattr(func, "__qualname__", "") + if inspect.ismethod(func) or ("." in qualname and args[0] in implicit_names): args = args[1:] return args, kwargs -class _HookRelay: - """hook holder object for performing 1:N hook calls where N is the number - of registered plugins. +@final +class HookRelay: + """Hook holder object for performing 1:N hook calls where N is the number + of registered plugins.""" - """ + __slots__ = ("__dict__",) + def __init__(self) -> None: + """:meta private:""" -class _HookCaller: - def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None): - self.name = name - self._wrappers = [] - self._nonwrappers = [] - self._hookexec = hook_execute - self._call_history = None - self.spec = None + if TYPE_CHECKING: + + def __getattr__(self, name: str) -> HookCaller: ... + + +# Historical name (pluggy<=1.2), kept for backward compatibility. +_HookRelay = HookRelay + + +_CallHistory = List[Tuple[Mapping[str, object], Optional[Callable[[Any], None]]]] + + +class HookCaller: + """A caller of all registered implementations of a hook specification.""" + + __slots__ = ( + "name", + "spec", + "_hookexec", + "_hookimpls", + "_call_history", + ) + + def __init__( + self, + name: str, + hook_execute: _HookExec, + specmodule_or_class: _Namespace | None = None, + spec_opts: HookspecOpts | None = None, + ) -> None: + """:meta private:""" + #: Name of the hook getting called. + self.name: Final = name + self._hookexec: Final = hook_execute + # The hookimpls list. The caller iterates it *in reverse*. Format: + # 1. trylast nonwrappers + # 2. nonwrappers + # 3. tryfirst nonwrappers + # 4. trylast wrappers + # 5. wrappers + # 6. tryfirst wrappers + self._hookimpls: Final[list[HookImpl]] = [] + self._call_history: _CallHistory | None = None + # TODO: Document, or make private. + self.spec: HookSpec | None = None if specmodule_or_class is not None: assert spec_opts is not None self.set_specification(specmodule_or_class, spec_opts) - def has_spec(self): + # TODO: Document, or make private. + def has_spec(self) -> bool: return self.spec is not None - def set_specification(self, specmodule_or_class, spec_opts): - assert not self.has_spec() + # TODO: Document, or make private. + def set_specification( + self, + specmodule_or_class: _Namespace, + spec_opts: HookspecOpts, + ) -> None: + if self.spec is not None: + raise ValueError( + f"Hook {self.spec.name!r} is already registered " + f"within namespace {self.spec.namespace}" + ) self.spec = HookSpec(specmodule_or_class, self.name, spec_opts) if spec_opts.get("historic"): self._call_history = [] - def is_historic(self): + def is_historic(self) -> bool: + """Whether this caller is :ref:`historic `.""" return self._call_history is not None - def _remove_plugin(self, plugin): - def remove(wrappers): - for i, method in enumerate(wrappers): - if method.plugin == plugin: - del wrappers[i] - return True - - if remove(self._wrappers) is None: - if remove(self._nonwrappers) is None: - raise ValueError(f"plugin {plugin!r} not found") + def _remove_plugin(self, plugin: _Plugin) -> None: + for i, method in enumerate(self._hookimpls): + if method.plugin == plugin: + del self._hookimpls[i] + return + raise ValueError(f"plugin {plugin!r} not found") - def get_hookimpls(self): - # Order is important for _hookexec - return self._nonwrappers + self._wrappers + def get_hookimpls(self) -> list[HookImpl]: + """Get all registered hook implementations for this hook.""" + return self._hookimpls.copy() - def _add_hookimpl(self, hookimpl): + def _add_hookimpl(self, hookimpl: HookImpl) -> None: """Add an implementation to the callback chain.""" - if hookimpl.hookwrapper: - methods = self._wrappers + for i, method in enumerate(self._hookimpls): + if method.hookwrapper or method.wrapper: + splitpoint = i + break else: - methods = self._nonwrappers + splitpoint = len(self._hookimpls) + if hookimpl.hookwrapper or hookimpl.wrapper: + start, end = splitpoint, len(self._hookimpls) + else: + start, end = 0, splitpoint if hookimpl.trylast: - methods.insert(0, hookimpl) + self._hookimpls.insert(start, hookimpl) elif hookimpl.tryfirst: - methods.append(hookimpl) + self._hookimpls.insert(end, hookimpl) else: # find last non-tryfirst method - i = len(methods) - 1 - while i >= 0 and methods[i].tryfirst: + i = end - 1 + while i >= start and self._hookimpls[i].tryfirst: i -= 1 - methods.insert(i + 1, hookimpl) - - def __repr__(self): - return f"<_HookCaller {self.name!r}>" + self._hookimpls.insert(i + 1, hookimpl) - def __call__(self, *args, **kwargs): - if args: - raise TypeError("hook calling supports only keyword arguments") - assert not self.is_historic() + def __repr__(self) -> str: + return f"" + def _verify_all_args_are_provided(self, kwargs: Mapping[str, object]) -> None: # This is written to avoid expensive operations when not needed. if self.spec: for argname in self.spec.argnames: if argname not in kwargs: - notincall = tuple(set(self.spec.argnames) - kwargs.keys()) + notincall = ", ".join( + repr(argname) + for argname in self.spec.argnames + # Avoid self.spec.argnames - kwargs.keys() - doesn't preserve order. + if argname not in kwargs.keys() + ) warnings.warn( "Argument(s) {} which are declared in the hookspec " - "can not be found in this hook call".format(notincall), + "cannot be found in this hook call".format(notincall), stacklevel=2, ) break - firstresult = self.spec.opts.get("firstresult") - else: - firstresult = False + def __call__(self, **kwargs: object) -> Any: + """Call the hook. - return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult) + Only accepts keyword arguments, which should match the hook + specification. - def call_historic(self, result_callback=None, kwargs=None): + Returns the result(s) of calling all registered plugins, see + :ref:`calling`. + """ + assert ( + not self.is_historic() + ), "Cannot directly call a historic hook - use call_historic instead." + self._verify_all_args_are_provided(kwargs) + firstresult = self.spec.opts.get("firstresult", False) if self.spec else False + # Copy because plugins may register other plugins during iteration (#438). + return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult) + + def call_historic( + self, + result_callback: Callable[[Any], None] | None = None, + kwargs: Mapping[str, object] | None = None, + ) -> None: """Call the hook with given ``kwargs`` for all registered plugins and - for all plugins which will be registered afterwards. + for all plugins which will be registered afterwards, see + :ref:`historic`. - If ``result_callback`` is not ``None`` it will be called for for each - non-``None`` result obtained from a hook implementation. + :param result_callback: + If provided, will be called for each non-``None`` result obtained + from a hook implementation. """ - self._call_history.append((kwargs or {}, result_callback)) + assert self._call_history is not None + kwargs = kwargs or {} + self._verify_all_args_are_provided(kwargs) + self._call_history.append((kwargs, result_callback)) # Historizing hooks don't return results. # Remember firstresult isn't compatible with historic. - res = self._hookexec(self.name, self.get_hookimpls(), kwargs, False) + # Copy because plugins may register other plugins during iteration (#438). + res = self._hookexec(self.name, self._hookimpls.copy(), kwargs, False) if result_callback is None: return - for x in res or []: - result_callback(x) + if isinstance(res, list): + for x in res: + result_callback(x) - def call_extra(self, methods, kwargs): + def call_extra( + self, methods: Sequence[Callable[..., object]], kwargs: Mapping[str, object] + ) -> Any: """Call the hook with some additional temporarily participating - methods using the specified ``kwargs`` as call parameters.""" - old = list(self._nonwrappers), list(self._wrappers) + methods using the specified ``kwargs`` as call parameters, see + :ref:`call_extra`.""" + assert ( + not self.is_historic() + ), "Cannot directly call a historic hook - use call_historic instead." + self._verify_all_args_are_provided(kwargs) + opts: HookimplOpts = { + "wrapper": False, + "hookwrapper": False, + "optionalhook": False, + "trylast": False, + "tryfirst": False, + "specname": None, + } + hookimpls = self._hookimpls.copy() for method in methods: - opts = dict(hookwrapper=False, trylast=False, tryfirst=False) hookimpl = HookImpl(None, "", method, opts) - self._add_hookimpl(hookimpl) - try: - return self(**kwargs) - finally: - self._nonwrappers, self._wrappers = old + # Find last non-tryfirst nonwrapper method. + i = len(hookimpls) - 1 + while i >= 0 and ( + # Skip wrappers. + (hookimpls[i].hookwrapper or hookimpls[i].wrapper) + # Skip tryfirst nonwrappers. + or hookimpls[i].tryfirst + ): + i -= 1 + hookimpls.insert(i + 1, hookimpl) + firstresult = self.spec.opts.get("firstresult", False) if self.spec else False + return self._hookexec(self.name, hookimpls, kwargs, firstresult) - def _maybe_apply_history(self, method): + def _maybe_apply_history(self, method: HookImpl) -> None: """Apply call history to a new hookimpl if it is marked as historic.""" if self.is_historic(): + assert self._call_history is not None for kwargs, result_callback in self._call_history: res = self._hookexec(self.name, [method], kwargs, False) if res and result_callback is not None: + # XXX: remember firstresult isn't compat with historic + assert isinstance(res, list) result_callback(res[0]) -class HookImpl: - def __init__(self, plugin, plugin_name, function, hook_impl_opts): - self.function = function - self.argnames, self.kwargnames = varnames(self.function) - self.plugin = plugin - self.opts = hook_impl_opts - self.plugin_name = plugin_name - self.__dict__.update(hook_impl_opts) +# Historical name (pluggy<=1.2), kept for backward compatibility. +_HookCaller = HookCaller + + +class _SubsetHookCaller(HookCaller): + """A proxy to another HookCaller which manages calls to all registered + plugins except the ones from remove_plugins.""" + + # This class is unusual: in inhertits from `HookCaller` so all of + # the *code* runs in the class, but it delegates all underlying *data* + # to the original HookCaller. + # `subset_hook_caller` used to be implemented by creating a full-fledged + # HookCaller, copying all hookimpls from the original. This had problems + # with memory leaks (#346) and historic calls (#347), which make a proxy + # approach better. + # An alternative implementation is to use a `_getattr__`/`__getattribute__` + # proxy, however that adds more overhead and is more tricky to implement. + + __slots__ = ( + "_orig", + "_remove_plugins", + ) + + def __init__(self, orig: HookCaller, remove_plugins: AbstractSet[_Plugin]) -> None: + self._orig = orig + self._remove_plugins = remove_plugins + self.name = orig.name # type: ignore[misc] + self._hookexec = orig._hookexec # type: ignore[misc] + + @property # type: ignore[misc] + def _hookimpls(self) -> list[HookImpl]: + return [ + impl + for impl in self._orig._hookimpls + if impl.plugin not in self._remove_plugins + ] - def __repr__(self): + @property + def spec(self) -> HookSpec | None: # type: ignore[override] + return self._orig.spec + + @property + def _call_history(self) -> _CallHistory | None: # type: ignore[override] + return self._orig._call_history + + def __repr__(self) -> str: + return f"<_SubsetHookCaller {self.name!r}>" + + +@final +class HookImpl: + """A hook implementation in a :class:`HookCaller`.""" + + __slots__ = ( + "function", + "argnames", + "kwargnames", + "plugin", + "opts", + "plugin_name", + "wrapper", + "hookwrapper", + "optionalhook", + "tryfirst", + "trylast", + ) + + def __init__( + self, + plugin: _Plugin, + plugin_name: str, + function: _HookImplFunction[object], + hook_impl_opts: HookimplOpts, + ) -> None: + """:meta private:""" + #: The hook implementation function. + self.function: Final = function + argnames, kwargnames = varnames(self.function) + #: The positional parameter names of ``function```. + self.argnames: Final = argnames + #: The keyword parameter names of ``function```. + self.kwargnames: Final = kwargnames + #: The plugin which defined this hook implementation. + self.plugin: Final = plugin + #: The :class:`HookimplOpts` used to configure this hook implementation. + self.opts: Final = hook_impl_opts + #: The name of the plugin which defined this hook implementation. + self.plugin_name: Final = plugin_name + #: Whether the hook implementation is a :ref:`wrapper `. + self.wrapper: Final = hook_impl_opts["wrapper"] + #: Whether the hook implementation is an :ref:`old-style wrapper + #: `. + self.hookwrapper: Final = hook_impl_opts["hookwrapper"] + #: Whether validation against a hook specification is :ref:`optional + #: `. + self.optionalhook: Final = hook_impl_opts["optionalhook"] + #: Whether to try to order this hook implementation :ref:`first + #: `. + self.tryfirst: Final = hook_impl_opts["tryfirst"] + #: Whether to try to order this hook implementation :ref:`last + #: `. + self.trylast: Final = hook_impl_opts["trylast"] + + def __repr__(self) -> str: return f"" +@final class HookSpec: - def __init__(self, namespace, name, opts): + __slots__ = ( + "namespace", + "function", + "name", + "argnames", + "kwargnames", + "opts", + "warn_on_impl", + "warn_on_impl_args", + ) + + def __init__(self, namespace: _Namespace, name: str, opts: HookspecOpts) -> None: self.namespace = namespace - self.function = function = getattr(namespace, name) + self.function: Callable[..., object] = getattr(namespace, name) self.name = name - self.argnames, self.kwargnames = varnames(function) + self.argnames, self.kwargnames = varnames(self.function) self.opts = opts self.warn_on_impl = opts.get("warn_on_impl") + self.warn_on_impl_args = opts.get("warn_on_impl_args") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_manager.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_manager.py index 65f4e5084276c..9998dd815b53f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_manager.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_manager.py @@ -1,95 +1,150 @@ +from __future__ import annotations + import inspect -import sys +import types +from typing import Any +from typing import Callable +from typing import cast +from typing import Final +from typing import Iterable +from typing import Mapping +from typing import Sequence +from typing import TYPE_CHECKING import warnings from . import _tracing -from ._callers import _Result, _multicall -from ._hooks import HookImpl, _HookRelay, _HookCaller, normalize_hookimpl_opts +from ._callers import _multicall +from ._hooks import _HookImplFunction +from ._hooks import _Namespace +from ._hooks import _Plugin +from ._hooks import _SubsetHookCaller +from ._hooks import HookCaller +from ._hooks import HookImpl +from ._hooks import HookimplOpts +from ._hooks import HookRelay +from ._hooks import HookspecOpts +from ._hooks import normalize_hookimpl_opts +from ._result import Result + + +if TYPE_CHECKING: + # importtlib.metadata import is slow, defer it. + import importlib.metadata + -if sys.version_info >= (3, 8): - from importlib import metadata as importlib_metadata -else: - import importlib_metadata +_BeforeTrace = Callable[[str, Sequence[HookImpl], Mapping[str, Any]], None] +_AfterTrace = Callable[[Result[Any], str, Sequence[HookImpl], Mapping[str, Any]], None] -def _warn_for_function(warning, function): +def _warn_for_function(warning: Warning, function: Callable[..., object]) -> None: + func = cast(types.FunctionType, function) warnings.warn_explicit( warning, type(warning), - lineno=function.__code__.co_firstlineno, - filename=function.__code__.co_filename, + lineno=func.__code__.co_firstlineno, + filename=func.__code__.co_filename, ) class PluginValidationError(Exception): - """plugin failed validation. + """Plugin failed validation. - :param object plugin: the plugin which failed validation, - may be a module or an arbitrary object. + :param plugin: The plugin which failed validation. + :param message: Error message. """ - def __init__(self, plugin, message): + def __init__(self, plugin: _Plugin, message: str) -> None: + super().__init__(message) + #: The plugin which failed validation. self.plugin = plugin - super(Exception, self).__init__(message) class DistFacade: """Emulate a pkg_resources Distribution""" - def __init__(self, dist): + def __init__(self, dist: importlib.metadata.Distribution) -> None: self._dist = dist @property - def project_name(self): - return self.metadata["name"] + def project_name(self) -> str: + name: str = self.metadata["name"] + return name - def __getattr__(self, attr, default=None): + def __getattr__(self, attr: str, default=None): return getattr(self._dist, attr, default) - def __dir__(self): + def __dir__(self) -> list[str]: return sorted(dir(self._dist) + ["_dist", "project_name"]) class PluginManager: - """Core :py:class:`.PluginManager` class which manages registration - of plugin objects and 1:N hook calling. + """Core class which manages registration of plugin objects and 1:N hook + calling. - You can register new hooks by calling :py:meth:`add_hookspecs(module_or_class) - <.PluginManager.add_hookspecs>`. - You can register plugin objects (which contain hooks) by calling - :py:meth:`register(plugin) <.PluginManager.register>`. The :py:class:`.PluginManager` - is initialized with a prefix that is searched for in the names of the dict - of registered plugin objects. + You can register new hooks by calling :meth:`add_hookspecs(module_or_class) + `. - For debugging purposes you can call :py:meth:`.PluginManager.enable_tracing` + You can register plugin objects (which contain hook implementations) by + calling :meth:`register(plugin) `. + + For debugging purposes you can call :meth:`PluginManager.enable_tracing` which will subsequently send debug information to the trace helper. + + :param project_name: + The short project name. Prefer snake case. Make sure it's unique! """ - def __init__(self, project_name): - self.project_name = project_name - self._name2plugin = {} - self._plugin2hookcallers = {} - self._plugin_distinfo = [] - self.trace = _tracing.TagTracer().get("pluginmanage") - self.hook = _HookRelay() + def __init__(self, project_name: str) -> None: + #: The project name. + self.project_name: Final = project_name + self._name2plugin: Final[dict[str, _Plugin]] = {} + self._plugin_distinfo: Final[list[tuple[_Plugin, DistFacade]]] = [] + #: The "hook relay", used to call a hook on all registered plugins. + #: See :ref:`calling`. + self.hook: Final = HookRelay() + #: The tracing entry point. See :ref:`tracing`. + self.trace: Final[_tracing.TagTracerSub] = _tracing.TagTracer().get( + "pluginmanage" + ) self._inner_hookexec = _multicall - def _hookexec(self, hook_name, methods, kwargs, firstresult): + def _hookexec( + self, + hook_name: str, + methods: Sequence[HookImpl], + kwargs: Mapping[str, object], + firstresult: bool, + ) -> object | list[object]: # called from all hookcaller instances. # enable_tracing will set its own wrapping function at self._inner_hookexec return self._inner_hookexec(hook_name, methods, kwargs, firstresult) - def register(self, plugin, name=None): - """Register a plugin and return its canonical name or ``None`` if the name - is blocked from registering. Raise a :py:class:`ValueError` if the plugin - is already registered.""" + def register(self, plugin: _Plugin, name: str | None = None) -> str | None: + """Register a plugin and return its name. + + :param name: + The name under which to register the plugin. If not specified, a + name is generated using :func:`get_canonical_name`. + + :returns: + The plugin name. If the name is blocked from registering, returns + ``None``. + + If the plugin is already registered, raises a :exc:`ValueError`. + """ plugin_name = name or self.get_canonical_name(plugin) - if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers: + if plugin_name in self._name2plugin: if self._name2plugin.get(plugin_name, -1) is None: - return # blocked plugin, return None to indicate no registration + return None # blocked plugin, return None to indicate no registration + raise ValueError( + "Plugin name already registered: %s=%s\n%s" + % (plugin_name, plugin, self._name2plugin) + ) + + if plugin in self._name2plugin.values(): raise ValueError( - "Plugin already registered: %s=%s\n%s" + "Plugin already registered under a different name: %s=%s\n%s" % (plugin_name, plugin, self._name2plugin) ) @@ -98,79 +153,115 @@ def register(self, plugin, name=None): self._name2plugin[plugin_name] = plugin # register matching hook implementations of the plugin - self._plugin2hookcallers[plugin] = hookcallers = [] for name in dir(plugin): hookimpl_opts = self.parse_hookimpl_opts(plugin, name) if hookimpl_opts is not None: normalize_hookimpl_opts(hookimpl_opts) - method = getattr(plugin, name) + method: _HookImplFunction[object] = getattr(plugin, name) hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts) name = hookimpl_opts.get("specname") or name - hook = getattr(self.hook, name, None) + hook: HookCaller | None = getattr(self.hook, name, None) if hook is None: - hook = _HookCaller(name, self._hookexec) + hook = HookCaller(name, self._hookexec) setattr(self.hook, name, hook) elif hook.has_spec(): self._verify_hook(hook, hookimpl) hook._maybe_apply_history(hookimpl) hook._add_hookimpl(hookimpl) - hookcallers.append(hook) return plugin_name - def parse_hookimpl_opts(self, plugin, name): - method = getattr(plugin, name) + def parse_hookimpl_opts(self, plugin: _Plugin, name: str) -> HookimplOpts | None: + """Try to obtain a hook implementation from an item with the given name + in the given plugin which is being searched for hook impls. + + :returns: + The parsed hookimpl options, or None to skip the given item. + + This method can be overridden by ``PluginManager`` subclasses to + customize how hook implementation are picked up. By default, returns the + options for items decorated with :class:`HookimplMarker`. + """ + method: object = getattr(plugin, name) if not inspect.isroutine(method): - return + return None try: - res = getattr(method, self.project_name + "_impl", None) + res: HookimplOpts | None = getattr( + method, self.project_name + "_impl", None + ) except Exception: - res = {} + res = {} # type: ignore[assignment] if res is not None and not isinstance(res, dict): # false positive - res = None + res = None # type:ignore[unreachable] return res - def unregister(self, plugin=None, name=None): - """unregister a plugin object and all its contained hook implementations - from internal data structures.""" + def unregister( + self, plugin: _Plugin | None = None, name: str | None = None + ) -> Any | None: + """Unregister a plugin and all of its hook implementations. + + The plugin can be specified either by the plugin object or the plugin + name. If both are specified, they must agree. + + Returns the unregistered plugin, or ``None`` if not found. + """ if name is None: assert plugin is not None, "one of name or plugin needs to be specified" name = self.get_name(plugin) + assert name is not None, "plugin is not registered" if plugin is None: plugin = self.get_plugin(name) + if plugin is None: + return None + + hookcallers = self.get_hookcallers(plugin) + if hookcallers: + for hookcaller in hookcallers: + hookcaller._remove_plugin(plugin) # if self._name2plugin[name] == None registration was blocked: ignore if self._name2plugin.get(name): + assert name is not None del self._name2plugin[name] - for hookcaller in self._plugin2hookcallers.pop(plugin, []): - hookcaller._remove_plugin(plugin) - return plugin - def set_blocked(self, name): - """block registrations of the given name, unregister if already registered.""" + def set_blocked(self, name: str) -> None: + """Block registrations of the given name, unregister if already registered.""" self.unregister(name=name) self._name2plugin[name] = None - def is_blocked(self, name): - """return ``True`` if the given plugin name is blocked.""" + def is_blocked(self, name: str) -> bool: + """Return whether the given plugin name is blocked.""" return name in self._name2plugin and self._name2plugin[name] is None - def add_hookspecs(self, module_or_class): - """add new hook specifications defined in the given ``module_or_class``. - Functions are recognized if they have been decorated accordingly.""" + def unblock(self, name: str) -> bool: + """Unblocks a name. + + Returns whether the name was actually blocked. + """ + if self._name2plugin.get(name, -1) is None: + del self._name2plugin[name] + return True + return False + + def add_hookspecs(self, module_or_class: _Namespace) -> None: + """Add new hook specifications defined in the given ``module_or_class``. + + Functions are recognized as hook specifications if they have been + decorated with a matching :class:`HookspecMarker`. + """ names = [] for name in dir(module_or_class): spec_opts = self.parse_hookspec_opts(module_or_class, name) if spec_opts is not None: - hc = getattr(self.hook, name, None) + hc: HookCaller | None = getattr(self.hook, name, None) if hc is None: - hc = _HookCaller(name, self._hookexec, module_or_class, spec_opts) + hc = HookCaller(name, self._hookexec, module_or_class, spec_opts) setattr(self.hook, name, hc) else: - # plugins registered this hook without knowing the spec + # Plugins registered this hook without knowing the spec. hc.set_specification(module_or_class, spec_opts) for hookfunction in hc.get_hookimpls(): self._verify_hook(hc, hookfunction) @@ -181,48 +272,68 @@ def add_hookspecs(self, module_or_class): f"did not find any {self.project_name!r} hooks in {module_or_class!r}" ) - def parse_hookspec_opts(self, module_or_class, name): + def parse_hookspec_opts( + self, module_or_class: _Namespace, name: str + ) -> HookspecOpts | None: + """Try to obtain a hook specification from an item with the given name + in the given module or class which is being searched for hook specs. + + :returns: + The parsed hookspec options for defining a hook, or None to skip the + given item. + + This method can be overridden by ``PluginManager`` subclasses to + customize how hook specifications are picked up. By default, returns the + options for items decorated with :class:`HookspecMarker`. + """ method = getattr(module_or_class, name) - return getattr(method, self.project_name + "_spec", None) - - def get_plugins(self): - """return the set of registered plugins.""" - return set(self._plugin2hookcallers) - - def is_registered(self, plugin): - """Return ``True`` if the plugin is already registered.""" - return plugin in self._plugin2hookcallers - - def get_canonical_name(self, plugin): - """Return canonical name for a plugin object. Note that a plugin - may be registered under a different name which was specified - by the caller of :py:meth:`register(plugin, name) <.PluginManager.register>`. - To obtain the name of an registered plugin use :py:meth:`get_name(plugin) - <.PluginManager.get_name>` instead.""" - return getattr(plugin, "__name__", None) or str(id(plugin)) - - def get_plugin(self, name): - """Return a plugin or ``None`` for the given name.""" + opts: HookspecOpts | None = getattr(method, self.project_name + "_spec", None) + return opts + + def get_plugins(self) -> set[Any]: + """Return a set of all registered plugin objects.""" + return {x for x in self._name2plugin.values() if x is not None} + + def is_registered(self, plugin: _Plugin) -> bool: + """Return whether the plugin is already registered.""" + return any(plugin == val for val in self._name2plugin.values()) + + def get_canonical_name(self, plugin: _Plugin) -> str: + """Return a canonical name for a plugin object. + + Note that a plugin may be registered under a different name + specified by the caller of :meth:`register(plugin, name) `. + To obtain the name of a registered plugin use :meth:`get_name(plugin) + ` instead. + """ + name: str | None = getattr(plugin, "__name__", None) + return name or str(id(plugin)) + + def get_plugin(self, name: str) -> Any | None: + """Return the plugin registered under the given name, if any.""" return self._name2plugin.get(name) - def has_plugin(self, name): - """Return ``True`` if a plugin with the given name is registered.""" + def has_plugin(self, name: str) -> bool: + """Return whether a plugin with the given name is registered.""" return self.get_plugin(name) is not None - def get_name(self, plugin): - """Return name for registered plugin or ``None`` if not registered.""" + def get_name(self, plugin: _Plugin) -> str | None: + """Return the name the plugin is registered under, or ``None`` if + is isn't.""" for name, val in self._name2plugin.items(): if plugin == val: return name + return None - def _verify_hook(self, hook, hookimpl): - if hook.is_historic() and hookimpl.hookwrapper: + def _verify_hook(self, hook: HookCaller, hookimpl: HookImpl) -> None: + if hook.is_historic() and (hookimpl.hookwrapper or hookimpl.wrapper): raise PluginValidationError( hookimpl.plugin, - "Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" + "Plugin %r\nhook %r\nhistoric incompatible with yield/wrapper/hookwrapper" % (hookimpl.plugin_name, hook.name), ) + assert hook.spec is not None if hook.spec.warn_on_impl: _warn_for_function(hook.spec.warn_on_impl, hookimpl.function) @@ -242,20 +353,38 @@ def _verify_hook(self, hook, hookimpl): ), ) - if hookimpl.hookwrapper and not inspect.isgeneratorfunction(hookimpl.function): + if hook.spec.warn_on_impl_args: + for hookimpl_argname in hookimpl.argnames: + argname_warning = hook.spec.warn_on_impl_args.get(hookimpl_argname) + if argname_warning is not None: + _warn_for_function(argname_warning, hookimpl.function) + + if ( + hookimpl.wrapper or hookimpl.hookwrapper + ) and not inspect.isgeneratorfunction(hookimpl.function): raise PluginValidationError( hookimpl.plugin, "Plugin %r for hook %r\nhookimpl definition: %s\n" - "Declared as hookwrapper=True but function is not a generator function" + "Declared as wrapper=True or hookwrapper=True " + "but function is not a generator function" % (hookimpl.plugin_name, hook.name, _formatdef(hookimpl.function)), ) - def check_pending(self): - """Verify that all hooks which have not been verified against - a hook specification are optional, otherwise raise :py:class:`.PluginValidationError`.""" + if hookimpl.wrapper and hookimpl.hookwrapper: + raise PluginValidationError( + hookimpl.plugin, + "Plugin %r for hook %r\nhookimpl definition: %s\n" + "The wrapper=True and hookwrapper=True options are mutually exclusive" + % (hookimpl.plugin_name, hook.name, _formatdef(hookimpl.function)), + ) + + def check_pending(self) -> None: + """Verify that all hooks which have not been verified against a + hook specification are optional, otherwise raise + :exc:`PluginValidationError`.""" for name in self.hook.__dict__: if name[0] != "_": - hook = getattr(self.hook, name) + hook: HookCaller = getattr(self.hook, name) if not hook.has_spec(): for hookimpl in hook.get_hookimpls(): if not hookimpl.optionalhook: @@ -265,16 +394,21 @@ def check_pending(self): % (name, hookimpl.plugin), ) - def load_setuptools_entrypoints(self, group, name=None): + def load_setuptools_entrypoints(self, group: str, name: str | None = None) -> int: """Load modules from querying the specified setuptools ``group``. - :param str group: entry point group to load plugins - :param str name: if given, loads only plugins with the given ``name``. - :rtype: int - :return: return the number of loaded plugins by this call. + :param group: + Entry point group to load plugins. + :param name: + If given, loads only plugins with the given ``name``. + + :return: + The number of plugins loaded by this call. """ + import importlib.metadata + count = 0 - for dist in list(importlib_metadata.distributions()): + for dist in list(importlib.metadata.distributions()): for ep in dist.entry_points: if ( ep.group != group @@ -290,84 +424,105 @@ def load_setuptools_entrypoints(self, group, name=None): count += 1 return count - def list_plugin_distinfo(self): - """return list of distinfo/plugin tuples for all setuptools registered - plugins.""" + def list_plugin_distinfo(self) -> list[tuple[_Plugin, DistFacade]]: + """Return a list of (plugin, distinfo) pairs for all + setuptools-registered plugins.""" return list(self._plugin_distinfo) - def list_name_plugin(self): - """return list of name/plugin pairs.""" + def list_name_plugin(self) -> list[tuple[str, _Plugin]]: + """Return a list of (name, plugin) pairs for all registered plugins.""" return list(self._name2plugin.items()) - def get_hookcallers(self, plugin): - """get all hook callers for the specified plugin.""" - return self._plugin2hookcallers.get(plugin) + def get_hookcallers(self, plugin: _Plugin) -> list[HookCaller] | None: + """Get all hook callers for the specified plugin. - def add_hookcall_monitoring(self, before, after): - """add before/after tracing functions for all hooks - and return an undo function which, when called, - will remove the added tracers. + :returns: + The hook callers, or ``None`` if ``plugin`` is not registered in + this plugin manager. + """ + if self.get_name(plugin) is None: + return None + hookcallers = [] + for hookcaller in self.hook.__dict__.values(): + for hookimpl in hookcaller.get_hookimpls(): + if hookimpl.plugin is plugin: + hookcallers.append(hookcaller) + return hookcallers + + def add_hookcall_monitoring( + self, before: _BeforeTrace, after: _AfterTrace + ) -> Callable[[], None]: + """Add before/after tracing functions for all hooks. + + Returns an undo function which, when called, removes the added tracers. ``before(hook_name, hook_impls, kwargs)`` will be called ahead of all hook calls and receive a hookcaller instance, a list of HookImpl instances and the keyword arguments for the hook call. ``after(outcome, hook_name, hook_impls, kwargs)`` receives the - same arguments as ``before`` but also a :py:class:`pluggy._callers._Result` object + same arguments as ``before`` but also a :class:`~pluggy.Result` object which represents the result of the overall hook call. """ oldcall = self._inner_hookexec - def traced_hookexec(hook_name, hook_impls, kwargs, firstresult): - before(hook_name, hook_impls, kwargs) - outcome = _Result.from_call( - lambda: oldcall(hook_name, hook_impls, kwargs, firstresult) + def traced_hookexec( + hook_name: str, + hook_impls: Sequence[HookImpl], + caller_kwargs: Mapping[str, object], + firstresult: bool, + ) -> object | list[object]: + before(hook_name, hook_impls, caller_kwargs) + outcome = Result.from_call( + lambda: oldcall(hook_name, hook_impls, caller_kwargs, firstresult) ) - after(outcome, hook_name, hook_impls, kwargs) + after(outcome, hook_name, hook_impls, caller_kwargs) return outcome.get_result() self._inner_hookexec = traced_hookexec - def undo(): + def undo() -> None: self._inner_hookexec = oldcall return undo - def enable_tracing(self): - """enable tracing of hook calls and return an undo function.""" + def enable_tracing(self) -> Callable[[], None]: + """Enable tracing of hook calls. + + Returns an undo function which, when called, removes the added tracing. + """ hooktrace = self.trace.root.get("hook") - def before(hook_name, methods, kwargs): + def before( + hook_name: str, methods: Sequence[HookImpl], kwargs: Mapping[str, object] + ) -> None: hooktrace.root.indent += 1 hooktrace(hook_name, kwargs) - def after(outcome, hook_name, methods, kwargs): - if outcome.excinfo is None: + def after( + outcome: Result[object], + hook_name: str, + methods: Sequence[HookImpl], + kwargs: Mapping[str, object], + ) -> None: + if outcome.exception is None: hooktrace("finish", hook_name, "-->", outcome.get_result()) hooktrace.root.indent -= 1 return self.add_hookcall_monitoring(before, after) - def subset_hook_caller(self, name, remove_plugins): - """Return a new :py:class:`._hooks._HookCaller` instance for the named method - which manages calls to all registered plugins except the - ones from remove_plugins.""" - orig = getattr(self.hook, name) - plugins_to_remove = [plug for plug in remove_plugins if hasattr(plug, name)] + def subset_hook_caller( + self, name: str, remove_plugins: Iterable[_Plugin] + ) -> HookCaller: + """Return a proxy :class:`~pluggy.HookCaller` instance for the named + method which manages calls to all registered plugins except the ones + from remove_plugins.""" + orig: HookCaller = getattr(self.hook, name) + plugins_to_remove = {plug for plug in remove_plugins if hasattr(plug, name)} if plugins_to_remove: - hc = _HookCaller( - orig.name, orig._hookexec, orig.spec.namespace, orig.spec.opts - ) - for hookimpl in orig.get_hookimpls(): - plugin = hookimpl.plugin - if plugin not in plugins_to_remove: - hc._add_hookimpl(hookimpl) - # we also keep track of this hook caller so it - # gets properly removed on plugin unregistration - self._plugin2hookcallers.setdefault(plugin, []).append(hc) - return hc + return _SubsetHookCaller(orig, plugins_to_remove) return orig -def _formatdef(func): +def _formatdef(func: Callable[..., object]) -> str: return f"{func.__name__}{inspect.signature(func)}" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_result.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_result.py index 4c1f7f1f3c01c..f9a081c4f6881 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_result.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_result.py @@ -1,60 +1,104 @@ """ Hook wrapper "result" utilities. """ -import sys +from __future__ import annotations -def _raise_wrapfail(wrap_controller, msg): - co = wrap_controller.gi_code - raise RuntimeError( - "wrap_controller at %r %s:%d %s" - % (co.co_name, co.co_filename, co.co_firstlineno, msg) - ) +from types import TracebackType +from typing import Callable +from typing import cast +from typing import final +from typing import Generic +from typing import Optional +from typing import Tuple +from typing import Type +from typing import TypeVar + + +_ExcInfo = Tuple[Type[BaseException], BaseException, Optional[TracebackType]] +ResultType = TypeVar("ResultType") class HookCallError(Exception): - """Hook was called wrongly.""" + """Hook was called incorrectly.""" + + +@final +class Result(Generic[ResultType]): + """An object used to inspect and set the result in a :ref:`hook wrapper + `.""" + __slots__ = ("_result", "_exception") -class _Result: - def __init__(self, result, excinfo): + def __init__( + self, + result: ResultType | None, + exception: BaseException | None, + ) -> None: + """:meta private:""" self._result = result - self._excinfo = excinfo + self._exception = exception @property - def excinfo(self): - return self._excinfo + def excinfo(self) -> _ExcInfo | None: + """:meta private:""" + exc = self._exception + if exc is None: + return None + else: + return (type(exc), exc, exc.__traceback__) + + @property + def exception(self) -> BaseException | None: + """:meta private:""" + return self._exception @classmethod - def from_call(cls, func): + def from_call(cls, func: Callable[[], ResultType]) -> Result[ResultType]: + """:meta private:""" __tracebackhide__ = True - result = excinfo = None + result = exception = None try: result = func() - except BaseException: - excinfo = sys.exc_info() - - return cls(result, excinfo) + except BaseException as exc: + exception = exc + return cls(result, exception) - def force_result(self, result): + def force_result(self, result: ResultType) -> None: """Force the result(s) to ``result``. If the hook was marked as a ``firstresult`` a single value should - be set otherwise set a (modified) list of results. Any exceptions + be set, otherwise set a (modified) list of results. Any exceptions found during invocation will be deleted. + + This overrides any previous result or exception. """ self._result = result - self._excinfo = None + self._exception = None + + def force_exception(self, exception: BaseException) -> None: + """Force the result to fail with ``exception``. - def get_result(self): + This overrides any previous result or exception. + + .. versionadded:: 1.1.0 + """ + self._result = None + self._exception = exception + + def get_result(self) -> ResultType: """Get the result(s) for this hook call. If the hook was marked as a ``firstresult`` only a single value - will be returned otherwise a list of results. + will be returned, otherwise a list of results. """ __tracebackhide__ = True - if self._excinfo is None: - return self._result + exc = self._exception + if exc is None: + return cast(ResultType, self._result) else: - ex = self._excinfo - raise ex[1].with_traceback(ex[2]) + raise exc.with_traceback(exc.__traceback__) + + +# Historical name (pluggy<=1.2), kept for backward compatibility. +_Result = Result diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_tracing.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_tracing.py index 82c016271e1ea..cd238ad7e5495 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_tracing.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_tracing.py @@ -2,17 +2,28 @@ Tracing utils """ +from __future__ import annotations + +from typing import Any +from typing import Callable +from typing import Sequence +from typing import Tuple + + +_Writer = Callable[[str], object] +_Processor = Callable[[Tuple[str, ...], Tuple[Any, ...]], object] + class TagTracer: - def __init__(self): - self._tags2proc = {} - self._writer = None + def __init__(self) -> None: + self._tags2proc: dict[tuple[str, ...], _Processor] = {} + self._writer: _Writer | None = None self.indent = 0 - def get(self, name): + def get(self, name: str) -> TagTracerSub: return TagTracerSub(self, (name,)) - def _format_message(self, tags, args): + def _format_message(self, tags: Sequence[str], args: Sequence[object]) -> str: if isinstance(args[-1], dict): extra = args[-1] args = args[:-1] @@ -29,7 +40,7 @@ def _format_message(self, tags, args): return "".join(lines) - def _processmessage(self, tags, args): + def _processmessage(self, tags: tuple[str, ...], args: tuple[object, ...]) -> None: if self._writer is not None and args: self._writer(self._format_message(tags, args)) try: @@ -39,10 +50,10 @@ def _processmessage(self, tags, args): else: processor(tags, args) - def setwriter(self, writer): + def setwriter(self, writer: _Writer | None) -> None: self._writer = writer - def setprocessor(self, tags, processor): + def setprocessor(self, tags: str | tuple[str, ...], processor: _Processor) -> None: if isinstance(tags, str): tags = tuple(tags.split(":")) else: @@ -51,12 +62,12 @@ def setprocessor(self, tags, processor): class TagTracerSub: - def __init__(self, root, tags): + def __init__(self, root: TagTracer, tags: tuple[str, ...]) -> None: self.root = root self.tags = tags - def __call__(self, *args): + def __call__(self, *args: object) -> None: self.root._processmessage(self.tags, args) - def get(self, name): + def get(self, name: str) -> TagTracerSub: return self.__class__(self.root, self.tags + (name,)) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_warnings.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_warnings.py new file mode 100644 index 0000000000000..6356c770c7d1f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/_warnings.py @@ -0,0 +1,27 @@ +from typing import final + + +class PluggyWarning(UserWarning): + """Base class for all warnings emitted by pluggy.""" + + __module__ = "pluggy" + + +@final +class PluggyTeardownRaisedWarning(PluggyWarning): + """A plugin raised an exception during an :ref:`old-style hookwrapper + ` teardown. + + Such exceptions are not handled by pluggy, and may cause subsequent + teardowns to be executed at unexpected times, or be skipped entirely. + + This is an issue in the plugin implementation. + + If the exception is unintended, fix the underlying cause. + + If the exception is intended, switch to :ref:`new-style hook wrappers + `, or use :func:`result.force_exception() + ` to set the exception instead of raising. + """ + + __module__ = "pluggy" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/py.typed b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/src/pluggy/py.typed new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/benchmark.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/benchmark.py index b0d4b9536a010..d13e50aa465b1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/benchmark.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/benchmark.py @@ -1,10 +1,14 @@ """ Benchmarking and performance tests. """ + import pytest -from pluggy import HookspecMarker, HookimplMarker, PluginManager -from pluggy._hooks import HookImpl + +from pluggy import HookimplMarker +from pluggy import HookspecMarker +from pluggy import PluginManager from pluggy._callers import _multicall +from pluggy._hooks import HookImpl hookspec = HookspecMarker("example") @@ -16,9 +20,9 @@ def hook(arg1, arg2, arg3): return arg1, arg2, arg3 -@hookimpl(hookwrapper=True) +@hookimpl(wrapper=True) def wrapper(arg1, arg2, arg3): - yield + return (yield) @pytest.fixture(params=[10, 100], ids="hooks={}".format) @@ -42,7 +46,7 @@ def setup(): firstresult = False return (hook_name, hook_impls, caller_kwargs, firstresult), {} - benchmark.pedantic(_multicall, setup=setup) + benchmark.pedantic(_multicall, setup=setup, rounds=10) @pytest.mark.parametrize( @@ -67,30 +71,30 @@ def test_call_hook(benchmark, plugins, wrappers, nesting): class HookSpec: @hookspec def fun(self, hooks, nesting: int): - yield + pass class Plugin: - def __init__(self, num): + def __init__(self, num: int) -> None: self.num = num - def __repr__(self): + def __repr__(self) -> str: return f"" @hookimpl - def fun(self, hooks, nesting: int): + def fun(self, hooks, nesting: int) -> None: if nesting: hooks.fun(hooks=hooks, nesting=nesting - 1) class PluginWrap: - def __init__(self, num): + def __init__(self, num: int) -> None: self.num = num - def __repr__(self): + def __repr__(self) -> str: return f"" - @hookimpl(hookwrapper=True) + @hookimpl(wrapper=True) def fun(self): - yield + return (yield) pm.add_hookspecs(HookSpec) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/conftest.py index 1fd4ecd5bdf4c..8842bd7264422 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/conftest.py @@ -1,18 +1,19 @@ import pytest +from pluggy import HookspecMarker +from pluggy import PluginManager + @pytest.fixture( params=[lambda spec: spec, lambda spec: spec()], ids=["spec-is-class", "spec-is-instance"], ) -def he_pm(request, pm): - from pluggy import HookspecMarker - +def he_pm(request, pm: PluginManager) -> PluginManager: hookspec = HookspecMarker("example") class Hooks: @hookspec - def he_method1(self, arg): + def he_method1(self, arg: int) -> int: return arg + 1 pm.add_hookspecs(request.param(Hooks)) @@ -20,7 +21,5 @@ def he_method1(self, arg): @pytest.fixture -def pm(): - from pluggy import PluginManager - +def pm() -> PluginManager: return PluginManager("example") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_details.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_details.py index 0ceb3b3eb1394..9b68a0814f8a3 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_details.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_details.py @@ -1,18 +1,21 @@ -import warnings import pytest -from pluggy import PluginManager, HookimplMarker, HookspecMarker + +from pluggy import HookimplMarker +from pluggy import HookspecMarker +from pluggy import PluginManager + hookspec = HookspecMarker("example") hookimpl = HookimplMarker("example") -def test_parse_hookimpl_override(): +def test_parse_hookimpl_override() -> None: class MyPluginManager(PluginManager): def parse_hookimpl_opts(self, module_or_class, name): opts = PluginManager.parse_hookimpl_opts(self, module_or_class, name) if opts is None: if name.startswith("x1"): - opts = {} + opts = {} # type: ignore[assignment] return opts class Plugin: @@ -23,6 +26,10 @@ def x1meth(self): def x1meth2(self): yield # pragma: no cover + @hookimpl(wrapper=True, trylast=True) + def x1meth3(self): + return (yield) # pragma: no cover + class Spec: @hookspec def x1meth(self): @@ -32,19 +39,37 @@ def x1meth(self): def x1meth2(self): pass + @hookspec + def x1meth3(self): + pass + pm = MyPluginManager(hookspec.project_name) pm.register(Plugin()) pm.add_hookspecs(Spec) - assert not pm.hook.x1meth._nonwrappers[0].hookwrapper - assert not pm.hook.x1meth._nonwrappers[0].tryfirst - assert not pm.hook.x1meth._nonwrappers[0].trylast - assert not pm.hook.x1meth._nonwrappers[0].optionalhook - - assert pm.hook.x1meth2._wrappers[0].tryfirst - assert pm.hook.x1meth2._wrappers[0].hookwrapper - -def test_warn_when_deprecated_specified(recwarn): + hookimpls = pm.hook.x1meth.get_hookimpls() + assert len(hookimpls) == 1 + assert not hookimpls[0].hookwrapper + assert not hookimpls[0].wrapper + assert not hookimpls[0].tryfirst + assert not hookimpls[0].trylast + assert not hookimpls[0].optionalhook + + hookimpls = pm.hook.x1meth2.get_hookimpls() + assert len(hookimpls) == 1 + assert hookimpls[0].hookwrapper + assert not hookimpls[0].wrapper + assert hookimpls[0].tryfirst + + hookimpls = pm.hook.x1meth3.get_hookimpls() + assert len(hookimpls) == 1 + assert not hookimpls[0].hookwrapper + assert hookimpls[0].wrapper + assert not hookimpls[0].tryfirst + assert hookimpls[0].trylast + + +def test_warn_when_deprecated_specified(recwarn) -> None: warning = DeprecationWarning("foo is deprecated") class Spec: @@ -68,20 +93,53 @@ def foo(self): assert record.lineno == Plugin.foo.__code__.co_firstlineno -def test_plugin_getattr_raises_errors(): +def test_warn_when_deprecated_args_specified(recwarn) -> None: + warning1 = DeprecationWarning("old1 is deprecated") + warning2 = DeprecationWarning("old2 is deprecated") + + class Spec: + @hookspec( + warn_on_impl_args={ + "old1": warning1, + "old2": warning2, + }, + ) + def foo(self, old1, new, old2): + raise NotImplementedError() + + class Plugin: + @hookimpl + def foo(self, old2, old1, new): + raise NotImplementedError() + + pm = PluginManager(hookspec.project_name) + pm.add_hookspecs(Spec) + + with pytest.warns(DeprecationWarning) as records: + pm.register(Plugin()) + (record1, record2) = records + assert record1.message is warning2 + assert record1.filename == Plugin.foo.__code__.co_filename + assert record1.lineno == Plugin.foo.__code__.co_firstlineno + assert record2.message is warning1 + assert record2.filename == Plugin.foo.__code__.co_filename + assert record2.lineno == Plugin.foo.__code__.co_firstlineno + + +def test_plugin_getattr_raises_errors() -> None: """Pluggy must be able to handle plugins which raise weird exceptions when getattr() gets called (#11). """ class DontTouchMe: def __getattr__(self, x): - raise Exception("cant touch me") + raise Exception("can't touch me") class Module: pass module = Module() - module.x = DontTouchMe() + module.x = DontTouchMe() # type: ignore[attr-defined] pm = PluginManager(hookspec.project_name) # register() would raise an error @@ -89,38 +147,36 @@ class Module: assert pm.get_plugin("donttouch") is module -def test_warning_on_call_vs_hookspec_arg_mismatch(): - """Verify that is a hook is called with less arguments then defined in the - spec that a warning is emitted. - """ +def test_not_all_arguments_are_provided_issues_a_warning(pm: PluginManager) -> None: + """Calling a hook without providing all arguments specified in + the hook spec issues a warning.""" class Spec: @hookspec - def myhook(self, arg1, arg2): + def hello(self, arg1, arg2): pass - class Plugin: - @hookimpl - def myhook(self, arg1): + @hookspec(historic=True) + def herstory(self, arg1, arg2): pass - pm = PluginManager(hookspec.project_name) - pm.register(Plugin()) - pm.add_hookspecs(Spec()) + pm.add_hookspecs(Spec) - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("always") + with pytest.warns(UserWarning, match=r"'arg1', 'arg2'.*cannot be found.*$"): + pm.hook.hello() + with pytest.warns(UserWarning, match=r"'arg2'.*cannot be found.*$"): + pm.hook.hello(arg1=1) + with pytest.warns(UserWarning, match=r"'arg1'.*cannot be found.*$"): + pm.hook.hello(arg2=2) - # calling should trigger a warning - pm.hook.myhook(arg1=1) + with pytest.warns(UserWarning, match=r"'arg1', 'arg2'.*cannot be found.*$"): + pm.hook.hello.call_extra([], kwargs=dict()) - assert len(warns) == 1 - warning = warns[-1] - assert issubclass(warning.category, Warning) - assert "Argument(s) ('arg2',)" in str(warning.message) + with pytest.warns(UserWarning, match=r"'arg1', 'arg2'.*cannot be found.*$"): + pm.hook.herstory.call_historic(kwargs=dict()) -def test_repr(): +def test_repr() -> None: class Plugin: @hookimpl def myhook(self): @@ -130,6 +186,6 @@ def myhook(self): plugin = Plugin() pname = pm.register(plugin) - assert repr(pm.hook.myhook._nonwrappers[0]) == ( + assert repr(pm.hook.myhook.get_hookimpls()[0]) == ( f"" ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_helpers.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_helpers.py index 465858c499cb7..4fe26a57bc50c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_helpers.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_helpers.py @@ -1,17 +1,23 @@ +from functools import wraps +from typing import Any +from typing import Callable +from typing import cast +from typing import TypeVar + from pluggy._hooks import varnames from pluggy._manager import _formatdef -def test_varnames(): - def f(x): +def test_varnames() -> None: + def f(x) -> None: i = 3 # noqa class A: - def f(self, y): + def f(self, y) -> None: pass class B: - def __call__(self, z): + def __call__(self, z) -> None: pass assert varnames(f) == (("x",), ()) @@ -19,23 +25,23 @@ def __call__(self, z): assert varnames(B()) == (("z",), ()) -def test_varnames_default(): - def f(x, y=3): +def test_varnames_default() -> None: + def f(x, y=3) -> None: pass assert varnames(f) == (("x",), ("y",)) -def test_varnames_class(): +def test_varnames_class() -> None: class C: - def __init__(self, x): + def __init__(self, x) -> None: pass class D: pass class E: - def __init__(self, x): + def __init__(self, x) -> None: pass class F: @@ -47,14 +53,14 @@ class F: assert varnames(F) == ((), ()) -def test_varnames_keyword_only(): - def f1(x, *, y): +def test_varnames_keyword_only() -> None: + def f1(x, *, y) -> None: pass - def f2(x, *, y=3): + def f2(x, *, y=3) -> None: pass - def f3(x=1, *, y=3): + def f3(x=1, *, y=3) -> None: pass assert varnames(f1) == (("x",), ()) @@ -62,7 +68,7 @@ def f3(x=1, *, y=3): assert varnames(f3) == ((), ("x",)) -def test_formatdef(): +def test_formatdef() -> None: def function1(): pass @@ -82,3 +88,29 @@ def function4(arg1, *args, **kwargs): pass assert _formatdef(function4) == "function4(arg1, *args, **kwargs)" + + +def test_varnames_decorator() -> None: + F = TypeVar("F", bound=Callable[..., Any]) + + def my_decorator(func: F) -> F: + @wraps(func) + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + + return cast(F, wrapper) + + @my_decorator + def example(a, b=123) -> None: + pass + + class Example: + @my_decorator + def example_method(self, x, y=1) -> None: + pass + + ex_inst = Example() + + assert varnames(example) == (("a",), ("b",)) + assert varnames(Example.example_method) == (("x",), ("y",)) + assert varnames(ex_inst.example_method) == (("x",), ("y",)) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_hookcaller.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_hookcaller.py index 9eeaef8666652..3db76de2562fe 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_hookcaller.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_hookcaller.py @@ -1,90 +1,122 @@ +from typing import Callable +from typing import Generator +from typing import List +from typing import Sequence +from typing import TypeVar + import pytest -from pluggy import HookimplMarker, HookspecMarker, PluginValidationError +from pluggy import HookimplMarker +from pluggy import HookspecMarker +from pluggy import PluginManager +from pluggy import PluginValidationError +from pluggy._hooks import HookCaller from pluggy._hooks import HookImpl + hookspec = HookspecMarker("example") hookimpl = HookimplMarker("example") @pytest.fixture -def hc(pm): +def hc(pm: PluginManager) -> HookCaller: class Hooks: @hookspec - def he_method1(self, arg): + def he_method1(self, arg: object) -> None: pass pm.add_hookspecs(Hooks) return pm.hook.he_method1 -@pytest.fixture -def addmeth(hc): - def addmeth(tryfirst=False, trylast=False, hookwrapper=False): - def wrap(func): - hookimpl(tryfirst=tryfirst, trylast=trylast, hookwrapper=hookwrapper)(func) - hc._add_hookimpl(HookImpl(None, "", func, func.example_impl)) +FuncT = TypeVar("FuncT", bound=Callable[..., object]) + + +class AddMeth: + def __init__(self, hc: HookCaller) -> None: + self.hc = hc + + def __call__( + self, + tryfirst: bool = False, + trylast: bool = False, + hookwrapper: bool = False, + wrapper: bool = False, + ) -> Callable[[FuncT], FuncT]: + def wrap(func: FuncT) -> FuncT: + hookimpl( + tryfirst=tryfirst, + trylast=trylast, + hookwrapper=hookwrapper, + wrapper=wrapper, + )(func) + self.hc._add_hookimpl( + HookImpl(None, "", func, func.example_impl), # type: ignore[attr-defined] + ) return func return wrap - return addmeth + +@pytest.fixture +def addmeth(hc: HookCaller) -> AddMeth: + return AddMeth(hc) -def funcs(hookmethods): +def funcs(hookmethods: Sequence[HookImpl]) -> List[Callable[..., object]]: return [hookmethod.function for hookmethod in hookmethods] -def test_adding_nonwrappers(hc, addmeth): +def test_adding_nonwrappers(hc: HookCaller, addmeth: AddMeth) -> None: @addmeth() - def he_method1(): + def he_method1() -> None: pass @addmeth() - def he_method2(): + def he_method2() -> None: pass @addmeth() - def he_method3(): + def he_method3() -> None: pass - assert funcs(hc._nonwrappers) == [he_method1, he_method2, he_method3] + assert funcs(hc.get_hookimpls()) == [he_method1, he_method2, he_method3] -def test_adding_nonwrappers_trylast(hc, addmeth): +def test_adding_nonwrappers_trylast(hc: HookCaller, addmeth: AddMeth) -> None: @addmeth() - def he_method1_middle(): + def he_method1_middle() -> None: pass @addmeth(trylast=True) - def he_method1(): + def he_method1() -> None: pass @addmeth() - def he_method1_b(): + def he_method1_b() -> None: pass - assert funcs(hc._nonwrappers) == [he_method1, he_method1_middle, he_method1_b] + assert funcs(hc.get_hookimpls()) == [he_method1, he_method1_middle, he_method1_b] -def test_adding_nonwrappers_trylast3(hc, addmeth): +def test_adding_nonwrappers_trylast3(hc: HookCaller, addmeth: AddMeth) -> None: @addmeth() - def he_method1_a(): + def he_method1_a() -> None: pass @addmeth(trylast=True) - def he_method1_b(): + def he_method1_b() -> None: pass @addmeth() - def he_method1_c(): + def he_method1_c() -> None: pass @addmeth(trylast=True) - def he_method1_d(): + def he_method1_d() -> None: pass - assert funcs(hc._nonwrappers) == [ + assert funcs(hc.get_hookimpls()) == [ he_method1_d, he_method1_b, he_method1_a, @@ -92,93 +124,212 @@ def he_method1_d(): ] -def test_adding_nonwrappers_trylast2(hc, addmeth): +def test_adding_nonwrappers_trylast2(hc: HookCaller, addmeth: AddMeth) -> None: @addmeth() - def he_method1_middle(): + def he_method1_middle() -> None: pass @addmeth() - def he_method1_b(): + def he_method1_b() -> None: pass @addmeth(trylast=True) - def he_method1(): + def he_method1() -> None: pass - assert funcs(hc._nonwrappers) == [he_method1, he_method1_middle, he_method1_b] + assert funcs(hc.get_hookimpls()) == [he_method1, he_method1_middle, he_method1_b] -def test_adding_nonwrappers_tryfirst(hc, addmeth): +def test_adding_nonwrappers_tryfirst(hc: HookCaller, addmeth: AddMeth) -> None: @addmeth(tryfirst=True) - def he_method1(): + def he_method1() -> None: pass @addmeth() - def he_method1_middle(): + def he_method1_middle() -> None: pass @addmeth() - def he_method1_b(): + def he_method1_b() -> None: pass - assert funcs(hc._nonwrappers) == [he_method1_middle, he_method1_b, he_method1] + assert funcs(hc.get_hookimpls()) == [he_method1_middle, he_method1_b, he_method1] -def test_adding_wrappers_ordering(hc, addmeth): +def test_adding_wrappers_ordering(hc: HookCaller, addmeth: AddMeth) -> None: @addmeth(hookwrapper=True) def he_method1(): - pass + yield + + @addmeth(wrapper=True) + def he_method1_fun(): + yield @addmeth() def he_method1_middle(): - pass + return @addmeth(hookwrapper=True) - def he_method3(): - pass + def he_method3_fun(): + yield - assert funcs(hc._nonwrappers) == [he_method1_middle] - assert funcs(hc._wrappers) == [he_method1, he_method3] + @addmeth(hookwrapper=True) + def he_method3(): + yield + + assert funcs(hc.get_hookimpls()) == [ + he_method1_middle, + he_method1, + he_method1_fun, + he_method3_fun, + he_method3, + ] -def test_adding_wrappers_ordering_tryfirst(hc, addmeth): +def test_adding_wrappers_ordering_tryfirst(hc: HookCaller, addmeth: AddMeth) -> None: @addmeth(hookwrapper=True, tryfirst=True) def he_method1(): - pass + yield @addmeth(hookwrapper=True) def he_method2(): - pass + yield + + @addmeth(wrapper=True, tryfirst=True) + def he_method3(): + yield + + assert funcs(hc.get_hookimpls()) == [he_method2, he_method1, he_method3] + + +def test_adding_wrappers_complex(hc: HookCaller, addmeth: AddMeth) -> None: + assert funcs(hc.get_hookimpls()) == [] + + @addmeth(hookwrapper=True, trylast=True) + def m1(): + yield + + assert funcs(hc.get_hookimpls()) == [m1] + + @addmeth() + def m2() -> None: ... + + assert funcs(hc.get_hookimpls()) == [m2, m1] + + @addmeth(trylast=True) + def m3() -> None: ... + + assert funcs(hc.get_hookimpls()) == [m3, m2, m1] + + @addmeth(hookwrapper=True) + def m4() -> None: ... - assert hc._nonwrappers == [] - assert funcs(hc._wrappers) == [he_method2, he_method1] + assert funcs(hc.get_hookimpls()) == [m3, m2, m1, m4] + @addmeth(wrapper=True, tryfirst=True) + def m5(): + yield -def test_hookspec(pm): + assert funcs(hc.get_hookimpls()) == [m3, m2, m1, m4, m5] + + @addmeth(tryfirst=True) + def m6() -> None: ... + + assert funcs(hc.get_hookimpls()) == [m3, m2, m6, m1, m4, m5] + + @addmeth() + def m7() -> None: ... + + assert funcs(hc.get_hookimpls()) == [m3, m2, m7, m6, m1, m4, m5] + + @addmeth(wrapper=True) + def m8(): + yield + + assert funcs(hc.get_hookimpls()) == [m3, m2, m7, m6, m1, m4, m8, m5] + + @addmeth(trylast=True) + def m9() -> None: ... + + assert funcs(hc.get_hookimpls()) == [m9, m3, m2, m7, m6, m1, m4, m8, m5] + + @addmeth(tryfirst=True) + def m10() -> None: ... + + assert funcs(hc.get_hookimpls()) == [m9, m3, m2, m7, m6, m10, m1, m4, m8, m5] + + @addmeth(hookwrapper=True, trylast=True) + def m11() -> None: ... + + assert funcs(hc.get_hookimpls()) == [m9, m3, m2, m7, m6, m10, m11, m1, m4, m8, m5] + + @addmeth(wrapper=True) + def m12(): + yield + + assert funcs(hc.get_hookimpls()) == [ + m9, + m3, + m2, + m7, + m6, + m10, + m11, + m1, + m4, + m8, + m12, + m5, + ] + + @addmeth() + def m13() -> None: ... + + assert funcs(hc.get_hookimpls()) == [ + m9, + m3, + m2, + m7, + m13, + m6, + m10, + m11, + m1, + m4, + m8, + m12, + m5, + ] + + +def test_hookspec(pm: PluginManager) -> None: class HookSpec: @hookspec() - def he_myhook1(arg1): + def he_myhook1(arg1) -> None: pass @hookspec(firstresult=True) - def he_myhook2(arg1): + def he_myhook2(arg1) -> None: pass @hookspec(firstresult=False) - def he_myhook3(arg1): + def he_myhook3(arg1) -> None: pass pm.add_hookspecs(HookSpec) + assert pm.hook.he_myhook1.spec is not None assert not pm.hook.he_myhook1.spec.opts["firstresult"] + assert pm.hook.he_myhook2.spec is not None assert pm.hook.he_myhook2.spec.opts["firstresult"] + assert pm.hook.he_myhook3.spec is not None assert not pm.hook.he_myhook3.spec.opts["firstresult"] @pytest.mark.parametrize("name", ["hookwrapper", "optionalhook", "tryfirst", "trylast"]) @pytest.mark.parametrize("val", [True, False]) -def test_hookimpl(name, val): - @hookimpl(**{name: val}) - def he_myhook1(arg1): +def test_hookimpl(name: str, val: bool) -> None: + @hookimpl(**{name: val}) # type: ignore[misc,call-overload] + def he_myhook1(arg1) -> None: pass if val: @@ -187,13 +338,13 @@ def he_myhook1(arg1): assert not hasattr(he_myhook1, name) -def test_hookrelay_registry(pm): +def test_hookrelay_registry(pm: PluginManager) -> None: """Verify hook caller instances are registered by name onto the relay and can be likewise unregistered.""" class Api: @hookspec - def hello(self, arg): + def hello(self, arg: object) -> None: "api hook 1" pm.add_hookspecs(Api) @@ -215,13 +366,13 @@ def hello(self, arg): assert hook.hello(arg=3) == [] -def test_hookrelay_registration_by_specname(pm): +def test_hookrelay_registration_by_specname(pm: PluginManager) -> None: """Verify hook caller instances may also be registered by specifying a specname option to the hookimpl""" class Api: @hookspec - def hello(self, arg): + def hello(self, arg: object) -> None: "api hook 1" pm.add_hookspecs(Api) @@ -231,7 +382,7 @@ def hello(self, arg): class Plugin: @hookimpl(specname="hello") - def foo(self, arg): + def foo(self, arg: int) -> int: return arg + 1 plugin = Plugin() @@ -240,13 +391,13 @@ def foo(self, arg): assert out == [4] -def test_hookrelay_registration_by_specname_raises(pm): +def test_hookrelay_registration_by_specname_raises(pm: PluginManager) -> None: """Verify using specname still raises the types of errors during registration as it would have without using specname.""" class Api: @hookspec - def hello(self, arg): + def hello(self, arg: object) -> None: "api hook 1" pm.add_hookspecs(Api) @@ -254,7 +405,7 @@ def hello(self, arg): # make sure a bad signature still raises an error when using specname class Plugin: @hookimpl(specname="hello") - def foo(self, arg, too, many, args): + def foo(self, arg: int, too, many, args) -> int: return arg + 1 with pytest.raises(PluginValidationError): @@ -264,9 +415,100 @@ def foo(self, arg, too, many, args): # corresponding spec. EVEN if the function name matches one. class Plugin2: @hookimpl(specname="bar") - def hello(self, arg): + def hello(self, arg: int) -> int: return arg + 1 pm.register(Plugin2()) with pytest.raises(PluginValidationError): pm.check_pending() + + +def test_hook_conflict(pm: PluginManager) -> None: + class Api1: + @hookspec + def conflict(self) -> None: + """Api1 hook""" + + class Api2: + @hookspec + def conflict(self) -> None: + """Api2 hook""" + + pm.add_hookspecs(Api1) + with pytest.raises(ValueError) as exc: + pm.add_hookspecs(Api2) + assert str(exc.value) == ( + "Hook 'conflict' is already registered within namespace " + ".Api1'>" + ) + + +def test_call_extra_hook_order(hc: HookCaller, addmeth: AddMeth) -> None: + """Ensure that call_extra is calling hooks in the right order.""" + order = [] + + @addmeth(tryfirst=True) + def method1() -> str: + order.append("1") + return "1" + + @addmeth() + def method2() -> str: + order.append("2") + return "2" + + @addmeth(trylast=True) + def method3() -> str: + order.append("3") + return "3" + + @addmeth(wrapper=True, tryfirst=True) + def method4() -> Generator[None, str, str]: + order.append("4pre") + result = yield + order.append("4post") + return result + + @addmeth(wrapper=True) + def method5() -> Generator[None, str, str]: + order.append("5pre") + result = yield + order.append("5post") + return result + + @addmeth(wrapper=True, trylast=True) + def method6() -> Generator[None, str, str]: + order.append("6pre") + result = yield + order.append("6post") + return result + + def extra1() -> str: + order.append("extra1") + return "extra1" + + def extra2() -> str: + order.append("extra2") + return "extra2" + + result = hc.call_extra([extra1, extra2], {"arg": "test"}) + assert order == [ + "4pre", + "5pre", + "6pre", + "1", + "extra2", + "extra1", + "2", + "3", + "6post", + "5post", + "4post", + ] + assert result == [ + "1", + "extra2", + "extra1", + "2", + "3", + ] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_invocations.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_invocations.py index 323b9b21e8b6d..833ec2b79cec8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_invocations.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_invocations.py @@ -1,12 +1,18 @@ +from typing import Iterator + import pytest -from pluggy import PluginValidationError, HookimplMarker, HookspecMarker + +from pluggy import HookimplMarker +from pluggy import HookspecMarker +from pluggy import PluginManager +from pluggy import PluginValidationError hookspec = HookspecMarker("example") hookimpl = HookimplMarker("example") -def test_argmismatch(pm): +def test_argmismatch(pm: PluginManager) -> None: class Api: @hookspec def hello(self, arg): @@ -25,7 +31,7 @@ def hello(self, argwrong): assert "argwrong" in str(exc.value) -def test_only_kwargs(pm): +def test_only_kwargs(pm: PluginManager) -> None: class Api: @hookspec def hello(self, arg): @@ -33,14 +39,14 @@ def hello(self, arg): pm.add_hookspecs(Api) with pytest.raises(TypeError) as exc: - pm.hook.hello(3) + pm.hook.hello(3) # type: ignore[call-arg] - comprehensible = "hook calling supports only keyword arguments" - assert comprehensible in str(exc.value) + message = "__call__() takes 1 positional argument but 2 were given" + assert message in str(exc.value) -def test_opt_in_args(pm): - """Verfiy that two hookimpls with mutex args can serve +def test_opt_in_args(pm: PluginManager) -> None: + """Verify that two hookimpls with mutex args can serve under the same spec. """ @@ -67,7 +73,7 @@ def hello(self, arg2, common_arg): assert results == [2, 1] -def test_call_order(pm): +def test_call_order(pm: PluginManager) -> None: class Api: @hookspec def hello(self, arg): @@ -96,16 +102,27 @@ def hello(self, arg): assert arg == 0 outcome = yield assert outcome.get_result() == [3, 2, 1] + assert outcome.exception is None + assert outcome.excinfo is None + + class Plugin5: + @hookimpl(wrapper=True) + def hello(self, arg): + assert arg == 0 + result = yield + assert result == [3, 2, 1] + return result pm.register(Plugin1()) pm.register(Plugin2()) pm.register(Plugin3()) pm.register(Plugin4()) # hookwrapper should get same list result + pm.register(Plugin5()) # hookwrapper should get same list result res = pm.hook.hello(arg=0) assert res == [3, 2, 1] -def test_firstresult_definition(pm): +def test_firstresult_definition(pm: PluginManager) -> None: class Api: @hookspec(firstresult=True) def hello(self, arg): @@ -129,6 +146,14 @@ def hello(self, arg): return None class Plugin4: + @hookimpl(wrapper=True) + def hello(self, arg): + assert arg == 3 + outcome = yield + assert outcome == 2 + return outcome + + class Plugin5: @hookimpl(hookwrapper=True) def hello(self, arg): assert arg == 3 @@ -138,12 +163,13 @@ def hello(self, arg): pm.register(Plugin1()) # discarded - not the last registered plugin pm.register(Plugin2()) # used as result pm.register(Plugin3()) # None result is ignored - pm.register(Plugin4()) # hookwrapper should get same non-list result + pm.register(Plugin4()) # wrapper should get same non-list result + pm.register(Plugin5()) # hookwrapper should get same non-list result res = pm.hook.hello(arg=3) assert res == 2 -def test_firstresult_force_result(pm): +def test_firstresult_force_result_hookwrapper(pm: PluginManager) -> None: """Verify forcing a result in a wrapper.""" class Api: @@ -178,7 +204,42 @@ def hello(self, arg): assert res == 0 # this result is forced and not a list -def test_firstresult_returns_none(pm): +def test_firstresult_force_result(pm: PluginManager) -> None: + """Verify forcing a result in a wrapper.""" + + class Api: + @hookspec(firstresult=True) + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + + class Plugin1: + @hookimpl + def hello(self, arg): + return arg + 1 + + class Plugin2: + @hookimpl(wrapper=True) + def hello(self, arg): + assert arg == 3 + outcome = yield + assert outcome == 4 + return 0 + + class Plugin3: + @hookimpl + def hello(self, arg): + return None + + pm.register(Plugin1()) + pm.register(Plugin2()) # wrapper + pm.register(Plugin3()) # ignored since returns None + res = pm.hook.hello(arg=3) + assert res == 0 # this result is forced and not a list + + +def test_firstresult_returns_none(pm: PluginManager) -> None: """If None results are returned by underlying implementations ensure the multi-call loop returns a None value. """ @@ -200,7 +261,7 @@ def hello(self, arg): assert res is None -def test_firstresult_no_plugin(pm): +def test_firstresult_no_plugin(pm: PluginManager) -> None: """If no implementations/plugins have been registered for a firstresult hook the multi-call loop should return a None value. """ @@ -213,3 +274,55 @@ def hello(self, arg): pm.add_hookspecs(Api) res = pm.hook.hello(arg=3) assert res is None + + +def test_no_hookspec(pm: PluginManager) -> None: + """A hook with hookimpls can still be called even if no hookspec + was registered for it (and call_pending wasn't called to check + against it). + """ + + class Plugin: + @hookimpl + def hello(self, arg): + return "Plugin.hello" + + pm.register(Plugin()) + + assert pm.hook.hello(arg=10, extra=20) == ["Plugin.hello"] + + +def test_non_wrapper_generator(pm: PluginManager) -> None: + """A hookimpl can be a generator without being a wrapper, + meaning it returns an iterator result.""" + + class Api: + @hookspec + def hello(self) -> Iterator[int]: + raise NotImplementedError() + + pm.add_hookspecs(Api) + + class Plugin1: + @hookimpl + def hello(self): + yield 1 + + class Plugin2: + @hookimpl + def hello(self): + yield 2 + yield 3 + + class Plugin3: + @hookimpl(wrapper=True) + def hello(self): + return (yield) + + pm.register(Plugin1()) + pm.register(Plugin2()) # wrapper + res = pm.hook.hello() + assert [y for x in res for y in x] == [2, 3, 1] + pm.register(Plugin3()) + res = pm.hook.hello() + assert [y for x in res for y in x] == [2, 3, 1] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_multicall.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_multicall.py index 8ffb452f694ba..7d8d8f2881a42 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_multicall.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_multicall.py @@ -1,23 +1,37 @@ +from typing import Callable +from typing import List +from typing import Mapping +from typing import Sequence +from typing import Type +from typing import Union + import pytest -from pluggy import HookCallError, HookspecMarker, HookimplMarker -from pluggy._hooks import HookImpl + +from pluggy import HookCallError +from pluggy import HookimplMarker +from pluggy import HookspecMarker from pluggy._callers import _multicall +from pluggy._hooks import HookImpl hookspec = HookspecMarker("example") hookimpl = HookimplMarker("example") -def MC(methods, kwargs, firstresult=False): +def MC( + methods: Sequence[Callable[..., object]], + kwargs: Mapping[str, object], + firstresult: bool = False, +) -> Union[object, List[object]]: caller = _multicall hookfuncs = [] for method in methods: - f = HookImpl(None, "", method, method.example_impl) + f = HookImpl(None, "", method, method.example_impl) # type: ignore[attr-defined] hookfuncs.append(f) return caller("foo", hookfuncs, kwargs, firstresult) -def test_keyword_args(): +def test_keyword_args() -> None: @hookimpl def f(x): return x + 1 @@ -31,7 +45,7 @@ def f(self, x, y): assert reslist == [24 + 23, 24] -def test_keyword_args_with_defaultargs(): +def test_keyword_args_with_defaultargs() -> None: @hookimpl def f(x, z=1): return x + z @@ -40,7 +54,7 @@ def f(x, z=1): assert reslist == [24] -def test_tags_call_error(): +def test_tags_call_error() -> None: @hookimpl def f(x): return x @@ -49,7 +63,7 @@ def f(x): MC([f], {}) -def test_call_none_is_no_result(): +def test_call_none_is_no_result() -> None: @hookimpl def m1(): return 1 @@ -60,11 +74,11 @@ def m2(): res = MC([m1, m2], {}, firstresult=True) assert res == 1 - res = MC([m1, m2], {}, {}) + res = MC([m1, m2], {}, firstresult=False) assert res == [1] -def test_hookwrapper(): +def test_hookwrapper() -> None: out = [] @hookimpl(hookwrapper=True) @@ -87,7 +101,51 @@ def m2(): assert out == ["m1 init", "m2", "m1 finish"] -def test_hookwrapper_order(): +def test_hookwrapper_two_yields() -> None: + @hookimpl(hookwrapper=True) + def m(): + yield + yield + + with pytest.raises(RuntimeError, match="has second yield"): + MC([m], {}) + + +def test_wrapper() -> None: + out = [] + + @hookimpl(wrapper=True) + def m1(): + out.append("m1 init") + result = yield + out.append("m1 finish") + return result * 2 + + @hookimpl + def m2(): + out.append("m2") + return 2 + + res = MC([m2, m1], {}) + assert res == [2, 2] + assert out == ["m1 init", "m2", "m1 finish"] + out[:] = [] + res = MC([m2, m1], {}, firstresult=True) + assert res == 4 + assert out == ["m1 init", "m2", "m1 finish"] + + +def test_wrapper_two_yields() -> None: + @hookimpl(wrapper=True) + def m(): + yield + yield + + with pytest.raises(RuntimeError, match="has second yield"): + MC([m], {}) + + +def test_hookwrapper_order() -> None: out = [] @hookimpl(hookwrapper=True) @@ -96,18 +154,40 @@ def m1(): yield 1 out.append("m1 finish") - @hookimpl(hookwrapper=True) + @hookimpl(wrapper=True) def m2(): out.append("m2 init") - yield 2 + result = yield 2 out.append("m2 finish") + return result - res = MC([m2, m1], {}) - assert res == [] - assert out == ["m1 init", "m2 init", "m2 finish", "m1 finish"] + @hookimpl(hookwrapper=True) + def m3(): + out.append("m3 init") + yield 3 + out.append("m3 finish") + @hookimpl(hookwrapper=True) + def m4(): + out.append("m4 init") + yield 4 + out.append("m4 finish") -def test_hookwrapper_not_yield(): + res = MC([m4, m3, m2, m1], {}) + assert res == [] + assert out == [ + "m1 init", + "m2 init", + "m3 init", + "m4 init", + "m4 finish", + "m3 finish", + "m2 finish", + "m1 finish", + ] + + +def test_hookwrapper_not_yield() -> None: @hookimpl(hookwrapper=True) def m1(): pass @@ -116,7 +196,17 @@ def m1(): MC([m1], {}) -def test_hookwrapper_too_many_yield(): +def test_hookwrapper_yield_not_executed() -> None: + @hookimpl(hookwrapper=True) + def m1(): + if False: + yield # type: ignore[unreachable] + + with pytest.raises(RuntimeError, match="did not yield"): + MC([m1], {}) + + +def test_hookwrapper_too_many_yield() -> None: @hookimpl(hookwrapper=True) def m1(): yield 1 @@ -128,14 +218,47 @@ def m1(): assert (__file__ + ":") in str(ex.value) +def test_wrapper_yield_not_executed() -> None: + @hookimpl(wrapper=True) + def m1(): + if False: + yield # type: ignore[unreachable] + + with pytest.raises(RuntimeError, match="did not yield"): + MC([m1], {}) + + +def test_wrapper_too_many_yield() -> None: + out = [] + + @hookimpl(wrapper=True) + def m1(): + try: + yield 1 + yield 2 + finally: + out.append("cleanup") + + with pytest.raises(RuntimeError) as ex: + try: + MC([m1], {}) + finally: + out.append("finally") + assert "m1" in str(ex.value) + assert (__file__ + ":") in str(ex.value) + assert out == ["cleanup", "finally"] + + @pytest.mark.parametrize("exc", [ValueError, SystemExit]) -def test_hookwrapper_exception(exc): +def test_hookwrapper_exception(exc: "Type[BaseException]") -> None: out = [] @hookimpl(hookwrapper=True) def m1(): out.append("m1 init") - yield None + result = yield + assert isinstance(result.exception, exc) + assert result.excinfo[0] == exc out.append("m1 finish") @hookimpl @@ -145,3 +268,187 @@ def m2(): with pytest.raises(exc): MC([m2, m1], {}) assert out == ["m1 init", "m1 finish"] + + +def test_hookwrapper_force_exception() -> None: + out = [] + + @hookimpl(hookwrapper=True) + def m1(): + out.append("m1 init") + result = yield + try: + result.get_result() + except BaseException as exc: + result.force_exception(exc) + out.append("m1 finish") + + @hookimpl(hookwrapper=True) + def m2(): + out.append("m2 init") + result = yield + try: + result.get_result() + except BaseException as exc: + new_exc = OSError("m2") + new_exc.__cause__ = exc + result.force_exception(new_exc) + out.append("m2 finish") + + @hookimpl(hookwrapper=True) + def m3(): + out.append("m3 init") + yield + out.append("m3 finish") + + @hookimpl + def m4(): + raise ValueError("m4") + + with pytest.raises(OSError, match="m2") as excinfo: + MC([m4, m3, m2, m1], {}) + assert out == [ + "m1 init", + "m2 init", + "m3 init", + "m3 finish", + "m2 finish", + "m1 finish", + ] + assert excinfo.value.__cause__ is not None + assert str(excinfo.value.__cause__) == "m4" + + +@pytest.mark.parametrize("exc", [ValueError, SystemExit]) +def test_wrapper_exception(exc: "Type[BaseException]") -> None: + out = [] + + @hookimpl(wrapper=True) + def m1(): + out.append("m1 init") + try: + result = yield + except BaseException as e: + assert isinstance(e, exc) + raise + finally: + out.append("m1 finish") + return result + + @hookimpl + def m2(): + out.append("m2 init") + raise exc + + with pytest.raises(exc): + MC([m2, m1], {}) + assert out == ["m1 init", "m2 init", "m1 finish"] + + +def test_wrapper_exception_chaining() -> None: + @hookimpl + def m1(): + raise Exception("m1") + + @hookimpl(wrapper=True) + def m2(): + try: + yield + except Exception: + raise Exception("m2") + + @hookimpl(wrapper=True) + def m3(): + yield + return 10 + + @hookimpl(wrapper=True) + def m4(): + try: + yield + except Exception as e: + raise Exception("m4") from e + + with pytest.raises(Exception) as excinfo: + MC([m1, m2, m3, m4], {}) + assert str(excinfo.value) == "m4" + assert excinfo.value.__cause__ is not None + assert str(excinfo.value.__cause__) == "m2" + assert excinfo.value.__cause__.__context__ is not None + assert str(excinfo.value.__cause__.__context__) == "m1" + + +def test_unwind_inner_wrapper_teardown_exc() -> None: + out = [] + + @hookimpl(wrapper=True) + def m1(): + out.append("m1 init") + try: + yield + out.append("m1 unreachable") + except BaseException: + out.append("m1 teardown") + raise + finally: + out.append("m1 cleanup") + + @hookimpl(wrapper=True) + def m2(): + out.append("m2 init") + yield + out.append("m2 raise") + raise ValueError() + + with pytest.raises(ValueError): + try: + MC([m2, m1], {}) + finally: + out.append("finally") + + assert out == [ + "m1 init", + "m2 init", + "m2 raise", + "m1 teardown", + "m1 cleanup", + "finally", + ] + + +def test_suppress_inner_wrapper_teardown_exc() -> None: + out = [] + + @hookimpl(wrapper=True) + def m1(): + out.append("m1 init") + result = yield + out.append("m1 finish") + return result + + @hookimpl(wrapper=True) + def m2(): + out.append("m2 init") + try: + yield + out.append("m2 unreachable") + except ValueError: + out.append("m2 suppress") + return 22 + + @hookimpl(wrapper=True) + def m3(): + out.append("m3 init") + yield + out.append("m3 raise") + raise ValueError() + + assert MC([m3, m2, m1], {}) == 22 + assert out == [ + "m1 init", + "m2 init", + "m3 init", + "m3 raise", + "m2 suppress", + "m1 finish", + ] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_pluginmanager.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_pluginmanager.py index 304a007a5894a..c4ce08f3bc39a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_pluginmanager.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_pluginmanager.py @@ -1,22 +1,25 @@ """ ``PluginManager`` unit and public API testing. """ + +import importlib.metadata +from typing import Any +from typing import List + import pytest -from pluggy import ( - PluginValidationError, - HookCallError, - HookimplMarker, - HookspecMarker, -) -from pluggy._manager import importlib_metadata +from pluggy import HookCallError +from pluggy import HookimplMarker +from pluggy import HookspecMarker +from pluggy import PluginManager +from pluggy import PluginValidationError hookspec = HookspecMarker("example") hookimpl = HookimplMarker("example") -def test_plugin_double_register(pm): +def test_plugin_double_register(pm: PluginManager) -> None: """Registering the same plugin more then once isn't allowed""" pm.register(42, name="abc") with pytest.raises(ValueError): @@ -25,7 +28,7 @@ def test_plugin_double_register(pm): pm.register(42, name="def") -def test_pm(pm): +def test_pm(pm: PluginManager) -> None: """Basic registration with objects""" class A: @@ -43,12 +46,12 @@ class A: assert pm.unregister(a1) == a1 assert not pm.is_registered(a1) - out = pm.list_name_plugin() - assert len(out) == 1 - assert out == [("hello", a2)] + out2 = pm.list_name_plugin() + assert len(out2) == 1 + assert out2 == [("hello", a2)] -def test_has_plugin(pm): +def test_has_plugin(pm: PluginManager) -> None: class A: pass @@ -58,7 +61,7 @@ class A: assert pm.has_plugin("hello") -def test_register_dynamic_attr(he_pm): +def test_register_dynamic_attr(he_pm: PluginManager) -> None: class A: def __getattr__(self, name): if name[0] != "_": @@ -70,7 +73,7 @@ def __getattr__(self, name): assert not he_pm.get_hookcallers(a) -def test_pm_name(pm): +def test_pm_name(pm: PluginManager) -> None: class A: pass @@ -78,37 +81,49 @@ class A: name = pm.register(a1, name="hello") assert name == "hello" pm.unregister(a1) - assert pm.get_plugin(a1) is None + assert pm.get_plugin("hello") is None assert not pm.is_registered(a1) assert not pm.get_plugins() name2 = pm.register(a1, name="hello") assert name2 == name pm.unregister(name="hello") - assert pm.get_plugin(a1) is None + assert pm.get_plugin("hello") is None assert not pm.is_registered(a1) assert not pm.get_plugins() -def test_set_blocked(pm): +def test_set_blocked(pm: PluginManager) -> None: class A: pass a1 = A() name = pm.register(a1) + assert name is not None assert pm.is_registered(a1) assert not pm.is_blocked(name) + assert pm.get_plugins() == {a1} + pm.set_blocked(name) assert pm.is_blocked(name) assert not pm.is_registered(a1) + assert pm.get_plugins() == set() pm.set_blocked("somename") assert pm.is_blocked("somename") assert not pm.register(A(), "somename") pm.unregister(name="somename") assert pm.is_blocked("somename") + assert pm.get_plugins() == set() + + # Unblock. + assert not pm.unblock("someothername") + assert pm.unblock("somename") + assert not pm.is_blocked("somename") + assert not pm.unblock("somename") + assert pm.register(A(), "somename") -def test_register_mismatch_method(he_pm): +def test_register_mismatch_method(he_pm: PluginManager) -> None: class hello: @hookimpl def he_method_notexists(self): @@ -122,7 +137,7 @@ def he_method_notexists(self): assert excinfo.value.plugin is plugin -def test_register_mismatch_arg(he_pm): +def test_register_mismatch_arg(he_pm: PluginManager) -> None: class hello: @hookimpl def he_method1(self, qlwkje): @@ -135,7 +150,7 @@ def he_method1(self, qlwkje): assert excinfo.value.plugin is plugin -def test_register_hookwrapper_not_a_generator_function(he_pm): +def test_register_hookwrapper_not_a_generator_function(he_pm: PluginManager) -> None: class hello: @hookimpl(hookwrapper=True) def he_method1(self): @@ -148,31 +163,51 @@ def he_method1(self): assert excinfo.value.plugin is plugin -def test_register(pm): +def test_register_both_wrapper_and_hookwrapper(he_pm: PluginManager) -> None: + class hello: + @hookimpl(wrapper=True, hookwrapper=True) + def he_method1(self): + yield # pragma: no cover + + plugin = hello() + + with pytest.raises( + PluginValidationError, match="wrapper.*hookwrapper.*mutually exclusive" + ) as excinfo: + he_pm.register(plugin) + assert excinfo.value.plugin is plugin + + +def test_register(pm: PluginManager) -> None: class MyPlugin: - pass + @hookimpl + def he_method1(self): ... my = MyPlugin() pm.register(my) - assert my in pm.get_plugins() + assert pm.get_plugins() == {my} my2 = MyPlugin() pm.register(my2) - assert {my, my2}.issubset(pm.get_plugins()) + assert pm.get_plugins() == {my, my2} assert pm.is_registered(my) assert pm.is_registered(my2) pm.unregister(my) assert not pm.is_registered(my) - assert my not in pm.get_plugins() + assert pm.get_plugins() == {my2} + with pytest.raises(AssertionError, match=r"not registered"): + pm.unregister(my) -def test_register_unknown_hooks(pm): + +def test_register_unknown_hooks(pm: PluginManager) -> None: class Plugin1: @hookimpl def he_method1(self, arg): return arg + 1 pname = pm.register(Plugin1()) + assert pname is not None class Hooks: @hookspec @@ -182,10 +217,12 @@ def he_method1(self, arg): pm.add_hookspecs(Hooks) # assert not pm._unverified_hooks assert pm.hook.he_method1(arg=1) == [2] - assert len(pm.get_hookcallers(pm.get_plugin(pname))) == 1 + hookcallers = pm.get_hookcallers(pm.get_plugin(pname)) + assert hookcallers is not None + assert len(hookcallers) == 1 -def test_register_historic(pm): +def test_register_historic(pm: PluginManager) -> None: class Hooks: @hookspec(historic=True) def he_method1(self, arg): @@ -215,21 +252,52 @@ def he_method1(self, arg): assert out == [1, 10, 120, 12] +def test_historic_with_subset_hook_caller(pm: PluginManager) -> None: + class Hooks: + @hookspec(historic=True) + def he_method1(self, arg): ... + + pm.add_hookspecs(Hooks) + + out = [] + + class Plugin: + @hookimpl + def he_method1(self, arg): + out.append(arg) + + plugin = Plugin() + pm.register(plugin) + + class Plugin2: + @hookimpl + def he_method1(self, arg): + out.append(arg * 10) + + shc = pm.subset_hook_caller("he_method1", remove_plugins=[plugin]) + shc.call_historic(kwargs=dict(arg=1)) + + pm.register(Plugin2()) + assert out == [10] + + pm.register(Plugin()) + assert out == [10, 1] + + @pytest.mark.parametrize("result_callback", [True, False]) -def test_with_result_memorized(pm, result_callback): - """Verify that ``_HookCaller._maybe_apply_history()` +def test_with_result_memorized(pm: PluginManager, result_callback: bool) -> None: + """Verify that ``HookCaller._maybe_apply_history()` correctly applies the ``result_callback`` function, when provided, to the result from calling each newly registered hook. """ out = [] - if result_callback: + if not result_callback: + callback = None + else: - def callback(res): + def callback(res) -> None: out.append(res) - else: - callback = None - class Hooks: @hookspec(historic=True) def he_method1(self, arg): @@ -259,7 +327,7 @@ def he_method1(self, arg): assert out == [] -def test_with_callbacks_immediately_executed(pm): +def test_with_callbacks_immediately_executed(pm: PluginManager) -> None: class Hooks: @hookspec(historic=True) def he_method1(self, arg): @@ -293,7 +361,7 @@ def he_method1(self, arg): assert out == [20, 10, 30] -def test_register_historic_incompat_hookwrapper(pm): +def test_register_historic_incompat_hookwrapper(pm: PluginManager) -> None: class Hooks: @hookspec(historic=True) def he_method1(self, arg): @@ -312,7 +380,24 @@ def he_method1(self, arg): pm.register(Plugin()) -def test_call_extra(pm): +def test_register_historic_incompat_wrapper(pm: PluginManager) -> None: + class Hooks: + @hookspec(historic=True) + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + + class Plugin: + @hookimpl(wrapper=True) + def he_method1(self, arg): + yield + + with pytest.raises(PluginValidationError): + pm.register(Plugin()) + + +def test_call_extra(pm: PluginManager) -> None: class Hooks: @hookspec def he_method1(self, arg): @@ -327,7 +412,7 @@ def he_method1(arg): assert out == [10] -def test_call_with_too_few_args(pm): +def test_call_with_too_few_args(pm: PluginManager) -> None: class Hooks: @hookspec def he_method1(self, arg): @@ -346,7 +431,7 @@ def he_method1(self, arg): pm.hook.he_method1() -def test_subset_hook_caller(pm): +def test_subset_hook_caller(pm: PluginManager) -> None: class Hooks: @hookspec def he_method1(self, arg): @@ -395,8 +480,10 @@ class PluginNo: pm.hook.he_method1(arg=1) assert out == [10] + assert repr(hc) == "<_SubsetHookCaller 'he_method1'>" -def test_get_hookimpls(pm): + +def test_get_hookimpls(pm: PluginManager) -> None: class Hooks: @hookspec def he_method1(self, arg): @@ -428,12 +515,61 @@ class PluginNo: assert hook_plugins == [plugin1, plugin2] -def test_add_hookspecs_nohooks(pm): +def test_get_hookcallers(pm: PluginManager) -> None: + class Hooks: + @hookspec + def he_method1(self): ... + + @hookspec + def he_method2(self): ... + + pm.add_hookspecs(Hooks) + + class Plugin1: + @hookimpl + def he_method1(self): ... + + @hookimpl + def he_method2(self): ... + + class Plugin2: + @hookimpl + def he_method1(self): ... + + class Plugin3: + @hookimpl + def he_method2(self): ... + + plugin1 = Plugin1() + pm.register(plugin1) + plugin2 = Plugin2() + pm.register(plugin2) + plugin3 = Plugin3() + pm.register(plugin3) + + hookcallers1 = pm.get_hookcallers(plugin1) + assert hookcallers1 is not None + assert len(hookcallers1) == 2 + hookcallers2 = pm.get_hookcallers(plugin2) + assert hookcallers2 is not None + assert len(hookcallers2) == 1 + hookcallers3 = pm.get_hookcallers(plugin3) + assert hookcallers3 is not None + assert len(hookcallers3) == 1 + assert hookcallers1 == hookcallers2 + hookcallers3 + + assert pm.get_hookcallers(object()) is None + + +def test_add_hookspecs_nohooks(pm: PluginManager) -> None: + class NoHooks: + pass + with pytest.raises(ValueError): - pm.add_hookspecs(10) + pm.add_hookspecs(NoHooks) -def test_load_setuptools_instantiation(monkeypatch, pm): +def test_load_setuptools_instantiation(monkeypatch, pm: PluginManager) -> None: class EntryPoint: name = "myname" group = "hello" @@ -453,23 +589,24 @@ class Distribution: def my_distributions(): return (dist,) - monkeypatch.setattr(importlib_metadata, "distributions", my_distributions) + monkeypatch.setattr(importlib.metadata, "distributions", my_distributions) num = pm.load_setuptools_entrypoints("hello") assert num == 1 plugin = pm.get_plugin("myname") + assert plugin is not None assert plugin.x == 42 ret = pm.list_plugin_distinfo() # poor man's `assert ret == [(plugin, mock.ANY)]` assert len(ret) == 1 assert len(ret[0]) == 2 assert ret[0][0] == plugin - assert ret[0][1]._dist == dist + assert ret[0][1]._dist == dist # type: ignore[comparison-overlap] num = pm.load_setuptools_entrypoints("hello") assert num == 0 # no plugin loaded by this call -def test_add_tracefuncs(he_pm): - out = [] +def test_add_tracefuncs(he_pm: PluginManager) -> None: + out: List[Any] = [] class api1: @hookimpl @@ -507,7 +644,7 @@ def after(outcome, hook_name, hook_impls, kwargs): assert len(out) == 4 + 2 -def test_hook_tracing(he_pm): +def test_hook_tracing(he_pm: PluginManager) -> None: saveindent = [] class api1: @@ -522,7 +659,7 @@ def he_method1(self): raise ValueError() he_pm.register(api1()) - out = [] + out: List[Any] = [] he_pm.trace.root.setwriter(out.append) undo = he_pm.enable_tracing() try: @@ -542,3 +679,79 @@ def he_method1(self): assert saveindent[0] > indent finally: undo() + + +@pytest.mark.parametrize("historic", [False, True]) +def test_register_while_calling( + pm: PluginManager, + historic: bool, +) -> None: + """Test that registering an impl of a hook while it is being called does + not affect the call itself, only later calls. + + For historic hooks however, the hook is called immediately on registration. + + Regression test for #438. + """ + hookspec = HookspecMarker("example") + + class Hooks: + @hookspec(historic=historic) + def configure(self) -> int: + raise NotImplementedError() + + class Plugin1: + @hookimpl + def configure(self) -> int: + return 1 + + class Plugin2: + def __init__(self) -> None: + self.already_registered = False + + @hookimpl + def configure(self) -> int: + if not self.already_registered: + pm.register(Plugin4()) + pm.register(Plugin5()) + pm.register(Plugin6()) + self.already_registered = True + return 2 + + class Plugin3: + @hookimpl + def configure(self) -> int: + return 3 + + class Plugin4: + @hookimpl(tryfirst=True) + def configure(self) -> int: + return 4 + + class Plugin5: + @hookimpl() + def configure(self) -> int: + return 5 + + class Plugin6: + @hookimpl(trylast=True) + def configure(self) -> int: + return 6 + + pm.add_hookspecs(Hooks) + pm.register(Plugin1()) + pm.register(Plugin2()) + pm.register(Plugin3()) + + if not historic: + result = pm.hook.configure() + assert result == [3, 2, 1] + result = pm.hook.configure() + assert result == [4, 5, 3, 2, 1, 6] + else: + result = [] + pm.hook.configure.call_historic(result.append) + assert result == [4, 5, 6, 3, 2, 1] + result = [] + pm.hook.configure.call_historic(result.append) + assert result == [4, 5, 3, 2, 1, 6] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_tracer.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_tracer.py index 992ec679149b5..5e538369446ab 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_tracer.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_tracer.py @@ -1,17 +1,19 @@ -from pluggy._tracing import TagTracer +from typing import List import pytest +from pluggy._tracing import TagTracer + @pytest.fixture -def rootlogger(): +def rootlogger() -> TagTracer: return TagTracer() -def test_simple(rootlogger): +def test_simple(rootlogger: TagTracer) -> None: log = rootlogger.get("pytest") log("hello") - out = [] + out: List[str] = [] rootlogger.setwriter(out.append) log("world") assert len(out) == 1 @@ -21,7 +23,7 @@ def test_simple(rootlogger): assert out[1] == "hello [pytest:collection]\n" -def test_indent(rootlogger): +def test_indent(rootlogger: TagTracer) -> None: log = rootlogger.get("1") out = [] log.root.setwriter(lambda arg: out.append(arg)) @@ -49,8 +51,7 @@ def test_indent(rootlogger): ] -def test_readable_output_dictargs(rootlogger): - +def test_readable_output_dictargs(rootlogger: TagTracer) -> None: out = rootlogger._format_message(["test"], [1]) assert out == "1 [test]\n" @@ -58,7 +59,7 @@ def test_readable_output_dictargs(rootlogger): assert out2 == "test [test]\n a: 1\n" -def test_setprocessor(rootlogger): +def test_setprocessor(rootlogger: TagTracer) -> None: log = rootlogger.get("1") log2 = log.get("2") assert log2.tags == tuple("12") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_warnings.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_warnings.py new file mode 100644 index 0000000000000..4f5454be24f55 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/testing/test_warnings.py @@ -0,0 +1,49 @@ +from pathlib import Path + +import pytest + +from pluggy import HookimplMarker +from pluggy import HookspecMarker +from pluggy import PluggyTeardownRaisedWarning +from pluggy import PluginManager + + +hookspec = HookspecMarker("example") +hookimpl = HookimplMarker("example") + + +def test_teardown_raised_warning(pm: PluginManager) -> None: + class Api: + @hookspec + def my_hook(self): + raise NotImplementedError() + + pm.add_hookspecs(Api) + + class Plugin1: + @hookimpl + def my_hook(self): + pass + + class Plugin2: + @hookimpl(hookwrapper=True) + def my_hook(self): + yield + 1 / 0 + + class Plugin3: + @hookimpl(hookwrapper=True) + def my_hook(self): + yield + + pm.register(Plugin1(), "plugin1") + pm.register(Plugin2(), "plugin2") + pm.register(Plugin3(), "plugin3") + with pytest.warns( + PluggyTeardownRaisedWarning, + match=r"\bplugin2\b.*\bmy_hook\b.*\n.*ZeroDivisionError", + ) as wc: + with pytest.raises(ZeroDivisionError): + pm.hook.my_hook() + assert len(wc.list) == 1 + assert Path(wc.list[0].filename).name == "test_warnings.py" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/tox.ini b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/tox.ini index 97b3eb7792092..de464a078f100 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/tox.ini +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pluggy/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=linting,docs,py{36,37,38,39,py3},py{36,37}-pytest{main} +envlist=docs,py{38,39,310,311,py3},py{38}-pytest{main} [testenv] commands= @@ -20,18 +20,15 @@ deps= pytest pytest-benchmark -[testenv:linting] -skip_install = true -basepython = python3 -deps = pre-commit -commands = pre-commit run --all-files --show-diff-on-failure - [testenv:docs] deps = - sphinx - pygments + -r{toxinidir}/docs/requirements.txt commands = - sphinx-build -W -b html {toxinidir}/docs {toxinidir}/build/html-docs + 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 -b html {toxinidir}/docs {toxinidir}/build/html-docs -t changelog_towncrier_draft {posargs:} [pytest] minversion=2.0 @@ -45,7 +42,7 @@ filterwarnings = max-line-length=99 [testenv:release] -decription = do a release, required posarg of the version number +description = do a release, required posarg of the version number basepython = python3 skipsdist = True usedevelop = True diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/dependencies/default/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/dependencies/default/w3c-import.log new file mode 100644 index 0000000000000..07139599127d8 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/dependencies/default/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/dependencies/default/constraints.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/dependencies/default/requirements.txt diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/dependencies/pytest-min/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/dependencies/pytest-min/w3c-import.log new file mode 100644 index 0000000000000..81d028cc11235 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/dependencies/pytest-min/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/dependencies/pytest-min/constraints.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/dependencies/pytest-min/requirements.txt diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/w3c-import.log new file mode 100644 index 0000000000000..4d044a8e6d8b1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/PKG-INFO +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/SOURCES.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/dependency_links.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/entry_points.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/requires.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/top_level.txt diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio/w3c-import.log new file mode 100644 index 0000000000000..774fc0605b206 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio/_version.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio/plugin.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio/py.typed diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/async_fixtures/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/async_fixtures/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/async_fixtures/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/async_fixtures/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/async_fixtures/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/async_fixtures/w3c-import.log new file mode 100644 index 0000000000000..45301cc84efbe --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/async_fixtures/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/async_fixtures/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/async_fixtures/test_async_fixtures.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/async_fixtures/test_async_fixtures_scope.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/async_fixtures/test_async_fixtures_with_finalizer.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/async_fixtures/test_async_gen_fixtures.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/async_fixtures/test_nested.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/async_fixtures/test_parametrized_loop.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/hypothesis/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/hypothesis/w3c-import.log new file mode 100644 index 0000000000000..af377bd0c8ca7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/hypothesis/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/hypothesis/test_base.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/hypothesis/test_inherited_test.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/loop_fixture_scope/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/loop_fixture_scope/w3c-import.log new file mode 100644 index 0000000000000..5343bc048a766 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/loop_fixture_scope/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/loop_fixture_scope/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/loop_fixture_scope/test_loop_fixture_scope.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/markers/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/markers/w3c-import.log new file mode 100644 index 0000000000000..51cd235fc0114 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/markers/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/markers/test_class_marker.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/markers/test_module_marker.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/modes/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/modes/w3c-import.log new file mode 100644 index 0000000000000..bf1929f3ddf52 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/modes/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/modes/test_auto_mode.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/modes/test_legacy_mode.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/modes/test_strict_mode.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/multiloop/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/multiloop/w3c-import.log new file mode 100644 index 0000000000000..7adc72841b9f1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/multiloop/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/multiloop/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/multiloop/test_alternative_loops.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/respect_event_loop_policy/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/respect_event_loop_policy/w3c-import.log new file mode 100644 index 0000000000000..3b62708100588 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/respect_event_loop_policy/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/respect_event_loop_policy/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/respect_event_loop_policy/test_respects_event_loop_policy.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/trio/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/trio/w3c-import.log new file mode 100644 index 0000000000000..263db2e785cc6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/trio/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/trio/test_fixtures.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/w3c-import.log new file mode 100644 index 0000000000000..79de78c9abaf3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/test_asyncio_fixture.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/test_dependent_fixtures.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/test_event_loop_scope.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/test_flaky_integration.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/test_simple.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tests/test_subprocess.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tools/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tools/w3c-import.log new file mode 100644 index 0000000000000..0311771979ee4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tools/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tools/get-version.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/w3c-import.log new file mode 100644 index 0000000000000..d33a676cfebfd --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/w3c-import.log @@ -0,0 +1,25 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/CHANGELOG.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/MANIFEST.in +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/Makefile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/PKG-INFO +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/pyproject.toml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/setup.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest-asyncio/tox.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/w3c-import.log new file mode 100644 index 0000000000000..a4a1f6a5f884e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/1_bug_report.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/2_feature_request.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/config.yml diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/dependabot.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/dependabot.yml index 507789bf5a4cd..294b13743e2d4 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/dependabot.yml +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/dependabot.yml @@ -9,3 +9,9 @@ updates: allow: - dependency-type: direct - dependency-type: indirect +- package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + time: "03:00" + open-pull-requests-limit: 10 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/w3c-import.log new file mode 100644 index 0000000000000..ad3f117057fc5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/FUNDING.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/PULL_REQUEST_TEMPLATE.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/config.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/dependabot.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/labels.toml diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/backport.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/backport.yml new file mode 100644 index 0000000000000..38ce7260278ce --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/backport.yml @@ -0,0 +1,51 @@ +name: backport + +on: + # Note that `pull_request_target` has security implications: + # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ + # In particular: + # - Only allow triggers that can be used only be trusted users + # - Don't execute any code from the target branch + # - Don't use cache + pull_request_target: + types: [labeled] + +# Set permissions at the job level. +permissions: {} + +jobs: + backport: + if: startsWith(github.event.label.name, 'backport ') && github.event.pull_request.merged + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: true + + - name: Create backport PR + run: | + set -eux + + git config --global user.name "pytest bot" + git config --global user.email "pytestbot@gmail.com" + + label='${{ github.event.label.name }}' + target_branch="${label#backport }" + backport_branch=backport-${{ github.event.number }}-to-"${target_branch}" + subject="[$target_branch] $(gh pr view --json title -q .title ${{ github.event.number }})" + + git checkout origin/"${target_branch}" -b "${backport_branch}" + git cherry-pick -x --mainline 1 ${{ github.event.pull_request.merge_commit_sha }} + git commit --amend --message "$subject" + git push --set-upstream origin --force-with-lease "${backport_branch}" + gh pr create \ + --base "${target_branch}" \ + --title "${subject}" \ + --body "Backport of PR #${{ github.event.number }} to $target_branch branch. PR created by backport workflow." + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/deploy.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/deploy.yml new file mode 100644 index 0000000000000..20a72270fde2b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/deploy.yml @@ -0,0 +1,108 @@ +name: deploy + +on: + workflow_dispatch: + inputs: + version: + description: 'Release version' + required: true + default: '1.2.3' + + +# Set permissions at the job level. +permissions: {} + +jobs: + package: + runs-on: ubuntu-latest + env: + SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }} + timeout-minutes: 10 + + # Required by attest-build-provenance-github. + permissions: + id-token: write + attestations: write + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Build and Check Package + uses: hynek/build-and-inspect-python-package@v2.5.0 + with: + attest-build-provenance-github: 'true' + + deploy: + if: github.repository == 'pytest-dev/pytest' + needs: [package] + runs-on: ubuntu-latest + environment: deploy + timeout-minutes: 30 + permissions: + id-token: write + contents: write + steps: + - uses: actions/checkout@v4 + + - name: Download Package + uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@v1.8.14 + + - name: Push tag + run: | + git config user.name "pytest bot" + git config user.email "pytestbot@gmail.com" + git tag --annotate --message=v${{ github.event.inputs.version }} ${{ github.event.inputs.version }} ${{ github.sha }} + git push origin ${{ github.event.inputs.version }} + + release-notes: + + # todo: generate the content in the build job + # the goal being of using a github action script to push the release data + # after success instead of creating a complete python/tox env + needs: [deploy] + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Download Package + uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install tox + run: | + python -m pip install --upgrade pip + pip install --upgrade tox + + - name: Generate release notes + run: | + sudo apt-get install pandoc + tox -e generate-gh-release-notes -- ${{ github.event.inputs.version }} scripts/latest-release-notes.md + + - name: Publish GitHub Release + uses: softprops/action-gh-release@v2 + with: + body_path: scripts/latest-release-notes.md + files: dist/* + tag_name: ${{ github.event.inputs.version }} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/prepare-release-pr.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/prepare-release-pr.yml index 429834b3f2149..1bb23fab84470 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/prepare-release-pr.yml +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/prepare-release-pr.yml @@ -27,12 +27,12 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: "3.8" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/stale.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/stale.yml new file mode 100644 index 0000000000000..82f9a1f257911 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/stale.yml @@ -0,0 +1,23 @@ +name: close needs-information issues +on: + schedule: + - cron: "30 1 * * *" + workflow_dispatch: + +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - uses: actions/stale@v9 + with: + debug-only: false + days-before-issue-stale: 14 + days-before-issue-close: 7 + only-labels: "status: needs information" + stale-issue-label: "stale" + stale-issue-message: "This issue is stale because it has the `status: needs information` label and requested follow-up information was not provided for 14 days." + close-issue-message: "This issue was closed because it has the `status: needs information` label and follow-up information has not been provided for 7 days since being marked as stale." + days-before-pr-stale: -1 + days-before-pr-close: -1 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/test.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/test.yml new file mode 100644 index 0000000000000..09d37aaa2c868 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/test.yml @@ -0,0 +1,229 @@ +name: test + +on: + push: + branches: + - main + - "[0-9]+.[0-9]+.x" + - "test-me-*" + tags: + - "[0-9]+.[0-9]+.[0-9]+" + - "[0-9]+.[0-9]+.[0-9]+rc[0-9]+" + + pull_request: + branches: + - main + - "[0-9]+.[0-9]+.x" + +env: + PYTEST_ADDOPTS: "--color=yes" + +# Cancel running jobs for the same workflow and branch. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +# Set permissions at the job level. +permissions: {} + +jobs: + package: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + - name: Build and Check Package + uses: hynek/build-and-inspect-python-package@v2.5.0 + + build: + needs: [package] + + runs-on: ${{ matrix.os }} + timeout-minutes: 45 + permissions: + contents: read + + strategy: + fail-fast: false + matrix: + name: [ + "windows-py38", + "windows-py38-pluggy", + "windows-py39", + "windows-py310", + "windows-py311", + "windows-py312", + "windows-py313", + + "ubuntu-py38", + "ubuntu-py38-pluggy", + "ubuntu-py38-freeze", + "ubuntu-py39", + "ubuntu-py310", + "ubuntu-py311", + "ubuntu-py312", + "ubuntu-py313", + "ubuntu-pypy3", + + "macos-py38", + "macos-py39", + "macos-py310", + "macos-py312", + "macos-py313", + + "doctesting", + "plugins", + ] + + include: + - name: "windows-py38" + python: "3.8" + os: windows-latest + tox_env: "py38-unittestextras" + use_coverage: true + - name: "windows-py38-pluggy" + python: "3.8" + os: windows-latest + tox_env: "py38-pluggymain-pylib-xdist" + - name: "windows-py39" + python: "3.9" + os: windows-latest + tox_env: "py39-xdist" + - name: "windows-py310" + python: "3.10" + os: windows-latest + tox_env: "py310-xdist" + - name: "windows-py311" + python: "3.11" + os: windows-latest + tox_env: "py311" + - name: "windows-py312" + python: "3.12" + os: windows-latest + tox_env: "py312" + - name: "windows-py313" + python: "3.13-dev" + os: windows-latest + tox_env: "py313" + + - name: "ubuntu-py38" + python: "3.8" + os: ubuntu-latest + tox_env: "py38-lsof-numpy-pexpect" + use_coverage: true + - name: "ubuntu-py38-pluggy" + python: "3.8" + os: ubuntu-latest + tox_env: "py38-pluggymain-pylib-xdist" + - name: "ubuntu-py38-freeze" + python: "3.8" + os: ubuntu-latest + tox_env: "py38-freeze" + - name: "ubuntu-py39" + python: "3.9" + os: ubuntu-latest + tox_env: "py39-xdist" + - name: "ubuntu-py310" + python: "3.10" + os: ubuntu-latest + tox_env: "py310-xdist" + - name: "ubuntu-py311" + python: "3.11" + os: ubuntu-latest + tox_env: "py311" + use_coverage: true + - name: "ubuntu-py312" + python: "3.12" + os: ubuntu-latest + tox_env: "py312" + use_coverage: true + - name: "ubuntu-py313" + python: "3.13-dev" + os: ubuntu-latest + tox_env: "py313" + use_coverage: true + - name: "ubuntu-pypy3" + python: "pypy-3.8" + os: ubuntu-latest + tox_env: "pypy3-xdist" + + - name: "macos-py38" + python: "3.8" + os: macos-latest + tox_env: "py38-xdist" + - name: "macos-py39" + python: "3.9" + os: macos-latest + tox_env: "py39-xdist" + use_coverage: true + - name: "macos-py310" + python: "3.10" + os: macos-latest + tox_env: "py310-xdist" + - name: "macos-py312" + python: "3.12" + os: macos-latest + tox_env: "py312-xdist" + - name: "macos-py313" + python: "3.13-dev" + os: macos-latest + tox_env: "py313-xdist" + + - name: "plugins" + python: "3.12" + os: ubuntu-latest + tox_env: "plugins" + + - name: "doctesting" + python: "3.8" + os: ubuntu-latest + tox_env: "doctesting" + use_coverage: true + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Download Package + uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + check-latest: ${{ endsWith(matrix.python, '-dev') }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox coverage + + - name: Test without coverage + if: "! matrix.use_coverage" + shell: bash + run: tox run -e ${{ matrix.tox_env }} --installpkg `find dist/*.tar.gz` + + - name: Test with coverage + if: "matrix.use_coverage" + shell: bash + run: tox run -e ${{ matrix.tox_env }}-coverage --installpkg `find dist/*.tar.gz` + + - name: Generate coverage report + if: "matrix.use_coverage" + run: python -m coverage xml + + - name: Upload coverage to Codecov + if: "matrix.use_coverage" + uses: codecov/codecov-action@v4 + continue-on-error: true + with: + fail_ci_if_error: true + files: ./coverage.xml + verbose: true diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/update-plugin-list.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/update-plugin-list.yml index 193469072ffaa..6943e2076083c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/update-plugin-list.yml +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/update-plugin-list.yml @@ -11,7 +11,7 @@ on: permissions: {} jobs: - createPullRequest: + update-plugin-list: if: github.repository_owner == 'pytest-dev' runs-on: ubuntu-latest permissions: @@ -20,25 +20,33 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: "3.11" + cache: pip + - name: requests-cache + uses: actions/cache@v4 + with: + path: ~/.cache/pytest-plugin-list/ + key: plugins-http-cache-${{ github.run_id }} # Can use time based key as well + restore-keys: plugins-http-cache- - name: Install dependencies run: | python -m pip install --upgrade pip - pip install packaging requests tabulate[widechars] tqdm + pip install packaging requests tabulate[widechars] tqdm requests-cache platformdirs + - name: Update Plugin List run: python scripts/update-plugin-list.py - name: Create Pull Request - uses: peter-evans/create-pull-request@2455e1596942c2902952003bbb574afbbe2ab2e6 + uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83 with: commit-message: '[automated] Update plugin list' author: 'pytest bot ' diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/w3c-import.log new file mode 100644 index 0000000000000..db9263256464f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/backport.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/deploy.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/prepare-release-pr.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/stale.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/test.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/.github/workflows/update-plugin-list.yml diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/AUTHORS b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/AUTHORS index 9413f9c2e749a..54ed85fc73212 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/AUTHORS +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/AUTHORS @@ -8,13 +8,19 @@ Abdeali JK Abdelrahman Elbehery Abhijeet Kasurde Adam Johnson +Adam Stewart Adam Uhlir Ahn Ki-Wook +Akhilesh Ramakrishnan Akiomi Kamakura Alan Velasco +Alessio Izzo +Alex Jones +Alex Lambson Alexander Johnson Alexander King Alexei Kozlenok +Alice Purcell Allan Feldman Aly Sivji Amir Elkess @@ -30,6 +36,7 @@ Andrey Paramonov Andrzej Klajnert Andrzej Ostrowski Andy Freeland +Anita Hammer Anthon van der Neut Anthony Shaw Anthony Sottile @@ -42,19 +49,28 @@ Ariel Pillemer Armin Rigo Aron Coyle Aron Curzon +Arthur Richard +Ashish Kurmi Aviral Verma Aviv Palivoda +Babak Keyvani Barney Gale +Ben Brown Ben Gartner +Ben Leith Ben Webb Benjamin Peterson +Benjamin Schubert Bernard Pratz +Bo Wu Bob Ippolito Brian Dorsey +Brian Larsen Brian Maissy Brian Okken Brianna Laugher Bruno Oliveira +Cal Jacobson Cal Leeming Carl Friedrich Bolz Carlos Jenkins @@ -62,9 +78,12 @@ Ceridwen Charles Cloud Charles Machalow Charnjit SiNGH (CCSJ) +Cheuk Ting Ho +Chris Mahoney Chris Lamb Chris NeJame Chris Rose +Chris Wheeler Christian Boelsen Christian Fetzer Christian Neumüller @@ -76,13 +95,17 @@ Christopher Dignam Christopher Gilling Claire Cecil Claudio Madotto +Clément M.T. Robert CrazyMerlyn Cristian Vera Cyrus Maden Damian Skrzypczak Daniel Grana Daniel Hahler +Daniel Miller Daniel Nuri +Daniel Sánchez Castelló +Daniel Valenzuela Zenteno Daniel Wandschneider Daniele Procida Danielle Jenkins @@ -97,6 +120,7 @@ Daw-Ran Liou Debi Mishra Denis Kirisov Denivy Braiam Rück +Dheeraj C K Dhiren Serai Diego Russo Dmitry Dygalo @@ -107,6 +131,8 @@ Edison Gustavo Muenz Edoardo Batini Edson Tadeu M. Manoel Eduardo Schettino +Edward Haigh +Eero Vaher Eli Boyarski Elizaveta Shashkova Éloi Rivard @@ -114,16 +140,24 @@ Endre Galaczi Eric Hunsberger Eric Liu Eric Siegerman +Eric Yuan Erik Aronesty +Erik Hasse Erik M. Bray Evan Kepner +Evgeny Seliverstov +Fabian Sturm Fabien Zarifian Fabio Zadrozny +faph +Felix Hofstätter Felix Nieuwenhuizen Feng Ma Florian Bruhin Florian Dahlitz Floris Bruynooghe +Fraser Stark +Gabriel Landau Gabriel Reis Garvit Shubham Gene Wood @@ -149,8 +183,12 @@ Ian Bicking Ian Lesperance Ilya Konstantinov Ionuț Turturică +Isaac Virshup +Israel Fruchter +Itxaso Aizpurua Iwan Briquemont Jaap Broekhuizen +Jake VanderPlas Jakob van Santen Jakub Mitoraj James Bourbeau @@ -162,8 +200,11 @@ Javier Romero Jeff Rackauckas Jeff Widman Jenni Rinker +Jens Tröger John Eddie Ayson +John Litborn John Towler +Jon Parise Jon Sonesen Jonas Obrist Jordan Guymon @@ -173,26 +214,32 @@ Joseph Hunkeler Josh Karpel Joshua Bronson Jurko Gospodnetić -Justyna Janczyszyn Justice Ndou +Justyna Janczyszyn Kale Kundert Kamran Ahmad +Kenny Y Karl O. Pinc Karthikeyan Singaravelan Katarzyna Jachim Katarzyna Król Katerina Koukiou Keri Volans +Kevin C Kevin Cox +Kevin Hierro Carrasco Kevin J. Foley +Kian Eliasi Kian-Meng Ang Kodi B. Arfer +Kojo Idrissa Kostis Anagnostopoulos Kristoffer Nordström Kyle Altendorf Lawrence Mitchell Lee Kamentsky Lev Maximov +Levon Saldamli Lewis Cowles Llandy Riveron Del Risco Loic Esteve @@ -203,12 +250,15 @@ Maho Maik Figura Mandeep Bhutani Manuel Krebber +Marc Mueller Marc Schlaich Marcelo Duarte Trevisani Marcin Bachry +Marc Bresson Marco Gorelli Mark Abramowitz Mark Dickinson +Marko Pacak Markus Unterwaditzer Martijn Faassen Martin Altmayer @@ -222,7 +272,6 @@ Matthias Hafner Maxim Filipenko Maximilian Cosmo Sitter mbyt -Mickey Pashov Michael Aquilina Michael Birtwell Michael Droettboom @@ -230,23 +279,30 @@ Michael Goerz Michael Krebs Michael Seifert Michal Wajszczuk +Michał Górny Michał Zięba +Mickey Pashov Mihai Capotă +Mihail Milushev Mike Hoyle (hoylemd) Mike Lundy +Milan Lesnek Miro Hrončok +mrbean-bremen Nathaniel Compton Nathaniel Waisbrot Ned Batchelder +Neil Martin Neven Mundar Nicholas Devenish Nicholas Murphy Niclas Olofsson Nicolas Delaby Nikolay Kondratyev -Olga Matoula +Nipunn Koorapati Oleg Pidsadnyi Oleg Sushchenko +Olga Matoula Oliver Bestwalter Omar Kohl Omer Hadari @@ -254,30 +310,39 @@ Ondřej Súkup Oscar Benjamin Parth Patel Patrick Hayes +Patrick Lannigan +Paul Müller +Paul Reece Pauli Virtanen Pavel Karateev Paweł Adamczak Pedro Algarvio Petter Strandmark Philipp Loose +Pierre Sassoulas Pieter Mulder Piotr Banaszkiewicz Piotr Helm +Poulami Sau Prakhar Gurunani Prashant Anand Prashant Sharma Pulkit Goyal Punyashloka Biswal Quentin Pradet +q0w Ralf Schmitt -Ram Rachum Ralph Giles +Ram Rachum Ran Benita Raphael Castaneda Raphael Pierzina +Rafal Semik Raquel Alegre Ravi Chandra +Reagan Lee Robert Holt +Roberto Aldera Roberto Polli Roland Puntaier Romain Dorgueil @@ -286,25 +351,37 @@ Ronny Pfannschmidt Ross Lawley Ruaridh Williamson Russel Winder +Russell Martin +Ryan Puddephatt Ryan Wooden +Sadra Barikbin Saiprasad Kale +Samuel Colvin Samuel Dion-Girardeau Samuel Searles-Bryant +Samuel Therrien (Avasam) Samuele Pedroni Sanket Duthade Sankt Petersbug +Saravanan Padmanaban +Sean Malloy Segev Finer Serhii Mozghovyi Seth Junot Shantanu Jain +Sharad Nair Shubham Adep +Simon Blanchard Simon Gomizelj +Simon Holesch Simon Kerr Skylar Downes Srinivas Reddy Thatiparthy +Stefaan Lippens Stefan Farmbauer Stefan Scherfke Stefan Zimmermann +Stefanie Molin Stefano Taschini Steffen Allner Stephan Obermann @@ -314,31 +391,42 @@ Tadek Teleżyński Takafumi Arakaki Taneli Hukkinen Tanvi Mehta +Tanya Agarwal Tarcisio Fischer Tareq Alayan +Tatiana Ovary Ted Xiao Terje Runde Thomas Grainger Thomas Hisch Tim Hoffmann Tim Strazny +TJ Bruno +Tobias Diez Tom Dalton Tom Viner Tomáš Gavenčiak Tomer Keren +Tony Narlock Tor Colvin Trevor Bekolay +Tushar Sadhwani Tyler Goodlet +Tyler Smart Tzu-ping Chung Vasily Kuznetsov Victor Maryama +Victor Rodriguez Victor Uriarte Vidar T. Fauske +Vijay Arora Virgil Dupras Vitaly Lashmanov +Vivaan Verma Vlad Dragos Vlad Radziuk Vladyslav Rachek +Volodymyr Kochetkov Volodymyr Piskun Wei Lin Wil Cooley @@ -348,9 +436,16 @@ Wouter van Ackooy Xixi Zhao Xuan Luong Xuecong Liao +Yannick Péroux +Yao Xiao Yoav Caspi +Yuliang Shao +Yusuke Kadowaki +Yutian Li Yuval Shimon Zac Hatfield-Dodds Zachary Kneupper +Zachary OBrien +Zhouxin Qiu Zoltán Máté Zsolt Cserna diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/CONTRIBUTING.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/CONTRIBUTING.rst index 24bca723c8bd3..d7da59c812dc8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/CONTRIBUTING.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/CONTRIBUTING.rst @@ -50,6 +50,8 @@ Fix bugs -------- Look through the `GitHub issues for bugs `_. +See also the `"good first issue" issues `_ +that are friendly to new contributors. :ref:`Talk ` 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. @@ -195,11 +197,12 @@ Short version ~~~~~~~~~~~~~ #. Fork the repository. +#. Fetch tags from upstream if necessary (if you cloned only main `git fetch --tags https://github.com/pytest-dev/pytest`). #. Enable and install `pre-commit `_ to ensure style-guides and code checks are followed. -#. Follow **PEP-8** for naming and `black `_ for formatting. +#. Follow `PEP-8 `_ for naming. #. Tests are run using ``tox``:: - tox -e linting,py37 + tox -e linting,py39 The test environments above are usually enough to cover most cases locally. @@ -221,7 +224,7 @@ changes you want to review and merge. Pull requests are stored on Once you send a pull request, we can discuss its potential modifications and even add more commits to it later on. There's an excellent tutorial on how Pull Requests work in the -`GitHub Help Center `_. +`GitHub Help Center `_. Here is a simple overview, with pytest-specific bits: @@ -234,6 +237,7 @@ Here is a simple overview, with pytest-specific bits: $ git clone git@github.com:YOUR_GITHUB_USERNAME/pytest.git $ cd pytest + $ git fetch --tags https://github.com/pytest-dev/pytest # now, create your own branch off "main": $ git checkout -b your-bugfix-branch-name main @@ -242,6 +246,11 @@ Here is a simple overview, with pytest-specific bits: be released in micro releases whereas features will be released in minor releases and incompatible changes in major releases. + You will need the tags to test locally, so be sure you have the tags from the main repository. If you suspect you don't, set the main repository as upstream and fetch the tags:: + + $ git remote add upstream https://github.com/pytest-dev/pytest + $ git fetch upstream --tags + If you need some help with Git, follow this quick start guide: https://git.wiki.kernel.org/index.php/QuickStart @@ -265,35 +274,35 @@ Here is a simple overview, with pytest-specific bits: #. Run all the tests - You need to have Python 3.7 available in your system. Now + You need to have Python 3.8 or later available in your system. Now running tests is as simple as issuing this command:: - $ tox -e linting,py37 + $ tox -e linting,py39 - This command will run tests via the "tox" tool against Python 3.7 + This command will run tests via the "tox" tool against Python 3.9 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 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 3.7 and pass options to pytest + You can pass different options to ``tox``. For example, to run tests on Python 3.9 and pass options to pytest (e.g. enter pdb on failure) to pytest you can do:: - $ tox -e py37 -- --pdb + $ tox -e py39 -- --pdb - Or to only run tests in a particular test module on Python 3.7:: + Or to only run tests in a particular test module on Python 3.9:: - $ tox -e py37 -- testing/test_config.py + $ tox -e py39 -- testing/test_config.py 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:: + an editable install with the ``dev`` extra:: $ python3 -m venv .venv $ source .venv/bin/activate # Linux $ .venv/Scripts/activate.bat # Windows - $ pip install -e ".[testing]" + $ pip install -e ".[dev]" Afterwards, you can edit the files and run pytest normally:: @@ -378,7 +387,7 @@ them. Backporting bug fixes for the next patch release ------------------------------------------------ -Pytest makes feature release every few weeks or months. In between, patch releases +Pytest makes a 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. @@ -387,10 +396,17 @@ 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 ``main`` branch, with a regular pull +#. First, make sure the bug is fixed in the ``main`` branch, with a regular pull request, as described above. An exception to this is if the bug fix is not applicable to ``main`` anymore. +Automatic method: + +Add a ``backport 1.2.x`` label to the PR you want to backport. This will create +a backport PR against the ``1.2.x`` branch. + +Manual method: + #. ``git checkout origin/1.2.x -b backport-XXXX`` # use the main PR number here #. Locate the merge commit on the PR, in the *merged* message, for example: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/README.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/README.rst index 14733765173f9..a81e082cdd7d6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/README.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/README.rst @@ -20,16 +20,13 @@ :target: https://codecov.io/gh/pytest-dev/pytest :alt: Code coverage Status -.. image:: https://github.com/pytest-dev/pytest/workflows/main/badge.svg - :target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Amain +.. image:: https://github.com/pytest-dev/pytest/actions/workflows/test.yml/badge.svg + :target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Atest .. image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest/main.svg :target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest/main :alt: pre-commit.ci status -.. 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 @@ -97,12 +94,12 @@ Features - `Modular fixtures `_ for managing small or parametrized long-lived test resources -- Can run `unittest `_ (or trial), - `nose `_ test suites out of the box +- Can run `unittest `_ (or trial) + test suites out of the box -- Python 3.6+ and PyPy3 +- Python 3.8+ or PyPy3 -- Rich plugin architecture, with over 850+ `external plugins `_ and thriving community +- Rich plugin architecture, with over 1300+ `external plugins `_ and thriving community Documentation diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/RELEASING.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/RELEASING.rst index 25ce90d0f655a..08004a84c00a8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/RELEASING.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/RELEASING.rst @@ -37,7 +37,7 @@ breaking changes or new features. For a new minor release, first create a new maintenance branch from ``main``:: - git fetch --all + git fetch upstream git branch 7.1.x upstream/main git push upstream 7.1.x @@ -63,7 +63,7 @@ Major releases 1. Create a new maintenance branch from ``main``:: - git fetch --all + git fetch upstream git branch 8.0.x upstream/main git push upstream 8.0.x @@ -133,32 +133,32 @@ 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:: +#. After all tests pass and the PR has been approved, trigger the ``deploy`` job + in https://github.com/pytest-dev/pytest/actions/workflows/deploy.yml, using the ``release-MAJOR.MINOR.PATCH`` branch + as source. - 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 + This job will require approval from ``pytest-dev/core``, after which it will publish to PyPI + and tag the repository. - Wait for the deploy to complete, then make sure it is `available on PyPI `_. - -#. Merge the PR. +#. Merge the PR. **Make sure it's not squash-merged**, so that the tagged commit ends up in the main branch. #. Cherry-pick the CHANGELOG / announce files to the ``main`` branch:: - git fetch --all --prune + git fetch upstream git checkout upstream/main -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. -#. For major and minor releases, tag the release cherry-pick merge commit in main with +#. For major and minor releases (or the first prerelease of it), tag the release cherry-pick merge commit in main with a dev tag for the next feature release:: git checkout main git pull git tag MAJOR.{MINOR+1}.0.dev0 - git push git@github.com:pytest-dev/pytest.git MAJOR.{MINOR+1}.0.dev0 + git push upstream MAJOR.{MINOR+1}.0.dev0 + +#. For major and minor releases, change the default version in the `Read the Docs Settings `_ to the new branch. #. Send an email announcement with the contents from:: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/TIDELIFT.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/TIDELIFT.rst index 2fe25841c3a44..1ba246bd86846 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/TIDELIFT.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/TIDELIFT.rst @@ -23,9 +23,9 @@ members of the `contributors team`_ interested in receiving funding. The current list of contributors receiving funding are: -* `@asottile`_ * `@nicoddemus`_ * `@The-Compiler`_ +* `@RonnyPfannschmidt`_ Contributors interested in receiving a part of the funds just need to submit a PR adding their name to the list. Contributors that want to stop receiving the funds should also submit a PR @@ -55,6 +55,6 @@ funds. Just drop a line to one of the `@pytest-dev/tidelift-admins`_ or use the .. _`@pytest-dev/tidelift-admins`: https://github.com/orgs/pytest-dev/teams/tidelift-admins/members .. _`agreement`: https://tidelift.com/docs/lifting/agreement -.. _`@asottile`: https://github.com/asottile .. _`@nicoddemus`: https://github.com/nicoddemus .. _`@The-Compiler`: https://github.com/The-Compiler +.. _`@RonnyPfannschmidt`: https://github.com/RonnyPfannschmidt diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/bench.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/bench.py index c40fc8636c0c6..437d3259d8312 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/bench.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/bench.py @@ -1,10 +1,12 @@ import sys + if __name__ == "__main__": import cProfile - import pytest # NOQA import pstats + import pytest # noqa: F401 + script = sys.argv[1:] if len(sys.argv) > 1 else ["empty.py"] cProfile.run("pytest.cmdline.main(%r)" % script, "prof") p = pstats.Stats("prof") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/bench_argcomplete.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/bench_argcomplete.py index 335733df72b43..459a12f93141b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/bench_argcomplete.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/bench_argcomplete.py @@ -4,6 +4,7 @@ # FastFilesCompleter 0.7383 1.0760 import timeit + imports = [ "from argcomplete.completers import FilesCompleter as completer", "from _pytest._argcomplete import FastFilesCompleter as completer", diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/skip.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/skip.py index f0c9d1ddbeffb..fd5c292d92c1b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/skip.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/skip.py @@ -1,5 +1,6 @@ import pytest + SKIP = True diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/unit_test.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/unit_test.py index ad52069dbfdd2..d3db111e1ae51 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/unit_test.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/unit_test.py @@ -1,5 +1,6 @@ from unittest import TestCase # noqa: F401 + for i in range(15000): exec( f""" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/w3c-import.log new file mode 100644 index 0000000000000..3ea3a2d7e9637 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/bench.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/bench_argcomplete.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/empty.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/manyparam.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/skip.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/unit_test.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/bench/xunit.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/changelog/README.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/changelog/README.rst index 6d026f57ef3ed..88956ef28d8b4 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/changelog/README.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/changelog/README.rst @@ -14,7 +14,7 @@ Each file should be named like ``..rst``, where ```` is an issue number, and ```` is one of: * ``feature``: new user facing features, like new command-line options and new behavior. -* ``improvement``: improvement of existing functionality, usually without requiring user intervention (for example, new fields being written in ``--junitxml``, improved colors in terminal, etc). +* ``improvement``: improvement of existing functionality, usually without requiring user intervention (for example, new fields being written in ``--junit-xml``, improved colors in terminal, etc). * ``bugfix``: fixes a bug. * ``doc``: documentation improvement, like rewording an entire session or adding missing docs. * ``deprecation``: feature deprecation. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/changelog/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/changelog/w3c-import.log new file mode 100644 index 0000000000000..139916aa5ee09 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/changelog/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/changelog/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/changelog/_template.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/globaltoc.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/globaltoc.html index 7c595e7ebf2a1..09d970b64ed1f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/globaltoc.html +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/globaltoc.html @@ -17,7 +17,6 @@

About the project

  • Changelog
  • Contributing
  • Backwards Compatibility
  • -
  • Python 2.7 and 3.4 Support
  • Sponsor
  • pytest for Enterprise
  • License
  • @@ -30,5 +29,3 @@

    About the project

    {%- endif %}
    -Index -
    diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/slim_searchbox.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/slim_searchbox.html index e98ad4ed905d3..f088ff8d31286 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/slim_searchbox.html +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/slim_searchbox.html @@ -5,11 +5,10 @@ - + {%- endif %} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/w3c-import.log new file mode 100644 index 0000000000000..9e84b23f2507e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/globaltoc.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/layout.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/links.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/relations.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/sidebarintro.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/_templates/slim_searchbox.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/adopt.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/adopt.rst index 13d82bf011673..b95a117debb8f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/adopt.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/adopt.rst @@ -44,7 +44,7 @@ Partner projects, sign up here! (by 22 March) 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 unittest_ tests by default, so using pytest as your testrunner may be possible from day 1. Job done, right? Progressive success might look like: @@ -62,7 +62,6 @@ 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: nose.html .. _unittest: unittest.html .. _assert: assert.html .. _pycmd: https://bitbucket.org/hpk42/pycmd/overview diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/index.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/index.rst index 9505b0b9e46eb..8a33f7fb57d14 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/index.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/index.rst @@ -6,6 +6,31 @@ Release announcements :maxdepth: 2 + release-8.2.1 + release-8.2.0 + release-8.1.2 + release-8.1.1 + release-8.1.0 + release-8.0.2 + release-8.0.1 + release-8.0.0 + release-8.0.0rc2 + release-8.0.0rc1 + release-7.4.4 + release-7.4.3 + release-7.4.2 + release-7.4.1 + release-7.4.0 + release-7.3.2 + release-7.3.1 + release-7.3.0 + release-7.2.2 + release-7.2.1 + release-7.2.0 + release-7.1.3 + release-7.1.2 + release-7.1.1 + release-7.1.0 release-7.0.1 release-7.0.0 release-7.0.0rc1 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.0.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.0.rst index ecb1a1db98893..c2a9f6da4d59f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.0.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.0.rst @@ -62,7 +62,7 @@ New Features - new "-q" option which decreases verbosity and prints a more nose/unittest-style "dot" output. -- many many more detailed improvements details +- many, many, more detailed improvements details Fixes ----------------------- @@ -109,7 +109,7 @@ Important Notes in conftest.py files. They will cause nothing special. - removed support for calling the pre-1.0 collection API of "run()" and "join" - removed reading option values from conftest.py files or env variables. - This can now be done much much better and easier through the ini-file + This can now be done much, much, better and easier through the ini-file mechanism and the "addopts" entry in particular. - removed the "disabled" attribute in test classes. Use the skipping and pytestmark mechanism to skip or xfail a test class. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.2.2.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.2.2.rst index 22ef0bc7a1665..510b35ee1d029 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.2.2.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.2.2.rst @@ -4,7 +4,7 @@ pytest-2.2.2: bug fixes pytest-2.2.2 (updated to 2.2.3 to fix packaging issues) is a minor backward-compatible release of the versatile py.test testing tool. It contains bug fixes and a few refinements particularly to reporting with -"--collectonly", see below for betails. +"--collectonly", see below for details. For general information see here: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.4.0.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.4.0.rst index 138cc89576c21..9b864329674b3 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.4.0.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.4.0.rst @@ -181,7 +181,7 @@ Bug fixes: 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. + module-level functions. Thanks Anatoly Bubenkoff. - you can specify "-q" or "-qq" to get different levels of "quieter" reporting (thanks Katarzyna Jachim) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.0.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.0.rst index c6cdcdd8a830a..fe64f1b8668af 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.0.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.0.rst @@ -83,7 +83,7 @@ holger krekel Thanks Ralph Schmitt for the precise failure example. - fix issue244 by implementing special index for parameters to only use - indices for paramentrized test ids + indices for parametrized test ids - fix issue287 by running all finalizers but saving the exception from the first failing finalizer and re-raising it so teardown will diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.0.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.0.rst index 56fbd6cc1e4d9..c00df585738b6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.0.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.0.rst @@ -73,7 +73,7 @@ holger krekel - 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 +- change XPASS colour to yellow rather than red when tests are run with -v. - fix issue473: work around mock putting an unbound method into a class diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.0.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.0.rst index 2840178a07f77..83cddb34157b7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.0.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.0.rst @@ -55,7 +55,7 @@ holger krekel 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 +- fix issue650: new option ``--doctest-ignore-import-errors`` which will turn import errors in doctests into skips. Thanks Charles Cloud for the complete PR. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.0.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.0.rst new file mode 100644 index 0000000000000..3361e1c8a328c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.0.rst @@ -0,0 +1,48 @@ +pytest-7.1.0 +======================================= + +The pytest team is proud to announce the 7.1.0 release! + +This release contains new features, improvements, and bug fixes, +the full list of changes is available in 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 of the contributors to this release: + +* Akuli +* Andrew Svetlov +* Anthony Sottile +* Brett Holman +* Bruno Oliveira +* Chris NeJame +* Dan Alvizu +* Elijah DeLee +* Emmanuel Arias +* Fabian Egli +* Florian Bruhin +* Gabor Szabo +* Hasan Ramezani +* Hugo van Kemenade +* Kian Meng, Ang +* Kojo Idrissa +* Masaru Tsuchiyama +* Olga Matoula +* P. L. Lim +* Ran Benita +* Tobias Deiminger +* Yuval Shimon +* eduardo naufel schettino +* Éric + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.1.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.1.rst new file mode 100644 index 0000000000000..d271c4557a218 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.1.rst @@ -0,0 +1,18 @@ +pytest-7.1.1 +======================================= + +pytest 7.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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.2.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.2.rst new file mode 100644 index 0000000000000..ba33cdc694b52 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.2.rst @@ -0,0 +1,23 @@ +pytest-7.1.2 +======================================= + +pytest 7.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 of the contributors to this release: + +* Anthony Sottile +* Bruno Oliveira +* Hugo van Kemenade +* Kian Eliasi +* Ran Benita +* Zac Hatfield-Dodds + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.3.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.3.rst new file mode 100644 index 0000000000000..4cb1b271704ef --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.3.rst @@ -0,0 +1,28 @@ +pytest-7.1.3 +======================================= + +pytest 7.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 of the contributors to this release: + +* Anthony Sottile +* Bruno Oliveira +* Gergely Kalmár +* Nipunn Koorapati +* Pax +* Sviatoslav Sydorenko +* Tim Hoffmann +* Tony Narlock +* Wolfremium +* Zach OBrien +* aizpurua23a + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.2.0.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.2.0.rst new file mode 100644 index 0000000000000..eca84aeb669a9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.2.0.rst @@ -0,0 +1,93 @@ +pytest-7.2.0 +======================================= + +The pytest team is proud to announce the 7.2.0 release! + +This release contains new features, improvements, and bug fixes, +the full list of changes is available in 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 of the contributors to this release: + +* Aaron Berdy +* Adam Turner +* Albert Villanova del Moral +* Alice Purcell +* Anthony Sottile +* Anton Yakutovich +* Babak Keyvani +* Brandon Chinn +* Bruno Oliveira +* Chanvin Xiao +* Cheuk Ting Ho +* Chris Wheeler +* EmptyRabbit +* Ezio Melotti +* Florian Best +* Florian Bruhin +* Fredrik Berndtsson +* Gabriel Landau +* Gergely Kalmár +* Hugo van Kemenade +* James Gerity +* John Litborn +* Jon Parise +* Kevin C +* Kian Eliasi +* MatthewFlamm +* Miro Hrončok +* Nate Meyvis +* Neil Girdhar +* Nhieuvu1802 +* Nipunn Koorapati +* Ofek Lev +* Paul Müller +* Paul Reece +* Pax +* Pete Baughman +* Peyman Salehi +* Philipp A +* Ran Benita +* Robert O'Shea +* Ronny Pfannschmidt +* Rowin +* Ruth Comer +* Samuel Colvin +* Samuel Gaist +* Sandro Tosi +* Shantanu +* Simon K +* Stephen Rosen +* Sviatoslav Sydorenko +* Tatiana Ovary +* Thierry Moisan +* Thomas Grainger +* Tim Hoffmann +* Tobias Diez +* Tony Narlock +* Vivaan Verma +* Wolfremium +* Zac Hatfield-Dodds +* Zach OBrien +* aizpurua23a +* gresm +* holesch +* itxasos23 +* johnkangw +* skhomuti +* sommersoft +* wodny +* zx.qiu + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.2.1.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.2.1.rst new file mode 100644 index 0000000000000..80ac7aff07f55 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.2.1.rst @@ -0,0 +1,25 @@ +pytest-7.2.1 +======================================= + +pytest 7.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 of the contributors to this release: + +* Anthony Sottile +* Bruno Oliveira +* Daniel Valenzuela +* Kadino +* Prerak Patel +* Ronny Pfannschmidt +* Santiago Castro +* s-padmanaban + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.2.2.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.2.2.rst new file mode 100644 index 0000000000000..b34a6ff5c1e91 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.2.2.rst @@ -0,0 +1,25 @@ +pytest-7.2.2 +======================================= + +pytest 7.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 of the contributors to this release: + +* Bruno Oliveira +* Garvit Shubham +* Mahesh Vashishtha +* Ramsey +* Ronny Pfannschmidt +* Teejay +* q0w +* vin01 + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.3.0.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.3.0.rst new file mode 100644 index 0000000000000..33258dabadeb2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.3.0.rst @@ -0,0 +1,130 @@ +pytest-7.3.0 +======================================= + +The pytest team is proud to announce the 7.3.0 release! + +This release contains new features, improvements, and bug fixes, +the full list of changes is available in 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 of the contributors to this release: + +* Aaron Berdy +* Adam Turner +* Albert Villanova del Moral +* Alessio Izzo +* Alex Hadley +* Alice Purcell +* Anthony Sottile +* Anton Yakutovich +* Ashish Kurmi +* Babak Keyvani +* Billy +* Brandon Chinn +* Bruno Oliveira +* Cal Jacobson +* Chanvin Xiao +* Cheuk Ting Ho +* Chris Wheeler +* Daniel Garcia Moreno +* Daniel Scheffler +* Daniel Valenzuela +* EmptyRabbit +* Ezio Melotti +* Felix Hofstätter +* Florian Best +* Florian Bruhin +* Fredrik Berndtsson +* Gabriel Landau +* Garvit Shubham +* Gergely Kalmár +* HTRafal +* Hugo van Kemenade +* Ilya Konstantinov +* Itxaso Aizpurua +* James Gerity +* Jay +* John Litborn +* Jon Parise +* Jouke Witteveen +* Kadino +* Kevin C +* Kian Eliasi +* Klaus Rettinghaus +* Kodi Arfer +* Mahesh Vashishtha +* Manuel Jacob +* Marko Pacak +* MatthewFlamm +* Miro Hrončok +* Nate Meyvis +* Neil Girdhar +* Nhieuvu1802 +* Nipunn Koorapati +* Ofek Lev +* Paul Kehrer +* Paul Müller +* Paul Reece +* Pax +* Pete Baughman +* Peyman Salehi +* Philipp A +* Pierre Sassoulas +* Prerak Patel +* Ramsey +* Ran Benita +* Robert O'Shea +* Ronny Pfannschmidt +* Rowin +* Ruth Comer +* Samuel Colvin +* Samuel Gaist +* Sandro Tosi +* Santiago Castro +* Shantanu +* Simon K +* Stefanie Molin +* Stephen Rosen +* Sviatoslav Sydorenko +* Tatiana Ovary +* Teejay +* Thierry Moisan +* Thomas Grainger +* Tim Hoffmann +* Tobias Diez +* Tony Narlock +* Vivaan Verma +* Wolfremium +* Yannick PÉROUX +* Yusuke Kadowaki +* Zac Hatfield-Dodds +* Zach OBrien +* aizpurua23a +* bitzge +* bluthej +* gresm +* holesch +* itxasos23 +* johnkangw +* q0w +* rdb +* s-padmanaban +* skhomuti +* sommersoft +* vin01 +* wim glenn +* wodny +* zx.qiu + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.3.1.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.3.1.rst new file mode 100644 index 0000000000000..e920fa8af532e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.3.1.rst @@ -0,0 +1,18 @@ +pytest-7.3.1 +======================================= + +pytest 7.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 of the contributors to this release: + +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.3.2.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.3.2.rst new file mode 100644 index 0000000000000..b3b112f0d8e56 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.3.2.rst @@ -0,0 +1,21 @@ +pytest-7.3.2 +======================================= + +pytest 7.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 of the contributors to this release: + +* Adam J. Stewart +* Alessio Izzo +* Bruno Oliveira +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.0.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.0.rst new file mode 100644 index 0000000000000..5a0d18267d338 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.0.rst @@ -0,0 +1,49 @@ +pytest-7.4.0 +======================================= + +The pytest team is proud to announce the 7.4.0 release! + +This release contains new features, improvements, and bug fixes, +the full list of changes is available in 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 of the contributors to this release: + +* Adam J. Stewart +* Alessio Izzo +* Alex +* Alex Lambson +* Brian Larsen +* Bruno Oliveira +* Bryan Ricker +* Chris Mahoney +* Facundo Batista +* Florian Bruhin +* Jarrett Keifer +* Kenny Y +* Miro Hrončok +* Ran Benita +* Roberto Aldera +* Ronny Pfannschmidt +* Sergey Kim +* Stefanie Molin +* Vijay Arora +* Ville Skyttä +* Zac Hatfield-Dodds +* bzoracler +* leeyueh +* nondescryptid +* theirix + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.1.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.1.rst new file mode 100644 index 0000000000000..efadcf919e8a8 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.1.rst @@ -0,0 +1,20 @@ +pytest-7.4.1 +======================================= + +pytest 7.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 of the contributors to this release: + +* Bruno Oliveira +* Florian Bruhin +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.2.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.2.rst new file mode 100644 index 0000000000000..22191e7b4f95c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.2.rst @@ -0,0 +1,18 @@ +pytest-7.4.2 +======================================= + +pytest 7.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 of the contributors to this release: + +* Bruno Oliveira + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.3.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.3.rst new file mode 100644 index 0000000000000..0f319c1e7f0d2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.3.rst @@ -0,0 +1,19 @@ +pytest-7.4.3 +======================================= + +pytest 7.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 of the contributors to this release: + +* Bruno Oliveira +* Marc Mueller + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.4.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.4.rst new file mode 100644 index 0000000000000..c9633678d2e2e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.4.rst @@ -0,0 +1,20 @@ +pytest-7.4.4 +======================================= + +pytest 7.4.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 of the contributors to this release: + +* Bruno Oliveira +* Ran Benita +* Zac Hatfield-Dodds + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.0.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.0.rst new file mode 100644 index 0000000000000..00f54fd822504 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.0.rst @@ -0,0 +1,26 @@ +pytest-8.0.0 +======================================= + +The pytest team is proud to announce the 8.0.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: + +* Bruno Oliveira +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.0rc1.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.0rc1.rst new file mode 100644 index 0000000000000..547c8cbc53baf --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.0rc1.rst @@ -0,0 +1,82 @@ +pytest-8.0.0rc1 +======================================= + +The pytest team is proud to announce the 8.0.0rc1 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: + +* Akhilesh Ramakrishnan +* Aleksandr Brodin +* Anthony Sottile +* Arthur Richard +* Avasam +* Benjamin Schubert +* Bruno Oliveira +* Carsten Grohmann +* Cheukting +* Chris Mahoney +* Christoph Anton Mitterer +* DetachHead +* Erik Hasse +* Florian Bruhin +* Fraser Stark +* Ha Pam +* Hugo van Kemenade +* Isaac Virshup +* Israel Fruchter +* Jens Tröger +* Jon Parise +* Kenny Y +* Lesnek +* Marc Mueller +* Michał Górny +* Mihail Milushev +* Milan Lesnek +* Miro Hrončok +* Patrick Lannigan +* Ran Benita +* Reagan Lee +* Ronny Pfannschmidt +* Sadra Barikbin +* Sean Malloy +* Sean Patrick Malloy +* Sharad Nair +* Simon Blanchard +* Sourabh Beniwal +* Stefaan Lippens +* Tanya Agarwal +* Thomas Grainger +* Tom Mortimer-Jones +* Tushar Sadhwani +* Tyler Smart +* Uday Kumar +* Warren Markham +* WarrenTheRabbit +* Zac Hatfield-Dodds +* Ziad Kermadi +* akhilramkee +* antosikv +* bowugit +* mickeypash +* neilmartin2000 +* pomponchik +* ryanpudd +* touilleWoman +* ubaumann + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.0rc2.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.0rc2.rst new file mode 100644 index 0000000000000..1a6444c5214a5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.0rc2.rst @@ -0,0 +1,32 @@ +pytest-8.0.0rc2 +======================================= + +The pytest team is proud to announce the 8.0.0rc2 prerelease! + +This is a prerelease, not intended for production use, but to test the upcoming features and improvements +in order to catch any major problems before the final version is released to the major public. + +We appreciate your help testing this out before the final release, making sure to report any +regressions to our issue tracker: + +https://github.com/pytest-dev/pytest/issues + +When doing so, please include the string ``[prerelease]`` in the title. + +You can upgrade from PyPI via: + + pip install pytest==8.0.0rc2 + +Users are encouraged to take a look at the CHANGELOG carefully: + + https://docs.pytest.org/en/release-8.0.0rc2/changelog.html + +Thanks to all the contributors to this release: + +* Ben Brown +* Bruno Oliveira +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.1.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.1.rst new file mode 100644 index 0000000000000..7d828e55bd98e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.1.rst @@ -0,0 +1,21 @@ +pytest-8.0.1 +======================================= + +pytest 8.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 of the contributors to this release: + +* Bruno Oliveira +* Clément Robert +* Pierre Sassoulas +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.2.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.2.rst new file mode 100644 index 0000000000000..c42159c57cff7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.2.rst @@ -0,0 +1,18 @@ +pytest-8.0.2 +======================================= + +pytest 8.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: + +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.1.0.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.1.0.rst new file mode 100644 index 0000000000000..62cafdd78bb36 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.1.0.rst @@ -0,0 +1,54 @@ +pytest-8.1.0 +======================================= + +The pytest team is proud to announce the 8.1.0 release! + +This release contains new features, improvements, and bug fixes, +the full list of changes is available in 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 of the contributors to this release: + +* Ben Brown +* Ben Leith +* Bruno Oliveira +* Clément Robert +* Dave Hall +* Dương Quốc Khánh +* Eero Vaher +* Eric Larson +* Fabian Sturm +* Faisal Fawad +* Florian Bruhin +* Franck Charras +* Joachim B Haga +* John Litborn +* Loïc Estève +* Marc Bresson +* Patrick Lannigan +* Pierre Sassoulas +* Ran Benita +* Reagan Lee +* Ronny Pfannschmidt +* Russell Martin +* clee2000 +* donghui +* faph +* jakkdl +* mrbean-bremen +* robotherapist +* whysage +* woutdenolf + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.1.1.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.1.1.rst new file mode 100644 index 0000000000000..89b617b487dcf --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.1.1.rst @@ -0,0 +1,18 @@ +pytest-8.1.1 +======================================= + +pytest 8.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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.1.2.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.1.2.rst new file mode 100644 index 0000000000000..19e41e0f7c54b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.1.2.rst @@ -0,0 +1,18 @@ +pytest-8.1.2 +======================================= + +pytest 8.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 of the contributors to this release: + +* Bruno Oliveira + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.2.0.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.2.0.rst new file mode 100644 index 0000000000000..2a63c8d872286 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.2.0.rst @@ -0,0 +1,43 @@ +pytest-8.2.0 +======================================= + +The pytest team is proud to announce the 8.2.0 release! + +This release contains new features, improvements, and bug fixes, +the full list of changes is available in 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 of the contributors to this release: + +* Bruno Oliveira +* Daniel Miller +* Florian Bruhin +* HolyMagician03-UMich +* John Litborn +* Levon Saldamli +* Linghao Zhang +* Manuel López-Ibáñez +* Pierre Sassoulas +* Ran Benita +* Ronny Pfannschmidt +* Sebastian Meyer +* Shekhar verma +* Tamir Duberstein +* Tobias Stoeckmann +* dj +* jakkdl +* poulami-sau +* tserg + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.2.1.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.2.1.rst new file mode 100644 index 0000000000000..4452edec110e5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.2.1.rst @@ -0,0 +1,19 @@ +pytest-8.2.1 +======================================= + +pytest 8.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 of the contributors to this release: + +* Bruno Oliveira +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/sprint2016.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/sprint2016.rst index 8e70658987671..8d47a205c7168 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/sprint2016.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/sprint2016.rst @@ -49,7 +49,7 @@ place on 20th, 21st, 22nd, 24th and 25th. On the 23rd we took a break day for some hot hiking in the Black Forest. Sprint activity was organised heavily around pairing, with plenty of group -discusssions to take advantage of the high bandwidth, and lightning talks +discussions to take advantage of the high bandwidth, and lightning talks as well. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/w3c-import.log new file mode 100644 index 0000000000000..5eee37fbdc44f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/w3c-import.log @@ -0,0 +1,188 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.1.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.1.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.1.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.1.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.2.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.2.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.2.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.2.4.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.4.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.5.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.4.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.4.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.4.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.4.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.5.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.6.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.7.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.9.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.9.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.9.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.4.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.5.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.6.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.7.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.10.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.10.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.4.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.5.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.3.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.3.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.3.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.4.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.4.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.4.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.5.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.5.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.4.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.4.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.8.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.8.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.8.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.0.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.0.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.0.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.1.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.1.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.2.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.2.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.3.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.3.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.4.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.4.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.4.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.5.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.4.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.5.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.6.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.7.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.8.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.9.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.0.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.0.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.4.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.4.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.5.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.0rc1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.1.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.1.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.1.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.2.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.2.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.2.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.2.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.2.4.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.2.5.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.0.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.0.0rc1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.0.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.1.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.2.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.2.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.2.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.3.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.3.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.3.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.3.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-7.4.4.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.0rc1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.0rc2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.0.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.1.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.1.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.1.2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.2.0.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-8.2.1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/announce/sprint2016.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/backwards-compatibility.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/backwards-compatibility.rst index 3a0ff12616461..e04e64a76f9a6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/backwards-compatibility.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/backwards-compatibility.rst @@ -22,7 +22,7 @@ b) transitional: the old and new API don't conflict 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). - A deprecated feature scheduled to be removed in major version X will use the warning class `PytestRemovedInXWarning` (a subclass of :class:`~pytest.PytestDeprecationwarning`). + A deprecated feature scheduled to be removed in major version X will use the warning class `PytestRemovedInXWarning` (a subclass of :class:`~pytest.PytestDeprecationWarning`). 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 `PytestRemovedInXWarning` (e.g. `PytestRemovedIn4Warning`) 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. @@ -77,3 +77,21 @@ 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 `_ and `removal `_ labels on GitHub. + + +Python version support +====================== + +Released pytest versions support all Python versions that are actively maintained at the time of the release: + +============== =================== +pytest version min. Python version +============== =================== +8.0+ 3.8+ +7.1+ 3.7+ +6.2 - 7.0 3.6+ +5.0 - 6.1 3.5+ +3.3 - 4.6 2.7, 3.4+ +============== =================== + +`Status of Python Versions `__. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/builtin.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/builtin.rst index c7e7863b2188c..458253fabbb20 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/builtin.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/builtin.rst @@ -18,11 +18,11 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a $ pytest --fixtures -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collected 0 items - cache -- .../_pytest/cacheprovider.py:510 + cache -- .../_pytest/cacheprovider.py:549 Return a cache object that can persist state between testing sessions. cache.get(key, default) @@ -33,39 +33,89 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a Values can be any object handled by the json stdlib module. - capsys -- .../_pytest/capture.py:878 - Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. - - The captured output is made available via ``capsys.readouterr()`` method - calls, which return a ``(out, err)`` namedtuple. - ``out`` and ``err`` will be ``text`` objects. - - capsysbinary -- .../_pytest/capture.py:895 + capsysbinary -- .../_pytest/capture.py:1003 Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsysbinary.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``bytes`` objects. - capfd -- .../_pytest/capture.py:912 + Returns an instance of :class:`CaptureFixture[bytes] `. + + Example: + .. code-block:: python + + def test_output(capsysbinary): + print("hello") + captured = capsysbinary.readouterr() + assert captured.out == b"hello\n" + + capfd -- .../_pytest/capture.py:1030 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. - capfdbinary -- .../_pytest/capture.py:929 + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + .. code-block:: python + + def test_system_echo(capfd): + os.system('echo "hello"') + captured = capfd.readouterr() + assert captured.out == "hello\n" + + capfdbinary -- .../_pytest/capture.py:1057 Enable bytes 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 ``byte`` objects. - doctest_namespace [session scope] -- .../_pytest/doctest.py:731 + Returns an instance of :class:`CaptureFixture[bytes] `. + + Example: + .. code-block:: python + + def test_system_echo(capfdbinary): + os.system('echo "hello"') + captured = capfdbinary.readouterr() + assert captured.out == b"hello\n" + + capsys -- .../_pytest/capture.py:976 + Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. + + The captured output is made available via ``capsys.readouterr()`` method + calls, which return a ``(out, err)`` namedtuple. + ``out`` and ``err`` will be ``text`` objects. + + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + .. code-block:: python + + def test_output(capsys): + print("hello") + captured = capsys.readouterr() + assert captured.out == "hello\n" + + doctest_namespace [session scope] -- .../_pytest/doctest.py:738 Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. - pytestconfig [session scope] -- .../_pytest/fixtures.py:1365 + Usually this fixture is used in conjunction with another ``autouse`` fixture: + + .. code-block:: python + + @pytest.fixture(autouse=True) + def add_np(doctest_namespace): + doctest_namespace["np"] = numpy + + For more details: :ref:`doctest_namespace`. + + pytestconfig [session scope] -- .../_pytest/fixtures.py:1335 Session-scoped fixture that returns the session's :class:`pytest.Config` object. @@ -75,7 +125,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a if pytestconfig.getoption("verbose") > 0: ... - record_property -- .../_pytest/junitxml.py:282 + record_property -- .../_pytest/junitxml.py:284 Add extra properties to the calling test. User properties become part of the test report and are available to the @@ -89,13 +139,13 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a def test_function(record_property): record_property("example_key", 1) - record_xml_attribute -- .../_pytest/junitxml.py:305 + record_xml_attribute -- .../_pytest/junitxml.py:307 Add extra xml attributes to the tag for the calling test. The fixture is callable with ``name, value``. The value is automatically XML-encoded. - record_testsuite_property [session scope] -- .../_pytest/junitxml.py:343 + record_testsuite_property [session scope] -- .../_pytest/junitxml.py:345 Record a new ```` tag as child of the root ````. This is suitable to writing global information regarding the entire test @@ -109,7 +159,10 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a record_testsuite_property("ARCH", "PPC") record_testsuite_property("STORAGE_TYPE", "CEPH") - ``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped. + :param name: + The property name. + :param value: + The property value. Will be converted to a string. .. warning:: @@ -117,24 +170,29 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a `pytest-xdist `__ plugin. See :issue:`7767` for details. - tmpdir_factory [session scope] -- .../_pytest/legacypath.py:295 + tmpdir_factory [session scope] -- .../_pytest/legacypath.py:303 Return a :class:`pytest.TempdirFactory` instance for the test session. - tmpdir -- .../_pytest/legacypath.py:302 + tmpdir -- .../_pytest/legacypath.py:310 Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. By default, a new base temporary directory is created each test session, and old bases are removed after 3 sessions, to aid in debugging. If - ``--basetemp`` is used then it is cleared each session. See :ref:`base - temporary directory`. + ``--basetemp`` is used then it is cleared each session. See + :ref:`temporary directory location and retention`. The returned object is a `legacy_path`_ object. + .. note:: + These days, it is preferred to use ``tmp_path``. + + :ref:`About the tmpdir and tmpdir_factory fixtures`. + .. _legacy_path: https://py.readthedocs.io/en/latest/path.html - caplog -- .../_pytest/logging.py:483 + caplog -- .../_pytest/logging.py:602 Access and control log capturing. Captured logs are available through the following properties/methods:: @@ -145,43 +203,50 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a * caplog.record_tuples -> list of (logger_name, level, message) tuples * caplog.clear() -> clear captured records and formatted log output string - monkeypatch -- .../_pytest/monkeypatch.py:29 + monkeypatch -- .../_pytest/monkeypatch.py:33 A convenient fixture for monkey-patching. - The fixture provides these methods to modify objects, dictionaries or - os.environ:: + The fixture provides these methods to modify objects, dictionaries, or + :data:`os.environ`: - 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=None) - monkeypatch.delenv(name, raising=True) - monkeypatch.syspath_prepend(path) - monkeypatch.chdir(path) + * :meth:`monkeypatch.setattr(obj, name, value, raising=True) ` + * :meth:`monkeypatch.delattr(obj, name, raising=True) ` + * :meth:`monkeypatch.setitem(mapping, name, value) ` + * :meth:`monkeypatch.delitem(obj, name, raising=True) ` + * :meth:`monkeypatch.setenv(name, value, prepend=None) ` + * :meth:`monkeypatch.delenv(name, raising=True) ` + * :meth:`monkeypatch.syspath_prepend(path) ` + * :meth:`monkeypatch.chdir(path) ` + * :meth:`monkeypatch.context() ` 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. + fixture has finished. The ``raising`` parameter determines if a :class:`KeyError` + or :class:`AttributeError` will be raised if the set/deletion operation does not have the + specified target. - recwarn -- .../_pytest/recwarn.py:29 + To undo modifications done by the fixture in a contained scope, + use :meth:`context() `. + + recwarn -- .../_pytest/recwarn.py:32 Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. - See https://docs.python.org/library/how-to/capture-warnings.html for information + See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information on warning categories. - tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:183 + tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:242 Return a :class:`pytest.TempPathFactory` instance for the test session. - tmp_path -- .../_pytest/tmpdir.py:198 + tmp_path -- .../_pytest/tmpdir.py:257 Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. By default, a new base temporary directory is created each test session, - and old bases are removed after 3 sessions, to aid in debugging. If - ``--basetemp`` is used then it is cleared each session. See :ref:`base - temporary directory`. + and old bases are removed after 3 sessions, to aid in debugging. + This behavior can be configured with :confval:`tmp_path_retention_count` and + :confval:`tmp_path_retention_policy`. + If ``--basetemp`` is used then it is cleared each session. See + :ref:`temporary directory location and retention`. The returned object is a :class:`pathlib.Path` object. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/changelog.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/changelog.rst index 1acdad366da6c..f69b9782bbc28 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/changelog.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/changelog.rst @@ -28,6 +28,1385 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 8.2.1 (2024-05-19) +========================= + +Improvements +------------ + +- `#12334 `_: Support for Python 3.13 (beta1 at the time of writing). + + + +Bug Fixes +--------- + +- `#12120 `_: Fix `PermissionError` crashes arising from directories which are not selected on the command-line. + + +- `#12191 `_: Keyboard interrupts and system exits are now properly handled during the test collection. + + +- `#12300 `_: Fixed handling of 'Function not implemented' error under squashfuse_ll, which is a different way to say that the mountpoint is read-only. + + +- `#12308 `_: Fix a regression in pytest 8.2.0 where the permissions of automatically-created ``.pytest_cache`` directories became ``rwx------`` instead of the expected ``rwxr-xr-x``. + + + +Trivial/Internal Changes +------------------------ + +- `#12333 `_: pytest releases are now attested using the recent `Artifact Attestation ` support from GitHub, allowing users to verify the provenance of pytest's sdist and wheel artifacts. + + +pytest 8.2.0 (2024-04-27) +========================= + +Breaking Changes +---------------- + +- `#12089 `_: pytest now requires that :class:`unittest.TestCase` subclasses can be instantiated freely using ``MyTestCase('runTest')``. + + If the class doesn't allow this, you may see an error during collection such as ``AttributeError: 'MyTestCase' object has no attribute 'runTest'``. + + Classes which do not override ``__init__``, or do not access the test method in ``__init__`` using ``getattr`` or similar, are unaffected. + + Classes which do should take care to not crash when ``"runTest"`` is given, as is shown in `unittest.TestCases's implementation `_. + Alternatively, consider using :meth:`setUp ` instead of ``__init__``. + + If you run into this issue using ``tornado.AsyncTestCase``, please see `issue 12263 `_. + + If you run into this issue using an abstract ``TestCase`` subclass, please see `issue 12275 `_. + + Historical note: the effect of this change on custom TestCase implementations was not properly considered initially, this is why it was done in a minor release. We apologize for the inconvenience. + +Deprecations +------------ + +- `#12069 `_: A deprecation warning is now raised when implementations of one of the following hooks request a deprecated ``py.path.local`` parameter instead of the ``pathlib.Path`` parameter which replaced it: + + - :hook:`pytest_ignore_collect` - the ``path`` parameter - use ``collection_path`` instead. + - :hook:`pytest_collect_file` - the ``path`` parameter - use ``file_path`` instead. + - :hook:`pytest_pycollect_makemodule` - the ``path`` parameter - use ``module_path`` instead. + - :hook:`pytest_report_header` - the ``startdir`` parameter - use ``start_path`` instead. + - :hook:`pytest_report_collectionfinish` - the ``startdir`` parameter - use ``start_path`` instead. + + The replacement parameters are available since pytest 7.0.0. + The old parameters will be removed in pytest 9.0.0. + + See :ref:`legacy-path-hooks-deprecated` for more details. + + + +Features +-------- + +- `#11871 `_: Added support for reading command line arguments from a file using the prefix character ``@``, like e.g.: ``pytest @tests.txt``. The file must have one argument per line. + + See :ref:`Read arguments from file ` for details. + + + +Improvements +------------ + +- `#11523 `_: :func:`pytest.importorskip` will now issue a warning if the module could be found, but raised :class:`ImportError` instead of :class:`ModuleNotFoundError`. + + The warning can be suppressed by passing ``exc_type=ImportError`` to :func:`pytest.importorskip`. + + See :ref:`import-or-skip-import-error` for details. + + +- `#11728 `_: For ``unittest``-based tests, exceptions during class cleanup (as raised by functions registered with :meth:`TestCase.addClassCleanup `) are now reported instead of silently failing. + + +- `#11777 `_: Text is no longer truncated in the ``short test summary info`` section when ``-vv`` is given. + + +- `#12112 `_: Improved namespace packages detection when :confval:`consider_namespace_packages` is enabled, covering more situations (like editable installs). + + +- `#9502 `_: Added :envvar:`PYTEST_VERSION` environment variable which is defined at the start of the pytest session and undefined afterwards. It contains the value of ``pytest.__version__``, and among other things can be used to easily check if code is running from within a pytest run. + + + +Bug Fixes +--------- + +- `#12065 `_: Fixed a regression in pytest 8.0.0 where test classes containing ``setup_method`` and tests using ``@staticmethod`` or ``@classmethod`` would crash with ``AttributeError: 'NoneType' object has no attribute 'setup_method'``. + + Now the :attr:`request.instance ` attribute of tests using ``@staticmethod`` and ``@classmethod`` is no longer ``None``, but a fresh instance of the class, like in non-static methods. + Previously it was ``None``, and all fixtures of such tests would share a single ``self``. + + +- `#12135 `_: Fixed issue where fixtures adding their finalizer multiple times to fixtures they request would cause unreliable and non-intuitive teardown ordering in some instances. + + +- `#12194 `_: Fixed a bug with ``--importmode=importlib`` and ``--doctest-modules`` where child modules did not appear as attributes in parent modules. + + +- `#1489 `_: Fixed some instances where teardown of higher-scoped fixtures was not happening in the reverse order they were initialized in. + + + +Trivial/Internal Changes +------------------------ + +- `#12069 `_: ``pluggy>=1.5.0`` is now required. + + +- `#12167 `_: :ref:`cache `: create supporting files (``CACHEDIR.TAG``, ``.gitignore``, etc.) in a temporary directory to provide atomic semantics. + + +pytest 8.1.2 (2024-04-26) +========================= + +Bug Fixes +--------- + +- `#12114 `_: Fixed error in :func:`pytest.approx` when used with `numpy` arrays and comparing with other types. + + +pytest 8.1.1 (2024-03-08) +========================= + +.. note:: + + This release is not a usual bug fix release -- it contains features and improvements, being a follow up + to ``8.1.0``, which has been yanked from PyPI. + +Features +-------- + +- `#11475 `_: Added the new :confval:`consider_namespace_packages` configuration option, defaulting to ``False``. + + If set to ``True``, pytest will attempt to identify modules that are part of `namespace packages `__ when importing modules. + + +- `#11653 `_: Added the new :confval:`verbosity_test_cases` configuration option for fine-grained control of test execution verbosity. + See :ref:`Fine-grained verbosity ` for more details. + + + +Improvements +------------ + +- `#10865 `_: :func:`pytest.warns` now validates that :func:`warnings.warn` was called with a `str` or a `Warning`. + Currently in Python it is possible to use other types, however this causes an exception when :func:`warnings.filterwarnings` is used to filter those warnings (see `CPython #103577 `__ for a discussion). + While this can be considered a bug in CPython, we decided to put guards in pytest as the error message produced without this check in place is confusing. + + +- `#11311 `_: When using ``--override-ini`` for paths in invocations without a configuration file defined, the current working directory is used + as the relative directory. + + Previously this would raise an :class:`AssertionError`. + + +- `#11475 `_: :ref:`--import-mode=importlib ` now tries to import modules using the standard import mechanism (but still without changing :py:data:`sys.path`), falling back to importing modules directly only if that fails. + + This means that installed packages will be imported under their canonical name if possible first, for example ``app.core.models``, instead of having the module name always be derived from their path (for example ``.env310.lib.site_packages.app.core.models``). + + +- `#11801 `_: Added the :func:`iter_parents() <_pytest.nodes.Node.iter_parents>` helper method on nodes. + It is similar to :func:`listchain <_pytest.nodes.Node.listchain>`, but goes from bottom to top, and returns an iterator, not a list. + + +- `#11850 `_: Added support for :data:`sys.last_exc` for post-mortem debugging on Python>=3.12. + + +- `#11962 `_: In case no other suitable candidates for configuration file are found, a ``pyproject.toml`` (even without a ``[tool.pytest.ini_options]`` table) will be considered as the configuration file and define the ``rootdir``. + + +- `#11978 `_: Add ``--log-file-mode`` option to the logging plugin, enabling appending to log-files. This option accepts either ``"w"`` or ``"a"`` and defaults to ``"w"``. + + Previously, the mode was hard-coded to be ``"w"`` which truncates the file before logging. + + +- `#12047 `_: When multiple finalizers of a fixture raise an exception, now all exceptions are reported as an exception group. + Previously, only the first exception was reported. + + + +Bug Fixes +--------- + +- `#11475 `_: Fixed regression where ``--importmode=importlib`` would import non-test modules more than once. + + +- `#11904 `_: Fixed a regression in pytest 8.0.0 that would cause test collection to fail due to permission errors when using ``--pyargs``. + + This change improves the collection tree for tests specified using ``--pyargs``, see :pull:`12043` for a comparison with pytest 8.0 and <8. + + +- `#12011 `_: Fixed a regression in 8.0.1 whereby ``setup_module`` xunit-style fixtures are not executed when ``--doctest-modules`` is passed. + + +- `#12014 `_: Fix the ``stacklevel`` used when warning about marks used on fixtures. + + +- `#12039 `_: Fixed a regression in ``8.0.2`` where tests created using :fixture:`tmp_path` have been collected multiple times in CI under Windows. + + +Improved Documentation +---------------------- + +- `#11790 `_: Documented the retention of temporary directories created using the ``tmp_path`` fixture in more detail. + + + +Trivial/Internal Changes +------------------------ + +- `#11785 `_: Some changes were made to private functions which may affect plugins which access them: + + - ``FixtureManager._getautousenames()`` now takes a ``Node`` itself instead of the nodeid. + - ``FixtureManager.getfixturedefs()`` now takes the ``Node`` itself instead of the nodeid. + - The ``_pytest.nodes.iterparentnodeids()`` function is removed without replacement. + Prefer to traverse the node hierarchy itself instead. + If you really need to, copy the function from the previous pytest release. + + +- `#12069 `_: Delayed the deprecation of the following features to ``9.0.0``: + + * :ref:`node-ctor-fspath-deprecation`. + * :ref:`legacy-path-hooks-deprecated`. + + It was discovered after ``8.1.0`` was released that the warnings about the impeding removal were not being displayed, so the team decided to revert the removal. + + This is the reason for ``8.1.0`` being yanked. + + +pytest 8.1.0 (YANKED) +===================== + + +.. note:: + + This release has been **yanked**: it broke some plugins without the proper warning period, due to + some warnings not showing up as expected. + + See `#12069 `__. + + +pytest 8.0.2 (2024-02-24) +========================= + +Bug Fixes +--------- + +- `#11895 `_: Fix collection on Windows where initial paths contain the short version of a path (for example ``c:\PROGRA~1\tests``). + + +- `#11953 `_: Fix an ``IndexError`` crash raising from ``getstatementrange_ast``. + + +- `#12021 `_: Reverted a fix to `--maxfail` handling in pytest 8.0.0 because it caused a regression in pytest-xdist whereby session fixture teardowns may get executed multiple times when the max-fails is reached. + + +pytest 8.0.1 (2024-02-16) +========================= + +Bug Fixes +--------- + +- `#11875 `_: Correctly handle errors from :func:`getpass.getuser` in Python 3.13. + + +- `#11879 `_: Fix an edge case where ``ExceptionInfo._stringify_exception`` could crash :func:`pytest.raises`. + + +- `#11906 `_: Fix regression with :func:`pytest.warns` using custom warning subclasses which have more than one parameter in their `__init__`. + + +- `#11907 `_: Fix a regression in pytest 8.0.0 whereby calling :func:`pytest.skip` and similar control-flow exceptions within a :func:`pytest.warns()` block would get suppressed instead of propagating. + + +- `#11929 `_: Fix a regression in pytest 8.0.0 whereby autouse fixtures defined in a module get ignored by the doctests in the module. + + +- `#11937 `_: Fix a regression in pytest 8.0.0 whereby items would be collected in reverse order in some circumstances. + + +pytest 8.0.0 (2024-01-27) +========================= + +Bug Fixes +--------- + +- `#11842 `_: Properly escape the ``reason`` of a :ref:`skip ` mark when writing JUnit XML files. + + +- `#11861 `_: Avoid microsecond exceeds ``1_000_000`` when using ``log-date-format`` with ``%f`` specifier, which might cause the test suite to crash. + + +pytest 8.0.0rc2 (2024-01-17) +============================ + + +Improvements +------------ + +- `#11233 `_: Improvements to ``-r`` for xfailures and xpasses: + + * Report tracebacks for xfailures when ``-rx`` is set. + * Report captured output for xpasses when ``-rX`` is set. + * For xpasses, add ``-`` in summary between test name and reason, to match how xfail is displayed. + +- `#11825 `_: The :hook:`pytest_plugin_registered` hook has a new ``plugin_name`` parameter containing the name by which ``plugin`` is registered. + + +Bug Fixes +--------- + +- `#11706 `_: Fix reporting of teardown errors in higher-scoped fixtures when using `--maxfail` or `--stepwise`. + + NOTE: This change was reverted in pytest 8.0.2 to fix a `regression `_ it caused in pytest-xdist. + + +- `#11758 `_: Fixed ``IndexError: string index out of range`` crash in ``if highlighted[-1] == "\n" and source[-1] != "\n"``. + This bug was introduced in pytest 8.0.0rc1. + + +- `#9765 `_, `#11816 `_: Fixed a frustrating bug that afflicted some users with the only error being ``assert mod not in mods``. The issue was caused by the fact that ``str(Path(mod))`` and ``mod.__file__`` don't necessarily produce the same string, and was being erroneously used interchangably in some places in the code. + + This fix also broke the internal API of ``PytestPluginManager.consider_conftest`` by introducing a new parameter -- we mention this in case it is being used by external code, even if marked as *private*. + + +pytest 8.0.0rc1 (2023-12-30) +============================ + +Breaking Changes +---------------- + +Old Deprecations Are Now Errors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- `#7363 `_: **PytestRemovedIn8Warning deprecation warnings are now errors by default.** + + Following our plan to remove deprecated features with as little disruption as + possible, all warnings of type ``PytestRemovedIn8Warning`` now generate errors + instead of warning messages by default. + + **The affected features will be effectively removed in pytest 8.1**, so please consult the + :ref:`deprecations` section in the docs for directions on how to update existing code. + + In the pytest ``8.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.PytestRemovedIn8Warning + + But this will stop working when pytest ``8.1`` is released. + + **If you have concerns** about the removal of a specific feature, please add a + comment to :issue:`7363`. + + +Version Compatibility +^^^^^^^^^^^^^^^^^^^^^ + +- `#11151 `_: Dropped support for Python 3.7, which `reached end-of-life on 2023-06-27 `__. + + +- ``pluggy>=1.3.0`` is now required. + + +Collection Changes +^^^^^^^^^^^^^^^^^^ + +In this version we've made several breaking changes to pytest's collection phase, +particularly around how filesystem directories and Python packages are collected, +fixing deficiencies and allowing for cleanups and improvements to pytest's internals. +A deprecation period for these changes was not possible. + + +- `#7777 `_: Files and directories are now collected in alphabetical order jointly, unless changed by a plugin. + Previously, files were collected before directories. + See below for an example. + + +- `#8976 `_: Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only. + Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself + (unless :confval:`python_files` was changed to allow `__init__.py` file). + + To collect the entire package, specify just the directory: `pytest pkg`. + + +- `#11137 `_: :class:`pytest.Package` is no longer a :class:`pytest.Module` or :class:`pytest.File`. + + The ``Package`` collector node designates a Python package, that is, a directory with an `__init__.py` file. + Previously ``Package`` was a subtype of ``pytest.Module`` (which represents a single Python module), + the module being the `__init__.py` file. + This has been deemed a design mistake (see :issue:`11137` and :issue:`7777` for details). + + The ``path`` property of ``Package`` nodes now points to the package directory instead of the ``__init__.py`` file. + + Note that a ``Module`` node for ``__init__.py`` (which is not a ``Package``) may still exist, + if it is picked up during collection (e.g. if you configured :confval:`python_files` to include ``__init__.py`` files). + + +- `#7777 `_: Added a new :class:`pytest.Directory` base collection node, which all collector nodes for filesystem directories are expected to subclass. + This is analogous to the existing :class:`pytest.File` for file nodes. + + Changed :class:`pytest.Package` to be a subclass of :class:`pytest.Directory`. + A ``Package`` represents a filesystem directory which is a Python package, + i.e. contains an ``__init__.py`` file. + + :class:`pytest.Package` now only collects files in its own directory; previously it collected recursively. + Sub-directories are collected as their own collector nodes, which then collect themselves, thus creating a collection tree which mirrors the filesystem hierarchy. + + Added a new :class:`pytest.Dir` concrete collection node, a subclass of :class:`pytest.Directory`. + This node represents a filesystem directory, which is not a :class:`pytest.Package`, + that is, does not contain an ``__init__.py`` file. + Similarly to ``Package``, it only collects the files in its own directory. + + :class:`pytest.Session` now only collects the initial arguments, without recursing into directories. + This work is now done by the :func:`recursive expansion process ` of directory collector nodes. + + :attr:`session.name ` is now ``""``; previously it was the rootdir directory name. + This matches :attr:`session.nodeid <_pytest.nodes.Node.nodeid>` which has always been `""`. + + The collection tree now contains directories/packages up to the :ref:`rootdir `, + for initial arguments that are found within the rootdir. + For files outside the rootdir, only the immediate directory/package is collected -- + note however that collecting from outside the rootdir is discouraged. + + As an example, given the following filesystem tree:: + + myroot/ + pytest.ini + top/ + ├── aaa + │ └── test_aaa.py + ├── test_a.py + ├── test_b + │ ├── __init__.py + │ └── test_b.py + ├── test_c.py + └── zzz + ├── __init__.py + └── test_zzz.py + + the collection tree, as shown by `pytest --collect-only top/` but with the otherwise-hidden :class:`~pytest.Session` node added for clarity, + is now the following:: + + + + + + + + + + + + + + + + + + + Previously, it was:: + + + + + + + + + + + + + + + + Code/plugins which rely on a specific shape of the collection tree might need to update. + + +- `#11676 `_: The classes :class:`~_pytest.nodes.Node`, :class:`~pytest.Collector`, :class:`~pytest.Item`, :class:`~pytest.File`, :class:`~_pytest.nodes.FSCollector` are now marked abstract (see :mod:`abc`). + + We do not expect this change to affect users and plugin authors, it will only cause errors when the code is already wrong or problematic. + + +Other breaking changes +^^^^^^^^^^^^^^^^^^^^^^ + +These are breaking changes where deprecation was not possible. + + +- `#11282 `_: Sanitized the handling of the ``default`` parameter when defining configuration options. + + Previously if ``default`` was not supplied for :meth:`parser.addini ` and the configuration option value was not defined in a test session, then calls to :func:`config.getini ` returned an *empty list* or an *empty string* depending on whether ``type`` was supplied or not respectively, which is clearly incorrect. Also, ``None`` was not honored even if ``default=None`` was used explicitly while defining the option. + + Now the behavior of :meth:`parser.addini ` is as follows: + + * If ``default`` is NOT passed but ``type`` is provided, then a type-specific default will be returned. For example ``type=bool`` will return ``False``, ``type=str`` will return ``""``, etc. + * If ``default=None`` is passed and the option is not defined in a test session, then ``None`` will be returned, regardless of the ``type``. + * If neither ``default`` nor ``type`` are provided, assume ``type=str`` and return ``""`` as default (this is as per previous behavior). + + The team decided to not introduce a deprecation period for this change, as doing so would be complicated both in terms of communicating this to the community as well as implementing it, and also because the team believes this change should not break existing plugins except in rare cases. + + +- `#11667 `_: pytest's ``setup.py`` file is removed. + If you relied on this file, e.g. to install pytest using ``setup.py install``, + please see `Why you shouldn't invoke setup.py directly `_ for alternatives. + + +- `#9288 `_: :func:`~pytest.warns` now re-emits unmatched warnings when the context + closes -- previously it would consume all warnings, hiding those that were not + matched by the function. + + While this is a new feature, we announce it as a breaking change + because many test suites are configured to error-out on warnings, and will + therefore fail on the newly-re-emitted warnings. + + +- The internal ``FixtureManager.getfixtureclosure`` method has changed. Plugins which use this method or + which subclass ``FixtureManager`` and overwrite that method will need to adapt to the change. + + + +Deprecations +------------ + +- `#10465 `_: Test functions returning a value other than ``None`` will now issue a :class:`pytest.PytestWarning` instead of ``pytest.PytestRemovedIn8Warning``, meaning this will stay a warning instead of becoming an error in the future. + + +- `#3664 `_: Applying a mark to a fixture function now issues a warning: marks in fixtures never had any effect, but it is a common user error to apply a mark to a fixture (for example ``usefixtures``) and expect it to work. + + This will become an error in pytest 9.0. + + + +Features and Improvements +------------------------- + +Improved Diffs +^^^^^^^^^^^^^^ + +These changes improve the diffs that pytest prints when an assertion fails. +Note that syntax highlighting requires the ``pygments`` package. + + +- `#11520 `_: The very verbose (``-vv``) diff output is now colored as a diff instead of a big chunk of red. + + Python code in error reports is now syntax-highlighted as Python. + + The sections in the error reports are now better separated. + + +- `#1531 `_: The very verbose diff (``-vv``) for every standard library container type is improved. The indentation is now consistent and the markers are on their own separate lines, which should reduce the diffs shown to users. + + Previously, the standard Python pretty printer was used to generate the output, which puts opening and closing + markers on the same line as the first/last entry, in addition to not having consistent indentation. + + +- `#10617 `_: Added more comprehensive set assertion rewrites for comparisons other than equality ``==``, with + the following operations now providing better failure messages: ``!=``, ``<=``, ``>=``, ``<``, and ``>``. + + +Separate Control For Assertion Verbosity +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- `#11387 `_: Added the new :confval:`verbosity_assertions` configuration option for fine-grained control of failed assertions verbosity. + + If you've ever wished that pytest always show you full diffs, but without making everything else verbose, this is for you. + + See :ref:`Fine-grained verbosity ` for more details. + + For plugin authors, :attr:`config.get_verbosity ` can be used to retrieve the verbosity level for a specific verbosity type. + + +Additional Support For Exception Groups and ``__notes__`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +These changes improve pytest's support for exception groups. + + +- `#10441 `_: Added :func:`ExceptionInfo.group_contains() `, an assertion helper that tests if an :class:`ExceptionGroup` contains a matching exception. + + See :ref:`assert-matching-exception-groups` for an example. + + +- `#11227 `_: Allow :func:`pytest.raises` ``match`` argument to match against `PEP-678 ` ``__notes__``. + + +Custom Directory collectors +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- `#7777 `_: Added a new hook :hook:`pytest_collect_directory`, + which is called by filesystem-traversing collector nodes, + such as :class:`pytest.Session`, :class:`pytest.Dir` and :class:`pytest.Package`, + to create a collector node for a sub-directory. + It is expected to return a subclass of :class:`pytest.Directory`. + This hook allows plugins to :ref:`customize the collection of directories `. + + +"New-style" Hook Wrappers +^^^^^^^^^^^^^^^^^^^^^^^^^ + +- `#11122 `_: pytest now uses "new-style" hook wrappers internally, available since pluggy 1.2.0. + See `pluggy's 1.2.0 changelog `_ and the :ref:`updated docs ` for details. + + Plugins which want to use new-style wrappers can do so if they require ``pytest>=8``. + + +Other Improvements +^^^^^^^^^^^^^^^^^^ + +- `#11216 `_: If a test is skipped from inside an :ref:`xunit setup fixture `, the test summary now shows the test location instead of the fixture location. + + +- `#11314 `_: Logging to a file using the ``--log-file`` option will use ``--log-level``, ``--log-format`` and ``--log-date-format`` as fallback + if ``--log-file-level``, ``--log-file-format`` and ``--log-file-date-format`` are not provided respectively. + + +- `#11610 `_: Added the :func:`LogCaptureFixture.filtering() ` context manager which + adds a given :class:`logging.Filter` object to the :fixture:`caplog` fixture. + + +- `#11447 `_: :func:`pytest.deprecated_call` now also considers warnings of type :class:`FutureWarning`. + + +- `#11600 `_: Improved the documentation and type signature for :func:`pytest.mark.xfail `'s ``condition`` param to use ``False`` as the default value. + + +- `#7469 `_: :class:`~pytest.FixtureDef` is now exported as ``pytest.FixtureDef`` for typing purposes. + + +- `#11353 `_: Added typing to :class:`~pytest.PytestPluginManager`. + + +Bug Fixes +--------- + +- `#10701 `_: :meth:`pytest.WarningsRecorder.pop` will return the most-closely-matched warning in the list, + rather than the first warning which is an instance of the requested type. + + +- `#11255 `_: Fixed crash on `parametrize(..., scope="package")` without a package present. + + +- `#11277 `_: Fixed a bug that when there are multiple fixtures for an indirect parameter, + the scope of the highest-scope fixture is picked for the parameter set, instead of that of the one with the narrowest scope. + + +- `#11456 `_: Parametrized tests now *really do* ensure that the ids given to each input are unique - for + example, ``a, a, a0`` now results in ``a1, a2, a0`` instead of the previous (buggy) ``a0, a1, a0``. + This necessarily means changing nodeids where these were previously colliding, and for + readability adds an underscore when non-unique ids end in a number. + + +- `#11563 `_: Fixed a crash when using an empty string for the same parametrized value more than once. + + +- `#11712 `_: Fixed handling ``NO_COLOR`` and ``FORCE_COLOR`` to ignore an empty value. + + +- `#9036 `_: ``pytest.warns`` and similar functions now capture warnings when an exception is raised inside a ``with`` block. + + + +Improved Documentation +---------------------- + +- `#11011 `_: Added a warning about modifying the root logger during tests when using ``caplog``. + + +- `#11065 `_: Use ``pytestconfig`` instead of ``request.config`` in cache example to be consistent with the API documentation. + + +Trivial/Internal Changes +------------------------ + +- `#11208 `_: The (internal) ``FixtureDef.cached_result`` type has changed. + Now the third item ``cached_result[2]``, when set, is an exception instance instead of an exception triplet. + + +- `#11218 `_: (This entry is meant to assist plugins which access private pytest internals to instantiate ``FixtureRequest`` objects.) + + :class:`~pytest.FixtureRequest` is now an abstract class which can't be instantiated directly. + A new concrete ``TopRequest`` subclass of ``FixtureRequest`` has been added for the ``request`` fixture in test functions, + as counterpart to the existing ``SubRequest`` subclass for the ``request`` fixture in fixture functions. + + +- `#11315 `_: The :fixture:`pytester` fixture now uses the :fixture:`monkeypatch` fixture to manage the current working directory. + If you use ``pytester`` in combination with :func:`monkeypatch.undo() `, the CWD might get restored. + Use :func:`monkeypatch.context() ` instead. + + +- `#11333 `_: Corrected the spelling of ``Config.ArgsSource.INVOCATION_DIR``. + The previous spelling ``INCOVATION_DIR`` remains as an alias. + + +- `#11638 `_: Fixed the selftests to pass correctly if ``FORCE_COLOR``, ``NO_COLOR`` or ``PY_COLORS`` is set in the calling environment. + +pytest 7.4.4 (2023-12-31) +========================= + +Bug Fixes +--------- + +- `#11140 `_: Fix non-string constants at the top of file being detected as docstrings on Python>=3.8. + + +- `#11572 `_: Handle an edge case where :data:`sys.stderr` and :data:`sys.__stderr__` might already be closed when :ref:`faulthandler` is tearing down. + + +- `#11710 `_: Fixed tracebacks from collection errors not getting pruned. + + +- `#7966 `_: Removed unhelpful error message from assertion rewrite mechanism when exceptions are raised in ``__iter__`` methods. Now they are treated un-iterable instead. + + + +Improved Documentation +---------------------- + +- `#11091 `_: Updated documentation to refer to hyphenated options: replaced ``--junitxml`` with ``--junit-xml`` and ``--collectonly`` with ``--collect-only``. + + +pytest 7.4.3 (2023-10-24) +========================= + +Bug Fixes +--------- + +- `#10447 `_: Markers are now considered in the reverse mro order to ensure base class markers are considered first -- this resolves a regression. + + +- `#11239 `_: Fixed ``:=`` in asserts impacting unrelated test cases. + + +- `#11439 `_: Handled an edge case where :data:`sys.stderr` might already be closed when :ref:`faulthandler` is tearing down. + + +pytest 7.4.2 (2023-09-07) +========================= + +Bug Fixes +--------- + +- `#11237 `_: Fix doctest collection of `functools.cached_property` objects. + + +- `#11306 `_: Fixed bug using ``--importmode=importlib`` which would cause package ``__init__.py`` files to be imported more than once in some cases. + + +- `#11367 `_: Fixed bug where `user_properties` where not being saved in the JUnit XML file if a fixture failed during teardown. + + +- `#11394 `_: Fixed crash when parsing long command line arguments that might be interpreted as files. + + + +Improved Documentation +---------------------- + +- `#11391 `_: Improved disclaimer on pytest plugin reference page to better indicate this is an automated, non-curated listing. + + +pytest 7.4.1 (2023-09-02) +========================= + +Bug Fixes +--------- + +- `#10337 `_: Fixed bug where fake intermediate modules generated by ``--import-mode=importlib`` would not include the + child modules as attributes of the parent modules. + + +- `#10702 `_: Fixed error assertion handling in :func:`pytest.approx` when ``None`` is an expected or received value when comparing dictionaries. + + +- `#10811 `_: Fixed issue when using ``--import-mode=importlib`` together with ``--doctest-modules`` that caused modules + to be imported more than once, causing problems with modules that have import side effects. + + +pytest 7.4.0 (2023-06-23) +========================= + +Features +-------- + +- `#10901 `_: Added :func:`ExceptionInfo.from_exception() `, a simpler way to create an :class:`~pytest.ExceptionInfo` from an exception. + This can replace :func:`ExceptionInfo.from_exc_info() ` for most uses. + + + +Improvements +------------ + +- `#10872 `_: Update test log report annotation to named tuple and fixed inconsistency in docs for :hook:`pytest_report_teststatus` hook. + + +- `#10907 `_: When an exception traceback to be displayed is completely filtered out (by mechanisms such as ``__tracebackhide__``, internal frames, and similar), now only the exception string and the following message are shown: + + "All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames.". + + Previously, the last frame of the traceback was shown, even though it was hidden. + + +- `#10940 `_: Improved verbose output (``-vv``) of ``skip`` and ``xfail`` reasons by performing text wrapping while leaving a clear margin for progress output. + + Added ``TerminalReporter.wrap_write()`` as a helper for that. + + +- `#10991 `_: Added handling of ``%f`` directive to print microseconds in log format options, such as ``log-date-format``. + + +- `#11005 `_: Added the underlying exception to the cache provider's path creation and write warning messages. + + +- `#11013 `_: Added warning when :confval:`testpaths` is set, but paths are not found by glob. In this case, pytest will fall back to searching from the current directory. + + +- `#11043 `_: When `--confcutdir` is not specified, and there is no config file present, the conftest cutoff directory (`--confcutdir`) is now set to the :ref:`rootdir `. + Previously in such cases, `conftest.py` files would be probed all the way to the root directory of the filesystem. + If you are badly affected by this change, consider adding an empty config file to your desired cutoff directory, or explicitly set `--confcutdir`. + + +- `#11081 `_: The :confval:`norecursedirs` check is now performed in a :hook:`pytest_ignore_collect` implementation, so plugins can affect it. + + If after updating to this version you see that your `norecursedirs` setting is not being respected, + it means that a conftest or a plugin you use has a bad `pytest_ignore_collect` implementation. + Most likely, your hook returns `False` for paths it does not want to ignore, + which ends the processing and doesn't allow other plugins, including pytest itself, to ignore the path. + The fix is to return `None` instead of `False` for paths your hook doesn't want to ignore. + + +- `#8711 `_: :func:`caplog.set_level() ` and :func:`caplog.at_level() ` + will temporarily enable the requested ``level`` if ``level`` was disabled globally via + ``logging.disable(LEVEL)``. + + + +Bug Fixes +--------- + +- `#10831 `_: Terminal Reporting: Fixed bug when running in ``--tb=line`` mode where ``pytest.fail(pytrace=False)`` tests report ``None``. + + +- `#11068 `_: Fixed the ``--last-failed`` whole-file skipping functionality ("skipped N files") for :ref:`non-python test files `. + + +- `#11104 `_: Fixed a regression in pytest 7.3.2 which caused to :confval:`testpaths` to be considered for loading initial conftests, + even when it was not utilized (e.g. when explicit paths were given on the command line). + Now the ``testpaths`` are only considered when they are in use. + + +- `#1904 `_: Fixed traceback entries hidden with ``__tracebackhide__ = True`` still being shown for chained exceptions (parts after "... the above exception ..." message). + + +- `#7781 `_: Fix writing non-encodable text to log file when using ``--debug``. + + + +Improved Documentation +---------------------- + +- `#9146 `_: Improved documentation for :func:`caplog.set_level() `. + + + +Trivial/Internal Changes +------------------------ + +- `#11031 `_: Enhanced the CLI flag for ``-c`` to now include ``--config-file`` to make it clear that this flag applies to the usage of a custom config file. + + +pytest 7.3.2 (2023-06-10) +========================= + +Bug Fixes +--------- + +- `#10169 `_: Fix bug where very long option names could cause pytest to break with ``OSError: [Errno 36] File name too long`` on some systems. + + +- `#10894 `_: Support for Python 3.12 (beta at the time of writing). + + +- `#10987 `_: :confval:`testpaths` is now honored to load root ``conftests``. + + +- `#10999 `_: The `monkeypatch` `setitem`/`delitem` type annotations now allow `TypedDict` arguments. + + +- `#11028 `_: Fixed bug in assertion rewriting where a variable assigned with the walrus operator could not be used later in a function call. + + +- `#11054 `_: Fixed ``--last-failed``'s "(skipped N files)" functionality for files inside of packages (directories with `__init__.py` files). + + +pytest 7.3.1 (2023-04-14) +========================= + +Improvements +------------ + +- `#10875 `_: Python 3.12 support: fixed ``RuntimeError: TestResult has no addDuration method`` when running ``unittest`` tests. + + +- `#10890 `_: Python 3.12 support: fixed ``shutil.rmtree(onerror=...)`` deprecation warning when using :fixture:`tmp_path`. + + + +Bug Fixes +--------- + +- `#10896 `_: Fixed performance regression related to :fixture:`tmp_path` and the new :confval:`tmp_path_retention_policy` option. + + +- `#10903 `_: Fix crash ``INTERNALERROR IndexError: list index out of range`` which happens when displaying an exception where all entries are hidden. + This reverts the change "Correctly handle ``__tracebackhide__`` for chained exceptions." introduced in version 7.3.0. + + +pytest 7.3.0 (2023-04-08) +========================= + +Features +-------- + +- `#10525 `_: Test methods decorated with ``@classmethod`` can now be discovered as tests, following the same rules as normal methods. This fills the gap that static methods were discoverable as tests but not class methods. + + +- `#10755 `_: :confval:`console_output_style` now supports ``progress-even-when-capture-no`` to force the use of the progress output even when capture is disabled. This is useful in large test suites where capture may have significant performance impact. + + +- `#7431 `_: ``--log-disable`` CLI option added to disable individual loggers. + + +- `#8141 `_: Added :confval:`tmp_path_retention_count` and :confval:`tmp_path_retention_policy` configuration options to control how directories created by the :fixture:`tmp_path` fixture are kept. + + + +Improvements +------------ + +- `#10226 `_: If multiple errors are raised in teardown, we now re-raise an ``ExceptionGroup`` of them instead of discarding all but the last. + + +- `#10658 `_: Allow ``-p`` arguments to include spaces (eg: ``-p no:logging`` instead of + ``-pno:logging``). Mostly useful in the ``addopts`` section of the configuration + file. + + +- `#10710 `_: Added ``start`` and ``stop`` timestamps to ``TestReport`` objects. + + +- `#10727 `_: Split the report header for ``rootdir``, ``config file`` and ``testpaths`` so each has its own line. + + +- `#10840 `_: pytest should no longer crash on AST with pathological position attributes, for example testing AST produced by `Hylang __`. + + +- `#6267 `_: The full output of a test is no longer truncated if the truncation message would be longer than + the hidden text. The line number shown has also been fixed. + + + +Bug Fixes +--------- + +- `#10743 `_: The assertion rewriting mechanism now works correctly when assertion expressions contain the walrus operator. + + +- `#10765 `_: Fixed :fixture:`tmp_path` fixture always raising :class:`OSError` on ``emscripten`` platform due to missing :func:`os.getuid`. + + +- `#1904 `_: Correctly handle ``__tracebackhide__`` for chained exceptions. + NOTE: This change was reverted in version 7.3.1. + + + +Improved Documentation +---------------------- + +- `#10782 `_: Fixed the minimal example in :ref:`goodpractices`: ``pip install -e .`` requires a ``version`` entry in ``pyproject.toml`` to run successfully. + + + +Trivial/Internal Changes +------------------------ + +- `#10669 `_: pytest no longer directly depends on the `attrs `__ package. While + we at pytest all love the package dearly and would like to thank the ``attrs`` team for many years of cooperation and support, + it makes sense for ``pytest`` to have as little external dependencies as possible, as this helps downstream projects. + With that in mind, we have replaced the pytest's limited internal usage to use the standard library's ``dataclasses`` instead. + + Nice diffs for ``attrs`` classes are still supported though. + + +pytest 7.2.2 (2023-03-03) +========================= + +Bug Fixes +--------- + +- `#10533 `_: Fixed :func:`pytest.approx` handling of dictionaries containing one or more values of `0.0`. + + +- `#10592 `_: Fixed crash if `--cache-show` and `--help` are passed at the same time. + + +- `#10597 `_: Fixed bug where a fixture method named ``teardown`` would be called as part of ``nose`` teardown stage. + + +- `#10626 `_: Fixed crash if ``--fixtures`` and ``--help`` are passed at the same time. + + +- `#10660 `_: Fixed :py:func:`pytest.raises` to return a 'ContextManager' so that type-checkers could narrow + :code:`pytest.raises(...) if ... else nullcontext()` down to 'ContextManager' rather than 'object'. + + + +Improved Documentation +---------------------- + +- `#10690 `_: Added `CI` and `BUILD_NUMBER` environment variables to the documentation. + + +- `#10721 `_: Fixed entry-points declaration in the documentation example using Hatch. + + +- `#10753 `_: Changed wording of the module level skip to be very explicit + about not collecting tests and not executing the rest of the module. + + +pytest 7.2.1 (2023-01-13) +========================= + +Bug Fixes +--------- + +- `#10452 `_: Fix 'importlib.abc.TraversableResources' deprecation warning in Python 3.12. + + +- `#10457 `_: If a test is skipped from inside a fixture, the test summary now shows the test location instead of the fixture location. + + +- `#10506 `_: Fix bug where sometimes pytest would use the file system root directory as :ref:`rootdir ` on Windows. + + +- `#10607 `_: Fix a race condition when creating junitxml reports, which could occur when multiple instances of pytest execute in parallel. + + +- `#10641 `_: Fix a race condition when creating or updating the stepwise plugin's cache, which could occur when multiple xdist worker nodes try to simultaneously update the stepwise plugin's cache. + + +pytest 7.2.0 (2022-10-23) +========================= + +Deprecations +------------ + +- `#10012 `_: Update :class:`pytest.PytestUnhandledCoroutineWarning` to a deprecation; it will raise an error in pytest 8. + + +- `#10396 `_: pytest no longer depends on the ``py`` library. ``pytest`` provides a vendored copy of ``py.error`` and ``py.path`` modules but will use the ``py`` library if it is installed. If you need other ``py.*`` modules, continue to install the deprecated ``py`` library separately, otherwise it can usually be removed as a dependency. + + +- `#4562 `_: Deprecate configuring hook specs/impls using attributes/marks. + + Instead use :py:func:`pytest.hookimpl` and :py:func:`pytest.hookspec`. + For more details, see the :ref:`docs `. + + +- `#9886 `_: The functionality for running tests written for ``nose`` has been officially deprecated. + + This includes: + + * Plain ``setup`` and ``teardown`` functions and methods: this might catch users by surprise, as ``setup()`` and ``teardown()`` are not pytest idioms, but part of the ``nose`` support. + * Setup/teardown using the `@with_setup `_ decorator. + + For more details, consult the :ref:`deprecation docs `. + + .. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup + +- `#7337 `_: A deprecation warning is now emitted if a test function returns something other than `None`. This prevents a common mistake among beginners that expect that returning a `bool` (for example `return foo(a, b) == result`) would cause a test to pass or fail, instead of using `assert`. The plan is to make returning non-`None` from tests an error in the future. + + +Features +-------- + +- `#9897 `_: Added shell-style wildcard support to ``testpaths``. + + + +Improvements +------------ + +- `#10218 `_: ``@pytest.mark.parametrize()`` (and similar functions) now accepts any ``Sequence[str]`` for the argument names, + instead of just ``list[str]`` and ``tuple[str, ...]``. + + (Note that ``str``, which is itself a ``Sequence[str]``, is still treated as a + comma-delimited name list, as before). + + +- `#10381 `_: The ``--no-showlocals`` flag has been added. This can be passed directly to tests to override ``--showlocals`` declared through ``addopts``. + + +- `#3426 `_: Assertion failures with strings in NFC and NFD forms that normalize to the same string now have a dedicated error message detailing the issue, and their utf-8 representation is expressed instead. + + +- `#8508 `_: Introduce multiline display for warning matching via :py:func:`pytest.warns` and + enhance match comparison for :py:func:`pytest.ExceptionInfo.match` as returned by :py:func:`pytest.raises`. + + +- `#8646 `_: Improve :py:func:`pytest.raises`. Previously passing an empty tuple would give a confusing + error. We now raise immediately with a more helpful message. + + +- `#9741 `_: On Python 3.11, use the standard library's :mod:`tomllib` to parse TOML. + + `tomli` is no longer a dependency on Python 3.11. + + +- `#9742 `_: Display assertion message without escaped newline characters with ``-vv``. + + +- `#9823 `_: Improved error message that is shown when no collector is found for a given file. + + +- `#9873 `_: Some coloring has been added to the short test summary. + + +- `#9883 `_: Normalize the help description of all command-line options. + + +- `#9920 `_: Display full crash messages in ``short test summary info``, when running in a CI environment. + + +- `#9987 `_: Added support for hidden configuration file by allowing ``.pytest.ini`` as an alternative to ``pytest.ini``. + + + +Bug Fixes +--------- + +- `#10150 `_: :data:`sys.stdin` now contains all expected methods of a file-like object when capture is enabled. + + +- `#10382 `_: Do not break into pdb when ``raise unittest.SkipTest()`` appears top-level in a file. + + +- `#7792 `_: Marks are now inherited according to the full MRO in test classes. Previously, if a test class inherited from two or more classes, only marks from the first super-class would apply. + + When inheriting marks from super-classes, marks from the sub-classes are now ordered before marks from the super-classes, in MRO order. Previously it was the reverse. + + When inheriting marks from super-classes, the `pytestmark` attribute of the sub-class now only contains the marks directly applied to it. Previously, it also contained marks from its super-classes. Please note that this attribute should not normally be accessed directly; use :func:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` instead. + + +- `#9159 `_: Showing inner exceptions by forcing native display in ``ExceptionGroups`` even when using display options other than ``--tb=native``. A temporary step before full implementation of pytest-native display for inner exceptions in ``ExceptionGroups``. + + +- `#9877 `_: Ensure ``caplog.get_records(when)`` returns current/correct data after invoking ``caplog.clear()``. + + + +Improved Documentation +---------------------- + +- `#10344 `_: Update information on writing plugins to use ``pyproject.toml`` instead of ``setup.py``. + + +- `#9248 `_: The documentation is now built using Sphinx 5.x (up from 3.x previously). + + +- `#9291 `_: Update documentation on how :func:`pytest.warns` affects :class:`DeprecationWarning`. + + + +Trivial/Internal Changes +------------------------ + +- `#10313 `_: Made ``_pytest.doctest.DoctestItem`` export ``pytest.DoctestItem`` for + type check and runtime purposes. Made `_pytest.doctest` use internal APIs + to avoid circular imports. + + +- `#9906 `_: Made ``_pytest.compat`` re-export ``importlib_metadata`` in the eyes of type checkers. + + +- `#9910 `_: Fix default encoding warning (``EncodingWarning``) in ``cacheprovider`` + + +- `#9984 `_: Improve the error message when we attempt to access a fixture that has been + torn down. + Add an additional sentence to the docstring explaining when it's not a good + idea to call ``getfixturevalue``. + + +pytest 7.1.3 (2022-08-31) +========================= + +Bug Fixes +--------- + +- `#10060 `_: When running with ``--pdb``, ``TestCase.tearDown`` is no longer called for tests when the *class* has been skipped via ``unittest.skip`` or ``pytest.mark.skip``. + + +- `#10190 `_: Invalid XML characters in setup or teardown error messages are now properly escaped for JUnit XML reports. + + +- `#10230 `_: Ignore ``.py`` files created by ``pyproject.toml``-based editable builds introduced in `pip 21.3 `__. + + +- `#3396 `_: Doctests now respect the ``--import-mode`` flag. + + +- `#9514 `_: Type-annotate ``FixtureRequest.param`` as ``Any`` as a stop gap measure until :issue:`8073` is fixed. + + +- `#9791 `_: Fixed a path handling code in ``rewrite.py`` that seems to work fine, but was incorrect and fails in some systems. + + +- `#9917 `_: Fixed string representation for :func:`pytest.approx` when used to compare tuples. + + + +Improved Documentation +---------------------- + +- `#9937 `_: Explicit note that :fixture:`tmpdir` fixture is discouraged in favour of :fixture:`tmp_path`. + + + +Trivial/Internal Changes +------------------------ + +- `#10114 `_: Replace `atomicwrites `__ dependency on windows with `os.replace`. + + +pytest 7.1.2 (2022-04-23) +========================= + +Bug Fixes +--------- + +- `#9726 `_: An unnecessary ``numpy`` import inside :func:`pytest.approx` was removed. + + +- `#9820 `_: Fix comparison of ``dataclasses`` with ``InitVar``. + + +- `#9869 `_: Increase ``stacklevel`` for the ``NODE_CTOR_FSPATH_ARG`` deprecation to point to the + user's code, not pytest. + + +- `#9871 `_: Fix a bizarre (and fortunately rare) bug where the `temp_path` fixture could raise + an internal error while attempting to get the current user's username. + + +pytest 7.1.1 (2022-03-17) +========================= + +Bug Fixes +--------- + +- `#9767 `_: Fixed a regression in pytest 7.1.0 where some conftest.py files outside of the source tree (e.g. in the `site-packages` directory) were not picked up. + + +pytest 7.1.0 (2022-03-13) +========================= + +Breaking Changes +---------------- + +- `#8838 `_: As per our policy, the following features have been deprecated in the 6.X series and are now + removed: + + * ``pytest._fillfuncargs`` function. + + * ``pytest_warning_captured`` hook - use ``pytest_warning_recorded`` instead. + + * ``-k -foobar`` syntax - use ``-k 'not foobar'`` instead. + + * ``-k foobar:`` syntax. + + * ``pytest.collect`` module - import from ``pytest`` directly. + + For more information consult + `Deprecations and Removals `__ in the docs. + + +- `#9437 `_: Dropped support for Python 3.6, which reached `end-of-life `__ at 2021-12-23. + + + +Improvements +------------ + +- `#5192 `_: Fixed test output for some data types where ``-v`` would show less information. + + Also, when showing diffs for sequences, ``-q`` would produce full diffs instead of the expected diff. + + +- `#9362 `_: pytest now avoids specialized assert formatting when it is detected that the default ``__eq__`` is overridden in ``attrs`` or ``dataclasses``. + + +- `#9536 `_: When ``-vv`` is given on command line, show skipping and xfail reasons in full instead of truncating them to fit the terminal width. + + +- `#9644 `_: More information about the location of resources that led Python to raise :class:`ResourceWarning` can now + be obtained by enabling :mod:`tracemalloc`. + + See :ref:`resource-warnings` for more information. + + +- `#9678 `_: More types are now accepted in the ``ids`` argument to ``@pytest.mark.parametrize``. + Previously only `str`, `float`, `int` and `bool` were accepted; + now `bytes`, `complex`, `re.Pattern`, `Enum` and anything with a `__name__` are also accepted. + + +- `#9692 `_: :func:`pytest.approx` now raises a :class:`TypeError` when given an unordered sequence (such as :class:`set`). + + Note that this implies that custom classes which only implement ``__iter__`` and ``__len__`` are no longer supported as they don't guarantee order. + + + +Bug Fixes +--------- + +- `#8242 `_: The deprecation of raising :class:`unittest.SkipTest` to skip collection of + tests during the pytest collection phase is reverted - this is now a supported + feature again. + + +- `#9493 `_: Symbolic link components are no longer resolved in conftest paths. + This means that if a conftest appears twice in collection tree, using symlinks, it will be executed twice. + For example, given + + tests/real/conftest.py + tests/real/test_it.py + tests/link -> tests/real + + running ``pytest tests`` now imports the conftest twice, once as ``tests/real/conftest.py`` and once as ``tests/link/conftest.py``. + This is a fix to match a similar change made to test collection itself in pytest 6.0 (see :pull:`6523` for details). + + +- `#9626 `_: Fixed count of selected tests on terminal collection summary when there were errors or skipped modules. + + If there were errors or skipped modules on collection, pytest would mistakenly subtract those from the selected count. + + +- `#9645 `_: Fixed regression where ``--import-mode=importlib`` used together with :envvar:`PYTHONPATH` or :confval:`pythonpath` would cause import errors in test suites. + + +- `#9708 `_: :fixture:`pytester` now requests a :fixture:`monkeypatch` fixture instead of creating one internally. This solves some issues with tests that involve pytest environment variables. + + +- `#9730 `_: Malformed ``pyproject.toml`` files now produce a clearer error message. + + pytest 7.0.1 (2022-02-11) ========================= @@ -64,7 +1443,7 @@ Deprecations ``__init__`` method, they should take ``**kwargs``. See :ref:`uncooperative-constructors-deprecated` for details. - Note that a deprection warning is only emitted when there is a conflict in the + Note that a deprecation warning is only emitted when there is a conflict in the arguments pytest expected to pass. This deprecation was already part of pytest 7.0.0rc1 but wasn't documented. @@ -76,7 +1455,7 @@ Bug Fixes - `#9355 `_: Fixed error message prints function decorators when using assert in Python 3.8 and above. -- `#9396 `_: Ensure :attr:`pytest.Config.inifile` is available during the :func:`pytest_cmdline_main <_pytest.hookspec.pytest_cmdline_main>` hook (regression during ``7.0.0rc1``). +- `#9396 `_: Ensure `pytest.Config.inifile` is available during the :hook:`pytest_cmdline_main` hook (regression during ``7.0.0rc1``). @@ -106,7 +1485,7 @@ Breaking Changes - `#7259 `_: The :ref:`Node.reportinfo() ` function first return value type has been expanded from `py.path.local | str` to `os.PathLike[str] | str`. Most plugins which refer to `reportinfo()` only define it as part of a custom :class:`pytest.Item` implementation. - Since `py.path.local` is a `os.PathLike[str]`, these plugins are unaffacted. + Since `py.path.local` is an `os.PathLike[str]`, these plugins are unaffected. Plugins and users which call `reportinfo()`, use the first return value and interact with it as a `py.path.local`, would need to adjust by calling `py.path.local(fspath)`. Although preferably, avoid the legacy `py.path.local` and use `pathlib.Path`, or use `item.location` or `item.path`, instead. @@ -211,6 +1590,8 @@ Deprecations :class:`unittest.SkipTest` / :meth:`unittest.TestCase.skipTest` / :func:`unittest.skip` in unittest test cases is fully supported. + .. note:: This deprecation has been reverted in pytest 7.1.0. + - `#8315 `_: Several behaviors of :meth:`Parser.addoption ` are now scheduled for removal in pytest 8 (deprecated since pytest 2.4.0): @@ -219,13 +1600,13 @@ Deprecations - ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead. -- `#8447 `_: Defining a custom pytest node type which is both an :class:`pytest.Item ` and a :class:`pytest.Collector ` (e.g. :class:`pytest.File `) now issues a warning. +- `#8447 `_: Defining a custom pytest node type which is both an :class:`~pytest.Item` and a :class:`~pytest.Collector` (e.g. :class:`~pytest.File`) now issues a warning. It was never sanely supported and triggers hard to debug errors. See :ref:`the deprecation note ` for full details. -- `#8592 `_: :hook:`pytest_cmdline_preparse` has been officially deprecated. It will be removed in a future release. Use :hook:`pytest_load_initial_conftests` instead. +- `#8592 `_: ``pytest_cmdline_preparse`` has been officially deprecated. It will be removed in a future release. Use :hook:`pytest_load_initial_conftests` instead. See :ref:`the deprecation note ` for full details. @@ -261,7 +1642,7 @@ Features - `#7132 `_: Added two environment variables :envvar:`PYTEST_THEME` and :envvar:`PYTEST_THEME_MODE` to let the users customize the pygments theme used. -- `#7259 `_: Added :meth:`cache.mkdir() `, which is similar to the existing :meth:`cache.makedir() `, +- `#7259 `_: Added :meth:`cache.mkdir() `, which is similar to the existing ``cache.makedir()``, but returns a :class:`pathlib.Path` instead of a legacy ``py.path.local``. Added a ``paths`` type to :meth:`parser.addini() `, @@ -287,7 +1668,7 @@ Features - ``pytest.HookRecorder`` for the :class:`HookRecorder ` type returned from :class:`~pytest.Pytester`. - ``pytest.RecordedHookCall`` for the :class:`RecordedHookCall ` type returned from :class:`~pytest.HookRecorder`. - ``pytest.RunResult`` for the :class:`RunResult ` type returned from :class:`~pytest.Pytester`. - - ``pytest.LineMatcher`` for the :class:`LineMatcher ` type used in :class:`~pytest.RunResult` and others. + - ``pytest.LineMatcher`` for the :class:`LineMatcher ` type used in :class:`~pytest.RunResult` and others. - ``pytest.TestReport`` for the :class:`TestReport ` type used in various hooks. - ``pytest.CollectReport`` for the :class:`CollectReport ` type used in various hooks. @@ -320,7 +1701,7 @@ Features - `#8251 `_: Implement ``Node.path`` as a ``pathlib.Path``. Both the old ``fspath`` and this new attribute gets set no matter whether ``path`` or ``fspath`` (deprecated) is passed to the constructor. It is a replacement for the ``fspath`` attribute (which represents the same path as ``py.path.local``). While ``fspath`` is not deprecated yet - due to the ongoing migration of methods like :meth:`~_pytest.Item.reportinfo`, we expect to deprecate it in a future release. + due to the ongoing migration of methods like :meth:`~pytest.Item.reportinfo`, we expect to deprecate it in a future release. .. note:: The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the @@ -352,7 +1733,7 @@ Features See :ref:`plugin-stash` for details. -- `#8953 `_: :class:`RunResult <_pytest.pytester.RunResult>` method :meth:`assert_outcomes <_pytest.pytester.RunResult.assert_outcomes>` now accepts a +- `#8953 `_: :class:`~pytest.RunResult` method :meth:`~pytest.RunResult.assert_outcomes` now accepts a ``warnings`` argument to assert the total number of warnings captured. @@ -364,7 +1745,7 @@ Features used. -- `#9113 `_: :class:`RunResult <_pytest.pytester.RunResult>` method :meth:`assert_outcomes <_pytest.pytester.RunResult.assert_outcomes>` now accepts a +- `#9113 `_: :class:`~pytest.RunResult` method :meth:`~pytest.RunResult.assert_outcomes` now accepts a ``deselected`` argument to assert the total number of deselected tests. @@ -377,7 +1758,7 @@ Improvements - `#7480 `_: A deprecation scheduled to be removed in a major version X (e.g. pytest 7, 8, 9, ...) now uses warning category `PytestRemovedInXWarning`, a subclass of :class:`~pytest.PytestDeprecationWarning`, - instead of :class:`PytestDeprecationWarning` directly. + instead of :class:`~pytest.PytestDeprecationWarning` directly. See :ref:`backwards-compatibility` for more details. @@ -416,7 +1797,7 @@ Improvements - `#8803 `_: It is now possible to add colors to custom log levels on cli log. - By using :func:`add_color_level <_pytest.logging.add_color_level>` from a ``pytest_configure`` hook, colors can be added:: + By using ``add_color_level`` from a :hook:`pytest_configure` hook, colors can be added:: logging_plugin = config.pluginmanager.get_plugin('logging-plugin') logging_plugin.log_cli_handler.formatter.add_color_level(logging.INFO, 'cyan') @@ -481,7 +1862,7 @@ Bug Fixes - `#8503 `_: :meth:`pytest.MonkeyPatch.syspath_prepend` no longer fails when ``setuptools`` is not installed. - It now only calls :func:`pkg_resources.fixup_namespace_packages` if + It now only calls ``pkg_resources.fixup_namespace_packages`` if ``pkg_resources`` was previously imported, because it is not needed otherwise. @@ -612,7 +1993,7 @@ Bug Fixes the ``tmp_path``/``tmpdir`` fixture). Now the directories are created with private permissions. - pytest used to silently use a pre-existing ``/tmp/pytest-of-`` directory, + pytest used to silently use a preexisting ``/tmp/pytest-of-`` directory, even if owned by another user. This means another user could pre-create such a directory and gain control of another user's temporary directory. Now such a condition results in an error. @@ -708,7 +2089,7 @@ Features This is part of the movement to use :class:`pathlib.Path` objects internally, in order to remove the dependency to ``py`` in the future. - Internally, the old :class:`Testdir <_pytest.pytester.Testdir>` is now a thin wrapper around :class:`Pytester <_pytest.pytester.Pytester>`, preserving the old interface. + Internally, the old ``pytest.Testdir`` is now a thin wrapper around :class:`~pytest.Pytester`, preserving the old interface. - :issue:`7695`: A new hook was added, `pytest_markeval_namespace` which should return a dictionary. @@ -746,7 +2127,7 @@ Features Improvements ------------ -- :issue:`1265`: Added an ``__str__`` implementation to the :class:`~pytest.pytester.LineMatcher` class which is returned from ``pytester.run_pytest().stdout`` and similar. It returns the entire output, like the existing ``str()`` method. +- :issue:`1265`: Added an ``__str__`` implementation to the :class:`~pytest.LineMatcher` class which is returned from ``pytester.run_pytest().stdout`` and similar. It returns the entire output, like the existing ``str()`` method. - :issue:`2044`: Verbose mode now shows the reason that a test was skipped in the test's terminal line after the "SKIPPED", "XFAIL" or "XPASS". @@ -810,7 +2191,7 @@ Bug Fixes - :issue:`7911`: Directories created by by :fixture:`tmp_path` and :fixture:`tmpdir` are now considered stale after 3 days without modification (previous value was 3 hours) to avoid deleting directories still in use in long running test suites. -- :issue:`7913`: Fixed a crash or hang in :meth:`pytester.spawn <_pytest.pytester.Pytester.spawn>` when the :mod:`readline` module is involved. +- :issue:`7913`: Fixed a crash or hang in :meth:`pytester.spawn ` when the :mod:`readline` module is involved. - :issue:`7951`: Fixed handling of recursive symlinks when collecting tests. @@ -927,7 +2308,7 @@ Deprecations if you use this and want a replacement. -- :issue:`7255`: The :hook:`pytest_warning_captured` hook is deprecated in favor +- :issue:`7255`: The ``pytest_warning_captured`` hook is deprecated in favor of :hook:`pytest_warning_recorded`, and will be removed in a future version. @@ -955,8 +2336,8 @@ Improvements - :issue:`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. -- :issue:`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, +- :issue:`7685`: Added two new attributes :attr:`rootpath ` and :attr:`inipath ` to :class:`~pytest.Config`. + These attributes are :class:`pathlib.Path` versions of the existing ``rootdir`` and ``inifile`` attributes, and should be preferred over them when possible. @@ -1027,7 +2408,7 @@ Trivial/Internal Changes - :issue:`7587`: The dependency on the ``more-itertools`` package has been removed. -- :issue:`7631`: The result type of :meth:`capfd.readouterr() <_pytest.capture.CaptureFixture.readouterr>` (and similar) is no longer a namedtuple, +- :issue:`7631`: The result type of :meth:`capfd.readouterr() ` (and similar) is no longer a namedtuple, but should behave like one in all respects. This was done for technical reasons. @@ -1339,7 +2720,7 @@ Features 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 + ``--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. @@ -1356,7 +2737,7 @@ Improvements ------------ - :issue:`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 + is printed to stderr when the output of ``pytest`` is piped and the pipe is closed by the piped-to program (common examples are ``less`` and ``head``). @@ -1405,10 +2786,10 @@ Improvements - :issue:`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`. -- :issue:`7133`: :meth:`caplog.set_level() <_pytest.logging.LogCaptureFixture.set_level>` will now override any :confval:`log_level` set via the CLI or configuration file. +- :issue:`7133`: :meth:`caplog.set_level() ` will now override any :confval:`log_level` set via the CLI or configuration file. -- :issue:`7159`: :meth:`caplog.set_level() <_pytest.logging.LogCaptureFixture.set_level>` and :meth:`caplog.at_level() <_pytest.logging.LogCaptureFixture.at_level>` no longer affect +- :issue:`7159`: :meth:`caplog.set_level() ` and :meth:`caplog.at_level() ` no longer affect the level of logs that are shown in the *Captured log report* report section. @@ -1503,7 +2884,7 @@ Bug Fixes parameter when Python is called with the ``-bb`` flag. -- :issue:`7143`: Fix :meth:`pytest.File.from_parent` so it forwards extra keyword arguments to the constructor. +- :issue:`7143`: Fix :meth:`pytest.File.from_parent <_pytest.nodes.Node.from_parent>` so it forwards extra keyword arguments to the constructor. - :issue:`7145`: Classes with broken ``__getattribute__`` methods are displayed correctly during failures. @@ -1658,7 +3039,7 @@ Breaking Changes This hook has been marked as deprecated and not been even called by pytest for over 10 years now. -- :issue:`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. +- :issue:`6673`: Reversed / fix meaning of "+/-" in error diffs. "-" means that something expected is missing in the result and "+" means that there are unexpected extras in the result. - :issue:`6737`: The ``cached_result`` attribute of ``FixtureDef`` is now set to ``None`` when @@ -1754,7 +3135,7 @@ Improvements - :issue:`6384`: Make `--showlocals` work also with `--tb=short`. -- :issue:`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`. +- :issue:`6653`: Add support for matching lines consecutively with :class:`~pytest.LineMatcher`'s :func:`~pytest.LineMatcher.fnmatch_lines` and :func:`~pytest.LineMatcher.re_match_lines`. - :issue:`6658`: Code is now highlighted in tracebacks when ``pygments`` is installed. @@ -1822,7 +3203,7 @@ Bug Fixes - :issue:`6597`: Fix node ids which contain a parametrized empty-string variable. -- :issue:`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. +- :issue:`6646`: Assertion rewriting hooks are (re)stored for the current item, which fixes them being still used after e.g. pytester's ``testdir.runpytest`` etc. - :issue:`6660`: :py:func:`pytest.exit` is handled when emitted from the :hook:`pytest_sessionfinish` hook. This includes quitting from a debugger. @@ -1888,7 +3269,7 @@ Bug Fixes ``multiprocessing`` module. -- :issue:`6436`: :class:`FixtureDef <_pytest.fixtures.FixtureDef>` objects now properly register their finalizers with autouse and +- :issue:`6436`: :class:`~pytest.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. @@ -1944,7 +3325,7 @@ Improvements Bug Fixes --------- -- :issue:`5914`: pytester: fix :py:func:`~_pytest.pytester.LineMatcher.no_fnmatch_line` when used after positive matching. +- :issue:`5914`: pytester: fix :py:func:`~pytest.LineMatcher.no_fnmatch_line` when used after positive matching. - :issue:`6082`: Fix line detection for doctest samples inside :py:class:`python:property` docstrings, as a workaround to :bpo:`17446`. @@ -2008,8 +3389,8 @@ Features rather than implicitly. -- :issue:`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`. +- :issue:`5914`: :fixture:`testdir` learned two new functions, :py:func:`~pytest.LineMatcher.no_fnmatch_line` and + :py:func:`~pytest.LineMatcher.no_re_match_line`. The functions are used to ensure the captured text *does not* match the given pattern. @@ -2495,7 +3876,8 @@ Important This release is a Python3.5+ only release. -For more details, see our :std:doc:`Python 2.7 and 3.4 support plan `. +For more details, see our `Python 2.7 and 3.4 support plan +`_. Removals -------- @@ -2719,7 +4101,11 @@ Features - :issue:`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 :ref:`policy ` 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. + Remark: while this is technically a new feature and according to our + `policy `_ + 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 ------------------------ @@ -2891,7 +4277,8 @@ Important The ``4.6.X`` series will be the last series to support **Python 2 and Python 3.4**. -For more details, see our :std:doc:`Python 2.7 and 3.4 support plan `. +For more details, see our `Python 2.7 and 3.4 support plan +`_. Features @@ -3257,7 +4644,7 @@ Bug Fixes Improved Documentation ---------------------- -- :issue:`4974`: Update docs for ``pytest_cmdline_parse`` hook to note availability liminations +- :issue:`4974`: Update docs for ``pytest_cmdline_parse`` hook to note availability limitations @@ -3598,7 +4985,7 @@ Removals See our :ref:`docs ` on information on how to update your code. -- :issue:`4546`: Remove ``Node.get_marker(name)`` the return value was not usable for more than a existence check. +- :issue:`4546`: Remove ``Node.get_marker(name)`` the return value was not usable for more than an existence check. Use ``Node.get_closest_marker(name)`` as a replacement. @@ -5115,7 +6502,7 @@ Features Bug Fixes --------- -- Fix hanging pexpect test on MacOS by using flush() instead of wait(). +- Fix hanging pexpect test on macOS by using flush() instead of wait(). (:issue:`2022`) - Fix restoring Python state after in-process pytest runs with the @@ -5163,7 +6550,7 @@ 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 + (for example, ``"-k foo and import"`` will show an error that you cannot use the ``import`` keyword in expressions). (:issue:`2953`) - Change parametrized automatic test id generation to use the ``__name__`` @@ -5855,7 +7242,7 @@ Changes * fix :issue:`2013`: turn RecordedWarning into ``namedtuple``, to give it a comprehensible repr while preventing unwarranted modification. -* fix :issue:`2208`: ensure an iteration limit for _pytest.compat.get_real_func. +* fix :issue:`2208`: ensure an iteration limit for ``_pytest.compat.get_real_func``. Thanks :user:`RonnyPfannschmidt` for the report and PR. * Hooks are now verified after collection is complete, rather than right after loading installed plugins. This @@ -6059,7 +7446,7 @@ Bug Fixes Thanks :user:`adborden` for the report and :user:`nicoddemus` for the PR. * Clean up unittest TestCase objects after tests are complete (:issue:`1649`). - Thanks :user:`d_b_w` for the report and PR. + Thanks :user:`d-b-w` for the report and PR. 3.0.3 (2016-09-28) @@ -6074,7 +7461,7 @@ Bug Fixes Thanks :user:`nicoddemus` for the PR. * Fix pkg_resources import error in Jython projects (:issue:`1853`). - Thanks :user:`raquel-ucl` for the PR. + Thanks :user:`raquelalegre` for the PR. * Got rid of ``AttributeError: 'Module' object has no attribute '_obj'`` exception in Python 3 (:issue:`1944`). @@ -6939,7 +8326,7 @@ time or change existing behaviors in order to make them less surprising/more use 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. + if "y" is not a preexisting attribute. Thanks Florian Bruhin. - fix issue741: make running output from testdir.run copy/pasteable Thanks Bruno Oliveira. @@ -6995,7 +8382,7 @@ time or change existing behaviors in order to make them less surprising/more use - 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. + Thanks 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. @@ -7099,7 +8486,7 @@ time or change existing behaviors in order to make them less surprising/more use 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 +- fix issue650: new option ``--doctest-ignore-import-errors`` which will turn import errors in doctests into skips. Thanks Charles Cloud for the complete PR. @@ -7287,7 +8674,7 @@ time or change existing behaviors in order to make them less surprising/more use - 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 +- change XPASS colour to yellow rather than red when tests are run with -v. - fix issue473: work around mock putting an unbound method into a class @@ -7460,7 +8847,7 @@ time or change existing behaviors in order to make them less surprising/more use Thanks Ralph Schmitt for the precise failure example. - fix issue244 by implementing special index for parameters to only use - indices for paramentrized test ids + indices for parametrized test ids - fix issue287 by running all finalizers but saving the exception from the first failing finalizer and re-raising it so teardown will @@ -7468,7 +8855,7 @@ time or change existing behaviors in order to make them less surprising/more use 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. @@ -7725,7 +9112,7 @@ Bug fixes: 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. + module-level functions. Thanks Anatoly Bubenkoff. - you can specify "-q" or "-qq" to get different levels of "quieter" reporting (thanks Katarzyna Jachim) @@ -8147,7 +9534,7 @@ Bug fixes: 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 issue49: avoid confusing error when initialization 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 @@ -8272,7 +9659,7 @@ Bug fixes: 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" + parameterization remains the "pytest_generate_tests" mechanism, see the docs. 2.0.0 (2010-11-25) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/conf.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/conf.py index b316163532ac0..32ecaa17435e7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/conf.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/conf.py @@ -15,16 +15,15 @@ # # The full version, including alpha/beta/rc tags. # The short X.Y version. -import ast import os import shutil import sys from textwrap import dedent -from typing import List from typing import TYPE_CHECKING from _pytest import __version__ as version + if TYPE_CHECKING: import sphinx.application @@ -38,6 +37,7 @@ autodoc_member_order = "bysource" autodoc_typehints = "description" +autodoc_typehints_description_target = "documented" todo_include_todos = 1 latex_engine = "lualatex" @@ -162,14 +162,58 @@ _repo = "https://github.com/pytest-dev/pytest" extlinks = { - "bpo": ("https://bugs.python.org/issue%s", "bpo-"), - "pypi": ("https://pypi.org/project/%s/", ""), - "issue": (f"{_repo}/issues/%s", "issue #"), - "pull": (f"{_repo}/pull/%s", "pull request #"), - "user": ("https://github.com/%s", "@"), + "bpo": ("https://bugs.python.org/issue%s", "bpo-%s"), + "pypi": ("https://pypi.org/project/%s/", "%s"), + "issue": (f"{_repo}/issues/%s", "issue #%s"), + "pull": (f"{_repo}/pull/%s", "pull request #%s"), + "user": ("https://github.com/%s", "@%s"), } +nitpicky = True +nitpick_ignore = [ + # TODO (fix in pluggy?) + ("py:class", "HookCaller"), + ("py:class", "HookspecMarker"), + ("py:exc", "PluginValidationError"), + # Might want to expose/TODO (https://github.com/pytest-dev/pytest/issues/7469) + ("py:class", "ExceptionRepr"), + ("py:class", "Exit"), + ("py:class", "SubRequest"), + ("py:class", "SubRequest"), + ("py:class", "TerminalReporter"), + ("py:class", "_pytest._code.code.TerminalRepr"), + ("py:class", "_pytest.fixtures.FixtureFunctionMarker"), + ("py:class", "_pytest.logging.LogCaptureHandler"), + ("py:class", "_pytest.mark.structures.ParameterSet"), + # Intentionally undocumented/private + ("py:class", "_pytest._code.code.Traceback"), + ("py:class", "_pytest._py.path.LocalPath"), + ("py:class", "_pytest.capture.CaptureResult"), + ("py:class", "_pytest.compat.NotSetType"), + ("py:class", "_pytest.python.PyCollector"), + ("py:class", "_pytest.python.PyobjMixin"), + ("py:class", "_pytest.python_api.RaisesContext"), + ("py:class", "_pytest.recwarn.WarningsChecker"), + ("py:class", "_pytest.reports.BaseReport"), + # Undocumented third parties + ("py:class", "_tracing.TagTracerSub"), + ("py:class", "warnings.WarningMessage"), + # Undocumented type aliases + ("py:class", "LEGACY_PATH"), + ("py:class", "_PluggyPlugin"), + # TypeVars + ("py:class", "_pytest._code.code.E"), + ("py:class", "_pytest.fixtures.FixtureFunction"), + ("py:class", "_pytest.nodes._NodeType"), + ("py:class", "_pytest.python_api.E"), + ("py:class", "_pytest.recwarn.T"), + ("py:class", "_pytest.runner.TResult"), + ("py:obj", "_pytest.fixtures.FixtureValue"), + ("py:obj", "_pytest.stash.T"), +] + + # -- Options for HTML output --------------------------------------------------- sys.path.append(os.path.abspath("_themes")) @@ -247,7 +291,7 @@ html_domain_indices = True # If false, no index is generated. -html_use_index = True +html_use_index = False # If true, the index is split into individual pages for each letter. # html_split_index = False @@ -320,7 +364,9 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1)] +man_pages = [ + ("how-to/usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1) +] # -- Options for Epub output --------------------------------------------------- @@ -338,7 +384,7 @@ # The scheme of the identifier. Typical schemes are ISBN or URL. # epub_scheme = '' -# The unique identifier of the text. This can be a ISBN number +# The unique identifier of the text. This can be an ISBN number # or the project homepage. # epub_identifier = '' @@ -349,7 +395,7 @@ # The format is a list of tuples containing the path and title. # epub_pre_files = [] -# HTML files shat should be inserted after the pages created by sphinx. +# HTML files that should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. # epub_post_files = [] @@ -382,7 +428,6 @@ ] -# Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { "pluggy": ("https://pluggy.readthedocs.io/en/stable", None), "python": ("https://docs.python.org/3", None), @@ -390,19 +435,17 @@ "pip": ("https://pip.pypa.io/en/stable", None), "tox": ("https://tox.wiki/en/stable", None), "virtualenv": ("https://virtualenv.pypa.io/en/stable", None), - "django": ( - "http://docs.djangoproject.com/en/stable", - "http://docs.djangoproject.com/en/stable/_objects", - ), "setuptools": ("https://setuptools.pypa.io/en/stable", None), + "packaging": ("https://packaging.python.org/en/latest", None), } def configure_logging(app: "sphinx.application.Sphinx") -> None: """Configure Sphinx's WarningHandler to handle (expected) missing include.""" - import sphinx.util.logging import logging + import sphinx.util.logging + class WarnLogFilter(logging.Filter): def filter(self, record: logging.LogRecord) -> bool: """Ignore warnings about missing include with "only" directive. @@ -422,8 +465,6 @@ def filter(self, record: logging.LogRecord) -> bool: 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", @@ -454,25 +495,6 @@ def setup(app: "sphinx.application.Sphinx") -> None: configure_logging(app) - # Make Sphinx mark classes with "final" when decorated with @final. - # We need this because we import final from pytest._compat, not from - # typing (for Python < 3.8 compat), so Sphinx doesn't detect it. - # To keep things simple we accept any `@final` decorator. - # Ref: https://github.com/pytest-dev/pytest/pull/7780 - import sphinx.pycode.ast - import sphinx.pycode.parser - - original_is_final = sphinx.pycode.parser.VariableCommentPicker.is_final - - def patched_is_final(self, decorators: List[ast.expr]) -> bool: - if original_is_final(self, decorators): - return True - return any( - sphinx.pycode.ast.unparse(decorator) == "final" for decorator in decorators - ) - - sphinx.pycode.parser.VariableCommentPicker.is_final = patched_is_final - # legacypath.py monkey-patches pytest.Testdir in. Import the file so # that autodoc can discover references to it. import _pytest.legacypath # noqa: F401 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/contents.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/contents.rst index 049d44ba9d8a3..181207203b220 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/contents.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/contents.rst @@ -44,7 +44,6 @@ How-to guides how-to/existingtestsuite how-to/unittest - how-to/nose how-to/xunit_setup how-to/bash-completion @@ -85,7 +84,6 @@ Further topics backwards-compatibility deprecations - py27-py34-deprecation contributing development_guide diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/deprecations.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/deprecations.rst index 0f19744adec31..a65ea33166320 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/deprecations.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/deprecations.rst @@ -16,25 +16,42 @@ Deprecated Features ------------------- Below is a complete list of all pytest features which are considered deprecated. Using those features will issue -:class:`PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters `. +:class:`~pytest.PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters `. -.. _instance-collector-deprecation: -The ``pytest.Instance`` collector -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _import-or-skip-import-error: -.. versionremoved:: 7.0 +``pytest.importorskip`` default behavior regarding :class:`ImportError` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``pytest.Instance`` collector type has been removed. +.. deprecated:: 8.2 -Previously, Python test methods were collected as :class:`~pytest.Class` -> ``Instance`` -> :class:`~pytest.Function`. -Now :class:`~pytest.Class` collects the test methods directly. +Traditionally :func:`pytest.importorskip` will capture :class:`ImportError`, with the original intent being to skip +tests where a dependent module is not installed, for example testing with different dependencies. -Most plugins which reference ``Instance`` do so in order to ignore or skip it, -using a check such as ``if isinstance(node, Instance): return``. -Such plugins should simply remove consideration of ``Instance`` on pytest>=7. -However, to keep such uses working, a dummy type has been instanted in ``pytest.Instance`` and ``_pytest.python.Instance``, -and importing it emits a deprecation warning. This will be removed in pytest 8. +However some packages might be installed in the system, but are not importable due to +some other issue, for example, a compilation error or a broken installation. In those cases :func:`pytest.importorskip` +would still silently skip the test, but more often than not users would like to see the unexpected +error so the underlying issue can be fixed. + +In ``8.2`` the ``exc_type`` parameter has been added, giving users the ability of passing :class:`ModuleNotFoundError` +to skip tests only if the module cannot really be found, and not because of some other error. + +Catching only :class:`ModuleNotFoundError` by default (and letting other errors propagate) would be the best solution, +however for backward compatibility, pytest will keep the existing behavior but raise an warning if: + +1. The captured exception is of type :class:`ImportError`, and: +2. The user does not pass ``exc_type`` explicitly. + +If the import attempt raises :class:`ModuleNotFoundError` (the usual case), then the module is skipped and no +warning is emitted. + +This way, the usual cases will keep working the same way, while unexpected errors will now issue a warning, with +users being able to supress the warning by passing ``exc_type=ImportError`` explicitly. + +In ``9.0``, the warning will turn into an error, and in ``9.1`` :func:`pytest.importorskip` will only capture +:class:`ModuleNotFoundError` by default and no warnings will be issued anymore -- but users can still capture +:class:`ImportError` by passing it to ``exc_type``. .. _node-ctor-fspath-deprecation: @@ -70,12 +87,54 @@ arguments they only pass on to the superclass. resolved in future versions as we slowly get rid of the :pypi:`py` dependency (see :issue:`9283` for a longer discussion). -Due to the ongoing migration of methods like :meth:`~_pytest.Item.reportinfo` +Due to the ongoing migration of methods like :meth:`~pytest.Item.reportinfo` which still is expected to return a ``py.path.local`` object, nodes still have both ``fspath`` (``py.path.local``) and ``path`` (``pathlib.Path``) attributes, no matter what argument was used in the constructor. We expect to deprecate the ``fspath`` attribute in a future release. + +Configuring hook specs/impls using markers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Before pluggy, pytest's plugin library, was its own package and had a clear API, +pytest just used ``pytest.mark`` to configure hooks. + +The :py:func:`pytest.hookimpl` and :py:func:`pytest.hookspec` decorators +have been available since years and should be used instead. + +.. code-block:: python + + @pytest.mark.tryfirst + def pytest_runtest_call(): ... + + + # or + def pytest_runtest_call(): ... + + + pytest_runtest_call.tryfirst = True + +should be changed to: + +.. code-block:: python + + @pytest.hookimpl(tryfirst=True) + def pytest_runtest_call(): ... + +Changed ``hookimpl`` attributes: + +* ``tryfirst`` +* ``trylast`` +* ``optionalhook`` +* ``hookwrapper`` + +Changed ``hookwrapper`` attributes: + +* ``firstresult`` +* ``historic`` + + .. _legacy-path-hooks-deprecated: ``py.path.local`` arguments for hooks replaced with ``pathlib.Path`` @@ -122,62 +181,6 @@ Directly constructing the following classes is now deprecated: These constructors have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 8. -.. _cmdline-preparse-deprecated: - -Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 7.0 - -Passing the keyword argument ``msg`` to :func:`pytest.skip`, :func:`pytest.fail` or :func:`pytest.exit` -is now deprecated and ``reason`` should be used instead. This change is to bring consistency between these -functions and the ``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which already accept a ``reason`` argument. - -.. code-block:: python - - def test_fail_example(): - # old - pytest.fail(msg="foo") - # new - pytest.fail(reason="bar") - - - def test_skip_example(): - # old - pytest.skip(msg="foo") - # new - pytest.skip(reason="bar") - - - def test_exit_example(): - # old - pytest.exit(msg="foo") - # new - pytest.exit(reason="bar") - - -Implementing the ``pytest_cmdline_preparse`` hook -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 7.0 - -Implementing the :hook:`pytest_cmdline_preparse` hook has been officially deprecated. -Implement the :hook:`pytest_load_initial_conftests` hook instead. - -.. code-block:: python - - def pytest_cmdline_preparse(config: Config, args: List[str]) -> None: - ... - - - # becomes: - - - def pytest_load_initial_conftests( - early_config: Config, parser: Parser, args: List[str] - ) -> None: - ... - .. _diamond-inheritance-deprecated: Diamond inheritance between :class:`pytest.Collector` and :class:`pytest.Item` @@ -185,7 +188,7 @@ Diamond inheritance between :class:`pytest.Collector` and :class:`pytest.Item` .. deprecated:: 7.0 -Defining a custom pytest node type which is both an :class:`pytest.Item ` and a :class:`pytest.Collector ` (e.g. :class:`pytest.File `) now issues a warning. +Defining a custom pytest node type which is both an :class:`~pytest.Item` and a :class:`~pytest.Collector` (e.g. :class:`~pytest.File`) now issues a warning. It was never sanely supported and triggers hard to debug errors. Some plugins providing linting/code analysis have been using this as a hack. @@ -197,8 +200,8 @@ Instead, a separate collector node should be used, which collects the item. See .. _uncooperative-constructors-deprecated: -Constructors of custom :class:`pytest.Node` subclasses should take ``**kwargs`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Constructors of custom :class:`~_pytest.nodes.Node` subclasses should take ``**kwargs`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. deprecated:: 7.0 @@ -229,35 +232,245 @@ conflicts (such as :class:`pytest.File` now taking ``path`` instead of ``fspath``, as :ref:`outlined above `), a deprecation warning is now raised. -Backward compatibilities in ``Parser.addoption`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Applying a mark to a fixture function +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. deprecated:: 2.4 +.. deprecated:: 7.4 -Several behaviors of :meth:`Parser.addoption ` are now -scheduled for removal in pytest 8 (deprecated since pytest 2.4.0): +Applying a mark to a fixture function never had any effect, but it is a common user error. -- ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead. -- ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead. +.. code-block:: python + + @pytest.mark.usefixtures("clean_database") + @pytest.fixture + def user() -> User: ... + +Users expected in this case that the ``usefixtures`` mark would have its intended effect of using the ``clean_database`` fixture when ``user`` was invoked, when in fact it has no effect at all. + +Now pytest will issue a warning when it encounters this problem, and will raise an error in the future versions. + + +Returning non-None value in test functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.2 + +A :class:`pytest.PytestReturnNotNoneWarning` is now emitted if a test function returns something other than `None`. + +This prevents a common mistake among beginners that expect that returning a `bool` would cause a test to pass or fail, for example: + +.. code-block:: python + + @pytest.mark.parametrize( + ["a", "b", "result"], + [ + [1, 2, 5], + [2, 3, 8], + [5, 3, 18], + ], + ) + def test_foo(a, b, result): + return foo(a, b) == result + +Given that pytest ignores the return value, this might be surprising that it will never fail. + +The proper fix is to change the `return` to an `assert`: + +.. code-block:: python + + @pytest.mark.parametrize( + ["a", "b", "result"], + [ + [1, 2, 5], + [2, 3, 8], + [5, 3, 18], + ], + ) + def test_foo(a, b, result): + assert foo(a, b) == result + + +The ``yield_fixture`` function/decorator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 6.2 + +``pytest.yield_fixture`` is a deprecated alias for :func:`pytest.fixture`. + +It has been so for a very long time, so can be search/replaced safely. + + +Removed Features and Breaking Changes +------------------------------------- + +As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after +an appropriate period of deprecation has passed. + +Some breaking changes which could not be deprecated are also listed. + +.. _nose-deprecation: + +Support for tests written for nose +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.2 +.. versionremoved:: 8.0 + +Support for running tests written for `nose `__ is now deprecated. + +``nose`` has been in maintenance mode-only for years, and maintaining the plugin is not trivial as it spills +over the code base (see :issue:`9886` for more details). + +setup/teardown +^^^^^^^^^^^^^^ + +One thing that might catch users by surprise is that plain ``setup`` and ``teardown`` methods are not pytest native, +they are in fact part of the ``nose`` support. + + +.. code-block:: python + class Test: + def setup(self): + self.resource = make_resource() -Raising ``unittest.SkipTest`` during collection -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def teardown(self): + self.resource.close() + + def test_foo(self): ... + + def test_bar(self): ... + + + +Native pytest support uses ``setup_method`` and ``teardown_method`` (see :ref:`xunit-method-setup`), so the above should be changed to: + +.. code-block:: python + + class Test: + def setup_method(self): + self.resource = make_resource() + + def teardown_method(self): + self.resource.close() + + def test_foo(self): ... + + def test_bar(self): ... + + +This is easy to do in an entire code base by doing a simple find/replace. + +@with_setup +^^^^^^^^^^^ + +Code using `@with_setup `_ such as this: + +.. code-block:: python + + from nose.tools import with_setup + + + def setup_some_resource(): ... + + + def teardown_some_resource(): ... + + + @with_setup(setup_some_resource, teardown_some_resource) + def test_foo(): ... + +Will also need to be ported to a supported pytest style. One way to do it is using a fixture: + +.. code-block:: python + + import pytest + + + def setup_some_resource(): ... + + + def teardown_some_resource(): ... + + + @pytest.fixture + def some_resource(): + setup_some_resource() + yield + teardown_some_resource() + + + def test_foo(some_resource): ... + + +.. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup + + +The ``compat_co_firstlineno`` attribute +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Nose inspects this attribute on function objects to allow overriding the function's inferred line number. +Pytest no longer respects this attribute. + + + +Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. deprecated:: 7.0 +.. versionremoved:: 8.0 + +Passing the keyword argument ``msg`` to :func:`pytest.skip`, :func:`pytest.fail` or :func:`pytest.exit` +is now deprecated and ``reason`` should be used instead. This change is to bring consistency between these +functions and the ``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which already accept a ``reason`` argument. + +.. code-block:: python + + def test_fail_example(): + # old + pytest.fail(msg="foo") + # new + pytest.fail(reason="bar") + + + def test_skip_example(): + # old + pytest.skip(msg="foo") + # new + pytest.skip(reason="bar") + + + def test_exit_example(): + # old + pytest.exit(msg="foo") + # new + pytest.exit(reason="bar") + + +.. _instance-collector-deprecation: + +The ``pytest.Instance`` collector +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Raising :class:`unittest.SkipTest` to skip collection of tests during the -pytest collection phase is deprecated. Use :func:`pytest.skip` instead. +.. versionremoved:: 7.0 + +The ``pytest.Instance`` collector type has been removed. + +Previously, Python test methods were collected as :class:`~pytest.Class` -> ``Instance`` -> :class:`~pytest.Function`. +Now :class:`~pytest.Class` collects the test methods directly. + +Most plugins which reference ``Instance`` do so in order to ignore or skip it, +using a check such as ``if isinstance(node, Instance): return``. +Such plugins should simply remove consideration of ``Instance`` on pytest>=7. +However, to keep such uses working, a dummy type has been instanced in ``pytest.Instance`` and ``_pytest.python.Instance``, +and importing it emits a deprecation warning. This was removed in pytest 8. -Note: This deprecation only relates to using `unittest.SkipTest` during test -collection. You are probably not doing that. Ordinary usage of -:class:`unittest.SkipTest` / :meth:`unittest.TestCase.skipTest` / -:func:`unittest.skip` in unittest test cases is fully supported. Using ``pytest.warns(None)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. deprecated:: 7.0 +.. versionremoved:: 8.0 :func:`pytest.warns(None) ` is now deprecated because it was frequently misused. Its correct usage was checking that the code emits at least one warning of any type - like ``pytest.warns()`` @@ -265,10 +478,25 @@ or ``pytest.warns(Warning)``. See :ref:`warns use cases` for examples. + +Backward compatibilities in ``Parser.addoption`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 2.4 +.. versionremoved:: 8.0 + +Several behaviors of :meth:`Parser.addoption ` are now +removed in pytest 8 (deprecated since pytest 2.4.0): + +- ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead. +- ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead. + + The ``--strict`` command-line option ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. deprecated:: 6.2 +.. versionremoved:: 8.0 The ``--strict`` command-line option has been deprecated in favor of ``--strict-markers``, which better conveys what the option does. @@ -278,39 +506,172 @@ flag for all strictness related options (``--strict-markers`` and ``--strict-con at the moment, more might be introduced in the future). -The ``yield_fixture`` function/decorator -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _cmdline-preparse-deprecated: -.. deprecated:: 6.2 +Implementing the ``pytest_cmdline_preparse`` hook +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``pytest.yield_fixture`` is a deprecated alias for :func:`pytest.fixture`. +.. deprecated:: 7.0 +.. versionremoved:: 8.0 -It has been so for a very long time, so can be search/replaced safely. +Implementing the ``pytest_cmdline_preparse`` hook has been officially deprecated. +Implement the :hook:`pytest_load_initial_conftests` hook instead. +.. code-block:: python -The ``pytest_warning_captured`` hook -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def pytest_cmdline_preparse(config: Config, args: List[str]) -> None: ... -.. deprecated:: 6.0 -This hook has an `item` parameter which cannot be serialized by ``pytest-xdist``. + # becomes: + + + def pytest_load_initial_conftests( + early_config: Config, parser: Parser, args: List[str] + ) -> None: ... + + +Collection changes in pytest 8 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Added a new :class:`pytest.Directory` base collection node, which all collector nodes for filesystem directories are expected to subclass. +This is analogous to the existing :class:`pytest.File` for file nodes. + +Changed :class:`pytest.Package` to be a subclass of :class:`pytest.Directory`. +A ``Package`` represents a filesystem directory which is a Python package, +i.e. contains an ``__init__.py`` file. + +:class:`pytest.Package` now only collects files in its own directory; previously it collected recursively. +Sub-directories are collected as sub-collector nodes, thus creating a collection tree which mirrors the filesystem hierarchy. + +:attr:`session.name ` is now ``""``; previously it was the rootdir directory name. +This matches :attr:`session.nodeid <_pytest.nodes.Node.nodeid>` which has always been `""`. + +Added a new :class:`pytest.Dir` concrete collection node, a subclass of :class:`pytest.Directory`. +This node represents a filesystem directory, which is not a :class:`pytest.Package`, +i.e. does not contain an ``__init__.py`` file. +Similarly to ``Package``, it only collects the files in its own directory, +while collecting sub-directories as sub-collector nodes. + +Files and directories are now collected in alphabetical order jointly, unless changed by a plugin. +Previously, files were collected before directories. + +The collection tree now contains directories/packages up to the :ref:`rootdir `, +for initial arguments that are found within the rootdir. +For files outside the rootdir, only the immediate directory/package is collected -- +note however that collecting from outside the rootdir is discouraged. + +As an example, given the following filesystem tree:: + + myroot/ + pytest.ini + top/ + ├── aaa + │ └── test_aaa.py + ├── test_a.py + ├── test_b + │ ├── __init__.py + │ └── test_b.py + ├── test_c.py + └── zzz + ├── __init__.py + └── test_zzz.py + +the collection tree, as shown by `pytest --collect-only top/` but with the otherwise-hidden :class:`~pytest.Session` node added for clarity, +is now the following:: + + + + + + + + + + + + + + + + + + +Previously, it was:: + + + + + + + + + + + + + + + +Code/plugins which rely on a specific shape of the collection tree might need to update. + + +:class:`pytest.Package` is no longer a :class:`pytest.Module` or :class:`pytest.File` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionchanged:: 8.0 + +The ``Package`` collector node designates a Python package, that is, a directory with an `__init__.py` file. +Previously ``Package`` was a subtype of ``pytest.Module`` (which represents a single Python module), +the module being the `__init__.py` file. +This has been deemed a design mistake (see :issue:`11137` and :issue:`7777` for details). + +The ``path`` property of ``Package`` nodes now points to the package directory instead of the ``__init__.py`` file. + +Note that a ``Module`` node for ``__init__.py`` (which is not a ``Package``) may still exist, +if it is picked up during collection (e.g. if you configured :confval:`python_files` to include ``__init__.py`` files). + + +Collecting ``__init__.py`` files no longer collects package +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionremoved:: 8.0 + +Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only. +Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself +(unless :confval:`python_files` was changed to allow `__init__.py` file). + +To collect the entire package, specify just the directory: `pytest pkg`. -Use the ``pytest_warning_recored`` hook instead, which replaces the ``item`` parameter -by a ``nodeid`` parameter. The ``pytest.collect`` module ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. deprecated:: 6.0 +.. versionremoved:: 7.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_warning_captured`` hook +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 6.0 +.. versionremoved:: 7.0 + +This hook has an `item` parameter which cannot be serialized by ``pytest-xdist``. + +Use the ``pytest_warning_recorded`` hook instead, which replaces the ``item`` parameter +by a ``nodeid`` parameter. + + + The ``pytest._fillfuncargs`` function ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. deprecated:: 6.0 +.. versionremoved:: 7.0 This function was kept for backward compatibility with an older plugin. @@ -319,12 +680,6 @@ 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -410,7 +765,7 @@ By using ``legacy`` you will keep using the legacy/xunit1 format when upgrading 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 +the ``--junit-xml`` option is given in the command line but ``junit_family`` is not explicitly configured in ``pytest.ini``. Services known to support the ``xunit2`` format: @@ -587,8 +942,7 @@ Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated (50, 500), ], ) - def test_foo(a, b): - ... + def test_foo(a, b): ... This code applies the ``pytest.mark.xfail(reason="flaky")`` mark to the ``(6, 36)`` value of the above parametrization call. @@ -611,8 +965,7 @@ To update the code, use ``pytest.param``: (50, 500), ], ) - def test_foo(a, b): - ... + def test_foo(a, b): ... .. _pytest_funcarg__ prefix deprecated: @@ -763,15 +1116,13 @@ This is just a matter of renaming the fixture as the API is the same: .. code-block:: python - def test_foo(record_xml_property): - ... + def test_foo(record_xml_property): ... Change to: .. code-block:: python - def test_foo(record_property): - ... + def test_foo(record_property): ... .. _passing command-line string to pytest.main deprecated: @@ -866,7 +1217,7 @@ that are then turned into proper test methods. Example: .. code-block:: python def check(x, y): - assert x ** x == y + assert x**x == y def test_squared(): @@ -881,7 +1232,7 @@ This form of test function doesn't support fixtures properly, and users should s @pytest.mark.parametrize("x, y", [(2, 4), (3, 9)]) def test_squared(x, y): - assert x ** x == y + assert x**x == y .. _internal classes accessed through node deprecated: @@ -933,8 +1284,7 @@ Example of usage: .. code-block:: python - class MySymbol: - ... + class MySymbol: ... def pytest_namespace(): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/failure_demo.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/failure_demo.py index abb9bce5097a8..f7a9c27942686 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/failure_demo.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/failure_demo.py @@ -172,7 +172,7 @@ def test_raise(self): raise ValueError("demo error") def test_tupleerror(self): - a, b = [1] # NOQA + a, b = [1] # noqa: F841 def test_reinterpret_fails_with_print_for_the_fun_of_it(self): items = [1, 2, 3] @@ -180,7 +180,7 @@ def test_reinterpret_fails_with_print_for_the_fun_of_it(self): a, b = items.pop() def test_some_error(self): - if namenotexi: # NOQA + if namenotexi: # noqa: F821 pass def func1(self): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/conftest.py index 7cdf18cdbc131..4aa7ec23bd1c9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/conftest.py @@ -2,6 +2,7 @@ import pytest + mydir = os.path.dirname(__file__) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/w3c-import.log new file mode 100644 index 0000000000000..042b2b6bc867a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/test_hello_world.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/test_failures.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/test_failures.py index 350518b43c76a..19d862f60b782 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/test_failures.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/test_failures.py @@ -1,6 +1,7 @@ import os.path import shutil + failure_demo = os.path.join(os.path.dirname(__file__), "failure_demo.py") pytest_plugins = ("pytester",) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/w3c-import.log new file mode 100644 index 0000000000000..8c22ab9378fbd --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/failure_demo.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/test_failures.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/test_setup_flow_example.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/attic.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/attic.rst index 2ea8700620435..2b1f2766dce78 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/attic.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/attic.rst @@ -25,7 +25,7 @@ example: specifying and selecting acceptance tests self.tmpdir = request.config.mktemp(request.function.__name__, numbered=True) def run(self, *cmd): - """ called by test code to execute an acceptance test. """ + """called by test code to execute an acceptance test.""" self.tmpdir.chdir() return subprocess.check_output(cmd).decode() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/conftest.py index f905738c4f6c7..66e70f14dd713 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/conftest.py @@ -1 +1 @@ -collect_ignore = ["nonpython"] +collect_ignore = ["nonpython", "customdirectory"] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory.rst new file mode 100644 index 0000000000000..1e4d7e370de29 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory.rst @@ -0,0 +1,77 @@ +.. _`custom directory collectors`: + +Using a custom directory collector +==================================================== + +By default, pytest collects directories using :class:`pytest.Package`, for directories with ``__init__.py`` files, +and :class:`pytest.Dir` for other directories. +If you want to customize how a directory is collected, you can write your own :class:`pytest.Directory` collector, +and use :hook:`pytest_collect_directory` to hook it up. + +.. _`directory manifest plugin`: + +A basic example for a directory manifest file +-------------------------------------------------------------- + +Suppose you want to customize how collection is done on a per-directory basis. +Here is an example ``conftest.py`` plugin that allows directories to contain a ``manifest.json`` file, +which defines how the collection should be done for the directory. +In this example, only a simple list of files is supported, +however you can imagine adding other keys, such as exclusions and globs. + +.. include:: customdirectory/conftest.py + :literal: + +You can create a ``manifest.json`` file and some test files: + +.. include:: customdirectory/tests/manifest.json + :literal: + +.. include:: customdirectory/tests/test_first.py + :literal: + +.. include:: customdirectory/tests/test_second.py + :literal: + +.. include:: customdirectory/tests/test_third.py + :literal: + +An you can now execute the test specification: + +.. code-block:: pytest + + customdirectory $ pytest + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y + rootdir: /home/sweet/project/customdirectory + configfile: pytest.ini + collected 2 items + + tests/test_first.py . [ 50%] + tests/test_second.py . [100%] + + ============================ 2 passed in 0.12s ============================= + +.. regendoc:wipe + +Notice how ``test_three.py`` was not executed, because it is not listed in the manifest. + +You can verify that your custom collector appears in the collection tree: + +.. code-block:: pytest + + customdirectory $ pytest --collect-only + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y + rootdir: /home/sweet/project/customdirectory + configfile: pytest.ini + collected 2 items + + + + + + + + + ======================== 2 tests collected in 0.12s ======================== diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/conftest.py new file mode 100644 index 0000000000000..b2f68dba41a20 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/conftest.py @@ -0,0 +1,28 @@ +# content of conftest.py +import json + +import pytest + + +class ManifestDirectory(pytest.Directory): + def collect(self): + # The standard pytest behavior is to loop over all `test_*.py` files and + # call `pytest_collect_file` on each file. This collector instead reads + # the `manifest.json` file and only calls `pytest_collect_file` for the + # files defined there. + manifest_path = self.path / "manifest.json" + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + ihook = self.ihook + for file in manifest["files"]: + yield from ihook.pytest_collect_file( + file_path=self.path / file, parent=self + ) + + +@pytest.hookimpl +def pytest_collect_directory(path, parent): + # Use our custom collector for directories containing a `manifest.json` file. + if path.joinpath("manifest.json").is_file(): + return ManifestDirectory.from_parent(parent=parent, path=path) + # Otherwise fallback to the standard behavior. + return None diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/pytest.ini b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/pytest.ini new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/manifest.json b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/manifest.json new file mode 100644 index 0000000000000..6ab6d0a522269 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/manifest.json @@ -0,0 +1,6 @@ +{ + "files": [ + "test_first.py", + "test_second.py" + ] +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/test_first.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/test_first.py new file mode 100644 index 0000000000000..0a78de59945d3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/test_first.py @@ -0,0 +1,3 @@ +# content of test_first.py +def test_1(): + pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/test_second.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/test_second.py new file mode 100644 index 0000000000000..eed724a7d969f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/test_second.py @@ -0,0 +1,3 @@ +# content of test_second.py +def test_2(): + pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/test_third.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/test_third.py new file mode 100644 index 0000000000000..61cf59dc16c2a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/test_third.py @@ -0,0 +1,3 @@ +# content of test_third.py +def test_3(): + pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/w3c-import.log new file mode 100644 index 0000000000000..7f0e0091957be --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/manifest.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/test_first.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/test_second.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/tests/test_third.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/w3c-import.log new file mode 100644 index 0000000000000..4f8d45b15c0e2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory/pytest.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_dependencies.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_dependencies.py index b3512c2a64dbd..e76e3f93c622f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_dependencies.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_dependencies.py @@ -17,7 +17,7 @@ def b(a, order): @pytest.fixture -def c(a, b, order): +def c(b, order): order.append("c") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/w3c-import.log new file mode 100644 index 0000000000000..ddce45b201a5a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/w3c-import.log @@ -0,0 +1,33 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/fixture_availability.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/fixture_availability_plugins.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_autouse.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_autouse.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_autouse_flat.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_dependencies.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_dependencies.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_dependencies_flat.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_dependencies_unclear.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_scope.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_scope.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_request_different_scope.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_request_different_scope.svg diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/index.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/index.rst index 71e855534ff94..840819002d4c5 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/index.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/index.rst @@ -18,7 +18,6 @@ For basic examples, see - :ref:`Fixtures ` for basic fixture/setup examples - :ref:`parametrize` for basic test function parametrization - :ref:`unittest` for basic unittest integration -- :ref:`noseintegration` for basic nosetests integration The following examples aim at various use cases you might encounter. @@ -32,3 +31,4 @@ The following examples aim at various use cases you might encounter. special pythoncollection nonpython + customdirectory diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/markers.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/markers.rst index 3226c0871e093..c04d2a078ddbc 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/markers.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/markers.rst @@ -45,7 +45,7 @@ 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-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 4 items / 3 deselected / 1 selected @@ -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-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 4 items / 1 deselected / 3 selected @@ -82,7 +82,7 @@ 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-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 1 item @@ -97,7 +97,7 @@ You can also select on the class: $ pytest -v test_server.py::TestClass =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 1 item @@ -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-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 2 items @@ -136,7 +136,7 @@ Or select multiple nodes: Node IDs for failing tests are displayed in the test summary info when running pytest with the ``-rf`` option. You can also - construct Node IDs from the output of ``pytest --collectonly``. + construct Node IDs from the output of ``pytest --collect-only``. Using ``-k expr`` to select tests based on their name ------------------------------------------------------- @@ -156,7 +156,7 @@ The expression matching is now case-insensitive. $ pytest -v -k http # running with the above defined example module =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 4 items / 3 deselected / 1 selected @@ -171,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-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 4 items / 1 deselected / 3 selected @@ -188,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-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 4 items / 2 deselected / 2 selected @@ -246,9 +246,9 @@ You can ask which markers exist for your test suite - the list includes our just @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.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. + @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead. - @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. + @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. DEPRECATED, use @pytest.hookimpl(trylast=True) instead. For an example on how to add and work with markers from a plugin, see @@ -346,7 +346,7 @@ Custom marker and command line option to control test runs Plugins can provide custom markers and implement specific behaviour based on it. This is a self-contained example which adds a command line option and a parametrized test function marker to run tests -specifies via named environments: +specified via named environments: .. code-block:: python @@ -375,7 +375,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}".format(envnames)) + pytest.skip(f"test requires env in {envnames!r}") A test file using this local plugin: @@ -397,7 +397,7 @@ the test needs: $ pytest -E stage2 =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -411,7 +411,7 @@ and here is one that specifies exactly the environment needed: $ pytest -E stage1 =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -438,9 +438,9 @@ The ``--markers`` option always gives you a list of available markers: @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.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. + @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead. - @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. + @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. DEPRECATED, use @pytest.hookimpl(trylast=True) instead. .. _`passing callables to custom markers`: @@ -528,7 +528,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={} kwargs={}".format(mark.args, mark.kwargs)) + print(f"glob args={mark.args} kwargs={mark.kwargs}") sys.stdout.flush() Let's run this without capturing output and see what we get: @@ -558,6 +558,7 @@ for your particular platform, you could use the following plugin: # content of conftest.py # import sys + import pytest ALL = set("darwin linux win32".split()) @@ -567,7 +568,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 {}".format(plat)) + pytest.skip(f"cannot run on platform {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: @@ -603,14 +604,14 @@ 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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items test_plat.py s.s. [100%] ========================= short test summary info ========================== - SKIPPED [2] conftest.py:12: cannot run on platform linux + SKIPPED [2] conftest.py:13: 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: @@ -619,7 +620,7 @@ 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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items / 3 deselected / 1 selected @@ -682,7 +683,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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items / 2 deselected / 2 selected @@ -708,7 +709,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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items / 1 deselected / 3 selected diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/multipython.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/multipython.py index 9005d31adddcd..861ae9e528da3 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/multipython.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/multipython.py @@ -1,14 +1,14 @@ -""" -module containing a parametrized tests testing cross-python -serialization via the pickle module. -""" +"""Module containing a parametrized tests testing cross-python serialization +via the pickle module.""" + import shutil import subprocess import textwrap import pytest -pythonlist = ["python3.5", "python3.6", "python3.7"] + +pythonlist = ["python3.9", "python3.10", "python3.11"] @pytest.fixture(params=pythonlist) @@ -33,37 +33,33 @@ def dumps(self, obj): dumpfile = self.picklefile.with_name("dump.py") dumpfile.write_text( textwrap.dedent( - r""" + rf""" import pickle - f = open({!r}, 'wb') - s = pickle.dump({!r}, f, protocol=2) + f = open({str(self.picklefile)!r}, 'wb') + s = pickle.dump({obj!r}, f, protocol=2) f.close() - """.format( - str(self.picklefile), obj - ) + """ ) ) - subprocess.check_call((self.pythonpath, str(dumpfile))) + subprocess.run((self.pythonpath, str(dumpfile)), check=True) def load_and_is_true(self, expression): loadfile = self.picklefile.with_name("load.py") loadfile.write_text( textwrap.dedent( - r""" + rf""" import pickle - f = open({!r}, 'rb') + f = open({str(self.picklefile)!r}, 'rb') obj = pickle.load(f) f.close() - res = eval({!r}) + res = eval({expression!r}) if not res: raise SystemExit(1) - """.format( - str(self.picklefile), expression - ) + """ ) ) print(loadfile) - subprocess.check_call((self.pythonpath, str(loadfile))) + subprocess.run((self.pythonpath, str(loadfile)), check=True) @pytest.mark.parametrize("obj", [42, {}, {1: 3}]) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython.rst index f79f15b4f79d3..aa463e2416bfd 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython.rst @@ -9,7 +9,7 @@ Working with non-python tests A basic example for specifying tests in Yaml files -------------------------------------------------------------- -.. _`pytest-yamlwsgi`: http://bitbucket.org/aafshar/pytest-yamlwsgi/src/tip/pytest_yamlwsgi.py +.. _`pytest-yamlwsgi`: https://pypi.org/project/pytest-yamlwsgi/ 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: @@ -28,7 +28,7 @@ now execute the test specification: nonpython $ pytest test_simple.yaml =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project/nonpython collected 2 items @@ -64,7 +64,7 @@ consulted when reporting in ``verbose`` mode: nonpython $ pytest -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project/nonpython collecting ... collected 2 items @@ -90,7 +90,7 @@ interesting to just look at the collection tree: nonpython $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project/nonpython collected 2 items diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/conftest.py index bc39a1f6b2048..e969e3e25187c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/conftest.py @@ -12,7 +12,7 @@ def collect(self): # We need a yaml parser, e.g. PyYAML. import yaml - raw = yaml.safe_load(self.path.open()) + raw = yaml.safe_load(self.path.open(encoding="utf-8")) for name, spec in sorted(raw.items()): yield YamlItem.from_parent(self, name=name, spec=spec) @@ -38,6 +38,7 @@ def repr_failure(self, excinfo): " no further details known at this point.", ] ) + return super().repr_failure(excinfo) def reportinfo(self): return self.path, 0, f"usecase: {self.name}" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/w3c-import.log new file mode 100644 index 0000000000000..67f0e99b0920c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/test_simple.yaml diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/parametrize.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/parametrize.rst index 66d72f3cc0d43..03f6852e5c0f5 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/parametrize.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/parametrize.rst @@ -4,8 +4,6 @@ Parametrizing tests ================================================= -.. currentmodule:: _pytest.python - ``pytest`` allows to easily parametrize test functions. For basic docs, see :ref:`parametrize-basics`. @@ -160,19 +158,20 @@ 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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 8 items - - - - - - - - - + + + + + + + + + + ======================== 8 tests collected in 0.12s ======================== @@ -185,7 +184,7 @@ A quick port of "testscenarios" Here is a quick port to run tests configured with :pypi:`testscenarios`, 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 @@ -222,7 +221,7 @@ 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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items @@ -236,16 +235,17 @@ 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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items - - - - - - + + + + + + + ======================== 4 tests collected in 0.12s ======================== @@ -314,13 +314,14 @@ 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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items - - - + + + + ======================== 2 tests collected in 0.12s ======================== @@ -412,7 +413,7 @@ The result of this test will be successful: $ pytest -v test_indirect_list.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 1 item @@ -483,8 +484,8 @@ argument sets to use for each test function. Let's run it: FAILED test_parametrize.py::TestClass::test_equals[1-2] - assert 1 == 2 1 failed, 2 passed in 0.12s -Indirect parametrization with multiple fixtures --------------------------------------------------------------- +Parametrization with multiple fixtures +-------------------------------------- Here is a stripped down real-life example of using parametrized testing for testing serialization of objects between different python @@ -502,15 +503,14 @@ Running it results in some skips if we don't have all the python interpreters in .. code-block:: pytest . $ pytest -rs -q multipython.py - sssssssssssssssssssssssssss [100%] + ssssssssssss...ssssssssssss [100%] ========================= short test summary info ========================== - SKIPPED [9] multipython.py:29: 'python3.5' not found - SKIPPED [9] multipython.py:29: 'python3.6' not found - SKIPPED [9] multipython.py:29: 'python3.7' not found - 27 skipped in 0.12s + SKIPPED [12] multipython.py:65: 'python3.9' not found + SKIPPED [12] multipython.py:65: 'python3.11' not found + 3 passed, 24 skipped in 0.12s -Indirect parametrization of optional implementations/imports --------------------------------------------------------------------- +Parametrization of optional implementations/imports +--------------------------------------------------- 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 @@ -567,14 +567,14 @@ If you run this with reporting for skips enabled: $ pytest -rs test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items test_module.py .s [100%] ========================= short test summary info ========================== - SKIPPED [1] conftest.py:12: could not import 'opt2': No module named 'opt2' + SKIPPED [1] test_module.py:3: 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 @@ -628,7 +628,7 @@ 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-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 24 items / 21 deselected / 3 selected @@ -657,52 +657,34 @@ Use :func:`pytest.raises` with the :ref:`pytest.mark.parametrize ref` decorator to write parametrized tests 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: +``contextlib.nullcontext`` can be used to test cases that are not expected to +raise exceptions but that should result in some value. The value is given as the +``enter_result`` parameter, which will be available as the ``with`` statement’s +target (``e`` in the example below). -.. code-block:: python +For example: - from contextlib import contextmanager - import pytest +.. code-block:: python + from contextlib import nullcontext - @contextmanager - def does_not_raise(): - yield + import pytest @pytest.mark.parametrize( "example_input,expectation", [ - (3, does_not_raise()), - (2, does_not_raise()), - (1, does_not_raise()), + (3, nullcontext(2)), + (2, nullcontext(3)), + (1, nullcontext(6)), (0, pytest.raises(ZeroDivisionError)), ], ) def test_division(example_input, expectation): """Test how much I know division.""" - with expectation: - assert (6 / example_input) is not None - -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``: - -.. code-block:: python - - from contextlib import nullcontext as does_not_raise - -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: - -.. code-block:: python + with expectation as e: + assert (6 / example_input) == e - from contextlib2 import nullcontext as does_not_raise +In the example above, the first three test cases should run without any +exceptions, while the fourth should raise a``ZeroDivisionError`` exception, +which is expected by pytest. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/pythoncollection.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/pythoncollection.rst index b9c2386ac51a6..aa9d05d722753 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/pythoncollection.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/pythoncollection.rst @@ -147,14 +147,16 @@ The test collection would look like this: $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y - rootdir: /home/sweet/project, configfile: pytest.ini + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y + rootdir: /home/sweet/project + configfile: pytest.ini collected 2 items - - - - + + + + + ======================== 2 tests collected in 0.12s ======================== @@ -208,15 +210,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-7.x.y, pluggy-1.x.y - rootdir: /home/sweet/project, configfile: pytest.ini + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y + rootdir: /home/sweet/project + configfile: pytest.ini collected 3 items - - - - - + + + + + + + ======================== 3 tests collected in 0.12s ======================== @@ -289,8 +294,9 @@ file will be left out: $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y - rootdir: /home/sweet/project, configfile: pytest.ini + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y + rootdir: /home/sweet/project + configfile: pytest.ini collected 0 items ======================= no tests collected in 0.12s ======================== diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/reportingdemo.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/reportingdemo.rst index cab9314361559..2c34cc2b00d38 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/reportingdemo.rst +++ b/LayoutTests/imported/w3c/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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project/assertion collected 44 items @@ -80,6 +80,7 @@ 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 E - eggs E + spam @@ -91,6 +92,7 @@ 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 E - foo 2 bar E ? ^ E + foo 1 bar @@ -104,6 +106,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_multiline_text(self): > assert "foo\nspam\nbar" == "foo\neggs\nbar" E AssertionError: assert 'foo\nspam\nbar' == 'foo\neggs\nbar' + E E foo E - eggs E + spam @@ -119,6 +122,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: b = "1" * 100 + "b" + "2" * 100 > assert a == b E AssertionError: assert '111111111111...2222222222222' == '111111111111...2222222222222' + E 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 - 1111111111b222222222 @@ -136,12 +140,12 @@ Here is a nice run of several failures and how ``pytest`` presents things: 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\n...n2\n2\n2\n2\n' + E 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 E 1 E 1 - E 1 E 1... E E ...Full output truncated (7 lines hidden), use '-vv' to show @@ -154,8 +158,9 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_list(self): > assert [0, 1, 2] == [0, 1, 3] E assert [0, 1, 2] == [0, 1, 3] + E E At index 2 diff: 2 != 3 - E Use -v to get the full diff + E Use -v to get more diff failure_demo.py:63: AssertionError ______________ TestSpecialisedExplanations.test_eq_list_long _______________ @@ -167,8 +172,9 @@ Here is a nice run of several failures and how ``pytest`` presents things: b = [0] * 100 + [2] + [3] * 100 > assert a == b E assert [0, 0, 0, 0, 0, 0, ...] == [0, 0, 0, 0, 0, 0, ...] + E E At index 100 diff: 1 != 2 - E Use -v to get the full diff + E Use -v to get more diff failure_demo.py:68: AssertionError _________________ TestSpecialisedExplanations.test_eq_dict _________________ @@ -178,15 +184,15 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_dict(self): > assert {"a": 0, "b": 1, "c": 0} == {"a": 0, "b": 2, "d": 0} E AssertionError: assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} + E E Omitting 1 identical items, use -vv to show E Differing items: E {'b': 1} != {'b': 2} E Left contains 1 more item: E {'c': 0} E Right contains 1 more item: - E {'d': 0}... - E - E ...Full output truncated (2 lines hidden), use '-vv' to show + E {'d': 0} + E Use -v to get more diff failure_demo.py:71: AssertionError _________________ TestSpecialisedExplanations.test_eq_set __________________ @@ -195,16 +201,16 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_set(self): > assert {0, 10, 11, 12} == {0, 20, 21} - E AssertionError: assert {0, 10, 11, 12} == {0, 20, 21} + E assert {0, 10, 11, 12} == {0, 20, 21} + E E Extra items in the left set: E 10 E 11 E 12 E Extra items in the right set: E 20 - E 21... - E - E ...Full output truncated (2 lines hidden), use '-vv' to show + E 21 + E Use -v to get more diff failure_demo.py:74: AssertionError _____________ TestSpecialisedExplanations.test_eq_longer_list ______________ @@ -214,8 +220,9 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_longer_list(self): > assert [1, 2] == [1, 2, 3] E assert [1, 2] == [1, 2, 3] + E E Right contains one more item: 3 - E Use -v to get the full diff + E Use -v to get more diff failure_demo.py:77: AssertionError _________________ TestSpecialisedExplanations.test_in_list _________________ @@ -235,15 +242,15 @@ Here is a nice run of several failures and how ``pytest`` presents things: text = "some multiline\ntext\nwhich\nincludes foo\nand a\ntail" > assert "foo" not in text E AssertionError: assert 'foo' not in 'some multil...nand a\ntail' + E E 'foo' is contained here: E some multiline E text E which E includes foo E ? +++ - E and a... - E - E ...Full output truncated (2 lines hidden), use '-vv' to show + E and a + E tail failure_demo.py:84: AssertionError ___________ TestSpecialisedExplanations.test_not_in_text_single ____________ @@ -254,6 +261,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: text = "single foo line" > assert "foo" not in text E AssertionError: assert 'foo' not in 'single foo line' + E E 'foo' is contained here: E single foo line E ? +++ @@ -267,6 +275,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: text = "head " * 50 + "foo " + "tail " * 20 > assert "foo" not in text E AssertionError: assert 'foo' not in 'head head h...l tail tail ' + E 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 ? +++ @@ -280,6 +289,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: text = "head " * 50 + "f" * 70 + "tail " * 20 > assert "f" * 70 not in text E AssertionError: assert 'fffffffffff...ffffffffffff' not in 'head head h...l tail tail ' + E 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 ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -307,9 +317,9 @@ Here is a nice run of several failures and how ``pytest`` presents things: 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 + E b: 'b' != 'c' + E - c + E + b failure_demo.py:108: AssertionError ________________ TestSpecialisedExplanations.test_eq_attrs _________________ @@ -334,9 +344,9 @@ Here is a nice run of several failures and how ``pytest`` presents things: 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 + E b: 'b' != 'c' + E - c + E + b failure_demo.py:120: AssertionError ______________________________ test_attribute ______________________________ @@ -435,7 +445,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: self = def test_tupleerror(self): - > a, b = [1] # NOQA + > a, b = [1] # noqa: F841 E ValueError: not enough values to unpack (expected 2, got 1) failure_demo.py:175: ValueError @@ -457,7 +467,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: self = def test_some_error(self): - > if namenotexi: # NOQA + > if namenotexi: # noqa: F821 E NameError: name 'namenotexi' is not defined failure_demo.py:183: NameError @@ -673,7 +683,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: 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_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 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/simple.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/simple.rst index a70f34049928a..7064f61f0e23b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/simple.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/simple.rst @@ -168,7 +168,7 @@ Now we'll get feedback on a bad argument: If you need to provide more detailed error messages, you can use the -``type`` parameter and raise ``pytest.UsageError``: +``type`` parameter and raise :exc:`pytest.UsageError`: .. code-block:: python @@ -232,7 +232,7 @@ directory with the above conftest.py: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 0 items @@ -296,7 +296,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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -312,7 +312,7 @@ Or run it including the ``slow`` marked test: $ pytest --runslow =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -342,7 +342,7 @@ Example: def checkconfig(x): __tracebackhide__ = True if not hasattr(x, "config"): - pytest.fail("not configured: {}".format(x)) + pytest.fail(f"not configured: {x}") def test_something(): @@ -376,6 +376,7 @@ this to make sure unexpected exception types aren't hidden: .. code-block:: python import operator + import pytest @@ -386,7 +387,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: {}".format(x)) + raise ConfigException(f"not configured: {x}") def test_something(): @@ -455,7 +456,7 @@ which will add the string to the test header accordingly: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y project deps: mylib-1.1 rootdir: /home/sweet/project collected 0 items @@ -483,7 +484,7 @@ which will add info only when run with "--v": $ pytest -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache info1: did you know that ... did you? @@ -498,7 +499,7 @@ and nothing when run plainly: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 0 items @@ -537,7 +538,7 @@ Now we can profile which test functions execute the slowest: $ pytest --durations=3 =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 3 items @@ -565,6 +566,7 @@ 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) @@ -608,7 +610,7 @@ an ``incremental`` marker which is to be used on classes: 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)) + pytest.xfail(f"previous test failed ({test_name})") These two hook implementations work together to abort incremental-marked @@ -642,7 +644,7 @@ If we run this: $ pytest -rx =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items @@ -658,9 +660,33 @@ If we run this: E assert 0 test_step.py:11: AssertionError + ================================ XFAILURES ================================= + ______________________ TestUserHandling.test_deletion ______________________ + + item = + + def pytest_runtest_setup(item): + if "incremental" in item.keywords: + # 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(f"previous test failed ({test_name})") + E _pytest.outcomes.XFailed: previous test failed (test_modification) + + conftest.py:47: XFailed ========================= short test summary info ========================== - XFAIL test_step.py::TestUserHandling::test_deletion - reason: previous test failed (test_modification) + XFAIL test_step.py::TestUserHandling::test_deletion - reason: previous test failed (test_modification) ================== 1 failed, 2 passed, 1 xfailed in 0.12s ================== We'll see that ``test_deletion`` was not executed because ``test_modification`` @@ -690,7 +716,7 @@ Here is an example for making a ``db`` fixture available in a directory: pass - @pytest.fixture(scope="session") + @pytest.fixture(scope="package") def db(): return DB() @@ -725,14 +751,14 @@ We can run this: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 7 items - test_step.py .Fx. [ 57%] - a/test_db.py F [ 71%] - a/test_db2.py F [ 85%] - b/test_error.py E [100%] + a/test_db.py F [ 14%] + a/test_db2.py F [ 28%] + b/test_error.py E [ 42%] + test_step.py .Fx. [100%] ================================== ERRORS ================================== _______________________ ERROR at setup of test_root ________________________ @@ -744,39 +770,39 @@ We can run this: /home/sweet/project/b/test_error.py:1 ================================= FAILURES ================================= - ____________________ TestUserHandling.test_modification ____________________ - - self = - - def test_modification(self): - > assert 0 - E assert 0 - - test_step.py:11: AssertionError _________________________________ test_a1 __________________________________ - db = + db = def test_a1(db): > assert 0, db # to show value - E AssertionError: + E AssertionError: E assert 0 a/test_db.py:2: AssertionError _________________________________ test_a2 __________________________________ - db = + db = def test_a2(db): > assert 0, db # to show value - E AssertionError: + E AssertionError: E assert 0 a/test_db2.py:2: AssertionError + ____________________ TestUserHandling.test_modification ____________________ + + self = + + def test_modification(self): + > assert 0 + E assert 0 + + test_step.py:11: AssertionError ========================= short test summary info ========================== - FAILED test_step.py::TestUserHandling::test_modification - assert 0 FAILED a/test_db.py::test_a1 - AssertionError: 1 and sys.argv[1] == "--pytest": @@ -1083,4 +1115,4 @@ application with standard ``pytest`` command-line options: .. code-block:: bash - ./app_main --pytest --verbose --tb=long --junitxml=results.xml test-suite/ + ./app_main --pytest --verbose --tb=long --junit=xml=results.xml test-suite/ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/w3c-import.log new file mode 100644 index 0000000000000..9a3d1f9dd70b7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/w3c-import.log @@ -0,0 +1,30 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/attic.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/customdirectory.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/markers.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/multipython.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/parametrize.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/pythoncollection.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/pythoncollection.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/reportingdemo.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/simple.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/special.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/xfail_demo.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/xfail_demo.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/xfail_demo.py index 01e6da1ad2ed2..1040c89298d07 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/xfail_demo.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/example/xfail_demo.py @@ -1,5 +1,6 @@ import pytest + xfail = pytest.mark.xfail diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/anatomy.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/anatomy.rst index e86dd74251e84..93d3400dae2b3 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/anatomy.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/anatomy.rst @@ -34,7 +34,7 @@ a function/method call. **Assert** is where we look at that resulting state and check if it looks how we'd expect after the dust has settled. It's where we gather evidence to say the -behavior does or does not aligns with what we expect. The ``assert`` in our test +behavior does or does not align with what we expect. The ``assert`` in our test is where we take that measurement/observation and apply our judgement to it. If something should be green, we'd say ``assert thing == "green"``. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/fixtures.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/fixtures.rst index 194e576493e5a..0bb3bf49fb097 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/fixtures.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/fixtures.rst @@ -85,7 +85,7 @@ style of setup/teardown functions: In addition, pytest continues to support :ref:`xunitsetup`. You can mix both styles, moving incrementally from classic to new style, as you prefer. You can also start out from existing :ref:`unittest.TestCase -style ` or :ref:`nose based ` projects. +style `. @@ -162,7 +162,7 @@ A note about fixture cleanup ---------------------------- pytest does not do any special processing for :data:`SIGTERM ` and -:data:`SIGQUIT ` signals (:data:`SIGINT ` is handled naturally +``SIGQUIT`` signals (:data:`SIGINT ` is handled naturally by the Python runtime via :class:`KeyboardInterrupt`), so fixtures that manage external resources which are important to be cleared when the Python process is terminated (by those signals) might leak resources. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/flaky.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/flaky.rst index 50121c7a761bd..41cbe8479895b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/flaky.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/flaky.rst @@ -52,10 +52,9 @@ Plugins Rerunning any failed tests can mitigate the negative effects of flaky tests by giving them additional chances to pass, so that the overall build does not fail. Several pytest plugins support this: -* `flaky `_ -* `pytest-flakefinder `_ - `blog post `_ * `pytest-rerunfailures `_ * `pytest-replay `_: This plugin helps to reproduce locally crashes or flaky tests observed during CI runs. +* `pytest-flakefinder `_ - `blog post `_ Plugins to deliberately randomize tests can help expose tests with state problems: @@ -94,7 +93,7 @@ Mark Lapierre discusses the `Pros and Cons of Quarantined Tests `_ and rerun failed tests. +Azure Pipelines (the Azure cloud CI/CD tool, formerly Visual Studio Team Services or VSTS) has a feature to `identify flaky tests `_ and rerun failed tests. @@ -106,7 +105,7 @@ This is a limited list, please submit an issue or pull request to expand it! * Gao, Zebao, Yalan Liang, Myra B. Cohen, Atif M. Memon, and Zhen Wang. "Making system user interactive tests repeatable: When and what should we control?." In *Software Engineering (ICSE), 2015 IEEE/ACM 37th IEEE International Conference on*, vol. 1, pp. 55-65. IEEE, 2015. `PDF `__ * Palomba, Fabio, and Andy Zaidman. "Does refactoring of test smells induce fixing flaky tests?." In *Software Maintenance and Evolution (ICSME), 2017 IEEE International Conference on*, pp. 1-12. IEEE, 2017. `PDF in Google Drive `__ * Bell, Jonathan, Owolabi Legunsen, Michael Hilton, Lamyaa Eloussi, Tifany Yung, and Darko Marinov. "DeFlaker: Automatically detecting flaky tests." In *Proceedings of the 2018 International Conference on Software Engineering*. 2018. `PDF `__ - +* Dutta, Saikat and Shi, August and Choudhary, Rutvik and Zhang, Zhekun and Jain, Aryaman and Misailovic, Sasa. "Detecting flaky tests in probabilistic and machine learning applications." In *Proceedings of the 29th ACM SIGSOFT International Symposium on Software Testing and Analysis (ISSTA)*, pp. 211-224. ACM, 2020. `PDF `__ Resources ^^^^^^^^^ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/goodpractices.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/goodpractices.rst index 32a14991ae2a8..1390ba4e8fedb 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/goodpractices.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/goodpractices.rst @@ -12,41 +12,27 @@ For development, we recommend you use :mod:`venv` for virtual environments and as well as the ``pytest`` package itself. This ensures your code and dependencies are isolated from your system Python installation. -Next, place a ``pyproject.toml`` file in the root of your package: +Create a ``pyproject.toml`` file in the root of your repository as described in +:doc:`packaging:tutorials/packaging-projects`. +The first few lines should look like this: .. code-block:: toml [build-system] - requires = ["setuptools>=42", "wheel"] - build-backend = "setuptools.build_meta" + requires = ["hatchling"] + build-backend = "hatchling.build" -and a ``setup.cfg`` file containing your package's metadata with the following minimum content: + [project] + name = "PACKAGENAME" + version = "PACKAGEVERSION" -.. code-block:: ini - - [metadata] - name = PACKAGENAME - - [options] - packages = find: - -where ``PACKAGENAME`` is the name of your package. - -.. note:: - - If your pip version is older than ``21.3``, you'll also need a ``setup.py`` file: - - .. code-block:: python - - from setuptools import setup - - setup() +where ``PACKAGENAME`` and ``PACKAGEVERSION`` are the name and version of your package respectively. You can then install your package in "editable" mode by running from the same directory: .. code-block:: bash - pip install -e . + pip install -e . which lets you change your source code (both tests and application) and rerun tests at will. @@ -65,8 +51,8 @@ Conventions for Python test discovery * In those directories, search for ``test_*.py`` or ``*_test.py`` files, imported by their `test package name`_. * From those files, collect test items: - * ``test`` prefixed test functions or methods outside of class - * ``test`` prefixed test functions or methods inside ``Test`` prefixed test classes (without an ``__init__`` method) + * ``test`` prefixed test functions or methods outside of class. + * ``test`` prefixed test functions or methods inside ``Test`` prefixed test classes (without an ``__init__`` method). Methods decorated with ``@staticmethod`` and ``@classmethods`` are also considered. For examples of how to customize your test discovery :doc:`/example/pythoncollection`. @@ -74,8 +60,10 @@ Within Python modules, ``pytest`` also discovers tests using the standard :ref:`unittest.TestCase ` subclassing technique. -Choosing a test layout / import rules -------------------------------------- +.. _`test layout`: + +Choosing a test layout +---------------------- ``pytest`` supports two common test layouts: @@ -89,11 +77,11 @@ to keep tests separate from actual application code (often a good idea): .. code-block:: text pyproject.toml - setup.cfg - mypkg/ - __init__.py - app.py - view.py + src/ + mypkg/ + __init__.py + app.py + view.py tests/ test_app.py test_view.py @@ -103,83 +91,56 @@ This has the following benefits: * Your tests can run against an installed version after executing ``pip install .``. * Your tests can run against the local copy with an editable install after executing ``pip install --editable .``. -* If you don't use an editable install and are relying on the fact that Python by default puts the current - directory in ``sys.path`` to import your package, you can execute ``python -m pytest`` to execute the tests against the - local copy directly, without using ``pip``. -.. note:: +For new projects, we recommend to use ``importlib`` :ref:`import mode ` +(see which-import-mode_ for a detailed explanation). +To this end, add the following to your ``pyproject.toml``: - See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and - ``python -m pytest``. +.. code-block:: toml -Note that this scheme has a drawback if you are using ``prepend`` :ref:`import mode ` -(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``. + [tool.pytest.ini_options] + addopts = [ + "--import-mode=importlib", + ] -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: +.. _src-layout: -.. code-block:: text +Generally, but especially if you use the default import mode ``prepend``, +it is **strongly** suggested to use a ``src`` layout. +Here, your application root package resides in a sub-directory of your root, +i.e. ``src/mypkg/`` instead of ``mypkg``. - pyproject.toml - setup.cfg - mypkg/ - ... - tests/ - __init__.py - foo/ - __init__.py - test_view.py - bar/ - __init__.py - test_view.py +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ș. -Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``, allowing -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. +.. _blog post: https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure> -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. +.. note:: -.. _`src-layout`: + If you do not use an editable install and use the ``src`` layout as above you need to extend the Python's + search path for module files to execute the tests against the local copy directly. You can do it in an + ad-hoc manner by setting the ``PYTHONPATH`` environment variable: -In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a -sub-directory of your root: + .. code-block:: bash -.. code-block:: text + PYTHONPATH=src pytest - pyproject.toml - setup.cfg - src/ - mypkg/ - __init__.py - app.py - view.py - tests/ - __init__.py - foo/ - __init__.py - test_view.py - bar/ - __init__.py - test_view.py + or in a permanent manner by using the :confval:`pythonpath` configuration variable and adding the + following to your ``pyproject.toml``: + .. code-block:: toml -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ș `_. + [tool.pytest.ini_options] + pythonpath = "src" .. note:: - The new ``--import-mode=importlib`` (see :ref:`import-modes`) doesn't have - any of the drawbacks above because ``sys.path`` is 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. + If you do not use an editable install and not use the ``src`` layout (``mypkg`` directly in the root + directory) you can rely on the fact that Python by default puts the current directory in ``sys.path`` to + import your package and run ``python -m pytest`` to execute the tests against the local copy directly. + See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and + ``python -m pytest``. Tests as part of application code ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -191,12 +152,11 @@ want to distribute them along with your application: .. code-block:: text pyproject.toml - setup.cfg - mypkg/ + [src/]mypkg/ __init__.py app.py view.py - test/ + tests/ __init__.py test_app.py test_view.py @@ -254,6 +214,56 @@ Note that this layout also works in conjunction with the ``src`` layout mentione much less surprising. +.. _which-import-mode: + +Choosing an import mode +^^^^^^^^^^^^^^^^^^^^^^^ + +For historical reasons, pytest defaults to the ``prepend`` :ref:`import mode ` +instead of the ``importlib`` import mode we recommend for new projects. +The reason lies in the way the ``prepend`` mode works: + +Since there are no packages to derive a full package name from, +``pytest`` will import your test files as *top-level* modules. +The test files in the first example (:ref:`src layout `) would be imported as +``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to ``sys.path``. + +This results in a drawback compared to the import mode ``importlib``: +your test files must have **unique names**. + +If you need to have test modules with the same name, +as a workaround you might add ``__init__.py`` files to your ``tests`` folder and subfolders, +changing them to packages: + +.. code-block:: text + + pyproject.toml + mypkg/ + ... + tests/ + __init__.py + foo/ + __init__.py + test_view.py + bar/ + __init__.py + test_view.py + +Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``, +allowing 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. + +The ``importlib`` import mode does not have any of the drawbacks above, +because ``sys.path`` is not changed when importing test modules. + + .. _`buildout`: http://www.buildout.org/en/latest/ .. _`use tox`: @@ -263,8 +273,8 @@ tox Once you are done with your work and want to make sure that your actual package passes all tests you may want to look into :doc:`tox `, the -virtualenv test automation tool and its :doc:`pytest support `. -tox helps you to setup virtualenv environments with pre-defined +virtualenv test automation tool. +``tox`` helps you to setup virtualenv environments with pre-defined dependencies and then executing a pre-configured test command with options. It will run tests against the installed package and not against your source code checkout, helping to detect packaging @@ -286,3 +296,20 @@ See also `pypa/setuptools#1684 ` setuptools intends to `remove the test command `_. + +Checking with flake8-pytest-style +--------------------------------- + +In order to ensure that pytest is being used correctly in your project, +it can be helpful to use the `flake8-pytest-style `_ flake8 plugin. + +flake8-pytest-style checks for common mistakes and coding style violations in pytest code, +such as incorrect use of fixtures, test function names, and markers. +By using this plugin, you can catch these errors early in the development process +and ensure that your pytest code is consistent and easy to maintain. + +A list of the lints detected by flake8-pytest-style can be found on its `PyPI page `_. + +.. note:: + + flake8-pytest-style is not an official pytest project. Some of the rules enforce certain style choices, such as using `@pytest.fixture()` over `@pytest.fixture`, but you can configure the plugin to fit your preferred style. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/pythonpath.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/pythonpath.rst index 2330356b863cf..33eba86b57a50 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/pythonpath.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/pythonpath.rst @@ -10,21 +10,29 @@ 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 :data:`sys.path`. Some aspects of the +Importing files in Python is a non-trivial processes, so aspects of the import process can be controlled through the ``--import-mode`` command-line flag, which can assume these values: +.. _`import-mode-prepend`: + * ``prepend`` (default): the directory path containing each module will be inserted into the *beginning* - of :py:data:`sys.path` if not already there, and then imported with the :func:`__import__ <__import__>` builtin. + of :py:data:`sys.path` if not already there, and then imported with + the :func:`importlib.import_module ` function. + + It is highly recommended to arrange your test modules as packages by adding ``__init__.py`` files to your directories + containing tests. This will make the tests part of a proper Python package, allowing pytest to resolve their full + name (for example ``tests.core.test_core`` for ``test_core.py`` inside the ``tests.core`` package). - This requires test module names to be unique when the test directory tree is not arranged in - packages, because the modules will put in :py:data:`sys.modules` after importing. + If the test directory tree is not arranged as packages, then each test file needs to have a unique name + compared to the other test files, otherwise pytest will raise an error if it finds two tests with the same name. This is the classic mechanism, dating back from the time Python 2 was still supported. +.. _`import-mode-append`: + * ``append``: the directory containing each module is appended to the end of :py:data:`sys.path` if not already - there, and imported with ``__import__``. + there, and imported with :func:`importlib.import_module `. 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: @@ -38,23 +46,78 @@ these values: 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 ` layouts. + we advocate for using :ref:`src-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 :py:data:`sys.modules` after importing. -* ``importlib``: new in pytest-6.0, this mode uses :mod:`importlib` to import test modules. This gives full control over the import process, and doesn't require changing :py:data:`sys.path`. +.. _`import-mode-importlib`: + +* ``importlib``: this mode uses more fine control mechanisms provided by :mod:`importlib` to import test modules, without changing :py:data:`sys.path`. + + Advantages of this mode: + + * pytest will not change :py:data:`sys.path` at all. + * Test module names do not need to be unique -- pytest will generate a unique name automatically based on the ``rootdir``. + + Disadvantages: + + * Test modules can't import each other. + * Testing utility modules in the tests directories (for example a ``tests.helpers`` module containing test-related functions/classes) + are not importable. The recommendation in this case it to place testing utility modules together with the application/library + code, for example ``app.testing.helpers``. + + Important: by "test utility modules" we mean functions/classes which are imported by + other tests directly; this does not include fixtures, which should be placed in ``conftest.py`` files, along + with the test modules, and are discovered automatically by pytest. + + It works like this: + + 1. Given a certain module path, for example ``tests/core/test_models.py``, derives a canonical name + like ``tests.core.test_models`` and tries to import it. + + For non-test modules this will work if they are accessible via :py:data:`sys.path`, so + for example ``.env/lib/site-packages/app/core.py`` will be importable as ``app.core``. + This is happens when plugins import non-test modules (for example doctesting). + + If this step succeeds, the module is returned. + + For test modules, unless they are reachable from :py:data:`sys.path`, this step will fail. + + 2. If the previous step fails, we import the module directly using ``importlib`` facilities, which lets us import it without + changing :py:data:`sys.path`. + + Because Python requires the module to also be available in :py:data:`sys.modules`, pytest derives a unique name for it based + on its relative location from the ``rootdir``, and adds the module to :py:data:`sys.modules`. + + For example, ``tests/core/test_models.py`` will end up being imported as the module ``tests.core.test_models``. + + .. versionadded:: 6.0 + +.. note:: + + Initially we intended to make ``importlib`` the default in future releases, however it is clear now that + it has its own set of drawbacks so the default will remain ``prepend`` for the foreseeable future. + +.. note:: + + By default, pytest will not attempt to resolve namespace packages automatically, but that can + be changed via the :confval:`consider_namespace_packages` configuration variable. + +.. seealso:: + + The :confval:`pythonpath` configuration variable. + + The :confval:`consider_namespace_packages` configuration variable. - For this reason this doesn't require test module names to be unique, but also makes test - modules non-importable by each other. + :ref:`test layout`. - We intend to make ``importlib`` the default in future releases, depending on feedback. ``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 +change :py:data:`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 @@ -83,7 +146,7 @@ pytest will find ``foo/bar/tests/test_foo.py`` and realize it is part of a packa there's an ``__init__.py`` file in the same folder. It will then search upwards until it can find the last folder which still contains an ``__init__.py`` file in order to find the package *root* (in this case ``foo/``). To load the module, it will insert ``root/`` to the front of -``sys.path`` (if not there already) in order to load +:py:data:`sys.path` (if not there already) in order to load ``test_foo.py`` as the *module* ``foo.bar.tests.test_foo``. The same logic applies to the ``conftest.py`` file: it will be imported as ``foo.conftest`` module. @@ -113,8 +176,8 @@ When executing: pytest will find ``foo/bar/tests/test_foo.py`` and realize it is NOT part of a package given that there's no ``__init__.py`` file in the same folder. It will then add ``root/foo/bar/tests`` to -``sys.path`` in order to import ``test_foo.py`` as the *module* ``test_foo``. The same is done -with the ``conftest.py`` file by adding ``root/foo`` to ``sys.path`` to import it as ``conftest``. +:py:data:`sys.path` in order to import ``test_foo.py`` as the *module* ``test_foo``. The same is done +with the ``conftest.py`` file by adding ``root/foo`` to :py:data:`sys.path` to import it as ``conftest``. For this reason this layout cannot have test modules with the same name, as they all will be imported in the global import namespace. @@ -127,7 +190,7 @@ Invoking ``pytest`` versus ``python -m pytest`` ----------------------------------------------- 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 +equivalent behaviour, except that the latter will add the current directory to :py:data:`sys.path`, which is standard ``python`` behavior. See also :ref:`invoke-python`. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/w3c-import.log new file mode 100644 index 0000000000000..91f6f758aa5b5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/anatomy.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/fixtures.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/flaky.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/goodpractices.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/explanation/pythonpath.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/funcarg_compare.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/funcarg_compare.rst index 3bf4527cfb526..8b900d30f2000 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/funcarg_compare.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/funcarg_compare.rst @@ -11,8 +11,6 @@ funcarg mechanism, see :ref:`historical funcargs and pytest.funcargs`. If you are new to pytest, then you can simply ignore this section and read the other sections. -.. currentmodule:: _pytest - Shortcomings of the previous ``pytest_funcarg__`` mechanism -------------------------------------------------------------- @@ -46,7 +44,7 @@ There are several limitations and difficulties with this approach: 2. parametrizing the "db" resource is not straight forward: you need to apply a "parametrize" decorator or implement a - :py:func:`~hookspec.pytest_generate_tests` hook + :hook:`pytest_generate_tests` hook calling :py:func:`~pytest.Metafunc.parametrize` which performs parametrization at the places where the resource is used. Moreover, you need to modify the factory to use an @@ -94,15 +92,14 @@ Direct parametrization of funcarg resource factories 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 +or implement a :hook:`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: .. code-block:: python @pytest.fixture(params=["mysql", "pg"]) - def db(request): - ... # use request.param + def db(request): ... # 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 @@ -143,8 +140,7 @@ argument: .. code-block:: python @pytest.fixture() - def db(request): - ... + def db(request): ... The name under which the funcarg resource can be requested is ``db``. @@ -153,8 +149,7 @@ aka: .. code-block:: python - def pytest_funcarg__db(request): - ... + def pytest_funcarg__db(request): ... But it is then not possible to define scoping and parametrization. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/getting-started.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/getting-started.rst index 5d13a768069e5..94e0d80e656d1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/getting-started.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/getting-started.rst @@ -9,7 +9,7 @@ Get Started Install ``pytest`` ---------------------------------------- -``pytest`` requires: Python 3.6, 3.7, 3.8, 3.9, or PyPy3. +``pytest`` requires: Python 3.8+ or PyPy3. 1. Run the following command in your command line: @@ -22,7 +22,7 @@ Install ``pytest`` .. code-block:: bash $ pytest --version - pytest 7.0.1 + pytest 8.2.1 .. _`simpletest`: @@ -47,7 +47,7 @@ The test $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -97,6 +97,30 @@ Use the :ref:`raises ` helper to assert that some code raises an e with pytest.raises(SystemExit): f() +You can also use the context provided by :ref:`raises ` to +assert that an expected exception is part of a raised :class:`ExceptionGroup`: + +.. code-block:: python + + # content of test_exceptiongroup.py + import pytest + + + def f(): + raise ExceptionGroup( + "Group message", + [ + RuntimeError(), + ], + ) + + + def test_exception_in_group(): + with pytest.raises(ExceptionGroup) as excinfo: + f() + assert excinfo.group_contains(RuntimeError) + assert not excinfo.group_contains(TypeError) + Execute the test function with “quiet” reporting mode: .. code-block:: pytest @@ -250,7 +274,7 @@ Continue reading Check out additional pytest resources to help you customize tests for your unique workflow: * ":ref:`usage`" for command line invocation examples -* ":ref:`existingtestsuite`" for working with pre-existing tests +* ":ref:`existingtestsuite`" for working with preexisting tests * ":ref:`mark`" for information on the ``pytest.mark`` mechanism * ":ref:`fixtures`" for providing a functional baseline to your tests * ":ref:`plugins`" for managing and writing plugins diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/historical-notes.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/historical-notes.rst index 29ebbd5d19951..5eb527c582ba0 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/historical-notes.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/historical-notes.rst @@ -112,7 +112,7 @@ More details can be found in the :pull:`original PR <3317>`. .. note:: 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`. + at which point markers will no longer be limited to instances of :py:class:`~pytest.Mark`. cache plugin integrated into the core @@ -227,8 +227,7 @@ to use strings: @pytest.mark.skipif("sys.version_info >= (3,3)") - def test_function(): - ... + def test_function(): ... During test function setup the skipif condition is evaluated by calling ``eval('sys.version_info >= (3,0)', namespace)``. The namespace contains @@ -262,8 +261,7 @@ configuration value which you might have added: .. code-block:: python @pytest.mark.skipif("not config.getvalue('db')") - def test_function(): - ... + def test_function(): ... The equivalent with "boolean conditions" is: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/assert.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/assert.rst index cb70db6b8edf9..7b02774469584 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/assert.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/assert.rst @@ -29,7 +29,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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -54,14 +54,13 @@ operators. (See :ref:`tbreportdemo`). This allows you to use the idiomatic python constructs without boilerplate code while not losing introspection information. -However, if you specify a message with the assertion like this: +If a message is specified with the assertion like this: .. code-block:: python assert a % 2 == 0, "value was odd, should be even" -then no assertion introspection takes places at all and the message -will be simply shown in the traceback. +it is printed alongside the assertion introspection in the traceback. See :ref:`assert-details` for more information on assertion introspection. @@ -99,6 +98,27 @@ and if you need to have access to the actual exception info you may use: the actual exception raised. The main attributes of interest are ``.type``, ``.value`` and ``.traceback``. +Note that ``pytest.raises`` will match the exception type or any subclasses (like the standard ``except`` statement). +If you want to check if a block of code is raising an exact exception type, you need to check that explicitly: + + +.. code-block:: python + + def test_foo_not_implemented(): + def foo(): + raise NotImplementedError + + with pytest.raises(RuntimeError) as excinfo: + foo() + assert excinfo.type is RuntimeError + +The :func:`pytest.raises` call will succeed, even though the function raises :class:`NotImplementedError`, because +:class:`NotImplementedError` is a subclass of :class:`RuntimeError`; however the following `assert` statement will +catch the problem. + +Matching exception messages +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + You can pass a ``match`` keyword parameter to the context-manager to test that a regular expression matches on the string representation of an exception (similar to the ``TestCase.assertRaisesRegex`` method from ``unittest``): @@ -116,36 +136,113 @@ that a regular expression matches on the string representation of an exception with pytest.raises(ValueError, match=r".* 123 .*"): myfunc() -The regexp parameter of the ``match`` method is matched with the ``re.search`` -function, so in the above example ``match='123'`` would have worked as -well. +Notes: + +* The ``match`` parameter is matched with the :func:`re.search` + function, so in the above example ``match='123'`` would have worked as well. +* The ``match`` parameter also matches against `PEP-678 `__ ``__notes__``. + + +.. _`assert-matching-exception-groups`: -There's an alternate form of the :func:`pytest.raises` function where you pass -a function that will be executed with the given ``*args`` and ``**kwargs`` and -assert that the given exception is raised: +Matching exception groups +~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also use the :func:`excinfo.group_contains() ` +method to test for exceptions returned as part of an :class:`ExceptionGroup`: .. code-block:: python - pytest.raises(ExpectedException, func, *args, **kwargs) + def test_exception_in_group(): + with pytest.raises(ExceptionGroup) as excinfo: + raise ExceptionGroup( + "Group message", + [ + RuntimeError("Exception 123 raised"), + ], + ) + assert excinfo.group_contains(RuntimeError, match=r".* 123 .*") + assert not excinfo.group_contains(TypeError) + +The optional ``match`` keyword parameter works the same way as for +:func:`pytest.raises`. + +By default ``group_contains()`` will recursively search for a matching +exception at any level of nested ``ExceptionGroup`` instances. You can +specify a ``depth`` keyword parameter if you only want to match an +exception at a specific level; exceptions contained directly in the top +``ExceptionGroup`` would match ``depth=1``. + +.. code-block:: python + + def test_exception_in_group_at_given_depth(): + with pytest.raises(ExceptionGroup) as excinfo: + raise ExceptionGroup( + "Group message", + [ + RuntimeError(), + ExceptionGroup( + "Nested group", + [ + TypeError(), + ], + ), + ], + ) + assert excinfo.group_contains(RuntimeError, depth=1) + assert excinfo.group_contains(TypeError, depth=2) + assert not excinfo.group_contains(RuntimeError, depth=2) + assert not excinfo.group_contains(TypeError, depth=1) + +Alternate form (legacy) +~~~~~~~~~~~~~~~~~~~~~~~ + +There is an alternate form where you pass +a function that will be executed, along ``*args`` and ``**kwargs``, and :func:`pytest.raises` +will execute the function with the arguments and assert that the given exception is raised: + +.. code-block:: python + + def func(x): + if x <= 0: + raise ValueError("x needs to be larger than zero") + + + pytest.raises(ValueError, func, x=-1) The reporter will provide you with helpful output in case of failures such as *no exception* or *wrong exception*. -Note that it is also possible to specify a "raises" argument to -``pytest.mark.xfail``, which checks that the test is failing in a more +This form was the original :func:`pytest.raises` API, developed before the ``with`` statement was +added to the Python language. Nowadays, this form is rarely used, with the context-manager form (using ``with``) +being considered more readable. +Nonetheless, this form is fully supported and not deprecated in any way. + +xfail mark and pytest.raises +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is also possible to specify a ``raises`` argument to +:ref:`pytest.mark.xfail `, which checks that the test is failing in a more specific way than just having any exception raised: .. code-block:: python + def f(): + raise IndexError() + + @pytest.mark.xfail(raises=IndexError) def test_f(): f() -Using :func:`pytest.raises` is likely to be better for cases where you are -testing exceptions your own code is deliberately raising, whereas using -``@pytest.mark.xfail`` with a check function is probably better for something -like documenting unfixed bugs (where the test describes what "should" happen) -or bugs in dependencies. + +This will only "xfail" if the test fails by raising ``IndexError`` or subclasses. + +* Using :ref:`pytest.mark.xfail ` with the ``raises`` parameter is probably better for something + like documenting unfixed bugs (where the test describes what "should" happen) or bugs in dependencies. + +* Using :func:`pytest.raises` is likely to be better for cases where you are + testing exceptions your own code is deliberately raising, which is the majority of cases. .. _`assertwarns`: @@ -183,7 +280,7 @@ if you run this module: $ pytest test_assert2.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -197,11 +294,12 @@ if you run this module: set2 = set("8035") > assert set1 == set2 E AssertionError: assert {'0', '1', '3', '8'} == {'0', '3', '5', '8'} + E E Extra items in the left set: E '1' E Extra items in the right set: E '5' - E Use -v to get the full diff + E Use -v to get more diff test_assert2.py:4: AssertionError ========================= short test summary info ========================== @@ -238,7 +336,7 @@ file which provides an alternative explanation for ``Foo`` objects: if isinstance(left, Foo) and isinstance(right, Foo) and op == "==": return [ "Comparing Foo instances:", - " vals: {} != {}".format(left.val, right.val), + f" vals: {left.val} != {right.val}", ] now, given this test module: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/bash-completion.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/bash-completion.rst index 245dfd6d9a8f1..117ff7ec13b28 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/bash-completion.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/bash-completion.rst @@ -5,7 +5,7 @@ How to set up bash completion ============================= When using bash as your shell, ``pytest`` can use argcomplete -(https://argcomplete.readthedocs.io/) for auto-completion. +(https://kislyuk.github.io/argcomplete/) for auto-completion. For this ``argcomplete`` needs to be installed **and** enabled. Install argcomplete using: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/cache.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/cache.rst index e7994645dd30a..40cd3f00dd662 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/cache.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/cache.rst @@ -86,7 +86,7 @@ If you then run it with ``--lf``: $ pytest --lf =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items run-last-failure: rerun previous 2 failures @@ -132,7 +132,7 @@ of ``FF`` and dots): $ pytest --ff =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 50 items run-last-failure: rerun previous 2 failures first @@ -176,14 +176,21 @@ with more recent files coming first. Behavior when no tests failed in the last run --------------------------------------------- -When no tests failed in the last run, or when no cached ``lastfailed`` data was -found, ``pytest`` can be configured either to run all of the tests or no tests, -using the ``--last-failed-no-failures`` option, which takes one of the following values: +The ``--lfnf/--last-failed-no-failures`` option governs the behavior of ``--last-failed``. +Determines whether to execute tests when there are no previously (known) +failures or when no cached ``lastfailed`` data was found. + +There are two options: + +* ``all``: when there are no known test failures, runs all tests (the full test suite). This is the default. +* ``none``: when there are no known test failures, just emits a message stating this and exit successfully. + +Example: .. code-block:: bash - pytest --last-failed --last-failed-no-failures all # run all tests (default behavior) - pytest --last-failed --last-failed-no-failures none # run no tests and exit + pytest --last-failed --last-failed-no-failures all # runs the full test suite (default behavior) + pytest --last-failed --last-failed-no-failures none # runs no tests and exits successfully The new config.cache object -------------------------------- @@ -199,7 +206,6 @@ across pytest invocations: # content of test_caching.py import pytest - import time def expensive_computation(): @@ -207,12 +213,12 @@ across pytest invocations: @pytest.fixture - def mydata(request): - val = request.config.cache.get("example/value", None) + def mydata(pytestconfig): + val = pytestconfig.cache.get("example/value", None) if val is None: expensive_computation() val = 42 - request.config.cache.set("example/value", val) + pytestconfig.cache.set("example/value", val) return val @@ -234,7 +240,7 @@ 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:20: AssertionError + test_caching.py:19: AssertionError -------------------------- Captured stdout setup --------------------------- running expensive computation... ========================= short test summary info ========================== @@ -257,7 +263,7 @@ the cache and nothing will be printed: > assert mydata == 23 E assert 42 == 23 - test_caching.py:20: AssertionError + test_caching.py:19: AssertionError ========================= short test summary info ========================== FAILED test_caching.py::test_function - assert 42 == 23 1 failed in 0.12s @@ -275,7 +281,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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project cachedir: /home/sweet/project/.pytest_cache --------------------------- cache values for '*' --------------------------- @@ -297,7 +303,7 @@ filtering: $ pytest --cache-show example/* =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project cachedir: /home/sweet/project/.pytest_cache ----------------------- cache values for 'example/*' ----------------------- diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/capture-stdout-stderr.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/capture-stdout-stderr.rst index 9ccea719b6477..5e23f0c024e7e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/capture-stdout-stderr.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/capture-stdout-stderr.rst @@ -83,7 +83,7 @@ of the failing function and hide the other one: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/capture-warnings.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/capture-warnings.rst index 065c11e610cfb..afabad5da1438 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/capture-warnings.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/capture-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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -42,6 +42,8 @@ Running pytest now produces this output: -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html ======================= 1 passed, 1 warning in 0.12s ======================= +.. _`controlling-warnings`: + Controlling warnings -------------------- @@ -107,6 +109,18 @@ When a warning matches more than one option in the list, the action for the last is performed. +.. note:: + + The ``-W`` flag and the ``filterwarnings`` ini option use warning filters that are + similar in structure, but each configuration option interprets its filter + differently. For example, *message* in ``filterwarnings`` is a string containing a + regular expression that the start of the warning message must match, + case-insensitively, while *message* in ``-W`` is a literal string that the start of + the warning message must contain (case-insensitively), ignoring any whitespace at + the start or end of message. Consult the `warning filter`_ documentation for more + details. + + .. _`filterwarnings`: ``@pytest.mark.filterwarnings`` @@ -176,11 +190,14 @@ using an external system. DeprecationWarning and PendingDeprecationWarning ------------------------------------------------ - By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning`` warnings from user code and third-party libraries, as recommended by :pep:`565`. This helps users keep their code modern and avoid breakages when deprecated warnings are effectively removed. +However, in the specific case where users capture any type of warnings in their test, either with +:func:`pytest.warns`, :func:`pytest.deprecated_call` or using the :ref:`recwarn ` fixture, +no warning will be displayed at all. + Sometimes it is useful to hide some specific deprecation warnings that happen in code that you have no control over (such as third-party libraries), in which case you might use the warning filters options (ini or marks) to ignore those warnings. @@ -197,6 +214,9 @@ For example: This will ignore all warnings of type ``DeprecationWarning`` where the start of the message matches the regular expression ``".*U.*mode is deprecated"``. +See :ref:`@pytest.mark.filterwarnings ` and +:ref:`Controlling warnings ` for more examples. + .. note:: If warnings are configured at the interpreter level, using @@ -245,14 +265,15 @@ when called with a ``17`` argument. Asserting warnings with the warns function ------------------------------------------ - - You can check that code raises a particular warning using :func:`pytest.warns`, -which works in a similar manner to :ref:`raises `: +which works in a similar manner to :ref:`raises ` (except that +:ref:`raises ` does not capture all exceptions, only the +``expected_exception``): .. code-block:: python import warnings + import pytest @@ -260,21 +281,35 @@ which works in a similar manner to :ref:`raises `: with pytest.warns(UserWarning): warnings.warn("my warning", UserWarning) -The test will fail if the warning in question is not raised. The keyword -argument ``match`` to assert that the exception matches a text or regex:: +The test will fail if the warning in question is not raised. Use the keyword +argument ``match`` to assert that the warning matches a text or regex. +To match a literal string that may contain regular expression metacharacters like ``(`` or ``.``, the pattern can +first be escaped with ``re.escape``. + +Some examples: - >>> with warns(UserWarning, match='must be 0 or None'): +.. code-block:: pycon + + + >>> with warns(UserWarning, match="must be 0 or None"): ... warnings.warn("value must be 0 or None", UserWarning) + ... - >>> with warns(UserWarning, match=r'must be \d+$'): + >>> with warns(UserWarning, match=r"must be \d+$"): ... warnings.warn("value must be 42", UserWarning) + ... - >>> with warns(UserWarning, match=r'must be \d+$'): + >>> with warns(UserWarning, match=r"must be \d+$"): ... warnings.warn("this is not here", UserWarning) + ... Traceback (most recent call last): ... Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted... + >>> with warns(UserWarning, match=re.escape("issue with foo() func")): + ... warnings.warn("issue with foo() func") + ... + You can also call :func:`pytest.warns` on a function or code string: .. code-block:: python @@ -347,8 +382,6 @@ warnings: a WarningsRecorder instance. To view the recorded warnings, you can iterate over this instance, call ``len`` on it to get the number of recorded warnings, or index into it to get a particular recorded warning. -.. currentmodule:: _pytest.warnings - Full API: :class:`~_pytest.recwarn.WarningsRecorder`. .. _`warns use cases`: @@ -358,20 +391,32 @@ Additional use cases of warnings in tests Here are some use cases involving warnings that often come up in tests, and suggestions on how to deal with them: -- To ensure that **any** warning is emitted, use: +- To ensure that **at least one** of the indicated warnings is issued, use: .. code-block:: python - with pytest.warns(): + def test_warning(): + with pytest.warns((RuntimeWarning, UserWarning)): + ... + +- To ensure that **only** certain warnings are issued, use: + +.. code-block:: python + + def test_warning(recwarn): ... + assert len(recwarn) == 1 + user_warning = recwarn.pop(UserWarning) + assert issubclass(user_warning.category, UserWarning) - To ensure that **no** warnings are emitted, use: .. code-block:: python - with warnings.catch_warnings(): - warnings.simplefilter("error") - ... + def test_warning(): + with warnings.catch_warnings(): + warnings.simplefilter("error") + ... - To suppress warnings, use: @@ -441,3 +486,18 @@ Please read our :ref:`backwards-compatibility` to learn how we proceed about dep features. The full list of warnings is listed in :ref:`the reference documentation `. + + +.. _`resource-warnings`: + +Resource Warnings +----------------- + +Additional information of the source of a :class:`ResourceWarning` can be obtained when captured by pytest if +:mod:`tracemalloc` module is enabled. + +One convenient way to enable :mod:`tracemalloc` when running tests is to set the :envvar:`PYTHONTRACEMALLOC` to a large +enough number of frames (say ``20``, but that number is application dependent). + +For more information, consult the `Python Development Mode `__ +section in the Python documentation. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/doctest.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/doctest.rst index ce0b5a5f64960..c2a6cc8e9586b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/doctest.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/doctest.rst @@ -30,7 +30,7 @@ then you can just invoke ``pytest`` directly: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -58,7 +58,7 @@ and functions, including from test modules: $ pytest --doctest-modules =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -126,14 +126,17 @@ pytest also introduces new options: in expected doctest output. * ``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:: + the precision you have written in the expected doctest output. The numbers are + compared using :func:`pytest.approx` with relative tolerance equal to the + precision. For example, the following output would only need to match to 2 + decimal places when comparing ``3.14`` to + ``pytest.approx(math.pi, rel=10**-2)``:: >>> math.pi 3.14 - If you wrote ``3.1416`` then the actual output would need to match to 4 - decimal places; and so on. + If you wrote ``3.1416`` then the actual output would need to match to + approximately 4 decimal places; and so on. This avoids false positives caused by limited floating-point precision, like this:: @@ -221,6 +224,7 @@ place the objects you want to appear in the doctest namespace: .. code-block:: python # content of conftest.py + import pytest import numpy @@ -239,7 +243,6 @@ which can then be used in your doctests directly: >>> len(a) 10 """ - pass Note that like the normal ``conftest.py``, the fixtures are discovered in the directory tree conftest is in. Meaning that if you put your doctest with your source code, the relevant conftest.py needs to be in the same directory tree. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/existingtestsuite.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/existingtestsuite.rst index 9909e7d113a9e..1c37023c72a25 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/existingtestsuite.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/existingtestsuite.rst @@ -4,8 +4,8 @@ How to use pytest with an existing test suite ============================================== Pytest can be used with most existing test suites, but its -behavior differs from other test runners such as :ref:`nose ` or -Python's default unittest framework. +behavior differs from other test runners such as Python's +default unittest framework. Before using this section you will want to :ref:`install pytest `. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/failures.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/failures.rst index ef87550915a18..b3d0c155b48fb 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/failures.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/failures.rst @@ -135,10 +135,6 @@ Warning about unraisable exceptions and unhandled thread exceptions .. versionadded:: 6.2 -.. note:: - - These features only work on Python>=3.8. - Unhandled exceptions are exceptions that are raised in a situation in which they cannot propagate to a caller. The most common case is an exception raised in a :meth:`__del__ ` implementation. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/fixtures.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/fixtures.rst index 0801387745505..6cc20c8c3e4cd 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/fixtures.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/fixtures.rst @@ -398,9 +398,10 @@ access the fixture function: .. code-block:: python # content of conftest.py - import pytest import smtplib + import pytest + @pytest.fixture(scope="module") def smtp_connection(): @@ -432,7 +433,7 @@ marked ``smtp_connection`` fixture function. Running the test looks like this: $ pytest test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -493,7 +494,7 @@ Fixtures are created when first requested by a test, and are destroyed based on * ``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. +* ``package``: the fixture is destroyed during teardown of the last test in the package where the fixture is defined, including sub-packages and sub-directories within it. * ``session``: the fixture is destroyed at the end of the test session. .. note:: @@ -609,10 +610,10 @@ Here's what that might look like: .. code-block:: python # content of test_emaillib.py - import pytest - from emaillib import Email, MailAdminClient + import pytest + @pytest.fixture def mail_admin(): @@ -630,6 +631,7 @@ Here's what that might look like: def receiving_user(mail_admin): user = mail_admin.create_user() yield user + user.clear_mailbox() mail_admin.delete_user(user) @@ -683,10 +685,10 @@ Here's how the previous example would look using the ``addfinalizer`` method: .. code-block:: python # content of test_emaillib.py - import pytest - from emaillib import Email, MailAdminClient + import pytest + @pytest.fixture def mail_admin(): @@ -736,6 +738,87 @@ does offer some nuances for when you're in a pinch. . [100%] 1 passed in 0.12s +Note on finalizer order +"""""""""""""""""""""""" + +Finalizers are executed in a first-in-last-out order. +For yield fixtures, the first teardown code to run is from the right-most fixture, i.e. the last test parameter. + + +.. code-block:: python + + # content of test_finalizers.py + import pytest + + + def test_bar(fix_w_yield1, fix_w_yield2): + print("test_bar") + + + @pytest.fixture + def fix_w_yield1(): + yield + print("after_yield_1") + + + @pytest.fixture + def fix_w_yield2(): + yield + print("after_yield_2") + + +.. code-block:: pytest + + $ pytest -s test_finalizers.py + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y + rootdir: /home/sweet/project + collected 1 item + + test_finalizers.py test_bar + .after_yield_2 + after_yield_1 + + + ============================ 1 passed in 0.12s ============================= + +For finalizers, the first fixture to run is last call to `request.addfinalizer`. + +.. code-block:: python + + # content of test_finalizers.py + from functools import partial + import pytest + + + @pytest.fixture + def fix_w_finalizers(request): + request.addfinalizer(partial(print, "finalizer_2")) + request.addfinalizer(partial(print, "finalizer_1")) + + + def test_bar(fix_w_finalizers): + print("test_bar") + + +.. code-block:: pytest + + $ pytest -s test_finalizers.py + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y + rootdir: /home/sweet/project + collected 1 item + + test_finalizers.py test_bar + .finalizer_1 + finalizer_2 + + + ============================ 1 passed in 0.12s ============================= + +This is so because yield fixtures use `addfinalizer` behind the scenes: when the fixture executes, `addfinalizer` registers a function that resumes the generator, which in turn calls the teardown code. + + .. _`safe teardowns`: Safe teardowns @@ -752,10 +835,10 @@ above): .. code-block:: python # content of test_emaillib.py - import pytest - from emaillib import Email, MailAdminClient + import pytest + @pytest.fixture def setup(): @@ -1030,16 +1113,17 @@ read an optional server URL from the test module which uses our fixture: .. code-block:: python # content of conftest.py - import pytest import smtplib + import pytest + @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 {} ({})".format(smtp_connection, server)) + print(f"finalizing {smtp_connection} ({server})") smtp_connection.close() We use the ``request.module`` attribute to optionally obtain an @@ -1153,7 +1237,6 @@ If the data created by the factory requires managing, the fixture can take care @pytest.fixture def make_customer_record(): - created_records = [] def _make_customer_record(name): @@ -1188,20 +1271,21 @@ 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 ` object: +through the special :py:class:`request ` object: .. code-block:: python # content of conftest.py - import pytest import smtplib + import pytest + @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 {}".format(smtp_connection)) + print(f"finalizing {smtp_connection}") smtp_connection.close() The main change is the declaration of ``params`` with @@ -1330,27 +1414,30 @@ 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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project - collected 11 items - - - - - - - - - - - - - - - - - - ======================= 11 tests collected in 0.12s ======================== + collected 12 items + + + + + + + + + + + + + + + + + + + + + ======================= 12 tests collected in 0.12s ======================== .. _`fixture-parametrize-marks`: @@ -1382,7 +1469,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-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 3 items @@ -1432,7 +1519,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-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 2 items @@ -1503,7 +1590,7 @@ to show the setup/teardown flow: def test_2(otherarg, modarg): - print(" RUN test2 with otherarg {} and modarg {}".format(otherarg, modarg)) + print(f" RUN test2 with otherarg {otherarg} and modarg {modarg}") Let's run the tests in verbose mode and with looking at the print-output: @@ -1512,7 +1599,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-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 8 items @@ -1604,6 +1691,7 @@ and declare its use in a test module via a ``usefixtures`` marker: # content of test_setenv.py import os + import pytest @@ -1611,7 +1699,7 @@ and declare its use in a test module via a ``usefixtures`` marker: class TestDirectoryInit: def test_cwd_starts_empty(self): assert os.listdir(os.getcwd()) == [] - with open("myfile", "w") as f: + with open("myfile", "w", encoding="utf-8") as f: f.write("hello") def test_cwd_again_starts_empty(self): @@ -1633,8 +1721,7 @@ You can specify multiple fixtures like this: .. code-block:: python @pytest.mark.usefixtures("cleandir", "anotherfixture") - def test(): - ... + def test(): ... and you may specify fixture usage at the test module level using :globalvar:`pytestmark`: @@ -1662,11 +1749,9 @@ into an ini-file: @pytest.mark.usefixtures("my_other_fixture") @pytest.fixture - def my_fixture_that_sadly_wont_use_my_other_fixture(): - ... + def my_fixture_that_sadly_wont_use_my_other_fixture(): ... - Currently this will not generate any error or warning, but this is intended - to be handled by :issue:`3664`. + This generates a deprecation warning, and will become an error in Pytest 8. .. _`override fixtures`: @@ -1684,8 +1769,6 @@ Given the tests file structure is: :: tests/ - __init__.py - conftest.py # content of tests/conftest.py import pytest @@ -1700,8 +1783,6 @@ Given the tests file structure is: assert username == 'username' subfolder/ - __init__.py - conftest.py # content of tests/subfolder/conftest.py import pytest @@ -1710,8 +1791,8 @@ Given the tests file structure is: def username(username): return 'overridden-' + username - test_something.py - # content of tests/subfolder/test_something.py + test_something_else.py + # content of tests/subfolder/test_something_else.py def test_username(username): assert username == 'overridden-username' @@ -1727,8 +1808,6 @@ Given the tests file structure is: :: tests/ - __init__.py - conftest.py # content of tests/conftest.py import pytest @@ -1770,8 +1849,6 @@ Given the tests file structure is: :: tests/ - __init__.py - conftest.py # content of tests/conftest.py import pytest @@ -1808,8 +1885,6 @@ Given the tests file structure is: :: tests/ - __init__.py - conftest.py # content of tests/conftest.py import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/index.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/index.rst index 6f52aaecdc3f8..225f289651e0d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/index.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/index.rst @@ -52,7 +52,6 @@ pytest and other test systems existingtestsuite unittest - nose xunit_setup pytest development environment diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/logging.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/logging.rst index 2e8734fa6a3c5..300e9f6e6c241 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/logging.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/logging.rst @@ -55,6 +55,13 @@ These options can also be customized through ``pytest.ini`` file: log_format = %(asctime)s %(levelname)s %(message)s log_date_format = %Y-%m-%d %H:%M:%S +Specific loggers can be disabled via ``--log-disable={logger_name}``. +This argument can be passed multiple times: + +.. code-block:: bash + + pytest --log-disable=main --log-disable=testing + Further it is possible to disable reporting of captured content (stdout, stderr and logs) on failed tests completely with: @@ -73,7 +80,6 @@ messages. This is supported by the ``caplog`` fixture: def test_foo(caplog): caplog.set_level(logging.INFO) - pass 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 @@ -83,7 +89,6 @@ logger: def test_foo(caplog): caplog.set_level(logging.CRITICAL, logger="root.baz") - pass The log levels set are restored automatically at the end of the test. @@ -161,14 +166,19 @@ the records for the ``setup`` and ``call`` stages during teardown like so: x.message for x in caplog.get_records(when) if x.levelno == logging.WARNING ] if messages: - pytest.fail( - "warning messages encountered during testing: {}".format(messages) - ) + pytest.fail(f"warning messages encountered during testing: {messages}") The full API is available at :class:`pytest.LogCaptureFixture`. +.. warning:: + + The ``caplog`` fixture adds a handler to the root logger to capture logs. If the root logger is + modified during a test, for example with ``logging.config.dictConfig``, this handler may be + removed and cause no logs to be captured. To avoid this, ensure that any root logger configuration + only adds to the existing handlers. + .. _live_logs: @@ -180,8 +190,8 @@ logging records as they are emitted directly into the console. You can specify the logging level for which log records with equal or higher level are printed to the console by passing ``--log-cli-level``. This setting -accepts the logging level names as seen in python's documentation or an integer -as the logging level num. +accepts the logging level names or numeric values as seen in +:ref:`logging's documentation `. Additionally, you can also specify ``--log-cli-format`` and ``--log-cli-date-format`` which mirror and default to ``--log-format`` and @@ -196,13 +206,15 @@ option names are: * ``log_cli_date_format`` If you need to record the whole test suite logging calls to a file, you can pass -``--log-file=/path/to/log/file``. This log file is opened in write mode which +``--log-file=/path/to/log/file``. This log file is opened in write mode by default which means that it will be overwritten at each run tests session. +If you'd like the file opened in append mode instead, then you can pass ``--log-file-mode=a``. +Note that relative paths for the log-file location, whether passed on the CLI or declared in a +config file, are always resolved relative to the current working directory. You can also specify the logging level for the log file by passing -``--log-file-level``. This setting accepts the logging level names as seen in -python's documentation(ie, uppercased level names) or an integer as the logging -level num. +``--log-file-level``. This setting accepts the logging level names or numeric +values as seen in :ref:`logging's documentation `. Additionally, you can also specify ``--log-file-format`` and ``--log-file-date-format`` which are equal to ``--log-format`` and @@ -212,12 +224,13 @@ All of the log file options can also be set in the configuration INI file. The option names are: * ``log_file`` +* ``log_file_mode`` * ``log_file_level`` * ``log_file_format`` * ``log_file_date_format`` You can call ``set_log_path()`` to customize the log_file path dynamically. This functionality -is considered **experimental**. +is considered **experimental**. Note that ``set_log_path()`` respects the ``log_file_mode`` option. .. _log_colors: @@ -230,7 +243,7 @@ through ``add_color_level()``. Example: .. code-block:: python - @pytest.hookimpl + @pytest.hookimpl(trylast=True) def pytest_configure(config): logging_plugin = config.pluginmanager.get_plugin("logging-plugin") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/monkeypatch.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/monkeypatch.rst index 9c61233f7e577..a9504dcb32a8d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/monkeypatch.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/monkeypatch.rst @@ -3,7 +3,7 @@ How to monkeypatch/mock modules and environments ================================================================ -.. currentmodule:: _pytest.monkeypatch +.. currentmodule:: pytest Sometimes tests need to invoke functionality which depends on global settings or which invokes code which cannot be easily @@ -14,17 +14,16 @@ 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 +* :meth:`monkeypatch.setattr(obj, name, value, raising=True) ` +* :meth:`monkeypatch.delattr(obj, name, raising=True) ` +* :meth:`monkeypatch.setitem(mapping, name, value) ` +* :meth:`monkeypatch.delitem(obj, name, raising=True) ` +* :meth:`monkeypatch.setenv(name, value, prepend=None) ` +* :meth:`monkeypatch.delenv(name, raising=True) ` +* :meth:`monkeypatch.syspath_prepend(path) ` +* :meth:`monkeypatch.chdir(path) ` +* :meth:`monkeypatch.context() ` - monkeypatch.setattr(obj, name, value, raising=True) - monkeypatch.setattr("somemodule.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=None) - 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`` @@ -55,13 +54,16 @@ during a test. 5. Use :py:meth:`monkeypatch.syspath_prepend ` to modify ``sys.path`` which will also call ``pkg_resources.fixup_namespace_packages`` and :py:func:`importlib.invalidate_caches`. +6. Use :py:meth:`monkeypatch.context ` to apply patches only in a specific scope, which can help +control teardown of complex fixtures or patches to the stdlib. + See the `monkeypatch blog post`_ for some introduction material and a discussion of its motivation. .. _`monkeypatch blog post`: https://tetamap.wordpress.com//2009/03/03/monkeypatching-in-unit-tests-done-right/ -Simple example: monkeypatching functions ----------------------------------------- +Monkeypatching functions +------------------------ 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`` @@ -133,10 +135,10 @@ This can be done in our test file by defining a class to represent ``r``. # 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(): @@ -144,7 +146,6 @@ This can be done in our test file by defining a class to represent ``r``. 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): @@ -179,6 +180,7 @@ This mock can be shared across tests using a ``fixture``: # app.py that includes the get_json() function import app + # custom class to be the mock return value of requests.get() class MockResponse: @staticmethod @@ -356,7 +358,6 @@ For testing purposes we can patch the ``DEFAULT_CONFIG`` dictionary to specific 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") @@ -381,7 +382,6 @@ You can use the :py:meth:`monkeypatch.delitem ` to remove v def test_missing_user(monkeypatch): - # patch the DEFAULT_CONFIG t be missing the 'user' key monkeypatch.delitem(app.DEFAULT_CONFIG, "user", raising=False) @@ -402,6 +402,7 @@ separate fixtures for each potential mock and reference them in the needed tests # 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): @@ -423,7 +424,6 @@ separate fixtures for each potential mock and reference them in the needed tests # 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() @@ -431,12 +431,11 @@ separate fixtures for each potential mock and reference them in the needed tests def test_missing_user(mock_missing_default_user): - with pytest.raises(KeyError): _ = app.create_connection_string() -.. currentmodule:: _pytest.monkeypatch +.. currentmodule:: pytest API Reference ------------- diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/output.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/output.rst index 4b90988f49d40..7a4e32edc7804 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/output.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/output.rst @@ -12,8 +12,15 @@ Examples for modifying traceback printing: .. code-block:: bash - pytest --showlocals # show local variables in tracebacks - pytest -l # show local variables (shortcut) + pytest --showlocals # show local variables in tracebacks + pytest -l # show local variables (shortcut) + pytest --no-showlocals # hide local variables (if addopts enables them) + + pytest --capture=fd # default, capture at the file descriptor level + pytest --capture=sys # capture at the sys level + pytest --capture=no # don't capture + pytest -s # don't capture (shortcut) + pytest --capture=tee-sys # capture to logs but also output to sys level streams pytest --tb=auto # (default) 'long' tracebacks for the first and last # entry, but 'short' style for the other entries @@ -35,6 +42,16 @@ option you make sure a trace is shown. Verbosity -------------------------------------------------- +Examples for modifying printing verbosity: + +.. code-block:: bash + + pytest --quiet # quiet - less verbose - mode + pytest -q # quiet - less verbose - mode (shortcut) + pytest -v # increase verbosity, display individual test names + pytest -vv # more verbose, display more details from the test output + pytest -vvv # not a standard , but may be used for even more detail in certain setups + The ``-v`` flag controls the verbosity of pytest output in various aspects: test session progress, assertion details when tests fail, fixtures details with ``--fixtures``, etc. @@ -83,8 +100,9 @@ Executing pytest normally gives us this output (we are skipping the header to fo fruits2 = ["banana", "apple", "orange", "melon", "kiwi"] > assert fruits1 == fruits2 E AssertionError: assert ['banana', 'a...elon', 'kiwi'] == ['banana', 'a...elon', 'kiwi'] + E E At index 2 diff: 'grapes' != 'orange' - E Use -v to get the full diff + E Use -v to get more diff test_verbosity_example.py:8: AssertionError ____________________________ test_numbers_fail _____________________________ @@ -94,12 +112,13 @@ Executing pytest normally gives us this output (we are skipping the header to fo number_to_text2 = {str(x * 10): x * 10 for x in range(5)} > assert number_to_text1 == number_to_text2 E AssertionError: assert {'0': 0, '1':..., '3': 3, ...} == {'0': 0, '10'...'30': 30, ...} + E E Omitting 1 identical items, use -vv to show E Left contains 4 more items: E {'1': 1, '2': 2, '3': 3, '4': 4} E Right contains 4 more items: E {'10': 10, '20': 20, '30': 30, '40': 40} - E Use -v to get the full diff + E Use -v to get more diff test_verbosity_example.py:14: AssertionError ___________________________ test_long_text_fail ____________________________ @@ -145,12 +164,15 @@ Now we can increase pytest's verbosity: fruits2 = ["banana", "apple", "orange", "melon", "kiwi"] > assert fruits1 == fruits2 E AssertionError: assert ['banana', 'a...elon', 'kiwi'] == ['banana', 'a...elon', 'kiwi'] + E E At index 2 diff: 'grapes' != 'orange' + E E Full diff: - E - ['banana', 'apple', 'orange', 'melon', 'kiwi'] - E ? ^ ^^ - E + ['banana', 'apple', 'grapes', 'melon', 'kiwi'] - E ? ^ ^ + + E [ + E 'banana', + E 'apple',... + E + E ...Full output truncated (7 lines hidden), use '-vv' to show test_verbosity_example.py:8: AssertionError ____________________________ test_numbers_fail _____________________________ @@ -160,15 +182,15 @@ Now we can increase pytest's verbosity: number_to_text2 = {str(x * 10): x * 10 for x in range(5)} > assert number_to_text1 == number_to_text2 E AssertionError: assert {'0': 0, '1':..., '3': 3, ...} == {'0': 0, '10'...'30': 30, ...} + E E Omitting 1 identical items, use -vv to show E Left contains 4 more items: E {'1': 1, '2': 2, '3': 3, '4': 4} E Right contains 4 more items: E {'10': 10, '20': 20, '30': 30, '40': 40} - E Full diff: - E - {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40}... + E ... E - E ...Full output truncated (3 lines hidden), use '-vv' to show + E ...Full output truncated (16 lines hidden), use '-vv' to show test_verbosity_example.py:14: AssertionError ___________________________ test_long_text_fail ____________________________ @@ -214,12 +236,20 @@ Now if we increase verbosity even more: fruits2 = ["banana", "apple", "orange", "melon", "kiwi"] > assert fruits1 == fruits2 E AssertionError: assert ['banana', 'apple', 'grapes', 'melon', 'kiwi'] == ['banana', 'apple', 'orange', 'melon', 'kiwi'] + E E At index 2 diff: 'grapes' != 'orange' + E E Full diff: - E - ['banana', 'apple', 'orange', 'melon', 'kiwi'] - E ? ^ ^^ - E + ['banana', 'apple', 'grapes', 'melon', 'kiwi'] - E ? ^ ^ + + E [ + E 'banana', + E 'apple', + E - 'orange', + E ? ^ ^^ + E + 'grapes', + E ? ^ ^ + + E 'melon', + E 'kiwi', + E ] test_verbosity_example.py:8: AssertionError ____________________________ test_numbers_fail _____________________________ @@ -229,16 +259,30 @@ Now if we increase verbosity even more: number_to_text2 = {str(x * 10): x * 10 for x in range(5)} > assert number_to_text1 == number_to_text2 E AssertionError: assert {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4} == {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40} + E E Common items: E {'0': 0} E Left contains 4 more items: E {'1': 1, '2': 2, '3': 3, '4': 4} E Right contains 4 more items: E {'10': 10, '20': 20, '30': 30, '40': 40} + E E Full diff: - E - {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40} - E ? - - - - - - - - - E + {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4} + E { + E '0': 0, + E - '10': 10, + E ? - - + E + '1': 1, + E - '20': 20, + E ? - - + E + '2': 2, + E - '30': 30, + E ? - - + E + '3': 3, + E - '40': 40, + E ? - - + E + '4': 4, + E } test_verbosity_example.py:14: AssertionError ___________________________ test_long_text_fail ____________________________ @@ -250,9 +294,47 @@ Now if we increase verbosity even more: test_verbosity_example.py:19: AssertionError ========================= short test summary info ========================== - FAILED test_verbosity_example.py::test_words_fail - AssertionError: asser... - FAILED test_verbosity_example.py::test_numbers_fail - AssertionError: ass... - FAILED test_verbosity_example.py::test_long_text_fail - AssertionError: a... + FAILED test_verbosity_example.py::test_words_fail - AssertionError: assert ['banana', 'apple', 'grapes', 'melon', 'kiwi'] == ['banana', 'apple', 'orange', 'melon', 'kiwi'] + + At index 2 diff: 'grapes' != 'orange' + + Full diff: + [ + 'banana', + 'apple', + - 'orange', + ? ^ ^^ + + 'grapes', + ? ^ ^ + + 'melon', + 'kiwi', + ] + FAILED test_verbosity_example.py::test_numbers_fail - AssertionError: assert {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4} == {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40} + + Common items: + {'0': 0} + Left contains 4 more items: + {'1': 1, '2': 2, '3': 3, '4': 4} + Right contains 4 more items: + {'10': 10, '20': 20, '30': 30, '40': 40} + + Full diff: + { + '0': 0, + - '10': 10, + ? - - + + '1': 1, + - '20': 20, + ? - - + + '2': 2, + - '30': 30, + ? - - + + '3': 3, + - '40': 40, + ? - - + + '4': 4, + } + FAILED test_verbosity_example.py::test_long_text_fail - AssertionError: assert 'hello world' in 'Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet ' ======================= 3 failed, 1 passed in 0.12s ======================== Notice now that: @@ -269,6 +351,22 @@ situations, for example you are shown even fixtures that start with ``_`` if you Using higher verbosity levels (``-vvv``, ``-vvvv``, ...) is supported, but has no effect in pytest itself at the moment, however some plugins might make use of higher verbosity. +.. _`pytest.fine_grained_verbosity`: + +Fine-grained verbosity +~~~~~~~~~~~~~~~~~~~~~~ + +In addition to specifying the application wide verbosity level, it is possible to control specific aspects independently. +This is done by setting a verbosity level in the configuration file for the specific aspect of the output. + +:confval:`verbosity_assertions`: Controls how verbose the assertion output should be when pytest is executed. Running +``pytest --no-header`` with a value of ``2`` would have the same output as the previous example, but each test inside +the file is shown by a single character in the output. + +:confval:`verbosity_test_cases`: Controls how verbose the test execution output should be when pytest is executed. +Running ``pytest --no-header`` with a value of ``2`` would have the same output as the first verbosity example, but each +test inside the file gets its own line in the output. + .. _`pytest.detailed_failed_tests_usage`: Producing a detailed summary report @@ -323,7 +421,7 @@ Example: $ pytest -ra =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 6 items @@ -346,11 +444,19 @@ Example: E assert 0 test_example.py:14: AssertionError + ================================ XFAILURES ================================= + ________________________________ test_xfail ________________________________ + + def test_xfail(): + > pytest.xfail("xfailing this test") + E _pytest.outcomes.XFailed: xfailing this test + + test_example.py:26: XFailed + ================================= XPASSES ================================== ========================= short test summary info ========================== 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 + 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.12s === @@ -380,7 +486,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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 6 items @@ -415,7 +521,7 @@ captured output: $ pytest -rpP =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 6 items @@ -478,7 +584,7 @@ integration servers, use this invocation: .. code-block:: bash - pytest --junitxml=path + pytest --junit-xml=path to create an XML file at ``path``. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/parametrize.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/parametrize.rst index a0c9968428cb8..b6466c491b4ae 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/parametrize.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/parametrize.rst @@ -56,7 +56,7 @@ them in turn: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 3 items @@ -167,7 +167,7 @@ Let's run this: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 3 items diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/plugins.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/plugins.rst index cae737e96ed57..7d5bcd85a3103 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/plugins.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/plugins.rst @@ -21,7 +21,7 @@ there is no need to activate it. Here is a little annotated list for some popular plugins: * :pypi:`pytest-django`: write tests - for :std:doc:`django ` apps, using pytest integration. + for `django `_ apps, using pytest integration. * :pypi:`pytest-twisted`: write tests for `twisted `_ apps, starting a reactor and @@ -51,8 +51,8 @@ Here is a little annotated list for some popular plugins: * :pypi:`pytest-flakes`: check source code with pyflakes. -* :pypi:`oejskit`: - a plugin to run javascript unittests in live browsers. +* :pypi:`allure-pytest`: + report test results via `allure-framework `_. To see a complete list of all plugins with their latest testing status against different pytest and Python versions, please visit diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/skipping.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/skipping.rst index e2f59c77ae8c0..09a19766f99f7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/skipping.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/skipping.rst @@ -47,8 +47,7 @@ which may be passed an optional ``reason``: .. code-block:: python @pytest.mark.skip(reason="no way of currently testing this") - def test_the_unknown(): - ... + def test_the_unknown(): ... Alternatively, it is also possible to skip imperatively during test execution or setup @@ -69,6 +68,7 @@ It is also possible to skip the whole module using .. code-block:: python import sys + import pytest if not sys.platform.startswith("win"): @@ -84,16 +84,15 @@ It is also possible to skip the whole module using If you wish to skip something conditionally then you can use ``skipif`` instead. Here is an example of marking a test function to be skipped -when run on an interpreter earlier than Python3.6: +when run on an interpreter earlier than Python3.10: .. code-block:: python import sys - @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher") - def test_function(): - ... + @pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher") + def test_function(): ... If the condition evaluates to ``True`` during collection, the test function will be skipped, with the specified reason appearing in the summary when using ``-rs``. @@ -111,8 +110,7 @@ You can share ``skipif`` markers between modules. Consider this test module: @minversion - def test_function(): - ... + def test_function(): ... You can import the marker and reuse it in another test module: @@ -123,8 +121,7 @@ You can import the marker and reuse it in another test module: @minversion - def test_anotherfunction(): - ... + def test_anotherfunction(): ... For larger test suites it's usually a good idea to have one file where you define the markers which you then consistently apply @@ -231,8 +228,7 @@ expect a test to fail: .. code-block:: python @pytest.mark.xfail - def test_function(): - ... + def test_function(): ... 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 @@ -274,8 +270,7 @@ that condition as the first parameter: .. code-block:: python @pytest.mark.xfail(sys.platform == "win32", reason="bug in a 3rd party library") - def test_function(): - ... + def test_function(): ... Note that you have to pass a reason as well (see the parameter description at :ref:`pytest.mark.xfail ref`). @@ -288,8 +283,7 @@ You can specify the motive of an expected failure with the ``reason`` parameter: .. code-block:: python @pytest.mark.xfail(reason="known parser issue") - def test_function(): - ... + def test_function(): ... ``raises`` parameter @@ -301,8 +295,7 @@ a single exception, or a tuple of exceptions, in the ``raises`` argument. .. code-block:: python @pytest.mark.xfail(raises=RuntimeError) - def test_function(): - ... + def test_function(): ... Then the test will be reported as a regular failure if it fails with an exception not mentioned in ``raises``. @@ -316,8 +309,7 @@ even executed, use the ``run`` parameter as ``False``: .. code-block:: python @pytest.mark.xfail(run=False) - def test_function(): - ... + def test_function(): ... This is specially useful for xfailing tests that are crashing the interpreter and should be investigated later. @@ -333,8 +325,7 @@ You can change this by setting the ``strict`` keyword-only parameter to ``True`` .. code-block:: python @pytest.mark.xfail(strict=True) - def test_function(): - ... + def test_function(): ... This will make ``XPASS`` ("unexpectedly passing") results from this test to fail the test suite. @@ -409,6 +400,7 @@ test instances when using parametrize: .. code-block:: python import sys + import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/tmp_path.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/tmp_path.rst index ebd74d42e903d..3cc5152e99217 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/tmp_path.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/tmp_path.rst @@ -8,9 +8,8 @@ How to use temporary directories and files in tests The ``tmp_path`` fixture ------------------------ -You can use the ``tmp_path`` fixture which will -provide a temporary directory unique to the test invocation, -created in the `base temporary directory`_. +You can use the ``tmp_path`` fixture which will provide a temporary directory +unique to each test function. ``tmp_path`` is a :class:`pathlib.Path` object. Here is an example test usage: @@ -24,8 +23,8 @@ created in the `base temporary directory`_. d = tmp_path / "sub" d.mkdir() p = d / "hello.txt" - p.write_text(CONTENT) - assert p.read_text() == CONTENT + p.write_text(CONTENT, encoding="utf-8") + assert p.read_text(encoding="utf-8") == CONTENT assert len(list(tmp_path.iterdir())) == 1 assert 0 @@ -36,7 +35,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-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -51,8 +50,8 @@ Running this would result in a passed test except for the last d = tmp_path / "sub" d.mkdir() p = d / "hello.txt" - p.write_text(CONTENT) - assert p.read_text() == CONTENT + p.write_text(CONTENT, encoding="utf-8") + assert p.read_text(encoding="utf-8") == CONTENT assert len(list(tmp_path.iterdir())) == 1 > assert 0 E assert 0 @@ -62,6 +61,11 @@ Running this would result in a passed test except for the last FAILED test_tmp_path.py::test_create_file - assert 0 ============================ 1 failed in 0.12s ============================= +By default, ``pytest`` retains the temporary directory for the last 3 ``pytest`` +invocations. Concurrent invocations of the same test function are supported by +configuring the base temporary directory to be unique for each concurrent +run. See `temporary directory location and retention`_ for details. + .. _`tmp_path_factory example`: The ``tmp_path_factory`` fixture @@ -100,26 +104,45 @@ See :ref:`tmp_path_factory API ` for details. .. _tmpdir: The ``tmpdir`` and ``tmpdir_factory`` fixtures ---------------------------------------------------- +---------------------------------------------- The ``tmpdir`` and ``tmpdir_factory`` fixtures are similar to ``tmp_path`` and ``tmp_path_factory``, but use/return legacy `py.path.local`_ objects -rather than standard :class:`pathlib.Path` objects. These days, prefer to -use ``tmp_path`` and ``tmp_path_factory``. +rather than standard :class:`pathlib.Path` objects. + +.. note:: + These days, it is preferred to use ``tmp_path`` and ``tmp_path_factory``. + + In order to help modernize old code bases, one can run pytest with the legacypath + plugin disabled: + + .. code-block:: bash + + pytest -p no:legacypath + + This will trigger errors on tests using the legacy paths. + It can also be permanently set as part of the :confval:`addopts` parameter in the + config file. See :fixture:`tmpdir ` :fixture:`tmpdir_factory ` API for details. -.. _`base temporary directory`: +.. _`temporary directory location and retention`: -The default base temporary directory ------------------------------------------------ +Temporary directory location and retention +------------------------------------------ Temporary directories are by default created as sub-directories of the system temporary directory. The base name will be ``pytest-NUM`` where -``NUM`` will be incremented with each test run. Moreover, entries older -than 3 temporary directories will be removed. +``NUM`` will be incremented with each test run. +By default, entries older than 3 temporary directories will be removed. +This behavior can be configured with :confval:`tmp_path_retention_count` and +:confval:`tmp_path_retention_policy`. + +Using the ``--basetemp`` +option will remove the directory before every run, effectively meaning the temporary directories +of only the most recent run will be kept. You can override the default temporary directory setting like this: @@ -133,7 +156,7 @@ You can override the default temporary directory setting like this: 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. +automatically configure a `basetemp` directory for the sub processes such that all temporary +data lands below a single per-test run temporary directory. .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/unittest.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/unittest.rst index bff7511077808..508aebde01675 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/unittest.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/unittest.rst @@ -27,12 +27,15 @@ Almost all ``unittest`` features are supported: * ``setUpClass/tearDownClass``; * ``setUpModule/tearDownModule``; +.. _`pytest-subtests`: https://github.com/pytest-dev/pytest-subtests .. _`load_tests protocol`: https://docs.python.org/3/library/unittest.html#load-tests-protocol +Additionally, :ref:`subtests ` are supported by the +`pytest-subtests`_ plugin. + Up to this point pytest does not have support for the following features: * `load_tests protocol`_; -* :ref:`subtests `; Benefits out of the box ----------------------- @@ -115,6 +118,7 @@ fixture definition: # content of test_unittest_db.py import unittest + import pytest @@ -136,7 +140,7 @@ the ``self.db`` values in the traceback: $ pytest test_unittest_db.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -153,7 +157,7 @@ the ``self.db`` values in the traceback: E AssertionError: .DummyDB object at 0xdeadbeef0001> E assert 0 - test_unittest_db.py:10: AssertionError + test_unittest_db.py:11: AssertionError ___________________________ MyTest.test_method2 ____________________________ self = @@ -163,7 +167,7 @@ the ``self.db`` values in the traceback: E AssertionError: .DummyDB object at 0xdeadbeef0001> E assert 0 - test_unittest_db.py:13: AssertionError + test_unittest_db.py:14: AssertionError ========================= short test summary info ========================== FAILED test_unittest_db.py::MyTest::test_method1 - AssertionError: `). **Run tests in a module** @@ -35,31 +36,43 @@ Pytest supports several ways to run and select tests from the command-line. .. code-block:: bash - pytest -k "MyClass and not method" + pytest -k 'MyClass and not method' 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``. +Use ``""`` instead of ``''`` in expression when running this on Windows .. _nodeids: -**Run tests by node ids** +**Run tests by collection arguments** -Each collected test is assigned a unique ``nodeid`` which consist of the module filename followed -by specifiers like class names, function names and parameters from parametrization, separated by ``::`` characters. +Pass the module filename relative to the working directory, followed by specifiers like the class name and function name +separated by ``::`` characters, and parameters from parameterization enclosed in ``[]``. To run a specific test within a module: .. code-block:: bash - pytest test_mod.py::test_func + pytest tests/test_mod.py::test_func +To run all tests in a class: -Another example specifying a test method in the command line: +.. code-block:: bash + + pytest tests/test_mod.py::TestClass + +Specifying a specific test method: .. code-block:: bash - pytest test_mod.py::TestClass::test_method + pytest tests/test_mod.py::TestClass::test_method + +Specifying a specific parametrization of a test: + +.. code-block:: bash + + pytest tests/test_mod.py::test_func[x1,y2] **Run tests by marker expressions** @@ -79,6 +92,28 @@ For more information see :ref:`marks `. This will import ``pkg.testing`` and use its filesystem location to find and run tests from. +.. _args-from-file: + +**Read arguments from file** + +.. versionadded:: 8.2 + +All of the above can be read from a file using the ``@`` prefix: + +.. code-block:: bash + + pytest @tests_to_run.txt + +where ``tests_to_run.txt`` contains an entry per line, e.g.: + +.. code-block:: text + + tests/test_file.py + tests/test_mod.py::test_func[x1,y2] + tests/test_mod.py::TestClass + -m slow + +This file can also be generated using ``pytest --collect-only -q`` and modified as needed. Getting help on version, option names, environment variables -------------------------------------------------------------- @@ -172,7 +207,8 @@ You can invoke ``pytest`` from Python code directly: this acts as if you would call "pytest" from the command line. It will not raise :class:`SystemExit` but return the :ref:`exit code ` instead. -You can pass in options and arguments: +If you don't pass it any arguments, ``main`` reads the arguments from the command line arguments of the process (:data:`sys.argv`), which may be undesirable. +You can pass in options and arguments explicitly: .. code-block:: python @@ -183,9 +219,10 @@ You can specify additional plugins to ``pytest.main``: .. code-block:: python # content of myinvoke.py - import pytest import sys + import pytest + class MyPlugin: def pytest_sessionfinish(self): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/w3c-import.log new file mode 100644 index 0000000000000..0abb6e09e68d1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/w3c-import.log @@ -0,0 +1,39 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/assert.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/bash-completion.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/cache.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/capture-stdout-stderr.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/capture-warnings.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/doctest.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/existingtestsuite.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/failures.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/fixtures.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/logging.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/mark.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/monkeypatch.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/output.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/parametrize.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/plugins.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/skipping.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/tmp_path.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/unittest.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/usage.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/writing_hook_functions.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/writing_plugins.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/xunit_setup.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/writing_hook_functions.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/writing_hook_functions.rst index f615fced861de..f4c00d04fdae9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/writing_hook_functions.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/writing_hook_functions.rst @@ -56,23 +56,17 @@ The remaining hook functions will not be called in this case. .. _`hookwrapper`: -hookwrapper: executing around other hooks +hook wrappers: executing around other hooks ------------------------------------------------- -.. currentmodule:: _pytest.core - - - pytest plugins can implement hook wrappers which wrap the execution of other hook implementations. A hook wrapper is a generator function which yields exactly once. When pytest invokes hooks it first executes hook wrappers and passes the same arguments as to the regular hooks. At the yield point of the hook wrapper pytest will execute the next hook -implementations and return their result to the yield point in the form of -a :py:class:`Result ` instance which encapsulates a result or -exception info. The yield point itself will thus typically not raise -exceptions (unless there are bugs). +implementations and return their result to the yield point, or will +propagate an exception if they raised. Here is an example definition of a hook wrapper: @@ -81,26 +75,35 @@ Here is an example definition of a hook wrapper: import pytest - @pytest.hookimpl(hookwrapper=True) + @pytest.hookimpl(wrapper=True) def pytest_pyfunc_call(pyfuncitem): do_something_before_next_hook_executes() - outcome = yield - # outcome.excinfo may be None or a (cls, val, tb) tuple + # If the outcome is an exception, will raise the exception. + res = yield + + new_res = post_process_result(res) - res = outcome.get_result() # will raise if outcome was exception + # Override the return value to the plugin system. + return new_res - post_process_result(res) +The hook wrapper needs to return a result for the hook, or raise an exception. - outcome.force_result(new_res) # to override the return value to the plugin system +In many cases, the wrapper only needs to perform tracing or other side effects +around the actual hook implementations, in which case it can return the result +value of the ``yield``. The simplest (though useless) hook wrapper is +``return (yield)``. -Note that hook wrappers don't return results themselves, they merely -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. +In other cases, the wrapper wants the adjust or adapt the result, in which case +it can return a new value. If the result of the underlying hook is a mutable +object, the wrapper may modify that result, but it's probably better to avoid it. + +If the hook implementation failed with an exception, the wrapper can handle that +exception using a ``try-catch-finally`` around the ``yield``, by propagating it, +suppressing it, or raising a different exception entirely. For more information, consult the -:ref:`pluggy documentation about hookwrappers `. +:ref:`pluggy documentation about hook wrappers `. .. _plugin-hookorder: @@ -130,11 +133,14 @@ after others, i.e. the position in the ``N``-sized list of functions: # Plugin 3 - @pytest.hookimpl(hookwrapper=True) + @pytest.hookimpl(wrapper=True) def pytest_collection_modifyitems(items): # will execute even before the tryfirst one above! - outcome = yield - # will execute after all non-hookwrappers executed + try: + return (yield) + finally: + # will execute after all non-wrappers executed + ... Here is the order of execution: @@ -149,13 +155,13 @@ Here is the order of execution: Plugin1). 4. Plugin3's pytest_collection_modifyitems then executing the code after the yield - point. The yield receives a :py:class:`Result ` instance which encapsulates - the result from calling the non-wrappers. Wrappers shall not modify the result. + point. The yield receives the result from calling the non-wrappers, or raises + an exception if the non-wrappers raised. -It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with -``hookwrapper=True`` in which case it will influence the ordering of hookwrappers -among each other. +It's possible to use ``tryfirst`` and ``trylast`` also on hook wrappers +in which case it will influence the ordering of hook wrappers among each other. +.. _`declaringhooks`: Declaring new hooks ------------------------ @@ -165,13 +171,11 @@ Declaring new hooks 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 `__. -.. currentmodule:: _pytest.hookspec - Plugins and ``conftest.py`` files may declare new hooks that can then be implemented by other plugins in order to alter behaviour or interact with the new plugin: -.. autofunction:: pytest_addhooks +.. autofunction:: _pytest.hookspec.pytest_addhooks :noindex: Hooks are usually declared as do-nothing functions that contain only @@ -194,7 +198,7 @@ class or module can then be passed to the ``pluginmanager`` using the ``pytest_a .. code-block:: python def pytest_addhooks(pluginmanager): - """ This example assumes the hooks are grouped in the 'sample_hook' module. """ + """This example assumes the hooks are grouped in the 'sample_hook' module.""" from my_app.tests import sample_hook pluginmanager.add_hookspecs(sample_hook) @@ -249,18 +253,19 @@ and use pytest_addoption as follows: # 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. """ + """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. """ + """This example assumes the hooks are grouped in the 'hooks' module.""" from . import hooks pluginmanager.add_hookspecs(hooks) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/writing_plugins.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/writing_plugins.rst index b2d2b6563d63e..4bb6d18333395 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/writing_plugins.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/writing_plugins.rst @@ -46,24 +46,18 @@ Plugin discovery order at tool startup 5. by loading all plugins specified through the :envvar:`PYTEST_PLUGINS` environment variable. -6. by loading all :file:`conftest.py` files as inferred by the command line - invocation: +6. by loading all "initial ":file:`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. + - determine the test paths: specified on the command line, otherwise in + :confval:`testpaths` if defined and running from the rootdir, otherwise the + current dir + - for each test path, load ``conftest.py`` and ``test*/conftest.py`` relative + to the directory part of the test path, if exist. Before a ``conftest.py`` + file is loaded, load ``conftest.py`` files in all of its parent directories. + After a ``conftest.py`` file is loaded, recursively 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/ .. _`conftest.py plugins`: .. _`localplugin`: .. _`local conftest plugins`: @@ -108,9 +102,9 @@ Here is how you might run it:: See also: :ref:`pythonpath`. .. note:: - Some hooks should be implemented only in plugins or conftest.py files situated at the - tests root directory due to how pytest discovers plugins during startup, - see the documentation of each hook for details. + Some hooks cannot be implemented in conftest.py files which are not + :ref:`initial ` due to how pytest discovers plugins during + startup. See the documentation of each hook for details. Writing your own plugin ----------------------- @@ -147,29 +141,32 @@ Making your plugin installable by others If you want to make your plugin externally available, you may define a so-called entry point for your distribution so -that ``pytest`` finds your plugin module. Entry points are -a feature that is provided by :std:doc:`setuptools:index`. pytest looks up -the ``pytest11`` entrypoint to discover its -plugins and you can thus make your plugin available by defining -it in your setuptools-invocation: +that ``pytest`` finds your plugin module. Entry points are +a feature that is provided by :std:doc:`setuptools `. -.. sourcecode:: python +pytest looks up the ``pytest11`` entrypoint to discover its +plugins, thus you can make your plugin available by defining +it in your ``pyproject.toml`` file. + +.. sourcecode:: toml + + # sample ./pyproject.toml file + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" - # sample ./setup.py file - from setuptools import setup + [project] + name = "myproject" + classifiers = [ + "Framework :: Pytest", + ] - setup( - name="myproject", - packages=["myproject"], - # the following makes a plugin available to pytest - entry_points={"pytest11": ["name_of_plugin = myproject.pluginmodule"]}, - # custom PyPI classifier for pytest plugins - classifiers=["Framework :: Pytest"], - ) + [project.entry-points.pytest11] + myproject = "myproject.pluginmodule" If a package is installed this way, ``pytest`` will load ``myproject.pluginmodule`` as a plugin which can define -:ref:`hooks `. +:ref:`hooks `. Confirm registration with ``pytest --trace-config`` .. note:: @@ -367,7 +364,7 @@ string value of ``Hello World!`` if we do not supply a value or ``Hello def _hello(name=None): if not name: name = request.config.getoption("name") - return "Hello {name}!".format(name=name) + return f"Hello {name}!" return _hello @@ -445,8 +442,9 @@ in our ``pytest.ini`` to tell pytest where to look for example files. $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y - rootdir: /home/sweet/project, configfile: pytest.ini + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y + rootdir: /home/sweet/project + configfile: pytest.ini collected 2 items test_example.py .. [100%] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/xunit_setup.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/xunit_setup.rst index 5a97b2c85f13a..3de6681ff8fb8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/xunit_setup.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/how-to/xunit_setup.rst @@ -32,7 +32,7 @@ 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.""" + """setup any state specific to the execution of the given module.""" def teardown_module(module): @@ -63,6 +63,8 @@ and after all test methods of the class are called: setup_class. """ +.. _xunit-method-setup: + Method and function level setup/teardown ----------------------------------------------- diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/img/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/img/w3c-import.log new file mode 100644 index 0000000000000..f26fcc3cea566 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/img/w3c-import.log @@ -0,0 +1,26 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/img/cramer2.png +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/img/favicon.png +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/img/freiburg2.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/img/gaynor3.png +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/img/keleshev.png +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/img/pullrequest.png +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/img/pylib.png +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/img/pytest1.png +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/img/pytest_logo_curves.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/img/theuni.png diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/index.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/index.rst index d1b3d2e8a0816..83eb27b0a53f3 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/index.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/index.rst @@ -1,11 +1,14 @@ :orphan: -.. - .. sidebar:: Next Open Trainings +.. sidebar:: Next Open Trainings and Events - - `Professional Testing with Python `_, via `Python Academy `_, February 1st to 3rd, 2022, Leipzig (Germany) and remote. + - `Professional Testing with Python `_, via `Python Academy `_ (3 day in-depth training): + * **June 11th to 13th 2024**, Remote + * **March 4th to 6th 2025**, Leipzig, Germany / Remote + - `pytest development sprint `_, **June 17th -- 22nd 2024** + - pytest tips and tricks for a better testsuite, `Europython 2024 `_, **July 8th -- 14th 2024** (3h), Prague - Also see `previous talks and blogposts `_. + Also see :doc:`previous talks and blogposts `. .. _features: @@ -18,12 +21,10 @@ The ``pytest`` framework makes it easy to write small, readable tests, and can scale to support complex functional testing for applications and libraries. -**Pythons**: ``pytest`` requires: Python 3.6, 3.7, 3.8, 3.9, or PyPy3. +``pytest`` requires: Python 3.8+ or PyPy3. **PyPI package name**: :pypi:`pytest` -**Documentation as PDF**: `download latest `_ - A quick example --------------- @@ -45,7 +46,7 @@ To execute it: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -77,17 +78,17 @@ Features - :ref:`Modular fixtures ` for managing small or parametrized long-lived test resources -- Can run :ref:`unittest ` (including trial) and :ref:`nose ` test suites out of the box +- Can run :ref:`unittest ` (including trial) test suites out of the box -- Python 3.6+ and PyPy 3 +- Python 3.8+ or PyPy 3 -- Rich plugin architecture, with over 800+ :ref:`external plugins ` and thriving community +- Rich plugin architecture, with over 1300+ :ref:`external plugins ` and thriving community Documentation ------------- -* :ref:`Get started ` - install pytest and grasp its basics just twenty minutes +* :ref:`Get started ` - install pytest and grasp its basics in just twenty minutes * :ref:`How-to guides ` - step-by-step guides, covering a vast range of use-cases and needs * :ref:`Reference guides ` - includes the complete pytest API reference, lists of plugins and more * :ref:`Explanation ` - background, discussion of key topics, answers to higher-level questions @@ -99,11 +100,6 @@ Bugs/Requests Please use the `GitHub issue tracker `_ to submit bugs or request features. -Changelog ---------- - -Consult the :ref:`Changelog ` page for fixes and enhancements of each version. - Support pytest -------------- @@ -136,13 +132,3 @@ 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 `_. Tidelift will coordinate the fix and disclosure. - - -License -------- - -Copyright Holger Krekel and others, 2004. - -Distributed under the terms of the `MIT`_ license, pytest is free and open source software. - -.. _`MIT`: https://github.com/pytest-dev/pytest/blob/main/LICENSE diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/naming20.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/naming20.rst index 5a81df2698d2b..1121306638433 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/naming20.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/naming20.rst @@ -8,7 +8,7 @@ If you used older version of the ``py`` distribution (which included the py.test command line tool and Python name space) you accessed helpers and possibly collection classes through the ``py.test`` Python namespaces. The new ``pytest`` -Python module flaty provides the same objects, following +Python module flatly provides the same objects, following these renaming rules:: py.test.XYZ -> pytest.XYZ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/proposals/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/proposals/w3c-import.log new file mode 100644 index 0000000000000..43735effe994d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/proposals/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/proposals/parametrize_with_fixtures.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/customize.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/customize.rst index fe10ca066b25e..cab1117266f1d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/customize.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/customize.rst @@ -29,9 +29,11 @@ pytest.ini ``pytest.ini`` files take precedence over other files, even when empty. +Alternatively, the hidden version ``.pytest.ini`` can be used. + .. code-block:: ini - # pytest.ini + # pytest.ini or .pytest.ini [pytest] minversion = 6.0 addopts = -ra -q @@ -88,7 +90,7 @@ and can also be used to hold pytest configuration if they have a ``[pytest]`` se setup.cfg ~~~~~~~~~ -``setup.cfg`` files are general purpose configuration files, used originally by :doc:`distutils `, and can also be used to hold pytest configuration +``setup.cfg`` files are general purpose configuration files, used originally by ``distutils`` (now deprecated) and `setuptools `__, and can also be used to hold pytest configuration if they have a ``[tool:pytest]`` section. .. code-block:: ini @@ -175,13 +177,20 @@ Files will only be matched for configuration if: * ``tox.ini``: contains a ``[pytest]`` section. * ``setup.cfg``: contains a ``[tool:pytest]`` section. +Finally, a ``pyproject.toml`` file will be considered the ``configfile`` if no other match was found, in this case +even if it does not contain a ``[tool.pytest.ini_options]`` table (this was added in ``8.1``). + The files are considered in the order above. Options from multiple ``configfiles`` candidates are never merged - the first match wins. +The configuration file also determines the value of the ``rootpath``. + The :class:`Config ` object (accessible via hooks or through the :fixture:`pytestconfig` fixture) will subsequently carry these attributes: -- :attr:`config.rootpath `: the determined root directory, guaranteed to exist. +- :attr:`config.rootpath `: the determined root directory, guaranteed to exist. It is used as + a reference directory for constructing test addresses ("nodeids") and can be used also by plugins for storing + per-testrun information. - :attr:`config.inipath `: the determined ``configfile``, may be ``None`` (it is named ``inipath`` for historical reasons). @@ -191,9 +200,7 @@ will subsequently carry these attributes: 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. + Example: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/fixtures.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/fixtures.rst index d25979ab95d39..dff93a035efd2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/fixtures.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/fixtures.rst @@ -11,9 +11,6 @@ Fixtures reference .. seealso:: :ref:`about-fixtures` .. seealso:: :ref:`how-to-fixtures` - -.. currentmodule:: _pytest.python - .. _`Dependency injection`: https://en.wikipedia.org/wiki/Dependency_injection @@ -42,7 +39,7 @@ Built-in fixtures Store and retrieve values across pytest runs. :fixture:`doctest_namespace` - Provide a dict injected into the docstests namespace. + Provide a dict injected into the doctests namespace. :fixture:`monkeypatch` Temporarily modify classes, functions, dictionaries, @@ -76,15 +73,13 @@ Built-in fixtures :class:`pathlib.Path` objects. :fixture:`tmpdir` - Provide a :class:`py.path.local` object to a temporary + Provide a `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; + ``py.path.local`` objects; replaced by :fixture:`tmp_path_factory`. @@ -98,7 +93,7 @@ Fixture availability is determined from the perspective of the test. A fixture is only available for tests to request if they are in the scope that fixture is defined in. If a fixture is defined inside a class, it can only be requested by tests inside that class. But if a fixture is defined inside the global scope of -the module, than every test in that module, even if it's defined inside a class, +the module, then every test in that module, even if it's defined inside a class, can request it. Similarly, a test can also only be affected by an autouse fixture if that test @@ -335,7 +330,7 @@ For example: .. literalinclude:: /example/fixtures/test_fixtures_order_dependencies.py -If we map out what depends on what, we get something that look like this: +If we map out what depends on what, we get something that looks like this: .. image:: /example/fixtures/test_fixtures_order_dependencies.* :align: center diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/index.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/index.rst index d9648400317af..ee1b2e6214df1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/index.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/index.rst @@ -8,8 +8,8 @@ Reference guides .. toctree:: :maxdepth: 1 + reference fixtures - plugin_list customize - reference exit-codes + plugin_list diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/plugin_list.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/plugin_list.rst index ebf400913690a..e1d1e3ec24ac1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/plugin_list.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/plugin_list.rst @@ -1,1005 +1,1548 @@ +.. Note this file is autogenerated by scripts/update-plugin-list.py - usually weekly via github action + .. _plugin-list: -Plugin List -=========== +Pytest Plugin List +================== + +Below is an automated compilation of ``pytest``` plugins available on `PyPI `_. +It includes PyPI projects whose names begin with ``pytest-`` or ``pytest_`` and a handful of manually selected projects. +Packages classified as inactive are excluded. + +For detailed insights into how this list is generated, +please refer to `the update script `_. + +.. warning:: + + Please be aware that this list is not a curated collection of projects + and does not undergo a systematic review process. + It serves purely as an informational resource to aid in the discovery of ``pytest`` plugins. + + Do not presume any endorsement from the ``pytest`` project or its developers, + and always conduct your own quality assessment before incorporating any of these plugins into your own projects. -PyPI projects that match "pytest-\*" are considered plugins and are listed -automatically. Packages classified as inactive are excluded. .. The following conditional uses a different format for this list when creating a PDF, because otherwise the table gets far too wide for the page. -This list contains 963 plugins. +This list contains 1448 plugins. .. only:: not latex - =============================================== ======================================================================================================================================================================== ============== ===================== ================================================ - name summary last release status requires - =============================================== ======================================================================================================================================================================== ============== ===================== ================================================ - :pypi:`pytest-accept` A pytest-plugin for updating doctest outputs Nov 22, 2021 N/A pytest (>=6,<7) - :pypi:`pytest-adaptavist` pytest plugin for generating test execution results within Jira Test Management (tm4j) Nov 30, 2021 N/A pytest (>=5.4.0) - :pypi:`pytest-addons-test` 用于测试pytest的插件 Aug 02, 2021 N/A pytest (>=6.2.4,<7.0.0) - :pypi:`pytest-adf` Pytest plugin for writing Azure Data Factory integration tests May 10, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-adf-azure-identity` Pytest plugin for writing Azure Data Factory integration tests Mar 06, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-agent` Service that exposes a REST API that can be used to interract remotely with Pytest. It is shipped with a dashboard that enables running tests in a more convenient way. Nov 25, 2021 N/A N/A - :pypi:`pytest-aggreport` pytest plugin for pytest-repeat that generate aggregate report of the same test cases with additional statistics details. Mar 07, 2021 4 - Beta pytest (>=6.2.2) - :pypi:`pytest-aio` Pytest plugin for testing async python code Oct 20, 2021 4 - Beta pytest - :pypi:`pytest-aiofiles` pytest fixtures for writing aiofiles tests with pyfakefs May 14, 2017 5 - Production/Stable N/A - :pypi:`pytest-aiohttp` pytest plugin for aiohttp support Dec 05, 2017 N/A pytest - :pypi:`pytest-aiohttp-client` Pytest \`client\` fixture for the Aiohttp Nov 01, 2020 N/A pytest (>=6) - :pypi:`pytest-aioresponses` py.test integration for aioresponses Jul 29, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-aioworkers` A plugin to test aioworkers project with pytest Dec 04, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-airflow` pytest support for airflow. Apr 03, 2019 3 - Alpha pytest (>=4.4.0) - :pypi:`pytest-airflow-utils` Nov 15, 2021 N/A N/A - :pypi:`pytest-alembic` A pytest plugin for verifying alembic migrations. Dec 02, 2021 N/A pytest (>=1.0) - :pypi:`pytest-allclose` Pytest fixture extending Numpy's allclose function Jul 30, 2019 5 - Production/Stable pytest - :pypi:`pytest-allure-adaptor` Plugin for py.test to generate allure xml reports Jan 10, 2018 N/A pytest (>=2.7.3) - :pypi:`pytest-allure-adaptor2` Plugin for py.test to generate allure xml reports Oct 14, 2020 N/A pytest (>=2.7.3) - :pypi:`pytest-allure-dsl` pytest plugin to test case doc string dls instructions Oct 25, 2020 4 - Beta pytest - :pypi:`pytest-allure-spec-coverage` The pytest plugin aimed to display test coverage of the specs(requirements) in Allure Oct 26, 2021 N/A pytest - :pypi:`pytest-alphamoon` Static code checks used at Alphamoon Oct 21, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-android` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Feb 21, 2019 3 - Alpha pytest - :pypi:`pytest-anki` A pytest plugin for testing Anki add-ons Oct 14, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-annotate` pytest-annotate: Generate PyAnnotate annotations from your pytest tests. Nov 29, 2021 3 - Alpha pytest (<7.0.0,>=3.2.0) - :pypi:`pytest-ansible` Plugin for py.test to simplify calling ansible modules from tests or fixtures May 25, 2021 5 - Production/Stable N/A - :pypi:`pytest-ansible-playbook` Pytest fixture which runs given ansible playbook file. Mar 08, 2019 4 - Beta N/A - :pypi:`pytest-ansible-playbook-runner` Pytest fixture which runs given ansible playbook file. Dec 02, 2020 4 - Beta pytest (>=3.1.0) - :pypi:`pytest-antilru` Bust functools.lru_cache when running pytest to avoid test pollution Apr 11, 2019 5 - Production/Stable pytest - :pypi:`pytest-anyio` The pytest anyio plugin is built into anyio. You don't need this package. Jun 29, 2021 N/A pytest - :pypi:`pytest-anything` Pytest fixtures to assert anything and something Feb 18, 2021 N/A N/A - :pypi:`pytest-aoc` Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures Nov 23, 2021 N/A pytest ; extra == 'test' - :pypi:`pytest-api` PyTest-API Python Web Framework built for testing purposes. May 04, 2021 N/A N/A - :pypi:`pytest-apistellar` apistellar plugin for pytest. Jun 18, 2019 N/A N/A - :pypi:`pytest-appengine` AppEngine integration that works well with pytest-django Feb 27, 2017 N/A N/A - :pypi:`pytest-appium` Pytest plugin for appium Dec 05, 2019 N/A N/A - :pypi:`pytest-approvaltests` A plugin to use approvaltests with pytest Feb 07, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-argus` pyest results colection plugin Jun 24, 2021 5 - Production/Stable pytest (>=6.2.4) - :pypi:`pytest-arraydiff` pytest plugin to help with comparing array output from tests Dec 06, 2018 4 - Beta pytest - :pypi:`pytest-asgi-server` Convenient ASGI client/server fixtures for Pytest Dec 12, 2020 N/A pytest (>=5.4.1) - :pypi:`pytest-asptest` test Answer Set Programming programs Apr 28, 2018 4 - Beta N/A - :pypi:`pytest-assertutil` pytest-assertutil May 10, 2019 N/A N/A - :pypi:`pytest-assert-utils` Useful assertion utilities for use with pytest Sep 21, 2021 3 - Alpha N/A - :pypi:`pytest-assume` A pytest plugin that allows multiple failures per test Jun 24, 2021 N/A pytest (>=2.7) - :pypi:`pytest-ast-back-to-python` A plugin for pytest devs to view how assertion rewriting recodes the AST Sep 29, 2019 4 - Beta N/A - :pypi:`pytest-astropy` Meta-package containing dependencies for testing Sep 21, 2021 5 - Production/Stable pytest (>=4.6) - :pypi:`pytest-astropy-header` pytest plugin to add diagnostic information to the header of the test output Dec 18, 2019 3 - Alpha pytest (>=2.8) - :pypi:`pytest-ast-transformer` May 04, 2019 3 - Alpha pytest - :pypi:`pytest-asyncio` Pytest support for asyncio. Oct 15, 2021 4 - Beta pytest (>=5.4.0) - :pypi:`pytest-asyncio-cooperative` Run all your asynchronous tests cooperatively. Oct 12, 2021 4 - Beta N/A - :pypi:`pytest-asyncio-network-simulator` pytest-asyncio-network-simulator: Plugin for pytest for simulator the network in tests Jul 31, 2018 3 - Alpha pytest (<3.7.0,>=3.3.2) - :pypi:`pytest-async-mongodb` pytest plugin for async MongoDB Oct 18, 2017 5 - Production/Stable pytest (>=2.5.2) - :pypi:`pytest-async-sqlalchemy` Database testing fixtures using the SQLAlchemy asyncio API Oct 07, 2021 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-atomic` Skip rest of tests if previous test failed. Nov 24, 2018 4 - Beta N/A - :pypi:`pytest-attrib` pytest plugin to select tests based on attributes similar to the nose-attrib plugin May 24, 2016 4 - Beta N/A - :pypi:`pytest-austin` Austin plugin for pytest Oct 11, 2020 4 - Beta N/A - :pypi:`pytest-autochecklog` automatically check condition and log all the checks Apr 25, 2015 4 - Beta N/A - :pypi:`pytest-automation` pytest plugin for building a test suite, using YAML files to extend pytest parameterize functionality. Oct 01, 2021 N/A pytest - :pypi:`pytest-automock` Pytest plugin for automatical mocks creation Apr 22, 2020 N/A pytest ; extra == 'dev' - :pypi:`pytest-auto-parametrize` pytest plugin: avoid repeating arguments in parametrize Oct 02, 2016 3 - Alpha N/A - :pypi:`pytest-autotest` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Aug 25, 2021 N/A pytest - :pypi:`pytest-avoidance` Makes pytest skip tests that don not need rerunning May 23, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-aws` pytest plugin for testing AWS resource configurations Oct 04, 2017 4 - Beta N/A - :pypi:`pytest-aws-config` Protect your AWS credentials in unit tests May 28, 2021 N/A N/A - :pypi:`pytest-axe` pytest plugin for axe-selenium-python Nov 12, 2018 N/A pytest (>=3.0.0) - :pypi:`pytest-azurepipelines` Formatting PyTest output for Azure Pipelines UI Jul 23, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-bandit` A bandit plugin for pytest Feb 23, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-base-url` pytest plugin for URL based testing Jun 19, 2020 5 - Production/Stable pytest (>=2.7.3) - :pypi:`pytest-bdd` BDD for pytest Oct 25, 2021 6 - Mature pytest (>=4.3) - :pypi:`pytest-bdd-splinter` Common steps for pytest bdd and splinter integration Aug 12, 2019 5 - Production/Stable pytest (>=4.0.0) - :pypi:`pytest-bdd-web` A simple plugin to use with pytest Jan 02, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-bdd-wrappers` Feb 11, 2020 2 - Pre-Alpha N/A - :pypi:`pytest-beakerlib` A pytest plugin that reports test results to the BeakerLib framework Mar 17, 2017 5 - Production/Stable pytest - :pypi:`pytest-beds` Fixtures for testing Google Appengine (GAE) apps Jun 07, 2016 4 - Beta N/A - :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A - :pypi:`pytest-benchmark` A \`\`pytest\`\` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer. Apr 17, 2021 5 - Production/Stable pytest (>=3.8) - :pypi:`pytest-bg-process` Pytest plugin to initialize background process Aug 17, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-bigchaindb` A BigchainDB plugin for pytest. Aug 17, 2021 4 - Beta N/A - :pypi:`pytest-bigquery-mock` Provides a mock fixture for python bigquery client Aug 05, 2021 N/A pytest (>=5.0) - :pypi:`pytest-black` A pytest plugin to enable format checking with black Oct 05, 2020 4 - Beta N/A - :pypi:`pytest-black-multipy` Allow '--black' on older Pythons Jan 14, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' - :pypi:`pytest-blame` A pytest plugin helps developers to debug by providing useful commits history. May 04, 2019 N/A pytest (>=4.4.0) - :pypi:`pytest-blender` Blender Pytest plugin. Oct 29, 2021 N/A pytest (==6.2.5) ; extra == 'dev' - :pypi:`pytest-blink1` Pytest plugin to emit notifications via the Blink(1) RGB LED Jan 07, 2018 4 - Beta N/A - :pypi:`pytest-blockage` Disable network requests during a test run. Feb 13, 2019 N/A pytest - :pypi:`pytest-blocker` pytest plugin to mark a test as blocker and skip all other tests Sep 07, 2015 4 - Beta N/A - :pypi:`pytest-board` Local continuous test runner with pytest and watchdog. Jan 20, 2019 N/A N/A - :pypi:`pytest-bpdb` A py.test plug-in to enable drop to bpdb debugger on test failure. Jan 19, 2015 2 - Pre-Alpha N/A - :pypi:`pytest-bravado` Pytest-bravado automatically generates from OpenAPI specification client fixtures. Jul 19, 2021 N/A N/A - :pypi:`pytest-breakword` Use breakword with pytest Aug 04, 2021 N/A pytest (>=6.2.4,<7.0.0) - :pypi:`pytest-breed-adapter` A simple plugin to connect with breed-server Nov 07, 2018 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-briefcase` A pytest plugin for running tests on a Briefcase project. Jun 14, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-browser` A pytest plugin for console based browser test selection just after the collection phase Dec 10, 2016 3 - Alpha N/A - :pypi:`pytest-browsermob-proxy` BrowserMob proxy plugin for py.test. Jun 11, 2013 4 - Beta N/A - :pypi:`pytest-browserstack-local` \`\`py.test\`\` plugin to run \`\`BrowserStackLocal\`\` in background. Feb 09, 2018 N/A N/A - :pypi:`pytest-bug` Pytest plugin for marking tests as a bug Jun 02, 2020 5 - Production/Stable pytest (>=3.6.0) - :pypi:`pytest-bugtong-tag` pytest-bugtong-tag is a plugin for pytest Apr 23, 2021 N/A N/A - :pypi:`pytest-bugzilla` py.test bugzilla integration plugin May 05, 2010 4 - Beta N/A - :pypi:`pytest-bugzilla-notifier` A plugin that allows you to execute create, update, and read information from BugZilla bugs Jun 15, 2018 4 - Beta pytest (>=2.9.2) - :pypi:`pytest-buildkite` Plugin for pytest that automatically publishes coverage and pytest report annotations to Buildkite. Jul 13, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-builtin-types` Nov 17, 2021 N/A pytest - :pypi:`pytest-bwrap` Run your tests in Bubblewrap sandboxes Oct 26, 2018 3 - Alpha N/A - :pypi:`pytest-cache` pytest plugin with mechanisms for caching across test runs Jun 04, 2013 3 - Alpha N/A - :pypi:`pytest-cache-assert` Cache assertion data to simplify regression testing of complex serializable data Nov 03, 2021 4 - Beta pytest (>=5) - :pypi:`pytest-cagoule` Pytest plugin to only run tests affected by changes Jan 01, 2020 3 - Alpha N/A - :pypi:`pytest-camel-collect` Enable CamelCase-aware pytest class collection Aug 02, 2020 N/A pytest (>=2.9) - :pypi:`pytest-canonical-data` A plugin which allows to compare results with canonical results, based on previous runs May 08, 2020 2 - Pre-Alpha pytest (>=3.5.0) - :pypi:`pytest-caprng` A plugin that replays pRNG state on failure. May 02, 2018 4 - Beta N/A - :pypi:`pytest-capture-deprecatedwarnings` pytest plugin to capture all deprecatedwarnings and put them in one file Apr 30, 2019 N/A N/A - :pypi:`pytest-capturelogs` A sample Python project Sep 11, 2021 3 - Alpha N/A - :pypi:`pytest-cases` Separate test code from test cases in pytest. Nov 08, 2021 5 - Production/Stable N/A - :pypi:`pytest-cassandra` Cassandra CCM Test Fixtures for pytest Nov 04, 2017 1 - Planning N/A - :pypi:`pytest-catchlog` py.test plugin to catch log messages. This is a fork of pytest-capturelog. Jan 24, 2016 4 - Beta pytest (>=2.6) - :pypi:`pytest-catch-server` Pytest plugin with server for catching HTTP requests. Dec 12, 2019 5 - Production/Stable N/A - :pypi:`pytest-celery` pytest-celery a shim pytest plugin to enable celery.contrib.pytest May 06, 2021 N/A N/A - :pypi:`pytest-chainmaker` pytest plugin for chainmaker Oct 15, 2021 N/A N/A - :pypi:`pytest-chalice` A set of py.test fixtures for AWS Chalice Jul 01, 2020 4 - Beta N/A - :pypi:`pytest-change-report` turn . into √,turn F into x Sep 14, 2020 N/A pytest - :pypi:`pytest-chdir` A pytest fixture for changing current working directory Jan 28, 2020 N/A pytest (>=5.0.0,<6.0.0) - :pypi:`pytest-checkdocs` check the README when running tests Jul 31, 2021 5 - Production/Stable pytest (>=4.6) ; extra == 'testing' - :pypi:`pytest-checkipdb` plugin to check if there are ipdb debugs left Jul 22, 2020 5 - Production/Stable pytest (>=2.9.2) - :pypi:`pytest-check-links` Check links in files Jul 29, 2020 N/A pytest (>=4.6) - :pypi:`pytest-check-mk` pytest plugin to test Check_MK checks Nov 19, 2015 4 - Beta pytest - :pypi:`pytest-circleci` py.test plugin for CircleCI May 03, 2019 N/A N/A - :pypi:`pytest-circleci-parallelized` Parallelize pytest across CircleCI workers. Mar 26, 2019 N/A N/A - :pypi:`pytest-ckan` Backport of CKAN 2.9 pytest plugin and fixtures to CAKN 2.8 Apr 28, 2020 4 - Beta pytest - :pypi:`pytest-clarity` A plugin providing an alternative, colourful diff output for failing assertions. Jun 11, 2021 N/A N/A - :pypi:`pytest-cldf` Easy quality control for CLDF datasets using pytest May 06, 2019 N/A N/A - :pypi:`pytest-click` Py.test plugin for Click Aug 29, 2020 5 - Production/Stable pytest (>=5.0) - :pypi:`pytest-clld` Nov 29, 2021 N/A pytest (>=3.6) - :pypi:`pytest-cloud` Distributed tests planner plugin for pytest testing framework. Oct 05, 2020 6 - Mature N/A - :pypi:`pytest-cloudflare-worker` pytest plugin for testing cloudflare workers Mar 30, 2021 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-cobra` PyTest plugin for testing Smart Contracts for Ethereum blockchain. Jun 29, 2019 3 - Alpha pytest (<4.0.0,>=3.7.1) - :pypi:`pytest-codeblocks` Test code blocks in your READMEs Oct 13, 2021 4 - Beta pytest (>=6) - :pypi:`pytest-codecheckers` pytest plugin to add source code sanity checks (pep8 and friends) Feb 13, 2010 N/A N/A - :pypi:`pytest-codecov` Pytest plugin for uploading pytest-cov results to codecov.io Oct 27, 2021 4 - Beta pytest (>=4.6.0) - :pypi:`pytest-codegen` Automatically create pytest test signatures Aug 23, 2020 2 - Pre-Alpha N/A - :pypi:`pytest-codestyle` pytest plugin to run pycodestyle Mar 23, 2020 3 - Alpha N/A - :pypi:`pytest-collect-formatter` Formatter for pytest collect output Mar 29, 2021 5 - Production/Stable N/A - :pypi:`pytest-collect-formatter2` Formatter for pytest collect output May 31, 2021 5 - Production/Stable N/A - :pypi:`pytest-colordots` Colorizes the progress indicators Oct 06, 2017 5 - Production/Stable N/A - :pypi:`pytest-commander` An interactive GUI test runner for PyTest Aug 17, 2021 N/A pytest (<7.0.0,>=6.2.4) - :pypi:`pytest-common-subject` pytest framework for testing different aspects of a common method Nov 12, 2020 N/A pytest (>=3.6,<7) - :pypi:`pytest-concurrent` Concurrently execute test cases with multithread, multiprocess and gevent Jan 12, 2019 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-config` Base configurations and utilities for developing your Python project test suite with pytest. Nov 07, 2014 5 - Production/Stable N/A - :pypi:`pytest-confluence-report` Package stands for pytest plugin to upload results into Confluence page. Nov 06, 2020 N/A N/A - :pypi:`pytest-console-scripts` Pytest plugin for testing console scripts Sep 28, 2021 4 - Beta N/A - :pypi:`pytest-consul` pytest plugin with fixtures for testing consul aware apps Nov 24, 2018 3 - Alpha pytest - :pypi:`pytest-container` Pytest fixtures for writing container based tests Nov 19, 2021 3 - Alpha pytest (>=3.10) - :pypi:`pytest-contextfixture` Define pytest fixtures as context managers. Mar 12, 2013 4 - Beta N/A - :pypi:`pytest-contexts` A plugin to run tests written with the Contexts framework using pytest May 19, 2021 4 - Beta N/A - :pypi:`pytest-cookies` The pytest plugin for your Cookiecutter templates. 🍪 May 24, 2021 5 - Production/Stable pytest (>=3.3.0) - :pypi:`pytest-couchdbkit` py.test extension for per-test couchdb databases using couchdbkit Apr 17, 2012 N/A N/A - :pypi:`pytest-count` count erros and send email Jan 12, 2018 4 - Beta N/A - :pypi:`pytest-cov` Pytest plugin for measuring coverage. Oct 04, 2021 5 - Production/Stable pytest (>=4.6) - :pypi:`pytest-cover` Pytest plugin for measuring coverage. Forked from \`pytest-cov\`. Aug 01, 2015 5 - Production/Stable N/A - :pypi:`pytest-coverage` Jun 17, 2015 N/A N/A - :pypi:`pytest-coverage-context` Coverage dynamic context support for PyTest, including sub-processes Jan 04, 2021 4 - Beta pytest (>=6.1.0) - :pypi:`pytest-cov-exclude` Pytest plugin for excluding tests based on coverage data Apr 29, 2016 4 - Beta pytest (>=2.8.0,<2.9.0); extra == 'dev' - :pypi:`pytest-cpp` Use pytest's runner to discover and execute C++ tests Dec 03, 2021 5 - Production/Stable pytest (!=5.4.0,!=5.4.1) - :pypi:`pytest-cram` Run cram tests with pytest. Aug 08, 2020 N/A N/A - :pypi:`pytest-crate` Manages CrateDB instances during your integration tests May 28, 2019 3 - Alpha pytest (>=4.0) - :pypi:`pytest-cricri` A Cricri plugin for pytest. Jan 27, 2018 N/A pytest - :pypi:`pytest-crontab` add crontab task in crontab Dec 09, 2019 N/A N/A - :pypi:`pytest-csv` CSV output for pytest. Apr 22, 2021 N/A pytest (>=6.0) - :pypi:`pytest-curio` Pytest support for curio. Oct 07, 2020 N/A N/A - :pypi:`pytest-curl-report` pytest plugin to generate curl command line report Dec 11, 2016 4 - Beta N/A - :pypi:`pytest-custom-concurrency` Custom grouping concurrence for pytest Feb 08, 2021 N/A N/A - :pypi:`pytest-custom-exit-code` Exit pytest test session with custom exit code in different scenarios Aug 07, 2019 4 - Beta pytest (>=4.0.2) - :pypi:`pytest-custom-nodeid` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 07, 2021 N/A N/A - :pypi:`pytest-custom-report` Configure the symbols displayed for test outcomes Jan 30, 2019 N/A pytest - :pypi:`pytest-custom-scheduling` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 01, 2021 N/A N/A - :pypi:`pytest-cython` A plugin for testing Cython extension modules Jan 26, 2021 4 - Beta pytest (>=2.7.3) - :pypi:`pytest-darker` A pytest plugin for checking of modified code using Darker Aug 16, 2020 N/A pytest (>=6.0.1) ; extra == 'test' - :pypi:`pytest-dash` pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A - :pypi:`pytest-data` Useful functions for managing data for pytest fixtures Nov 01, 2016 5 - Production/Stable N/A - :pypi:`pytest-databricks` Pytest plugin for remote Databricks notebooks testing Jul 29, 2020 N/A pytest - :pypi:`pytest-datadir` pytest plugin for test data directories and files Oct 22, 2019 5 - Production/Stable pytest (>=2.7.0) - :pypi:`pytest-datadir-mgr` Manager for test data providing downloads, caching of generated files, and a context for temp directories. Aug 16, 2021 5 - Production/Stable pytest - :pypi:`pytest-datadir-ng` Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. Dec 25, 2019 5 - Production/Stable pytest - :pypi:`pytest-data-file` Fixture "data" and "case_data" for test from yaml file Dec 04, 2019 N/A N/A - :pypi:`pytest-datafiles` py.test plugin to create a 'tmpdir' containing predefined files/directories. Oct 07, 2018 5 - Production/Stable pytest (>=3.6) - :pypi:`pytest-datafixtures` Data fixtures for pytest made simple Dec 05, 2020 5 - Production/Stable N/A - :pypi:`pytest-data-from-files` pytest plugin to provide data from files loaded automatically Oct 13, 2021 4 - Beta pytest - :pypi:`pytest-dataplugin` A pytest plugin for managing an archive of test data. Sep 16, 2017 1 - Planning N/A - :pypi:`pytest-datarecorder` A py.test plugin recording and comparing test output. Apr 20, 2020 5 - Production/Stable pytest - :pypi:`pytest-datatest` A pytest plugin for test driven data-wrangling (this is the development version of datatest's pytest integration). Oct 15, 2020 4 - Beta pytest (>=3.3) - :pypi:`pytest-db` Session scope fixture "db" for mysql query or change Dec 04, 2019 N/A N/A - :pypi:`pytest-dbfixtures` Databases fixtures plugin for py.test. Dec 07, 2016 4 - Beta N/A - :pypi:`pytest-db-plugin` Nov 27, 2021 N/A pytest (>=5.0) - :pypi:`pytest-dbt-adapter` A pytest plugin for testing dbt adapter plugins Nov 24, 2021 N/A pytest (<7,>=6) - :pypi:`pytest-dbus-notification` D-BUS notifications for pytest results. Mar 05, 2014 5 - Production/Stable N/A - :pypi:`pytest-deadfixtures` A simple plugin to list unused fixtures in pytest Jul 23, 2020 5 - Production/Stable N/A - :pypi:`pytest-deepcov` deepcov Mar 30, 2021 N/A N/A - :pypi:`pytest-defer` Aug 24, 2021 N/A N/A - :pypi:`pytest-demo-plugin` pytest示例插件 May 15, 2021 N/A N/A - :pypi:`pytest-dependency` Manage dependencies of tests Feb 14, 2020 4 - Beta N/A - :pypi:`pytest-depends` Tests that depend on other tests Apr 05, 2020 5 - Production/Stable pytest (>=3) - :pypi:`pytest-deprecate` Mark tests as testing a deprecated feature with a warning note. Jul 01, 2019 N/A N/A - :pypi:`pytest-describe` Describe-style plugin for pytest Nov 13, 2021 4 - Beta pytest (>=4.0.0) - :pypi:`pytest-describe-it` plugin for rich text descriptions Jul 19, 2019 4 - Beta pytest - :pypi:`pytest-devpi-server` DevPI server fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-diamond` pytest plugin for diamond Aug 31, 2015 4 - Beta N/A - :pypi:`pytest-dicom` pytest plugin to provide DICOM fixtures Dec 19, 2018 3 - Alpha pytest - :pypi:`pytest-dictsdiff` Jul 26, 2019 N/A N/A - :pypi:`pytest-diff` A simple plugin to use with pytest Mar 30, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-disable` pytest plugin to disable a test and skip it from testrun Sep 10, 2015 4 - Beta N/A - :pypi:`pytest-disable-plugin` Disable plugins per test Feb 28, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-discord` A pytest plugin to notify test results to a Discord channel. Mar 20, 2021 3 - Alpha pytest (!=6.0.0,<7,>=3.3.2) - :pypi:`pytest-django` A Django plugin for pytest. Dec 02, 2021 5 - Production/Stable pytest (>=5.4.0) - :pypi:`pytest-django-ahead` A Django plugin for pytest. Oct 27, 2016 5 - Production/Stable pytest (>=2.9) - :pypi:`pytest-djangoapp` Nice pytest plugin to help you with Django pluggable application testing. Aug 04, 2021 4 - Beta N/A - :pypi:`pytest-django-cache-xdist` A djangocachexdist plugin for pytest May 12, 2020 4 - Beta N/A - :pypi:`pytest-django-casperjs` Integrate CasperJS with your django tests as a pytest fixture. Mar 15, 2015 2 - Pre-Alpha N/A - :pypi:`pytest-django-dotenv` Pytest plugin used to setup environment variables with django-dotenv Nov 26, 2019 4 - Beta pytest (>=2.6.0) - :pypi:`pytest-django-factories` Factories for your Django models that can be used as Pytest fixtures. Nov 12, 2020 4 - Beta N/A - :pypi:`pytest-django-gcir` A Django plugin for pytest. Mar 06, 2018 5 - Production/Stable N/A - :pypi:`pytest-django-haystack` Cleanup your Haystack indexes between tests Sep 03, 2017 5 - Production/Stable pytest (>=2.3.4) - :pypi:`pytest-django-ifactory` A model instance factory for pytest-django Jan 13, 2021 3 - Alpha N/A - :pypi:`pytest-django-lite` The bare minimum to integrate py.test with Django. Jan 30, 2014 N/A N/A - :pypi:`pytest-django-liveserver-ssl` Jul 30, 2021 3 - Alpha N/A - :pypi:`pytest-django-model` A Simple Way to Test your Django Models Feb 14, 2019 4 - Beta N/A - :pypi:`pytest-django-ordering` A pytest plugin for preserving the order in which Django runs tests. Jul 25, 2019 5 - Production/Stable pytest (>=2.3.0) - :pypi:`pytest-django-queries` Generate performance reports from your django database performance tests. Mar 01, 2021 N/A N/A - :pypi:`pytest-djangorestframework` A djangorestframework plugin for pytest Aug 11, 2019 4 - Beta N/A - :pypi:`pytest-django-rq` A pytest plugin to help writing unit test for django-rq Apr 13, 2020 4 - Beta N/A - :pypi:`pytest-django-sqlcounts` py.test plugin for reporting the number of SQLs executed per django testcase. Jun 16, 2015 4 - Beta N/A - :pypi:`pytest-django-testing-postgresql` Use a temporary PostgreSQL database with pytest-django Dec 05, 2019 3 - Alpha N/A - :pypi:`pytest-doc` A documentation plugin for py.test. Jun 28, 2015 5 - Production/Stable N/A - :pypi:`pytest-docgen` An RST Documentation Generator for pytest-based test suites Apr 17, 2020 N/A N/A - :pypi:`pytest-docker` Simple pytest fixtures for Docker and docker-compose based tests Jun 14, 2021 N/A pytest (<7.0,>=4.0) - :pypi:`pytest-docker-butla` Jun 16, 2019 3 - Alpha N/A - :pypi:`pytest-dockerc` Run, manage and stop Docker Compose project from Docker API Oct 09, 2020 5 - Production/Stable pytest (>=3.0) - :pypi:`pytest-docker-compose` Manages Docker containers during your integration tests Jan 26, 2021 5 - Production/Stable pytest (>=3.3) - :pypi:`pytest-docker-db` A plugin to use docker databases for pytests Mar 20, 2021 5 - Production/Stable pytest (>=3.1.1) - :pypi:`pytest-docker-fixtures` pytest docker fixtures Nov 23, 2021 3 - Alpha N/A - :pypi:`pytest-docker-git-fixtures` Pytest fixtures for testing with git scm. Mar 11, 2021 4 - Beta pytest - :pypi:`pytest-docker-pexpect` pytest plugin for writing functional tests with pexpect and docker Jan 14, 2019 N/A pytest - :pypi:`pytest-docker-postgresql` A simple plugin to use with pytest Sep 24, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-docker-py` Easy to use, simple to extend, pytest plugin that minimally leverages docker-py. Nov 27, 2018 N/A pytest (==4.0.0) - :pypi:`pytest-docker-registry-fixtures` Pytest fixtures for testing with docker registries. Mar 04, 2021 4 - Beta pytest - :pypi:`pytest-docker-tools` Docker integration tests for pytest Jul 23, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) - :pypi:`pytest-docs` Documentation tool for pytest Nov 11, 2018 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-docstyle` pytest plugin to run pydocstyle Mar 23, 2020 3 - Alpha N/A - :pypi:`pytest-doctest-custom` A py.test plugin for customizing string representations of doctest results. Jul 25, 2016 4 - Beta N/A - :pypi:`pytest-doctest-ellipsis-markers` Setup additional values for ELLIPSIS_MARKER for doctests Jan 12, 2018 4 - Beta N/A - :pypi:`pytest-doctest-import` A simple pytest plugin to import names and add them to the doctest namespace. Nov 13, 2018 4 - Beta pytest (>=3.3.0) - :pypi:`pytest-doctestplus` Pytest plugin with advanced doctest features. Nov 16, 2021 3 - Alpha pytest (>=4.6) - :pypi:`pytest-doctest-ufunc` A plugin to run doctests in docstrings of Numpy ufuncs Aug 02, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-dolphin` Some extra stuff that we use ininternally Nov 30, 2016 4 - Beta pytest (==3.0.4) - :pypi:`pytest-doorstop` A pytest plugin for adding test results into doorstop items. Jun 09, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-dotenv` A py.test plugin that parses environment files before running tests Jun 16, 2020 4 - Beta pytest (>=5.0.0) - :pypi:`pytest-drf` A Django REST framework plugin for pytest. Nov 12, 2020 5 - Production/Stable pytest (>=3.6) - :pypi:`pytest-drivings` Tool to allow webdriver automation to be ran locally or remotely Jan 13, 2021 N/A N/A - :pypi:`pytest-drop-dup-tests` A Pytest plugin to drop duplicated tests during collection May 23, 2020 4 - Beta pytest (>=2.7) - :pypi:`pytest-dummynet` A py.test plugin providing access to a dummynet. Oct 13, 2021 5 - Production/Stable pytest - :pypi:`pytest-dump2json` A pytest plugin for dumping test results to json. Jun 29, 2015 N/A N/A - :pypi:`pytest-duration-insights` Jun 25, 2021 N/A N/A - :pypi:`pytest-dynamicrerun` A pytest plugin to rerun tests dynamically based off of test outcome and output. Aug 15, 2020 4 - Beta N/A - :pypi:`pytest-dynamodb` DynamoDB fixtures for pytest Jun 03, 2021 5 - Production/Stable pytest - :pypi:`pytest-easy-addoption` pytest-easy-addoption: Easy way to work with pytest addoption Jan 22, 2020 N/A N/A - :pypi:`pytest-easy-api` Simple API testing with pytest Mar 26, 2018 N/A N/A - :pypi:`pytest-easyMPI` Package that supports mpi tests in pytest Oct 21, 2020 N/A N/A - :pypi:`pytest-easyread` pytest plugin that makes terminal printouts of the reports easier to read Nov 17, 2017 N/A N/A - :pypi:`pytest-easy-server` Pytest plugin for easy testing against servers May 01, 2021 4 - Beta pytest (<5.0.0,>=4.3.1) ; python_version < "3.5" - :pypi:`pytest-ec2` Pytest execution on EC2 instance Oct 22, 2019 3 - Alpha N/A - :pypi:`pytest-echo` pytest plugin with mechanisms for echoing environment variables, package version and generic attributes Jan 08, 2020 5 - Production/Stable N/A - :pypi:`pytest-elasticsearch` Elasticsearch fixtures and fixture factories for Pytest. May 12, 2021 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-elements` Tool to help automate user interfaces Jan 13, 2021 N/A pytest (>=5.4,<6.0) - :pypi:`pytest-elk-reporter` A simple plugin to use with pytest Jan 24, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-email` Send execution result email Jul 08, 2020 N/A pytest - :pypi:`pytest-embedded` pytest embedded plugin Nov 29, 2021 N/A pytest (>=6.2.0) - :pypi:`pytest-embedded-idf` pytest embedded plugin for esp-idf project Nov 29, 2021 N/A N/A - :pypi:`pytest-embedded-jtag` pytest embedded plugin for testing with jtag Nov 29, 2021 N/A N/A - :pypi:`pytest-embedded-qemu` pytest embedded plugin for qemu, not target chip Nov 29, 2021 N/A N/A - :pypi:`pytest-embedded-qemu-idf` pytest embedded plugin for esp-idf project by qemu, not target chip Jun 29, 2021 N/A N/A - :pypi:`pytest-embedded-serial` pytest embedded plugin for testing serial ports Nov 29, 2021 N/A N/A - :pypi:`pytest-embedded-serial-esp` pytest embedded plugin for testing espressif boards via serial ports Nov 29, 2021 N/A N/A - :pypi:`pytest-emoji` A pytest plugin that adds emojis to your test result report Feb 19, 2019 4 - Beta pytest (>=4.2.1) - :pypi:`pytest-emoji-output` Pytest plugin to represent test output with emoji support Oct 10, 2021 4 - Beta pytest (==6.0.1) - :pypi:`pytest-enabler` Enable installed pytest plugins Nov 08, 2021 5 - Production/Stable pytest (>=6) ; extra == 'testing' - :pypi:`pytest-encode` set your encoding and logger Nov 06, 2021 N/A N/A - :pypi:`pytest-encode-kane` set your encoding and logger Nov 16, 2021 N/A pytest - :pypi:`pytest-enhancements` Improvements for pytest (rejected upstream) Oct 30, 2019 4 - Beta N/A - :pypi:`pytest-env` py.test plugin that allows you to add environment variables. Jun 16, 2017 4 - Beta N/A - :pypi:`pytest-envfiles` A py.test plugin that parses environment files before running tests Oct 08, 2015 3 - Alpha N/A - :pypi:`pytest-env-info` Push information about the running pytest into envvars Nov 25, 2017 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-envraw` py.test plugin that allows you to add environment variables. Aug 27, 2020 4 - Beta pytest (>=2.6.0) - :pypi:`pytest-envvars` Pytest plugin to validate use of envvars on your tests Jun 13, 2020 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-env-yaml` Apr 02, 2019 N/A N/A - :pypi:`pytest-eradicate` pytest plugin to check for commented out code Sep 08, 2020 N/A pytest (>=2.4.2) - :pypi:`pytest-error-for-skips` Pytest plugin to treat skipped tests a test failure Dec 19, 2019 4 - Beta pytest (>=4.6) - :pypi:`pytest-eth` PyTest plugin for testing Smart Contracts for Ethereum Virtual Machine (EVM). Aug 14, 2020 1 - Planning N/A - :pypi:`pytest-ethereum` pytest-ethereum: Pytest library for ethereum projects. Jun 24, 2019 3 - Alpha pytest (==3.3.2); extra == 'dev' - :pypi:`pytest-eucalyptus` Pytest Plugin for BDD Aug 13, 2019 N/A pytest (>=4.2.0) - :pypi:`pytest-eventlet` Applies eventlet monkey-patch as a pytest plugin. Oct 04, 2021 N/A pytest ; extra == 'dev' - :pypi:`pytest-excel` pytest plugin for generating excel reports Oct 06, 2020 5 - Production/Stable N/A - :pypi:`pytest-exceptional` Better exceptions Mar 16, 2017 4 - Beta N/A - :pypi:`pytest-exception-script` Walk your code through exception script to check it's resiliency to failures. Aug 04, 2020 3 - Alpha pytest - :pypi:`pytest-executable` pytest plugin for testing executables Nov 10, 2021 4 - Beta pytest (<6.3,>=4.3) - :pypi:`pytest-expect` py.test plugin to store test expectations and mark tests based on them Apr 21, 2016 4 - Beta N/A - :pypi:`pytest-expecter` Better testing with expecter and pytest. Jul 08, 2020 5 - Production/Stable N/A - :pypi:`pytest-expectr` This plugin is used to expect multiple assert using pytest framework. Oct 05, 2018 N/A pytest (>=2.4.2) - :pypi:`pytest-explicit` A Pytest plugin to ignore certain marked tests by default Jun 15, 2021 5 - Production/Stable pytest - :pypi:`pytest-exploratory` Interactive console for pytest. Aug 03, 2021 N/A pytest (>=5.3) - :pypi:`pytest-external-blockers` a special outcome for tests that are blocked for external reasons Oct 05, 2021 N/A pytest - :pypi:`pytest-extra-durations` A pytest plugin to get durations on a per-function basis and per module basis. Apr 21, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-fabric` Provides test utilities to run fabric task tests by using docker containers Sep 12, 2018 5 - Production/Stable N/A - :pypi:`pytest-factory` Use factories for test setup with py.test Sep 06, 2020 3 - Alpha pytest (>4.3) - :pypi:`pytest-factoryboy` Factory Boy support for pytest. Dec 30, 2020 6 - Mature pytest (>=4.6) - :pypi:`pytest-factoryboy-fixtures` Generates pytest fixtures that allow the use of type hinting Jun 25, 2020 N/A N/A - :pypi:`pytest-factoryboy-state` Simple factoryboy random state management Dec 11, 2020 4 - Beta pytest (>=5.0) - :pypi:`pytest-failed-screenshot` Test case fails,take a screenshot,save it,attach it to the allure Apr 21, 2021 N/A N/A - :pypi:`pytest-failed-to-verify` A pytest plugin that helps better distinguishing real test failures from setup flakiness. Aug 08, 2019 5 - Production/Stable pytest (>=4.1.0) - :pypi:`pytest-faker` Faker integration with the pytest framework. Dec 19, 2016 6 - Mature N/A - :pypi:`pytest-falcon` Pytest helpers for Falcon. Sep 07, 2016 4 - Beta N/A - :pypi:`pytest-falcon-client` Pytest \`client\` fixture for the Falcon Framework Mar 19, 2019 N/A N/A - :pypi:`pytest-fantasy` Pytest plugin for Flask Fantasy Framework Mar 14, 2019 N/A N/A - :pypi:`pytest-fastapi` Dec 27, 2020 N/A N/A - :pypi:`pytest-fastest` Use SCM and coverage to run only needed tests Mar 05, 2020 N/A N/A - :pypi:`pytest-fast-first` Pytest plugin that runs fast tests first Apr 02, 2021 3 - Alpha pytest - :pypi:`pytest-faulthandler` py.test plugin that activates the fault handler module for tests (dummy package) Jul 04, 2019 6 - Mature pytest (>=5.0) - :pypi:`pytest-fauxfactory` Integration of fauxfactory into pytest. Dec 06, 2017 5 - Production/Stable pytest (>=3.2) - :pypi:`pytest-figleaf` py.test figleaf coverage plugin Jan 18, 2010 5 - Production/Stable N/A - :pypi:`pytest-filecov` A pytest plugin to detect unused files Jun 27, 2021 4 - Beta pytest - :pypi:`pytest-filedata` easily load data from files Jan 17, 2019 4 - Beta N/A - :pypi:`pytest-filemarker` A pytest plugin that runs marked tests when files change. Dec 01, 2020 N/A pytest - :pypi:`pytest-filter-case` run test cases filter by mark Nov 05, 2020 N/A N/A - :pypi:`pytest-filter-subpackage` Pytest plugin for filtering based on sub-packages Jan 09, 2020 3 - Alpha pytest (>=3.0) - :pypi:`pytest-find-dependencies` A pytest plugin to find dependencies between tests Apr 21, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-finer-verdicts` A pytest plugin to treat non-assertion failures as test errors. Jun 18, 2020 N/A pytest (>=5.4.3) - :pypi:`pytest-firefox` pytest plugin to manipulate firefox Aug 08, 2017 3 - Alpha pytest (>=3.0.2) - :pypi:`pytest-fixture-config` Fixture configuration utils for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-fixture-maker` Pytest plugin to load fixtures from YAML files Sep 21, 2021 N/A N/A - :pypi:`pytest-fixture-marker` A pytest plugin to add markers based on fixtures used. Oct 11, 2020 5 - Production/Stable N/A - :pypi:`pytest-fixture-order` pytest plugin to control fixture evaluation order Aug 25, 2020 N/A pytest (>=3.0) - :pypi:`pytest-fixtures` Common fixtures for pytest May 01, 2019 5 - Production/Stable N/A - :pypi:`pytest-fixture-tools` Plugin for pytest which provides tools for fixtures Aug 18, 2020 6 - Mature pytest - :pypi:`pytest-fixture-typecheck` A pytest plugin to assert type annotations at runtime. Aug 24, 2021 N/A pytest - :pypi:`pytest-flake8` pytest plugin to check FLAKE8 requirements Dec 16, 2020 4 - Beta pytest (>=3.5) - :pypi:`pytest-flake8-path` A pytest fixture for testing flake8 plugins. Aug 11, 2021 5 - Production/Stable pytest - :pypi:`pytest-flakefinder` Runs tests multiple times to expose flakiness. Jul 28, 2020 4 - Beta pytest (>=2.7.1) - :pypi:`pytest-flakes` pytest plugin to check source code with pyflakes Dec 02, 2021 5 - Production/Stable pytest (>=5) - :pypi:`pytest-flaptastic` Flaptastic py.test plugin Mar 17, 2019 N/A N/A - :pypi:`pytest-flask` A set of py.test fixtures to test Flask applications. Feb 27, 2021 5 - Production/Stable pytest (>=5.2) - :pypi:`pytest-flask-sqlalchemy` A pytest plugin for preserving test isolation in Flask-SQlAlchemy using database transactions. Apr 04, 2019 4 - Beta pytest (>=3.2.1) - :pypi:`pytest-flask-sqlalchemy-transactions` Run tests in transactions using pytest, Flask, and SQLalchemy. Aug 02, 2018 4 - Beta pytest (>=3.2.1) - :pypi:`pytest-flyte` Pytest fixtures for simplifying Flyte integration testing May 03, 2021 N/A pytest - :pypi:`pytest-focus` A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest - :pypi:`pytest-forcefail` py.test plugin to make the test failing regardless of pytest.mark.xfail May 15, 2018 4 - Beta N/A - :pypi:`pytest-forward-compatability` A name to avoid typosquating pytest-foward-compatibility Sep 06, 2020 N/A N/A - :pypi:`pytest-forward-compatibility` A pytest plugin to shim pytest commandline options for fowards compatibility Sep 29, 2020 N/A N/A - :pypi:`pytest-freezegun` Wrap tests with fixtures in freeze_time Jul 19, 2020 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-freeze-reqs` Check if requirement files are frozen Apr 29, 2021 N/A N/A - :pypi:`pytest-frozen-uuids` Deterministically frozen UUID's for your tests Oct 19, 2021 N/A pytest (>=3.0) - :pypi:`pytest-func-cov` Pytest plugin for measuring function coverage Apr 15, 2021 3 - Alpha pytest (>=5) - :pypi:`pytest-funparam` An alternative way to parametrize test cases. Dec 02, 2021 4 - Beta pytest >=4.6.0 - :pypi:`pytest-fxa` pytest plugin for Firefox Accounts Aug 28, 2018 5 - Production/Stable N/A - :pypi:`pytest-fxtest` Oct 27, 2020 N/A N/A - :pypi:`pytest-gc` The garbage collector plugin for py.test Feb 01, 2018 N/A N/A - :pypi:`pytest-gcov` Uses gcov to measure test coverage of a C library Feb 01, 2018 3 - Alpha N/A - :pypi:`pytest-gevent` Ensure that gevent is properly patched when invoking pytest Feb 25, 2020 N/A pytest - :pypi:`pytest-gherkin` A flexible framework for executing BDD gherkin tests Jul 27, 2019 3 - Alpha pytest (>=5.0.0) - :pypi:`pytest-ghostinspector` For finding/executing Ghost Inspector tests May 17, 2016 3 - Alpha N/A - :pypi:`pytest-girder` A set of pytest fixtures for testing Girder applications. Nov 30, 2021 N/A N/A - :pypi:`pytest-git` Git repository fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-gitcov` Pytest plugin for reporting on coverage of the last git commit. Jan 11, 2020 2 - Pre-Alpha N/A - :pypi:`pytest-git-fixtures` Pytest fixtures for testing with git. Mar 11, 2021 4 - Beta pytest - :pypi:`pytest-github` Plugin for py.test that associates tests with github issues using a marker. Mar 07, 2019 5 - Production/Stable N/A - :pypi:`pytest-github-actions-annotate-failures` pytest plugin to annotate failed tests with a workflow command for GitHub Actions Oct 24, 2021 N/A pytest (>=4.0.0) - :pypi:`pytest-gitignore` py.test plugin to ignore the same files as git Jul 17, 2015 4 - Beta N/A - :pypi:`pytest-glamor-allure` Extends allure-pytest functionality Nov 26, 2021 4 - Beta pytest - :pypi:`pytest-gnupg-fixtures` Pytest fixtures for testing with gnupg. Mar 04, 2021 4 - Beta pytest - :pypi:`pytest-golden` Plugin for pytest that offloads expected outputs to data files Nov 23, 2020 N/A pytest (>=6.1.2,<7.0.0) - :pypi:`pytest-graphql-schema` Get graphql schema as fixture for pytest Oct 18, 2019 N/A N/A - :pypi:`pytest-greendots` Green progress dots Feb 08, 2014 3 - Alpha N/A - :pypi:`pytest-growl` Growl notifications for pytest results. Jan 13, 2014 5 - Production/Stable N/A - :pypi:`pytest-grpc` pytest plugin for grpc May 01, 2020 N/A pytest (>=3.6.0) - :pypi:`pytest-hammertime` Display "🔨 " instead of "." for passed pytest tests. Jul 28, 2018 N/A pytest - :pypi:`pytest-harvest` Store data created during your pytest tests execution, and retrieve it at the end of the session, e.g. for applicative benchmarking purposes. Apr 01, 2021 5 - Production/Stable N/A - :pypi:`pytest-helm-chart` A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. Jun 15, 2020 4 - Beta pytest (>=5.4.2,<6.0.0) - :pypi:`pytest-helm-charts` A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. Oct 26, 2021 4 - Beta pytest (>=6.1.2,<7.0.0) - :pypi:`pytest-helper` Functions to help in using the pytest testing framework May 31, 2019 5 - Production/Stable N/A - :pypi:`pytest-helpers` pytest helpers May 17, 2020 N/A pytest - :pypi:`pytest-helpers-namespace` Pytest Helpers Namespace Plugin Apr 29, 2021 5 - Production/Stable pytest (>=6.0.0) - :pypi:`pytest-hidecaptured` Hide captured output May 04, 2018 4 - Beta pytest (>=2.8.5) - :pypi:`pytest-historic` Custom report to display pytest historical execution records Apr 08, 2020 N/A pytest - :pypi:`pytest-historic-hook` Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report Apr 08, 2020 N/A pytest - :pypi:`pytest-homeassistant` A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A - :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Nov 20, 2021 3 - Alpha pytest (==6.2.5) - :pypi:`pytest-honors` Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A - :pypi:`pytest-hoverfly` Simplify working with Hoverfly from pytest Jul 12, 2021 N/A pytest (>=5.0) - :pypi:`pytest-hoverfly-wrapper` Integrates the Hoverfly HTTP proxy into Pytest Aug 29, 2021 4 - Beta N/A - :pypi:`pytest-hpfeeds` Helpers for testing hpfeeds in your python project Aug 27, 2021 4 - Beta pytest (>=6.2.4,<7.0.0) - :pypi:`pytest-html` pytest plugin for generating HTML reports Dec 13, 2020 5 - Production/Stable pytest (!=6.0.0,>=5.0) - :pypi:`pytest-html-lee` optimized pytest plugin for generating HTML reports Jun 30, 2020 5 - Production/Stable pytest (>=5.0) - :pypi:`pytest-html-profiling` Pytest plugin for generating HTML reports with per-test profiling and optionally call graph visualizations. Based on pytest-html by Dave Hunt. Feb 11, 2020 5 - Production/Stable pytest (>=3.0) - :pypi:`pytest-html-reporter` Generates a static html report based on pytest framework Apr 25, 2021 N/A N/A - :pypi:`pytest-html-thread` pytest plugin for generating HTML reports Dec 29, 2020 5 - Production/Stable N/A - :pypi:`pytest-http` Fixture "http" for http requests Dec 05, 2019 N/A N/A - :pypi:`pytest-httpbin` Easily test your HTTP library against a local copy of httpbin Feb 11, 2019 5 - Production/Stable N/A - :pypi:`pytest-http-mocker` Pytest plugin for http mocking (via https://github.com/vilus/mocker) Oct 20, 2019 N/A N/A - :pypi:`pytest-httpretty` A thin wrapper of HTTPretty for pytest Feb 16, 2014 3 - Alpha N/A - :pypi:`pytest-httpserver` pytest-httpserver is a httpserver for pytest Oct 18, 2021 3 - Alpha pytest ; extra == 'dev' - :pypi:`pytest-httpx` Send responses to httpx. Nov 16, 2021 5 - Production/Stable pytest (==6.*) - :pypi:`pytest-httpx-blockage` Disable httpx requests during a test run Nov 16, 2021 N/A pytest (>=6.2.5) - :pypi:`pytest-hue` Visualise PyTest status via your Phillips Hue lights May 09, 2019 N/A N/A - :pypi:`pytest-hylang` Pytest plugin to allow running tests written in hylang Mar 28, 2021 N/A pytest - :pypi:`pytest-hypo-25` help hypo module for pytest Jan 12, 2020 3 - Alpha N/A - :pypi:`pytest-ibutsu` A plugin to sent pytest results to an Ibutsu server Jun 16, 2021 4 - Beta pytest - :pypi:`pytest-icdiff` use icdiff for better error messages in pytest assertions Apr 08, 2020 4 - Beta N/A - :pypi:`pytest-idapro` A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api Nov 03, 2018 N/A N/A - :pypi:`pytest-idempotent` Pytest plugin for testing function idempotence. Nov 26, 2021 N/A N/A - :pypi:`pytest-ignore-flaky` ignore failures from flaky tests (pytest plugin) Apr 23, 2021 5 - Production/Stable N/A - :pypi:`pytest-image-diff` Jul 28, 2021 3 - Alpha pytest - :pypi:`pytest-incremental` an incremental test runner (pytest plugin) Apr 24, 2021 5 - Production/Stable N/A - :pypi:`pytest-influxdb` Plugin for influxdb and pytest integration. Apr 20, 2021 N/A N/A - :pypi:`pytest-info-collector` pytest plugin to collect information from tests May 26, 2019 3 - Alpha N/A - :pypi:`pytest-informative-node` display more node ininformation. Apr 25, 2019 4 - Beta N/A - :pypi:`pytest-infrastructure` pytest stack validation prior to testing executing Apr 12, 2020 4 - Beta N/A - :pypi:`pytest-ini` Reuse pytest.ini to store env variables Sep 30, 2021 N/A N/A - :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Aug 17, 2021 5 - Production/Stable N/A - :pypi:`pytest-inmanta-extensions` Inmanta tests package May 27, 2021 5 - Production/Stable N/A - :pypi:`pytest-Inomaly` A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A - :pypi:`pytest-insta` A practical snapshot testing plugin for pytest Apr 07, 2021 N/A pytest (>=6.0.2,<7.0.0) - :pypi:`pytest-instafail` pytest plugin to show failures instantly Jun 14, 2020 4 - Beta pytest (>=2.9) - :pypi:`pytest-instrument` pytest plugin to instrument tests Apr 05, 2020 5 - Production/Stable pytest (>=5.1.0) - :pypi:`pytest-integration` Organizing pytests by integration or not Apr 16, 2020 N/A N/A - :pypi:`pytest-integration-mark` Automatic integration test marking and excluding plugin for pytest Jul 19, 2021 N/A pytest (>=5.2,<7.0) - :pypi:`pytest-interactive` A pytest plugin for console based interactive test selection just after the collection phase Nov 30, 2017 3 - Alpha N/A - :pypi:`pytest-intercept-remote` Pytest plugin for intercepting outgoing connection requests during pytest run. May 24, 2021 4 - Beta pytest (>=4.6) - :pypi:`pytest-invenio` Pytest fixtures for Invenio. May 11, 2021 5 - Production/Stable pytest (<7,>=6) - :pypi:`pytest-involve` Run tests covering a specific file or changeset Feb 02, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-ipdb` A py.test plug-in to enable drop to ipdb debugger on test failure. Sep 02, 2014 2 - Pre-Alpha N/A - :pypi:`pytest-ipynb` THIS PROJECT IS ABANDONED Jan 29, 2019 3 - Alpha N/A - :pypi:`pytest-isort` py.test plugin to check import ordering using isort Apr 27, 2021 5 - Production/Stable N/A - :pypi:`pytest-it` Pytest plugin to display test reports as a plaintext spec, inspired by Rspec: https://github.com/mattduck/pytest-it. Jan 22, 2020 4 - Beta N/A - :pypi:`pytest-iterassert` Nicer list and iterable assertion messages for pytest May 11, 2020 3 - Alpha N/A - :pypi:`pytest-jasmine` Run jasmine tests from your pytest test suite Nov 04, 2017 1 - Planning N/A - :pypi:`pytest-jest` A custom jest-pytest oriented Pytest reporter May 22, 2018 4 - Beta pytest (>=3.3.2) - :pypi:`pytest-jira` py.test JIRA integration plugin, using markers Dec 02, 2021 3 - Alpha N/A - :pypi:`pytest-jira-xray` pytest plugin to integrate tests with JIRA XRAY Nov 28, 2021 3 - Alpha pytest - :pypi:`pytest-jobserver` Limit parallel tests with posix jobserver. May 15, 2019 5 - Production/Stable pytest - :pypi:`pytest-joke` Test failures are better served with humor. Oct 08, 2019 4 - Beta pytest (>=4.2.1) - :pypi:`pytest-json` Generate JSON test reports Jan 18, 2016 4 - Beta N/A - :pypi:`pytest-jsonlint` UNKNOWN Aug 04, 2016 N/A N/A - :pypi:`pytest-json-report` A pytest plugin to report test results as JSON files Sep 24, 2021 4 - Beta pytest (>=3.8.0) - :pypi:`pytest-kafka` Zookeeper, Kafka server, and Kafka consumer fixtures for Pytest Aug 24, 2021 N/A pytest - :pypi:`pytest-kafkavents` A plugin to send pytest events to Kafka Sep 08, 2021 4 - Beta pytest - :pypi:`pytest-kind` Kubernetes test support with KIND for pytest Jan 24, 2021 5 - Production/Stable N/A - :pypi:`pytest-kivy` Kivy GUI tests fixtures using pytest Jul 06, 2021 4 - Beta pytest (>=3.6) - :pypi:`pytest-knows` A pytest plugin that can automaticly skip test case based on dependence info calculated by trace Aug 22, 2014 N/A N/A - :pypi:`pytest-konira` Run Konira DSL tests with py.test Oct 09, 2011 N/A N/A - :pypi:`pytest-krtech-common` pytest krtech common library Nov 28, 2016 4 - Beta N/A - :pypi:`pytest-kwparametrize` Alternate syntax for @pytest.mark.parametrize with test cases as dictionaries and default value fallbacks Jan 22, 2021 N/A pytest (>=6) - :pypi:`pytest-lambda` Define pytest fixtures with lambda functions. Aug 23, 2021 3 - Alpha pytest (>=3.6,<7) - :pypi:`pytest-lamp` Jan 06, 2017 3 - Alpha N/A - :pypi:`pytest-layab` Pytest fixtures for layab. Oct 05, 2020 5 - Production/Stable N/A - :pypi:`pytest-lazy-fixture` It helps to use fixtures in pytest.mark.parametrize Feb 01, 2020 4 - Beta pytest (>=3.2.5) - :pypi:`pytest-ldap` python-ldap fixtures for pytest Aug 18, 2020 N/A pytest - :pypi:`pytest-leaks` A pytest plugin to trace resource leaks. Nov 27, 2019 1 - Planning N/A - :pypi:`pytest-level` Select tests of a given level or lower Oct 21, 2019 N/A pytest - :pypi:`pytest-libfaketime` A python-libfaketime plugin for pytest. Dec 22, 2018 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-libiio` A pytest plugin to manage interfacing with libiio contexts Oct 29, 2021 4 - Beta N/A - :pypi:`pytest-libnotify` Pytest plugin that shows notifications about the test run Apr 02, 2021 3 - Alpha pytest - :pypi:`pytest-ligo` Jan 16, 2020 4 - Beta N/A - :pypi:`pytest-lineno` A pytest plugin to show the line numbers of test functions Dec 04, 2020 N/A pytest - :pypi:`pytest-line-profiler` Profile code executed by pytest May 03, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-lisa` Pytest plugin for organizing tests. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) - :pypi:`pytest-listener` A simple network listener May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-litf` A pytest plugin that stream output in LITF format Jan 18, 2021 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-live` Live results for pytest Mar 08, 2020 N/A pytest - :pypi:`pytest-localftpserver` A PyTest plugin which provides an FTP fixture for your tests Aug 25, 2021 5 - Production/Stable pytest - :pypi:`pytest-localserver` py.test plugin to test server connections locally. Nov 19, 2021 4 - Beta N/A - :pypi:`pytest-localstack` Pytest plugin for AWS integration tests Aug 22, 2019 4 - Beta pytest (>=3.3.0) - :pypi:`pytest-lockable` lockable resource plugin for pytest Nov 09, 2021 5 - Production/Stable pytest - :pypi:`pytest-locker` Used to lock object during testing. Essentially changing assertions from being hard coded to asserting that nothing changed Oct 29, 2021 N/A pytest (>=5.4) - :pypi:`pytest-log` print log Aug 15, 2021 N/A pytest (>=3.8) - :pypi:`pytest-logbook` py.test plugin to capture logbook log messages Nov 23, 2015 5 - Production/Stable pytest (>=2.8) - :pypi:`pytest-logdog` Pytest plugin to test logging Jun 15, 2021 1 - Planning pytest (>=6.2.0) - :pypi:`pytest-logfest` Pytest plugin providing three logger fixtures with basic or full writing to log files Jul 21, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-logger` Plugin configuring handlers for loggers from Python logging module. Jul 25, 2019 4 - Beta pytest (>=3.2) - :pypi:`pytest-logging` Configures logging and allows tweaking the log level with a py.test flag Nov 04, 2015 4 - Beta N/A - :pypi:`pytest-log-report` Package for creating a pytest test run reprot Dec 26, 2019 N/A N/A - :pypi:`pytest-manual-marker` pytest marker for marking manual tests Oct 11, 2021 3 - Alpha pytest (>=6) - :pypi:`pytest-markdown` Test your markdown docs with pytest Jan 15, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) - :pypi:`pytest-marker-bugzilla` py.test bugzilla integration plugin, using markers Jan 09, 2020 N/A N/A - :pypi:`pytest-markers-presence` A simple plugin to detect missed pytest tags and markers" Feb 04, 2021 4 - Beta pytest (>=6.0) - :pypi:`pytest-markfiltration` UNKNOWN Nov 08, 2011 3 - Alpha N/A - :pypi:`pytest-mark-no-py3` pytest plugin and bowler codemod to help migrate tests to Python 3 May 17, 2019 N/A pytest - :pypi:`pytest-marks` UNKNOWN Nov 23, 2012 3 - Alpha N/A - :pypi:`pytest-matcher` Match test output against patterns stored in files Apr 23, 2020 5 - Production/Stable pytest (>=3.4) - :pypi:`pytest-match-skip` Skip matching marks. Matches partial marks using wildcards. May 15, 2019 4 - Beta pytest (>=4.4.1) - :pypi:`pytest-mat-report` this is report Jan 20, 2021 N/A N/A - :pypi:`pytest-matrix` Provide tools for generating tests from combinations of fixtures. Jun 24, 2020 5 - Production/Stable pytest (>=5.4.3,<6.0.0) - :pypi:`pytest-mccabe` pytest plugin to run the mccabe code complexity checker. Jul 22, 2020 3 - Alpha pytest (>=5.4.0) - :pypi:`pytest-md` Plugin for generating Markdown reports for pytest results Jul 11, 2019 3 - Alpha pytest (>=4.2.1) - :pypi:`pytest-md-report` A pytest plugin to make a test results report with Markdown table format. May 04, 2021 4 - Beta pytest (!=6.0.0,<7,>=3.3.2) - :pypi:`pytest-memprof` Estimates memory consumption of test functions Mar 29, 2019 4 - Beta N/A - :pypi:`pytest-menu` A pytest plugin for console based interactive test selection just after the collection phase Oct 04, 2017 3 - Alpha pytest (>=2.4.2) - :pypi:`pytest-mercurial` pytest plugin to write integration tests for projects using Mercurial Python internals Nov 21, 2020 1 - Planning N/A - :pypi:`pytest-message` Pytest plugin for sending report message of marked tests execution Nov 04, 2021 N/A pytest (>=6.2.5) - :pypi:`pytest-messenger` Pytest to Slack reporting plugin Dec 16, 2020 5 - Production/Stable N/A - :pypi:`pytest-metadata` pytest plugin for test session metadata Nov 27, 2020 5 - Production/Stable pytest (>=2.9.0) - :pypi:`pytest-metrics` Custom metrics report for pytest Apr 04, 2020 N/A pytest - :pypi:`pytest-mimesis` Mimesis integration with the pytest test runner Mar 21, 2020 5 - Production/Stable pytest (>=4.2) - :pypi:`pytest-minecraft` A pytest plugin for running tests against Minecraft releases Sep 26, 2020 N/A pytest (>=6.0.1,<7.0.0) - :pypi:`pytest-missing-fixtures` Pytest plugin that creates missing fixtures Oct 14, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-ml` Test your machine learning! May 04, 2019 4 - Beta N/A - :pypi:`pytest-mocha` pytest plugin to display test execution output like a mochajs Apr 02, 2020 4 - Beta pytest (>=5.4.0) - :pypi:`pytest-mock` Thin-wrapper around the mock package for easier use with pytest May 06, 2021 5 - Production/Stable pytest (>=5.0) - :pypi:`pytest-mock-api` A mock API server with configurable routes and responses available as a fixture. Feb 13, 2019 1 - Planning pytest (>=4.0.0) - :pypi:`pytest-mock-generator` A pytest fixture wrapper for https://pypi.org/project/mock-generator Aug 10, 2021 5 - Production/Stable N/A - :pypi:`pytest-mock-helper` Help you mock HTTP call and generate mock code Jan 24, 2018 N/A pytest - :pypi:`pytest-mockito` Base fixtures for mockito Jul 11, 2018 4 - Beta N/A - :pypi:`pytest-mockredis` An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database. Jan 02, 2018 2 - Pre-Alpha N/A - :pypi:`pytest-mock-resources` A pytest plugin for easily instantiating reproducible mock resources. Dec 03, 2021 N/A pytest (>=1.0) - :pypi:`pytest-mock-server` Mock server plugin for pytest Apr 06, 2020 4 - Beta N/A - :pypi:`pytest-mockservers` A set of fixtures to test your requests to HTTP/UDP servers Mar 31, 2020 N/A pytest (>=4.3.0) - :pypi:`pytest-modifyjunit` Utility for adding additional properties to junit xml for IDM QE Jan 10, 2019 N/A N/A - :pypi:`pytest-modifyscope` pytest plugin to modify fixture scope Apr 12, 2020 N/A pytest - :pypi:`pytest-molecule` PyTest Molecule Plugin :: discover and run molecule tests Oct 06, 2021 5 - Production/Stable N/A - :pypi:`pytest-mongo` MongoDB process and client fixtures plugin for Pytest. Jun 07, 2021 5 - Production/Stable pytest - :pypi:`pytest-mongodb` pytest plugin for MongoDB fixtures Dec 07, 2019 5 - Production/Stable pytest (>=2.5.2) - :pypi:`pytest-monitor` Pytest plugin for analyzing resource usage. Aug 24, 2021 5 - Production/Stable pytest - :pypi:`pytest-monkeyplus` pytest's monkeypatch subclass with extra functionalities Sep 18, 2012 5 - Production/Stable N/A - :pypi:`pytest-monkeytype` pytest-monkeytype: Generate Monkeytype annotations from your pytest tests. Jul 29, 2020 4 - Beta N/A - :pypi:`pytest-moto` Fixtures for integration tests of AWS services,uses moto mocking library. Aug 28, 2015 1 - Planning N/A - :pypi:`pytest-motor` A pytest plugin for motor, the non-blocking MongoDB driver. Jul 21, 2021 3 - Alpha pytest - :pypi:`pytest-mp` A test batcher for multiprocessed Pytest runs May 23, 2018 4 - Beta pytest - :pypi:`pytest-mpi` pytest plugin to collect information from tests Mar 14, 2021 3 - Alpha pytest - :pypi:`pytest-mpl` pytest plugin to help with testing figures output from Matplotlib Jul 02, 2021 4 - Beta pytest - :pypi:`pytest-mproc` low-startup-overhead, scalable, distributed-testing pytest plugin Mar 07, 2021 4 - Beta pytest - :pypi:`pytest-multi-check` Pytest-плагин, реализует возможность мульти проверок и мягких проверок Jun 03, 2021 N/A pytest - :pypi:`pytest-multihost` Utility for writing multi-host tests for pytest Apr 07, 2020 4 - Beta N/A - :pypi:`pytest-multilog` Multi-process logs handling and other helpers for pytest Jun 10, 2021 N/A N/A - :pypi:`pytest-multithreading` a pytest plugin for th and concurrent testing Aug 12, 2021 N/A pytest (>=3.6) - :pypi:`pytest-mutagen` Add the mutation testing feature to pytest Jul 24, 2020 N/A pytest (>=5.4) - :pypi:`pytest-mypy` Mypy static type checker plugin for Pytest Mar 21, 2021 4 - Beta pytest (>=3.5) - :pypi:`pytest-mypyd` Mypy static type checker plugin for Pytest Aug 20, 2019 4 - Beta pytest (<4.7,>=2.8) ; python_version < "3.5" - :pypi:`pytest-mypy-plugins` pytest plugin for writing tests for mypy plugins Oct 19, 2021 3 - Alpha pytest (>=6.0.0) - :pypi:`pytest-mypy-plugins-shim` Substitute for "pytest-mypy-plugins" for Python implementations which aren't supported by mypy. Apr 12, 2021 N/A N/A - :pypi:`pytest-mypy-testing` Pytest plugin to check mypy output. Jun 13, 2021 N/A pytest - :pypi:`pytest-mysql` MySQL process and client fixtures for pytest Nov 22, 2021 5 - Production/Stable pytest - :pypi:`pytest-needle` pytest plugin for visual testing websites using selenium Dec 10, 2018 4 - Beta pytest (<5.0.0,>=3.0.0) - :pypi:`pytest-neo` pytest-neo is a plugin for pytest that shows tests like screen of Matrix. Apr 23, 2019 3 - Alpha pytest (>=3.7.2) - :pypi:`pytest-network` A simple plugin to disable network on socket level. May 07, 2020 N/A N/A - :pypi:`pytest-never-sleep` pytest plugin helps to avoid adding tests without mock \`time.sleep\` May 05, 2021 3 - Alpha pytest (>=3.5.1) - :pypi:`pytest-nginx` nginx fixture for pytest Aug 12, 2017 5 - Production/Stable N/A - :pypi:`pytest-nginx-iplweb` nginx fixture for pytest - iplweb temporary fork Mar 01, 2019 5 - Production/Stable N/A - :pypi:`pytest-ngrok` Jan 22, 2020 3 - Alpha N/A - :pypi:`pytest-ngsfixtures` pytest ngs fixtures Sep 06, 2019 2 - Pre-Alpha pytest (>=5.0.0) - :pypi:`pytest-nice` A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest - :pypi:`pytest-nice-parametrize` A small snippet for nicer PyTest's Parametrize Apr 17, 2021 5 - Production/Stable N/A - :pypi:`pytest-nlcov` Pytest plugin to get the coverage of the new lines (based on git diff) only Jul 07, 2021 N/A N/A - :pypi:`pytest-nocustom` Run all tests without custom markers Jul 07, 2021 5 - Production/Stable N/A - :pypi:`pytest-nodev` Test-driven source code search for Python. Jul 21, 2016 4 - Beta pytest (>=2.8.1) - :pypi:`pytest-nogarbage` Ensure a test produces no garbage Aug 29, 2021 5 - Production/Stable pytest (>=4.6.0) - :pypi:`pytest-notebook` A pytest plugin for testing Jupyter Notebooks Sep 16, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-notice` Send pytest execution result email Nov 05, 2020 N/A N/A - :pypi:`pytest-notification` A pytest plugin for sending a desktop notification and playing a sound upon completion of tests Jun 19, 2020 N/A pytest (>=4) - :pypi:`pytest-notifier` A pytest plugin to notify test result Jun 12, 2020 3 - Alpha pytest - :pypi:`pytest-notimplemented` Pytest markers for not implemented features and tests. Aug 27, 2019 N/A pytest (>=5.1,<6.0) - :pypi:`pytest-notion` A PyTest Reporter to send test runs to Notion.so Aug 07, 2019 N/A N/A - :pypi:`pytest-nunit` A pytest plugin for generating NUnit3 test result XML output Aug 04, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-ochrus` pytest results data-base and HTML reporter Feb 21, 2018 4 - Beta N/A - :pypi:`pytest-odoo` py.test plugin to run Odoo tests Nov 04, 2021 4 - Beta pytest (>=2.9) - :pypi:`pytest-odoo-fixtures` Project description Jun 25, 2019 N/A N/A - :pypi:`pytest-oerp` pytest plugin to test OpenERP modules Feb 28, 2012 3 - Alpha N/A - :pypi:`pytest-ok` The ultimate pytest output plugin Apr 01, 2019 4 - Beta N/A - :pypi:`pytest-only` Use @pytest.mark.only to run a single test Jan 19, 2020 N/A N/A - :pypi:`pytest-oot` Run object-oriented tests in a simple format Sep 18, 2016 4 - Beta N/A - :pypi:`pytest-openfiles` Pytest plugin for detecting inadvertent open file handles Apr 16, 2020 3 - Alpha pytest (>=4.6) - :pypi:`pytest-opentmi` pytest plugin for publish results to opentmi Nov 04, 2021 5 - Production/Stable pytest (>=5.0) - :pypi:`pytest-operator` Fixtures for Operators Oct 26, 2021 N/A N/A - :pypi:`pytest-optional` include/exclude values of fixtures in pytest Oct 07, 2015 N/A N/A - :pypi:`pytest-optional-tests` Easy declaration of optional tests (i.e., that are not run by default) Jul 09, 2019 4 - Beta pytest (>=4.5.0) - :pypi:`pytest-orchestration` A pytest plugin for orchestrating tests Jul 18, 2019 N/A N/A - :pypi:`pytest-order` pytest plugin to run your tests in a specific order May 30, 2021 4 - Beta pytest (>=5.0) - :pypi:`pytest-ordering` pytest plugin to run your tests in a specific order Nov 14, 2018 4 - Beta pytest - :pypi:`pytest-osxnotify` OS X notifications for py.test results. May 15, 2015 N/A N/A - :pypi:`pytest-otel` pytest-otel report OpenTelemetry traces about test executed Dec 03, 2021 N/A N/A - :pypi:`pytest-pact` A simple plugin to use with pytest Jan 07, 2019 4 - Beta N/A - :pypi:`pytest-pahrametahrize` Parametrize your tests with a Boston accent. Nov 24, 2021 4 - Beta pytest (>=6.0,<7.0) - :pypi:`pytest-parallel` a pytest plugin for parallel and concurrent testing Oct 10, 2021 3 - Alpha pytest (>=3.0.0) - :pypi:`pytest-parallel-39` a pytest plugin for parallel and concurrent testing Jul 12, 2021 3 - Alpha pytest (>=3.0.0) - :pypi:`pytest-param` pytest plugin to test all, first, last or random params Sep 11, 2016 4 - Beta pytest (>=2.6.0) - :pypi:`pytest-paramark` Configure pytest fixtures using a combination of"parametrize" and markers Jan 10, 2020 4 - Beta pytest (>=4.5.0) - :pypi:`pytest-parametrization` Simpler PyTest parametrization Nov 30, 2021 5 - Production/Stable pytest - :pypi:`pytest-parametrize-cases` A more user-friendly way to write parametrized tests. Dec 12, 2020 N/A pytest (>=6.1.2,<7.0.0) - :pypi:`pytest-parametrized` Pytest plugin for parametrizing tests with default iterables. Oct 19, 2020 5 - Production/Stable pytest - :pypi:`pytest-parawtf` Finally spell paramete?ri[sz]e correctly Dec 03, 2018 4 - Beta pytest (>=3.6.0) - :pypi:`pytest-pass` Check out https://github.com/elilutsky/pytest-pass Dec 04, 2019 N/A N/A - :pypi:`pytest-passrunner` Pytest plugin providing the 'run_on_pass' marker Feb 10, 2021 5 - Production/Stable pytest (>=4.6.0) - :pypi:`pytest-paste-config` Allow setting the path to a paste config file Sep 18, 2013 3 - Alpha N/A - :pypi:`pytest-patches` A contextmanager pytest fixture for handling multiple mock patches Aug 30, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-pdb` pytest plugin which adds pdb helper commands related to pytest. Jul 31, 2018 N/A N/A - :pypi:`pytest-peach` pytest plugin for fuzzing with Peach API Security Apr 12, 2019 4 - Beta pytest (>=2.8.7) - :pypi:`pytest-pep257` py.test plugin for pep257 Jul 09, 2016 N/A N/A - :pypi:`pytest-pep8` pytest plugin to check PEP8 requirements Apr 27, 2014 N/A N/A - :pypi:`pytest-percent` Change the exit code of pytest test sessions when a required percent of tests pass. May 21, 2020 N/A pytest (>=5.2.0) - :pypi:`pytest-perf` pytest-perf Jun 27, 2021 5 - Production/Stable pytest (>=4.6) ; extra == 'testing' - :pypi:`pytest-performance` A simple plugin to ensure the execution of critical sections of code has not been impacted Sep 11, 2020 5 - Production/Stable pytest (>=3.7.0) - :pypi:`pytest-persistence` Pytest tool for persistent objects Nov 06, 2021 N/A N/A - :pypi:`pytest-pgsql` Pytest plugins and helpers for tests using a Postgres database. May 13, 2020 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-phmdoctest` pytest plugin to test Python examples in Markdown using phmdoctest. Nov 10, 2021 4 - Beta pytest (>=6.2) ; extra == 'test' - :pypi:`pytest-picked` Run the tests related to the changed files Dec 23, 2020 N/A pytest (>=3.5.0) - :pypi:`pytest-pigeonhole` Jun 25, 2018 5 - Production/Stable pytest (>=3.4) - :pypi:`pytest-pikachu` Show surprise when tests are passing Aug 05, 2021 5 - Production/Stable pytest - :pypi:`pytest-pilot` Slice in your test base thanks to powerful markers. Oct 09, 2020 5 - Production/Stable N/A - :pypi:`pytest-pings` 🦊 The pytest plugin for Firefox Telemetry 📊 Jun 29, 2019 3 - Alpha pytest (>=5.0.0) - :pypi:`pytest-pinned` A simple pytest plugin for pinning tests Sep 17, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-pinpoint` A pytest plugin which runs SBFL algorithms to detect faults. Sep 25, 2020 N/A pytest (>=4.4.0) - :pypi:`pytest-pipeline` Pytest plugin for functional testing of data analysispipelines Jan 24, 2017 3 - Alpha N/A - :pypi:`pytest-platform-markers` Markers for pytest to skip tests on specific platforms Sep 09, 2019 4 - Beta pytest (>=3.6.0) - :pypi:`pytest-play` pytest plugin that let you automate actions and assertions with test metrics reporting executing plain YAML files Jun 12, 2019 5 - Production/Stable N/A - :pypi:`pytest-playbook` Pytest plugin for reading playbooks. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) - :pypi:`pytest-playwright` A pytest wrapper with fixtures for Playwright to automate web browsers Oct 28, 2021 N/A pytest - :pypi:`pytest-playwrights` A pytest wrapper with fixtures for Playwright to automate web browsers Dec 02, 2021 N/A N/A - :pypi:`pytest-playwright-snapshot` A pytest wrapper for snapshot testing with playwright Aug 19, 2021 N/A N/A - :pypi:`pytest-plt` Fixtures for quickly making Matplotlib plots in tests Aug 17, 2020 5 - Production/Stable pytest - :pypi:`pytest-plugin-helpers` A plugin to help developing and testing other plugins Nov 23, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-plus` PyTest Plus Plugin :: extends pytest functionality Mar 19, 2020 5 - Production/Stable pytest (>=3.50) - :pypi:`pytest-pmisc` Mar 21, 2019 5 - Production/Stable N/A - :pypi:`pytest-pointers` Pytest plugin to define functions you test with special marks for better navigation and reports Oct 14, 2021 N/A N/A - :pypi:`pytest-polarion-cfme` pytest plugin for collecting test cases and recording test results Nov 13, 2017 3 - Alpha N/A - :pypi:`pytest-polarion-collect` pytest plugin for collecting polarion test cases data Jun 18, 2020 3 - Alpha pytest - :pypi:`pytest-polecat` Provides Polecat pytest fixtures Aug 12, 2019 4 - Beta N/A - :pypi:`pytest-ponyorm` PonyORM in Pytest Oct 31, 2018 N/A pytest (>=3.1.1) - :pypi:`pytest-poo` Visualize your crappy tests Mar 25, 2021 5 - Production/Stable pytest (>=2.3.4) - :pypi:`pytest-poo-fail` Visualize your failed tests with poo Feb 12, 2015 5 - Production/Stable N/A - :pypi:`pytest-pop` A pytest plugin to help with testing pop projects Aug 19, 2021 5 - Production/Stable pytest - :pypi:`pytest-portion` Select a portion of the collected tests Jan 28, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-postgres` Run PostgreSQL in Docker container in Pytest. Mar 22, 2020 N/A pytest - :pypi:`pytest-postgresql` Postgresql fixtures and fixture factories for Pytest. Nov 05, 2021 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-power` pytest plugin with powerful fixtures Dec 31, 2020 N/A pytest (>=5.4) - :pypi:`pytest-pretty-terminal` pytest plugin for generating prettier terminal output Nov 24, 2021 N/A pytest (>=3.4.1) - :pypi:`pytest-pride` Minitest-style test colors Apr 02, 2016 3 - Alpha N/A - :pypi:`pytest-print` pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout) Jun 17, 2021 5 - Production/Stable pytest (>=6) - :pypi:`pytest-profiling` Profiling plugin for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-progress` pytest plugin for instant test progress status Nov 09, 2021 5 - Production/Stable pytest (>=2.7) - :pypi:`pytest-prometheus` Report test pass / failures to a Prometheus PushGateway Oct 03, 2017 N/A N/A - :pypi:`pytest-prosper` Test helpers for Prosper projects Sep 24, 2018 N/A N/A - :pypi:`pytest-pspec` A rspec format reporter for Python ptest Jun 02, 2020 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-psqlgraph` pytest plugin for testing applications that use psqlgraph Oct 19, 2021 4 - Beta pytest (>=6.0) - :pypi:`pytest-ptera` Use ptera probes in tests Oct 20, 2021 N/A pytest (>=6.2.4,<7.0.0) - :pypi:`pytest-pudb` Pytest PuDB debugger integration Oct 25, 2018 3 - Alpha pytest (>=2.0) - :pypi:`pytest-purkinje` py.test plugin for purkinje test runner Oct 28, 2017 2 - Pre-Alpha N/A - :pypi:`pytest-pycharm` Plugin for py.test to enter PyCharm debugger on uncaught exceptions Aug 13, 2020 5 - Production/Stable pytest (>=2.3) - :pypi:`pytest-pycodestyle` pytest plugin to run pycodestyle Aug 10, 2020 3 - Alpha N/A - :pypi:`pytest-pydev` py.test plugin to connect to a remote debug server with PyDev or PyCharm. Nov 15, 2017 3 - Alpha N/A - :pypi:`pytest-pydocstyle` pytest plugin to run pydocstyle Aug 10, 2020 3 - Alpha N/A - :pypi:`pytest-pylint` pytest plugin to check source code with pylint Nov 09, 2020 5 - Production/Stable pytest (>=5.4) - :pypi:`pytest-pypi` Easily test your HTTP library against a local copy of pypi Mar 04, 2018 3 - Alpha N/A - :pypi:`pytest-pypom-navigation` Core engine for cookiecutter-qa and pytest-play packages Feb 18, 2019 4 - Beta pytest (>=3.0.7) - :pypi:`pytest-pyppeteer` A plugin to run pyppeteer in pytest. Feb 16, 2021 4 - Beta pytest (>=6.0.2) - :pypi:`pytest-pyq` Pytest fixture "q" for pyq Mar 10, 2020 5 - Production/Stable N/A - :pypi:`pytest-pyramid` pytest_pyramid - provides fixtures for testing pyramid applications with pytest test suite Oct 15, 2021 5 - Production/Stable pytest - :pypi:`pytest-pyramid-server` Pyramid server fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-pyright` Pytest plugin for type checking code with Pyright Aug 16, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-pytestrail` Pytest plugin for interaction with TestRail Aug 27, 2020 4 - Beta pytest (>=3.8.0) - :pypi:`pytest-pythonpath` pytest plugin for adding to the PYTHONPATH from command line or configs. Aug 22, 2018 5 - Production/Stable N/A - :pypi:`pytest-pytorch` pytest plugin for a better developer experience when working with the PyTorch test suite May 25, 2021 4 - Beta pytest - :pypi:`pytest-qasync` Pytest support for qasync. Jul 12, 2021 4 - Beta pytest (>=5.4.0) - :pypi:`pytest-qatouch` Pytest plugin for uploading test results to your QA Touch Testrun. Jun 26, 2021 4 - Beta pytest (>=6.2.0) - :pypi:`pytest-qgis` A pytest plugin for testing QGIS python plugins Nov 25, 2021 5 - Production/Stable pytest (>=6.2.3) - :pypi:`pytest-qml` Run QML Tests with pytest Dec 02, 2020 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-qr` pytest plugin to generate test result QR codes Nov 25, 2021 4 - Beta N/A - :pypi:`pytest-qt` pytest support for PyQt and PySide applications Jun 13, 2021 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-qt-app` QT app fixture for py.test Dec 23, 2015 5 - Production/Stable N/A - :pypi:`pytest-quarantine` A plugin for pytest to manage expected test failures Nov 24, 2019 5 - Production/Stable pytest (>=4.6) - :pypi:`pytest-quickcheck` pytest plugin to generate random data inspired by QuickCheck Nov 15, 2020 4 - Beta pytest (<6.0.0,>=4.0) - :pypi:`pytest-rabbitmq` RabbitMQ process and client fixtures for pytest Jun 02, 2021 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-race` Race conditions tester for pytest Nov 21, 2016 4 - Beta N/A - :pypi:`pytest-rage` pytest plugin to implement PEP712 Oct 21, 2011 3 - Alpha N/A - :pypi:`pytest-railflow-testrail-reporter` Generate json reports along with specified metadata defined in test markers. Dec 02, 2021 5 - Production/Stable pytest - :pypi:`pytest-raises` An implementation of pytest.raises as a pytest.mark fixture Apr 23, 2020 N/A pytest (>=3.2.2) - :pypi:`pytest-raisesregexp` Simple pytest plugin to look for regex in Exceptions Dec 18, 2015 N/A N/A - :pypi:`pytest-raisin` Plugin enabling the use of exception instances with pytest.raises Jun 25, 2020 N/A pytest - :pypi:`pytest-random` py.test plugin to randomize tests Apr 28, 2013 3 - Alpha N/A - :pypi:`pytest-randomly` Pytest plugin to randomly order tests and control random.seed. Nov 30, 2021 5 - Production/Stable pytest - :pypi:`pytest-randomness` Pytest plugin about random seed management May 30, 2019 3 - Alpha N/A - :pypi:`pytest-random-num` Randomise the order in which pytest tests are run with some control over the randomness Oct 19, 2020 5 - Production/Stable N/A - :pypi:`pytest-random-order` Randomise the order in which pytest tests are run with some control over the randomness Nov 30, 2018 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-readme` Test your README.md file Dec 28, 2014 5 - Production/Stable N/A - :pypi:`pytest-reana` Pytest fixtures for REANA. Nov 22, 2021 3 - Alpha N/A - :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Jul 08, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-recordings` Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal Aug 13, 2020 N/A N/A - :pypi:`pytest-redis` Redis fixtures and fixture factories for Pytest. Nov 03, 2021 5 - Production/Stable pytest - :pypi:`pytest-redislite` Pytest plugin for testing code using Redis Sep 19, 2021 4 - Beta pytest - :pypi:`pytest-redmine` Pytest plugin for redmine Mar 19, 2018 1 - Planning N/A - :pypi:`pytest-ref` A plugin to store reference files to ease regression testing Nov 23, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-reference-formatter` Conveniently run pytest with a dot-formatted test reference. Oct 01, 2019 4 - Beta N/A - :pypi:`pytest-regressions` Easy to use fixtures to write regression tests. Jan 27, 2021 5 - Production/Stable pytest (>=3.5.0) - :pypi:`pytest-regtest` pytest plugin for regression tests Jun 03, 2021 N/A N/A - :pypi:`pytest-relative-order` a pytest plugin that sorts tests using "before" and "after" markers May 17, 2021 4 - Beta N/A - :pypi:`pytest-relaxed` Relaxed test discovery/organization for pytest Jun 14, 2019 5 - Production/Stable pytest (<5,>=3) - :pypi:`pytest-remfiles` Pytest plugin to create a temporary directory with remote files Jul 01, 2019 5 - Production/Stable N/A - :pypi:`pytest-remotedata` Pytest plugin for controlling remote data access. Jul 20, 2019 3 - Alpha pytest (>=3.1) - :pypi:`pytest-remote-response` Pytest plugin for capturing and mocking connection requests. Jun 30, 2021 4 - Beta pytest (>=4.6) - :pypi:`pytest-remove-stale-bytecode` py.test plugin to remove stale byte code files. Mar 04, 2020 4 - Beta pytest - :pypi:`pytest-reorder` Reorder tests depending on their paths and names. May 31, 2018 4 - Beta pytest - :pypi:`pytest-repeat` pytest plugin for repeating tests Oct 31, 2020 5 - Production/Stable pytest (>=3.6) - :pypi:`pytest-replay` Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests Jun 09, 2021 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-repo-health` A pytest plugin to report on repository standards conformance Nov 23, 2021 3 - Alpha pytest - :pypi:`pytest-report` Creates json report that is compatible with atom.io's linter message format May 11, 2016 4 - Beta N/A - :pypi:`pytest-reporter` Generate Pytest reports with templates Jul 22, 2021 4 - Beta pytest - :pypi:`pytest-reporter-html1` A basic HTML report template for Pytest Jun 08, 2021 4 - Beta N/A - :pypi:`pytest-reportinfra` Pytest plugin for reportinfra Aug 11, 2019 3 - Alpha N/A - :pypi:`pytest-reporting` A plugin to report summarized results in a table format Oct 25, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-reportlog` Replacement for the --resultlog option, focused in simplicity and extensibility Dec 11, 2020 3 - Alpha pytest (>=5.2) - :pypi:`pytest-report-me` A pytest plugin to generate report. Dec 31, 2020 N/A pytest - :pypi:`pytest-report-parameters` pytest plugin for adding tests' parameters to junit report Jun 18, 2020 3 - Alpha pytest (>=2.4.2) - :pypi:`pytest-reportportal` Agent for Reporting results of tests to the Report Portal Jun 18, 2021 N/A pytest (>=3.8.0) - :pypi:`pytest-reqs` pytest plugin to check pinned requirements May 12, 2019 N/A pytest (>=2.4.2) - :pypi:`pytest-requests` A simple plugin to use with pytest Jun 24, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-reraise` Make multi-threaded pytest test cases fail when they should Jun 17, 2021 5 - Production/Stable pytest (>=4.6) - :pypi:`pytest-rerun` Re-run only changed files in specified branch Jul 08, 2019 N/A pytest (>=3.6) - :pypi:`pytest-rerunfailures` pytest plugin to re-run tests to eliminate flaky failures Sep 17, 2021 5 - Production/Stable pytest (>=5.3) - :pypi:`pytest-resilient-circuits` Resilient Circuits fixtures for PyTest. Nov 15, 2021 N/A N/A - :pypi:`pytest-resource` Load resource fixture plugin to use with pytest Nov 14, 2018 4 - Beta N/A - :pypi:`pytest-resource-path` Provides path for uniform access to test resources in isolated directory May 01, 2021 5 - Production/Stable pytest (>=3.5.0) - :pypi:`pytest-responsemock` Simplified requests calls mocking for pytest Oct 10, 2020 5 - Production/Stable N/A - :pypi:`pytest-responses` py.test integration for responses Apr 26, 2021 N/A pytest (>=2.5) - :pypi:`pytest-restrict` Pytest plugin to restrict the test types allowed Aug 12, 2021 5 - Production/Stable pytest - :pypi:`pytest-rethinkdb` A RethinkDB plugin for pytest. Jul 24, 2016 4 - Beta N/A - :pypi:`pytest-reverse` Pytest plugin to reverse test order. Aug 12, 2021 5 - Production/Stable pytest - :pypi:`pytest-ringo` pytest plugin to test webapplications using the Ringo webframework Sep 27, 2017 3 - Alpha N/A - :pypi:`pytest-rng` Fixtures for seeding tests and making randomness reproducible Aug 08, 2019 5 - Production/Stable pytest - :pypi:`pytest-roast` pytest plugin for ROAST configuration override and fixtures Jul 29, 2021 5 - Production/Stable pytest - :pypi:`pytest-rocketchat` Pytest to Rocket.Chat reporting plugin Apr 18, 2021 5 - Production/Stable N/A - :pypi:`pytest-rotest` Pytest integration with rotest Sep 08, 2019 N/A pytest (>=3.5.0) - :pypi:`pytest-rpc` Extend py.test for RPC OpenStack testing. Feb 22, 2019 4 - Beta pytest (~=3.6) - :pypi:`pytest-rst` Test code from RST documents with pytest Sep 21, 2021 N/A pytest - :pypi:`pytest-rt` pytest data collector plugin for Testgr Sep 04, 2021 N/A N/A - :pypi:`pytest-rts` Coverage-based regression test selection (RTS) plugin for pytest May 17, 2021 N/A pytest - :pypi:`pytest-run-changed` Pytest plugin that runs changed tests only Apr 02, 2021 3 - Alpha pytest - :pypi:`pytest-runfailed` implement a --failed option for pytest Mar 24, 2016 N/A N/A - :pypi:`pytest-runner` Invoke py.test as distutils command with dependency resolution May 19, 2021 5 - Production/Stable pytest (>=4.6) ; extra == 'testing' - :pypi:`pytest-runtime-xfail` Call runtime_xfail() to mark running test as xfail. Aug 26, 2021 N/A N/A - :pypi:`pytest-salt` Pytest Salt Plugin Jan 27, 2020 4 - Beta N/A - :pypi:`pytest-salt-containers` A Pytest plugin that builds and creates docker containers Nov 09, 2016 4 - Beta N/A - :pypi:`pytest-salt-factories` Pytest Salt Plugin Sep 16, 2021 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-salt-from-filenames` Simple PyTest Plugin For Salt's Test Suite Specifically Jan 29, 2019 4 - Beta pytest (>=4.1) - :pypi:`pytest-salt-runtests-bridge` Simple PyTest Plugin For Salt's Test Suite Specifically Dec 05, 2019 4 - Beta pytest (>=4.1) - :pypi:`pytest-sanic` a pytest plugin for Sanic Oct 25, 2021 N/A pytest (>=5.2) - :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A - :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A - :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Dec 03, 2021 5 - Production/Stable N/A - :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A - :pypi:`pytest-schema` 👍 Validate return values against a schema-like object in testing Aug 31, 2020 5 - Production/Stable pytest (>=3.5.0) - :pypi:`pytest-securestore` An encrypted password store for use within pytest cases Nov 08, 2021 4 - Beta N/A - :pypi:`pytest-select` A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) - :pypi:`pytest-selenium` pytest plugin for Selenium Sep 19, 2020 5 - Production/Stable pytest (>=5.0.0) - :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Dec 03, 2021 5 - Production/Stable N/A - :pypi:`pytest-selenium-enhancer` pytest plugin for Selenium Nov 26, 2020 5 - Production/Stable N/A - :pypi:`pytest-selenium-pdiff` A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A - :pypi:`pytest-send-email` Send pytest execution result email Dec 04, 2019 N/A N/A - :pypi:`pytest-sentry` A pytest plugin to send testrun information to Sentry.io Apr 21, 2021 N/A pytest - :pypi:`pytest-server-fixtures` Extensible server fixures for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-serverless` Automatically mocks resources from serverless.yml in pytest using moto. Nov 27, 2021 4 - Beta N/A - :pypi:`pytest-services` Services plugin for pytest testing framework Oct 30, 2020 6 - Mature N/A - :pypi:`pytest-session2file` pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test. Jan 26, 2021 3 - Alpha pytest - :pypi:`pytest-session-fixture-globalize` py.test plugin to make session fixtures behave as if written in conftest, even if it is written in some modules May 15, 2018 4 - Beta N/A - :pypi:`pytest-session_to_file` pytest-session_to_file is a py.test plugin for capturing and saving to file the stdout of py.test. Oct 01, 2015 3 - Alpha N/A - :pypi:`pytest-sftpserver` py.test plugin to locally test sftp server connections. Sep 16, 2019 4 - Beta N/A - :pypi:`pytest-shard` Dec 11, 2020 4 - Beta pytest - :pypi:`pytest-shell` A pytest plugin to help with testing shell scripts / black box commands Nov 07, 2021 N/A N/A - :pypi:`pytest-sheraf` Versatile ZODB abstraction layer - pytest fixtures Feb 11, 2020 N/A pytest - :pypi:`pytest-sherlock` pytest plugin help to find coupled tests Nov 18, 2021 5 - Production/Stable pytest (>=3.5.1) - :pypi:`pytest-shortcuts` Expand command-line shortcuts listed in pytest configuration Oct 29, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-shutil` A goodie-bag of unix shell and environment tools for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-simplehttpserver` Simple pytest fixture to spin up an HTTP server Jun 24, 2021 4 - Beta N/A - :pypi:`pytest-simple-plugin` Simple pytest plugin Nov 27, 2019 N/A N/A - :pypi:`pytest-simple-settings` simple-settings plugin for pytest Nov 17, 2020 4 - Beta pytest - :pypi:`pytest-single-file-logging` Allow for multiple processes to log to a single file May 05, 2016 4 - Beta pytest (>=2.8.1) - :pypi:`pytest-skip-markers` Pytest Salt Plugin Oct 04, 2021 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-skipper` A plugin that selects only tests with changes in execution path Mar 26, 2017 3 - Alpha pytest (>=3.0.6) - :pypi:`pytest-skippy` Automatically skip tests that don't need to run! Jan 27, 2018 3 - Alpha pytest (>=2.3.4) - :pypi:`pytest-skip-slow` A pytest plugin to skip \`@pytest.mark.slow\` tests by default. Sep 28, 2021 N/A N/A - :pypi:`pytest-slack` Pytest to Slack reporting plugin Dec 15, 2020 5 - Production/Stable N/A - :pypi:`pytest-slow` A pytest plugin to skip \`@pytest.mark.slow\` tests by default. Sep 28, 2021 N/A N/A - :pypi:`pytest-smartcollect` A plugin for collecting tests that touch changed code Oct 04, 2018 N/A pytest (>=3.5.0) - :pypi:`pytest-smartcov` Smart coverage plugin for pytest. Sep 30, 2017 3 - Alpha N/A - :pypi:`pytest-smtp` Send email with pytest execution result Feb 20, 2021 N/A pytest - :pypi:`pytest-snail` Plugin for adding a marker to slow running tests. 🐌 Nov 04, 2019 3 - Alpha pytest (>=5.0.1) - :pypi:`pytest-snapci` py.test plugin for Snap-CI Nov 12, 2015 N/A N/A - :pypi:`pytest-snapshot` A plugin for snapshot testing with pytest. Dec 02, 2021 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-snmpserver` May 12, 2021 N/A N/A - :pypi:`pytest-socket` Pytest Plugin to disable socket calls during tests Aug 28, 2021 4 - Beta pytest (>=3.6.3) - :pypi:`pytest-soft-assertions` May 05, 2020 3 - Alpha pytest - :pypi:`pytest-solr` Solr process and client fixtures for py.test. May 11, 2020 3 - Alpha pytest (>=3.0.0) - :pypi:`pytest-sorter` A simple plugin to first execute tests that historically failed more Apr 20, 2021 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-sourceorder` Test-ordering plugin for pytest Sep 01, 2021 4 - Beta pytest - :pypi:`pytest-spark` pytest plugin to run the tests with support of pyspark. Feb 23, 2020 4 - Beta pytest - :pypi:`pytest-spawner` py.test plugin to spawn process and communicate with them. Jul 31, 2015 4 - Beta N/A - :pypi:`pytest-spec` Library pytest-spec is a pytest plugin to display test execution output like a SPECIFICATION. May 04, 2021 N/A N/A - :pypi:`pytest-sphinx` Doctest plugin for pytest with support for Sphinx-specific doctest-directives Aug 05, 2020 4 - Beta N/A - :pypi:`pytest-spiratest` Exports unit tests as test runs in SpiraTest/Team/Plan Oct 13, 2021 N/A N/A - :pypi:`pytest-splinter` Splinter plugin for pytest testing framework Dec 25, 2020 6 - Mature N/A - :pypi:`pytest-split` Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. Nov 09, 2021 4 - Beta pytest (>=5,<7) - :pypi:`pytest-splitio` Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0) - :pypi:`pytest-split-tests` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. Jul 30, 2021 5 - Production/Stable pytest (>=2.5) - :pypi:`pytest-split-tests-tresorit` Feb 22, 2021 1 - Planning N/A - :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Nov 29, 2021 N/A pytest (>5.4.0,<6.3) - :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Oct 07, 2021 N/A N/A - :pypi:`pytest-splunk-env` pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0) - :pypi:`pytest-sqitch` sqitch for pytest Apr 06, 2020 4 - Beta N/A - :pypi:`pytest-sqlalchemy` pytest plugin with sqlalchemy related fixtures Mar 13, 2018 3 - Alpha N/A - :pypi:`pytest-sql-bigquery` Yet another SQL-testing framework for BigQuery provided by pytest plugin Dec 19, 2019 N/A pytest - :pypi:`pytest-srcpaths` Add paths to sys.path Oct 15, 2021 N/A N/A - :pypi:`pytest-ssh` pytest plugin for ssh command run May 27, 2019 N/A pytest - :pypi:`pytest-start-from` Start pytest run from a given point Apr 11, 2016 N/A N/A - :pypi:`pytest-statsd` pytest plugin for reporting to graphite Nov 30, 2018 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-stepfunctions` A small description May 08, 2021 4 - Beta pytest - :pypi:`pytest-steps` Create step-wise / incremental tests in pytest. Sep 23, 2021 5 - Production/Stable N/A - :pypi:`pytest-stepwise` Run a test suite one failing test at a time. Dec 01, 2015 4 - Beta N/A - :pypi:`pytest-stoq` A plugin to pytest stoq Feb 09, 2021 4 - Beta N/A - :pypi:`pytest-stress` A Pytest plugin that allows you to loop tests for a user defined amount of time. Dec 07, 2019 4 - Beta pytest (>=3.6.0) - :pypi:`pytest-structlog` Structured logging assertions Sep 21, 2021 N/A pytest - :pypi:`pytest-structmpd` provide structured temporary directory Oct 17, 2018 N/A N/A - :pypi:`pytest-stub` Stub packages, modules and attributes. Apr 28, 2020 5 - Production/Stable N/A - :pypi:`pytest-stubprocess` Provide stub implementations for subprocesses in Python tests Sep 17, 2018 3 - Alpha pytest (>=3.5.0) - :pypi:`pytest-study` A pytest plugin to organize long run tests (named studies) without interfering the regular tests Sep 26, 2017 3 - Alpha pytest (>=2.0) - :pypi:`pytest-subprocess` A plugin to fake subprocess for pytest Nov 07, 2021 5 - Production/Stable pytest (>=4.0.0) - :pypi:`pytest-subtesthack` A hack to explicitly set up and tear down fixtures. Mar 02, 2021 N/A N/A - :pypi:`pytest-subtests` unittest subTest() support and subtests fixture May 29, 2021 4 - Beta pytest (>=5.3.0) - :pypi:`pytest-subunit` pytest-subunit is a plugin for py.test which outputs testsresult in subunit format. Aug 29, 2017 N/A N/A - :pypi:`pytest-sugar` pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). Jul 06, 2020 3 - Alpha N/A - :pypi:`pytest-sugar-bugfix159` Workaround for https://github.com/Frozenball/pytest-sugar/issues/159 Nov 07, 2018 5 - Production/Stable pytest (!=3.7.3,>=3.5); extra == 'testing' - :pypi:`pytest-super-check` Pytest plugin to check your TestCase classes call super in setUp, tearDown, etc. Aug 12, 2021 5 - Production/Stable pytest - :pypi:`pytest-svn` SVN repository fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-symbols` pytest-symbols is a pytest plugin that adds support for passing test environment symbols into pytest tests. Nov 20, 2017 3 - Alpha N/A - :pypi:`pytest-takeltest` Fixtures for ansible, testinfra and molecule Oct 13, 2021 N/A N/A - :pypi:`pytest-talisker` Nov 28, 2021 N/A N/A - :pypi:`pytest-tap` Test Anything Protocol (TAP) reporting plugin for pytest Oct 27, 2021 5 - Production/Stable pytest (>=3.0) - :pypi:`pytest-tape` easy assertion with expected results saved to yaml files Mar 17, 2021 4 - Beta N/A - :pypi:`pytest-target` Pytest plugin for remote target orchestration. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) - :pypi:`pytest-tblineinfo` tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used Dec 01, 2015 3 - Alpha pytest (>=2.0) - :pypi:`pytest-teamcity-logblock` py.test plugin to introduce block structure in teamcity build log, if output is not captured May 15, 2018 4 - Beta N/A - :pypi:`pytest-telegram` Pytest to Telegram reporting plugin Dec 10, 2020 5 - Production/Stable N/A - :pypi:`pytest-tempdir` Predictable and repeatable tempdir support. Oct 11, 2019 4 - Beta pytest (>=2.8.1) - :pypi:`pytest-terraform` A pytest plugin for using terraform fixtures Nov 10, 2021 N/A pytest (>=6.0) - :pypi:`pytest-terraform-fixture` generate terraform resources to use with pytest Nov 14, 2018 4 - Beta N/A - :pypi:`pytest-testbook` A plugin to run tests written in Jupyter notebook Dec 11, 2016 3 - Alpha N/A - :pypi:`pytest-testconfig` Test configuration plugin for pytest. Jan 11, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-testdirectory` A py.test plugin providing temporary directories in unit tests. Nov 06, 2018 5 - Production/Stable pytest - :pypi:`pytest-testdox` A testdox format reporter for pytest Oct 13, 2020 5 - Production/Stable pytest (>=3.7.0) - :pypi:`pytest-test-groups` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Oct 25, 2016 5 - Production/Stable N/A - :pypi:`pytest-testinfra` Test infrastructures Jun 20, 2021 5 - Production/Stable pytest (!=3.0.2) - :pypi:`pytest-testlink-adaptor` pytest reporting plugin for testlink Dec 20, 2018 4 - Beta pytest (>=2.6) - :pypi:`pytest-testmon` selects tests affected by changed files and methods Oct 22, 2021 4 - Beta N/A - :pypi:`pytest-testobject` Plugin to use TestObject Suites with Pytest Sep 24, 2019 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-testrail` pytest plugin for creating TestRail runs and adding results Aug 27, 2020 N/A pytest (>=3.6) - :pypi:`pytest-testrail2` A small example package Nov 17, 2020 N/A pytest (>=5) - :pypi:`pytest-testrail-api` Плагин Pytest, для интеграции с TestRail Nov 30, 2021 N/A pytest (>=5.5) - :pypi:`pytest-testrail-api-client` TestRail Api Python Client Dec 03, 2021 N/A pytest - :pypi:`pytest-testrail-appetize` pytest plugin for creating TestRail runs and adding results Sep 29, 2021 N/A N/A - :pypi:`pytest-testrail-client` pytest plugin for Testrail Sep 29, 2020 5 - Production/Stable N/A - :pypi:`pytest-testrail-e2e` pytest plugin for creating TestRail runs and adding results Oct 11, 2021 N/A pytest (>=3.6) - :pypi:`pytest-testrail-ns` pytest plugin for creating TestRail runs and adding results Oct 08, 2021 N/A pytest (>=3.6) - :pypi:`pytest-testrail-plugin` PyTest plugin for TestRail Apr 21, 2020 3 - Alpha pytest - :pypi:`pytest-testrail-reporter` Sep 10, 2018 N/A N/A - :pypi:`pytest-testreport` Nov 12, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-testslide` TestSlide fixture for pytest Jan 07, 2021 5 - Production/Stable pytest (~=6.2) - :pypi:`pytest-test-this` Plugin for py.test to run relevant tests, based on naively checking if a test contains a reference to the symbol you supply Sep 15, 2019 2 - Pre-Alpha pytest (>=2.3) - :pypi:`pytest-test-utils` Nov 30, 2021 N/A pytest (>=5) - :pypi:`pytest-tesults` Tesults plugin for pytest Jul 31, 2021 5 - Production/Stable pytest (>=3.5.0) - :pypi:`pytest-tezos` pytest-ligo Jan 16, 2020 4 - Beta N/A - :pypi:`pytest-thawgun` Pytest plugin for time travel May 26, 2020 3 - Alpha N/A - :pypi:`pytest-threadleak` Detects thread leaks Sep 08, 2017 4 - Beta N/A - :pypi:`pytest-tick` Ticking on tests Aug 31, 2021 5 - Production/Stable pytest (>=6.2.5,<7.0.0) - :pypi:`pytest-timeit` A pytest plugin to time test function runs Oct 13, 2016 4 - Beta N/A - :pypi:`pytest-timeout` pytest plugin to abort hanging tests Oct 11, 2021 5 - Production/Stable pytest (>=5.0.0) - :pypi:`pytest-timeouts` Linux-only Pytest plugin to control durations of various test case execution phases Sep 21, 2019 5 - Production/Stable N/A - :pypi:`pytest-timer` A timer plugin for pytest Jun 02, 2021 N/A N/A - :pypi:`pytest-timestamper` Pytest plugin to add a timestamp prefix to the pytest output Jun 06, 2021 N/A N/A - :pypi:`pytest-tipsi-django` Nov 17, 2021 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-tipsi-testing` Better fixtures management. Various helpers Nov 04, 2020 4 - Beta pytest (>=3.3.0) - :pypi:`pytest-tldr` A pytest plugin that limits the output to just the things you need. Mar 12, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-tm4j-reporter` Cloud Jira Test Management (TM4J) PyTest reporter plugin Sep 01, 2020 N/A pytest - :pypi:`pytest-tmreport` this is a vue-element ui report for pytest Nov 17, 2021 N/A N/A - :pypi:`pytest-todo` A small plugin for the pytest testing framework, marking TODO comments as failure May 23, 2019 4 - Beta pytest - :pypi:`pytest-tomato` Mar 01, 2019 5 - Production/Stable N/A - :pypi:`pytest-toolbelt` This is just a collection of utilities for pytest, but don't really belong in pytest proper. Aug 12, 2019 3 - Alpha N/A - :pypi:`pytest-toolbox` Numerous useful plugins for pytest. Apr 07, 2018 N/A pytest (>=3.5.0) - :pypi:`pytest-tornado` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Jun 17, 2020 5 - Production/Stable pytest (>=3.6) - :pypi:`pytest-tornado5` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Nov 16, 2018 5 - Production/Stable pytest (>=3.6) - :pypi:`pytest-tornado-yen3` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Oct 15, 2018 5 - Production/Stable N/A - :pypi:`pytest-tornasync` py.test plugin for testing Python 3.5+ Tornado code Jul 15, 2019 3 - Alpha pytest (>=3.0) - :pypi:`pytest-track` Feb 26, 2021 3 - Alpha pytest (>=3.0) - :pypi:`pytest-translations` Test your translation files. Nov 05, 2021 5 - Production/Stable N/A - :pypi:`pytest-travis-fold` Folds captured output sections in Travis CI build log Nov 29, 2017 4 - Beta pytest (>=2.6.0) - :pypi:`pytest-trello` Plugin for py.test that integrates trello using markers Nov 20, 2015 5 - Production/Stable N/A - :pypi:`pytest-trepan` Pytest plugin for trepan debugger. Jul 28, 2018 5 - Production/Stable N/A - :pypi:`pytest-trialtemp` py.test plugin for using the same _trial_temp working directory as trial Jun 08, 2015 N/A N/A - :pypi:`pytest-trio` Pytest plugin for trio Oct 16, 2020 N/A N/A - :pypi:`pytest-tspwplib` A simple plugin to use with tspwplib Jan 08, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-tstcls` Test Class Base Mar 23, 2020 5 - Production/Stable N/A - :pypi:`pytest-twisted` A twisted plugin for pytest. Aug 30, 2021 5 - Production/Stable pytest (>=2.3) - :pypi:`pytest-typhoon-xray` Typhoon HIL plugin for pytest Nov 03, 2021 4 - Beta N/A - :pypi:`pytest-tytest` Typhoon HIL plugin for pytest May 25, 2020 4 - Beta pytest (>=5.4.2) - :pypi:`pytest-ubersmith` Easily mock calls to ubersmith at the \`requests\` level. Apr 13, 2015 N/A N/A - :pypi:`pytest-ui` Text User Interface for running python tests Jul 05, 2021 4 - Beta pytest - :pypi:`pytest-unhandled-exception-exit-code` Plugin for py.test set a different exit code on uncaught exceptions Jun 22, 2020 5 - Production/Stable pytest (>=2.3) - :pypi:`pytest-unittest-filter` A pytest plugin for filtering unittest-based test classes Jan 12, 2019 4 - Beta pytest (>=3.1.0) - :pypi:`pytest-unmarked` Run only unmarked tests Aug 27, 2019 5 - Production/Stable N/A - :pypi:`pytest-unordered` Test equality of unordered collections in pytest Mar 28, 2021 4 - Beta N/A - :pypi:`pytest-upload-report` pytest-upload-report is a plugin for pytest that upload your test report for test results. Jun 18, 2021 5 - Production/Stable N/A - :pypi:`pytest-utils` Some helpers for pytest. Dec 04, 2021 4 - Beta pytest (>=6.2.5,<7.0.0) - :pypi:`pytest-vagrant` A py.test plugin providing access to vagrant. Sep 07, 2021 5 - Production/Stable pytest - :pypi:`pytest-valgrind` May 19, 2021 N/A N/A - :pypi:`pytest-variables` pytest plugin for providing variables to tests/fixtures Oct 23, 2019 5 - Production/Stable pytest (>=2.4.2) - :pypi:`pytest-variant` Variant support for Pytest Jun 20, 2021 N/A N/A - :pypi:`pytest-vcr` Plugin for managing VCR.py cassettes Apr 26, 2019 5 - Production/Stable pytest (>=3.6.0) - :pypi:`pytest-vcr-delete-on-fail` A pytest plugin that automates vcrpy cassettes deletion on test failure. Aug 13, 2021 4 - Beta pytest (>=6.2.2,<7.0.0) - :pypi:`pytest-vcrpandas` Test from HTTP interactions to dataframe processed. Jan 12, 2019 4 - Beta pytest - :pypi:`pytest-venv` py.test fixture for creating a virtual environment Aug 04, 2020 4 - Beta pytest - :pypi:`pytest-ver` Pytest module with Verification Report Aug 30, 2021 2 - Pre-Alpha N/A - :pypi:`pytest-verbose-parametrize` More descriptive output for parametrized py.test tests May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-vimqf` A simple pytest plugin that will shrink pytest output when specified, to fit vim quickfix window. Feb 08, 2021 4 - Beta pytest (>=6.2.2,<7.0.0) - :pypi:`pytest-virtualenv` Virtualenv fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-voluptuous` Pytest plugin for asserting data against voluptuous schema. Jun 09, 2020 N/A pytest - :pypi:`pytest-vscodedebug` A pytest plugin to easily enable debugging tests within Visual Studio Code Dec 04, 2020 4 - Beta N/A - :pypi:`pytest-vts` pytest plugin for automatic recording of http stubbed tests Jun 05, 2019 N/A pytest (>=2.3) - :pypi:`pytest-vw` pytest-vw makes your failing test cases succeed under CI tools scrutiny Oct 07, 2015 4 - Beta N/A - :pypi:`pytest-vyper` Plugin for the vyper smart contract language. May 28, 2020 2 - Pre-Alpha N/A - :pypi:`pytest-wa-e2e-plugin` Pytest plugin for testing whatsapp bots with end to end tests Feb 18, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-watch` Local continuous test runner with pytest and watchdog. May 20, 2018 N/A N/A - :pypi:`pytest-watcher` Continiously runs pytest on changes in \*.py files Sep 18, 2021 3 - Alpha N/A - :pypi:`pytest-wdl` Pytest plugin for testing WDL workflows. Nov 17, 2020 5 - Production/Stable N/A - :pypi:`pytest-webdriver` Selenium webdriver fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-wetest` Welian API Automation test framework pytest plugin Nov 10, 2018 4 - Beta N/A - :pypi:`pytest-whirlwind` Testing Tornado. Jun 12, 2020 N/A N/A - :pypi:`pytest-wholenodeid` pytest addon for displaying the whole node id for failures Aug 26, 2015 4 - Beta pytest (>=2.0) - :pypi:`pytest-win32consoletitle` Pytest progress in console title (Win32 only) Aug 08, 2021 N/A N/A - :pypi:`pytest-winnotify` Windows tray notifications for py.test results. Apr 22, 2016 N/A N/A - :pypi:`pytest-with-docker` pytest with docker helpers. Nov 09, 2021 N/A pytest - :pypi:`pytest-workflow` A pytest plugin for configuring workflow/pipeline tests using YAML files Dec 03, 2021 5 - Production/Stable pytest (>=5.4.0) - :pypi:`pytest-xdist` pytest xdist plugin for distributed testing and loop-on-failing modes Sep 21, 2021 5 - Production/Stable pytest (>=6.0.0) - :pypi:`pytest-xdist-debug-for-graingert` pytest xdist plugin for distributed testing and loop-on-failing modes Jul 24, 2019 5 - Production/Stable pytest (>=4.4.0) - :pypi:`pytest-xdist-forked` forked from pytest-xdist Feb 10, 2020 5 - Production/Stable pytest (>=4.4.0) - :pypi:`pytest-xdist-tracker` pytest plugin helps to reproduce failures for particular xdist node Nov 18, 2021 3 - Alpha pytest (>=3.5.1) - :pypi:`pytest-xfaillist` Maintain a xfaillist in an additional file to avoid merge-conflicts. Sep 17, 2021 N/A pytest (>=6.2.2,<7.0.0) - :pypi:`pytest-xfiles` Pytest fixtures providing data read from function, module or package related (x)files. Feb 27, 2018 N/A N/A - :pypi:`pytest-xlog` Extended logging for test and decorators May 31, 2020 4 - Beta N/A - :pypi:`pytest-xpara` An extended parametrizing plugin of pytest. Oct 30, 2017 3 - Alpha pytest - :pypi:`pytest-xprocess` A pytest plugin for managing processes across test runs. Jul 28, 2021 4 - Beta pytest (>=2.8) - :pypi:`pytest-xray` May 30, 2019 3 - Alpha N/A - :pypi:`pytest-xrayjira` Mar 17, 2020 3 - Alpha pytest (==4.3.1) - :pypi:`pytest-xray-server` Oct 27, 2021 3 - Alpha pytest (>=5.3.1) - :pypi:`pytest-xvfb` A pytest plugin to run Xvfb for tests. Jun 09, 2020 4 - Beta pytest (>=2.8.1) - :pypi:`pytest-yaml` This plugin is used to load yaml output to your test using pytest framework. Oct 05, 2018 N/A pytest - :pypi:`pytest-yamltree` Create or check file/directory trees described by YAML Mar 02, 2020 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-yamlwsgi` Run tests against wsgi apps defined in yaml May 11, 2010 N/A N/A - :pypi:`pytest-yapf` Run yapf Jul 06, 2017 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-yapf3` Validate your Python file format with yapf Aug 03, 2020 5 - Production/Stable pytest (>=5.4) - :pypi:`pytest-yield` PyTest plugin to run tests concurrently, each \`yield\` switch context to other one Jan 23, 2019 N/A N/A - :pypi:`pytest-yuk` Display tests you are uneasy with, using 🤢/🤮 for pass/fail of tests marked with yuk. Mar 26, 2021 N/A N/A - :pypi:`pytest-zafira` A Zafira plugin for pytest Sep 18, 2019 5 - Production/Stable pytest (==4.1.1) - :pypi:`pytest-zap` OWASP ZAP plugin for py.test. May 12, 2014 4 - Beta N/A - :pypi:`pytest-zebrunner` Pytest connector for Zebrunner reporting Dec 02, 2021 5 - Production/Stable pytest (>=4.5.0) - :pypi:`pytest-zigzag` Extend py.test for RPC OpenStack testing. Feb 27, 2019 4 - Beta pytest (~=3.6) - =============================================== ======================================================================================================================================================================== ============== ===================== ================================================ + =============================================== ====================================================================================================================================================================================================================================================================================================================================================================================== ============== ===================== ================================================ + name summary last_release status requires + =============================================== ====================================================================================================================================================================================================================================================================================================================================================================================== ============== ===================== ================================================ + :pypi:`logassert` Simple but powerful assertion and verification of logged lines. May 20, 2022 5 - Production/Stable N/A + :pypi:`logot` Test whether your code is logging correctly 🪵 Mar 23, 2024 5 - Production/Stable pytest<9,>=7; extra == "pytest" + :pypi:`nuts` Network Unit Testing System Aug 11, 2023 N/A pytest (>=7.3.0,<8.0.0) + :pypi:`pytest-abq` Pytest integration for the ABQ universal test runner. Apr 07, 2023 N/A N/A + :pypi:`pytest-abstracts` A contextmanager pytest fixture for handling multiple mock abstracts May 25, 2022 N/A N/A + :pypi:`pytest-accept` A pytest-plugin for updating doctest outputs Feb 10, 2024 N/A pytest (>=6) + :pypi:`pytest-adaptavist` pytest plugin for generating test execution results within Jira Test Management (tm4j) Oct 13, 2022 N/A pytest (>=5.4.0) + :pypi:`pytest-adaptavist-fixed` pytest plugin for generating test execution results within Jira Test Management (tm4j) Nov 08, 2023 N/A pytest >=5.4.0 + :pypi:`pytest-addons-test` 用于测试pytest的插件 Aug 02, 2021 N/A pytest (>=6.2.4,<7.0.0) + :pypi:`pytest-adf` Pytest plugin for writing Azure Data Factory integration tests May 10, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-adf-azure-identity` Pytest plugin for writing Azure Data Factory integration tests Mar 06, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-ads-testplan` Azure DevOps Test Case reporting for pytest tests Sep 15, 2022 N/A N/A + :pypi:`pytest-affected` Nov 06, 2023 N/A N/A + :pypi:`pytest-agent` Service that exposes a REST API that can be used to interract remotely with Pytest. It is shipped with a dashboard that enables running tests in a more convenient way. Nov 25, 2021 N/A N/A + :pypi:`pytest-aggreport` pytest plugin for pytest-repeat that generate aggregate report of the same test cases with additional statistics details. Mar 07, 2021 4 - Beta pytest (>=6.2.2) + :pypi:`pytest-ai1899` pytest plugin for connecting to ai1899 smart system stack Mar 13, 2024 5 - Production/Stable N/A + :pypi:`pytest-aio` Pytest plugin for testing async python code Apr 08, 2024 5 - Production/Stable pytest + :pypi:`pytest-aiofiles` pytest fixtures for writing aiofiles tests with pyfakefs May 14, 2017 5 - Production/Stable N/A + :pypi:`pytest-aiogram` May 06, 2023 N/A N/A + :pypi:`pytest-aiohttp` Pytest plugin for aiohttp support Sep 06, 2023 4 - Beta pytest >=6.1.0 + :pypi:`pytest-aiohttp-client` Pytest \`client\` fixture for the Aiohttp Jan 10, 2023 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-aiomoto` pytest-aiomoto Jun 24, 2023 N/A pytest (>=7.0,<8.0) + :pypi:`pytest-aioresponses` py.test integration for aioresponses Jul 29, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-aioworkers` A plugin to test aioworkers project with pytest May 01, 2023 5 - Production/Stable pytest>=6.1.0 + :pypi:`pytest-airflow` pytest support for airflow. Apr 03, 2019 3 - Alpha pytest (>=4.4.0) + :pypi:`pytest-airflow-utils` Nov 15, 2021 N/A N/A + :pypi:`pytest-alembic` A pytest plugin for verifying alembic migrations. Mar 04, 2024 N/A pytest (>=6.0) + :pypi:`pytest-allclose` Pytest fixture extending Numpy's allclose function Jul 30, 2019 5 - Production/Stable pytest + :pypi:`pytest-allure-adaptor` Plugin for py.test to generate allure xml reports Jan 10, 2018 N/A pytest (>=2.7.3) + :pypi:`pytest-allure-adaptor2` Plugin for py.test to generate allure xml reports Oct 14, 2020 N/A pytest (>=2.7.3) + :pypi:`pytest-allure-collection` pytest plugin to collect allure markers without running any tests Apr 13, 2023 N/A pytest + :pypi:`pytest-allure-dsl` pytest plugin to test case doc string dls instructions Oct 25, 2020 4 - Beta pytest + :pypi:`pytest-allure-intersection` Oct 27, 2022 N/A pytest (<5) + :pypi:`pytest-allure-spec-coverage` The pytest plugin aimed to display test coverage of the specs(requirements) in Allure Oct 26, 2021 N/A pytest + :pypi:`pytest-alphamoon` Static code checks used at Alphamoon Dec 30, 2021 5 - Production/Stable pytest (>=3.5.0) + :pypi:`pytest-analyzer` this plugin allows to analyze tests in pytest project, collect test metadata and sync it with testomat.io TCM system Feb 21, 2024 N/A pytest <8.0.0,>=7.3.1 + :pypi:`pytest-android` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Feb 21, 2019 3 - Alpha pytest + :pypi:`pytest-anki` A pytest plugin for testing Anki add-ons Jul 31, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-annotate` pytest-annotate: Generate PyAnnotate annotations from your pytest tests. Jun 07, 2022 3 - Alpha pytest (<8.0.0,>=3.2.0) + :pypi:`pytest-ansible` Plugin for pytest to simplify calling ansible modules from tests or fixtures Jan 18, 2024 5 - Production/Stable pytest >=6 + :pypi:`pytest-ansible-playbook` Pytest fixture which runs given ansible playbook file. Mar 08, 2019 4 - Beta N/A + :pypi:`pytest-ansible-playbook-runner` Pytest fixture which runs given ansible playbook file. Dec 02, 2020 4 - Beta pytest (>=3.1.0) + :pypi:`pytest-ansible-units` A pytest plugin for running unit tests within an ansible collection Apr 14, 2022 N/A N/A + :pypi:`pytest-antilru` Bust functools.lru_cache when running pytest to avoid test pollution Jul 05, 2022 5 - Production/Stable pytest + :pypi:`pytest-anyio` The pytest anyio plugin is built into anyio. You don't need this package. Jun 29, 2021 N/A pytest + :pypi:`pytest-anything` Pytest fixtures to assert anything and something Jan 18, 2024 N/A pytest + :pypi:`pytest-aoc` Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures Dec 02, 2023 5 - Production/Stable pytest ; extra == 'test' + :pypi:`pytest-aoreporter` pytest report Jun 27, 2022 N/A N/A + :pypi:`pytest-api` An ASGI middleware to populate OpenAPI Specification examples from pytest functions May 12, 2022 N/A pytest (>=7.1.1,<8.0.0) + :pypi:`pytest-api-soup` Validate multiple endpoints with unit testing using a single source of truth. Aug 27, 2022 N/A N/A + :pypi:`pytest-apistellar` apistellar plugin for pytest. Jun 18, 2019 N/A N/A + :pypi:`pytest-appengine` AppEngine integration that works well with pytest-django Feb 27, 2017 N/A N/A + :pypi:`pytest-appium` Pytest plugin for appium Dec 05, 2019 N/A N/A + :pypi:`pytest-approvaltests` A plugin to use approvaltests with pytest May 08, 2022 4 - Beta pytest (>=7.0.1) + :pypi:`pytest-approvaltests-geo` Extension for ApprovalTests.Python specific to geo data verification Feb 05, 2024 5 - Production/Stable pytest + :pypi:`pytest-archon` Rule your architecture like a real developer Dec 18, 2023 5 - Production/Stable pytest >=7.2 + :pypi:`pytest-argus` pyest results colection plugin Jun 24, 2021 5 - Production/Stable pytest (>=6.2.4) + :pypi:`pytest-arraydiff` pytest plugin to help with comparing array output from tests Nov 27, 2023 4 - Beta pytest >=4.6 + :pypi:`pytest-asgi-server` Convenient ASGI client/server fixtures for Pytest Dec 12, 2020 N/A pytest (>=5.4.1) + :pypi:`pytest-aspec` A rspec format reporter for pytest Dec 20, 2023 4 - Beta N/A + :pypi:`pytest-asptest` test Answer Set Programming programs Apr 28, 2018 4 - Beta N/A + :pypi:`pytest-assertcount` Plugin to count actual number of asserts in pytest Oct 23, 2022 N/A pytest (>=5.0.0) + :pypi:`pytest-assertions` Pytest Assertions Apr 27, 2022 N/A N/A + :pypi:`pytest-assertutil` pytest-assertutil May 10, 2019 N/A N/A + :pypi:`pytest-assert-utils` Useful assertion utilities for use with pytest Apr 14, 2022 3 - Alpha N/A + :pypi:`pytest-assume` A pytest plugin that allows multiple failures per test Jun 24, 2021 N/A pytest (>=2.7) + :pypi:`pytest-assurka` A pytest plugin for Assurka Studio Aug 04, 2022 N/A N/A + :pypi:`pytest-ast-back-to-python` A plugin for pytest devs to view how assertion rewriting recodes the AST Sep 29, 2019 4 - Beta N/A + :pypi:`pytest-asteroid` PyTest plugin for docker-based testing on database images Aug 15, 2022 N/A pytest (>=6.2.5,<8.0.0) + :pypi:`pytest-astropy` Meta-package containing dependencies for testing Sep 26, 2023 5 - Production/Stable pytest >=4.6 + :pypi:`pytest-astropy-header` pytest plugin to add diagnostic information to the header of the test output Sep 06, 2022 3 - Alpha pytest (>=4.6) + :pypi:`pytest-ast-transformer` May 04, 2019 3 - Alpha pytest + :pypi:`pytest_async` pytest-async - Run your coroutine in event loop without decorator Feb 26, 2020 N/A N/A + :pypi:`pytest-async-generators` Pytest fixtures for async generators Jul 05, 2023 N/A N/A + :pypi:`pytest-asyncio` Pytest support for asyncio Mar 19, 2024 4 - Beta pytest <9,>=7.0.0 + :pypi:`pytest-asyncio-cooperative` Run all your asynchronous tests cooperatively. Feb 25, 2024 N/A N/A + :pypi:`pytest-asyncio-network-simulator` pytest-asyncio-network-simulator: Plugin for pytest for simulator the network in tests Jul 31, 2018 3 - Alpha pytest (<3.7.0,>=3.3.2) + :pypi:`pytest-async-mongodb` pytest plugin for async MongoDB Oct 18, 2017 5 - Production/Stable pytest (>=2.5.2) + :pypi:`pytest-async-sqlalchemy` Database testing fixtures using the SQLAlchemy asyncio API Oct 07, 2021 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-atf-allure` 基于allure-pytest进行自定义 Nov 29, 2023 N/A pytest (>=7.4.2,<8.0.0) + :pypi:`pytest-atomic` Skip rest of tests if previous test failed. Nov 24, 2018 4 - Beta N/A + :pypi:`pytest-attrib` pytest plugin to select tests based on attributes similar to the nose-attrib plugin May 24, 2016 4 - Beta N/A + :pypi:`pytest-austin` Austin plugin for pytest Oct 11, 2020 4 - Beta N/A + :pypi:`pytest-autocap` automatically capture test & fixture stdout/stderr to files May 15, 2022 N/A pytest (<7.2,>=7.1.2) + :pypi:`pytest-autochecklog` automatically check condition and log all the checks Apr 25, 2015 4 - Beta N/A + :pypi:`pytest-automation` pytest plugin for building a test suite, using YAML files to extend pytest parameterize functionality. May 20, 2022 N/A pytest (>=7.0.0) + :pypi:`pytest-automock` Pytest plugin for automatical mocks creation May 16, 2023 N/A pytest ; extra == 'dev' + :pypi:`pytest-auto-parametrize` pytest plugin: avoid repeating arguments in parametrize Oct 02, 2016 3 - Alpha N/A + :pypi:`pytest-autotest` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Aug 25, 2021 N/A pytest + :pypi:`pytest-aviator` Aviator's Flakybot pytest plugin that automatically reruns flaky tests. Nov 04, 2022 4 - Beta pytest + :pypi:`pytest-avoidance` Makes pytest skip tests that don not need rerunning May 23, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-aws` pytest plugin for testing AWS resource configurations Oct 04, 2017 4 - Beta N/A + :pypi:`pytest-aws-config` Protect your AWS credentials in unit tests May 28, 2021 N/A N/A + :pypi:`pytest-aws-fixtures` A series of fixtures to use in integration tests involving actual AWS services. Feb 02, 2024 N/A pytest (>=8.0.0,<9.0.0) + :pypi:`pytest-axe` pytest plugin for axe-selenium-python Nov 12, 2018 N/A pytest (>=3.0.0) + :pypi:`pytest-axe-playwright-snapshot` A pytest plugin that runs Axe-core on Playwright pages and takes snapshots of the results. Jul 25, 2023 N/A pytest + :pypi:`pytest-azure` Pytest utilities and mocks for Azure Jan 18, 2023 3 - Alpha pytest + :pypi:`pytest-azure-devops` Simplifies using azure devops parallel strategy (https://docs.microsoft.com/en-us/azure/devops/pipelines/test/parallel-testing-any-test-runner) with pytest. Jun 20, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-azurepipelines` Formatting PyTest output for Azure Pipelines UI Oct 06, 2023 5 - Production/Stable pytest (>=5.0.0) + :pypi:`pytest-bandit` A bandit plugin for pytest Feb 23, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-bandit-xayon` A bandit plugin for pytest Oct 17, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-base-url` pytest plugin for URL based testing Jan 31, 2024 5 - Production/Stable pytest>=7.0.0 + :pypi:`pytest-bdd` BDD for pytest Mar 17, 2024 6 - Mature pytest (>=6.2.0) + :pypi:`pytest-bdd-html` pytest plugin to display BDD info in HTML test report Nov 22, 2022 3 - Alpha pytest (!=6.0.0,>=5.0) + :pypi:`pytest-bdd-ng` BDD for pytest Dec 31, 2023 4 - Beta pytest >=5.0 + :pypi:`pytest-bdd-report` A pytest-bdd plugin for generating useful and informative BDD test reports Feb 19, 2024 N/A pytest >=7.1.3 + :pypi:`pytest-bdd-splinter` Common steps for pytest bdd and splinter integration Aug 12, 2019 5 - Production/Stable pytest (>=4.0.0) + :pypi:`pytest-bdd-web` A simple plugin to use with pytest Jan 02, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-bdd-wrappers` Feb 11, 2020 2 - Pre-Alpha N/A + :pypi:`pytest-beakerlib` A pytest plugin that reports test results to the BeakerLib framework Mar 17, 2017 5 - Production/Stable pytest + :pypi:`pytest-beartype` Pytest plugin to run your tests with beartype checking enabled. Jan 25, 2024 N/A pytest + :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Apr 19, 2024 3 - Alpha pytest + :pypi:`pytest-beds` Fixtures for testing Google Appengine (GAE) apps Jun 07, 2016 4 - Beta N/A + :pypi:`pytest-beeprint` use icdiff for better error messages in pytest assertions Jul 04, 2023 4 - Beta N/A + :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A + :pypi:`pytest-benchmark` A \`\`pytest\`\` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer. Oct 25, 2022 5 - Production/Stable pytest (>=3.8) + :pypi:`pytest-better-datadir` A small example package Mar 13, 2023 N/A N/A + :pypi:`pytest-better-parametrize` Better description of parametrized test cases Mar 05, 2024 4 - Beta pytest >=6.2.0 + :pypi:`pytest-bg-process` Pytest plugin to initialize background process Jan 24, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-bigchaindb` A BigchainDB plugin for pytest. Jan 24, 2022 4 - Beta N/A + :pypi:`pytest-bigquery-mock` Provides a mock fixture for python bigquery client Dec 28, 2022 N/A pytest (>=5.0) + :pypi:`pytest-bisect-tests` Find tests leaking state and affecting other Mar 25, 2024 N/A N/A + :pypi:`pytest-black` A pytest plugin to enable format checking with black Oct 05, 2020 4 - Beta N/A + :pypi:`pytest-black-multipy` Allow '--black' on older Pythons Jan 14, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' + :pypi:`pytest-black-ng` A pytest plugin to enable format checking with black Oct 20, 2022 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-blame` A pytest plugin helps developers to debug by providing useful commits history. May 04, 2019 N/A pytest (>=4.4.0) + :pypi:`pytest-blender` Blender Pytest plugin. Aug 10, 2023 N/A pytest ; extra == 'dev' + :pypi:`pytest-blink1` Pytest plugin to emit notifications via the Blink(1) RGB LED Jan 07, 2018 4 - Beta N/A + :pypi:`pytest-blockage` Disable network requests during a test run. Dec 21, 2021 N/A pytest + :pypi:`pytest-blocker` pytest plugin to mark a test as blocker and skip all other tests Sep 07, 2015 4 - Beta N/A + :pypi:`pytest-blue` A pytest plugin that adds a \`blue\` fixture for printing stuff in blue. Sep 05, 2022 N/A N/A + :pypi:`pytest-board` Local continuous test runner with pytest and watchdog. Jan 20, 2019 N/A N/A + :pypi:`pytest-boost-xml` Plugin for pytest to generate boost xml reports Nov 30, 2022 4 - Beta N/A + :pypi:`pytest-bootstrap` Mar 04, 2022 N/A N/A + :pypi:`pytest-bpdb` A py.test plug-in to enable drop to bpdb debugger on test failure. Jan 19, 2015 2 - Pre-Alpha N/A + :pypi:`pytest-bravado` Pytest-bravado automatically generates from OpenAPI specification client fixtures. Feb 15, 2022 N/A N/A + :pypi:`pytest-breakword` Use breakword with pytest Aug 04, 2021 N/A pytest (>=6.2.4,<7.0.0) + :pypi:`pytest-breed-adapter` A simple plugin to connect with breed-server Nov 07, 2018 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-briefcase` A pytest plugin for running tests on a Briefcase project. Jun 14, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-broadcaster` Pytest plugin to broadcast pytest output to various destinations Apr 06, 2024 3 - Alpha pytest + :pypi:`pytest-browser` A pytest plugin for console based browser test selection just after the collection phase Dec 10, 2016 3 - Alpha N/A + :pypi:`pytest-browsermob-proxy` BrowserMob proxy plugin for py.test. Jun 11, 2013 4 - Beta N/A + :pypi:`pytest_browserstack` Py.test plugin for BrowserStack Jan 27, 2016 4 - Beta N/A + :pypi:`pytest-browserstack-local` \`\`py.test\`\` plugin to run \`\`BrowserStackLocal\`\` in background. Feb 09, 2018 N/A N/A + :pypi:`pytest-budosystems` Budo Systems is a martial arts school management system. This module is the Budo Systems Pytest Plugin. May 07, 2023 3 - Alpha pytest + :pypi:`pytest-bug` Pytest plugin for marking tests as a bug Sep 23, 2023 5 - Production/Stable pytest >=7.1.0 + :pypi:`pytest-bugtong-tag` pytest-bugtong-tag is a plugin for pytest Jan 16, 2022 N/A N/A + :pypi:`pytest-bugzilla` py.test bugzilla integration plugin May 05, 2010 4 - Beta N/A + :pypi:`pytest-bugzilla-notifier` A plugin that allows you to execute create, update, and read information from BugZilla bugs Jun 15, 2018 4 - Beta pytest (>=2.9.2) + :pypi:`pytest-buildkite` Plugin for pytest that automatically publishes coverage and pytest report annotations to Buildkite. Jul 13, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-builtin-types` Nov 17, 2021 N/A pytest + :pypi:`pytest-bwrap` Run your tests in Bubblewrap sandboxes Feb 25, 2024 3 - Alpha N/A + :pypi:`pytest-cache` pytest plugin with mechanisms for caching across test runs Jun 04, 2013 3 - Alpha N/A + :pypi:`pytest-cache-assert` Cache assertion data to simplify regression testing of complex serializable data Aug 14, 2023 5 - Production/Stable pytest (>=6.0.0) + :pypi:`pytest-cagoule` Pytest plugin to only run tests affected by changes Jan 01, 2020 3 - Alpha N/A + :pypi:`pytest-cairo` Pytest support for cairo-lang and starknet Apr 17, 2022 N/A pytest + :pypi:`pytest-call-checker` Small pytest utility to easily create test doubles Oct 16, 2022 4 - Beta pytest (>=7.1.3,<8.0.0) + :pypi:`pytest-camel-collect` Enable CamelCase-aware pytest class collection Aug 02, 2020 N/A pytest (>=2.9) + :pypi:`pytest-canonical-data` A plugin which allows to compare results with canonical results, based on previous runs May 08, 2020 2 - Pre-Alpha pytest (>=3.5.0) + :pypi:`pytest-caprng` A plugin that replays pRNG state on failure. May 02, 2018 4 - Beta N/A + :pypi:`pytest-capture-deprecatedwarnings` pytest plugin to capture all deprecatedwarnings and put them in one file Apr 30, 2019 N/A N/A + :pypi:`pytest-capture-warnings` pytest plugin to capture all warnings and put them in one file of your choice May 03, 2022 N/A pytest + :pypi:`pytest-cases` Separate test code from test cases in pytest. Apr 04, 2024 5 - Production/Stable N/A + :pypi:`pytest-cassandra` Cassandra CCM Test Fixtures for pytest Nov 04, 2017 1 - Planning N/A + :pypi:`pytest-catchlog` py.test plugin to catch log messages. This is a fork of pytest-capturelog. Jan 24, 2016 4 - Beta pytest (>=2.6) + :pypi:`pytest-catch-server` Pytest plugin with server for catching HTTP requests. Dec 12, 2019 5 - Production/Stable N/A + :pypi:`pytest-celery` Pytest plugin for Celery Apr 11, 2024 4 - Beta N/A + :pypi:`pytest-cfg-fetcher` Pass config options to your unit tests. Feb 26, 2024 N/A N/A + :pypi:`pytest-chainmaker` pytest plugin for chainmaker Oct 15, 2021 N/A N/A + :pypi:`pytest-chalice` A set of py.test fixtures for AWS Chalice Jul 01, 2020 4 - Beta N/A + :pypi:`pytest-change-assert` 修改报错中文为英文 Oct 19, 2022 N/A N/A + :pypi:`pytest-change-demo` turn . into √,turn F into x Mar 02, 2022 N/A pytest + :pypi:`pytest-change-report` turn . into √,turn F into x Sep 14, 2020 N/A pytest + :pypi:`pytest-change-xds` turn . into √,turn F into x Apr 16, 2022 N/A pytest + :pypi:`pytest-chdir` A pytest fixture for changing current working directory Jan 28, 2020 N/A pytest (>=5.0.0,<6.0.0) + :pypi:`pytest-check` A pytest plugin that allows multiple failures per test. Jan 18, 2024 N/A pytest>=7.0.0 + :pypi:`pytest-checkdocs` check the README when running tests Mar 31, 2024 5 - Production/Stable pytest>=6; extra == "testing" + :pypi:`pytest-checkipdb` plugin to check if there are ipdb debugs left Dec 04, 2023 5 - Production/Stable pytest >=2.9.2 + :pypi:`pytest-check-library` check your missing library Jul 17, 2022 N/A N/A + :pypi:`pytest-check-libs` check your missing library Jul 17, 2022 N/A N/A + :pypi:`pytest-check-links` Check links in files Jul 29, 2020 N/A pytest<9,>=7.0 + :pypi:`pytest-checklist` Pytest plugin to track and report unit/function coverage. Mar 12, 2024 N/A N/A + :pypi:`pytest-check-mk` pytest plugin to test Check_MK checks Nov 19, 2015 4 - Beta pytest + :pypi:`pytest-check-requirements` A package to prevent Dependency Confusion attacks against Yandex. Feb 20, 2024 N/A N/A + :pypi:`pytest-ch-framework` My pytest framework Apr 17, 2024 N/A pytest==8.0.1 + :pypi:`pytest-chic-report` A pytest plugin to send a report and printing summary of tests. Jan 31, 2023 5 - Production/Stable N/A + :pypi:`pytest-choose` Provide the pytest with the ability to collect use cases based on rules in text files Feb 04, 2024 N/A pytest >=7.0.0 + :pypi:`pytest-chunks` Run only a chunk of your test suite Jul 05, 2022 N/A pytest (>=6.0.0) + :pypi:`pytest_cid` Compare data structures containing matching CIDs of different versions and encoding Sep 01, 2023 4 - Beta pytest >= 5.0, < 7.0 + :pypi:`pytest-circleci` py.test plugin for CircleCI May 03, 2019 N/A N/A + :pypi:`pytest-circleci-parallelized` Parallelize pytest across CircleCI workers. Oct 20, 2022 N/A N/A + :pypi:`pytest-circleci-parallelized-rjp` Parallelize pytest across CircleCI workers. Jun 21, 2022 N/A pytest + :pypi:`pytest-ckan` Backport of CKAN 2.9 pytest plugin and fixtures to CAKN 2.8 Apr 28, 2020 4 - Beta pytest + :pypi:`pytest-clarity` A plugin providing an alternative, colourful diff output for failing assertions. Jun 11, 2021 N/A N/A + :pypi:`pytest-cldf` Easy quality control for CLDF datasets using pytest Nov 07, 2022 N/A pytest (>=3.6) + :pypi:`pytest_cleanup` Automated, comprehensive and well-organised pytest test cases. Jan 28, 2020 N/A N/A + :pypi:`pytest-cleanuptotal` A cleanup plugin for pytest Mar 19, 2024 5 - Production/Stable N/A + :pypi:`pytest-clerk` A set of pytest fixtures to help with integration testing with Clerk. Apr 19, 2024 N/A pytest<9.0.0,>=8.0.0 + :pypi:`pytest-click` Pytest plugin for Click Feb 11, 2022 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-cli-fixtures` Automatically register fixtures for custom CLI arguments Jul 28, 2022 N/A pytest (~=7.0) + :pypi:`pytest-clld` Jul 06, 2022 N/A pytest (>=3.6) + :pypi:`pytest-cloud` Distributed tests planner plugin for pytest testing framework. Oct 05, 2020 6 - Mature N/A + :pypi:`pytest-cloudflare-worker` pytest plugin for testing cloudflare workers Mar 30, 2021 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-cloudist` Distribute tests to cloud machines without fuss Sep 02, 2022 4 - Beta pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-cmake` Provide CMake module for Pytest Mar 18, 2024 N/A pytest<9,>=4 + :pypi:`pytest-cmake-presets` Execute CMake Presets via pytest Dec 26, 2022 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-cobra` PyTest plugin for testing Smart Contracts for Ethereum blockchain. Jun 29, 2019 3 - Alpha pytest (<4.0.0,>=3.7.1) + :pypi:`pytest_codeblocks` Test code blocks in your READMEs Sep 17, 2023 5 - Production/Stable pytest >= 7.0.0 + :pypi:`pytest-codecarbon` Pytest plugin for measuring carbon emissions Jun 15, 2022 N/A pytest + :pypi:`pytest-codecheckers` pytest plugin to add source code sanity checks (pep8 and friends) Feb 13, 2010 N/A N/A + :pypi:`pytest-codecov` Pytest plugin for uploading pytest-cov results to codecov.io Nov 29, 2022 4 - Beta pytest (>=4.6.0) + :pypi:`pytest-codegen` Automatically create pytest test signatures Aug 23, 2020 2 - Pre-Alpha N/A + :pypi:`pytest-codeowners` Pytest plugin for selecting tests by GitHub CODEOWNERS. Mar 30, 2022 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-codestyle` pytest plugin to run pycodestyle Mar 23, 2020 3 - Alpha N/A + :pypi:`pytest-codspeed` Pytest plugin to create CodSpeed benchmarks Mar 19, 2024 5 - Production/Stable pytest>=3.8 + :pypi:`pytest-collect-appoint-info` set your encoding Aug 03, 2023 N/A pytest + :pypi:`pytest-collect-formatter` Formatter for pytest collect output Mar 29, 2021 5 - Production/Stable N/A + :pypi:`pytest-collect-formatter2` Formatter for pytest collect output May 31, 2021 5 - Production/Stable N/A + :pypi:`pytest-collect-interface-info-plugin` Get executed interface information in pytest interface automation framework Sep 25, 2023 4 - Beta N/A + :pypi:`pytest-collector` Python package for collecting pytest. Aug 02, 2022 N/A pytest (>=7.0,<8.0) + :pypi:`pytest-collect-pytest-interinfo` A simple plugin to use with pytest Sep 26, 2023 4 - Beta N/A + :pypi:`pytest-colordots` Colorizes the progress indicators Oct 06, 2017 5 - Production/Stable N/A + :pypi:`pytest-commander` An interactive GUI test runner for PyTest Aug 17, 2021 N/A pytest (<7.0.0,>=6.2.4) + :pypi:`pytest-common-subject` pytest framework for testing different aspects of a common method May 15, 2022 N/A pytest (>=3.6,<8) + :pypi:`pytest-compare` pytest plugin for comparing call arguments. Jun 22, 2023 5 - Production/Stable N/A + :pypi:`pytest-concurrent` Concurrently execute test cases with multithread, multiprocess and gevent Jan 12, 2019 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-config` Base configurations and utilities for developing your Python project test suite with pytest. Nov 07, 2014 5 - Production/Stable N/A + :pypi:`pytest-confluence-report` Package stands for pytest plugin to upload results into Confluence page. Apr 17, 2022 N/A N/A + :pypi:`pytest-console-scripts` Pytest plugin for testing console scripts May 31, 2023 4 - Beta pytest (>=4.0.0) + :pypi:`pytest-consul` pytest plugin with fixtures for testing consul aware apps Nov 24, 2018 3 - Alpha pytest + :pypi:`pytest-container` Pytest fixtures for writing container based tests Apr 10, 2024 4 - Beta pytest>=3.10 + :pypi:`pytest-contextfixture` Define pytest fixtures as context managers. Mar 12, 2013 4 - Beta N/A + :pypi:`pytest-contexts` A plugin to run tests written with the Contexts framework using pytest May 19, 2021 4 - Beta N/A + :pypi:`pytest-cookies` The pytest plugin for your Cookiecutter templates. 🍪 Mar 22, 2023 5 - Production/Stable pytest (>=3.9.0) + :pypi:`pytest-copie` The pytest plugin for your copier templates 📒 Jan 27, 2024 3 - Alpha pytest + :pypi:`pytest-copier` A pytest plugin to help testing Copier templates Dec 11, 2023 4 - Beta pytest>=7.3.2 + :pypi:`pytest-couchdbkit` py.test extension for per-test couchdb databases using couchdbkit Apr 17, 2012 N/A N/A + :pypi:`pytest-count` count erros and send email Jan 12, 2018 4 - Beta N/A + :pypi:`pytest-cov` Pytest plugin for measuring coverage. Mar 24, 2024 5 - Production/Stable pytest>=4.6 + :pypi:`pytest-cover` Pytest plugin for measuring coverage. Forked from \`pytest-cov\`. Aug 01, 2015 5 - Production/Stable N/A + :pypi:`pytest-coverage` Jun 17, 2015 N/A N/A + :pypi:`pytest-coverage-context` Coverage dynamic context support for PyTest, including sub-processes Jun 28, 2023 4 - Beta N/A + :pypi:`pytest-coveragemarkers` Using pytest markers to track functional coverage and filtering of tests Apr 15, 2024 N/A pytest<8.0.0,>=7.1.2 + :pypi:`pytest-cov-exclude` Pytest plugin for excluding tests based on coverage data Apr 29, 2016 4 - Beta pytest (>=2.8.0,<2.9.0); extra == 'dev' + :pypi:`pytest_covid` Too many faillure, less tests. Jun 24, 2020 N/A N/A + :pypi:`pytest-cpp` Use pytest's runner to discover and execute C++ tests Nov 01, 2023 5 - Production/Stable pytest >=7.0 + :pypi:`pytest-cppython` A pytest plugin that imports CPPython testing types Mar 14, 2024 N/A N/A + :pypi:`pytest-cqase` Custom qase pytest plugin Aug 22, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-cram` Run cram tests with pytest. Aug 08, 2020 N/A N/A + :pypi:`pytest-crate` Manages CrateDB instances during your integration tests May 28, 2019 3 - Alpha pytest (>=4.0) + :pypi:`pytest-crayons` A pytest plugin for colorful print statements Oct 08, 2023 N/A pytest + :pypi:`pytest-create` pytest-create Feb 15, 2023 1 - Planning N/A + :pypi:`pytest-cricri` A Cricri plugin for pytest. Jan 27, 2018 N/A pytest + :pypi:`pytest-crontab` add crontab task in crontab Dec 09, 2019 N/A N/A + :pypi:`pytest-csv` CSV output for pytest. Apr 22, 2021 N/A pytest (>=6.0) + :pypi:`pytest-csv-params` Pytest plugin for Test Case Parametrization with CSV files Jul 01, 2023 5 - Production/Stable pytest (>=7.4.0,<8.0.0) + :pypi:`pytest-curio` Pytest support for curio. Oct 07, 2020 N/A N/A + :pypi:`pytest-curl-report` pytest plugin to generate curl command line report Dec 11, 2016 4 - Beta N/A + :pypi:`pytest-custom-concurrency` Custom grouping concurrence for pytest Feb 08, 2021 N/A N/A + :pypi:`pytest-custom-exit-code` Exit pytest test session with custom exit code in different scenarios Aug 07, 2019 4 - Beta pytest (>=4.0.2) + :pypi:`pytest-custom-nodeid` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 07, 2021 N/A N/A + :pypi:`pytest-custom-report` Configure the symbols displayed for test outcomes Jan 30, 2019 N/A pytest + :pypi:`pytest-custom-scheduling` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 01, 2021 N/A N/A + :pypi:`pytest-cython` A plugin for testing Cython extension modules Apr 05, 2024 5 - Production/Stable pytest>=8 + :pypi:`pytest-cython-collect` Jun 17, 2022 N/A pytest + :pypi:`pytest-darker` A pytest plugin for checking of modified code using Darker Feb 25, 2024 N/A pytest <7,>=6.0.1 + :pypi:`pytest-dash` pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A + :pypi:`pytest-dashboard` Apr 18, 2024 N/A pytest<8.0.0,>=7.4.3 + :pypi:`pytest-data` Useful functions for managing data for pytest fixtures Nov 01, 2016 5 - Production/Stable N/A + :pypi:`pytest-databases` Reusable database fixtures for any and all databases. Apr 19, 2024 4 - Beta pytest + :pypi:`pytest-databricks` Pytest plugin for remote Databricks notebooks testing Jul 29, 2020 N/A pytest + :pypi:`pytest-datadir` pytest plugin for test data directories and files Oct 03, 2023 5 - Production/Stable pytest >=5.0 + :pypi:`pytest-datadir-mgr` Manager for test data: downloads, artifact caching, and a tmpdir context. Apr 06, 2023 5 - Production/Stable pytest (>=7.1) + :pypi:`pytest-datadir-ng` Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. Dec 25, 2019 5 - Production/Stable pytest + :pypi:`pytest-datadir-nng` Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. Nov 09, 2022 5 - Production/Stable pytest (>=7.0.0,<8.0.0) + :pypi:`pytest-data-extractor` A pytest plugin to extract relevant metadata about tests into an external file (currently only json support) Jul 19, 2022 N/A pytest (>=7.0.1) + :pypi:`pytest-data-file` Fixture "data" and "case_data" for test from yaml file Dec 04, 2019 N/A N/A + :pypi:`pytest-datafiles` py.test plugin to create a 'tmp_path' containing predefined files/directories. Feb 24, 2023 5 - Production/Stable pytest (>=3.6) + :pypi:`pytest-datafixtures` Data fixtures for pytest made simple Dec 05, 2020 5 - Production/Stable N/A + :pypi:`pytest-data-from-files` pytest plugin to provide data from files loaded automatically Oct 13, 2021 4 - Beta pytest + :pypi:`pytest-dataplugin` A pytest plugin for managing an archive of test data. Sep 16, 2017 1 - Planning N/A + :pypi:`pytest-datarecorder` A py.test plugin recording and comparing test output. Feb 15, 2024 5 - Production/Stable pytest + :pypi:`pytest-dataset` Plugin for loading different datasets for pytest by prefix from json or yaml files Sep 01, 2023 5 - Production/Stable N/A + :pypi:`pytest-data-suites` Class-based pytest parametrization Apr 06, 2024 N/A pytest<9.0,>=6.0 + :pypi:`pytest-datatest` A pytest plugin for test driven data-wrangling (this is the development version of datatest's pytest integration). Oct 15, 2020 4 - Beta pytest (>=3.3) + :pypi:`pytest-db` Session scope fixture "db" for mysql query or change Dec 04, 2019 N/A N/A + :pypi:`pytest-dbfixtures` Databases fixtures plugin for py.test. Dec 07, 2016 4 - Beta N/A + :pypi:`pytest-db-plugin` Nov 27, 2021 N/A pytest (>=5.0) + :pypi:`pytest-dbt` Unit test dbt models with standard python tooling Jun 08, 2023 2 - Pre-Alpha pytest (>=7.0.0,<8.0.0) + :pypi:`pytest-dbt-adapter` A pytest plugin for testing dbt adapter plugins Nov 24, 2021 N/A pytest (<7,>=6) + :pypi:`pytest-dbt-conventions` A pytest plugin for linting a dbt project's conventions Mar 02, 2022 N/A pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-dbt-core` Pytest extension for dbt. Aug 25, 2023 N/A pytest >=6.2.5 ; extra == 'test' + :pypi:`pytest-dbt-postgres` Pytest tooling to unittest DBT & Postgres models Jan 02, 2024 N/A pytest (>=7.4.3,<8.0.0) + :pypi:`pytest-dbus-notification` D-BUS notifications for pytest results. Mar 05, 2014 5 - Production/Stable N/A + :pypi:`pytest-dbx` Pytest plugin to run unit tests for dbx (Databricks CLI extensions) related code Nov 29, 2022 N/A pytest (>=7.1.3,<8.0.0) + :pypi:`pytest-dc` Manages Docker containers during your integration tests Aug 16, 2023 5 - Production/Stable pytest >=3.3 + :pypi:`pytest-deadfixtures` A simple plugin to list unused fixtures in pytest Jul 23, 2020 5 - Production/Stable N/A + :pypi:`pytest-deduplicate` Identifies duplicate unit tests Aug 12, 2023 4 - Beta pytest + :pypi:`pytest-deepcov` deepcov Mar 30, 2021 N/A N/A + :pypi:`pytest-defer` Aug 24, 2021 N/A N/A + :pypi:`pytest-demo-plugin` pytest示例插件 May 15, 2021 N/A N/A + :pypi:`pytest-dependency` Manage dependencies of tests Dec 31, 2023 4 - Beta N/A + :pypi:`pytest-depends` Tests that depend on other tests Apr 05, 2020 5 - Production/Stable pytest (>=3) + :pypi:`pytest-deprecate` Mark tests as testing a deprecated feature with a warning note. Jul 01, 2019 N/A N/A + :pypi:`pytest-describe` Describe-style plugin for pytest Feb 10, 2024 5 - Production/Stable pytest <9,>=4.6 + :pypi:`pytest-describe-it` plugin for rich text descriptions Jul 19, 2019 4 - Beta pytest + :pypi:`pytest-deselect-if` A plugin to deselect pytests tests rather than using skipif Mar 24, 2024 4 - Beta pytest>=6.2.0 + :pypi:`pytest-devpi-server` DevPI server fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-dhos` Common fixtures for pytest in DHOS services and libraries Sep 07, 2022 N/A N/A + :pypi:`pytest-diamond` pytest plugin for diamond Aug 31, 2015 4 - Beta N/A + :pypi:`pytest-dicom` pytest plugin to provide DICOM fixtures Dec 19, 2018 3 - Alpha pytest + :pypi:`pytest-dictsdiff` Jul 26, 2019 N/A N/A + :pypi:`pytest-diff` A simple plugin to use with pytest Mar 30, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-diffeo` A package to prevent Dependency Confusion attacks against Yandex. Feb 20, 2024 N/A N/A + :pypi:`pytest-diff-selector` Get tests affected by code changes (using git) Feb 24, 2022 4 - Beta pytest (>=6.2.2) ; extra == 'all' + :pypi:`pytest-difido` PyTest plugin for generating Difido reports Oct 23, 2022 4 - Beta pytest (>=4.0.0) + :pypi:`pytest-dir-equal` pytest-dir-equals is a pytest plugin providing helpers to assert directories equality allowing golden testing Dec 11, 2023 4 - Beta pytest>=7.3.2 + :pypi:`pytest-disable` pytest plugin to disable a test and skip it from testrun Sep 10, 2015 4 - Beta N/A + :pypi:`pytest-disable-plugin` Disable plugins per test Feb 28, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-discord` A pytest plugin to notify test results to a Discord channel. Oct 18, 2023 4 - Beta pytest !=6.0.0,<8,>=3.3.2 + :pypi:`pytest-discover` Pytest plugin to record discovered tests in a file Mar 26, 2024 N/A pytest + :pypi:`pytest-django` A Django plugin for pytest. Jan 30, 2024 5 - Production/Stable pytest >=7.0.0 + :pypi:`pytest-django-ahead` A Django plugin for pytest. Oct 27, 2016 5 - Production/Stable pytest (>=2.9) + :pypi:`pytest-djangoapp` Nice pytest plugin to help you with Django pluggable application testing. May 19, 2023 4 - Beta pytest + :pypi:`pytest-django-cache-xdist` A djangocachexdist plugin for pytest May 12, 2020 4 - Beta N/A + :pypi:`pytest-django-casperjs` Integrate CasperJS with your django tests as a pytest fixture. Mar 15, 2015 2 - Pre-Alpha N/A + :pypi:`pytest-django-class` A pytest plugin for running django in class-scoped fixtures Aug 08, 2023 4 - Beta N/A + :pypi:`pytest-django-docker-pg` Jan 30, 2024 5 - Production/Stable pytest <8.0.0,>=7.0.0 + :pypi:`pytest-django-dotenv` Pytest plugin used to setup environment variables with django-dotenv Nov 26, 2019 4 - Beta pytest (>=2.6.0) + :pypi:`pytest-django-factories` Factories for your Django models that can be used as Pytest fixtures. Nov 12, 2020 4 - Beta N/A + :pypi:`pytest-django-filefield` Replaces FileField.storage with something you can patch globally. May 09, 2022 5 - Production/Stable pytest >= 5.2 + :pypi:`pytest-django-gcir` A Django plugin for pytest. Mar 06, 2018 5 - Production/Stable N/A + :pypi:`pytest-django-haystack` Cleanup your Haystack indexes between tests Sep 03, 2017 5 - Production/Stable pytest (>=2.3.4) + :pypi:`pytest-django-ifactory` A model instance factory for pytest-django Aug 27, 2023 5 - Production/Stable N/A + :pypi:`pytest-django-lite` The bare minimum to integrate py.test with Django. Jan 30, 2014 N/A N/A + :pypi:`pytest-django-liveserver-ssl` Jan 20, 2022 3 - Alpha N/A + :pypi:`pytest-django-model` A Simple Way to Test your Django Models Feb 14, 2019 4 - Beta N/A + :pypi:`pytest-django-ordering` A pytest plugin for preserving the order in which Django runs tests. Jul 25, 2019 5 - Production/Stable pytest (>=2.3.0) + :pypi:`pytest-django-queries` Generate performance reports from your django database performance tests. Mar 01, 2021 N/A N/A + :pypi:`pytest-djangorestframework` A djangorestframework plugin for pytest Aug 11, 2019 4 - Beta N/A + :pypi:`pytest-django-rq` A pytest plugin to help writing unit test for django-rq Apr 13, 2020 4 - Beta N/A + :pypi:`pytest-django-sqlcounts` py.test plugin for reporting the number of SQLs executed per django testcase. Jun 16, 2015 4 - Beta N/A + :pypi:`pytest-django-testing-postgresql` Use a temporary PostgreSQL database with pytest-django Jan 31, 2022 4 - Beta N/A + :pypi:`pytest-doc` A documentation plugin for py.test. Jun 28, 2015 5 - Production/Stable N/A + :pypi:`pytest-docfiles` pytest plugin to test codeblocks in your documentation. Dec 22, 2021 4 - Beta pytest (>=3.7.0) + :pypi:`pytest-docgen` An RST Documentation Generator for pytest-based test suites Apr 17, 2020 N/A N/A + :pypi:`pytest-docker` Simple pytest fixtures for Docker and Docker Compose based tests Feb 02, 2024 N/A pytest <9.0,>=4.0 + :pypi:`pytest-docker-apache-fixtures` Pytest fixtures for testing with apache2 (httpd). Feb 16, 2022 4 - Beta pytest + :pypi:`pytest-docker-butla` Jun 16, 2019 3 - Alpha N/A + :pypi:`pytest-dockerc` Run, manage and stop Docker Compose project from Docker API Oct 09, 2020 5 - Production/Stable pytest (>=3.0) + :pypi:`pytest-docker-compose` Manages Docker containers during your integration tests Jan 26, 2021 5 - Production/Stable pytest (>=3.3) + :pypi:`pytest-docker-compose-v2` Manages Docker containers during your integration tests Feb 28, 2024 4 - Beta pytest<8,>=7.2.2 + :pypi:`pytest-docker-db` A plugin to use docker databases for pytests Mar 20, 2021 5 - Production/Stable pytest (>=3.1.1) + :pypi:`pytest-docker-fixtures` pytest docker fixtures Apr 03, 2024 3 - Alpha N/A + :pypi:`pytest-docker-git-fixtures` Pytest fixtures for testing with git scm. Feb 09, 2022 4 - Beta pytest + :pypi:`pytest-docker-haproxy-fixtures` Pytest fixtures for testing with haproxy. Feb 09, 2022 4 - Beta pytest + :pypi:`pytest-docker-pexpect` pytest plugin for writing functional tests with pexpect and docker Jan 14, 2019 N/A pytest + :pypi:`pytest-docker-postgresql` A simple plugin to use with pytest Sep 24, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-docker-py` Easy to use, simple to extend, pytest plugin that minimally leverages docker-py. Nov 27, 2018 N/A pytest (==4.0.0) + :pypi:`pytest-docker-registry-fixtures` Pytest fixtures for testing with docker registries. Apr 08, 2022 4 - Beta pytest + :pypi:`pytest-docker-service` pytest plugin to start docker container Jan 03, 2024 3 - Alpha pytest (>=7.1.3) + :pypi:`pytest-docker-squid-fixtures` Pytest fixtures for testing with squid. Feb 09, 2022 4 - Beta pytest + :pypi:`pytest-docker-tools` Docker integration tests for pytest Feb 17, 2022 4 - Beta pytest (>=6.0.1) + :pypi:`pytest-docs` Documentation tool for pytest Nov 11, 2018 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-docstyle` pytest plugin to run pydocstyle Mar 23, 2020 3 - Alpha N/A + :pypi:`pytest-doctest-custom` A py.test plugin for customizing string representations of doctest results. Jul 25, 2016 4 - Beta N/A + :pypi:`pytest-doctest-ellipsis-markers` Setup additional values for ELLIPSIS_MARKER for doctests Jan 12, 2018 4 - Beta N/A + :pypi:`pytest-doctest-import` A simple pytest plugin to import names and add them to the doctest namespace. Nov 13, 2018 4 - Beta pytest (>=3.3.0) + :pypi:`pytest-doctest-mkdocstrings` Run pytest --doctest-modules with markdown docstrings in code blocks (\`\`\`) Mar 02, 2024 N/A pytest + :pypi:`pytest-doctestplus` Pytest plugin with advanced doctest features. Mar 10, 2024 5 - Production/Stable pytest >=4.6 + :pypi:`pytest-dogu-report` pytest plugin for dogu report Jul 07, 2023 N/A N/A + :pypi:`pytest-dogu-sdk` pytest plugin for the Dogu Dec 14, 2023 N/A N/A + :pypi:`pytest-dolphin` Some extra stuff that we use ininternally Nov 30, 2016 4 - Beta pytest (==3.0.4) + :pypi:`pytest-donde` record pytest session characteristics per test item (coverage and duration) into a persistent file and use them in your own plugin or script. Oct 01, 2023 4 - Beta pytest >=7.3.1 + :pypi:`pytest-doorstop` A pytest plugin for adding test results into doorstop items. Jun 09, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-dotenv` A py.test plugin that parses environment files before running tests Jun 16, 2020 4 - Beta pytest (>=5.0.0) + :pypi:`pytest-dot-only-pkcopley` A Pytest marker for only running a single test Oct 27, 2023 N/A N/A + :pypi:`pytest-draw` Pytest plugin for randomly selecting a specific number of tests Mar 21, 2023 3 - Alpha pytest + :pypi:`pytest-drf` A Django REST framework plugin for pytest. Jul 12, 2022 5 - Production/Stable pytest (>=3.7) + :pypi:`pytest-drivings` Tool to allow webdriver automation to be ran locally or remotely Jan 13, 2021 N/A N/A + :pypi:`pytest-drop-dup-tests` A Pytest plugin to drop duplicated tests during collection Mar 04, 2024 5 - Production/Stable pytest >=7 + :pypi:`pytest-dryrun` A Pytest plugin to ignore tests during collection without reporting them in the test summary. Jul 18, 2023 5 - Production/Stable pytest (>=7.4.0,<8.0.0) + :pypi:`pytest-dummynet` A py.test plugin providing access to a dummynet. Dec 15, 2021 5 - Production/Stable pytest + :pypi:`pytest-dump2json` A pytest plugin for dumping test results to json. Jun 29, 2015 N/A N/A + :pypi:`pytest-duration-insights` Jun 25, 2021 N/A N/A + :pypi:`pytest-durations` Pytest plugin reporting fixtures and test functions execution time. Apr 22, 2022 5 - Production/Stable pytest (>=4.6) + :pypi:`pytest-dynamicrerun` A pytest plugin to rerun tests dynamically based off of test outcome and output. Aug 15, 2020 4 - Beta N/A + :pypi:`pytest-dynamodb` DynamoDB fixtures for pytest Mar 12, 2024 5 - Production/Stable pytest + :pypi:`pytest-easy-addoption` pytest-easy-addoption: Easy way to work with pytest addoption Jan 22, 2020 N/A N/A + :pypi:`pytest-easy-api` A package to prevent Dependency Confusion attacks against Yandex. Feb 16, 2024 N/A N/A + :pypi:`pytest-easyMPI` Package that supports mpi tests in pytest Oct 21, 2020 N/A N/A + :pypi:`pytest-easyread` pytest plugin that makes terminal printouts of the reports easier to read Nov 17, 2017 N/A N/A + :pypi:`pytest-easy-server` Pytest plugin for easy testing against servers May 01, 2021 4 - Beta pytest (<5.0.0,>=4.3.1) ; python_version < "3.5" + :pypi:`pytest-ebics-sandbox` A pytest plugin for testing against an EBICS sandbox server. Requires docker. Aug 15, 2022 N/A N/A + :pypi:`pytest-ec2` Pytest execution on EC2 instance Oct 22, 2019 3 - Alpha N/A + :pypi:`pytest-echo` pytest plugin with mechanisms for echoing environment variables, package version and generic attributes Dec 05, 2023 5 - Production/Stable pytest >=2.2 + :pypi:`pytest-ekstazi` Pytest plugin to select test using Ekstazi algorithm Sep 10, 2022 N/A pytest + :pypi:`pytest-elasticsearch` Elasticsearch fixtures and fixture factories for Pytest. Mar 15, 2024 5 - Production/Stable pytest >=7.0 + :pypi:`pytest-elements` Tool to help automate user interfaces Jan 13, 2021 N/A pytest (>=5.4,<6.0) + :pypi:`pytest-eliot` An eliot plugin for pytest. Aug 31, 2022 1 - Planning pytest (>=5.4.0) + :pypi:`pytest-elk-reporter` A simple plugin to use with pytest Apr 04, 2024 4 - Beta pytest>=3.5.0 + :pypi:`pytest-email` Send execution result email Jul 08, 2020 N/A pytest + :pypi:`pytest-embedded` A pytest plugin that designed for embedded testing. Apr 09, 2024 5 - Production/Stable pytest>=7.0 + :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Apr 09, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Apr 09, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Apr 09, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Apr 09, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Apr 09, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Apr 09, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. Apr 09, 2024 5 - Production/Stable N/A + :pypi:`pytest-embrace` 💝 Dataclasses-as-tests. Describe the runtime once and multiply coverage with no boilerplate. Mar 25, 2023 N/A pytest (>=7.0,<8.0) + :pypi:`pytest-emoji` A pytest plugin that adds emojis to your test result report Feb 19, 2019 4 - Beta pytest (>=4.2.1) + :pypi:`pytest-emoji-output` Pytest plugin to represent test output with emoji support Apr 09, 2023 4 - Beta pytest (==7.0.1) + :pypi:`pytest-enabler` Enable installed pytest plugins Mar 21, 2024 5 - Production/Stable pytest>=6; extra == "testing" + :pypi:`pytest-encode` set your encoding and logger Nov 06, 2021 N/A N/A + :pypi:`pytest-encode-kane` set your encoding and logger Nov 16, 2021 N/A pytest + :pypi:`pytest-encoding` set your encoding and logger Aug 11, 2023 N/A pytest + :pypi:`pytest_energy_reporter` An energy estimation reporter for pytest Mar 28, 2024 3 - Alpha pytest<9.0.0,>=8.1.1 + :pypi:`pytest-enhanced-reports` Enhanced test reports for pytest Dec 15, 2022 N/A N/A + :pypi:`pytest-enhancements` Improvements for pytest (rejected upstream) Oct 30, 2019 4 - Beta N/A + :pypi:`pytest-env` pytest plugin that allows you to add environment variables. Nov 28, 2023 5 - Production/Stable pytest>=7.4.3 + :pypi:`pytest-envfiles` A py.test plugin that parses environment files before running tests Oct 08, 2015 3 - Alpha N/A + :pypi:`pytest-env-info` Push information about the running pytest into envvars Nov 25, 2017 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-environment` Pytest Environment Mar 17, 2024 1 - Planning N/A + :pypi:`pytest-envraw` py.test plugin that allows you to add environment variables. Aug 27, 2020 4 - Beta pytest (>=2.6.0) + :pypi:`pytest-envvars` Pytest plugin to validate use of envvars on your tests Jun 13, 2020 5 - Production/Stable pytest (>=3.0.0) + :pypi:`pytest-env-yaml` Apr 02, 2019 N/A N/A + :pypi:`pytest-eradicate` pytest plugin to check for commented out code Sep 08, 2020 N/A pytest (>=2.4.2) + :pypi:`pytest_erp` py.test plugin to send test info to report portal dynamically Jan 13, 2015 N/A N/A + :pypi:`pytest-error-for-skips` Pytest plugin to treat skipped tests a test failure Dec 19, 2019 4 - Beta pytest (>=4.6) + :pypi:`pytest-eth` PyTest plugin for testing Smart Contracts for Ethereum Virtual Machine (EVM). Aug 14, 2020 1 - Planning N/A + :pypi:`pytest-ethereum` pytest-ethereum: Pytest library for ethereum projects. Jun 24, 2019 3 - Alpha pytest (==3.3.2); extra == 'dev' + :pypi:`pytest-eucalyptus` Pytest Plugin for BDD Jun 28, 2022 N/A pytest (>=4.2.0) + :pypi:`pytest-eventlet` Applies eventlet monkey-patch as a pytest plugin. Oct 04, 2021 N/A pytest ; extra == 'dev' + :pypi:`pytest-evm` The testing package containing tools to test Web3-based projects Apr 20, 2024 4 - Beta pytest<9.0.0,>=8.1.1 + :pypi:`pytest_exact_fixtures` Parse queries in Lucene and Elasticsearch syntaxes Feb 04, 2019 N/A N/A + :pypi:`pytest-examples` Pytest plugin for testing examples in docstrings and markdown files. Jul 11, 2023 4 - Beta pytest>=7 + :pypi:`pytest-exasol-itde` Feb 15, 2024 N/A pytest (>=7,<9) + :pypi:`pytest-excel` pytest plugin for generating excel reports Sep 14, 2023 5 - Production/Stable N/A + :pypi:`pytest-exceptional` Better exceptions Mar 16, 2017 4 - Beta N/A + :pypi:`pytest-exception-script` Walk your code through exception script to check it's resiliency to failures. Aug 04, 2020 3 - Alpha pytest + :pypi:`pytest-executable` pytest plugin for testing executables Oct 07, 2023 N/A pytest <8,>=5 + :pypi:`pytest-execution-timer` A timer for the phases of Pytest's execution. Dec 24, 2021 4 - Beta N/A + :pypi:`pytest-exit-code` A pytest plugin that overrides the built-in exit codes to retain more information about the test results. Feb 23, 2024 4 - Beta pytest >=6.2.0 + :pypi:`pytest-expect` py.test plugin to store test expectations and mark tests based on them Apr 21, 2016 4 - Beta N/A + :pypi:`pytest-expectdir` A pytest plugin to provide initial/expected directories, and check a test transforms the initial directory to the expected one Mar 19, 2023 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-expecter` Better testing with expecter and pytest. Sep 18, 2022 5 - Production/Stable N/A + :pypi:`pytest-expectr` This plugin is used to expect multiple assert using pytest framework. Oct 05, 2018 N/A pytest (>=2.4.2) + :pypi:`pytest-expect-test` A fixture to support expect tests in pytest Apr 10, 2023 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-experiments` A pytest plugin to help developers of research-oriented software projects keep track of the results of their numerical experiments. Dec 13, 2021 4 - Beta pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-explicit` A Pytest plugin to ignore certain marked tests by default Jun 15, 2021 5 - Production/Stable pytest + :pypi:`pytest-exploratory` Interactive console for pytest. Aug 18, 2023 N/A pytest (>=6.2) + :pypi:`pytest-explorer` terminal ui for exploring and running tests Aug 01, 2023 N/A N/A + :pypi:`pytest-ext` pytest plugin for automation test Mar 31, 2024 N/A pytest>=5.3 + :pypi:`pytest-extensions` A collection of helpers for pytest to ease testing Aug 17, 2022 4 - Beta pytest ; extra == 'testing' + :pypi:`pytest-external-blockers` a special outcome for tests that are blocked for external reasons Oct 05, 2021 N/A pytest + :pypi:`pytest_extra` Some helpers for writing tests with pytest. Aug 14, 2014 N/A N/A + :pypi:`pytest-extra-durations` A pytest plugin to get durations on a per-function basis and per module basis. Apr 21, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-extra-markers` Additional pytest markers to dynamically enable/disable tests viia CLI flags Mar 05, 2023 4 - Beta pytest + :pypi:`pytest-fabric` Provides test utilities to run fabric task tests by using docker containers Sep 12, 2018 5 - Production/Stable N/A + :pypi:`pytest-factor` A package to prevent Dependency Confusion attacks against Yandex. Feb 20, 2024 N/A N/A + :pypi:`pytest-factory` Use factories for test setup with py.test Sep 06, 2020 3 - Alpha pytest (>4.3) + :pypi:`pytest-factoryboy` Factory Boy support for pytest. Mar 05, 2024 6 - Mature pytest (>=6.2) + :pypi:`pytest-factoryboy-fixtures` Generates pytest fixtures that allow the use of type hinting Jun 25, 2020 N/A N/A + :pypi:`pytest-factoryboy-state` Simple factoryboy random state management Mar 22, 2022 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-failed-screen-record` Create a video of the screen when pytest fails Jan 05, 2023 4 - Beta pytest (>=7.1.2d,<8.0.0) + :pypi:`pytest-failed-screenshot` Test case fails,take a screenshot,save it,attach it to the allure Apr 21, 2021 N/A N/A + :pypi:`pytest-failed-to-verify` A pytest plugin that helps better distinguishing real test failures from setup flakiness. Aug 08, 2019 5 - Production/Stable pytest (>=4.1.0) + :pypi:`pytest-fail-slow` Fail tests that take too long to run Feb 11, 2024 N/A pytest>=7.0 + :pypi:`pytest-faker` Faker integration with the pytest framework. Dec 19, 2016 6 - Mature N/A + :pypi:`pytest-falcon` Pytest helpers for Falcon. Sep 07, 2016 4 - Beta N/A + :pypi:`pytest-falcon-client` A package to prevent Dependency Confusion attacks against Yandex. Feb 21, 2024 N/A N/A + :pypi:`pytest-fantasy` Pytest plugin for Flask Fantasy Framework Mar 14, 2019 N/A N/A + :pypi:`pytest-fastapi` Dec 27, 2020 N/A N/A + :pypi:`pytest-fastapi-deps` A fixture which allows easy replacement of fastapi dependencies for testing Jul 20, 2022 5 - Production/Stable pytest + :pypi:`pytest-fastest` Use SCM and coverage to run only needed tests Oct 04, 2023 4 - Beta pytest (>=4.4) + :pypi:`pytest-fast-first` Pytest plugin that runs fast tests first Jan 19, 2023 3 - Alpha pytest + :pypi:`pytest-faulthandler` py.test plugin that activates the fault handler module for tests (dummy package) Jul 04, 2019 6 - Mature pytest (>=5.0) + :pypi:`pytest-fauxfactory` Integration of fauxfactory into pytest. Dec 06, 2017 5 - Production/Stable pytest (>=3.2) + :pypi:`pytest-figleaf` py.test figleaf coverage plugin Jan 18, 2010 5 - Production/Stable N/A + :pypi:`pytest-file` Pytest File Mar 18, 2024 1 - Planning N/A + :pypi:`pytest-filecov` A pytest plugin to detect unused files Jun 27, 2021 4 - Beta pytest + :pypi:`pytest-filedata` easily load data from files Jan 17, 2019 4 - Beta N/A + :pypi:`pytest-filemarker` A pytest plugin that runs marked tests when files change. Dec 01, 2020 N/A pytest + :pypi:`pytest-file-watcher` Pytest-File-Watcher is a CLI tool that watches for changes in your code and runs pytest on the changed files. Mar 23, 2023 N/A pytest + :pypi:`pytest-filter-case` run test cases filter by mark Nov 05, 2020 N/A N/A + :pypi:`pytest-filter-subpackage` Pytest plugin for filtering based on sub-packages Mar 04, 2024 5 - Production/Stable pytest >=4.6 + :pypi:`pytest-find-dependencies` A pytest plugin to find dependencies between tests Mar 16, 2024 4 - Beta pytest >=4.3.0 + :pypi:`pytest-finer-verdicts` A pytest plugin to treat non-assertion failures as test errors. Jun 18, 2020 N/A pytest (>=5.4.3) + :pypi:`pytest-firefox` pytest plugin to manipulate firefox Aug 08, 2017 3 - Alpha pytest (>=3.0.2) + :pypi:`pytest-fixture-classes` Fixtures as classes that work well with dependency injection, autocompletetion, type checkers, and language servers Sep 02, 2023 5 - Production/Stable pytest + :pypi:`pytest-fixturecollection` A pytest plugin to collect tests based on fixtures being used by tests Feb 22, 2024 4 - Beta pytest >=3.5.0 + :pypi:`pytest-fixture-config` Fixture configuration utils for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-fixture-maker` Pytest plugin to load fixtures from YAML files Sep 21, 2021 N/A N/A + :pypi:`pytest-fixture-marker` A pytest plugin to add markers based on fixtures used. Oct 11, 2020 5 - Production/Stable N/A + :pypi:`pytest-fixture-order` pytest plugin to control fixture evaluation order May 16, 2022 5 - Production/Stable pytest (>=3.0) + :pypi:`pytest-fixture-ref` Lets users reference fixtures without name matching magic. Nov 17, 2022 4 - Beta N/A + :pypi:`pytest-fixture-remover` A LibCST codemod to remove pytest fixtures applied via the usefixtures decorator, as well as its parametrizations. Feb 14, 2024 5 - Production/Stable N/A + :pypi:`pytest-fixture-rtttg` Warn or fail on fixture name clash Feb 23, 2022 N/A pytest (>=7.0.1,<8.0.0) + :pypi:`pytest-fixtures` Common fixtures for pytest May 01, 2019 5 - Production/Stable N/A + :pypi:`pytest-fixture-tools` Plugin for pytest which provides tools for fixtures Aug 18, 2020 6 - Mature pytest + :pypi:`pytest-fixture-typecheck` A pytest plugin to assert type annotations at runtime. Aug 24, 2021 N/A pytest + :pypi:`pytest-flake8` pytest plugin to check FLAKE8 requirements Mar 18, 2022 4 - Beta pytest (>=7.0) + :pypi:`pytest-flake8-path` A pytest fixture for testing flake8 plugins. Jul 10, 2023 5 - Production/Stable pytest + :pypi:`pytest-flake8-v2` pytest plugin to check FLAKE8 requirements Mar 01, 2022 5 - Production/Stable pytest (>=7.0) + :pypi:`pytest-flakefinder` Runs tests multiple times to expose flakiness. Oct 26, 2022 4 - Beta pytest (>=2.7.1) + :pypi:`pytest-flakes` pytest plugin to check source code with pyflakes Dec 02, 2021 5 - Production/Stable pytest (>=5) + :pypi:`pytest-flaptastic` Flaptastic py.test plugin Mar 17, 2019 N/A N/A + :pypi:`pytest-flask` A set of py.test fixtures to test Flask applications. Oct 23, 2023 5 - Production/Stable pytest >=5.2 + :pypi:`pytest-flask-ligand` Pytest fixtures and helper functions to use for testing flask-ligand microservices. Apr 25, 2023 4 - Beta pytest (~=7.3) + :pypi:`pytest-flask-sqlalchemy` A pytest plugin for preserving test isolation in Flask-SQlAlchemy using database transactions. Apr 30, 2022 4 - Beta pytest (>=3.2.1) + :pypi:`pytest-flask-sqlalchemy-transactions` Run tests in transactions using pytest, Flask, and SQLalchemy. Aug 02, 2018 4 - Beta pytest (>=3.2.1) + :pypi:`pytest-flexreport` Apr 15, 2023 4 - Beta pytest + :pypi:`pytest-fluent` A pytest plugin in order to provide logs via fluentd Jun 26, 2023 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-fluentbit` A pytest plugin in order to provide logs via fluentbit Jun 16, 2023 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-fly` pytest observer Apr 14, 2024 3 - Alpha pytest + :pypi:`pytest-flyte` Pytest fixtures for simplifying Flyte integration testing May 03, 2021 N/A pytest + :pypi:`pytest-focus` A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest + :pypi:`pytest-forbid` Mar 07, 2023 N/A pytest (>=7.2.2,<8.0.0) + :pypi:`pytest-forcefail` py.test plugin to make the test failing regardless of pytest.mark.xfail May 15, 2018 4 - Beta N/A + :pypi:`pytest-forks` Fork helper for pytest Mar 05, 2024 N/A N/A + :pypi:`pytest-forward-compatability` A name to avoid typosquating pytest-foward-compatibility Sep 06, 2020 N/A N/A + :pypi:`pytest-forward-compatibility` A pytest plugin to shim pytest commandline options for fowards compatibility Sep 29, 2020 N/A N/A + :pypi:`pytest-frappe` Pytest Frappe Plugin - A set of pytest fixtures to test Frappe applications Oct 29, 2023 4 - Beta pytest>=7.0.0 + :pypi:`pytest-freezegun` Wrap tests with fixtures in freeze_time Jul 19, 2020 4 - Beta pytest (>=3.0.0) + :pypi:`pytest-freezer` Pytest plugin providing a fixture interface for spulec/freezegun Jun 21, 2023 N/A pytest >= 3.6 + :pypi:`pytest-freeze-reqs` Check if requirement files are frozen Apr 29, 2021 N/A N/A + :pypi:`pytest-frozen-uuids` Deterministically frozen UUID's for your tests Apr 17, 2022 N/A pytest (>=3.0) + :pypi:`pytest-func-cov` Pytest plugin for measuring function coverage Apr 15, 2021 3 - Alpha pytest (>=5) + :pypi:`pytest-funparam` An alternative way to parametrize test cases. Dec 02, 2021 4 - Beta pytest >=4.6.0 + :pypi:`pytest-fxa` pytest plugin for Firefox Accounts Aug 28, 2018 5 - Production/Stable N/A + :pypi:`pytest-fxtest` Oct 27, 2020 N/A N/A + :pypi:`pytest-fzf` fzf-based test selector for pytest Feb 07, 2024 4 - Beta pytest >=6.0.0 + :pypi:`pytest_gae` pytest plugin for apps written with Google's AppEngine Aug 03, 2016 3 - Alpha N/A + :pypi:`pytest-gather-fixtures` set up asynchronous pytest fixtures concurrently Apr 12, 2022 N/A pytest (>=6.0.0) + :pypi:`pytest-gc` The garbage collector plugin for py.test Feb 01, 2018 N/A N/A + :pypi:`pytest-gcov` Uses gcov to measure test coverage of a C library Feb 01, 2018 3 - Alpha N/A + :pypi:`pytest-gcs` GCS fixtures and fixture factories for Pytest. Mar 01, 2024 5 - Production/Stable pytest >=6.2 + :pypi:`pytest-gee` The Python plugin for your GEE based packages. Feb 15, 2024 3 - Alpha pytest + :pypi:`pytest-gevent` Ensure that gevent is properly patched when invoking pytest Feb 25, 2020 N/A pytest + :pypi:`pytest-gherkin` A flexible framework for executing BDD gherkin tests Jul 27, 2019 3 - Alpha pytest (>=5.0.0) + :pypi:`pytest-gh-log-group` pytest plugin for gh actions Jan 11, 2022 3 - Alpha pytest + :pypi:`pytest-ghostinspector` For finding/executing Ghost Inspector tests May 17, 2016 3 - Alpha N/A + :pypi:`pytest-girder` A set of pytest fixtures for testing Girder applications. Apr 12, 2024 N/A pytest>=3.6 + :pypi:`pytest-git` Git repository fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-gitconfig` Provide a gitconfig sandbox for testing Oct 15, 2023 4 - Beta pytest>=7.1.2 + :pypi:`pytest-gitcov` Pytest plugin for reporting on coverage of the last git commit. Jan 11, 2020 2 - Pre-Alpha N/A + :pypi:`pytest-git-diff` Pytest plugin that allows the user to select the tests affected by a range of git commits Apr 02, 2024 N/A N/A + :pypi:`pytest-git-fixtures` Pytest fixtures for testing with git. Mar 11, 2021 4 - Beta pytest + :pypi:`pytest-github` Plugin for py.test that associates tests with github issues using a marker. Mar 07, 2019 5 - Production/Stable N/A + :pypi:`pytest-github-actions-annotate-failures` pytest plugin to annotate failed tests with a workflow command for GitHub Actions May 04, 2023 5 - Production/Stable pytest (>=4.0.0) + :pypi:`pytest-github-report` Generate a GitHub report using pytest in GitHub Workflows Jun 03, 2022 4 - Beta N/A + :pypi:`pytest-gitignore` py.test plugin to ignore the same files as git Jul 17, 2015 4 - Beta N/A + :pypi:`pytest-gitlabci-parallelized` Parallelize pytest across GitLab CI workers. Mar 08, 2023 N/A N/A + :pypi:`pytest-gitlab-code-quality` Collects warnings while testing and generates a GitLab Code Quality Report. Apr 03, 2024 N/A pytest>=8.1.1 + :pypi:`pytest-gitlab-fold` Folds output sections in GitLab CI build log Dec 31, 2023 4 - Beta pytest >=2.6.0 + :pypi:`pytest-git-selector` Utility to select tests that have had its dependencies modified (as identified by git diff) Nov 17, 2022 N/A N/A + :pypi:`pytest-glamor-allure` Extends allure-pytest functionality Jul 22, 2022 4 - Beta pytest + :pypi:`pytest-gnupg-fixtures` Pytest fixtures for testing with gnupg. Mar 04, 2021 4 - Beta pytest + :pypi:`pytest-golden` Plugin for pytest that offloads expected outputs to data files Jul 18, 2022 N/A pytest (>=6.1.2) + :pypi:`pytest-goldie` A plugin to support golden tests with pytest. May 23, 2023 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-google-chat` Notify google chat channel for test results Mar 27, 2022 4 - Beta pytest + :pypi:`pytest-graphql-schema` Get graphql schema as fixture for pytest Oct 18, 2019 N/A N/A + :pypi:`pytest-greendots` Green progress dots Feb 08, 2014 3 - Alpha N/A + :pypi:`pytest-group-by-class` A Pytest plugin for running a subset of your tests by splitting them in to groups of classes. Jun 27, 2023 5 - Production/Stable pytest (>=2.5) + :pypi:`pytest-growl` Growl notifications for pytest results. Jan 13, 2014 5 - Production/Stable N/A + :pypi:`pytest-grpc` pytest plugin for grpc May 01, 2020 N/A pytest (>=3.6.0) + :pypi:`pytest-grunnur` Py.Test plugin for Grunnur-based packages. Feb 05, 2023 N/A N/A + :pypi:`pytest_gui_status` Show pytest status in gui Jan 23, 2016 N/A pytest + :pypi:`pytest-hammertime` Display "🔨 " instead of "." for passed pytest tests. Jul 28, 2018 N/A pytest + :pypi:`pytest-hardware-test-report` A simple plugin to use with pytest Apr 01, 2024 4 - Beta pytest<9.0.0,>=8.0.0 + :pypi:`pytest-harmony` Chain tests and data with pytest Jan 17, 2023 N/A pytest (>=7.2.1,<8.0.0) + :pypi:`pytest-harvest` Store data created during your pytest tests execution, and retrieve it at the end of the session, e.g. for applicative benchmarking purposes. Mar 16, 2024 5 - Production/Stable N/A + :pypi:`pytest-helm-chart` A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. Jun 15, 2020 4 - Beta pytest (>=5.4.2,<6.0.0) + :pypi:`pytest-helm-charts` A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. Feb 07, 2024 4 - Beta pytest (>=8.0.0,<9.0.0) + :pypi:`pytest-helm-templates` Pytest fixtures for unit testing the output of helm templates Apr 05, 2024 N/A pytest~=7.4.0; extra == "dev" + :pypi:`pytest-helper` Functions to help in using the pytest testing framework May 31, 2019 5 - Production/Stable N/A + :pypi:`pytest-helpers` pytest helpers May 17, 2020 N/A pytest + :pypi:`pytest-helpers-namespace` Pytest Helpers Namespace Plugin Dec 29, 2021 5 - Production/Stable pytest (>=6.0.0) + :pypi:`pytest-henry` Aug 29, 2023 N/A N/A + :pypi:`pytest-hidecaptured` Hide captured output May 04, 2018 4 - Beta pytest (>=2.8.5) + :pypi:`pytest-himark` A plugin that will filter pytest's test collection using a json file. It will read a json file provided with a --json argument in pytest command line (or in pytest.ini), search the markers key and automatically add -m option to the command line for filtering out the tests marked with disabled markers. Apr 14, 2024 4 - Beta pytest>=6.2.0 + :pypi:`pytest-historic` Custom report to display pytest historical execution records Apr 08, 2020 N/A pytest + :pypi:`pytest-historic-hook` Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report Apr 08, 2020 N/A pytest + :pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Jan 14, 2024 N/A pytest (>=7.4.3,<8.0.0) + :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable pytest + :pypi:`pytest-homeassistant` A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Apr 13, 2024 3 - Alpha pytest==8.1.1 + :pypi:`pytest-honey` A simple plugin to use with pytest Jan 07, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-honors` Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A + :pypi:`pytest-hot-reloading` Apr 18, 2024 N/A N/A + :pypi:`pytest-hot-test` A plugin that tracks test changes Dec 10, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-houdini` pytest plugin for testing code in Houdini. Feb 09, 2024 N/A pytest + :pypi:`pytest-hoverfly` Simplify working with Hoverfly from pytest Jan 30, 2023 N/A pytest (>=5.0) + :pypi:`pytest-hoverfly-wrapper` Integrates the Hoverfly HTTP proxy into Pytest Feb 27, 2023 5 - Production/Stable pytest (>=3.7.0) + :pypi:`pytest-hpfeeds` Helpers for testing hpfeeds in your python project Feb 28, 2023 4 - Beta pytest (>=6.2.4,<7.0.0) + :pypi:`pytest-html` pytest plugin for generating HTML reports Nov 07, 2023 5 - Production/Stable pytest>=7.0.0 + :pypi:`pytest-html-cn` pytest plugin for generating HTML reports Aug 01, 2023 5 - Production/Stable N/A + :pypi:`pytest-html-lee` optimized pytest plugin for generating HTML reports Jun 30, 2020 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-html-merger` Pytest HTML reports merging utility Nov 11, 2023 N/A N/A + :pypi:`pytest-html-object-storage` Pytest report plugin for send HTML report on object-storage Jan 17, 2024 5 - Production/Stable N/A + :pypi:`pytest-html-profiling` Pytest plugin for generating HTML reports with per-test profiling and optionally call graph visualizations. Based on pytest-html by Dave Hunt. Feb 11, 2020 5 - Production/Stable pytest (>=3.0) + :pypi:`pytest-html-reporter` Generates a static html report based on pytest framework Feb 13, 2022 N/A N/A + :pypi:`pytest-html-report-merger` Oct 23, 2023 N/A N/A + :pypi:`pytest-html-thread` pytest plugin for generating HTML reports Dec 29, 2020 5 - Production/Stable N/A + :pypi:`pytest-http` Fixture "http" for http requests Dec 05, 2019 N/A N/A + :pypi:`pytest-httpbin` Easily test your HTTP library against a local copy of httpbin May 08, 2023 5 - Production/Stable pytest ; extra == 'test' + :pypi:`pytest-httpdbg` A pytest plugin to record HTTP(S) requests with stack trace Jan 10, 2024 3 - Alpha pytest >=7.0.0 + :pypi:`pytest-http-mocker` Pytest plugin for http mocking (via https://github.com/vilus/mocker) Oct 20, 2019 N/A N/A + :pypi:`pytest-httpretty` A thin wrapper of HTTPretty for pytest Feb 16, 2014 3 - Alpha N/A + :pypi:`pytest_httpserver` pytest-httpserver is a httpserver for pytest Feb 24, 2024 3 - Alpha N/A + :pypi:`pytest-httptesting` http_testing framework on top of pytest Jul 24, 2023 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-httpx` Send responses to httpx. Feb 21, 2024 5 - Production/Stable pytest <9,>=7 + :pypi:`pytest-httpx-blockage` Disable httpx requests during a test run Feb 16, 2023 N/A pytest (>=7.2.1) + :pypi:`pytest-httpx-recorder` Recorder feature based on pytest_httpx, like recorder feature in responses. Jan 04, 2024 5 - Production/Stable pytest + :pypi:`pytest-hue` Visualise PyTest status via your Phillips Hue lights May 09, 2019 N/A N/A + :pypi:`pytest-hylang` Pytest plugin to allow running tests written in hylang Mar 28, 2021 N/A pytest + :pypi:`pytest-hypo-25` help hypo module for pytest Jan 12, 2020 3 - Alpha N/A + :pypi:`pytest-iam` A fully functional OAUTH2 / OpenID Connect (OIDC) server to be used in your testsuite Apr 12, 2024 3 - Alpha pytest>=7.0.0 + :pypi:`pytest-ibutsu` A plugin to sent pytest results to an Ibutsu server Aug 05, 2022 4 - Beta pytest>=7.1 + :pypi:`pytest-icdiff` use icdiff for better error messages in pytest assertions Dec 05, 2023 4 - Beta pytest + :pypi:`pytest-idapro` A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api Nov 03, 2018 N/A N/A + :pypi:`pytest-idem` A pytest plugin to help with testing idem projects Dec 13, 2023 5 - Production/Stable N/A + :pypi:`pytest-idempotent` Pytest plugin for testing function idempotence. Jul 25, 2022 N/A N/A + :pypi:`pytest-ignore-flaky` ignore failures from flaky tests (pytest plugin) Apr 08, 2024 5 - Production/Stable pytest>=6.0 + :pypi:`pytest-ignore-test-results` A pytest plugin to ignore test results. Aug 17, 2023 2 - Pre-Alpha pytest>=7.0 + :pypi:`pytest-image-diff` Mar 09, 2023 3 - Alpha pytest + :pypi:`pytest-image-snapshot` A pytest plugin for image snapshot management and comparison. Dec 01, 2023 4 - Beta pytest >=3.5.0 + :pypi:`pytest-incremental` an incremental test runner (pytest plugin) Apr 24, 2021 5 - Production/Stable N/A + :pypi:`pytest-influxdb` Plugin for influxdb and pytest integration. Apr 20, 2021 N/A N/A + :pypi:`pytest-info-collector` pytest plugin to collect information from tests May 26, 2019 3 - Alpha N/A + :pypi:`pytest-info-plugin` Get executed interface information in pytest interface automation framework Sep 14, 2023 N/A N/A + :pypi:`pytest-informative-node` display more node ininformation. Apr 25, 2019 4 - Beta N/A + :pypi:`pytest-infrastructure` pytest stack validation prior to testing executing Apr 12, 2020 4 - Beta N/A + :pypi:`pytest-ini` Reuse pytest.ini to store env variables Apr 26, 2022 N/A N/A + :pypi:`pytest-initry` Plugin for sending automation test data from Pytest to the initry Apr 14, 2024 N/A pytest<9.0.0,>=8.1.1 + :pypi:`pytest-inline` A pytest plugin for writing inline tests. Oct 19, 2023 4 - Beta pytest >=7.0.0 + :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Dec 13, 2023 5 - Production/Stable pytest + :pypi:`pytest-inmanta-extensions` Inmanta tests package Apr 02, 2024 5 - Production/Stable N/A + :pypi:`pytest-inmanta-lsm` Common fixtures for inmanta LSM related modules Apr 15, 2024 5 - Production/Stable N/A + :pypi:`pytest-inmanta-yang` Common fixtures used in inmanta yang related modules Feb 22, 2024 4 - Beta pytest + :pypi:`pytest-Inomaly` A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A + :pypi:`pytest-in-robotframework` The extension enables easy execution of pytest tests within the Robot Framework environment. Mar 02, 2024 N/A pytest + :pypi:`pytest-insper` Pytest plugin for courses at Insper Mar 21, 2024 N/A pytest + :pypi:`pytest-insta` A practical snapshot testing plugin for pytest Feb 19, 2024 N/A pytest (>=7.2.0,<9.0.0) + :pypi:`pytest-instafail` pytest plugin to show failures instantly Mar 31, 2023 4 - Beta pytest (>=5) + :pypi:`pytest-instrument` pytest plugin to instrument tests Apr 05, 2020 5 - Production/Stable pytest (>=5.1.0) + :pypi:`pytest-integration` Organizing pytests by integration or not Nov 17, 2022 N/A N/A + :pypi:`pytest-integration-mark` Automatic integration test marking and excluding plugin for pytest May 22, 2023 N/A pytest (>=5.2) + :pypi:`pytest-interactive` A pytest plugin for console based interactive test selection just after the collection phase Nov 30, 2017 3 - Alpha N/A + :pypi:`pytest-intercept-remote` Pytest plugin for intercepting outgoing connection requests during pytest run. May 24, 2021 4 - Beta pytest (>=4.6) + :pypi:`pytest-interface-tester` Pytest plugin for checking charm relation interface protocol compliance. Feb 09, 2024 4 - Beta pytest + :pypi:`pytest-invenio` Pytest fixtures for Invenio. Feb 28, 2024 5 - Production/Stable pytest <7.2.0,>=6 + :pypi:`pytest-involve` Run tests covering a specific file or changeset Feb 02, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-ipdb` A py.test plug-in to enable drop to ipdb debugger on test failure. Mar 20, 2013 2 - Pre-Alpha N/A + :pypi:`pytest-ipynb` THIS PROJECT IS ABANDONED Jan 29, 2019 3 - Alpha N/A + :pypi:`pytest-ipywidgets` Apr 08, 2024 N/A pytest + :pypi:`pytest-isolate` Feb 20, 2023 4 - Beta pytest + :pypi:`pytest-isort` py.test plugin to check import ordering using isort Mar 05, 2024 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-it` Pytest plugin to display test reports as a plaintext spec, inspired by Rspec: https://github.com/mattduck/pytest-it. Jan 29, 2024 4 - Beta N/A + :pypi:`pytest-iterassert` Nicer list and iterable assertion messages for pytest May 11, 2020 3 - Alpha N/A + :pypi:`pytest-iters` A contextmanager pytest fixture for handling multiple mock iters May 24, 2022 N/A N/A + :pypi:`pytest_jar_yuan` A allure and pytest used package Dec 12, 2022 N/A N/A + :pypi:`pytest-jasmine` Run jasmine tests from your pytest test suite Nov 04, 2017 1 - Planning N/A + :pypi:`pytest-jelastic` Pytest plugin defining the necessary command-line options to pass to pytests testing a Jelastic environment. Nov 16, 2022 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-jest` A custom jest-pytest oriented Pytest reporter May 22, 2018 4 - Beta pytest (>=3.3.2) + :pypi:`pytest-jinja` A plugin to generate customizable jinja-based HTML reports in pytest Oct 04, 2022 3 - Alpha pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-jira` py.test JIRA integration plugin, using markers Apr 12, 2024 3 - Alpha N/A + :pypi:`pytest-jira-xfail` Plugin skips (xfail) tests if unresolved Jira issue(s) linked Jun 19, 2023 N/A pytest (>=7.2.0) + :pypi:`pytest-jira-xray` pytest plugin to integrate tests with JIRA XRAY Mar 27, 2024 4 - Beta pytest>=6.2.4 + :pypi:`pytest-job-selection` A pytest plugin for load balancing test suites Jan 30, 2023 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-jobserver` Limit parallel tests with posix jobserver. May 15, 2019 5 - Production/Stable pytest + :pypi:`pytest-joke` Test failures are better served with humor. Oct 08, 2019 4 - Beta pytest (>=4.2.1) + :pypi:`pytest-json` Generate JSON test reports Jan 18, 2016 4 - Beta N/A + :pypi:`pytest-json-fixtures` JSON output for the --fixtures flag Mar 14, 2023 4 - Beta N/A + :pypi:`pytest-jsonlint` UNKNOWN Aug 04, 2016 N/A N/A + :pypi:`pytest-json-report` A pytest plugin to report test results as JSON files Mar 15, 2022 4 - Beta pytest (>=3.8.0) + :pypi:`pytest-json-report-wip` A pytest plugin to report test results as JSON files Oct 28, 2023 4 - Beta pytest >=3.8.0 + :pypi:`pytest-jsonschema` A pytest plugin to perform JSONSchema validations Mar 27, 2024 4 - Beta pytest>=6.2.0 + :pypi:`pytest-jtr` pytest plugin supporting json test report output Apr 15, 2024 N/A pytest<8.0.0,>=7.1.2 + :pypi:`pytest-jupyter` A pytest plugin for testing Jupyter libraries and extensions. Apr 04, 2024 4 - Beta pytest>=7.0 + :pypi:`pytest-jupyterhub` A reusable JupyterHub pytest plugin Apr 25, 2023 5 - Production/Stable pytest + :pypi:`pytest-kafka` Zookeeper, Kafka server, and Kafka consumer fixtures for Pytest Jun 14, 2023 N/A pytest + :pypi:`pytest-kafkavents` A plugin to send pytest events to Kafka Sep 08, 2021 4 - Beta pytest + :pypi:`pytest-kasima` Display horizontal lines above and below the captured standard output for easy viewing. Jan 26, 2023 5 - Production/Stable pytest (>=7.2.1,<8.0.0) + :pypi:`pytest-keep-together` Pytest plugin to customize test ordering by running all 'related' tests together Dec 07, 2022 5 - Production/Stable pytest + :pypi:`pytest-kexi` Apr 29, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-keyring` A Pytest plugin to access the system's keyring to provide credentials for tests Oct 01, 2023 N/A pytest (>=7.1) + :pypi:`pytest-kind` Kubernetes test support with KIND for pytest Nov 30, 2022 5 - Production/Stable N/A + :pypi:`pytest-kivy` Kivy GUI tests fixtures using pytest Jul 06, 2021 4 - Beta pytest (>=3.6) + :pypi:`pytest-knows` A pytest plugin that can automaticly skip test case based on dependence info calculated by trace Aug 22, 2014 N/A N/A + :pypi:`pytest-konira` Run Konira DSL tests with py.test Oct 09, 2011 N/A N/A + :pypi:`pytest-koopmans` A plugin for testing the koopmans package Nov 21, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-krtech-common` pytest krtech common library Nov 28, 2016 4 - Beta N/A + :pypi:`pytest-kubernetes` Sep 14, 2023 N/A pytest (>=7.2.1,<8.0.0) + :pypi:`pytest-kuunda` pytest plugin to help with test data setup for PySpark tests Feb 25, 2024 4 - Beta pytest >=6.2.0 + :pypi:`pytest-kwparametrize` Alternate syntax for @pytest.mark.parametrize with test cases as dictionaries and default value fallbacks Jan 22, 2021 N/A pytest (>=6) + :pypi:`pytest-lambda` Define pytest fixtures with lambda functions. Aug 20, 2022 3 - Alpha pytest (>=3.6,<8) + :pypi:`pytest-lamp` Jan 06, 2017 3 - Alpha N/A + :pypi:`pytest-langchain` Pytest-style test runner for langchain agents Feb 26, 2023 N/A pytest + :pypi:`pytest-lark` Create fancy and clear HTML test reports. Nov 05, 2023 N/A N/A + :pypi:`pytest-launchable` Launchable Pytest Plugin Apr 05, 2023 N/A pytest (>=4.2.0) + :pypi:`pytest-layab` Pytest fixtures for layab. Oct 05, 2020 5 - Production/Stable N/A + :pypi:`pytest-lazy-fixture` It helps to use fixtures in pytest.mark.parametrize Feb 01, 2020 4 - Beta pytest (>=3.2.5) + :pypi:`pytest-lazy-fixtures` Allows you to use fixtures in @pytest.mark.parametrize. Mar 16, 2024 N/A pytest (>=7) + :pypi:`pytest-ldap` python-ldap fixtures for pytest Aug 18, 2020 N/A pytest + :pypi:`pytest-leak-finder` Find the test that's leaking before the one that fails Feb 15, 2023 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-leaks` A pytest plugin to trace resource leaks. Nov 27, 2019 1 - Planning N/A + :pypi:`pytest-leaping` A simple plugin to use with pytest Mar 27, 2024 4 - Beta pytest>=6.2.0 + :pypi:`pytest-level` Select tests of a given level or lower Oct 21, 2019 N/A pytest + :pypi:`pytest-libfaketime` A python-libfaketime plugin for pytest Apr 12, 2024 4 - Beta pytest>=3.0.0 + :pypi:`pytest-libiio` A pytest plugin to manage interfacing with libiio contexts Dec 22, 2023 4 - Beta N/A + :pypi:`pytest-libnotify` Pytest plugin that shows notifications about the test run Apr 02, 2021 3 - Alpha pytest + :pypi:`pytest-ligo` Jan 16, 2020 4 - Beta N/A + :pypi:`pytest-lineno` A pytest plugin to show the line numbers of test functions Dec 04, 2020 N/A pytest + :pypi:`pytest-line-profiler` Profile code executed by pytest Aug 10, 2023 4 - Beta pytest >=3.5.0 + :pypi:`pytest-line-profiler-apn` Profile code executed by pytest Dec 05, 2022 N/A pytest (>=3.5.0) + :pypi:`pytest-lisa` Pytest plugin for organizing tests. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) + :pypi:`pytest-listener` A simple network listener May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-litf` A pytest plugin that stream output in LITF format Jan 18, 2021 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-litter` Pytest plugin which verifies that tests do not modify file trees. Nov 23, 2023 4 - Beta pytest >=6.1 + :pypi:`pytest-live` Live results for pytest Mar 08, 2020 N/A pytest + :pypi:`pytest-local-badge` Generate local badges (shields) reporting your test suite status. Jan 15, 2023 N/A pytest (>=6.1.0) + :pypi:`pytest-localftpserver` A PyTest plugin which provides an FTP fixture for your tests Oct 14, 2023 5 - Production/Stable pytest + :pypi:`pytest-localserver` pytest plugin to test server connections locally. Oct 12, 2023 4 - Beta N/A + :pypi:`pytest-localstack` Pytest plugin for AWS integration tests Jun 07, 2023 4 - Beta pytest (>=6.0.0,<7.0.0) + :pypi:`pytest-lock` pytest-lock is a pytest plugin that allows you to "lock" the results of unit tests, storing them in a local cache. This is particularly useful for tests that are resource-intensive or don't need to be run every time. When the tests are run subsequently, pytest-lock will compare the current results with the locked results and issue a warning if there are any discrepancies. Feb 03, 2024 N/A pytest (>=7.4.3,<8.0.0) + :pypi:`pytest-lockable` lockable resource plugin for pytest Jan 24, 2024 5 - Production/Stable pytest + :pypi:`pytest-locker` Used to lock object during testing. Essentially changing assertions from being hard coded to asserting that nothing changed Oct 29, 2021 N/A pytest (>=5.4) + :pypi:`pytest-log` print log Aug 15, 2021 N/A pytest (>=3.8) + :pypi:`pytest-logbook` py.test plugin to capture logbook log messages Nov 23, 2015 5 - Production/Stable pytest (>=2.8) + :pypi:`pytest-logdog` Pytest plugin to test logging Jun 15, 2021 1 - Planning pytest (>=6.2.0) + :pypi:`pytest-logfest` Pytest plugin providing three logger fixtures with basic or full writing to log files Jul 21, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-logger` Plugin configuring handlers for loggers from Python logging module. Mar 10, 2024 5 - Production/Stable pytest (>=3.2) + :pypi:`pytest-logging` Configures logging and allows tweaking the log level with a py.test flag Nov 04, 2015 4 - Beta N/A + :pypi:`pytest-logging-end-to-end-test-tool` Sep 23, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-logikal` Common testing environment Mar 30, 2024 5 - Production/Stable pytest==8.1.1 + :pypi:`pytest-log-report` Package for creating a pytest test run reprot Dec 26, 2019 N/A N/A + :pypi:`pytest-loguru` Pytest Loguru Mar 20, 2024 5 - Production/Stable pytest; extra == "test" + :pypi:`pytest-loop` pytest plugin for looping tests Mar 30, 2024 5 - Production/Stable pytest + :pypi:`pytest-lsp` A pytest plugin for end-to-end testing of language servers Feb 07, 2024 3 - Alpha pytest + :pypi:`pytest-manual-marker` pytest marker for marking manual tests Aug 04, 2022 3 - Alpha pytest>=7 + :pypi:`pytest-markdoctest` A pytest plugin to doctest your markdown files Jul 22, 2022 4 - Beta pytest (>=6) + :pypi:`pytest-markdown` Test your markdown docs with pytest Jan 15, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) + :pypi:`pytest-markdown-docs` Run markdown code fences through pytest Mar 05, 2024 N/A pytest (>=7.0.0) + :pypi:`pytest-marker-bugzilla` py.test bugzilla integration plugin, using markers Jan 09, 2020 N/A N/A + :pypi:`pytest-markers-presence` A simple plugin to detect missed pytest tags and markers" Feb 04, 2021 4 - Beta pytest (>=6.0) + :pypi:`pytest-markfiltration` UNKNOWN Nov 08, 2011 3 - Alpha N/A + :pypi:`pytest-mark-no-py3` pytest plugin and bowler codemod to help migrate tests to Python 3 May 17, 2019 N/A pytest + :pypi:`pytest-marks` UNKNOWN Nov 23, 2012 3 - Alpha N/A + :pypi:`pytest-matcher` Easy way to match captured \`pytest\` output against expectations stored in files Mar 15, 2024 5 - Production/Stable pytest + :pypi:`pytest-match-skip` Skip matching marks. Matches partial marks using wildcards. May 15, 2019 4 - Beta pytest (>=4.4.1) + :pypi:`pytest-mat-report` this is report Jan 20, 2021 N/A N/A + :pypi:`pytest-matrix` Provide tools for generating tests from combinations of fixtures. Jun 24, 2020 5 - Production/Stable pytest (>=5.4.3,<6.0.0) + :pypi:`pytest-maxcov` Compute the maximum coverage available through pytest with the minimum execution time cost Sep 24, 2023 N/A pytest (>=7.4.0,<8.0.0) + :pypi:`pytest-maybe-context` Simplify tests with warning and exception cases. Apr 16, 2023 N/A pytest (>=7,<8) + :pypi:`pytest-maybe-raises` Pytest fixture for optional exception testing. May 27, 2022 N/A pytest ; extra == 'dev' + :pypi:`pytest-mccabe` pytest plugin to run the mccabe code complexity checker. Jul 22, 2020 3 - Alpha pytest (>=5.4.0) + :pypi:`pytest-md` Plugin for generating Markdown reports for pytest results Jul 11, 2019 3 - Alpha pytest (>=4.2.1) + :pypi:`pytest-md-report` A pytest plugin to make a test results report with Markdown table format. Feb 04, 2024 4 - Beta pytest !=6.0.0,<9,>=3.3.2 + :pypi:`pytest-meilisearch` Pytest helpers for testing projects using Meilisearch Feb 15, 2024 N/A pytest (>=7.4.3) + :pypi:`pytest-memlog` Log memory usage during tests May 03, 2023 N/A pytest (>=7.3.0,<8.0.0) + :pypi:`pytest-memprof` Estimates memory consumption of test functions Mar 29, 2019 4 - Beta N/A + :pypi:`pytest-memray` A simple plugin to use with pytest Apr 18, 2024 N/A pytest>=7.2 + :pypi:`pytest-menu` A pytest plugin for console based interactive test selection just after the collection phase Oct 04, 2017 3 - Alpha pytest (>=2.4.2) + :pypi:`pytest-mercurial` pytest plugin to write integration tests for projects using Mercurial Python internals Nov 21, 2020 1 - Planning N/A + :pypi:`pytest-mesh` pytest_mesh插件 Aug 05, 2022 N/A pytest (==7.1.2) + :pypi:`pytest-message` Pytest plugin for sending report message of marked tests execution Aug 04, 2022 N/A pytest (>=6.2.5) + :pypi:`pytest-messenger` Pytest to Slack reporting plugin Nov 24, 2022 5 - Production/Stable N/A + :pypi:`pytest-metadata` pytest plugin for test session metadata Feb 12, 2024 5 - Production/Stable pytest>=7.0.0 + :pypi:`pytest-metrics` Custom metrics report for pytest Apr 04, 2020 N/A pytest + :pypi:`pytest-mh` Pytest multihost plugin Mar 14, 2024 N/A pytest + :pypi:`pytest-mimesis` Mimesis integration with the pytest test runner Mar 21, 2020 5 - Production/Stable pytest (>=4.2) + :pypi:`pytest-minecraft` A pytest plugin for running tests against Minecraft releases Apr 06, 2022 N/A pytest (>=6.0.1) + :pypi:`pytest-mini` A plugin to test mp Feb 06, 2023 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-minio-mock` A pytest plugin for mocking Minio S3 interactions Apr 15, 2024 N/A pytest>=5.0.0 + :pypi:`pytest-missing-fixtures` Pytest plugin that creates missing fixtures Oct 14, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-mitmproxy` pytest plugin for mitmproxy tests Mar 07, 2024 N/A pytest >=7.0 + :pypi:`pytest-ml` Test your machine learning! May 04, 2019 4 - Beta N/A + :pypi:`pytest-mocha` pytest plugin to display test execution output like a mochajs Apr 02, 2020 4 - Beta pytest (>=5.4.0) + :pypi:`pytest-mock` Thin-wrapper around the mock package for easier use with pytest Mar 21, 2024 5 - Production/Stable pytest>=6.2.5 + :pypi:`pytest-mock-api` A mock API server with configurable routes and responses available as a fixture. Feb 13, 2019 1 - Planning pytest (>=4.0.0) + :pypi:`pytest-mock-generator` A pytest fixture wrapper for https://pypi.org/project/mock-generator May 16, 2022 5 - Production/Stable N/A + :pypi:`pytest-mock-helper` Help you mock HTTP call and generate mock code Jan 24, 2018 N/A pytest + :pypi:`pytest-mockito` Base fixtures for mockito Jul 11, 2018 4 - Beta N/A + :pypi:`pytest-mockredis` An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database. Jan 02, 2018 2 - Pre-Alpha N/A + :pypi:`pytest-mock-resources` A pytest plugin for easily instantiating reproducible mock resources. Apr 11, 2024 N/A pytest>=1.0 + :pypi:`pytest-mock-server` Mock server plugin for pytest Jan 09, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-mockservers` A set of fixtures to test your requests to HTTP/UDP servers Mar 31, 2020 N/A pytest (>=4.3.0) + :pypi:`pytest-mocktcp` A pytest plugin for testing TCP clients Oct 11, 2022 N/A pytest + :pypi:`pytest-modalt` Massively distributed pytest runs using modal.com Feb 27, 2024 4 - Beta pytest >=6.2.0 + :pypi:`pytest-modified-env` Pytest plugin to fail a test if it leaves modified \`os.environ\` afterwards. Jan 29, 2022 4 - Beta N/A + :pypi:`pytest-modifyjunit` Utility for adding additional properties to junit xml for IDM QE Jan 10, 2019 N/A N/A + :pypi:`pytest-modifyscope` pytest plugin to modify fixture scope Apr 12, 2020 N/A pytest + :pypi:`pytest-molecule` PyTest Molecule Plugin :: discover and run molecule tests Mar 29, 2022 5 - Production/Stable pytest (>=7.0.0) + :pypi:`pytest-molecule-JC` PyTest Molecule Plugin :: discover and run molecule tests Jul 18, 2023 5 - Production/Stable pytest (>=7.0.0) + :pypi:`pytest-mongo` MongoDB process and client fixtures plugin for Pytest. Mar 13, 2024 5 - Production/Stable pytest >=6.2 + :pypi:`pytest-mongodb` pytest plugin for MongoDB fixtures May 16, 2023 5 - Production/Stable N/A + :pypi:`pytest-monitor` Pytest plugin for analyzing resource usage. Jun 25, 2023 5 - Production/Stable pytest + :pypi:`pytest-monkeyplus` pytest's monkeypatch subclass with extra functionalities Sep 18, 2012 5 - Production/Stable N/A + :pypi:`pytest-monkeytype` pytest-monkeytype: Generate Monkeytype annotations from your pytest tests. Jul 29, 2020 4 - Beta N/A + :pypi:`pytest-moto` Fixtures for integration tests of AWS services,uses moto mocking library. Aug 28, 2015 1 - Planning N/A + :pypi:`pytest-motor` A pytest plugin for motor, the non-blocking MongoDB driver. Jul 21, 2021 3 - Alpha pytest + :pypi:`pytest-mp` A test batcher for multiprocessed Pytest runs May 23, 2018 4 - Beta pytest + :pypi:`pytest-mpi` pytest plugin to collect information from tests Jan 08, 2022 3 - Alpha pytest + :pypi:`pytest-mpiexec` pytest plugin for running individual tests with mpiexec Apr 13, 2023 3 - Alpha pytest + :pypi:`pytest-mpl` pytest plugin to help with testing figures output from Matplotlib Feb 14, 2024 4 - Beta pytest + :pypi:`pytest-mproc` low-startup-overhead, scalable, distributed-testing pytest plugin Nov 15, 2022 4 - Beta pytest (>=6) + :pypi:`pytest-mqtt` pytest-mqtt supports testing systems based on MQTT Mar 31, 2024 4 - Beta pytest<8; extra == "test" + :pypi:`pytest-multihost` Utility for writing multi-host tests for pytest Apr 07, 2020 4 - Beta N/A + :pypi:`pytest-multilog` Multi-process logs handling and other helpers for pytest Jan 17, 2023 N/A pytest + :pypi:`pytest-multithreading` a pytest plugin for th and concurrent testing Dec 07, 2022 N/A N/A + :pypi:`pytest-multithreading-allure` pytest_multithreading_allure Nov 25, 2022 N/A N/A + :pypi:`pytest-mutagen` Add the mutation testing feature to pytest Jul 24, 2020 N/A pytest (>=5.4) + :pypi:`pytest-my-cool-lib` Nov 02, 2023 N/A pytest (>=7.1.3,<8.0.0) + :pypi:`pytest-mypy` Mypy static type checker plugin for Pytest Dec 18, 2022 4 - Beta pytest (>=6.2) ; python_version >= "3.10" + :pypi:`pytest-mypyd` Mypy static type checker plugin for Pytest Aug 20, 2019 4 - Beta pytest (<4.7,>=2.8) ; python_version < "3.5" + :pypi:`pytest-mypy-plugins` pytest plugin for writing tests for mypy plugins Mar 31, 2024 4 - Beta pytest>=7.0.0 + :pypi:`pytest-mypy-plugins-shim` Substitute for "pytest-mypy-plugins" for Python implementations which aren't supported by mypy. Apr 12, 2021 N/A pytest>=6.0.0 + :pypi:`pytest-mypy-testing` Pytest plugin to check mypy output. Mar 04, 2024 N/A pytest>=7,<9 + :pypi:`pytest-mysql` MySQL process and client fixtures for pytest Oct 30, 2023 5 - Production/Stable pytest >=6.2 + :pypi:`pytest-ndb` pytest notebook debugger Oct 15, 2023 N/A pytest + :pypi:`pytest-needle` pytest plugin for visual testing websites using selenium Dec 10, 2018 4 - Beta pytest (<5.0.0,>=3.0.0) + :pypi:`pytest-neo` pytest-neo is a plugin for pytest that shows tests like screen of Matrix. Jan 08, 2022 3 - Alpha pytest (>=6.2.0) + :pypi:`pytest-neos` Pytest plugin for neos Apr 15, 2024 1 - Planning N/A + :pypi:`pytest-netdut` "Automated software testing for switches using pytest" Mar 07, 2024 N/A pytest <7.3,>=3.5.0 + :pypi:`pytest-network` A simple plugin to disable network on socket level. May 07, 2020 N/A N/A + :pypi:`pytest-network-endpoints` Network endpoints plugin for pytest Mar 06, 2022 N/A pytest + :pypi:`pytest-never-sleep` pytest plugin helps to avoid adding tests without mock \`time.sleep\` May 05, 2021 3 - Alpha pytest (>=3.5.1) + :pypi:`pytest-nginx` nginx fixture for pytest Aug 12, 2017 5 - Production/Stable N/A + :pypi:`pytest-nginx-iplweb` nginx fixture for pytest - iplweb temporary fork Mar 01, 2019 5 - Production/Stable N/A + :pypi:`pytest-ngrok` Jan 20, 2022 3 - Alpha pytest + :pypi:`pytest-ngsfixtures` pytest ngs fixtures Sep 06, 2019 2 - Pre-Alpha pytest (>=5.0.0) + :pypi:`pytest-nhsd-apim` Pytest plugin accessing NHSDigital's APIM proxies Feb 16, 2024 N/A pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-nice` A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest + :pypi:`pytest-nice-parametrize` A small snippet for nicer PyTest's Parametrize Apr 17, 2021 5 - Production/Stable N/A + :pypi:`pytest_nlcov` Pytest plugin to get the coverage of the new lines (based on git diff) only Apr 11, 2024 N/A N/A + :pypi:`pytest-nocustom` Run all tests without custom markers Apr 11, 2024 5 - Production/Stable N/A + :pypi:`pytest-node-dependency` pytest plugin for controlling execution flow Apr 10, 2024 5 - Production/Stable N/A + :pypi:`pytest-nodev` Test-driven source code search for Python. Jul 21, 2016 4 - Beta pytest (>=2.8.1) + :pypi:`pytest-nogarbage` Ensure a test produces no garbage Aug 29, 2021 5 - Production/Stable pytest (>=4.6.0) + :pypi:`pytest-nose-attrib` pytest plugin to use nose @attrib marks decorators and pick tests based on attributes and partially uses nose-attrib plugin approach Aug 13, 2023 N/A N/A + :pypi:`pytest_notebook` A pytest plugin for testing Jupyter Notebooks. Nov 28, 2023 4 - Beta pytest>=3.5.0 + :pypi:`pytest-notice` Send pytest execution result email Nov 05, 2020 N/A N/A + :pypi:`pytest-notification` A pytest plugin for sending a desktop notification and playing a sound upon completion of tests Jun 19, 2020 N/A pytest (>=4) + :pypi:`pytest-notifier` A pytest plugin to notify test result Jun 12, 2020 3 - Alpha pytest + :pypi:`pytest_notify` Get notifications when your tests ends Jul 05, 2017 N/A pytest>=3.0.0 + :pypi:`pytest-notimplemented` Pytest markers for not implemented features and tests. Aug 27, 2019 N/A pytest (>=5.1,<6.0) + :pypi:`pytest-notion` A PyTest Reporter to send test runs to Notion.so Aug 07, 2019 N/A N/A + :pypi:`pytest-nunit` A pytest plugin for generating NUnit3 test result XML output Feb 26, 2024 5 - Production/Stable N/A + :pypi:`pytest-oar` PyTest plugin for the OAR testing framework May 02, 2023 N/A pytest>=6.0.1 + :pypi:`pytest-object-getter` Import any object from a 3rd party module while mocking its namespace on demand. Jul 31, 2022 5 - Production/Stable pytest + :pypi:`pytest-ochrus` pytest results data-base and HTML reporter Feb 21, 2018 4 - Beta N/A + :pypi:`pytest-odc` A pytest plugin for simplifying ODC database tests Aug 04, 2023 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-odoo` py.test plugin to run Odoo tests Jul 06, 2023 4 - Beta pytest (>=7.2.0) + :pypi:`pytest-odoo-fixtures` Project description Jun 25, 2019 N/A N/A + :pypi:`pytest-oerp` pytest plugin to test OpenERP modules Feb 28, 2012 3 - Alpha N/A + :pypi:`pytest-offline` Mar 09, 2023 1 - Planning pytest (>=7.0.0,<8.0.0) + :pypi:`pytest-ogsm-plugin` 针对特定项目定制化插件,优化了pytest报告展示方式,并添加了项目所需特定参数 May 16, 2023 N/A N/A + :pypi:`pytest-ok` The ultimate pytest output plugin Apr 01, 2019 4 - Beta N/A + :pypi:`pytest-only` Use @pytest.mark.only to run a single test Mar 09, 2024 5 - Production/Stable pytest (<7.1) ; python_full_version <= "3.6.0" + :pypi:`pytest-oof` A Pytest plugin providing structured, programmatic access to a test run's results Dec 11, 2023 4 - Beta N/A + :pypi:`pytest-oot` Run object-oriented tests in a simple format Sep 18, 2016 4 - Beta N/A + :pypi:`pytest-openfiles` Pytest plugin for detecting inadvertent open file handles Apr 16, 2020 3 - Alpha pytest (>=4.6) + :pypi:`pytest-opentelemetry` A pytest plugin for instrumenting test runs via OpenTelemetry Oct 01, 2023 N/A pytest + :pypi:`pytest-opentmi` pytest plugin for publish results to opentmi Jun 02, 2022 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-operator` Fixtures for Operators Sep 28, 2022 N/A pytest + :pypi:`pytest-optional` include/exclude values of fixtures in pytest Oct 07, 2015 N/A N/A + :pypi:`pytest-optional-tests` Easy declaration of optional tests (i.e., that are not run by default) Jul 09, 2019 4 - Beta pytest (>=4.5.0) + :pypi:`pytest-orchestration` A pytest plugin for orchestrating tests Jul 18, 2019 N/A N/A + :pypi:`pytest-order` pytest plugin to run your tests in a specific order Apr 02, 2024 4 - Beta pytest>=5.0; python_version < "3.10" + :pypi:`pytest-ordering` pytest plugin to run your tests in a specific order Nov 14, 2018 4 - Beta pytest + :pypi:`pytest-order-modify` 新增run_marker 来自定义用例的执行顺序 Nov 04, 2022 N/A N/A + :pypi:`pytest-osxnotify` OS X notifications for py.test results. May 15, 2015 N/A N/A + :pypi:`pytest-ot` A pytest plugin for instrumenting test runs via OpenTelemetry Mar 21, 2024 N/A pytest; extra == "dev" + :pypi:`pytest-otel` OpenTelemetry plugin for Pytest Mar 18, 2024 N/A pytest==8.1.1 + :pypi:`pytest-override-env-var` Pytest mark to override a value of an environment variable. Feb 25, 2023 N/A N/A + :pypi:`pytest-owner` Add owner mark for tests Apr 25, 2022 N/A N/A + :pypi:`pytest-pact` A simple plugin to use with pytest Jan 07, 2019 4 - Beta N/A + :pypi:`pytest-pahrametahrize` Parametrize your tests with a Boston accent. Nov 24, 2021 4 - Beta pytest (>=6.0,<7.0) + :pypi:`pytest-parallel` a pytest plugin for parallel and concurrent testing Oct 10, 2021 3 - Alpha pytest (>=3.0.0) + :pypi:`pytest-parallel-39` a pytest plugin for parallel and concurrent testing Jul 12, 2021 3 - Alpha pytest (>=3.0.0) + :pypi:`pytest-parallelize-tests` pytest plugin that parallelizes test execution across multiple hosts Jan 27, 2023 4 - Beta N/A + :pypi:`pytest-param` pytest plugin to test all, first, last or random params Sep 11, 2016 4 - Beta pytest (>=2.6.0) + :pypi:`pytest-paramark` Configure pytest fixtures using a combination of"parametrize" and markers Jan 10, 2020 4 - Beta pytest (>=4.5.0) + :pypi:`pytest-parameterize-from-files` A pytest plugin that parameterizes tests from data files. Feb 15, 2024 4 - Beta pytest>=7.2.0 + :pypi:`pytest-parametrization` Simpler PyTest parametrization May 22, 2022 5 - Production/Stable N/A + :pypi:`pytest-parametrize-cases` A more user-friendly way to write parametrized tests. Mar 13, 2022 N/A pytest (>=6.1.2) + :pypi:`pytest-parametrized` Pytest decorator for parametrizing tests with default iterables. Nov 03, 2023 5 - Production/Stable pytest + :pypi:`pytest-parametrize-suite` A simple pytest extension for creating a named test suite. Jan 19, 2023 5 - Production/Stable pytest + :pypi:`pytest_param_files` Create pytest parametrize decorators from external files. Jul 29, 2023 N/A pytest + :pypi:`pytest-param-scope` pytest parametrize scope fixture workaround Oct 18, 2023 N/A pytest + :pypi:`pytest-parawtf` Finally spell paramete?ri[sz]e correctly Dec 03, 2018 4 - Beta pytest (>=3.6.0) + :pypi:`pytest-pass` Check out https://github.com/elilutsky/pytest-pass Dec 04, 2019 N/A N/A + :pypi:`pytest-passrunner` Pytest plugin providing the 'run_on_pass' marker Feb 10, 2021 5 - Production/Stable pytest (>=4.6.0) + :pypi:`pytest-paste-config` Allow setting the path to a paste config file Sep 18, 2013 3 - Alpha N/A + :pypi:`pytest-patch` An automagic \`patch\` fixture that can patch objects directly or by name. Apr 29, 2023 3 - Alpha pytest (>=7.0.0) + :pypi:`pytest-patches` A contextmanager pytest fixture for handling multiple mock patches Aug 30, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-patterns` pytest plugin to make testing complicated long string output easy to write and easy to debug Nov 17, 2023 4 - Beta N/A + :pypi:`pytest-pdb` pytest plugin which adds pdb helper commands related to pytest. Jul 31, 2018 N/A N/A + :pypi:`pytest-peach` pytest plugin for fuzzing with Peach API Security Apr 12, 2019 4 - Beta pytest (>=2.8.7) + :pypi:`pytest-pep257` py.test plugin for pep257 Jul 09, 2016 N/A N/A + :pypi:`pytest-pep8` pytest plugin to check PEP8 requirements Apr 27, 2014 N/A N/A + :pypi:`pytest-percent` Change the exit code of pytest test sessions when a required percent of tests pass. May 21, 2020 N/A pytest (>=5.2.0) + :pypi:`pytest-percents` Mar 16, 2024 N/A N/A + :pypi:`pytest-perf` Run performance tests against the mainline code. Jan 28, 2024 5 - Production/Stable pytest >=6 ; extra == 'testing' + :pypi:`pytest-performance` A simple plugin to ensure the execution of critical sections of code has not been impacted Sep 11, 2020 5 - Production/Stable pytest (>=3.7.0) + :pypi:`pytest-performancetotal` A performance plugin for pytest Mar 19, 2024 4 - Beta N/A + :pypi:`pytest-persistence` Pytest tool for persistent objects Jul 04, 2023 N/A N/A + :pypi:`pytest-pexpect` Pytest pexpect plugin. Mar 27, 2024 4 - Beta pytest>=6.2.0 + :pypi:`pytest-pg` A tiny plugin for pytest which runs PostgreSQL in Docker Apr 03, 2024 5 - Production/Stable pytest>=6.0.0 + :pypi:`pytest-pgsql` Pytest plugins and helpers for tests using a Postgres database. May 13, 2020 5 - Production/Stable pytest (>=3.0.0) + :pypi:`pytest-phmdoctest` pytest plugin to test Python examples in Markdown using phmdoctest. Apr 15, 2022 4 - Beta pytest (>=5.4.3) + :pypi:`pytest-picked` Run the tests related to the changed files Jul 27, 2023 N/A pytest (>=3.7.0) + :pypi:`pytest-pigeonhole` Jun 25, 2018 5 - Production/Stable pytest (>=3.4) + :pypi:`pytest-pikachu` Show surprise when tests are passing Aug 05, 2021 5 - Production/Stable pytest + :pypi:`pytest-pilot` Slice in your test base thanks to powerful markers. Oct 09, 2020 5 - Production/Stable N/A + :pypi:`pytest-pingguo-pytest-plugin` pingguo test Oct 26, 2022 4 - Beta N/A + :pypi:`pytest-pings` 🦊 The pytest plugin for Firefox Telemetry 📊 Jun 29, 2019 3 - Alpha pytest (>=5.0.0) + :pypi:`pytest-pinned` A simple pytest plugin for pinning tests Sep 17, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-pinpoint` A pytest plugin which runs SBFL algorithms to detect faults. Sep 25, 2020 N/A pytest (>=4.4.0) + :pypi:`pytest-pipeline` Pytest plugin for functional testing of data analysispipelines Jan 24, 2017 3 - Alpha N/A + :pypi:`pytest-pitch` runs tests in an order such that coverage increases as fast as possible Nov 02, 2023 4 - Beta pytest >=7.3.1 + :pypi:`pytest-platform-markers` Markers for pytest to skip tests on specific platforms Sep 09, 2019 4 - Beta pytest (>=3.6.0) + :pypi:`pytest-play` pytest plugin that let you automate actions and assertions with test metrics reporting executing plain YAML files Jun 12, 2019 5 - Production/Stable N/A + :pypi:`pytest-playbook` Pytest plugin for reading playbooks. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) + :pypi:`pytest-playwright` A pytest wrapper with fixtures for Playwright to automate web browsers Feb 02, 2024 N/A pytest (<9.0.0,>=6.2.4) + :pypi:`pytest_playwright_async` ASYNC Pytest plugin for Playwright Feb 25, 2024 N/A N/A + :pypi:`pytest-playwright-asyncio` Aug 29, 2023 N/A N/A + :pypi:`pytest-playwright-enhanced` A pytest plugin for playwright python Mar 24, 2024 N/A pytest<9.0.0,>=8.0.0 + :pypi:`pytest-playwrights` A pytest wrapper with fixtures for Playwright to automate web browsers Dec 02, 2021 N/A N/A + :pypi:`pytest-playwright-snapshot` A pytest wrapper for snapshot testing with playwright Aug 19, 2021 N/A N/A + :pypi:`pytest-playwright-visual` A pytest fixture for visual testing with Playwright Apr 28, 2022 N/A N/A + :pypi:`pytest-plone` Pytest plugin to test Plone addons Jan 05, 2023 3 - Alpha pytest + :pypi:`pytest-plt` Fixtures for quickly making Matplotlib plots in tests Jan 17, 2024 5 - Production/Stable pytest + :pypi:`pytest-plugin-helpers` A plugin to help developing and testing other plugins Nov 23, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-plus` PyTest Plus Plugin :: extends pytest functionality Mar 26, 2024 5 - Production/Stable pytest>=7.4.2 + :pypi:`pytest-pmisc` Mar 21, 2019 5 - Production/Stable N/A + :pypi:`pytest-pogo` Pytest plugin for pogo-migrate Mar 11, 2024 1 - Planning pytest (>=7,<9) + :pypi:`pytest-pointers` Pytest plugin to define functions you test with special marks for better navigation and reports Dec 26, 2022 N/A N/A + :pypi:`pytest-pokie` Pokie plugin for pytest Oct 19, 2023 5 - Production/Stable N/A + :pypi:`pytest-polarion-cfme` pytest plugin for collecting test cases and recording test results Nov 13, 2017 3 - Alpha N/A + :pypi:`pytest-polarion-collect` pytest plugin for collecting polarion test cases data Jun 18, 2020 3 - Alpha pytest + :pypi:`pytest-polecat` Provides Polecat pytest fixtures Aug 12, 2019 4 - Beta N/A + :pypi:`pytest-ponyorm` PonyORM in Pytest Oct 31, 2018 N/A pytest (>=3.1.1) + :pypi:`pytest-poo` Visualize your crappy tests Mar 25, 2021 5 - Production/Stable pytest (>=2.3.4) + :pypi:`pytest-poo-fail` Visualize your failed tests with poo Feb 12, 2015 5 - Production/Stable N/A + :pypi:`pytest-pook` Pytest plugin for pook Feb 15, 2024 4 - Beta pytest + :pypi:`pytest-pop` A pytest plugin to help with testing pop projects May 09, 2023 5 - Production/Stable pytest + :pypi:`pytest-porringer` Jan 18, 2024 N/A pytest>=7.4.4 + :pypi:`pytest-portion` Select a portion of the collected tests Jan 28, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-postgres` Run PostgreSQL in Docker container in Pytest. Mar 22, 2020 N/A pytest + :pypi:`pytest-postgresql` Postgresql fixtures and fixture factories for Pytest. Mar 11, 2024 5 - Production/Stable pytest >=6.2 + :pypi:`pytest-power` pytest plugin with powerful fixtures Dec 31, 2020 N/A pytest (>=5.4) + :pypi:`pytest-powerpack` Mar 17, 2024 N/A pytest (>=8.1.1,<9.0.0) + :pypi:`pytest-prefer-nested-dup-tests` A Pytest plugin to drop duplicated tests during collection, but will prefer keeping nested packages. Apr 27, 2022 4 - Beta pytest (>=7.1.1,<8.0.0) + :pypi:`pytest-pretty` pytest plugin for printing summary data as I want it Apr 05, 2023 5 - Production/Stable pytest>=7 + :pypi:`pytest-pretty-terminal` pytest plugin for generating prettier terminal output Jan 31, 2022 N/A pytest (>=3.4.1) + :pypi:`pytest-pride` Minitest-style test colors Apr 02, 2016 3 - Alpha N/A + :pypi:`pytest-print` pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout) Aug 25, 2023 5 - Production/Stable pytest>=7.4 + :pypi:`pytest-priority` pytest plugin for add priority for tests Jul 23, 2023 N/A N/A + :pypi:`pytest-proceed` Apr 10, 2024 N/A pytest + :pypi:`pytest-profiles` pytest plugin for configuration profiles Dec 09, 2021 4 - Beta pytest (>=3.7.0) + :pypi:`pytest-profiling` Profiling plugin for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-progress` pytest plugin for instant test progress status Jan 31, 2022 5 - Production/Stable N/A + :pypi:`pytest-prometheus` Report test pass / failures to a Prometheus PushGateway Oct 03, 2017 N/A N/A + :pypi:`pytest-prometheus-pushgateway` Pytest report plugin for Zulip Sep 27, 2022 5 - Production/Stable pytest + :pypi:`pytest-prosper` Test helpers for Prosper projects Sep 24, 2018 N/A N/A + :pypi:`pytest-prysk` Pytest plugin for prysk Mar 12, 2024 4 - Beta pytest (>=7.3.2) + :pypi:`pytest-pspec` A rspec format reporter for Python ptest Jun 02, 2020 4 - Beta pytest (>=3.0.0) + :pypi:`pytest-psqlgraph` pytest plugin for testing applications that use psqlgraph Oct 19, 2021 4 - Beta pytest (>=6.0) + :pypi:`pytest-ptera` Use ptera probes in tests Mar 01, 2022 N/A pytest (>=6.2.4,<7.0.0) + :pypi:`pytest-pudb` Pytest PuDB debugger integration Oct 25, 2018 3 - Alpha pytest (>=2.0) + :pypi:`pytest-pumpkin-spice` A pytest plugin that makes your test reporting pumpkin-spiced Sep 18, 2022 4 - Beta N/A + :pypi:`pytest-purkinje` py.test plugin for purkinje test runner Oct 28, 2017 2 - Pre-Alpha N/A + :pypi:`pytest-pusher` pytest plugin for push report to minio Jan 06, 2023 5 - Production/Stable pytest (>=3.6) + :pypi:`pytest-py125` Dec 03, 2022 N/A N/A + :pypi:`pytest-pycharm` Plugin for py.test to enter PyCharm debugger on uncaught exceptions Aug 13, 2020 5 - Production/Stable pytest (>=2.3) + :pypi:`pytest-pycodestyle` pytest plugin to run pycodestyle Oct 28, 2022 3 - Alpha N/A + :pypi:`pytest-pydev` py.test plugin to connect to a remote debug server with PyDev or PyCharm. Nov 15, 2017 3 - Alpha N/A + :pypi:`pytest-pydocstyle` pytest plugin to run pydocstyle Jan 05, 2023 3 - Alpha N/A + :pypi:`pytest-pylint` pytest plugin to check source code with pylint Oct 06, 2023 5 - Production/Stable pytest >=7.0 + :pypi:`pytest-pymysql-autorecord` Record PyMySQL queries and mock with the stored data. Sep 02, 2022 N/A N/A + :pypi:`pytest-pyodide` Pytest plugin for testing applications that use Pyodide Dec 09, 2023 N/A pytest + :pypi:`pytest-pypi` Easily test your HTTP library against a local copy of pypi Mar 04, 2018 3 - Alpha N/A + :pypi:`pytest-pypom-navigation` Core engine for cookiecutter-qa and pytest-play packages Feb 18, 2019 4 - Beta pytest (>=3.0.7) + :pypi:`pytest-pyppeteer` A plugin to run pyppeteer in pytest Apr 28, 2022 N/A pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-pyq` Pytest fixture "q" for pyq Mar 10, 2020 5 - Production/Stable N/A + :pypi:`pytest-pyramid` pytest_pyramid - provides fixtures for testing pyramid applications with pytest test suite Oct 11, 2023 5 - Production/Stable pytest + :pypi:`pytest-pyramid-server` Pyramid server fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-pyreport` PyReport is a lightweight reporting plugin for Pytest that provides concise HTML report Feb 03, 2024 N/A pytest + :pypi:`pytest-pyright` Pytest plugin for type checking code with Pyright Jan 26, 2024 4 - Beta pytest >=7.0.0 + :pypi:`pytest-pyspec` A plugin that transforms the pytest output into a result similar to the RSpec. It enables the use of docstrings to display results and also enables the use of the prefixes "describe", "with" and "it". Jan 02, 2024 N/A pytest (>=7.2.1,<8.0.0) + :pypi:`pytest-pystack` Plugin to run pystack after a timeout for a test suite. Jan 04, 2024 N/A pytest >=3.5.0 + :pypi:`pytest-pytestrail` Pytest plugin for interaction with TestRail Aug 27, 2020 4 - Beta pytest (>=3.8.0) + :pypi:`pytest-pythonhashseed` Pytest plugin to set PYTHONHASHSEED env var. Feb 25, 2024 4 - Beta pytest>=3.0.0 + :pypi:`pytest-pythonpath` pytest plugin for adding to the PYTHONPATH from command line or configs. Feb 10, 2022 5 - Production/Stable pytest (<7,>=2.5.2) + :pypi:`pytest-pytorch` pytest plugin for a better developer experience when working with the PyTorch test suite May 25, 2021 4 - Beta pytest + :pypi:`pytest-pyvenv` A package for create venv in tests Feb 27, 2024 N/A pytest ; extra == 'test' + :pypi:`pytest-pyvista` Pytest-pyvista package Sep 29, 2023 4 - Beta pytest>=3.5.0 + :pypi:`pytest-qaseio` Pytest plugin for Qase.io integration Sep 12, 2023 4 - Beta pytest (>=7.2.2,<8.0.0) + :pypi:`pytest-qasync` Pytest support for qasync. Jul 12, 2021 4 - Beta pytest (>=5.4.0) + :pypi:`pytest-qatouch` Pytest plugin for uploading test results to your QA Touch Testrun. Feb 14, 2023 4 - Beta pytest (>=6.2.0) + :pypi:`pytest-qgis` A pytest plugin for testing QGIS python plugins Nov 29, 2023 5 - Production/Stable pytest >=6.0 + :pypi:`pytest-qml` Run QML Tests with pytest Dec 02, 2020 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-qr` pytest plugin to generate test result QR codes Nov 25, 2021 4 - Beta N/A + :pypi:`pytest-qt` pytest support for PyQt and PySide applications Feb 07, 2024 5 - Production/Stable pytest + :pypi:`pytest-qt-app` QT app fixture for py.test Dec 23, 2015 5 - Production/Stable N/A + :pypi:`pytest-quarantine` A plugin for pytest to manage expected test failures Nov 24, 2019 5 - Production/Stable pytest (>=4.6) + :pypi:`pytest-quickcheck` pytest plugin to generate random data inspired by QuickCheck Nov 05, 2022 4 - Beta pytest (>=4.0) + :pypi:`pytest_quickify` Run test suites with pytest-quickify. Jun 14, 2019 N/A pytest + :pypi:`pytest-rabbitmq` RabbitMQ process and client fixtures for pytest Jul 05, 2023 5 - Production/Stable pytest (>=6.2) + :pypi:`pytest-race` Race conditions tester for pytest Jun 07, 2022 4 - Beta N/A + :pypi:`pytest-rage` pytest plugin to implement PEP712 Oct 21, 2011 3 - Alpha N/A + :pypi:`pytest-rail` pytest plugin for creating TestRail runs and adding results May 02, 2022 N/A pytest (>=3.6) + :pypi:`pytest-railflow-testrail-reporter` Generate json reports along with specified metadata defined in test markers. Jun 29, 2022 5 - Production/Stable pytest + :pypi:`pytest-raises` An implementation of pytest.raises as a pytest.mark fixture Apr 23, 2020 N/A pytest (>=3.2.2) + :pypi:`pytest-raisesregexp` Simple pytest plugin to look for regex in Exceptions Dec 18, 2015 N/A N/A + :pypi:`pytest-raisin` Plugin enabling the use of exception instances with pytest.raises Feb 06, 2022 N/A pytest + :pypi:`pytest-random` py.test plugin to randomize tests Apr 28, 2013 3 - Alpha N/A + :pypi:`pytest-randomly` Pytest plugin to randomly order tests and control random.seed. Aug 15, 2023 5 - Production/Stable pytest + :pypi:`pytest-randomness` Pytest plugin about random seed management May 30, 2019 3 - Alpha N/A + :pypi:`pytest-random-num` Randomise the order in which pytest tests are run with some control over the randomness Oct 19, 2020 5 - Production/Stable N/A + :pypi:`pytest-random-order` Randomise the order in which pytest tests are run with some control over the randomness Jan 20, 2024 5 - Production/Stable pytest >=3.0.0 + :pypi:`pytest-ranking` A Pytest plugin for automatically prioritizing/ranking tests to speed up failure detection Mar 18, 2024 4 - Beta pytest >=7.4.3 + :pypi:`pytest-readme` Test your README.md file Sep 02, 2022 5 - Production/Stable N/A + :pypi:`pytest-reana` Pytest fixtures for REANA. Mar 14, 2024 3 - Alpha N/A + :pypi:`pytest-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Nov 21, 2023 N/A N/A + :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Dec 06, 2023 4 - Beta pytest>=3.5.0 + :pypi:`pytest-recordings` Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal Aug 13, 2020 N/A N/A + :pypi:`pytest-redis` Redis fixtures and fixture factories for Pytest. Apr 19, 2023 5 - Production/Stable pytest (>=6.2) + :pypi:`pytest-redislite` Pytest plugin for testing code using Redis Apr 05, 2022 4 - Beta pytest + :pypi:`pytest-redmine` Pytest plugin for redmine Mar 19, 2018 1 - Planning N/A + :pypi:`pytest-ref` A plugin to store reference files to ease regression testing Nov 23, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-reference-formatter` Conveniently run pytest with a dot-formatted test reference. Oct 01, 2019 4 - Beta N/A + :pypi:`pytest-regex` Select pytest tests with regular expressions May 29, 2023 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-regex-dependency` Management of Pytest dependencies via regex patterns Jun 12, 2022 N/A pytest + :pypi:`pytest-regressions` Easy to use fixtures to write regression tests. Aug 31, 2023 5 - Production/Stable pytest >=6.2.0 + :pypi:`pytest-regtest` pytest plugin for snapshot regression testing Feb 26, 2024 N/A pytest>7.2 + :pypi:`pytest-relative-order` a pytest plugin that sorts tests using "before" and "after" markers May 17, 2021 4 - Beta N/A + :pypi:`pytest-relaxed` Relaxed test discovery/organization for pytest Mar 29, 2024 5 - Production/Stable pytest>=7 + :pypi:`pytest-remfiles` Pytest plugin to create a temporary directory with remote files Jul 01, 2019 5 - Production/Stable N/A + :pypi:`pytest-remotedata` Pytest plugin for controlling remote data access. Sep 26, 2023 5 - Production/Stable pytest >=4.6 + :pypi:`pytest-remote-response` Pytest plugin for capturing and mocking connection requests. Apr 26, 2023 5 - Production/Stable pytest (>=4.6) + :pypi:`pytest-remove-stale-bytecode` py.test plugin to remove stale byte code files. Jul 07, 2023 4 - Beta pytest + :pypi:`pytest-reorder` Reorder tests depending on their paths and names. May 31, 2018 4 - Beta pytest + :pypi:`pytest-repeat` pytest plugin for repeating tests Oct 09, 2023 5 - Production/Stable pytest + :pypi:`pytest_repeater` py.test plugin for repeating single test multiple times. Feb 09, 2018 1 - Planning N/A + :pypi:`pytest-replay` Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests Jan 11, 2024 5 - Production/Stable pytest + :pypi:`pytest-repo-health` A pytest plugin to report on repository standards conformance Apr 17, 2023 3 - Alpha pytest + :pypi:`pytest-report` Creates json report that is compatible with atom.io's linter message format May 11, 2016 4 - Beta N/A + :pypi:`pytest-reporter` Generate Pytest reports with templates Feb 28, 2024 4 - Beta pytest + :pypi:`pytest-reporter-html1` A basic HTML report template for Pytest Feb 28, 2024 4 - Beta N/A + :pypi:`pytest-reporter-html-dots` A basic HTML report for pytest using Jinja2 template engine. Jan 22, 2023 N/A N/A + :pypi:`pytest-reportinfra` Pytest plugin for reportinfra Aug 11, 2019 3 - Alpha N/A + :pypi:`pytest-reporting` A plugin to report summarized results in a table format Oct 25, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-reportlog` Replacement for the --resultlog option, focused in simplicity and extensibility May 22, 2023 3 - Alpha pytest + :pypi:`pytest-report-me` A pytest plugin to generate report. Dec 31, 2020 N/A pytest + :pypi:`pytest-report-parameters` pytest plugin for adding tests' parameters to junit report Jun 18, 2020 3 - Alpha pytest (>=2.4.2) + :pypi:`pytest-reportportal` Agent for Reporting results of tests to the Report Portal Mar 27, 2024 N/A pytest>=3.8.0 + :pypi:`pytest-report-stream` A pytest plugin which allows to stream test reports at runtime Oct 22, 2023 4 - Beta N/A + :pypi:`pytest-repo-structure` Pytest Repo Structure Mar 18, 2024 1 - Planning N/A + :pypi:`pytest-reqs` pytest plugin to check pinned requirements May 12, 2019 N/A pytest (>=2.4.2) + :pypi:`pytest-requests` A simple plugin to use with pytest Jun 24, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-requestselapsed` collect and show http requests elapsed time Aug 14, 2022 N/A N/A + :pypi:`pytest-requests-futures` Pytest Plugin to Mock Requests Futures Jul 06, 2022 5 - Production/Stable pytest + :pypi:`pytest-requires` A pytest plugin to elegantly skip tests with optional requirements Dec 21, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-reraise` Make multi-threaded pytest test cases fail when they should Sep 20, 2022 5 - Production/Stable pytest (>=4.6) + :pypi:`pytest-rerun` Re-run only changed files in specified branch Jul 08, 2019 N/A pytest (>=3.6) + :pypi:`pytest-rerun-all` Rerun testsuite for a certain time or iterations Nov 16, 2023 3 - Alpha pytest (>=7.0.0) + :pypi:`pytest-rerunclassfailures` pytest rerun class failures plugin Mar 29, 2024 5 - Production/Stable pytest>=7.2 + :pypi:`pytest-rerunfailures` pytest plugin to re-run tests to eliminate flaky failures Mar 13, 2024 5 - Production/Stable pytest >=7.2 + :pypi:`pytest-rerunfailures-all-logs` pytest plugin to re-run tests to eliminate flaky failures Mar 07, 2022 5 - Production/Stable N/A + :pypi:`pytest-reserial` Pytest fixture for recording and replaying serial port traffic. Feb 08, 2024 4 - Beta pytest + :pypi:`pytest-resilient-circuits` Resilient Circuits fixtures for PyTest Apr 03, 2024 N/A pytest~=4.6; python_version == "2.7" + :pypi:`pytest-resource` Load resource fixture plugin to use with pytest Nov 14, 2018 4 - Beta N/A + :pypi:`pytest-resource-path` Provides path for uniform access to test resources in isolated directory May 01, 2021 5 - Production/Stable pytest (>=3.5.0) + :pypi:`pytest-resource-usage` Pytest plugin for reporting running time and peak memory usage Nov 06, 2022 5 - Production/Stable pytest>=7.0.0 + :pypi:`pytest-responsemock` Simplified requests calls mocking for pytest Mar 10, 2022 5 - Production/Stable N/A + :pypi:`pytest-responses` py.test integration for responses Oct 11, 2022 N/A pytest (>=2.5) + :pypi:`pytest-rest-api` Aug 08, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-restrict` Pytest plugin to restrict the test types allowed Jul 10, 2023 5 - Production/Stable pytest + :pypi:`pytest-result-log` A pytest plugin that records the start, end, and result information of each use case in a log file Jan 10, 2024 N/A pytest>=7.2.0 + :pypi:`pytest-result-sender` Apr 20, 2023 N/A pytest>=7.3.1 + :pypi:`pytest-resume` A Pytest plugin to resuming from the last run test Apr 22, 2023 4 - Beta pytest (>=7.0) + :pypi:`pytest-rethinkdb` A RethinkDB plugin for pytest. Jul 24, 2016 4 - Beta N/A + :pypi:`pytest-retry` Adds the ability to retry flaky tests in CI environments Feb 04, 2024 N/A pytest >=7.0.0 + :pypi:`pytest-retry-class` A pytest plugin to rerun entire class on failure Mar 25, 2023 N/A pytest (>=5.3) + :pypi:`pytest-reusable-testcases` Apr 28, 2023 N/A N/A + :pypi:`pytest-reverse` Pytest plugin to reverse test order. Jul 10, 2023 5 - Production/Stable pytest + :pypi:`pytest-rich` Leverage rich for richer test session output Mar 03, 2022 4 - Beta pytest (>=7.0) + :pypi:`pytest-richer` Pytest plugin providing a Rich based reporter. Oct 27, 2023 3 - Alpha pytest + :pypi:`pytest-rich-reporter` A pytest plugin using Rich for beautiful test result formatting. Feb 17, 2022 1 - Planning pytest (>=5.0.0) + :pypi:`pytest-richtrace` A pytest plugin that displays the names and information of the pytest hook functions as they are executed. Jun 20, 2023 N/A N/A + :pypi:`pytest-ringo` pytest plugin to test webapplications using the Ringo webframework Sep 27, 2017 3 - Alpha N/A + :pypi:`pytest-rmsis` Sycronise pytest results to Jira RMsis Aug 10, 2022 N/A pytest (>=5.3.5) + :pypi:`pytest-rng` Fixtures for seeding tests and making randomness reproducible Aug 08, 2019 5 - Production/Stable pytest + :pypi:`pytest-roast` pytest plugin for ROAST configuration override and fixtures Nov 09, 2022 5 - Production/Stable pytest + :pypi:`pytest_robotframework` a pytest plugin that can run both python and robotframework tests while generating robot reports for them Mar 29, 2024 N/A pytest<9,>=7 + :pypi:`pytest-rocketchat` Pytest to Rocket.Chat reporting plugin Apr 18, 2021 5 - Production/Stable N/A + :pypi:`pytest-rotest` Pytest integration with rotest Sep 08, 2019 N/A pytest (>=3.5.0) + :pypi:`pytest-rpc` Extend py.test for RPC OpenStack testing. Feb 22, 2019 4 - Beta pytest (~=3.6) + :pypi:`pytest-rst` Test code from RST documents with pytest Jan 26, 2023 N/A N/A + :pypi:`pytest-rt` pytest data collector plugin for Testgr May 05, 2022 N/A N/A + :pypi:`pytest-rts` Coverage-based regression test selection (RTS) plugin for pytest May 17, 2021 N/A pytest + :pypi:`pytest-ruff` pytest plugin to check ruff requirements. Mar 10, 2024 4 - Beta pytest (>=5) + :pypi:`pytest-run-changed` Pytest plugin that runs changed tests only Apr 02, 2021 3 - Alpha pytest + :pypi:`pytest-runfailed` implement a --failed option for pytest Mar 24, 2016 N/A N/A + :pypi:`pytest-run-subprocess` Pytest Plugin for running and testing subprocesses. Nov 12, 2022 5 - Production/Stable pytest + :pypi:`pytest-runtime-types` Checks type annotations on runtime while running tests. Feb 09, 2023 N/A pytest + :pypi:`pytest-runtime-xfail` Call runtime_xfail() to mark running test as xfail. Aug 26, 2021 N/A pytest>=5.0.0 + :pypi:`pytest-runtime-yoyo` run case mark timeout Jun 12, 2023 N/A pytest (>=7.2.0) + :pypi:`pytest-saccharin` pytest-saccharin is a updated fork of pytest-sugar, a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). Oct 31, 2022 3 - Alpha N/A + :pypi:`pytest-salt` Pytest Salt Plugin Jan 27, 2020 4 - Beta N/A + :pypi:`pytest-salt-containers` A Pytest plugin that builds and creates docker containers Nov 09, 2016 4 - Beta N/A + :pypi:`pytest-salt-factories` Pytest Salt Plugin Mar 22, 2024 5 - Production/Stable pytest>=7.0.0 + :pypi:`pytest-salt-from-filenames` Simple PyTest Plugin For Salt's Test Suite Specifically Jan 29, 2019 4 - Beta pytest (>=4.1) + :pypi:`pytest-salt-runtests-bridge` Simple PyTest Plugin For Salt's Test Suite Specifically Dec 05, 2019 4 - Beta pytest (>=4.1) + :pypi:`pytest-sanic` a pytest plugin for Sanic Oct 25, 2021 N/A pytest (>=5.2) + :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A + :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A + :pypi:`pytest_sauce` pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs Jul 14, 2014 3 - Alpha N/A + :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Apr 14, 2024 5 - Production/Stable N/A + :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A + :pypi:`pytest-schedule` The job of test scheduling for humans. Jan 07, 2023 5 - Production/Stable N/A + :pypi:`pytest-schema` 👍 Validate return values against a schema-like object in testing Feb 16, 2024 5 - Production/Stable pytest >=3.5.0 + :pypi:`pytest-screenshot-on-failure` Saves a screenshot when a test case from a pytest execution fails Jul 21, 2023 4 - Beta N/A + :pypi:`pytest-securestore` An encrypted password store for use within pytest cases Nov 08, 2021 4 - Beta N/A + :pypi:`pytest-select` A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) + :pypi:`pytest-selenium` pytest plugin for Selenium Feb 01, 2024 5 - Production/Stable pytest>=6.0.0 + :pypi:`pytest-selenium-auto` pytest plugin to automatically capture screenshots upon selenium webdriver events Nov 07, 2023 N/A pytest >= 7.0.0 + :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Apr 14, 2024 5 - Production/Stable N/A + :pypi:`pytest-selenium-enhancer` pytest plugin for Selenium Apr 29, 2022 5 - Production/Stable N/A + :pypi:`pytest-selenium-pdiff` A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A + :pypi:`pytest-selfie` A pytest plugin for selfie snapshot testing. Apr 05, 2024 N/A pytest<9.0.0,>=8.0.0 + :pypi:`pytest-send-email` Send pytest execution result email Dec 04, 2019 N/A N/A + :pypi:`pytest-sentry` A pytest plugin to send testrun information to Sentry.io Apr 05, 2024 N/A pytest + :pypi:`pytest-sequence-markers` Pytest plugin for sequencing markers for execution of tests May 23, 2023 5 - Production/Stable N/A + :pypi:`pytest-server-fixtures` Extensible server fixures for py.test Dec 19, 2023 5 - Production/Stable pytest + :pypi:`pytest-serverless` Automatically mocks resources from serverless.yml in pytest using moto. May 09, 2022 4 - Beta N/A + :pypi:`pytest-servers` pytest servers Mar 19, 2024 3 - Alpha pytest>=6.2 + :pypi:`pytest-services` Services plugin for pytest testing framework Oct 30, 2020 6 - Mature N/A + :pypi:`pytest-session2file` pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test. Jan 26, 2021 3 - Alpha pytest + :pypi:`pytest-session-fixture-globalize` py.test plugin to make session fixtures behave as if written in conftest, even if it is written in some modules May 15, 2018 4 - Beta N/A + :pypi:`pytest-session_to_file` pytest-session_to_file is a py.test plugin for capturing and saving to file the stdout of py.test. Oct 01, 2015 3 - Alpha N/A + :pypi:`pytest-setupinfo` Displaying setup info during pytest command run Jan 23, 2023 N/A N/A + :pypi:`pytest-sftpserver` py.test plugin to locally test sftp server connections. Sep 16, 2019 4 - Beta N/A + :pypi:`pytest-shard` Dec 11, 2020 4 - Beta pytest + :pypi:`pytest-share-hdf` Plugin to save test data in HDF files and retrieve them for comparison Sep 21, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-sharkreport` this is pytest report plugin. Jul 11, 2022 N/A pytest (>=3.5) + :pypi:`pytest-shell` A pytest plugin to help with testing shell scripts / black box commands Mar 27, 2022 N/A N/A + :pypi:`pytest-shell-utilities` Pytest plugin to simplify running shell commands against the system Feb 23, 2024 5 - Production/Stable pytest >=7.4.0 + :pypi:`pytest-sheraf` Versatile ZODB abstraction layer - pytest fixtures Feb 11, 2020 N/A pytest + :pypi:`pytest-sherlock` pytest plugin help to find coupled tests Aug 14, 2023 5 - Production/Stable pytest >=3.5.1 + :pypi:`pytest-shortcuts` Expand command-line shortcuts listed in pytest configuration Oct 29, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-shutil` A goodie-bag of unix shell and environment tools for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-simbind` Pytest plugin to operate with objects generated by Simbind tool. Mar 28, 2024 N/A pytest>=7.0.0 + :pypi:`pytest-simplehttpserver` Simple pytest fixture to spin up an HTTP server Jun 24, 2021 4 - Beta N/A + :pypi:`pytest-simple-plugin` Simple pytest plugin Nov 27, 2019 N/A N/A + :pypi:`pytest-simple-settings` simple-settings plugin for pytest Nov 17, 2020 4 - Beta pytest + :pypi:`pytest-single-file-logging` Allow for multiple processes to log to a single file May 05, 2016 4 - Beta pytest (>=2.8.1) + :pypi:`pytest-skip-markers` Pytest Salt Plugin Jan 04, 2024 5 - Production/Stable pytest >=7.1.0 + :pypi:`pytest-skipper` A plugin that selects only tests with changes in execution path Mar 26, 2017 3 - Alpha pytest (>=3.0.6) + :pypi:`pytest-skippy` Automatically skip tests that don't need to run! Jan 27, 2018 3 - Alpha pytest (>=2.3.4) + :pypi:`pytest-skip-slow` A pytest plugin to skip \`@pytest.mark.slow\` tests by default. Feb 09, 2023 N/A pytest>=6.2.0 + :pypi:`pytest-skipuntil` A simple pytest plugin to skip flapping test with deadline Nov 25, 2023 4 - Beta pytest >=3.8.0 + :pypi:`pytest-slack` Pytest to Slack reporting plugin Dec 15, 2020 5 - Production/Stable N/A + :pypi:`pytest-slow` A pytest plugin to skip \`@pytest.mark.slow\` tests by default. Sep 28, 2021 N/A N/A + :pypi:`pytest-slowest-first` Sort tests by their last duration, slowest first Dec 11, 2022 4 - Beta N/A + :pypi:`pytest-slow-first` Prioritize running the slowest tests first. Jan 30, 2024 4 - Beta pytest >=3.5.0 + :pypi:`pytest-slow-last` Run tests in order of execution time (faster tests first) Dec 10, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-smartcollect` A plugin for collecting tests that touch changed code Oct 04, 2018 N/A pytest (>=3.5.0) + :pypi:`pytest-smartcov` Smart coverage plugin for pytest. Sep 30, 2017 3 - Alpha N/A + :pypi:`pytest-smell` Automated bad smell detection tool for Pytest Jun 26, 2022 N/A N/A + :pypi:`pytest-smtp` Send email with pytest execution result Feb 20, 2021 N/A pytest + :pypi:`pytest-smtp4dev` Plugin for smtp4dev API Jun 27, 2023 5 - Production/Stable N/A + :pypi:`pytest-smtpd` An SMTP server for testing built on aiosmtpd May 15, 2023 N/A pytest + :pypi:`pytest-smtp-test-server` pytest plugin for using \`smtp-test-server\` as a fixture Dec 03, 2023 2 - Pre-Alpha pytest (>=7.4.3,<8.0.0) + :pypi:`pytest-snail` Plugin for adding a marker to slow running tests. 🐌 Nov 04, 2019 3 - Alpha pytest (>=5.0.1) + :pypi:`pytest-snapci` py.test plugin for Snap-CI Nov 12, 2015 N/A N/A + :pypi:`pytest-snapshot` A plugin for snapshot testing with pytest. Apr 23, 2022 4 - Beta pytest (>=3.0.0) + :pypi:`pytest-snapshot-with-message-generator` A plugin for snapshot testing with pytest. Jul 25, 2023 4 - Beta pytest (>=3.0.0) + :pypi:`pytest-snmpserver` May 12, 2021 N/A N/A + :pypi:`pytest-snowflake-bdd` Setup test data and run tests on snowflake in BDD style! Jan 05, 2022 4 - Beta pytest (>=6.2.0) + :pypi:`pytest-socket` Pytest Plugin to disable socket calls during tests Jan 28, 2024 4 - Beta pytest (>=6.2.5) + :pypi:`pytest-sofaepione` Test the installation of SOFA and the SofaEpione plugin. Aug 17, 2022 N/A N/A + :pypi:`pytest-soft-assertions` May 05, 2020 3 - Alpha pytest + :pypi:`pytest-solidity` A PyTest library plugin for Solidity language. Jan 15, 2022 1 - Planning pytest (<7,>=6.0.1) ; extra == 'tests' + :pypi:`pytest-solr` Solr process and client fixtures for py.test. May 11, 2020 3 - Alpha pytest (>=3.0.0) + :pypi:`pytest-sort` Tools for sorting test cases Jan 07, 2024 N/A pytest >=7.4.0 + :pypi:`pytest-sorter` A simple plugin to first execute tests that historically failed more Apr 20, 2021 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-sosu` Unofficial PyTest plugin for Sauce Labs Aug 04, 2023 2 - Pre-Alpha pytest + :pypi:`pytest-sourceorder` Test-ordering plugin for pytest Sep 01, 2021 4 - Beta pytest + :pypi:`pytest-spark` pytest plugin to run the tests with support of pyspark. Feb 23, 2020 4 - Beta pytest + :pypi:`pytest-spawner` py.test plugin to spawn process and communicate with them. Jul 31, 2015 4 - Beta N/A + :pypi:`pytest-spec` Library pytest-spec is a pytest plugin to display test execution output like a SPECIFICATION. May 04, 2021 N/A N/A + :pypi:`pytest-spec2md` Library pytest-spec2md is a pytest plugin to create a markdown specification while running pytest. Apr 10, 2024 N/A pytest>7.0 + :pypi:`pytest-speed` Modern benchmarking library for python with pytest integration. Jan 22, 2023 3 - Alpha pytest>=7 + :pypi:`pytest-sphinx` Doctest plugin for pytest with support for Sphinx-specific doctest-directives Apr 13, 2024 4 - Beta pytest>=8.1.1 + :pypi:`pytest-spiratest` Exports unit tests as test runs in Spira (SpiraTest/Team/Plan) Jan 01, 2024 N/A N/A + :pypi:`pytest-splinter` Splinter plugin for pytest testing framework Sep 09, 2022 6 - Mature pytest (>=3.0.0) + :pypi:`pytest-splinter4` Pytest plugin for the splinter automation library Feb 01, 2024 6 - Mature pytest >=8.0.0 + :pypi:`pytest-split` Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. Jan 29, 2024 4 - Beta pytest (>=5,<9) + :pypi:`pytest-split-ext` Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. Sep 23, 2023 4 - Beta pytest (>=5,<8) + :pypi:`pytest-splitio` Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0) + :pypi:`pytest-split-tests` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. Jul 30, 2021 5 - Production/Stable pytest (>=2.5) + :pypi:`pytest-split-tests-tresorit` Feb 22, 2021 1 - Planning N/A + :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Apr 19, 2024 N/A pytest (>5.4.0,<8) + :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Mar 26, 2024 N/A N/A + :pypi:`pytest-splunk-env` pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0) + :pypi:`pytest-sqitch` sqitch for pytest Apr 06, 2020 4 - Beta N/A + :pypi:`pytest-sqlalchemy` pytest plugin with sqlalchemy related fixtures Mar 13, 2018 3 - Alpha N/A + :pypi:`pytest-sqlalchemy-mock` pytest sqlalchemy plugin for mock Mar 15, 2023 3 - Alpha pytest (>=2.0) + :pypi:`pytest-sqlalchemy-session` A pytest plugin for preserving test isolation that use SQLAlchemy. May 19, 2023 4 - Beta pytest (>=7.0) + :pypi:`pytest-sql-bigquery` Yet another SQL-testing framework for BigQuery provided by pytest plugin Dec 19, 2019 N/A pytest + :pypi:`pytest-sqlfluff` A pytest plugin to use sqlfluff to enable format checking of sql files. Dec 21, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-squadcast` Pytest report plugin for Squadcast Feb 22, 2022 5 - Production/Stable pytest + :pypi:`pytest-srcpaths` Add paths to sys.path Oct 15, 2021 N/A pytest>=6.2.0 + :pypi:`pytest-ssh` pytest plugin for ssh command run May 27, 2019 N/A pytest + :pypi:`pytest-start-from` Start pytest run from a given point Apr 11, 2016 N/A N/A + :pypi:`pytest-star-track-issue` A package to prevent Dependency Confusion attacks against Yandex. Feb 20, 2024 N/A N/A + :pypi:`pytest-static` pytest-static Jan 15, 2024 1 - Planning pytest (>=7.4.3,<8.0.0) + :pypi:`pytest-statsd` pytest plugin for reporting to graphite Nov 30, 2018 5 - Production/Stable pytest (>=3.0.0) + :pypi:`pytest-stepfunctions` A small description May 08, 2021 4 - Beta pytest + :pypi:`pytest-steps` Create step-wise / incremental tests in pytest. Sep 23, 2021 5 - Production/Stable N/A + :pypi:`pytest-stepwise` Run a test suite one failing test at a time. Dec 01, 2015 4 - Beta N/A + :pypi:`pytest-stf` pytest plugin for openSTF Mar 25, 2024 N/A pytest>=5.0 + :pypi:`pytest-stoq` A plugin to pytest stoq Feb 09, 2021 4 - Beta N/A + :pypi:`pytest-store` Pytest plugin to store values from test runs Nov 16, 2023 3 - Alpha pytest (>=7.0.0) + :pypi:`pytest-stress` A Pytest plugin that allows you to loop tests for a user defined amount of time. Dec 07, 2019 4 - Beta pytest (>=3.6.0) + :pypi:`pytest-structlog` Structured logging assertions Mar 13, 2024 N/A pytest + :pypi:`pytest-structmpd` provide structured temporary directory Oct 17, 2018 N/A N/A + :pypi:`pytest-stub` Stub packages, modules and attributes. Apr 28, 2020 5 - Production/Stable N/A + :pypi:`pytest-stubprocess` Provide stub implementations for subprocesses in Python tests Sep 17, 2018 3 - Alpha pytest (>=3.5.0) + :pypi:`pytest-study` A pytest plugin to organize long run tests (named studies) without interfering the regular tests Sep 26, 2017 3 - Alpha pytest (>=2.0) + :pypi:`pytest-subinterpreter` Run pytest in a subinterpreter Nov 25, 2023 N/A pytest>=7.0.0 + :pypi:`pytest-subprocess` A plugin to fake subprocess for pytest Jan 28, 2023 5 - Production/Stable pytest (>=4.0.0) + :pypi:`pytest-subtesthack` A hack to explicitly set up and tear down fixtures. Jul 16, 2022 N/A N/A + :pypi:`pytest-subtests` unittest subTest() support and subtests fixture Mar 07, 2024 4 - Beta pytest >=7.0 + :pypi:`pytest-subunit` pytest-subunit is a plugin for py.test which outputs testsresult in subunit format. Sep 17, 2023 N/A pytest (>=2.3) + :pypi:`pytest-sugar` pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). Feb 01, 2024 4 - Beta pytest >=6.2.0 + :pypi:`pytest-suitemanager` A simple plugin to use with pytest Apr 28, 2023 4 - Beta N/A + :pypi:`pytest-suite-timeout` A pytest plugin for ensuring max suite time Jan 26, 2024 N/A pytest>=7.0.0 + :pypi:`pytest-supercov` Pytest plugin for measuring explicit test-file to source-file coverage Jul 02, 2023 N/A N/A + :pypi:`pytest-svn` SVN repository fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-symbols` pytest-symbols is a pytest plugin that adds support for passing test environment symbols into pytest tests. Nov 20, 2017 3 - Alpha N/A + :pypi:`pytest-synodic` Synodic Pytest utilities Mar 09, 2024 N/A pytest>=8.0.2 + :pypi:`pytest-system-statistics` Pytest plugin to track and report system usage statistics Feb 16, 2022 5 - Production/Stable pytest (>=6.0.0) + :pypi:`pytest-system-test-plugin` Pyst - Pytest System-Test Plugin Feb 03, 2022 N/A N/A + :pypi:`pytest_tagging` a pytest plugin to tag tests Apr 08, 2024 N/A pytest<8.0.0,>=7.1.3 + :pypi:`pytest-takeltest` Fixtures for ansible, testinfra and molecule Feb 15, 2023 N/A N/A + :pypi:`pytest-talisker` Nov 28, 2021 N/A N/A + :pypi:`pytest-tally` A Pytest plugin to generate realtime summary stats, and display them in-console using a text-based dashboard. May 22, 2023 4 - Beta pytest (>=6.2.5) + :pypi:`pytest-tap` Test Anything Protocol (TAP) reporting plugin for pytest Jul 15, 2023 5 - Production/Stable pytest (>=3.0) + :pypi:`pytest-tape` easy assertion with expected results saved to yaml files Mar 17, 2021 4 - Beta N/A + :pypi:`pytest-target` Pytest plugin for remote target orchestration. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) + :pypi:`pytest-tblineinfo` tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used Dec 01, 2015 3 - Alpha pytest (>=2.0) + :pypi:`pytest-tcpclient` A pytest plugin for testing TCP clients Nov 16, 2022 N/A pytest (<8,>=7.1.3) + :pypi:`pytest-tdd` run pytest on a python module Aug 18, 2023 4 - Beta N/A + :pypi:`pytest-teamcity-logblock` py.test plugin to introduce block structure in teamcity build log, if output is not captured May 15, 2018 4 - Beta N/A + :pypi:`pytest-telegram` Pytest to Telegram reporting plugin Dec 10, 2020 5 - Production/Stable N/A + :pypi:`pytest-telegram-notifier` Telegram notification plugin for Pytest Jun 27, 2023 5 - Production/Stable N/A + :pypi:`pytest-tempdir` Predictable and repeatable tempdir support. Oct 11, 2019 4 - Beta pytest (>=2.8.1) + :pypi:`pytest-terra-fixt` Terraform and Terragrunt fixtures for pytest Sep 15, 2022 N/A pytest (==6.2.5) + :pypi:`pytest-terraform` A pytest plugin for using terraform fixtures Jun 20, 2023 N/A pytest (>=6.0) + :pypi:`pytest-terraform-fixture` generate terraform resources to use with pytest Nov 14, 2018 4 - Beta N/A + :pypi:`pytest-testbook` A plugin to run tests written in Jupyter notebook Dec 11, 2016 3 - Alpha N/A + :pypi:`pytest-testconfig` Test configuration plugin for pytest. Jan 11, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-testdirectory` A py.test plugin providing temporary directories in unit tests. May 02, 2023 5 - Production/Stable pytest + :pypi:`pytest-testdox` A testdox format reporter for pytest Jul 22, 2023 5 - Production/Stable pytest (>=4.6.0) + :pypi:`pytest-test-grouping` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Feb 01, 2023 5 - Production/Stable pytest (>=2.5) + :pypi:`pytest-test-groups` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Oct 25, 2016 5 - Production/Stable N/A + :pypi:`pytest-testinfra` Test infrastructures Feb 15, 2024 5 - Production/Stable pytest >=6 + :pypi:`pytest-testinfra-jpic` Test infrastructures Sep 21, 2023 5 - Production/Stable N/A + :pypi:`pytest-testinfra-winrm-transport` Test infrastructures Sep 21, 2023 5 - Production/Stable N/A + :pypi:`pytest-testlink-adaptor` pytest reporting plugin for testlink Dec 20, 2018 4 - Beta pytest (>=2.6) + :pypi:`pytest-testmon` selects tests affected by changed files and methods Feb 27, 2024 4 - Beta pytest <9,>=5 + :pypi:`pytest-testmon-dev` selects tests affected by changed files and methods Mar 30, 2023 4 - Beta pytest (<8,>=5) + :pypi:`pytest-testmon-oc` nOly selects tests affected by changed files and methods Jun 01, 2022 4 - Beta pytest (<8,>=5) + :pypi:`pytest-testmon-skip-libraries` selects tests affected by changed files and methods Mar 03, 2023 4 - Beta pytest (<8,>=5) + :pypi:`pytest-testobject` Plugin to use TestObject Suites with Pytest Sep 24, 2019 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-testpluggy` set your encoding Jan 07, 2022 N/A pytest + :pypi:`pytest-testrail` pytest plugin for creating TestRail runs and adding results Aug 27, 2020 N/A pytest (>=3.6) + :pypi:`pytest-testrail2` A pytest plugin to upload results to TestRail. Feb 10, 2023 N/A pytest (<8.0,>=7.2.0) + :pypi:`pytest-testrail-api-client` TestRail Api Python Client Dec 14, 2021 N/A pytest + :pypi:`pytest-testrail-appetize` pytest plugin for creating TestRail runs and adding results Sep 29, 2021 N/A N/A + :pypi:`pytest-testrail-client` pytest plugin for Testrail Sep 29, 2020 5 - Production/Stable N/A + :pypi:`pytest-testrail-e2e` pytest plugin for creating TestRail runs and adding results Oct 11, 2021 N/A pytest (>=3.6) + :pypi:`pytest-testrail-integrator` Pytest plugin for sending report to testrail system. Aug 01, 2022 N/A pytest (>=6.2.5) + :pypi:`pytest-testrail-ns` pytest plugin for creating TestRail runs and adding results Aug 12, 2022 N/A N/A + :pypi:`pytest-testrail-plugin` PyTest plugin for TestRail Apr 21, 2020 3 - Alpha pytest + :pypi:`pytest-testrail-reporter` Sep 10, 2018 N/A N/A + :pypi:`pytest-testrail-results` A pytest plugin to upload results to TestRail. Mar 04, 2024 N/A pytest >=7.2.0 + :pypi:`pytest-testreport` Dec 01, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-testreport-new` Oct 07, 2023 4 - Beta pytest >=3.5.0 + :pypi:`pytest-testslide` TestSlide fixture for pytest Jan 07, 2021 5 - Production/Stable pytest (~=6.2) + :pypi:`pytest-test-this` Plugin for py.test to run relevant tests, based on naively checking if a test contains a reference to the symbol you supply Sep 15, 2019 2 - Pre-Alpha pytest (>=2.3) + :pypi:`pytest-test-utils` Feb 08, 2024 N/A pytest >=3.9 + :pypi:`pytest-tesults` Tesults plugin for pytest Feb 15, 2024 5 - Production/Stable pytest >=3.5.0 + :pypi:`pytest-textual-snapshot` Snapshot testing for Textual apps Aug 23, 2023 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-tezos` pytest-ligo Jan 16, 2020 4 - Beta N/A + :pypi:`pytest-th2-bdd` pytest_th2_bdd May 13, 2022 N/A N/A + :pypi:`pytest-thawgun` Pytest plugin for time travel May 26, 2020 3 - Alpha N/A + :pypi:`pytest-thread` Jul 07, 2023 N/A N/A + :pypi:`pytest-threadleak` Detects thread leaks Jul 03, 2022 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-tick` Ticking on tests Aug 31, 2021 5 - Production/Stable pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-time` Jun 24, 2023 3 - Alpha pytest + :pypi:`pytest-timeassert-ethan` execution duration Dec 25, 2023 N/A pytest + :pypi:`pytest-timeit` A pytest plugin to time test function runs Oct 13, 2016 4 - Beta N/A + :pypi:`pytest-timeout` pytest plugin to abort hanging tests Mar 07, 2024 5 - Production/Stable pytest >=7.0.0 + :pypi:`pytest-timeouts` Linux-only Pytest plugin to control durations of various test case execution phases Sep 21, 2019 5 - Production/Stable N/A + :pypi:`pytest-timer` A timer plugin for pytest Dec 26, 2023 N/A pytest + :pypi:`pytest-timestamper` Pytest plugin to add a timestamp prefix to the pytest output Mar 27, 2024 N/A N/A + :pypi:`pytest-timestamps` A simple plugin to view timestamps for each test Sep 11, 2023 N/A pytest (>=7.3,<8.0) + :pypi:`pytest-tiny-api-client` The companion pytest plugin for tiny-api-client Jan 04, 2024 5 - Production/Stable pytest + :pypi:`pytest-tinybird` A pytest plugin to report test results to tinybird Jun 26, 2023 4 - Beta pytest (>=3.8.0) + :pypi:`pytest-tipsi-django` Better fixtures for django Feb 05, 2024 5 - Production/Stable pytest>=6.0.0 + :pypi:`pytest-tipsi-testing` Better fixtures management. Various helpers Feb 04, 2024 5 - Production/Stable pytest>=3.3.0 + :pypi:`pytest-tldr` A pytest plugin that limits the output to just the things you need. Oct 26, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-tm4j-reporter` Cloud Jira Test Management (TM4J) PyTest reporter plugin Sep 01, 2020 N/A pytest + :pypi:`pytest-tmnet` A small example package Mar 01, 2022 N/A N/A + :pypi:`pytest-tmp-files` Utilities to create temporary file hierarchies in pytest. Dec 08, 2023 N/A pytest + :pypi:`pytest-tmpfs` A pytest plugin that helps you on using a temporary filesystem for testing. Aug 29, 2022 N/A pytest + :pypi:`pytest-tmreport` this is a vue-element ui report for pytest Aug 12, 2022 N/A N/A + :pypi:`pytest-tmux` A pytest plugin that enables tmux driven tests Apr 22, 2023 4 - Beta N/A + :pypi:`pytest-todo` A small plugin for the pytest testing framework, marking TODO comments as failure May 23, 2019 4 - Beta pytest + :pypi:`pytest-tomato` Mar 01, 2019 5 - Production/Stable N/A + :pypi:`pytest-toolbelt` This is just a collection of utilities for pytest, but don't really belong in pytest proper. Aug 12, 2019 3 - Alpha N/A + :pypi:`pytest-toolbox` Numerous useful plugins for pytest. Apr 07, 2018 N/A pytest (>=3.5.0) + :pypi:`pytest-toolkit` Useful utils for testing Apr 13, 2024 N/A N/A + :pypi:`pytest-tools` Pytest tools Oct 21, 2022 4 - Beta N/A + :pypi:`pytest-tornado` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Jun 17, 2020 5 - Production/Stable pytest (>=3.6) + :pypi:`pytest-tornado5` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Nov 16, 2018 5 - Production/Stable pytest (>=3.6) + :pypi:`pytest-tornado-yen3` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Oct 15, 2018 5 - Production/Stable N/A + :pypi:`pytest-tornasync` py.test plugin for testing Python 3.5+ Tornado code Jul 15, 2019 3 - Alpha pytest (>=3.0) + :pypi:`pytest-trace` Save OpenTelemetry spans generated during testing Jun 19, 2022 N/A pytest (>=4.6) + :pypi:`pytest-track` Feb 26, 2021 3 - Alpha pytest (>=3.0) + :pypi:`pytest-translations` Test your translation files. Sep 11, 2023 5 - Production/Stable pytest (>=7) + :pypi:`pytest-travis-fold` Folds captured output sections in Travis CI build log Nov 29, 2017 4 - Beta pytest (>=2.6.0) + :pypi:`pytest-trello` Plugin for py.test that integrates trello using markers Nov 20, 2015 5 - Production/Stable N/A + :pypi:`pytest-trepan` Pytest plugin for trepan debugger. Jul 28, 2018 5 - Production/Stable N/A + :pypi:`pytest-trialtemp` py.test plugin for using the same _trial_temp working directory as trial Jun 08, 2015 N/A N/A + :pypi:`pytest-trio` Pytest plugin for trio Nov 01, 2022 N/A pytest (>=7.2.0) + :pypi:`pytest-trytond` Pytest plugin for the Tryton server framework Nov 04, 2022 4 - Beta pytest (>=5) + :pypi:`pytest-tspwplib` A simple plugin to use with tspwplib Jan 08, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-tst` Customize pytest options, output and exit code to make it compatible with tst Apr 27, 2022 N/A pytest (>=5.0.0) + :pypi:`pytest-tstcls` Test Class Base Mar 23, 2020 5 - Production/Stable N/A + :pypi:`pytest-tui` Text User Interface (TUI) and HTML report for Pytest test runs Dec 08, 2023 4 - Beta N/A + :pypi:`pytest-tutorials` Mar 11, 2023 N/A N/A + :pypi:`pytest-twilio-conversations-client-mock` Aug 02, 2022 N/A N/A + :pypi:`pytest-twisted` A twisted plugin for pytest. Mar 19, 2024 5 - Production/Stable pytest >=2.3 + :pypi:`pytest-typechecker` Run type checkers on specified test files Feb 04, 2022 N/A pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-typhoon-config` A Typhoon HIL plugin that facilitates test parameter configuration at runtime Apr 07, 2022 5 - Production/Stable N/A + :pypi:`pytest-typhoon-polarion` Typhoontest plugin for Siemens Polarion Feb 01, 2024 4 - Beta N/A + :pypi:`pytest-typhoon-xray` Typhoon HIL plugin for pytest Aug 15, 2023 4 - Beta N/A + :pypi:`pytest-tytest` Typhoon HIL plugin for pytest May 25, 2020 4 - Beta pytest (>=5.4.2) + :pypi:`pytest-ubersmith` Easily mock calls to ubersmith at the \`requests\` level. Apr 13, 2015 N/A N/A + :pypi:`pytest-ui` Text User Interface for running python tests Jul 05, 2021 4 - Beta pytest + :pypi:`pytest-ui-failed-screenshot` UI自动测试失败时自动截图,并将截图加入到测试报告中 Dec 06, 2022 N/A N/A + :pypi:`pytest-ui-failed-screenshot-allure` UI自动测试失败时自动截图,并将截图加入到Allure测试报告中 Dec 06, 2022 N/A N/A + :pypi:`pytest-uncollect-if` A plugin to uncollect pytests tests rather than using skipif Mar 24, 2024 4 - Beta pytest>=6.2.0 + :pypi:`pytest-unflakable` Unflakable plugin for PyTest Nov 12, 2023 4 - Beta pytest >=6.2.0 + :pypi:`pytest-unhandled-exception-exit-code` Plugin for py.test set a different exit code on uncaught exceptions Jun 22, 2020 5 - Production/Stable pytest (>=2.3) + :pypi:`pytest-unique` Pytest fixture to generate unique values. Sep 15, 2023 N/A pytest (>=7.4.2,<8.0.0) + :pypi:`pytest-unittest-filter` A pytest plugin for filtering unittest-based test classes Jan 12, 2019 4 - Beta pytest (>=3.1.0) + :pypi:`pytest-unmarked` Run only unmarked tests Aug 27, 2019 5 - Production/Stable N/A + :pypi:`pytest-unordered` Test equality of unordered collections in pytest Mar 13, 2024 4 - Beta pytest >=7.0.0 + :pypi:`pytest-unstable` Set a test as unstable to return 0 even if it failed Sep 27, 2022 4 - Beta N/A + :pypi:`pytest-unused-fixtures` A pytest plugin to list unused fixtures after a test run. Apr 08, 2024 4 - Beta pytest>7.3.2 + :pypi:`pytest-upload-report` pytest-upload-report is a plugin for pytest that upload your test report for test results. Jun 18, 2021 5 - Production/Stable N/A + :pypi:`pytest-utils` Some helpers for pytest. Feb 02, 2023 4 - Beta pytest (>=7.0.0,<8.0.0) + :pypi:`pytest-vagrant` A py.test plugin providing access to vagrant. Sep 07, 2021 5 - Production/Stable pytest + :pypi:`pytest-valgrind` May 19, 2021 N/A N/A + :pypi:`pytest-variables` pytest plugin for providing variables to tests/fixtures Feb 01, 2024 5 - Production/Stable pytest>=7.0.0 + :pypi:`pytest-variant` Variant support for Pytest Jun 06, 2022 N/A N/A + :pypi:`pytest-vcr` Plugin for managing VCR.py cassettes Apr 26, 2019 5 - Production/Stable pytest (>=3.6.0) + :pypi:`pytest-vcr-delete-on-fail` A pytest plugin that automates vcrpy cassettes deletion on test failure. Feb 16, 2024 5 - Production/Stable pytest (>=8.0.0,<9.0.0) + :pypi:`pytest-vcrpandas` Test from HTTP interactions to dataframe processed. Jan 12, 2019 4 - Beta pytest + :pypi:`pytest-vcs` Sep 22, 2022 4 - Beta N/A + :pypi:`pytest-venv` py.test fixture for creating a virtual environment Nov 23, 2023 4 - Beta pytest + :pypi:`pytest-ver` Pytest module with Verification Protocol, Verification Report and Trace Matrix Feb 07, 2024 4 - Beta pytest + :pypi:`pytest-verbose-parametrize` More descriptive output for parametrized py.test tests May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-vimqf` A simple pytest plugin that will shrink pytest output when specified, to fit vim quickfix window. Feb 08, 2021 4 - Beta pytest (>=6.2.2,<7.0.0) + :pypi:`pytest-virtualenv` Virtualenv fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-visual` Nov 01, 2023 3 - Alpha pytest >=7.0.0 + :pypi:`pytest-vnc` VNC client for Pytest Nov 06, 2023 N/A pytest + :pypi:`pytest-voluptuous` Pytest plugin for asserting data against voluptuous schema. Jun 09, 2020 N/A pytest + :pypi:`pytest-vscodedebug` A pytest plugin to easily enable debugging tests within Visual Studio Code Dec 04, 2020 4 - Beta N/A + :pypi:`pytest-vscode-pycharm-cls` A PyTest helper to enable start remote debugger on test start or failure or when pytest.set_trace is used. Feb 01, 2023 N/A pytest + :pypi:`pytest-vts` pytest plugin for automatic recording of http stubbed tests Jun 05, 2019 N/A pytest (>=2.3) + :pypi:`pytest-vulture` A pytest plugin to checks dead code with vulture Jun 01, 2023 N/A pytest (>=7.0.0) + :pypi:`pytest-vw` pytest-vw makes your failing test cases succeed under CI tools scrutiny Oct 07, 2015 4 - Beta N/A + :pypi:`pytest-vyper` Plugin for the vyper smart contract language. May 28, 2020 2 - Pre-Alpha N/A + :pypi:`pytest-wa-e2e-plugin` Pytest plugin for testing whatsapp bots with end to end tests Feb 18, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-wake` Mar 20, 2024 N/A pytest + :pypi:`pytest-watch` Local continuous test runner with pytest and watchdog. May 20, 2018 N/A N/A + :pypi:`pytest-watcher` Automatically rerun your tests on file modifications Apr 01, 2024 4 - Beta N/A + :pypi:`pytest_wdb` Trace pytest tests with wdb to halt on error with --wdb. Jul 04, 2016 N/A N/A + :pypi:`pytest-wdl` Pytest plugin for testing WDL workflows. Nov 17, 2020 5 - Production/Stable N/A + :pypi:`pytest-web3-data` A pytest plugin to fetch test data from IPFS HTTP gateways during pytest execution. Oct 04, 2023 4 - Beta pytest + :pypi:`pytest-webdriver` Selenium webdriver fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-webtest-extras` Pytest plugin to enhance pytest-html and allure reports of webtest projects by adding screenshots, comments and webpage sources. Nov 13, 2023 N/A pytest >= 7.0.0 + :pypi:`pytest-wetest` Welian API Automation test framework pytest plugin Nov 10, 2018 4 - Beta N/A + :pypi:`pytest-when` Utility which makes mocking more readable and controllable Mar 22, 2024 N/A pytest>=7.3.1 + :pypi:`pytest-whirlwind` Testing Tornado. Jun 12, 2020 N/A N/A + :pypi:`pytest-wholenodeid` pytest addon for displaying the whole node id for failures Aug 26, 2015 4 - Beta pytest (>=2.0) + :pypi:`pytest-win32consoletitle` Pytest progress in console title (Win32 only) Aug 08, 2021 N/A N/A + :pypi:`pytest-winnotify` Windows tray notifications for py.test results. Apr 22, 2016 N/A N/A + :pypi:`pytest-wiremock` A pytest plugin for programmatically using wiremock in integration tests Mar 27, 2022 N/A pytest (>=7.1.1,<8.0.0) + :pypi:`pytest-with-docker` pytest with docker helpers. Nov 09, 2021 N/A pytest + :pypi:`pytest-workflow` A pytest plugin for configuring workflow/pipeline tests using YAML files Mar 18, 2024 5 - Production/Stable pytest >=7.0.0 + :pypi:`pytest-xdist` pytest xdist plugin for distributed testing, most importantly across multiple CPUs Apr 19, 2024 5 - Production/Stable pytest >=6.2.0 + :pypi:`pytest-xdist-debug-for-graingert` pytest xdist plugin for distributed testing and loop-on-failing modes Jul 24, 2019 5 - Production/Stable pytest (>=4.4.0) + :pypi:`pytest-xdist-forked` forked from pytest-xdist Feb 10, 2020 5 - Production/Stable pytest (>=4.4.0) + :pypi:`pytest-xdist-tracker` pytest plugin helps to reproduce failures for particular xdist node Nov 18, 2021 3 - Alpha pytest (>=3.5.1) + :pypi:`pytest-xdist-worker-stats` A pytest plugin to list worker statistics after a xdist run. Apr 16, 2024 4 - Beta pytest>=7.0.0 + :pypi:`pytest-xfaillist` Maintain a xfaillist in an additional file to avoid merge-conflicts. Sep 17, 2021 N/A pytest (>=6.2.2,<7.0.0) + :pypi:`pytest-xfiles` Pytest fixtures providing data read from function, module or package related (x)files. Feb 27, 2018 N/A N/A + :pypi:`pytest-xiuyu` This is a pytest plugin Jul 25, 2023 5 - Production/Stable N/A + :pypi:`pytest-xlog` Extended logging for test and decorators May 31, 2020 4 - Beta N/A + :pypi:`pytest-xlsx` pytest plugin for generating test cases by xlsx(excel) Mar 22, 2024 N/A N/A + :pypi:`pytest-xpara` An extended parametrizing plugin of pytest. Oct 30, 2017 3 - Alpha pytest + :pypi:`pytest-xprocess` A pytest plugin for managing processes across test runs. Mar 31, 2024 4 - Beta pytest>=2.8 + :pypi:`pytest-xray` May 30, 2019 3 - Alpha N/A + :pypi:`pytest-xrayjira` Mar 17, 2020 3 - Alpha pytest (==4.3.1) + :pypi:`pytest-xray-server` May 03, 2022 3 - Alpha pytest (>=5.3.1) + :pypi:`pytest-xskynet` A package to prevent Dependency Confusion attacks against Yandex. Feb 20, 2024 N/A N/A + :pypi:`pytest-xvfb` A pytest plugin to run Xvfb (or Xephyr/Xvnc) for tests. May 29, 2023 4 - Beta pytest (>=2.8.1) + :pypi:`pytest-xvirt` A pytest plugin to virtualize test. For example to transparently running them on a remote box. Oct 01, 2023 4 - Beta pytest >=7.1.0 + :pypi:`pytest-yaml` This plugin is used to load yaml output to your test using pytest framework. Oct 05, 2018 N/A pytest + :pypi:`pytest-yaml-sanmu` pytest plugin for generating test cases by yaml Apr 19, 2024 N/A pytest>=7.4.0 + :pypi:`pytest-yamltree` Create or check file/directory trees described by YAML Mar 02, 2020 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-yamlwsgi` Run tests against wsgi apps defined in yaml May 11, 2010 N/A N/A + :pypi:`pytest-yaml-yoyo` http/https API run by yaml Jun 19, 2023 N/A pytest (>=7.2.0) + :pypi:`pytest-yapf` Run yapf Jul 06, 2017 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-yapf3` Validate your Python file format with yapf Mar 29, 2023 5 - Production/Stable pytest (>=7) + :pypi:`pytest-yield` PyTest plugin to run tests concurrently, each \`yield\` switch context to other one Jan 23, 2019 N/A N/A + :pypi:`pytest-yls` Pytest plugin to test the YLS as a whole. Mar 30, 2024 N/A pytest<8.0.0,>=7.2.2 + :pypi:`pytest-yuk` Display tests you are uneasy with, using 🤢/🤮 for pass/fail of tests marked with yuk. Mar 26, 2021 N/A pytest>=5.0.0 + :pypi:`pytest-zafira` A Zafira plugin for pytest Sep 18, 2019 5 - Production/Stable pytest (==4.1.1) + :pypi:`pytest-zap` OWASP ZAP plugin for py.test. May 12, 2014 4 - Beta N/A + :pypi:`pytest-zebrunner` Pytest connector for Zebrunner reporting Jan 08, 2024 5 - Production/Stable pytest (>=4.5.0) + :pypi:`pytest-zeebe` Pytest fixtures for testing Camunda 8 processes using a Zeebe test engine. Feb 01, 2024 N/A pytest (>=7.4.2,<8.0.0) + :pypi:`pytest-zest` Zesty additions to pytest. Nov 17, 2022 N/A N/A + :pypi:`pytest-zhongwen-wendang` PyTest 中文文档 Mar 04, 2024 4 - Beta N/A + :pypi:`pytest-zigzag` Extend py.test for RPC OpenStack testing. Feb 27, 2019 4 - Beta pytest (~=3.6) + :pypi:`pytest-zulip` Pytest report plugin for Zulip May 07, 2022 5 - Production/Stable pytest + :pypi:`pytest-zy` 接口自动化测试框架 Mar 24, 2024 N/A pytest~=7.2.0 + =============================================== ====================================================================================================================================================================================================================================================================================================================================================================================== ============== ===================== ================================================ .. only:: latex + :pypi:`logassert` + *last release*: May 20, 2022, + *status*: 5 - Production/Stable, + *requires*: N/A + + Simple but powerful assertion and verification of logged lines. + + :pypi:`logot` + *last release*: Mar 23, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest<9,>=7; extra == "pytest" + + Test whether your code is logging correctly 🪵 + + :pypi:`nuts` + *last release*: Aug 11, 2023, + *status*: N/A, + *requires*: pytest (>=7.3.0,<8.0.0) + + Network Unit Testing System + + :pypi:`pytest-abq` + *last release*: Apr 07, 2023, + *status*: N/A, + *requires*: N/A + + Pytest integration for the ABQ universal test runner. + + :pypi:`pytest-abstracts` + *last release*: May 25, 2022, + *status*: N/A, + *requires*: N/A + + A contextmanager pytest fixture for handling multiple mock abstracts + :pypi:`pytest-accept` - *last release*: Nov 22, 2021, + *last release*: Feb 10, 2024, *status*: N/A, - *requires*: pytest (>=6,<7) + *requires*: pytest (>=6) A pytest-plugin for updating doctest outputs :pypi:`pytest-adaptavist` - *last release*: Nov 30, 2021, + *last release*: Oct 13, 2022, *status*: N/A, *requires*: pytest (>=5.4.0) pytest plugin for generating test execution results within Jira Test Management (tm4j) + :pypi:`pytest-adaptavist-fixed` + *last release*: Nov 08, 2023, + *status*: N/A, + *requires*: pytest >=5.4.0 + + pytest plugin for generating test execution results within Jira Test Management (tm4j) + :pypi:`pytest-addons-test` *last release*: Aug 02, 2021, *status*: N/A, @@ -1021,6 +1564,20 @@ This list contains 963 plugins. Pytest plugin for writing Azure Data Factory integration tests + :pypi:`pytest-ads-testplan` + *last release*: Sep 15, 2022, + *status*: N/A, + *requires*: N/A + + Azure DevOps Test Case reporting for pytest tests + + :pypi:`pytest-affected` + *last release*: Nov 06, 2023, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-agent` *last release*: Nov 25, 2021, *status*: N/A, @@ -1035,9 +1592,16 @@ This list contains 963 plugins. pytest plugin for pytest-repeat that generate aggregate report of the same test cases with additional statistics details. + :pypi:`pytest-ai1899` + *last release*: Mar 13, 2024, + *status*: 5 - Production/Stable, + *requires*: N/A + + pytest plugin for connecting to ai1899 smart system stack + :pypi:`pytest-aio` - *last release*: Oct 20, 2021, - *status*: 4 - Beta, + *last release*: Apr 08, 2024, + *status*: 5 - Production/Stable, *requires*: pytest Pytest plugin for testing async python code @@ -1049,20 +1613,34 @@ This list contains 963 plugins. pytest fixtures for writing aiofiles tests with pyfakefs - :pypi:`pytest-aiohttp` - *last release*: Dec 05, 2017, + :pypi:`pytest-aiogram` + *last release*: May 06, 2023, *status*: N/A, - *requires*: pytest + *requires*: N/A + - pytest plugin for aiohttp support + + :pypi:`pytest-aiohttp` + *last release*: Sep 06, 2023, + *status*: 4 - Beta, + *requires*: pytest >=6.1.0 + + Pytest plugin for aiohttp support :pypi:`pytest-aiohttp-client` - *last release*: Nov 01, 2020, + *last release*: Jan 10, 2023, *status*: N/A, - *requires*: pytest (>=6) + *requires*: pytest (>=7.2.0,<8.0.0) Pytest \`client\` fixture for the Aiohttp + :pypi:`pytest-aiomoto` + *last release*: Jun 24, 2023, + *status*: N/A, + *requires*: pytest (>=7.0,<8.0) + + pytest-aiomoto + :pypi:`pytest-aioresponses` *last release*: Jul 29, 2021, *status*: 4 - Beta, @@ -1071,9 +1649,9 @@ This list contains 963 plugins. py.test integration for aioresponses :pypi:`pytest-aioworkers` - *last release*: Dec 04, 2019, - *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + *last release*: May 01, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest>=6.1.0 A plugin to test aioworkers project with pytest @@ -1092,9 +1670,9 @@ This list contains 963 plugins. :pypi:`pytest-alembic` - *last release*: Dec 02, 2021, + *last release*: Mar 04, 2024, *status*: N/A, - *requires*: pytest (>=1.0) + *requires*: pytest (>=6.0) A pytest plugin for verifying alembic migrations. @@ -1119,6 +1697,13 @@ This list contains 963 plugins. Plugin for py.test to generate allure xml reports + :pypi:`pytest-allure-collection` + *last release*: Apr 13, 2023, + *status*: N/A, + *requires*: pytest + + pytest plugin to collect allure markers without running any tests + :pypi:`pytest-allure-dsl` *last release*: Oct 25, 2020, *status*: 4 - Beta, @@ -1126,6 +1711,13 @@ This list contains 963 plugins. pytest plugin to test case doc string dls instructions + :pypi:`pytest-allure-intersection` + *last release*: Oct 27, 2022, + *status*: N/A, + *requires*: pytest (<5) + + + :pypi:`pytest-allure-spec-coverage` *last release*: Oct 26, 2021, *status*: N/A, @@ -1134,12 +1726,19 @@ This list contains 963 plugins. The pytest plugin aimed to display test coverage of the specs(requirements) in Allure :pypi:`pytest-alphamoon` - *last release*: Oct 21, 2021, - *status*: 4 - Beta, + *last release*: Dec 30, 2021, + *status*: 5 - Production/Stable, *requires*: pytest (>=3.5.0) Static code checks used at Alphamoon + :pypi:`pytest-analyzer` + *last release*: Feb 21, 2024, + *status*: N/A, + *requires*: pytest <8.0.0,>=7.3.1 + + this plugin allows to analyze tests in pytest project, collect test metadata and sync it with testomat.io TCM system + :pypi:`pytest-android` *last release*: Feb 21, 2019, *status*: 3 - Alpha, @@ -1148,25 +1747,25 @@ This list contains 963 plugins. This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. :pypi:`pytest-anki` - *last release*: Oct 14, 2021, + *last release*: Jul 31, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) A pytest plugin for testing Anki add-ons :pypi:`pytest-annotate` - *last release*: Nov 29, 2021, + *last release*: Jun 07, 2022, *status*: 3 - Alpha, - *requires*: pytest (<7.0.0,>=3.2.0) + *requires*: pytest (<8.0.0,>=3.2.0) pytest-annotate: Generate PyAnnotate annotations from your pytest tests. :pypi:`pytest-ansible` - *last release*: May 25, 2021, + *last release*: Jan 18, 2024, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest >=6 - Plugin for py.test to simplify calling ansible modules from tests or fixtures + Plugin for pytest to simplify calling ansible modules from tests or fixtures :pypi:`pytest-ansible-playbook` *last release*: Mar 08, 2019, @@ -1182,8 +1781,15 @@ This list contains 963 plugins. Pytest fixture which runs given ansible playbook file. + :pypi:`pytest-ansible-units` + *last release*: Apr 14, 2022, + *status*: N/A, + *requires*: N/A + + A pytest plugin for running unit tests within an ansible collection + :pypi:`pytest-antilru` - *last release*: Apr 11, 2019, + *last release*: Jul 05, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -1197,25 +1803,39 @@ This list contains 963 plugins. The pytest anyio plugin is built into anyio. You don't need this package. :pypi:`pytest-anything` - *last release*: Feb 18, 2021, + *last release*: Jan 18, 2024, *status*: N/A, - *requires*: N/A + *requires*: pytest Pytest fixtures to assert anything and something :pypi:`pytest-aoc` - *last release*: Nov 23, 2021, - *status*: N/A, + *last release*: Dec 02, 2023, + *status*: 5 - Production/Stable, *requires*: pytest ; extra == 'test' Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures + :pypi:`pytest-aoreporter` + *last release*: Jun 27, 2022, + *status*: N/A, + *requires*: N/A + + pytest report + :pypi:`pytest-api` - *last release*: May 04, 2021, + *last release*: May 12, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.1,<8.0.0) + + An ASGI middleware to populate OpenAPI Specification examples from pytest functions + + :pypi:`pytest-api-soup` + *last release*: Aug 27, 2022, *status*: N/A, *requires*: N/A - PyTest-API Python Web Framework built for testing purposes. + Validate multiple endpoints with unit testing using a single source of truth. :pypi:`pytest-apistellar` *last release*: Jun 18, 2019, @@ -1239,12 +1859,26 @@ This list contains 963 plugins. Pytest plugin for appium :pypi:`pytest-approvaltests` - *last release*: Feb 07, 2021, + *last release*: May 08, 2022, *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + *requires*: pytest (>=7.0.1) A plugin to use approvaltests with pytest + :pypi:`pytest-approvaltests-geo` + *last release*: Feb 05, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest + + Extension for ApprovalTests.Python specific to geo data verification + + :pypi:`pytest-archon` + *last release*: Dec 18, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest >=7.2 + + Rule your architecture like a real developer + :pypi:`pytest-argus` *last release*: Jun 24, 2021, *status*: 5 - Production/Stable, @@ -1253,9 +1887,9 @@ This list contains 963 plugins. pyest results colection plugin :pypi:`pytest-arraydiff` - *last release*: Dec 06, 2018, + *last release*: Nov 27, 2023, *status*: 4 - Beta, - *requires*: pytest + *requires*: pytest >=4.6 pytest plugin to help with comparing array output from tests @@ -1266,6 +1900,13 @@ This list contains 963 plugins. Convenient ASGI client/server fixtures for Pytest + :pypi:`pytest-aspec` + *last release*: Dec 20, 2023, + *status*: 4 - Beta, + *requires*: N/A + + A rspec format reporter for pytest + :pypi:`pytest-asptest` *last release*: Apr 28, 2018, *status*: 4 - Beta, @@ -1273,6 +1914,20 @@ This list contains 963 plugins. test Answer Set Programming programs + :pypi:`pytest-assertcount` + *last release*: Oct 23, 2022, + *status*: N/A, + *requires*: pytest (>=5.0.0) + + Plugin to count actual number of asserts in pytest + + :pypi:`pytest-assertions` + *last release*: Apr 27, 2022, + *status*: N/A, + *requires*: N/A + + Pytest Assertions + :pypi:`pytest-assertutil` *last release*: May 10, 2019, *status*: N/A, @@ -1281,7 +1936,7 @@ This list contains 963 plugins. pytest-assertutil :pypi:`pytest-assert-utils` - *last release*: Sep 21, 2021, + *last release*: Apr 14, 2022, *status*: 3 - Alpha, *requires*: N/A @@ -1294,6 +1949,13 @@ This list contains 963 plugins. A pytest plugin that allows multiple failures per test + :pypi:`pytest-assurka` + *last release*: Aug 04, 2022, + *status*: N/A, + *requires*: N/A + + A pytest plugin for Assurka Studio + :pypi:`pytest-ast-back-to-python` *last release*: Sep 29, 2019, *status*: 4 - Beta, @@ -1301,17 +1963,24 @@ This list contains 963 plugins. A plugin for pytest devs to view how assertion rewriting recodes the AST + :pypi:`pytest-asteroid` + *last release*: Aug 15, 2022, + *status*: N/A, + *requires*: pytest (>=6.2.5,<8.0.0) + + PyTest plugin for docker-based testing on database images + :pypi:`pytest-astropy` - *last release*: Sep 21, 2021, + *last release*: Sep 26, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=4.6) + *requires*: pytest >=4.6 Meta-package containing dependencies for testing :pypi:`pytest-astropy-header` - *last release*: Dec 18, 2019, + *last release*: Sep 06, 2022, *status*: 3 - Alpha, - *requires*: pytest (>=2.8) + *requires*: pytest (>=4.6) pytest plugin to add diagnostic information to the header of the test output @@ -1322,16 +1991,30 @@ This list contains 963 plugins. + :pypi:`pytest_async` + *last release*: Feb 26, 2020, + *status*: N/A, + *requires*: N/A + + pytest-async - Run your coroutine in event loop without decorator + + :pypi:`pytest-async-generators` + *last release*: Jul 05, 2023, + *status*: N/A, + *requires*: N/A + + Pytest fixtures for async generators + :pypi:`pytest-asyncio` - *last release*: Oct 15, 2021, + *last release*: Mar 19, 2024, *status*: 4 - Beta, - *requires*: pytest (>=5.4.0) + *requires*: pytest <9,>=7.0.0 - Pytest support for asyncio. + Pytest support for asyncio :pypi:`pytest-asyncio-cooperative` - *last release*: Oct 12, 2021, - *status*: 4 - Beta, + *last release*: Feb 25, 2024, + *status*: N/A, *requires*: N/A Run all your asynchronous tests cooperatively. @@ -1357,6 +2040,13 @@ This list contains 963 plugins. Database testing fixtures using the SQLAlchemy asyncio API + :pypi:`pytest-atf-allure` + *last release*: Nov 29, 2023, + *status*: N/A, + *requires*: pytest (>=7.4.2,<8.0.0) + + 基于allure-pytest进行自定义 + :pypi:`pytest-atomic` *last release*: Nov 24, 2018, *status*: 4 - Beta, @@ -1378,6 +2068,13 @@ This list contains 963 plugins. Austin plugin for pytest + :pypi:`pytest-autocap` + *last release*: May 15, 2022, + *status*: N/A, + *requires*: pytest (<7.2,>=7.1.2) + + automatically capture test & fixture stdout/stderr to files + :pypi:`pytest-autochecklog` *last release*: Apr 25, 2015, *status*: 4 - Beta, @@ -1386,14 +2083,14 @@ This list contains 963 plugins. automatically check condition and log all the checks :pypi:`pytest-automation` - *last release*: Oct 01, 2021, + *last release*: May 20, 2022, *status*: N/A, - *requires*: pytest + *requires*: pytest (>=7.0.0) pytest plugin for building a test suite, using YAML files to extend pytest parameterize functionality. :pypi:`pytest-automock` - *last release*: Apr 22, 2020, + *last release*: May 16, 2023, *status*: N/A, *requires*: pytest ; extra == 'dev' @@ -1413,6 +2110,13 @@ This list contains 963 plugins. This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. + :pypi:`pytest-aviator` + *last release*: Nov 04, 2022, + *status*: 4 - Beta, + *requires*: pytest + + Aviator's Flakybot pytest plugin that automatically reruns flaky tests. + :pypi:`pytest-avoidance` *last release*: May 23, 2019, *status*: 4 - Beta, @@ -1434,6 +2138,13 @@ This list contains 963 plugins. Protect your AWS credentials in unit tests + :pypi:`pytest-aws-fixtures` + *last release*: Feb 02, 2024, + *status*: N/A, + *requires*: pytest (>=8.0.0,<9.0.0) + + A series of fixtures to use in integration tests involving actual AWS services. + :pypi:`pytest-axe` *last release*: Nov 12, 2018, *status*: N/A, @@ -1441,11 +2152,32 @@ This list contains 963 plugins. pytest plugin for axe-selenium-python - :pypi:`pytest-azurepipelines` - *last release*: Jul 23, 2020, + :pypi:`pytest-axe-playwright-snapshot` + *last release*: Jul 25, 2023, + *status*: N/A, + *requires*: pytest + + A pytest plugin that runs Axe-core on Playwright pages and takes snapshots of the results. + + :pypi:`pytest-azure` + *last release*: Jan 18, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + Pytest utilities and mocks for Azure + + :pypi:`pytest-azure-devops` + *last release*: Jun 20, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) + Simplifies using azure devops parallel strategy (https://docs.microsoft.com/en-us/azure/devops/pipelines/test/parallel-testing-any-test-runner) with pytest. + + :pypi:`pytest-azurepipelines` + *last release*: Oct 06, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=5.0.0) + Formatting PyTest output for Azure Pipelines UI :pypi:`pytest-bandit` @@ -1455,20 +2187,48 @@ This list contains 963 plugins. A bandit plugin for pytest + :pypi:`pytest-bandit-xayon` + *last release*: Oct 17, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A bandit plugin for pytest + :pypi:`pytest-base-url` - *last release*: Jun 19, 2020, + *last release*: Jan 31, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.7.3) + *requires*: pytest>=7.0.0 pytest plugin for URL based testing :pypi:`pytest-bdd` - *last release*: Oct 25, 2021, + *last release*: Mar 17, 2024, *status*: 6 - Mature, - *requires*: pytest (>=4.3) + *requires*: pytest (>=6.2.0) BDD for pytest + :pypi:`pytest-bdd-html` + *last release*: Nov 22, 2022, + *status*: 3 - Alpha, + *requires*: pytest (!=6.0.0,>=5.0) + + pytest plugin to display BDD info in HTML test report + + :pypi:`pytest-bdd-ng` + *last release*: Dec 31, 2023, + *status*: 4 - Beta, + *requires*: pytest >=5.0 + + BDD for pytest + + :pypi:`pytest-bdd-report` + *last release*: Feb 19, 2024, + *status*: N/A, + *requires*: pytest >=7.1.3 + + A pytest-bdd plugin for generating useful and informative BDD test reports + :pypi:`pytest-bdd-splinter` *last release*: Aug 12, 2019, *status*: 5 - Production/Stable, @@ -1497,6 +2257,20 @@ This list contains 963 plugins. A pytest plugin that reports test results to the BeakerLib framework + :pypi:`pytest-beartype` + *last release*: Jan 25, 2024, + *status*: N/A, + *requires*: pytest + + Pytest plugin to run your tests with beartype checking enabled. + + :pypi:`pytest-bec-e2e` + *last release*: Apr 19, 2024, + *status*: 3 - Alpha, + *requires*: pytest + + BEC pytest plugin for end-to-end tests + :pypi:`pytest-beds` *last release*: Jun 07, 2016, *status*: 4 - Beta, @@ -1504,6 +2278,13 @@ This list contains 963 plugins. Fixtures for testing Google Appengine (GAE) apps + :pypi:`pytest-beeprint` + *last release*: Jul 04, 2023, + *status*: 4 - Beta, + *requires*: N/A + + use icdiff for better error messages in pytest assertions + :pypi:`pytest-bench` *last release*: Jul 21, 2014, *status*: 3 - Alpha, @@ -1512,33 +2293,54 @@ This list contains 963 plugins. Benchmark utility that plugs into pytest. :pypi:`pytest-benchmark` - *last release*: Apr 17, 2021, + *last release*: Oct 25, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=3.8) A \`\`pytest\`\` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer. + :pypi:`pytest-better-datadir` + *last release*: Mar 13, 2023, + *status*: N/A, + *requires*: N/A + + A small example package + + :pypi:`pytest-better-parametrize` + *last release*: Mar 05, 2024, + *status*: 4 - Beta, + *requires*: pytest >=6.2.0 + + Better description of parametrized test cases + :pypi:`pytest-bg-process` - *last release*: Aug 17, 2021, + *last release*: Jan 24, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) Pytest plugin to initialize background process :pypi:`pytest-bigchaindb` - *last release*: Aug 17, 2021, + *last release*: Jan 24, 2022, *status*: 4 - Beta, *requires*: N/A A BigchainDB plugin for pytest. :pypi:`pytest-bigquery-mock` - *last release*: Aug 05, 2021, + *last release*: Dec 28, 2022, *status*: N/A, *requires*: pytest (>=5.0) Provides a mock fixture for python bigquery client + :pypi:`pytest-bisect-tests` + *last release*: Mar 25, 2024, + *status*: N/A, + *requires*: N/A + + Find tests leaking state and affecting other + :pypi:`pytest-black` *last release*: Oct 05, 2020, *status*: 4 - Beta, @@ -1553,6 +2355,13 @@ This list contains 963 plugins. Allow '--black' on older Pythons + :pypi:`pytest-black-ng` + *last release*: Oct 20, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=7.0.0) + + A pytest plugin to enable format checking with black + :pypi:`pytest-blame` *last release*: May 04, 2019, *status*: N/A, @@ -1561,9 +2370,9 @@ This list contains 963 plugins. A pytest plugin helps developers to debug by providing useful commits history. :pypi:`pytest-blender` - *last release*: Oct 29, 2021, + *last release*: Aug 10, 2023, *status*: N/A, - *requires*: pytest (==6.2.5) ; extra == 'dev' + *requires*: pytest ; extra == 'dev' Blender Pytest plugin. @@ -1575,7 +2384,7 @@ This list contains 963 plugins. Pytest plugin to emit notifications via the Blink(1) RGB LED :pypi:`pytest-blockage` - *last release*: Feb 13, 2019, + *last release*: Dec 21, 2021, *status*: N/A, *requires*: pytest @@ -1588,6 +2397,13 @@ This list contains 963 plugins. pytest plugin to mark a test as blocker and skip all other tests + :pypi:`pytest-blue` + *last release*: Sep 05, 2022, + *status*: N/A, + *requires*: N/A + + A pytest plugin that adds a \`blue\` fixture for printing stuff in blue. + :pypi:`pytest-board` *last release*: Jan 20, 2019, *status*: N/A, @@ -1595,6 +2411,20 @@ This list contains 963 plugins. Local continuous test runner with pytest and watchdog. + :pypi:`pytest-boost-xml` + *last release*: Nov 30, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Plugin for pytest to generate boost xml reports + + :pypi:`pytest-bootstrap` + *last release*: Mar 04, 2022, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-bpdb` *last release*: Jan 19, 2015, *status*: 2 - Pre-Alpha, @@ -1603,7 +2433,7 @@ This list contains 963 plugins. A py.test plug-in to enable drop to bpdb debugger on test failure. :pypi:`pytest-bravado` - *last release*: Jul 19, 2021, + *last release*: Feb 15, 2022, *status*: N/A, *requires*: N/A @@ -1630,6 +2460,13 @@ This list contains 963 plugins. A pytest plugin for running tests on a Briefcase project. + :pypi:`pytest-broadcaster` + *last release*: Apr 06, 2024, + *status*: 3 - Alpha, + *requires*: pytest + + Pytest plugin to broadcast pytest output to various destinations + :pypi:`pytest-browser` *last release*: Dec 10, 2016, *status*: 3 - Alpha, @@ -1644,6 +2481,13 @@ This list contains 963 plugins. BrowserMob proxy plugin for py.test. + :pypi:`pytest_browserstack` + *last release*: Jan 27, 2016, + *status*: 4 - Beta, + *requires*: N/A + + Py.test plugin for BrowserStack + :pypi:`pytest-browserstack-local` *last release*: Feb 09, 2018, *status*: N/A, @@ -1651,15 +2495,22 @@ This list contains 963 plugins. \`\`py.test\`\` plugin to run \`\`BrowserStackLocal\`\` in background. + :pypi:`pytest-budosystems` + *last release*: May 07, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + Budo Systems is a martial arts school management system. This module is the Budo Systems Pytest Plugin. + :pypi:`pytest-bug` - *last release*: Jun 02, 2020, + *last release*: Sep 23, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.6.0) + *requires*: pytest >=7.1.0 Pytest plugin for marking tests as a bug :pypi:`pytest-bugtong-tag` - *last release*: Apr 23, 2021, + *last release*: Jan 16, 2022, *status*: N/A, *requires*: N/A @@ -1694,7 +2545,7 @@ This list contains 963 plugins. :pypi:`pytest-bwrap` - *last release*: Oct 26, 2018, + *last release*: Feb 25, 2024, *status*: 3 - Alpha, *requires*: N/A @@ -1708,9 +2559,9 @@ This list contains 963 plugins. pytest plugin with mechanisms for caching across test runs :pypi:`pytest-cache-assert` - *last release*: Nov 03, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=5) + *last release*: Aug 14, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=6.0.0) Cache assertion data to simplify regression testing of complex serializable data @@ -1721,8 +2572,22 @@ This list contains 963 plugins. Pytest plugin to only run tests affected by changes - :pypi:`pytest-camel-collect` - *last release*: Aug 02, 2020, + :pypi:`pytest-cairo` + *last release*: Apr 17, 2022, + *status*: N/A, + *requires*: pytest + + Pytest support for cairo-lang and starknet + + :pypi:`pytest-call-checker` + *last release*: Oct 16, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=7.1.3,<8.0.0) + + Small pytest utility to easily create test doubles + + :pypi:`pytest-camel-collect` + *last release*: Aug 02, 2020, *status*: N/A, *requires*: pytest (>=2.9) @@ -1749,15 +2614,15 @@ This list contains 963 plugins. pytest plugin to capture all deprecatedwarnings and put them in one file - :pypi:`pytest-capturelogs` - *last release*: Sep 11, 2021, - *status*: 3 - Alpha, - *requires*: N/A + :pypi:`pytest-capture-warnings` + *last release*: May 03, 2022, + *status*: N/A, + *requires*: pytest - A sample Python project + pytest plugin to capture all warnings and put them in one file of your choice :pypi:`pytest-cases` - *last release*: Nov 08, 2021, + *last release*: Apr 04, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -1785,11 +2650,18 @@ This list contains 963 plugins. Pytest plugin with server for catching HTTP requests. :pypi:`pytest-celery` - *last release*: May 06, 2021, + *last release*: Apr 11, 2024, + *status*: 4 - Beta, + *requires*: N/A + + Pytest plugin for Celery + + :pypi:`pytest-cfg-fetcher` + *last release*: Feb 26, 2024, *status*: N/A, *requires*: N/A - pytest-celery a shim pytest plugin to enable celery.contrib.pytest + Pass config options to your unit tests. :pypi:`pytest-chainmaker` *last release*: Oct 15, 2021, @@ -1805,6 +2677,20 @@ This list contains 963 plugins. A set of py.test fixtures for AWS Chalice + :pypi:`pytest-change-assert` + *last release*: Oct 19, 2022, + *status*: N/A, + *requires*: N/A + + 修改报错中文为英文 + + :pypi:`pytest-change-demo` + *last release*: Mar 02, 2022, + *status*: N/A, + *requires*: pytest + + turn . into √,turn F into x + :pypi:`pytest-change-report` *last release*: Sep 14, 2020, *status*: N/A, @@ -1812,6 +2698,13 @@ This list contains 963 plugins. turn . into √,turn F into x + :pypi:`pytest-change-xds` + *last release*: Apr 16, 2022, + *status*: N/A, + *requires*: pytest + + turn . into √,turn F into x + :pypi:`pytest-chdir` *last release*: Jan 28, 2020, *status*: N/A, @@ -1819,27 +2712,55 @@ This list contains 963 plugins. A pytest fixture for changing current working directory + :pypi:`pytest-check` + *last release*: Jan 18, 2024, + *status*: N/A, + *requires*: pytest>=7.0.0 + + A pytest plugin that allows multiple failures per test. + :pypi:`pytest-checkdocs` - *last release*: Jul 31, 2021, + *last release*: Mar 31, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=4.6) ; extra == 'testing' + *requires*: pytest>=6; extra == "testing" check the README when running tests :pypi:`pytest-checkipdb` - *last release*: Jul 22, 2020, + *last release*: Dec 04, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.9.2) + *requires*: pytest >=2.9.2 plugin to check if there are ipdb debugs left + :pypi:`pytest-check-library` + *last release*: Jul 17, 2022, + *status*: N/A, + *requires*: N/A + + check your missing library + + :pypi:`pytest-check-libs` + *last release*: Jul 17, 2022, + *status*: N/A, + *requires*: N/A + + check your missing library + :pypi:`pytest-check-links` *last release*: Jul 29, 2020, *status*: N/A, - *requires*: pytest (>=4.6) + *requires*: pytest<9,>=7.0 Check links in files + :pypi:`pytest-checklist` + *last release*: Mar 12, 2024, + *status*: N/A, + *requires*: N/A + + Pytest plugin to track and report unit/function coverage. + :pypi:`pytest-check-mk` *last release*: Nov 19, 2015, *status*: 4 - Beta, @@ -1847,6 +2768,48 @@ This list contains 963 plugins. pytest plugin to test Check_MK checks + :pypi:`pytest-check-requirements` + *last release*: Feb 20, 2024, + *status*: N/A, + *requires*: N/A + + A package to prevent Dependency Confusion attacks against Yandex. + + :pypi:`pytest-ch-framework` + *last release*: Apr 17, 2024, + *status*: N/A, + *requires*: pytest==8.0.1 + + My pytest framework + + :pypi:`pytest-chic-report` + *last release*: Jan 31, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + A pytest plugin to send a report and printing summary of tests. + + :pypi:`pytest-choose` + *last release*: Feb 04, 2024, + *status*: N/A, + *requires*: pytest >=7.0.0 + + Provide the pytest with the ability to collect use cases based on rules in text files + + :pypi:`pytest-chunks` + *last release*: Jul 05, 2022, + *status*: N/A, + *requires*: pytest (>=6.0.0) + + Run only a chunk of your test suite + + :pypi:`pytest_cid` + *last release*: Sep 01, 2023, + *status*: 4 - Beta, + *requires*: pytest >= 5.0, < 7.0 + + Compare data structures containing matching CIDs of different versions and encoding + :pypi:`pytest-circleci` *last release*: May 03, 2019, *status*: N/A, @@ -1855,12 +2818,19 @@ This list contains 963 plugins. py.test plugin for CircleCI :pypi:`pytest-circleci-parallelized` - *last release*: Mar 26, 2019, + *last release*: Oct 20, 2022, *status*: N/A, *requires*: N/A Parallelize pytest across CircleCI workers. + :pypi:`pytest-circleci-parallelized-rjp` + *last release*: Jun 21, 2022, + *status*: N/A, + *requires*: pytest + + Parallelize pytest across CircleCI workers. + :pypi:`pytest-ckan` *last release*: Apr 28, 2020, *status*: 4 - Beta, @@ -1876,21 +2846,49 @@ This list contains 963 plugins. A plugin providing an alternative, colourful diff output for failing assertions. :pypi:`pytest-cldf` - *last release*: May 06, 2019, + *last release*: Nov 07, 2022, *status*: N/A, - *requires*: N/A + *requires*: pytest (>=3.6) Easy quality control for CLDF datasets using pytest + :pypi:`pytest_cleanup` + *last release*: Jan 28, 2020, + *status*: N/A, + *requires*: N/A + + Automated, comprehensive and well-organised pytest test cases. + + :pypi:`pytest-cleanuptotal` + *last release*: Mar 19, 2024, + *status*: 5 - Production/Stable, + *requires*: N/A + + A cleanup plugin for pytest + + :pypi:`pytest-clerk` + *last release*: Apr 19, 2024, + *status*: N/A, + *requires*: pytest<9.0.0,>=8.0.0 + + A set of pytest fixtures to help with integration testing with Clerk. + :pypi:`pytest-click` - *last release*: Aug 29, 2020, + *last release*: Feb 11, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=5.0) - Py.test plugin for Click + Pytest plugin for Click + + :pypi:`pytest-cli-fixtures` + *last release*: Jul 28, 2022, + *status*: N/A, + *requires*: pytest (~=7.0) + + Automatically register fixtures for custom CLI arguments :pypi:`pytest-clld` - *last release*: Nov 29, 2021, + *last release*: Jul 06, 2022, *status*: N/A, *requires*: pytest (>=3.6) @@ -1910,6 +2908,27 @@ This list contains 963 plugins. pytest plugin for testing cloudflare workers + :pypi:`pytest-cloudist` + *last release*: Sep 02, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=7.1.2,<8.0.0) + + Distribute tests to cloud machines without fuss + + :pypi:`pytest-cmake` + *last release*: Mar 18, 2024, + *status*: N/A, + *requires*: pytest<9,>=4 + + Provide CMake module for Pytest + + :pypi:`pytest-cmake-presets` + *last release*: Dec 26, 2022, + *status*: N/A, + *requires*: pytest (>=7.2.0,<8.0.0) + + Execute CMake Presets via pytest + :pypi:`pytest-cobra` *last release*: Jun 29, 2019, *status*: 3 - Alpha, @@ -1917,13 +2936,20 @@ This list contains 963 plugins. PyTest plugin for testing Smart Contracts for Ethereum blockchain. - :pypi:`pytest-codeblocks` - *last release*: Oct 13, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=6) + :pypi:`pytest_codeblocks` + *last release*: Sep 17, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest >= 7.0.0 Test code blocks in your READMEs + :pypi:`pytest-codecarbon` + *last release*: Jun 15, 2022, + *status*: N/A, + *requires*: pytest + + Pytest plugin for measuring carbon emissions + :pypi:`pytest-codecheckers` *last release*: Feb 13, 2010, *status*: N/A, @@ -1932,7 +2958,7 @@ This list contains 963 plugins. pytest plugin to add source code sanity checks (pep8 and friends) :pypi:`pytest-codecov` - *last release*: Oct 27, 2021, + *last release*: Nov 29, 2022, *status*: 4 - Beta, *requires*: pytest (>=4.6.0) @@ -1945,6 +2971,13 @@ This list contains 963 plugins. Automatically create pytest test signatures + :pypi:`pytest-codeowners` + *last release*: Mar 30, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=6.0.0) + + Pytest plugin for selecting tests by GitHub CODEOWNERS. + :pypi:`pytest-codestyle` *last release*: Mar 23, 2020, *status*: 3 - Alpha, @@ -1952,6 +2985,20 @@ This list contains 963 plugins. pytest plugin to run pycodestyle + :pypi:`pytest-codspeed` + *last release*: Mar 19, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest>=3.8 + + Pytest plugin to create CodSpeed benchmarks + + :pypi:`pytest-collect-appoint-info` + *last release*: Aug 03, 2023, + *status*: N/A, + *requires*: pytest + + set your encoding + :pypi:`pytest-collect-formatter` *last release*: Mar 29, 2021, *status*: 5 - Production/Stable, @@ -1966,6 +3013,27 @@ This list contains 963 plugins. Formatter for pytest collect output + :pypi:`pytest-collect-interface-info-plugin` + *last release*: Sep 25, 2023, + *status*: 4 - Beta, + *requires*: N/A + + Get executed interface information in pytest interface automation framework + + :pypi:`pytest-collector` + *last release*: Aug 02, 2022, + *status*: N/A, + *requires*: pytest (>=7.0,<8.0) + + Python package for collecting pytest. + + :pypi:`pytest-collect-pytest-interinfo` + *last release*: Sep 26, 2023, + *status*: 4 - Beta, + *requires*: N/A + + A simple plugin to use with pytest + :pypi:`pytest-colordots` *last release*: Oct 06, 2017, *status*: 5 - Production/Stable, @@ -1981,12 +3049,19 @@ This list contains 963 plugins. An interactive GUI test runner for PyTest :pypi:`pytest-common-subject` - *last release*: Nov 12, 2020, + *last release*: May 15, 2022, *status*: N/A, - *requires*: pytest (>=3.6,<7) + *requires*: pytest (>=3.6,<8) pytest framework for testing different aspects of a common method + :pypi:`pytest-compare` + *last release*: Jun 22, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + pytest plugin for comparing call arguments. + :pypi:`pytest-concurrent` *last release*: Jan 12, 2019, *status*: 4 - Beta, @@ -2002,16 +3077,16 @@ This list contains 963 plugins. Base configurations and utilities for developing your Python project test suite with pytest. :pypi:`pytest-confluence-report` - *last release*: Nov 06, 2020, + *last release*: Apr 17, 2022, *status*: N/A, *requires*: N/A Package stands for pytest plugin to upload results into Confluence page. :pypi:`pytest-console-scripts` - *last release*: Sep 28, 2021, + *last release*: May 31, 2023, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest (>=4.0.0) Pytest plugin for testing console scripts @@ -2023,9 +3098,9 @@ This list contains 963 plugins. pytest plugin with fixtures for testing consul aware apps :pypi:`pytest-container` - *last release*: Nov 19, 2021, - *status*: 3 - Alpha, - *requires*: pytest (>=3.10) + *last release*: Apr 10, 2024, + *status*: 4 - Beta, + *requires*: pytest>=3.10 Pytest fixtures for writing container based tests @@ -2044,12 +3119,26 @@ This list contains 963 plugins. A plugin to run tests written with the Contexts framework using pytest :pypi:`pytest-cookies` - *last release*: May 24, 2021, + *last release*: Mar 22, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.3.0) + *requires*: pytest (>=3.9.0) The pytest plugin for your Cookiecutter templates. 🍪 + :pypi:`pytest-copie` + *last release*: Jan 27, 2024, + *status*: 3 - Alpha, + *requires*: pytest + + The pytest plugin for your copier templates 📒 + + :pypi:`pytest-copier` + *last release*: Dec 11, 2023, + *status*: 4 - Beta, + *requires*: pytest>=7.3.2 + + A pytest plugin to help testing Copier templates + :pypi:`pytest-couchdbkit` *last release*: Apr 17, 2012, *status*: N/A, @@ -2065,9 +3154,9 @@ This list contains 963 plugins. count erros and send email :pypi:`pytest-cov` - *last release*: Oct 04, 2021, + *last release*: Mar 24, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=4.6) + *requires*: pytest>=4.6 Pytest plugin for measuring coverage. @@ -2086,12 +3175,19 @@ This list contains 963 plugins. :pypi:`pytest-coverage-context` - *last release*: Jan 04, 2021, + *last release*: Jun 28, 2023, *status*: 4 - Beta, - *requires*: pytest (>=6.1.0) + *requires*: N/A Coverage dynamic context support for PyTest, including sub-processes + :pypi:`pytest-coveragemarkers` + *last release*: Apr 15, 2024, + *status*: N/A, + *requires*: pytest<8.0.0,>=7.1.2 + + Using pytest markers to track functional coverage and filtering of tests + :pypi:`pytest-cov-exclude` *last release*: Apr 29, 2016, *status*: 4 - Beta, @@ -2099,13 +3195,34 @@ This list contains 963 plugins. Pytest plugin for excluding tests based on coverage data + :pypi:`pytest_covid` + *last release*: Jun 24, 2020, + *status*: N/A, + *requires*: N/A + + Too many faillure, less tests. + :pypi:`pytest-cpp` - *last release*: Dec 03, 2021, + *last release*: Nov 01, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (!=5.4.0,!=5.4.1) + *requires*: pytest >=7.0 Use pytest's runner to discover and execute C++ tests + :pypi:`pytest-cppython` + *last release*: Mar 14, 2024, + *status*: N/A, + *requires*: N/A + + A pytest plugin that imports CPPython testing types + + :pypi:`pytest-cqase` + *last release*: Aug 22, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.2,<8.0.0) + + Custom qase pytest plugin + :pypi:`pytest-cram` *last release*: Aug 08, 2020, *status*: N/A, @@ -2120,6 +3237,20 @@ This list contains 963 plugins. Manages CrateDB instances during your integration tests + :pypi:`pytest-crayons` + *last release*: Oct 08, 2023, + *status*: N/A, + *requires*: pytest + + A pytest plugin for colorful print statements + + :pypi:`pytest-create` + *last release*: Feb 15, 2023, + *status*: 1 - Planning, + *requires*: N/A + + pytest-create + :pypi:`pytest-cricri` *last release*: Jan 27, 2018, *status*: N/A, @@ -2141,6 +3272,13 @@ This list contains 963 plugins. CSV output for pytest. + :pypi:`pytest-csv-params` + *last release*: Jul 01, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.4.0,<8.0.0) + + Pytest plugin for Test Case Parametrization with CSV files + :pypi:`pytest-curio` *last release*: Oct 07, 2020, *status*: N/A, @@ -2191,16 +3329,23 @@ This list contains 963 plugins. Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report :pypi:`pytest-cython` - *last release*: Jan 26, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=2.7.3) + *last release*: Apr 05, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest>=8 A plugin for testing Cython extension modules + :pypi:`pytest-cython-collect` + *last release*: Jun 17, 2022, + *status*: N/A, + *requires*: pytest + + + :pypi:`pytest-darker` - *last release*: Aug 16, 2020, + *last release*: Feb 25, 2024, *status*: N/A, - *requires*: pytest (>=6.0.1) ; extra == 'test' + *requires*: pytest <7,>=6.0.1 A pytest plugin for checking of modified code using Darker @@ -2211,6 +3356,13 @@ This list contains 963 plugins. pytest fixtures to run dash applications. + :pypi:`pytest-dashboard` + *last release*: Apr 18, 2024, + *status*: N/A, + *requires*: pytest<8.0.0,>=7.4.3 + + + :pypi:`pytest-data` *last release*: Nov 01, 2016, *status*: 5 - Production/Stable, @@ -2218,6 +3370,13 @@ This list contains 963 plugins. Useful functions for managing data for pytest fixtures + :pypi:`pytest-databases` + *last release*: Apr 19, 2024, + *status*: 4 - Beta, + *requires*: pytest + + Reusable database fixtures for any and all databases. + :pypi:`pytest-databricks` *last release*: Jul 29, 2020, *status*: N/A, @@ -2226,18 +3385,18 @@ This list contains 963 plugins. Pytest plugin for remote Databricks notebooks testing :pypi:`pytest-datadir` - *last release*: Oct 22, 2019, + *last release*: Oct 03, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.7.0) + *requires*: pytest >=5.0 pytest plugin for test data directories and files :pypi:`pytest-datadir-mgr` - *last release*: Aug 16, 2021, + *last release*: Apr 06, 2023, *status*: 5 - Production/Stable, - *requires*: pytest + *requires*: pytest (>=7.1) - Manager for test data providing downloads, caching of generated files, and a context for temp directories. + Manager for test data: downloads, artifact caching, and a tmpdir context. :pypi:`pytest-datadir-ng` *last release*: Dec 25, 2019, @@ -2246,6 +3405,20 @@ This list contains 963 plugins. Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. + :pypi:`pytest-datadir-nng` + *last release*: Nov 09, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.0.0,<8.0.0) + + Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. + + :pypi:`pytest-data-extractor` + *last release*: Jul 19, 2022, + *status*: N/A, + *requires*: pytest (>=7.0.1) + + A pytest plugin to extract relevant metadata about tests into an external file (currently only json support) + :pypi:`pytest-data-file` *last release*: Dec 04, 2019, *status*: N/A, @@ -2254,11 +3427,11 @@ This list contains 963 plugins. Fixture "data" and "case_data" for test from yaml file :pypi:`pytest-datafiles` - *last release*: Oct 07, 2018, + *last release*: Feb 24, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=3.6) - py.test plugin to create a 'tmpdir' containing predefined files/directories. + py.test plugin to create a 'tmp_path' containing predefined files/directories. :pypi:`pytest-datafixtures` *last release*: Dec 05, 2020, @@ -2282,12 +3455,26 @@ This list contains 963 plugins. A pytest plugin for managing an archive of test data. :pypi:`pytest-datarecorder` - *last release*: Apr 20, 2020, + *last release*: Feb 15, 2024, *status*: 5 - Production/Stable, *requires*: pytest A py.test plugin recording and comparing test output. + :pypi:`pytest-dataset` + *last release*: Sep 01, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + Plugin for loading different datasets for pytest by prefix from json or yaml files + + :pypi:`pytest-data-suites` + *last release*: Apr 06, 2024, + *status*: N/A, + *requires*: pytest<9.0,>=6.0 + + Class-based pytest parametrization + :pypi:`pytest-datatest` *last release*: Oct 15, 2020, *status*: 4 - Beta, @@ -2316,6 +3503,13 @@ This list contains 963 plugins. + :pypi:`pytest-dbt` + *last release*: Jun 08, 2023, + *status*: 2 - Pre-Alpha, + *requires*: pytest (>=7.0.0,<8.0.0) + + Unit test dbt models with standard python tooling + :pypi:`pytest-dbt-adapter` *last release*: Nov 24, 2021, *status*: N/A, @@ -2323,6 +3517,27 @@ This list contains 963 plugins. A pytest plugin for testing dbt adapter plugins + :pypi:`pytest-dbt-conventions` + *last release*: Mar 02, 2022, + *status*: N/A, + *requires*: pytest (>=6.2.5,<7.0.0) + + A pytest plugin for linting a dbt project's conventions + + :pypi:`pytest-dbt-core` + *last release*: Aug 25, 2023, + *status*: N/A, + *requires*: pytest >=6.2.5 ; extra == 'test' + + Pytest extension for dbt. + + :pypi:`pytest-dbt-postgres` + *last release*: Jan 02, 2024, + *status*: N/A, + *requires*: pytest (>=7.4.3,<8.0.0) + + Pytest tooling to unittest DBT & Postgres models + :pypi:`pytest-dbus-notification` *last release*: Mar 05, 2014, *status*: 5 - Production/Stable, @@ -2330,6 +3545,20 @@ This list contains 963 plugins. D-BUS notifications for pytest results. + :pypi:`pytest-dbx` + *last release*: Nov 29, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.3,<8.0.0) + + Pytest plugin to run unit tests for dbx (Databricks CLI extensions) related code + + :pypi:`pytest-dc` + *last release*: Aug 16, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest >=3.3 + + Manages Docker containers during your integration tests + :pypi:`pytest-deadfixtures` *last release*: Jul 23, 2020, *status*: 5 - Production/Stable, @@ -2337,6 +3566,13 @@ This list contains 963 plugins. A simple plugin to list unused fixtures in pytest + :pypi:`pytest-deduplicate` + *last release*: Aug 12, 2023, + *status*: 4 - Beta, + *requires*: pytest + + Identifies duplicate unit tests + :pypi:`pytest-deepcov` *last release*: Mar 30, 2021, *status*: N/A, @@ -2359,7 +3595,7 @@ This list contains 963 plugins. pytest示例插件 :pypi:`pytest-dependency` - *last release*: Feb 14, 2020, + *last release*: Dec 31, 2023, *status*: 4 - Beta, *requires*: N/A @@ -2380,9 +3616,9 @@ This list contains 963 plugins. Mark tests as testing a deprecated feature with a warning note. :pypi:`pytest-describe` - *last release*: Nov 13, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=4.0.0) + *last release*: Feb 10, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest <9,>=4.6 Describe-style plugin for pytest @@ -2393,6 +3629,13 @@ This list contains 963 plugins. plugin for rich text descriptions + :pypi:`pytest-deselect-if` + *last release*: Mar 24, 2024, + *status*: 4 - Beta, + *requires*: pytest>=6.2.0 + + A plugin to deselect pytests tests rather than using skipif + :pypi:`pytest-devpi-server` *last release*: May 28, 2019, *status*: 5 - Production/Stable, @@ -2400,6 +3643,13 @@ This list contains 963 plugins. DevPI server fixture for py.test + :pypi:`pytest-dhos` + *last release*: Sep 07, 2022, + *status*: N/A, + *requires*: N/A + + Common fixtures for pytest in DHOS services and libraries + :pypi:`pytest-diamond` *last release*: Aug 31, 2015, *status*: 4 - Beta, @@ -2428,31 +3678,66 @@ This list contains 963 plugins. A simple plugin to use with pytest - :pypi:`pytest-disable` - *last release*: Sep 10, 2015, - *status*: 4 - Beta, + :pypi:`pytest-diffeo` + *last release*: Feb 20, 2024, + *status*: N/A, *requires*: N/A - pytest plugin to disable a test and skip it from testrun + A package to prevent Dependency Confusion attacks against Yandex. - :pypi:`pytest-disable-plugin` - *last release*: Feb 28, 2019, + :pypi:`pytest-diff-selector` + *last release*: Feb 24, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=6.2.2) ; extra == 'all' + + Get tests affected by code changes (using git) + + :pypi:`pytest-difido` + *last release*: Oct 23, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=4.0.0) + + PyTest plugin for generating Difido reports + + :pypi:`pytest-dir-equal` + *last release*: Dec 11, 2023, + *status*: 4 - Beta, + *requires*: pytest>=7.3.2 + + pytest-dir-equals is a pytest plugin providing helpers to assert directories equality allowing golden testing + + :pypi:`pytest-disable` + *last release*: Sep 10, 2015, + *status*: 4 - Beta, + *requires*: N/A + + pytest plugin to disable a test and skip it from testrun + + :pypi:`pytest-disable-plugin` + *last release*: Feb 28, 2019, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) Disable plugins per test :pypi:`pytest-discord` - *last release*: Mar 20, 2021, - *status*: 3 - Alpha, - *requires*: pytest (!=6.0.0,<7,>=3.3.2) + *last release*: Oct 18, 2023, + *status*: 4 - Beta, + *requires*: pytest !=6.0.0,<8,>=3.3.2 A pytest plugin to notify test results to a Discord channel. + :pypi:`pytest-discover` + *last release*: Mar 26, 2024, + *status*: N/A, + *requires*: pytest + + Pytest plugin to record discovered tests in a file + :pypi:`pytest-django` - *last release*: Dec 02, 2021, + *last release*: Jan 30, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=5.4.0) + *requires*: pytest >=7.0.0 A Django plugin for pytest. @@ -2464,9 +3749,9 @@ This list contains 963 plugins. A Django plugin for pytest. :pypi:`pytest-djangoapp` - *last release*: Aug 04, 2021, + *last release*: May 19, 2023, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest Nice pytest plugin to help you with Django pluggable application testing. @@ -2484,6 +3769,20 @@ This list contains 963 plugins. Integrate CasperJS with your django tests as a pytest fixture. + :pypi:`pytest-django-class` + *last release*: Aug 08, 2023, + *status*: 4 - Beta, + *requires*: N/A + + A pytest plugin for running django in class-scoped fixtures + + :pypi:`pytest-django-docker-pg` + *last release*: Jan 30, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest <8.0.0,>=7.0.0 + + + :pypi:`pytest-django-dotenv` *last release*: Nov 26, 2019, *status*: 4 - Beta, @@ -2498,6 +3797,13 @@ This list contains 963 plugins. Factories for your Django models that can be used as Pytest fixtures. + :pypi:`pytest-django-filefield` + *last release*: May 09, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest >= 5.2 + + Replaces FileField.storage with something you can patch globally. + :pypi:`pytest-django-gcir` *last release*: Mar 06, 2018, *status*: 5 - Production/Stable, @@ -2513,8 +3819,8 @@ This list contains 963 plugins. Cleanup your Haystack indexes between tests :pypi:`pytest-django-ifactory` - *last release*: Jan 13, 2021, - *status*: 3 - Alpha, + *last release*: Aug 27, 2023, + *status*: 5 - Production/Stable, *requires*: N/A A model instance factory for pytest-django @@ -2527,7 +3833,7 @@ This list contains 963 plugins. The bare minimum to integrate py.test with Django. :pypi:`pytest-django-liveserver-ssl` - *last release*: Jul 30, 2021, + *last release*: Jan 20, 2022, *status*: 3 - Alpha, *requires*: N/A @@ -2576,8 +3882,8 @@ This list contains 963 plugins. py.test plugin for reporting the number of SQLs executed per django testcase. :pypi:`pytest-django-testing-postgresql` - *last release*: Dec 05, 2019, - *status*: 3 - Alpha, + *last release*: Jan 31, 2022, + *status*: 4 - Beta, *requires*: N/A Use a temporary PostgreSQL database with pytest-django @@ -2589,6 +3895,13 @@ This list contains 963 plugins. A documentation plugin for py.test. + :pypi:`pytest-docfiles` + *last release*: Dec 22, 2021, + *status*: 4 - Beta, + *requires*: pytest (>=3.7.0) + + pytest plugin to test codeblocks in your documentation. + :pypi:`pytest-docgen` *last release*: Apr 17, 2020, *status*: N/A, @@ -2597,11 +3910,18 @@ This list contains 963 plugins. An RST Documentation Generator for pytest-based test suites :pypi:`pytest-docker` - *last release*: Jun 14, 2021, + *last release*: Feb 02, 2024, *status*: N/A, - *requires*: pytest (<7.0,>=4.0) + *requires*: pytest <9.0,>=4.0 + + Simple pytest fixtures for Docker and Docker Compose based tests + + :pypi:`pytest-docker-apache-fixtures` + *last release*: Feb 16, 2022, + *status*: 4 - Beta, + *requires*: pytest - Simple pytest fixtures for Docker and docker-compose based tests + Pytest fixtures for testing with apache2 (httpd). :pypi:`pytest-docker-butla` *last release*: Jun 16, 2019, @@ -2624,6 +3944,13 @@ This list contains 963 plugins. Manages Docker containers during your integration tests + :pypi:`pytest-docker-compose-v2` + *last release*: Feb 28, 2024, + *status*: 4 - Beta, + *requires*: pytest<8,>=7.2.2 + + Manages Docker containers during your integration tests + :pypi:`pytest-docker-db` *last release*: Mar 20, 2021, *status*: 5 - Production/Stable, @@ -2632,19 +3959,26 @@ This list contains 963 plugins. A plugin to use docker databases for pytests :pypi:`pytest-docker-fixtures` - *last release*: Nov 23, 2021, + *last release*: Apr 03, 2024, *status*: 3 - Alpha, *requires*: N/A pytest docker fixtures :pypi:`pytest-docker-git-fixtures` - *last release*: Mar 11, 2021, + *last release*: Feb 09, 2022, *status*: 4 - Beta, *requires*: pytest Pytest fixtures for testing with git scm. + :pypi:`pytest-docker-haproxy-fixtures` + *last release*: Feb 09, 2022, + *status*: 4 - Beta, + *requires*: pytest + + Pytest fixtures for testing with haproxy. + :pypi:`pytest-docker-pexpect` *last release*: Jan 14, 2019, *status*: N/A, @@ -2667,16 +4001,30 @@ This list contains 963 plugins. Easy to use, simple to extend, pytest plugin that minimally leverages docker-py. :pypi:`pytest-docker-registry-fixtures` - *last release*: Mar 04, 2021, + *last release*: Apr 08, 2022, *status*: 4 - Beta, *requires*: pytest Pytest fixtures for testing with docker registries. + :pypi:`pytest-docker-service` + *last release*: Jan 03, 2024, + *status*: 3 - Alpha, + *requires*: pytest (>=7.1.3) + + pytest plugin to start docker container + + :pypi:`pytest-docker-squid-fixtures` + *last release*: Feb 09, 2022, + *status*: 4 - Beta, + *requires*: pytest + + Pytest fixtures for testing with squid. + :pypi:`pytest-docker-tools` - *last release*: Jul 23, 2021, + *last release*: Feb 17, 2022, *status*: 4 - Beta, - *requires*: pytest (>=6.0.1,<7.0.0) + *requires*: pytest (>=6.0.1) Docker integration tests for pytest @@ -2715,19 +4063,33 @@ This list contains 963 plugins. A simple pytest plugin to import names and add them to the doctest namespace. + :pypi:`pytest-doctest-mkdocstrings` + *last release*: Mar 02, 2024, + *status*: N/A, + *requires*: pytest + + Run pytest --doctest-modules with markdown docstrings in code blocks (\`\`\`) + :pypi:`pytest-doctestplus` - *last release*: Nov 16, 2021, - *status*: 3 - Alpha, - *requires*: pytest (>=4.6) + *last release*: Mar 10, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest >=4.6 Pytest plugin with advanced doctest features. - :pypi:`pytest-doctest-ufunc` - *last release*: Aug 02, 2020, - *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + :pypi:`pytest-dogu-report` + *last release*: Jul 07, 2023, + *status*: N/A, + *requires*: N/A - A plugin to run doctests in docstrings of Numpy ufuncs + pytest plugin for dogu report + + :pypi:`pytest-dogu-sdk` + *last release*: Dec 14, 2023, + *status*: N/A, + *requires*: N/A + + pytest plugin for the Dogu :pypi:`pytest-dolphin` *last release*: Nov 30, 2016, @@ -2736,6 +4098,13 @@ This list contains 963 plugins. Some extra stuff that we use ininternally + :pypi:`pytest-donde` + *last release*: Oct 01, 2023, + *status*: 4 - Beta, + *requires*: pytest >=7.3.1 + + record pytest session characteristics per test item (coverage and duration) into a persistent file and use them in your own plugin or script. + :pypi:`pytest-doorstop` *last release*: Jun 09, 2020, *status*: 4 - Beta, @@ -2750,10 +4119,24 @@ This list contains 963 plugins. A py.test plugin that parses environment files before running tests + :pypi:`pytest-dot-only-pkcopley` + *last release*: Oct 27, 2023, + *status*: N/A, + *requires*: N/A + + A Pytest marker for only running a single test + + :pypi:`pytest-draw` + *last release*: Mar 21, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + Pytest plugin for randomly selecting a specific number of tests + :pypi:`pytest-drf` - *last release*: Nov 12, 2020, + *last release*: Jul 12, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.6) + *requires*: pytest (>=3.7) A Django REST framework plugin for pytest. @@ -2765,14 +4148,21 @@ This list contains 963 plugins. Tool to allow webdriver automation to be ran locally or remotely :pypi:`pytest-drop-dup-tests` - *last release*: May 23, 2020, - *status*: 4 - Beta, - *requires*: pytest (>=2.7) + *last release*: Mar 04, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest >=7 A Pytest plugin to drop duplicated tests during collection + :pypi:`pytest-dryrun` + *last release*: Jul 18, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.4.0,<8.0.0) + + A Pytest plugin to ignore tests during collection without reporting them in the test summary. + :pypi:`pytest-dummynet` - *last release*: Oct 13, 2021, + *last release*: Dec 15, 2021, *status*: 5 - Production/Stable, *requires*: pytest @@ -2792,6 +4182,13 @@ This list contains 963 plugins. + :pypi:`pytest-durations` + *last release*: Apr 22, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=4.6) + + Pytest plugin reporting fixtures and test functions execution time. + :pypi:`pytest-dynamicrerun` *last release*: Aug 15, 2020, *status*: 4 - Beta, @@ -2800,7 +4197,7 @@ This list contains 963 plugins. A pytest plugin to rerun tests dynamically based off of test outcome and output. :pypi:`pytest-dynamodb` - *last release*: Jun 03, 2021, + *last release*: Mar 12, 2024, *status*: 5 - Production/Stable, *requires*: pytest @@ -2814,11 +4211,11 @@ This list contains 963 plugins. pytest-easy-addoption: Easy way to work with pytest addoption :pypi:`pytest-easy-api` - *last release*: Mar 26, 2018, + *last release*: Feb 16, 2024, *status*: N/A, *requires*: N/A - Simple API testing with pytest + A package to prevent Dependency Confusion attacks against Yandex. :pypi:`pytest-easyMPI` *last release*: Oct 21, 2020, @@ -2841,6 +4238,13 @@ This list contains 963 plugins. Pytest plugin for easy testing against servers + :pypi:`pytest-ebics-sandbox` + *last release*: Aug 15, 2022, + *status*: N/A, + *requires*: N/A + + A pytest plugin for testing against an EBICS sandbox server. Requires docker. + :pypi:`pytest-ec2` *last release*: Oct 22, 2019, *status*: 3 - Alpha, @@ -2849,16 +4253,23 @@ This list contains 963 plugins. Pytest execution on EC2 instance :pypi:`pytest-echo` - *last release*: Jan 08, 2020, + *last release*: Dec 05, 2023, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest >=2.2 pytest plugin with mechanisms for echoing environment variables, package version and generic attributes + :pypi:`pytest-ekstazi` + *last release*: Sep 10, 2022, + *status*: N/A, + *requires*: pytest + + Pytest plugin to select test using Ekstazi algorithm + :pypi:`pytest-elasticsearch` - *last release*: May 12, 2021, + *last release*: Mar 15, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.0.0) + *requires*: pytest >=7.0 Elasticsearch fixtures and fixture factories for Pytest. @@ -2869,10 +4280,17 @@ This list contains 963 plugins. Tool to help automate user interfaces + :pypi:`pytest-eliot` + *last release*: Aug 31, 2022, + *status*: 1 - Planning, + *requires*: pytest (>=5.4.0) + + An eliot plugin for pytest. + :pypi:`pytest-elk-reporter` - *last release*: Jan 24, 2021, + *last release*: Apr 04, 2024, *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + *requires*: pytest>=3.5.0 A simple plugin to use with pytest @@ -2884,53 +4302,67 @@ This list contains 963 plugins. Send execution result email :pypi:`pytest-embedded` - *last release*: Nov 29, 2021, - *status*: N/A, - *requires*: pytest (>=6.2.0) + *last release*: Apr 09, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest>=7.0 + + A pytest plugin that designed for embedded testing. - pytest embedded plugin + :pypi:`pytest-embedded-arduino` + *last release*: Apr 09, 2024, + *status*: 5 - Production/Stable, + *requires*: N/A + + Make pytest-embedded plugin work with Arduino. :pypi:`pytest-embedded-idf` - *last release*: Nov 29, 2021, - *status*: N/A, + *last release*: Apr 09, 2024, + *status*: 5 - Production/Stable, *requires*: N/A - pytest embedded plugin for esp-idf project + Make pytest-embedded plugin work with ESP-IDF. :pypi:`pytest-embedded-jtag` - *last release*: Nov 29, 2021, - *status*: N/A, + *last release*: Apr 09, 2024, + *status*: 5 - Production/Stable, *requires*: N/A - pytest embedded plugin for testing with jtag + Make pytest-embedded plugin work with JTAG. :pypi:`pytest-embedded-qemu` - *last release*: Nov 29, 2021, - *status*: N/A, + *last release*: Apr 09, 2024, + *status*: 5 - Production/Stable, *requires*: N/A - pytest embedded plugin for qemu, not target chip + Make pytest-embedded plugin work with QEMU. - :pypi:`pytest-embedded-qemu-idf` - *last release*: Jun 29, 2021, - *status*: N/A, + :pypi:`pytest-embedded-serial` + *last release*: Apr 09, 2024, + *status*: 5 - Production/Stable, *requires*: N/A - pytest embedded plugin for esp-idf project by qemu, not target chip + Make pytest-embedded plugin work with Serial. - :pypi:`pytest-embedded-serial` - *last release*: Nov 29, 2021, - *status*: N/A, + :pypi:`pytest-embedded-serial-esp` + *last release*: Apr 09, 2024, + *status*: 5 - Production/Stable, *requires*: N/A - pytest embedded plugin for testing serial ports + Make pytest-embedded plugin work with Espressif target boards. - :pypi:`pytest-embedded-serial-esp` - *last release*: Nov 29, 2021, - *status*: N/A, + :pypi:`pytest-embedded-wokwi` + *last release*: Apr 09, 2024, + *status*: 5 - Production/Stable, *requires*: N/A - pytest embedded plugin for testing espressif boards via serial ports + Make pytest-embedded plugin work with the Wokwi CLI. + + :pypi:`pytest-embrace` + *last release*: Mar 25, 2023, + *status*: N/A, + *requires*: pytest (>=7.0,<8.0) + + 💝 Dataclasses-as-tests. Describe the runtime once and multiply coverage with no boilerplate. :pypi:`pytest-emoji` *last release*: Feb 19, 2019, @@ -2940,16 +4372,16 @@ This list contains 963 plugins. A pytest plugin that adds emojis to your test result report :pypi:`pytest-emoji-output` - *last release*: Oct 10, 2021, + *last release*: Apr 09, 2023, *status*: 4 - Beta, - *requires*: pytest (==6.0.1) + *requires*: pytest (==7.0.1) Pytest plugin to represent test output with emoji support :pypi:`pytest-enabler` - *last release*: Nov 08, 2021, + *last release*: Mar 21, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=6) ; extra == 'testing' + *requires*: pytest>=6; extra == "testing" Enable installed pytest plugins @@ -2967,6 +4399,27 @@ This list contains 963 plugins. set your encoding and logger + :pypi:`pytest-encoding` + *last release*: Aug 11, 2023, + *status*: N/A, + *requires*: pytest + + set your encoding and logger + + :pypi:`pytest_energy_reporter` + *last release*: Mar 28, 2024, + *status*: 3 - Alpha, + *requires*: pytest<9.0.0,>=8.1.1 + + An energy estimation reporter for pytest + + :pypi:`pytest-enhanced-reports` + *last release*: Dec 15, 2022, + *status*: N/A, + *requires*: N/A + + Enhanced test reports for pytest + :pypi:`pytest-enhancements` *last release*: Oct 30, 2019, *status*: 4 - Beta, @@ -2975,11 +4428,11 @@ This list contains 963 plugins. Improvements for pytest (rejected upstream) :pypi:`pytest-env` - *last release*: Jun 16, 2017, - *status*: 4 - Beta, - *requires*: N/A + *last release*: Nov 28, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest>=7.4.3 - py.test plugin that allows you to add environment variables. + pytest plugin that allows you to add environment variables. :pypi:`pytest-envfiles` *last release*: Oct 08, 2015, @@ -2995,6 +4448,13 @@ This list contains 963 plugins. Push information about the running pytest into envvars + :pypi:`pytest-environment` + *last release*: Mar 17, 2024, + *status*: 1 - Planning, + *requires*: N/A + + Pytest Environment + :pypi:`pytest-envraw` *last release*: Aug 27, 2020, *status*: 4 - Beta, @@ -3023,6 +4483,13 @@ This list contains 963 plugins. pytest plugin to check for commented out code + :pypi:`pytest_erp` + *last release*: Jan 13, 2015, + *status*: N/A, + *requires*: N/A + + py.test plugin to send test info to report portal dynamically + :pypi:`pytest-error-for-skips` *last release*: Dec 19, 2019, *status*: 4 - Beta, @@ -3045,7 +4512,7 @@ This list contains 963 plugins. pytest-ethereum: Pytest library for ethereum projects. :pypi:`pytest-eucalyptus` - *last release*: Aug 13, 2019, + *last release*: Jun 28, 2022, *status*: N/A, *requires*: pytest (>=4.2.0) @@ -3058,8 +4525,36 @@ This list contains 963 plugins. Applies eventlet monkey-patch as a pytest plugin. + :pypi:`pytest-evm` + *last release*: Apr 20, 2024, + *status*: 4 - Beta, + *requires*: pytest<9.0.0,>=8.1.1 + + The testing package containing tools to test Web3-based projects + + :pypi:`pytest_exact_fixtures` + *last release*: Feb 04, 2019, + *status*: N/A, + *requires*: N/A + + Parse queries in Lucene and Elasticsearch syntaxes + + :pypi:`pytest-examples` + *last release*: Jul 11, 2023, + *status*: 4 - Beta, + *requires*: pytest>=7 + + Pytest plugin for testing examples in docstrings and markdown files. + + :pypi:`pytest-exasol-itde` + *last release*: Feb 15, 2024, + *status*: N/A, + *requires*: pytest (>=7,<9) + + + :pypi:`pytest-excel` - *last release*: Oct 06, 2020, + *last release*: Sep 14, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -3080,12 +4575,26 @@ This list contains 963 plugins. Walk your code through exception script to check it's resiliency to failures. :pypi:`pytest-executable` - *last release*: Nov 10, 2021, - *status*: 4 - Beta, - *requires*: pytest (<6.3,>=4.3) + *last release*: Oct 07, 2023, + *status*: N/A, + *requires*: pytest <8,>=5 pytest plugin for testing executables + :pypi:`pytest-execution-timer` + *last release*: Dec 24, 2021, + *status*: 4 - Beta, + *requires*: N/A + + A timer for the phases of Pytest's execution. + + :pypi:`pytest-exit-code` + *last release*: Feb 23, 2024, + *status*: 4 - Beta, + *requires*: pytest >=6.2.0 + + A pytest plugin that overrides the built-in exit codes to retain more information about the test results. + :pypi:`pytest-expect` *last release*: Apr 21, 2016, *status*: 4 - Beta, @@ -3093,8 +4602,15 @@ This list contains 963 plugins. py.test plugin to store test expectations and mark tests based on them + :pypi:`pytest-expectdir` + *last release*: Mar 19, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=5.0) + + A pytest plugin to provide initial/expected directories, and check a test transforms the initial directory to the expected one + :pypi:`pytest-expecter` - *last release*: Jul 08, 2020, + *last release*: Sep 18, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -3107,6 +4623,20 @@ This list contains 963 plugins. This plugin is used to expect multiple assert using pytest framework. + :pypi:`pytest-expect-test` + *last release*: Apr 10, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A fixture to support expect tests in pytest + + :pypi:`pytest-experiments` + *last release*: Dec 13, 2021, + *status*: 4 - Beta, + *requires*: pytest (>=6.2.5,<7.0.0) + + A pytest plugin to help developers of research-oriented software projects keep track of the results of their numerical experiments. + :pypi:`pytest-explicit` *last release*: Jun 15, 2021, *status*: 5 - Production/Stable, @@ -3115,12 +4645,33 @@ This list contains 963 plugins. A Pytest plugin to ignore certain marked tests by default :pypi:`pytest-exploratory` - *last release*: Aug 03, 2021, + *last release*: Aug 18, 2023, *status*: N/A, - *requires*: pytest (>=5.3) + *requires*: pytest (>=6.2) Interactive console for pytest. + :pypi:`pytest-explorer` + *last release*: Aug 01, 2023, + *status*: N/A, + *requires*: N/A + + terminal ui for exploring and running tests + + :pypi:`pytest-ext` + *last release*: Mar 31, 2024, + *status*: N/A, + *requires*: pytest>=5.3 + + pytest plugin for automation test + + :pypi:`pytest-extensions` + *last release*: Aug 17, 2022, + *status*: 4 - Beta, + *requires*: pytest ; extra == 'testing' + + A collection of helpers for pytest to ease testing + :pypi:`pytest-external-blockers` *last release*: Oct 05, 2021, *status*: N/A, @@ -3128,6 +4679,13 @@ This list contains 963 plugins. a special outcome for tests that are blocked for external reasons + :pypi:`pytest_extra` + *last release*: Aug 14, 2014, + *status*: N/A, + *requires*: N/A + + Some helpers for writing tests with pytest. + :pypi:`pytest-extra-durations` *last release*: Apr 21, 2020, *status*: 4 - Beta, @@ -3135,6 +4693,13 @@ This list contains 963 plugins. A pytest plugin to get durations on a per-function basis and per module basis. + :pypi:`pytest-extra-markers` + *last release*: Mar 05, 2023, + *status*: 4 - Beta, + *requires*: pytest + + Additional pytest markers to dynamically enable/disable tests viia CLI flags + :pypi:`pytest-fabric` *last release*: Sep 12, 2018, *status*: 5 - Production/Stable, @@ -3142,6 +4707,13 @@ This list contains 963 plugins. Provides test utilities to run fabric task tests by using docker containers + :pypi:`pytest-factor` + *last release*: Feb 20, 2024, + *status*: N/A, + *requires*: N/A + + A package to prevent Dependency Confusion attacks against Yandex. + :pypi:`pytest-factory` *last release*: Sep 06, 2020, *status*: 3 - Alpha, @@ -3150,9 +4722,9 @@ This list contains 963 plugins. Use factories for test setup with py.test :pypi:`pytest-factoryboy` - *last release*: Dec 30, 2020, + *last release*: Mar 05, 2024, *status*: 6 - Mature, - *requires*: pytest (>=4.6) + *requires*: pytest (>=6.2) Factory Boy support for pytest. @@ -3164,12 +4736,19 @@ This list contains 963 plugins. Generates pytest fixtures that allow the use of type hinting :pypi:`pytest-factoryboy-state` - *last release*: Dec 11, 2020, - *status*: 4 - Beta, + *last release*: Mar 22, 2022, + *status*: 5 - Production/Stable, *requires*: pytest (>=5.0) Simple factoryboy random state management + :pypi:`pytest-failed-screen-record` + *last release*: Jan 05, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.1.2d,<8.0.0) + + Create a video of the screen when pytest fails + :pypi:`pytest-failed-screenshot` *last release*: Apr 21, 2021, *status*: N/A, @@ -3184,6 +4763,13 @@ This list contains 963 plugins. A pytest plugin that helps better distinguishing real test failures from setup flakiness. + :pypi:`pytest-fail-slow` + *last release*: Feb 11, 2024, + *status*: N/A, + *requires*: pytest>=7.0 + + Fail tests that take too long to run + :pypi:`pytest-faker` *last release*: Dec 19, 2016, *status*: 6 - Mature, @@ -3199,11 +4785,11 @@ This list contains 963 plugins. Pytest helpers for Falcon. :pypi:`pytest-falcon-client` - *last release*: Mar 19, 2019, + *last release*: Feb 21, 2024, *status*: N/A, *requires*: N/A - Pytest \`client\` fixture for the Falcon Framework + A package to prevent Dependency Confusion attacks against Yandex. :pypi:`pytest-fantasy` *last release*: Mar 14, 2019, @@ -3219,15 +4805,22 @@ This list contains 963 plugins. - :pypi:`pytest-fastest` - *last release*: Mar 05, 2020, - *status*: N/A, - *requires*: N/A + :pypi:`pytest-fastapi-deps` + *last release*: Jul 20, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + A fixture which allows easy replacement of fastapi dependencies for testing + + :pypi:`pytest-fastest` + *last release*: Oct 04, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=4.4) Use SCM and coverage to run only needed tests :pypi:`pytest-fast-first` - *last release*: Apr 02, 2021, + *last release*: Jan 19, 2023, *status*: 3 - Alpha, *requires*: pytest @@ -3254,6 +4847,13 @@ This list contains 963 plugins. py.test figleaf coverage plugin + :pypi:`pytest-file` + *last release*: Mar 18, 2024, + *status*: 1 - Planning, + *requires*: N/A + + Pytest File + :pypi:`pytest-filecov` *last release*: Jun 27, 2021, *status*: 4 - Beta, @@ -3275,6 +4875,13 @@ This list contains 963 plugins. A pytest plugin that runs marked tests when files change. + :pypi:`pytest-file-watcher` + *last release*: Mar 23, 2023, + *status*: N/A, + *requires*: pytest + + Pytest-File-Watcher is a CLI tool that watches for changes in your code and runs pytest on the changed files. + :pypi:`pytest-filter-case` *last release*: Nov 05, 2020, *status*: N/A, @@ -3283,16 +4890,16 @@ This list contains 963 plugins. run test cases filter by mark :pypi:`pytest-filter-subpackage` - *last release*: Jan 09, 2020, - *status*: 3 - Alpha, - *requires*: pytest (>=3.0) + *last release*: Mar 04, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest >=4.6 Pytest plugin for filtering based on sub-packages :pypi:`pytest-find-dependencies` - *last release*: Apr 21, 2021, + *last release*: Mar 16, 2024, *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + *requires*: pytest >=4.3.0 A pytest plugin to find dependencies between tests @@ -3310,6 +4917,20 @@ This list contains 963 plugins. pytest plugin to manipulate firefox + :pypi:`pytest-fixture-classes` + *last release*: Sep 02, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest + + Fixtures as classes that work well with dependency injection, autocompletetion, type checkers, and language servers + + :pypi:`pytest-fixturecollection` + *last release*: Feb 22, 2024, + *status*: 4 - Beta, + *requires*: pytest >=3.5.0 + + A pytest plugin to collect tests based on fixtures being used by tests + :pypi:`pytest-fixture-config` *last release*: May 28, 2019, *status*: 5 - Production/Stable, @@ -3332,12 +4953,33 @@ This list contains 963 plugins. A pytest plugin to add markers based on fixtures used. :pypi:`pytest-fixture-order` - *last release*: Aug 25, 2020, - *status*: N/A, + *last release*: May 16, 2022, + *status*: 5 - Production/Stable, *requires*: pytest (>=3.0) pytest plugin to control fixture evaluation order + :pypi:`pytest-fixture-ref` + *last release*: Nov 17, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Lets users reference fixtures without name matching magic. + + :pypi:`pytest-fixture-remover` + *last release*: Feb 14, 2024, + *status*: 5 - Production/Stable, + *requires*: N/A + + A LibCST codemod to remove pytest fixtures applied via the usefixtures decorator, as well as its parametrizations. + + :pypi:`pytest-fixture-rtttg` + *last release*: Feb 23, 2022, + *status*: N/A, + *requires*: pytest (>=7.0.1,<8.0.0) + + Warn or fail on fixture name clash + :pypi:`pytest-fixtures` *last release*: May 01, 2019, *status*: 5 - Production/Stable, @@ -3360,21 +5002,28 @@ This list contains 963 plugins. A pytest plugin to assert type annotations at runtime. :pypi:`pytest-flake8` - *last release*: Dec 16, 2020, + *last release*: Mar 18, 2022, *status*: 4 - Beta, - *requires*: pytest (>=3.5) + *requires*: pytest (>=7.0) pytest plugin to check FLAKE8 requirements :pypi:`pytest-flake8-path` - *last release*: Aug 11, 2021, + *last release*: Jul 10, 2023, *status*: 5 - Production/Stable, *requires*: pytest A pytest fixture for testing flake8 plugins. + :pypi:`pytest-flake8-v2` + *last release*: Mar 01, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.0) + + pytest plugin to check FLAKE8 requirements + :pypi:`pytest-flakefinder` - *last release*: Jul 28, 2020, + *last release*: Oct 26, 2022, *status*: 4 - Beta, *requires*: pytest (>=2.7.1) @@ -3395,14 +5044,21 @@ This list contains 963 plugins. Flaptastic py.test plugin :pypi:`pytest-flask` - *last release*: Feb 27, 2021, + *last release*: Oct 23, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=5.2) + *requires*: pytest >=5.2 A set of py.test fixtures to test Flask applications. + :pypi:`pytest-flask-ligand` + *last release*: Apr 25, 2023, + *status*: 4 - Beta, + *requires*: pytest (~=7.3) + + Pytest fixtures and helper functions to use for testing flask-ligand microservices. + :pypi:`pytest-flask-sqlalchemy` - *last release*: Apr 04, 2019, + *last release*: Apr 30, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.2.1) @@ -3415,6 +5071,34 @@ This list contains 963 plugins. Run tests in transactions using pytest, Flask, and SQLalchemy. + :pypi:`pytest-flexreport` + *last release*: Apr 15, 2023, + *status*: 4 - Beta, + *requires*: pytest + + + + :pypi:`pytest-fluent` + *last release*: Jun 26, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.0.0) + + A pytest plugin in order to provide logs via fluentd + + :pypi:`pytest-fluentbit` + *last release*: Jun 16, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.0.0) + + A pytest plugin in order to provide logs via fluentbit + + :pypi:`pytest-fly` + *last release*: Apr 14, 2024, + *status*: 3 - Alpha, + *requires*: pytest + + pytest observer + :pypi:`pytest-flyte` *last release*: May 03, 2021, *status*: N/A, @@ -3429,6 +5113,13 @@ This list contains 963 plugins. A pytest plugin that alerts user of failed test cases with screen notifications + :pypi:`pytest-forbid` + *last release*: Mar 07, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.2,<8.0.0) + + + :pypi:`pytest-forcefail` *last release*: May 15, 2018, *status*: 4 - Beta, @@ -3436,6 +5127,13 @@ This list contains 963 plugins. py.test plugin to make the test failing regardless of pytest.mark.xfail + :pypi:`pytest-forks` + *last release*: Mar 05, 2024, + *status*: N/A, + *requires*: N/A + + Fork helper for pytest + :pypi:`pytest-forward-compatability` *last release*: Sep 06, 2020, *status*: N/A, @@ -3450,6 +5148,13 @@ This list contains 963 plugins. A pytest plugin to shim pytest commandline options for fowards compatibility + :pypi:`pytest-frappe` + *last release*: Oct 29, 2023, + *status*: 4 - Beta, + *requires*: pytest>=7.0.0 + + Pytest Frappe Plugin - A set of pytest fixtures to test Frappe applications + :pypi:`pytest-freezegun` *last release*: Jul 19, 2020, *status*: 4 - Beta, @@ -3457,6 +5162,13 @@ This list contains 963 plugins. Wrap tests with fixtures in freeze_time + :pypi:`pytest-freezer` + *last release*: Jun 21, 2023, + *status*: N/A, + *requires*: pytest >= 3.6 + + Pytest plugin providing a fixture interface for spulec/freezegun + :pypi:`pytest-freeze-reqs` *last release*: Apr 29, 2021, *status*: N/A, @@ -3465,7 +5177,7 @@ This list contains 963 plugins. Check if requirement files are frozen :pypi:`pytest-frozen-uuids` - *last release*: Oct 19, 2021, + *last release*: Apr 17, 2022, *status*: N/A, *requires*: pytest (>=3.0) @@ -3499,6 +5211,27 @@ This list contains 963 plugins. + :pypi:`pytest-fzf` + *last release*: Feb 07, 2024, + *status*: 4 - Beta, + *requires*: pytest >=6.0.0 + + fzf-based test selector for pytest + + :pypi:`pytest_gae` + *last release*: Aug 03, 2016, + *status*: 3 - Alpha, + *requires*: N/A + + pytest plugin for apps written with Google's AppEngine + + :pypi:`pytest-gather-fixtures` + *last release*: Apr 12, 2022, + *status*: N/A, + *requires*: pytest (>=6.0.0) + + set up asynchronous pytest fixtures concurrently + :pypi:`pytest-gc` *last release*: Feb 01, 2018, *status*: N/A, @@ -3513,6 +5246,20 @@ This list contains 963 plugins. Uses gcov to measure test coverage of a C library + :pypi:`pytest-gcs` + *last release*: Mar 01, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest >=6.2 + + GCS fixtures and fixture factories for Pytest. + + :pypi:`pytest-gee` + *last release*: Feb 15, 2024, + *status*: 3 - Alpha, + *requires*: pytest + + The Python plugin for your GEE based packages. + :pypi:`pytest-gevent` *last release*: Feb 25, 2020, *status*: N/A, @@ -3527,6 +5274,13 @@ This list contains 963 plugins. A flexible framework for executing BDD gherkin tests + :pypi:`pytest-gh-log-group` + *last release*: Jan 11, 2022, + *status*: 3 - Alpha, + *requires*: pytest + + pytest plugin for gh actions + :pypi:`pytest-ghostinspector` *last release*: May 17, 2016, *status*: 3 - Alpha, @@ -3535,9 +5289,9 @@ This list contains 963 plugins. For finding/executing Ghost Inspector tests :pypi:`pytest-girder` - *last release*: Nov 30, 2021, + *last release*: Apr 12, 2024, *status*: N/A, - *requires*: N/A + *requires*: pytest>=3.6 A set of pytest fixtures for testing Girder applications. @@ -3548,6 +5302,13 @@ This list contains 963 plugins. Git repository fixture for py.test + :pypi:`pytest-gitconfig` + *last release*: Oct 15, 2023, + *status*: 4 - Beta, + *requires*: pytest>=7.1.2 + + Provide a gitconfig sandbox for testing + :pypi:`pytest-gitcov` *last release*: Jan 11, 2020, *status*: 2 - Pre-Alpha, @@ -3555,6 +5316,13 @@ This list contains 963 plugins. Pytest plugin for reporting on coverage of the last git commit. + :pypi:`pytest-git-diff` + *last release*: Apr 02, 2024, + *status*: N/A, + *requires*: N/A + + Pytest plugin that allows the user to select the tests affected by a range of git commits + :pypi:`pytest-git-fixtures` *last release*: Mar 11, 2021, *status*: 4 - Beta, @@ -3570,12 +5338,19 @@ This list contains 963 plugins. Plugin for py.test that associates tests with github issues using a marker. :pypi:`pytest-github-actions-annotate-failures` - *last release*: Oct 24, 2021, - *status*: N/A, + *last release*: May 04, 2023, + *status*: 5 - Production/Stable, *requires*: pytest (>=4.0.0) pytest plugin to annotate failed tests with a workflow command for GitHub Actions + :pypi:`pytest-github-report` + *last release*: Jun 03, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Generate a GitHub report using pytest in GitHub Workflows + :pypi:`pytest-gitignore` *last release*: Jul 17, 2015, *status*: 4 - Beta, @@ -3583,8 +5358,36 @@ This list contains 963 plugins. py.test plugin to ignore the same files as git + :pypi:`pytest-gitlabci-parallelized` + *last release*: Mar 08, 2023, + *status*: N/A, + *requires*: N/A + + Parallelize pytest across GitLab CI workers. + + :pypi:`pytest-gitlab-code-quality` + *last release*: Apr 03, 2024, + *status*: N/A, + *requires*: pytest>=8.1.1 + + Collects warnings while testing and generates a GitLab Code Quality Report. + + :pypi:`pytest-gitlab-fold` + *last release*: Dec 31, 2023, + *status*: 4 - Beta, + *requires*: pytest >=2.6.0 + + Folds output sections in GitLab CI build log + + :pypi:`pytest-git-selector` + *last release*: Nov 17, 2022, + *status*: N/A, + *requires*: N/A + + Utility to select tests that have had its dependencies modified (as identified by git diff) + :pypi:`pytest-glamor-allure` - *last release*: Nov 26, 2021, + *last release*: Jul 22, 2022, *status*: 4 - Beta, *requires*: pytest @@ -3598,12 +5401,26 @@ This list contains 963 plugins. Pytest fixtures for testing with gnupg. :pypi:`pytest-golden` - *last release*: Nov 23, 2020, + *last release*: Jul 18, 2022, *status*: N/A, - *requires*: pytest (>=6.1.2,<7.0.0) + *requires*: pytest (>=6.1.2) Plugin for pytest that offloads expected outputs to data files + :pypi:`pytest-goldie` + *last release*: May 23, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A plugin to support golden tests with pytest. + + :pypi:`pytest-google-chat` + *last release*: Mar 27, 2022, + *status*: 4 - Beta, + *requires*: pytest + + Notify google chat channel for test results + :pypi:`pytest-graphql-schema` *last release*: Oct 18, 2019, *status*: N/A, @@ -3618,6 +5435,13 @@ This list contains 963 plugins. Green progress dots + :pypi:`pytest-group-by-class` + *last release*: Jun 27, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=2.5) + + A Pytest plugin for running a subset of your tests by splitting them in to groups of classes. + :pypi:`pytest-growl` *last release*: Jan 13, 2014, *status*: 5 - Production/Stable, @@ -3632,6 +5456,20 @@ This list contains 963 plugins. pytest plugin for grpc + :pypi:`pytest-grunnur` + *last release*: Feb 05, 2023, + *status*: N/A, + *requires*: N/A + + Py.Test plugin for Grunnur-based packages. + + :pypi:`pytest_gui_status` + *last release*: Jan 23, 2016, + *status*: N/A, + *requires*: pytest + + Show pytest status in gui + :pypi:`pytest-hammertime` *last release*: Jul 28, 2018, *status*: N/A, @@ -3639,8 +5477,22 @@ This list contains 963 plugins. Display "🔨 " instead of "." for passed pytest tests. + :pypi:`pytest-hardware-test-report` + *last release*: Apr 01, 2024, + *status*: 4 - Beta, + *requires*: pytest<9.0.0,>=8.0.0 + + A simple plugin to use with pytest + + :pypi:`pytest-harmony` + *last release*: Jan 17, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.1,<8.0.0) + + Chain tests and data with pytest + :pypi:`pytest-harvest` - *last release*: Apr 01, 2021, + *last release*: Mar 16, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -3654,12 +5506,19 @@ This list contains 963 plugins. A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. :pypi:`pytest-helm-charts` - *last release*: Oct 26, 2021, + *last release*: Feb 07, 2024, *status*: 4 - Beta, - *requires*: pytest (>=6.1.2,<7.0.0) + *requires*: pytest (>=8.0.0,<9.0.0) A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. + :pypi:`pytest-helm-templates` + *last release*: Apr 05, 2024, + *status*: N/A, + *requires*: pytest~=7.4.0; extra == "dev" + + Pytest fixtures for unit testing the output of helm templates + :pypi:`pytest-helper` *last release*: May 31, 2019, *status*: 5 - Production/Stable, @@ -3675,12 +5534,19 @@ This list contains 963 plugins. pytest helpers :pypi:`pytest-helpers-namespace` - *last release*: Apr 29, 2021, + *last release*: Dec 29, 2021, *status*: 5 - Production/Stable, *requires*: pytest (>=6.0.0) Pytest Helpers Namespace Plugin + :pypi:`pytest-henry` + *last release*: Aug 29, 2023, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-hidecaptured` *last release*: May 04, 2018, *status*: 4 - Beta, @@ -3688,6 +5554,13 @@ This list contains 963 plugins. Hide captured output + :pypi:`pytest-himark` + *last release*: Apr 14, 2024, + *status*: 4 - Beta, + *requires*: pytest>=6.2.0 + + A plugin that will filter pytest's test collection using a json file. It will read a json file provided with a --json argument in pytest command line (or in pytest.ini), search the markers key and automatically add -m option to the command line for filtering out the tests marked with disabled markers. + :pypi:`pytest-historic` *last release*: Apr 08, 2020, *status*: N/A, @@ -3702,6 +5575,20 @@ This list contains 963 plugins. Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report + :pypi:`pytest-history` + *last release*: Jan 14, 2024, + *status*: N/A, + *requires*: pytest (>=7.4.3,<8.0.0) + + Pytest plugin to keep a history of your pytest runs + + :pypi:`pytest-home` + *last release*: Oct 09, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest + + Home directory fixtures + :pypi:`pytest-homeassistant` *last release*: Aug 12, 2020, *status*: 4 - Beta, @@ -3710,12 +5597,19 @@ This list contains 963 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Nov 20, 2021, + *last release*: Apr 13, 2024, *status*: 3 - Alpha, - *requires*: pytest (==6.2.5) + *requires*: pytest==8.1.1 Experimental package to automatically extract test plugins for Home Assistant custom components + :pypi:`pytest-honey` + *last release*: Jan 07, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A simple plugin to use with pytest + :pypi:`pytest-honors` *last release*: Mar 06, 2020, *status*: 4 - Beta, @@ -3723,31 +5617,59 @@ This list contains 963 plugins. Report on tests that honor constraints, and guard against regressions + :pypi:`pytest-hot-reloading` + *last release*: Apr 18, 2024, + *status*: N/A, + *requires*: N/A + + + + :pypi:`pytest-hot-test` + *last release*: Dec 10, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A plugin that tracks test changes + + :pypi:`pytest-houdini` + *last release*: Feb 09, 2024, + *status*: N/A, + *requires*: pytest + + pytest plugin for testing code in Houdini. + :pypi:`pytest-hoverfly` - *last release*: Jul 12, 2021, + *last release*: Jan 30, 2023, *status*: N/A, *requires*: pytest (>=5.0) Simplify working with Hoverfly from pytest :pypi:`pytest-hoverfly-wrapper` - *last release*: Aug 29, 2021, - *status*: 4 - Beta, - *requires*: N/A + *last release*: Feb 27, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=3.7.0) Integrates the Hoverfly HTTP proxy into Pytest :pypi:`pytest-hpfeeds` - *last release*: Aug 27, 2021, + *last release*: Feb 28, 2023, *status*: 4 - Beta, *requires*: pytest (>=6.2.4,<7.0.0) Helpers for testing hpfeeds in your python project :pypi:`pytest-html` - *last release*: Dec 13, 2020, + *last release*: Nov 07, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (!=6.0.0,>=5.0) + *requires*: pytest>=7.0.0 + + pytest plugin for generating HTML reports + + :pypi:`pytest-html-cn` + *last release*: Aug 01, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A pytest plugin for generating HTML reports @@ -3758,6 +5680,20 @@ This list contains 963 plugins. optimized pytest plugin for generating HTML reports + :pypi:`pytest-html-merger` + *last release*: Nov 11, 2023, + *status*: N/A, + *requires*: N/A + + Pytest HTML reports merging utility + + :pypi:`pytest-html-object-storage` + *last release*: Jan 17, 2024, + *status*: 5 - Production/Stable, + *requires*: N/A + + Pytest report plugin for send HTML report on object-storage + :pypi:`pytest-html-profiling` *last release*: Feb 11, 2020, *status*: 5 - Production/Stable, @@ -3766,12 +5702,19 @@ This list contains 963 plugins. Pytest plugin for generating HTML reports with per-test profiling and optionally call graph visualizations. Based on pytest-html by Dave Hunt. :pypi:`pytest-html-reporter` - *last release*: Apr 25, 2021, + *last release*: Feb 13, 2022, *status*: N/A, *requires*: N/A Generates a static html report based on pytest framework + :pypi:`pytest-html-report-merger` + *last release*: Oct 23, 2023, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-html-thread` *last release*: Dec 29, 2020, *status*: 5 - Production/Stable, @@ -3787,12 +5730,19 @@ This list contains 963 plugins. Fixture "http" for http requests :pypi:`pytest-httpbin` - *last release*: Feb 11, 2019, + *last release*: May 08, 2023, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest ; extra == 'test' Easily test your HTTP library against a local copy of httpbin + :pypi:`pytest-httpdbg` + *last release*: Jan 10, 2024, + *status*: 3 - Alpha, + *requires*: pytest >=7.0.0 + + A pytest plugin to record HTTP(S) requests with stack trace + :pypi:`pytest-http-mocker` *last release*: Oct 20, 2019, *status*: N/A, @@ -3807,27 +5757,41 @@ This list contains 963 plugins. A thin wrapper of HTTPretty for pytest - :pypi:`pytest-httpserver` - *last release*: Oct 18, 2021, + :pypi:`pytest_httpserver` + *last release*: Feb 24, 2024, *status*: 3 - Alpha, - *requires*: pytest ; extra == 'dev' + *requires*: N/A pytest-httpserver is a httpserver for pytest + :pypi:`pytest-httptesting` + *last release*: Jul 24, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.0,<8.0.0) + + http_testing framework on top of pytest + :pypi:`pytest-httpx` - *last release*: Nov 16, 2021, + *last release*: Feb 21, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (==6.*) + *requires*: pytest <9,>=7 Send responses to httpx. :pypi:`pytest-httpx-blockage` - *last release*: Nov 16, 2021, + *last release*: Feb 16, 2023, *status*: N/A, - *requires*: pytest (>=6.2.5) + *requires*: pytest (>=7.2.1) Disable httpx requests during a test run + :pypi:`pytest-httpx-recorder` + *last release*: Jan 04, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest + + Recorder feature based on pytest_httpx, like recorder feature in responses. + :pypi:`pytest-hue` *last release*: May 09, 2019, *status*: N/A, @@ -3849,17 +5813,24 @@ This list contains 963 plugins. help hypo module for pytest + :pypi:`pytest-iam` + *last release*: Apr 12, 2024, + *status*: 3 - Alpha, + *requires*: pytest>=7.0.0 + + A fully functional OAUTH2 / OpenID Connect (OIDC) server to be used in your testsuite + :pypi:`pytest-ibutsu` - *last release*: Jun 16, 2021, + *last release*: Aug 05, 2022, *status*: 4 - Beta, - *requires*: pytest + *requires*: pytest>=7.1 A plugin to sent pytest results to an Ibutsu server :pypi:`pytest-icdiff` - *last release*: Apr 08, 2020, + *last release*: Dec 05, 2023, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest use icdiff for better error messages in pytest assertions @@ -3870,27 +5841,48 @@ This list contains 963 plugins. A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api + :pypi:`pytest-idem` + *last release*: Dec 13, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + A pytest plugin to help with testing idem projects + :pypi:`pytest-idempotent` - *last release*: Nov 26, 2021, + *last release*: Jul 25, 2022, *status*: N/A, *requires*: N/A Pytest plugin for testing function idempotence. :pypi:`pytest-ignore-flaky` - *last release*: Apr 23, 2021, + *last release*: Apr 08, 2024, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest>=6.0 ignore failures from flaky tests (pytest plugin) + :pypi:`pytest-ignore-test-results` + *last release*: Aug 17, 2023, + *status*: 2 - Pre-Alpha, + *requires*: pytest>=7.0 + + A pytest plugin to ignore test results. + :pypi:`pytest-image-diff` - *last release*: Jul 28, 2021, + *last release*: Mar 09, 2023, *status*: 3 - Alpha, *requires*: pytest + :pypi:`pytest-image-snapshot` + *last release*: Dec 01, 2023, + *status*: 4 - Beta, + *requires*: pytest >=3.5.0 + + A pytest plugin for image snapshot management and comparison. + :pypi:`pytest-incremental` *last release*: Apr 24, 2021, *status*: 5 - Production/Stable, @@ -3912,6 +5904,13 @@ This list contains 963 plugins. pytest plugin to collect information from tests + :pypi:`pytest-info-plugin` + *last release*: Sep 14, 2023, + *status*: N/A, + *requires*: N/A + + Get executed interface information in pytest interface automation framework + :pypi:`pytest-informative-node` *last release*: Apr 25, 2019, *status*: 4 - Beta, @@ -3927,26 +5926,54 @@ This list contains 963 plugins. pytest stack validation prior to testing executing :pypi:`pytest-ini` - *last release*: Sep 30, 2021, + *last release*: Apr 26, 2022, *status*: N/A, *requires*: N/A Reuse pytest.ini to store env variables + :pypi:`pytest-initry` + *last release*: Apr 14, 2024, + *status*: N/A, + *requires*: pytest<9.0.0,>=8.1.1 + + Plugin for sending automation test data from Pytest to the initry + + :pypi:`pytest-inline` + *last release*: Oct 19, 2023, + *status*: 4 - Beta, + *requires*: pytest >=7.0.0 + + A pytest plugin for writing inline tests. + :pypi:`pytest-inmanta` - *last release*: Aug 17, 2021, + *last release*: Dec 13, 2023, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest A py.test plugin providing fixtures to simplify inmanta modules testing. :pypi:`pytest-inmanta-extensions` - *last release*: May 27, 2021, + *last release*: Apr 02, 2024, *status*: 5 - Production/Stable, *requires*: N/A Inmanta tests package + :pypi:`pytest-inmanta-lsm` + *last release*: Apr 15, 2024, + *status*: 5 - Production/Stable, + *requires*: N/A + + Common fixtures for inmanta LSM related modules + + :pypi:`pytest-inmanta-yang` + *last release*: Feb 22, 2024, + *status*: 4 - Beta, + *requires*: pytest + + Common fixtures used in inmanta yang related modules + :pypi:`pytest-Inomaly` *last release*: Feb 13, 2018, *status*: 4 - Beta, @@ -3954,17 +5981,31 @@ This list contains 963 plugins. A simple image diff plugin for pytest + :pypi:`pytest-in-robotframework` + *last release*: Mar 02, 2024, + *status*: N/A, + *requires*: pytest + + The extension enables easy execution of pytest tests within the Robot Framework environment. + + :pypi:`pytest-insper` + *last release*: Mar 21, 2024, + *status*: N/A, + *requires*: pytest + + Pytest plugin for courses at Insper + :pypi:`pytest-insta` - *last release*: Apr 07, 2021, + *last release*: Feb 19, 2024, *status*: N/A, - *requires*: pytest (>=6.0.2,<7.0.0) + *requires*: pytest (>=7.2.0,<9.0.0) A practical snapshot testing plugin for pytest :pypi:`pytest-instafail` - *last release*: Jun 14, 2020, + *last release*: Mar 31, 2023, *status*: 4 - Beta, - *requires*: pytest (>=2.9) + *requires*: pytest (>=5) pytest plugin to show failures instantly @@ -3976,16 +6017,16 @@ This list contains 963 plugins. pytest plugin to instrument tests :pypi:`pytest-integration` - *last release*: Apr 16, 2020, + *last release*: Nov 17, 2022, *status*: N/A, *requires*: N/A Organizing pytests by integration or not :pypi:`pytest-integration-mark` - *last release*: Jul 19, 2021, + *last release*: May 22, 2023, *status*: N/A, - *requires*: pytest (>=5.2,<7.0) + *requires*: pytest (>=5.2) Automatic integration test marking and excluding plugin for pytest @@ -4003,10 +6044,17 @@ This list contains 963 plugins. Pytest plugin for intercepting outgoing connection requests during pytest run. + :pypi:`pytest-interface-tester` + *last release*: Feb 09, 2024, + *status*: 4 - Beta, + *requires*: pytest + + Pytest plugin for checking charm relation interface protocol compliance. + :pypi:`pytest-invenio` - *last release*: May 11, 2021, + *last release*: Feb 28, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (<7,>=6) + *requires*: pytest <7.2.0,>=6 Pytest fixtures for Invenio. @@ -4018,7 +6066,7 @@ This list contains 963 plugins. Run tests covering a specific file or changeset :pypi:`pytest-ipdb` - *last release*: Sep 02, 2014, + *last release*: Mar 20, 2013, *status*: 2 - Pre-Alpha, *requires*: N/A @@ -4031,15 +6079,29 @@ This list contains 963 plugins. THIS PROJECT IS ABANDONED + :pypi:`pytest-ipywidgets` + *last release*: Apr 08, 2024, + *status*: N/A, + *requires*: pytest + + + + :pypi:`pytest-isolate` + *last release*: Feb 20, 2023, + *status*: 4 - Beta, + *requires*: pytest + + + :pypi:`pytest-isort` - *last release*: Apr 27, 2021, + *last release*: Mar 05, 2024, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest (>=5.0) py.test plugin to check import ordering using isort :pypi:`pytest-it` - *last release*: Jan 22, 2020, + *last release*: Jan 29, 2024, *status*: 4 - Beta, *requires*: N/A @@ -4052,6 +6114,20 @@ This list contains 963 plugins. Nicer list and iterable assertion messages for pytest + :pypi:`pytest-iters` + *last release*: May 24, 2022, + *status*: N/A, + *requires*: N/A + + A contextmanager pytest fixture for handling multiple mock iters + + :pypi:`pytest_jar_yuan` + *last release*: Dec 12, 2022, + *status*: N/A, + *requires*: N/A + + A allure and pytest used package + :pypi:`pytest-jasmine` *last release*: Nov 04, 2017, *status*: 1 - Planning, @@ -4059,6 +6135,13 @@ This list contains 963 plugins. Run jasmine tests from your pytest test suite + :pypi:`pytest-jelastic` + *last release*: Nov 16, 2022, + *status*: N/A, + *requires*: pytest (>=7.2.0,<8.0.0) + + Pytest plugin defining the necessary command-line options to pass to pytests testing a Jelastic environment. + :pypi:`pytest-jest` *last release*: May 22, 2018, *status*: 4 - Beta, @@ -4066,20 +6149,41 @@ This list contains 963 plugins. A custom jest-pytest oriented Pytest reporter + :pypi:`pytest-jinja` + *last release*: Oct 04, 2022, + *status*: 3 - Alpha, + *requires*: pytest (>=6.2.5,<7.0.0) + + A plugin to generate customizable jinja-based HTML reports in pytest + :pypi:`pytest-jira` - *last release*: Dec 02, 2021, + *last release*: Apr 12, 2024, *status*: 3 - Alpha, *requires*: N/A py.test JIRA integration plugin, using markers + :pypi:`pytest-jira-xfail` + *last release*: Jun 19, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.0) + + Plugin skips (xfail) tests if unresolved Jira issue(s) linked + :pypi:`pytest-jira-xray` - *last release*: Nov 28, 2021, - *status*: 3 - Alpha, - *requires*: pytest + *last release*: Mar 27, 2024, + *status*: 4 - Beta, + *requires*: pytest>=6.2.4 pytest plugin to integrate tests with JIRA XRAY + :pypi:`pytest-job-selection` + *last release*: Jan 30, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A pytest plugin for load balancing test suites + :pypi:`pytest-jobserver` *last release*: May 15, 2019, *status*: 5 - Production/Stable, @@ -4101,6 +6205,13 @@ This list contains 963 plugins. Generate JSON test reports + :pypi:`pytest-json-fixtures` + *last release*: Mar 14, 2023, + *status*: 4 - Beta, + *requires*: N/A + + JSON output for the --fixtures flag + :pypi:`pytest-jsonlint` *last release*: Aug 04, 2016, *status*: N/A, @@ -4109,14 +6220,49 @@ This list contains 963 plugins. UNKNOWN :pypi:`pytest-json-report` - *last release*: Sep 24, 2021, + *last release*: Mar 15, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.8.0) A pytest plugin to report test results as JSON files + :pypi:`pytest-json-report-wip` + *last release*: Oct 28, 2023, + *status*: 4 - Beta, + *requires*: pytest >=3.8.0 + + A pytest plugin to report test results as JSON files + + :pypi:`pytest-jsonschema` + *last release*: Mar 27, 2024, + *status*: 4 - Beta, + *requires*: pytest>=6.2.0 + + A pytest plugin to perform JSONSchema validations + + :pypi:`pytest-jtr` + *last release*: Apr 15, 2024, + *status*: N/A, + *requires*: pytest<8.0.0,>=7.1.2 + + pytest plugin supporting json test report output + + :pypi:`pytest-jupyter` + *last release*: Apr 04, 2024, + *status*: 4 - Beta, + *requires*: pytest>=7.0 + + A pytest plugin for testing Jupyter libraries and extensions. + + :pypi:`pytest-jupyterhub` + *last release*: Apr 25, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest + + A reusable JupyterHub pytest plugin + :pypi:`pytest-kafka` - *last release*: Aug 24, 2021, + *last release*: Jun 14, 2023, *status*: N/A, *requires*: pytest @@ -4129,8 +6275,36 @@ This list contains 963 plugins. A plugin to send pytest events to Kafka + :pypi:`pytest-kasima` + *last release*: Jan 26, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.2.1,<8.0.0) + + Display horizontal lines above and below the captured standard output for easy viewing. + + :pypi:`pytest-keep-together` + *last release*: Dec 07, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Pytest plugin to customize test ordering by running all 'related' tests together + + :pypi:`pytest-kexi` + *last release*: Apr 29, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.2,<8.0.0) + + + + :pypi:`pytest-keyring` + *last release*: Oct 01, 2023, + *status*: N/A, + *requires*: pytest (>=7.1) + + A Pytest plugin to access the system's keyring to provide credentials for tests + :pypi:`pytest-kind` - *last release*: Jan 24, 2021, + *last release*: Nov 30, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -4157,6 +6331,13 @@ This list contains 963 plugins. Run Konira DSL tests with py.test + :pypi:`pytest-koopmans` + *last release*: Nov 21, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A plugin for testing the koopmans package + :pypi:`pytest-krtech-common` *last release*: Nov 28, 2016, *status*: 4 - Beta, @@ -4164,6 +6345,20 @@ This list contains 963 plugins. pytest krtech common library + :pypi:`pytest-kubernetes` + *last release*: Sep 14, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.1,<8.0.0) + + + + :pypi:`pytest-kuunda` + *last release*: Feb 25, 2024, + *status*: 4 - Beta, + *requires*: pytest >=6.2.0 + + pytest plugin to help with test data setup for PySpark tests + :pypi:`pytest-kwparametrize` *last release*: Jan 22, 2021, *status*: N/A, @@ -4172,9 +6367,9 @@ This list contains 963 plugins. Alternate syntax for @pytest.mark.parametrize with test cases as dictionaries and default value fallbacks :pypi:`pytest-lambda` - *last release*: Aug 23, 2021, + *last release*: Aug 20, 2022, *status*: 3 - Alpha, - *requires*: pytest (>=3.6,<7) + *requires*: pytest (>=3.6,<8) Define pytest fixtures with lambda functions. @@ -4185,6 +6380,27 @@ This list contains 963 plugins. + :pypi:`pytest-langchain` + *last release*: Feb 26, 2023, + *status*: N/A, + *requires*: pytest + + Pytest-style test runner for langchain agents + + :pypi:`pytest-lark` + *last release*: Nov 05, 2023, + *status*: N/A, + *requires*: N/A + + Create fancy and clear HTML test reports. + + :pypi:`pytest-launchable` + *last release*: Apr 05, 2023, + *status*: N/A, + *requires*: pytest (>=4.2.0) + + Launchable Pytest Plugin + :pypi:`pytest-layab` *last release*: Oct 05, 2020, *status*: 5 - Production/Stable, @@ -4199,6 +6415,13 @@ This list contains 963 plugins. It helps to use fixtures in pytest.mark.parametrize + :pypi:`pytest-lazy-fixtures` + *last release*: Mar 16, 2024, + *status*: N/A, + *requires*: pytest (>=7) + + Allows you to use fixtures in @pytest.mark.parametrize. + :pypi:`pytest-ldap` *last release*: Aug 18, 2020, *status*: N/A, @@ -4206,6 +6429,13 @@ This list contains 963 plugins. python-ldap fixtures for pytest + :pypi:`pytest-leak-finder` + *last release*: Feb 15, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + Find the test that's leaking before the one that fails + :pypi:`pytest-leaks` *last release*: Nov 27, 2019, *status*: 1 - Planning, @@ -4213,6 +6443,13 @@ This list contains 963 plugins. A pytest plugin to trace resource leaks. + :pypi:`pytest-leaping` + *last release*: Mar 27, 2024, + *status*: 4 - Beta, + *requires*: pytest>=6.2.0 + + A simple plugin to use with pytest + :pypi:`pytest-level` *last release*: Oct 21, 2019, *status*: N/A, @@ -4221,14 +6458,14 @@ This list contains 963 plugins. Select tests of a given level or lower :pypi:`pytest-libfaketime` - *last release*: Dec 22, 2018, + *last release*: Apr 12, 2024, *status*: 4 - Beta, - *requires*: pytest (>=3.0.0) + *requires*: pytest>=3.0.0 - A python-libfaketime plugin for pytest. + A python-libfaketime plugin for pytest :pypi:`pytest-libiio` - *last release*: Oct 29, 2021, + *last release*: Dec 22, 2023, *status*: 4 - Beta, *requires*: N/A @@ -4256,8 +6493,15 @@ This list contains 963 plugins. A pytest plugin to show the line numbers of test functions :pypi:`pytest-line-profiler` - *last release*: May 03, 2021, + *last release*: Aug 10, 2023, *status*: 4 - Beta, + *requires*: pytest >=3.5.0 + + Profile code executed by pytest + + :pypi:`pytest-line-profiler-apn` + *last release*: Dec 05, 2022, + *status*: N/A, *requires*: pytest (>=3.5.0) Profile code executed by pytest @@ -4283,6 +6527,13 @@ This list contains 963 plugins. A pytest plugin that stream output in LITF format + :pypi:`pytest-litter` + *last release*: Nov 23, 2023, + *status*: 4 - Beta, + *requires*: pytest >=6.1 + + Pytest plugin which verifies that tests do not modify file trees. + :pypi:`pytest-live` *last release*: Mar 08, 2020, *status*: N/A, @@ -4290,29 +6541,43 @@ This list contains 963 plugins. Live results for pytest + :pypi:`pytest-local-badge` + *last release*: Jan 15, 2023, + *status*: N/A, + *requires*: pytest (>=6.1.0) + + Generate local badges (shields) reporting your test suite status. + :pypi:`pytest-localftpserver` - *last release*: Aug 25, 2021, + *last release*: Oct 14, 2023, *status*: 5 - Production/Stable, *requires*: pytest A PyTest plugin which provides an FTP fixture for your tests :pypi:`pytest-localserver` - *last release*: Nov 19, 2021, + *last release*: Oct 12, 2023, *status*: 4 - Beta, *requires*: N/A - py.test plugin to test server connections locally. + pytest plugin to test server connections locally. :pypi:`pytest-localstack` - *last release*: Aug 22, 2019, + *last release*: Jun 07, 2023, *status*: 4 - Beta, - *requires*: pytest (>=3.3.0) + *requires*: pytest (>=6.0.0,<7.0.0) Pytest plugin for AWS integration tests + :pypi:`pytest-lock` + *last release*: Feb 03, 2024, + *status*: N/A, + *requires*: pytest (>=7.4.3,<8.0.0) + + pytest-lock is a pytest plugin that allows you to "lock" the results of unit tests, storing them in a local cache. This is particularly useful for tests that are resource-intensive or don't need to be run every time. When the tests are run subsequently, pytest-lock will compare the current results with the locked results and issue a warning if there are any discrepancies. + :pypi:`pytest-lockable` - *last release*: Nov 09, 2021, + *last release*: Jan 24, 2024, *status*: 5 - Production/Stable, *requires*: pytest @@ -4354,8 +6619,8 @@ This list contains 963 plugins. Pytest plugin providing three logger fixtures with basic or full writing to log files :pypi:`pytest-logger` - *last release*: Jul 25, 2019, - *status*: 4 - Beta, + *last release*: Mar 10, 2024, + *status*: 5 - Production/Stable, *requires*: pytest (>=3.2) Plugin configuring handlers for loggers from Python logging module. @@ -4367,6 +6632,20 @@ This list contains 963 plugins. Configures logging and allows tweaking the log level with a py.test flag + :pypi:`pytest-logging-end-to-end-test-tool` + *last release*: Sep 23, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.2,<8.0.0) + + + + :pypi:`pytest-logikal` + *last release*: Mar 30, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest==8.1.1 + + Common testing environment + :pypi:`pytest-log-report` *last release*: Dec 26, 2019, *status*: N/A, @@ -4374,13 +6653,41 @@ This list contains 963 plugins. Package for creating a pytest test run reprot + :pypi:`pytest-loguru` + *last release*: Mar 20, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest; extra == "test" + + Pytest Loguru + + :pypi:`pytest-loop` + *last release*: Mar 30, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest + + pytest plugin for looping tests + + :pypi:`pytest-lsp` + *last release*: Feb 07, 2024, + *status*: 3 - Alpha, + *requires*: pytest + + A pytest plugin for end-to-end testing of language servers + :pypi:`pytest-manual-marker` - *last release*: Oct 11, 2021, + *last release*: Aug 04, 2022, *status*: 3 - Alpha, - *requires*: pytest (>=6) + *requires*: pytest>=7 pytest marker for marking manual tests + :pypi:`pytest-markdoctest` + *last release*: Jul 22, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=6) + + A pytest plugin to doctest your markdown files + :pypi:`pytest-markdown` *last release*: Jan 15, 2021, *status*: 4 - Beta, @@ -4388,6 +6695,13 @@ This list contains 963 plugins. Test your markdown docs with pytest + :pypi:`pytest-markdown-docs` + *last release*: Mar 05, 2024, + *status*: N/A, + *requires*: pytest (>=7.0.0) + + Run markdown code fences through pytest + :pypi:`pytest-marker-bugzilla` *last release*: Jan 09, 2020, *status*: N/A, @@ -4424,11 +6738,11 @@ This list contains 963 plugins. UNKNOWN :pypi:`pytest-matcher` - *last release*: Apr 23, 2020, + *last release*: Mar 15, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.4) + *requires*: pytest - Match test output against patterns stored in files + Easy way to match captured \`pytest\` output against expectations stored in files :pypi:`pytest-match-skip` *last release*: May 15, 2019, @@ -4451,6 +6765,27 @@ This list contains 963 plugins. Provide tools for generating tests from combinations of fixtures. + :pypi:`pytest-maxcov` + *last release*: Sep 24, 2023, + *status*: N/A, + *requires*: pytest (>=7.4.0,<8.0.0) + + Compute the maximum coverage available through pytest with the minimum execution time cost + + :pypi:`pytest-maybe-context` + *last release*: Apr 16, 2023, + *status*: N/A, + *requires*: pytest (>=7,<8) + + Simplify tests with warning and exception cases. + + :pypi:`pytest-maybe-raises` + *last release*: May 27, 2022, + *status*: N/A, + *requires*: pytest ; extra == 'dev' + + Pytest fixture for optional exception testing. + :pypi:`pytest-mccabe` *last release*: Jul 22, 2020, *status*: 3 - Alpha, @@ -4466,12 +6801,26 @@ This list contains 963 plugins. Plugin for generating Markdown reports for pytest results :pypi:`pytest-md-report` - *last release*: May 04, 2021, + *last release*: Feb 04, 2024, *status*: 4 - Beta, - *requires*: pytest (!=6.0.0,<7,>=3.3.2) + *requires*: pytest !=6.0.0,<9,>=3.3.2 A pytest plugin to make a test results report with Markdown table format. + :pypi:`pytest-meilisearch` + *last release*: Feb 15, 2024, + *status*: N/A, + *requires*: pytest (>=7.4.3) + + Pytest helpers for testing projects using Meilisearch + + :pypi:`pytest-memlog` + *last release*: May 03, 2023, + *status*: N/A, + *requires*: pytest (>=7.3.0,<8.0.0) + + Log memory usage during tests + :pypi:`pytest-memprof` *last release*: Mar 29, 2019, *status*: 4 - Beta, @@ -4479,6 +6828,13 @@ This list contains 963 plugins. Estimates memory consumption of test functions + :pypi:`pytest-memray` + *last release*: Apr 18, 2024, + *status*: N/A, + *requires*: pytest>=7.2 + + A simple plugin to use with pytest + :pypi:`pytest-menu` *last release*: Oct 04, 2017, *status*: 3 - Alpha, @@ -4493,24 +6849,31 @@ This list contains 963 plugins. pytest plugin to write integration tests for projects using Mercurial Python internals + :pypi:`pytest-mesh` + *last release*: Aug 05, 2022, + *status*: N/A, + *requires*: pytest (==7.1.2) + + pytest_mesh插件 + :pypi:`pytest-message` - *last release*: Nov 04, 2021, + *last release*: Aug 04, 2022, *status*: N/A, *requires*: pytest (>=6.2.5) Pytest plugin for sending report message of marked tests execution :pypi:`pytest-messenger` - *last release*: Dec 16, 2020, + *last release*: Nov 24, 2022, *status*: 5 - Production/Stable, *requires*: N/A Pytest to Slack reporting plugin :pypi:`pytest-metadata` - *last release*: Nov 27, 2020, + *last release*: Feb 12, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.9.0) + *requires*: pytest>=7.0.0 pytest plugin for test session metadata @@ -4521,6 +6884,13 @@ This list contains 963 plugins. Custom metrics report for pytest + :pypi:`pytest-mh` + *last release*: Mar 14, 2024, + *status*: N/A, + *requires*: pytest + + Pytest multihost plugin + :pypi:`pytest-mimesis` *last release*: Mar 21, 2020, *status*: 5 - Production/Stable, @@ -4529,11 +6899,25 @@ This list contains 963 plugins. Mimesis integration with the pytest test runner :pypi:`pytest-minecraft` - *last release*: Sep 26, 2020, + *last release*: Apr 06, 2022, + *status*: N/A, + *requires*: pytest (>=6.0.1) + + A pytest plugin for running tests against Minecraft releases + + :pypi:`pytest-mini` + *last release*: Feb 06, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.0,<8.0.0) + + A plugin to test mp + + :pypi:`pytest-minio-mock` + *last release*: Apr 15, 2024, *status*: N/A, - *requires*: pytest (>=6.0.1,<7.0.0) + *requires*: pytest>=5.0.0 - A pytest plugin for running tests against Minecraft releases + A pytest plugin for mocking Minio S3 interactions :pypi:`pytest-missing-fixtures` *last release*: Oct 14, 2020, @@ -4542,6 +6926,13 @@ This list contains 963 plugins. Pytest plugin that creates missing fixtures + :pypi:`pytest-mitmproxy` + *last release*: Mar 07, 2024, + *status*: N/A, + *requires*: pytest >=7.0 + + pytest plugin for mitmproxy tests + :pypi:`pytest-ml` *last release*: May 04, 2019, *status*: 4 - Beta, @@ -4557,9 +6948,9 @@ This list contains 963 plugins. pytest plugin to display test execution output like a mochajs :pypi:`pytest-mock` - *last release*: May 06, 2021, + *last release*: Mar 21, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=5.0) + *requires*: pytest>=6.2.5 Thin-wrapper around the mock package for easier use with pytest @@ -4571,7 +6962,7 @@ This list contains 963 plugins. A mock API server with configurable routes and responses available as a fixture. :pypi:`pytest-mock-generator` - *last release*: Aug 10, 2021, + *last release*: May 16, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -4599,16 +6990,16 @@ This list contains 963 plugins. An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database. :pypi:`pytest-mock-resources` - *last release*: Dec 03, 2021, + *last release*: Apr 11, 2024, *status*: N/A, - *requires*: pytest (>=1.0) + *requires*: pytest>=1.0 A pytest plugin for easily instantiating reproducible mock resources. :pypi:`pytest-mock-server` - *last release*: Apr 06, 2020, + *last release*: Jan 09, 2022, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest (>=3.5.0) Mock server plugin for pytest @@ -4619,6 +7010,27 @@ This list contains 963 plugins. A set of fixtures to test your requests to HTTP/UDP servers + :pypi:`pytest-mocktcp` + *last release*: Oct 11, 2022, + *status*: N/A, + *requires*: pytest + + A pytest plugin for testing TCP clients + + :pypi:`pytest-modalt` + *last release*: Feb 27, 2024, + *status*: 4 - Beta, + *requires*: pytest >=6.2.0 + + Massively distributed pytest runs using modal.com + + :pypi:`pytest-modified-env` + *last release*: Jan 29, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Pytest plugin to fail a test if it leaves modified \`os.environ\` afterwards. + :pypi:`pytest-modifyjunit` *last release*: Jan 10, 2019, *status*: N/A, @@ -4634,28 +7046,35 @@ This list contains 963 plugins. pytest plugin to modify fixture scope :pypi:`pytest-molecule` - *last release*: Oct 06, 2021, + *last release*: Mar 29, 2022, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest (>=7.0.0) + + PyTest Molecule Plugin :: discover and run molecule tests + + :pypi:`pytest-molecule-JC` + *last release*: Jul 18, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.0.0) PyTest Molecule Plugin :: discover and run molecule tests :pypi:`pytest-mongo` - *last release*: Jun 07, 2021, + *last release*: Mar 13, 2024, *status*: 5 - Production/Stable, - *requires*: pytest + *requires*: pytest >=6.2 MongoDB process and client fixtures plugin for Pytest. :pypi:`pytest-mongodb` - *last release*: Dec 07, 2019, + *last release*: May 16, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.5.2) + *requires*: N/A pytest plugin for MongoDB fixtures :pypi:`pytest-monitor` - *last release*: Aug 24, 2021, + *last release*: Jun 25, 2023, *status*: 5 - Production/Stable, *requires*: pytest @@ -4697,32 +7116,39 @@ This list contains 963 plugins. A test batcher for multiprocessed Pytest runs :pypi:`pytest-mpi` - *last release*: Mar 14, 2021, + *last release*: Jan 08, 2022, *status*: 3 - Alpha, *requires*: pytest pytest plugin to collect information from tests + :pypi:`pytest-mpiexec` + *last release*: Apr 13, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + pytest plugin for running individual tests with mpiexec + :pypi:`pytest-mpl` - *last release*: Jul 02, 2021, + *last release*: Feb 14, 2024, *status*: 4 - Beta, *requires*: pytest pytest plugin to help with testing figures output from Matplotlib :pypi:`pytest-mproc` - *last release*: Mar 07, 2021, + *last release*: Nov 15, 2022, *status*: 4 - Beta, - *requires*: pytest + *requires*: pytest (>=6) low-startup-overhead, scalable, distributed-testing pytest plugin - :pypi:`pytest-multi-check` - *last release*: Jun 03, 2021, - *status*: N/A, - *requires*: pytest + :pypi:`pytest-mqtt` + *last release*: Mar 31, 2024, + *status*: 4 - Beta, + *requires*: pytest<8; extra == "test" - Pytest-плагин, реализует возможность мульти проверок и мягких проверок + pytest-mqtt supports testing systems based on MQTT :pypi:`pytest-multihost` *last release*: Apr 07, 2020, @@ -4732,19 +7158,26 @@ This list contains 963 plugins. Utility for writing multi-host tests for pytest :pypi:`pytest-multilog` - *last release*: Jun 10, 2021, + *last release*: Jan 17, 2023, *status*: N/A, - *requires*: N/A + *requires*: pytest Multi-process logs handling and other helpers for pytest :pypi:`pytest-multithreading` - *last release*: Aug 12, 2021, + *last release*: Dec 07, 2022, *status*: N/A, - *requires*: pytest (>=3.6) + *requires*: N/A a pytest plugin for th and concurrent testing + :pypi:`pytest-multithreading-allure` + *last release*: Nov 25, 2022, + *status*: N/A, + *requires*: N/A + + pytest_multithreading_allure + :pypi:`pytest-mutagen` *last release*: Jul 24, 2020, *status*: N/A, @@ -4752,10 +7185,17 @@ This list contains 963 plugins. Add the mutation testing feature to pytest + :pypi:`pytest-my-cool-lib` + *last release*: Nov 02, 2023, + *status*: N/A, + *requires*: pytest (>=7.1.3,<8.0.0) + + + :pypi:`pytest-mypy` - *last release*: Mar 21, 2021, + *last release*: Dec 18, 2022, *status*: 4 - Beta, - *requires*: pytest (>=3.5) + *requires*: pytest (>=6.2) ; python_version >= "3.10" Mypy static type checker plugin for Pytest @@ -4767,33 +7207,40 @@ This list contains 963 plugins. Mypy static type checker plugin for Pytest :pypi:`pytest-mypy-plugins` - *last release*: Oct 19, 2021, - *status*: 3 - Alpha, - *requires*: pytest (>=6.0.0) + *last release*: Mar 31, 2024, + *status*: 4 - Beta, + *requires*: pytest>=7.0.0 pytest plugin for writing tests for mypy plugins :pypi:`pytest-mypy-plugins-shim` *last release*: Apr 12, 2021, *status*: N/A, - *requires*: N/A + *requires*: pytest>=6.0.0 Substitute for "pytest-mypy-plugins" for Python implementations which aren't supported by mypy. :pypi:`pytest-mypy-testing` - *last release*: Jun 13, 2021, + *last release*: Mar 04, 2024, *status*: N/A, - *requires*: pytest + *requires*: pytest>=7,<9 Pytest plugin to check mypy output. :pypi:`pytest-mysql` - *last release*: Nov 22, 2021, + *last release*: Oct 30, 2023, *status*: 5 - Production/Stable, - *requires*: pytest + *requires*: pytest >=6.2 MySQL process and client fixtures for pytest + :pypi:`pytest-ndb` + *last release*: Oct 15, 2023, + *status*: N/A, + *requires*: pytest + + pytest notebook debugger + :pypi:`pytest-needle` *last release*: Dec 10, 2018, *status*: 4 - Beta, @@ -4802,12 +7249,26 @@ This list contains 963 plugins. pytest plugin for visual testing websites using selenium :pypi:`pytest-neo` - *last release*: Apr 23, 2019, + *last release*: Jan 08, 2022, *status*: 3 - Alpha, - *requires*: pytest (>=3.7.2) + *requires*: pytest (>=6.2.0) pytest-neo is a plugin for pytest that shows tests like screen of Matrix. + :pypi:`pytest-neos` + *last release*: Apr 15, 2024, + *status*: 1 - Planning, + *requires*: N/A + + Pytest plugin for neos + + :pypi:`pytest-netdut` + *last release*: Mar 07, 2024, + *status*: N/A, + *requires*: pytest <7.3,>=3.5.0 + + "Automated software testing for switches using pytest" + :pypi:`pytest-network` *last release*: May 07, 2020, *status*: N/A, @@ -4815,6 +7276,13 @@ This list contains 963 plugins. A simple plugin to disable network on socket level. + :pypi:`pytest-network-endpoints` + *last release*: Mar 06, 2022, + *status*: N/A, + *requires*: pytest + + Network endpoints plugin for pytest + :pypi:`pytest-never-sleep` *last release*: May 05, 2021, *status*: 3 - Alpha, @@ -4837,9 +7305,9 @@ This list contains 963 plugins. nginx fixture for pytest - iplweb temporary fork :pypi:`pytest-ngrok` - *last release*: Jan 22, 2020, + *last release*: Jan 20, 2022, *status*: 3 - Alpha, - *requires*: N/A + *requires*: pytest @@ -4850,6 +7318,13 @@ This list contains 963 plugins. pytest ngs fixtures + :pypi:`pytest-nhsd-apim` + *last release*: Feb 16, 2024, + *status*: N/A, + *requires*: pytest (>=6.2.5,<7.0.0) + + Pytest plugin accessing NHSDigital's APIM proxies + :pypi:`pytest-nice` *last release*: May 04, 2019, *status*: 4 - Beta, @@ -4864,20 +7339,27 @@ This list contains 963 plugins. A small snippet for nicer PyTest's Parametrize - :pypi:`pytest-nlcov` - *last release*: Jul 07, 2021, + :pypi:`pytest_nlcov` + *last release*: Apr 11, 2024, *status*: N/A, *requires*: N/A Pytest plugin to get the coverage of the new lines (based on git diff) only :pypi:`pytest-nocustom` - *last release*: Jul 07, 2021, + *last release*: Apr 11, 2024, *status*: 5 - Production/Stable, *requires*: N/A Run all tests without custom markers + :pypi:`pytest-node-dependency` + *last release*: Apr 10, 2024, + *status*: 5 - Production/Stable, + *requires*: N/A + + pytest plugin for controlling execution flow + :pypi:`pytest-nodev` *last release*: Jul 21, 2016, *status*: 4 - Beta, @@ -4892,12 +7374,19 @@ This list contains 963 plugins. Ensure a test produces no garbage - :pypi:`pytest-notebook` - *last release*: Sep 16, 2020, + :pypi:`pytest-nose-attrib` + *last release*: Aug 13, 2023, + *status*: N/A, + *requires*: N/A + + pytest plugin to use nose @attrib marks decorators and pick tests based on attributes and partially uses nose-attrib plugin approach + + :pypi:`pytest_notebook` + *last release*: Nov 28, 2023, *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + *requires*: pytest>=3.5.0 - A pytest plugin for testing Jupyter Notebooks + A pytest plugin for testing Jupyter Notebooks. :pypi:`pytest-notice` *last release*: Nov 05, 2020, @@ -4920,6 +7409,13 @@ This list contains 963 plugins. A pytest plugin to notify test result + :pypi:`pytest_notify` + *last release*: Jul 05, 2017, + *status*: N/A, + *requires*: pytest>=3.0.0 + + Get notifications when your tests ends + :pypi:`pytest-notimplemented` *last release*: Aug 27, 2019, *status*: N/A, @@ -4935,12 +7431,26 @@ This list contains 963 plugins. A PyTest Reporter to send test runs to Notion.so :pypi:`pytest-nunit` - *last release*: Aug 04, 2020, - *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + *last release*: Feb 26, 2024, + *status*: 5 - Production/Stable, + *requires*: N/A A pytest plugin for generating NUnit3 test result XML output + :pypi:`pytest-oar` + *last release*: May 02, 2023, + *status*: N/A, + *requires*: pytest>=6.0.1 + + PyTest plugin for the OAR testing framework + + :pypi:`pytest-object-getter` + *last release*: Jul 31, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Import any object from a 3rd party module while mocking its namespace on demand. + :pypi:`pytest-ochrus` *last release*: Feb 21, 2018, *status*: 4 - Beta, @@ -4948,10 +7458,17 @@ This list contains 963 plugins. pytest results data-base and HTML reporter + :pypi:`pytest-odc` + *last release*: Aug 04, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A pytest plugin for simplifying ODC database tests + :pypi:`pytest-odoo` - *last release*: Nov 04, 2021, + *last release*: Jul 06, 2023, *status*: 4 - Beta, - *requires*: pytest (>=2.9) + *requires*: pytest (>=7.2.0) py.test plugin to run Odoo tests @@ -4969,6 +7486,20 @@ This list contains 963 plugins. pytest plugin to test OpenERP modules + :pypi:`pytest-offline` + *last release*: Mar 09, 2023, + *status*: 1 - Planning, + *requires*: pytest (>=7.0.0,<8.0.0) + + + + :pypi:`pytest-ogsm-plugin` + *last release*: May 16, 2023, + *status*: N/A, + *requires*: N/A + + 针对特定项目定制化插件,优化了pytest报告展示方式,并添加了项目所需特定参数 + :pypi:`pytest-ok` *last release*: Apr 01, 2019, *status*: 4 - Beta, @@ -4977,12 +7508,19 @@ This list contains 963 plugins. The ultimate pytest output plugin :pypi:`pytest-only` - *last release*: Jan 19, 2020, - *status*: N/A, - *requires*: N/A + *last release*: Mar 09, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest (<7.1) ; python_full_version <= "3.6.0" Use @pytest.mark.only to run a single test + :pypi:`pytest-oof` + *last release*: Dec 11, 2023, + *status*: 4 - Beta, + *requires*: N/A + + A Pytest plugin providing structured, programmatic access to a test run's results + :pypi:`pytest-oot` *last release*: Sep 18, 2016, *status*: 4 - Beta, @@ -4997,17 +7535,24 @@ This list contains 963 plugins. Pytest plugin for detecting inadvertent open file handles + :pypi:`pytest-opentelemetry` + *last release*: Oct 01, 2023, + *status*: N/A, + *requires*: pytest + + A pytest plugin for instrumenting test runs via OpenTelemetry + :pypi:`pytest-opentmi` - *last release*: Nov 04, 2021, + *last release*: Jun 02, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=5.0) pytest plugin for publish results to opentmi :pypi:`pytest-operator` - *last release*: Oct 26, 2021, + *last release*: Sep 28, 2022, *status*: N/A, - *requires*: N/A + *requires*: pytest Fixtures for Operators @@ -5033,9 +7578,9 @@ This list contains 963 plugins. A pytest plugin for orchestrating tests :pypi:`pytest-order` - *last release*: May 30, 2021, + *last release*: Apr 02, 2024, *status*: 4 - Beta, - *requires*: pytest (>=5.0) + *requires*: pytest>=5.0; python_version < "3.10" pytest plugin to run your tests in a specific order @@ -5046,6 +7591,13 @@ This list contains 963 plugins. pytest plugin to run your tests in a specific order + :pypi:`pytest-order-modify` + *last release*: Nov 04, 2022, + *status*: N/A, + *requires*: N/A + + 新增run_marker 来自定义用例的执行顺序 + :pypi:`pytest-osxnotify` *last release*: May 15, 2015, *status*: N/A, @@ -5053,12 +7605,33 @@ This list contains 963 plugins. OS X notifications for py.test results. + :pypi:`pytest-ot` + *last release*: Mar 21, 2024, + *status*: N/A, + *requires*: pytest; extra == "dev" + + A pytest plugin for instrumenting test runs via OpenTelemetry + :pypi:`pytest-otel` - *last release*: Dec 03, 2021, + *last release*: Mar 18, 2024, + *status*: N/A, + *requires*: pytest==8.1.1 + + OpenTelemetry plugin for Pytest + + :pypi:`pytest-override-env-var` + *last release*: Feb 25, 2023, + *status*: N/A, + *requires*: N/A + + Pytest mark to override a value of an environment variable. + + :pypi:`pytest-owner` + *last release*: Apr 25, 2022, *status*: N/A, *requires*: N/A - pytest-otel report OpenTelemetry traces about test executed + Add owner mark for tests :pypi:`pytest-pact` *last release*: Jan 07, 2019, @@ -5088,6 +7661,13 @@ This list contains 963 plugins. a pytest plugin for parallel and concurrent testing + :pypi:`pytest-parallelize-tests` + *last release*: Jan 27, 2023, + *status*: 4 - Beta, + *requires*: N/A + + pytest plugin that parallelizes test execution across multiple hosts + :pypi:`pytest-param` *last release*: Sep 11, 2016, *status*: 4 - Beta, @@ -5102,26 +7682,54 @@ This list contains 963 plugins. Configure pytest fixtures using a combination of"parametrize" and markers + :pypi:`pytest-parameterize-from-files` + *last release*: Feb 15, 2024, + *status*: 4 - Beta, + *requires*: pytest>=7.2.0 + + A pytest plugin that parameterizes tests from data files. + :pypi:`pytest-parametrization` - *last release*: Nov 30, 2021, + *last release*: May 22, 2022, *status*: 5 - Production/Stable, - *requires*: pytest + *requires*: N/A Simpler PyTest parametrization :pypi:`pytest-parametrize-cases` - *last release*: Dec 12, 2020, + *last release*: Mar 13, 2022, *status*: N/A, - *requires*: pytest (>=6.1.2,<7.0.0) + *requires*: pytest (>=6.1.2) A more user-friendly way to write parametrized tests. :pypi:`pytest-parametrized` - *last release*: Oct 19, 2020, + *last release*: Nov 03, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest + + Pytest decorator for parametrizing tests with default iterables. + + :pypi:`pytest-parametrize-suite` + *last release*: Jan 19, 2023, *status*: 5 - Production/Stable, *requires*: pytest - Pytest plugin for parametrizing tests with default iterables. + A simple pytest extension for creating a named test suite. + + :pypi:`pytest_param_files` + *last release*: Jul 29, 2023, + *status*: N/A, + *requires*: pytest + + Create pytest parametrize decorators from external files. + + :pypi:`pytest-param-scope` + *last release*: Oct 18, 2023, + *status*: N/A, + *requires*: pytest + + pytest parametrize scope fixture workaround :pypi:`pytest-parawtf` *last release*: Dec 03, 2018, @@ -5151,6 +7759,13 @@ This list contains 963 plugins. Allow setting the path to a paste config file + :pypi:`pytest-patch` + *last release*: Apr 29, 2023, + *status*: 3 - Alpha, + *requires*: pytest (>=7.0.0) + + An automagic \`patch\` fixture that can patch objects directly or by name. + :pypi:`pytest-patches` *last release*: Aug 30, 2021, *status*: 4 - Beta, @@ -5158,6 +7773,13 @@ This list contains 963 plugins. A contextmanager pytest fixture for handling multiple mock patches + :pypi:`pytest-patterns` + *last release*: Nov 17, 2023, + *status*: 4 - Beta, + *requires*: N/A + + pytest plugin to make testing complicated long string output easy to write and easy to debug + :pypi:`pytest-pdb` *last release*: Jul 31, 2018, *status*: N/A, @@ -5193,12 +7815,19 @@ This list contains 963 plugins. Change the exit code of pytest test sessions when a required percent of tests pass. + :pypi:`pytest-percents` + *last release*: Mar 16, 2024, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-perf` - *last release*: Jun 27, 2021, + *last release*: Jan 28, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=4.6) ; extra == 'testing' + *requires*: pytest >=6 ; extra == 'testing' - pytest-perf + Run performance tests against the mainline code. :pypi:`pytest-performance` *last release*: Sep 11, 2020, @@ -5207,13 +7836,34 @@ This list contains 963 plugins. A simple plugin to ensure the execution of critical sections of code has not been impacted + :pypi:`pytest-performancetotal` + *last release*: Mar 19, 2024, + *status*: 4 - Beta, + *requires*: N/A + + A performance plugin for pytest + :pypi:`pytest-persistence` - *last release*: Nov 06, 2021, + *last release*: Jul 04, 2023, *status*: N/A, *requires*: N/A Pytest tool for persistent objects + :pypi:`pytest-pexpect` + *last release*: Mar 27, 2024, + *status*: 4 - Beta, + *requires*: pytest>=6.2.0 + + Pytest pexpect plugin. + + :pypi:`pytest-pg` + *last release*: Apr 03, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest>=6.0.0 + + A tiny plugin for pytest which runs PostgreSQL in Docker + :pypi:`pytest-pgsql` *last release*: May 13, 2020, *status*: 5 - Production/Stable, @@ -5222,16 +7872,16 @@ This list contains 963 plugins. Pytest plugins and helpers for tests using a Postgres database. :pypi:`pytest-phmdoctest` - *last release*: Nov 10, 2021, + *last release*: Apr 15, 2022, *status*: 4 - Beta, - *requires*: pytest (>=6.2) ; extra == 'test' + *requires*: pytest (>=5.4.3) pytest plugin to test Python examples in Markdown using phmdoctest. :pypi:`pytest-picked` - *last release*: Dec 23, 2020, + *last release*: Jul 27, 2023, *status*: N/A, - *requires*: pytest (>=3.5.0) + *requires*: pytest (>=3.7.0) Run the tests related to the changed files @@ -5256,6 +7906,13 @@ This list contains 963 plugins. Slice in your test base thanks to powerful markers. + :pypi:`pytest-pingguo-pytest-plugin` + *last release*: Oct 26, 2022, + *status*: 4 - Beta, + *requires*: N/A + + pingguo test + :pypi:`pytest-pings` *last release*: Jun 29, 2019, *status*: 3 - Alpha, @@ -5284,6 +7941,13 @@ This list contains 963 plugins. Pytest plugin for functional testing of data analysispipelines + :pypi:`pytest-pitch` + *last release*: Nov 02, 2023, + *status*: 4 - Beta, + *requires*: pytest >=7.3.1 + + runs tests in an order such that coverage increases as fast as possible + :pypi:`pytest-platform-markers` *last release*: Sep 09, 2019, *status*: 4 - Beta, @@ -5306,12 +7970,33 @@ This list contains 963 plugins. Pytest plugin for reading playbooks. :pypi:`pytest-playwright` - *last release*: Oct 28, 2021, + *last release*: Feb 02, 2024, *status*: N/A, - *requires*: pytest + *requires*: pytest (<9.0.0,>=6.2.4) A pytest wrapper with fixtures for Playwright to automate web browsers + :pypi:`pytest_playwright_async` + *last release*: Feb 25, 2024, + *status*: N/A, + *requires*: N/A + + ASYNC Pytest plugin for Playwright + + :pypi:`pytest-playwright-asyncio` + *last release*: Aug 29, 2023, + *status*: N/A, + *requires*: N/A + + + + :pypi:`pytest-playwright-enhanced` + *last release*: Mar 24, 2024, + *status*: N/A, + *requires*: pytest<9.0.0,>=8.0.0 + + A pytest plugin for playwright python + :pypi:`pytest-playwrights` *last release*: Dec 02, 2021, *status*: N/A, @@ -5326,8 +8011,22 @@ This list contains 963 plugins. A pytest wrapper for snapshot testing with playwright + :pypi:`pytest-playwright-visual` + *last release*: Apr 28, 2022, + *status*: N/A, + *requires*: N/A + + A pytest fixture for visual testing with Playwright + + :pypi:`pytest-plone` + *last release*: Jan 05, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + Pytest plugin to test Plone addons + :pypi:`pytest-plt` - *last release*: Aug 17, 2020, + *last release*: Jan 17, 2024, *status*: 5 - Production/Stable, *requires*: pytest @@ -5341,9 +8040,9 @@ This list contains 963 plugins. A plugin to help developing and testing other plugins :pypi:`pytest-plus` - *last release*: Mar 19, 2020, + *last release*: Mar 26, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.50) + *requires*: pytest>=7.4.2 PyTest Plus Plugin :: extends pytest functionality @@ -5354,13 +8053,27 @@ This list contains 963 plugins. + :pypi:`pytest-pogo` + *last release*: Mar 11, 2024, + *status*: 1 - Planning, + *requires*: pytest (>=7,<9) + + Pytest plugin for pogo-migrate + :pypi:`pytest-pointers` - *last release*: Oct 14, 2021, + *last release*: Dec 26, 2022, *status*: N/A, *requires*: N/A Pytest plugin to define functions you test with special marks for better navigation and reports + :pypi:`pytest-pokie` + *last release*: Oct 19, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + Pokie plugin for pytest + :pypi:`pytest-polarion-cfme` *last release*: Nov 13, 2017, *status*: 3 - Alpha, @@ -5403,13 +8116,27 @@ This list contains 963 plugins. Visualize your failed tests with poo + :pypi:`pytest-pook` + *last release*: Feb 15, 2024, + *status*: 4 - Beta, + *requires*: pytest + + Pytest plugin for pook + :pypi:`pytest-pop` - *last release*: Aug 19, 2021, + *last release*: May 09, 2023, *status*: 5 - Production/Stable, *requires*: pytest A pytest plugin to help with testing pop projects + :pypi:`pytest-porringer` + *last release*: Jan 18, 2024, + *status*: N/A, + *requires*: pytest>=7.4.4 + + + :pypi:`pytest-portion` *last release*: Jan 28, 2021, *status*: 4 - Beta, @@ -5425,9 +8152,9 @@ This list contains 963 plugins. Run PostgreSQL in Docker container in Pytest. :pypi:`pytest-postgresql` - *last release*: Nov 05, 2021, + *last release*: Mar 11, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.0.0) + *requires*: pytest >=6.2 Postgresql fixtures and fixture factories for Pytest. @@ -5436,10 +8163,31 @@ This list contains 963 plugins. *status*: N/A, *requires*: pytest (>=5.4) - pytest plugin with powerful fixtures + pytest plugin with powerful fixtures + + :pypi:`pytest-powerpack` + *last release*: Mar 17, 2024, + *status*: N/A, + *requires*: pytest (>=8.1.1,<9.0.0) + + + + :pypi:`pytest-prefer-nested-dup-tests` + *last release*: Apr 27, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=7.1.1,<8.0.0) + + A Pytest plugin to drop duplicated tests during collection, but will prefer keeping nested packages. + + :pypi:`pytest-pretty` + *last release*: Apr 05, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest>=7 + + pytest plugin for printing summary data as I want it :pypi:`pytest-pretty-terminal` - *last release*: Nov 24, 2021, + *last release*: Jan 31, 2022, *status*: N/A, *requires*: pytest (>=3.4.1) @@ -5453,12 +8201,33 @@ This list contains 963 plugins. Minitest-style test colors :pypi:`pytest-print` - *last release*: Jun 17, 2021, + *last release*: Aug 25, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=6) + *requires*: pytest>=7.4 pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout) + :pypi:`pytest-priority` + *last release*: Jul 23, 2023, + *status*: N/A, + *requires*: N/A + + pytest plugin for add priority for tests + + :pypi:`pytest-proceed` + *last release*: Apr 10, 2024, + *status*: N/A, + *requires*: pytest + + + + :pypi:`pytest-profiles` + *last release*: Dec 09, 2021, + *status*: 4 - Beta, + *requires*: pytest (>=3.7.0) + + pytest plugin for configuration profiles + :pypi:`pytest-profiling` *last release*: May 28, 2019, *status*: 5 - Production/Stable, @@ -5467,9 +8236,9 @@ This list contains 963 plugins. Profiling plugin for py.test :pypi:`pytest-progress` - *last release*: Nov 09, 2021, + *last release*: Jan 31, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.7) + *requires*: N/A pytest plugin for instant test progress status @@ -5480,6 +8249,13 @@ This list contains 963 plugins. Report test pass / failures to a Prometheus PushGateway + :pypi:`pytest-prometheus-pushgateway` + *last release*: Sep 27, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Pytest report plugin for Zulip + :pypi:`pytest-prosper` *last release*: Sep 24, 2018, *status*: N/A, @@ -5487,6 +8263,13 @@ This list contains 963 plugins. Test helpers for Prosper projects + :pypi:`pytest-prysk` + *last release*: Mar 12, 2024, + *status*: 4 - Beta, + *requires*: pytest (>=7.3.2) + + Pytest plugin for prysk + :pypi:`pytest-pspec` *last release*: Jun 02, 2020, *status*: 4 - Beta, @@ -5502,7 +8285,7 @@ This list contains 963 plugins. pytest plugin for testing applications that use psqlgraph :pypi:`pytest-ptera` - *last release*: Oct 20, 2021, + *last release*: Mar 01, 2022, *status*: N/A, *requires*: pytest (>=6.2.4,<7.0.0) @@ -5515,6 +8298,13 @@ This list contains 963 plugins. Pytest PuDB debugger integration + :pypi:`pytest-pumpkin-spice` + *last release*: Sep 18, 2022, + *status*: 4 - Beta, + *requires*: N/A + + A pytest plugin that makes your test reporting pumpkin-spiced + :pypi:`pytest-purkinje` *last release*: Oct 28, 2017, *status*: 2 - Pre-Alpha, @@ -5522,6 +8312,20 @@ This list contains 963 plugins. py.test plugin for purkinje test runner + :pypi:`pytest-pusher` + *last release*: Jan 06, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=3.6) + + pytest plugin for push report to minio + + :pypi:`pytest-py125` + *last release*: Dec 03, 2022, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-pycharm` *last release*: Aug 13, 2020, *status*: 5 - Production/Stable, @@ -5530,7 +8334,7 @@ This list contains 963 plugins. Plugin for py.test to enter PyCharm debugger on uncaught exceptions :pypi:`pytest-pycodestyle` - *last release*: Aug 10, 2020, + *last release*: Oct 28, 2022, *status*: 3 - Alpha, *requires*: N/A @@ -5544,19 +8348,33 @@ This list contains 963 plugins. py.test plugin to connect to a remote debug server with PyDev or PyCharm. :pypi:`pytest-pydocstyle` - *last release*: Aug 10, 2020, + *last release*: Jan 05, 2023, *status*: 3 - Alpha, *requires*: N/A pytest plugin to run pydocstyle :pypi:`pytest-pylint` - *last release*: Nov 09, 2020, + *last release*: Oct 06, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=5.4) + *requires*: pytest >=7.0 pytest plugin to check source code with pylint + :pypi:`pytest-pymysql-autorecord` + *last release*: Sep 02, 2022, + *status*: N/A, + *requires*: N/A + + Record PyMySQL queries and mock with the stored data. + + :pypi:`pytest-pyodide` + *last release*: Dec 09, 2023, + *status*: N/A, + *requires*: pytest + + Pytest plugin for testing applications that use Pyodide + :pypi:`pytest-pypi` *last release*: Mar 04, 2018, *status*: 3 - Alpha, @@ -5572,11 +8390,11 @@ This list contains 963 plugins. Core engine for cookiecutter-qa and pytest-play packages :pypi:`pytest-pyppeteer` - *last release*: Feb 16, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=6.0.2) + *last release*: Apr 28, 2022, + *status*: N/A, + *requires*: pytest (>=6.2.5,<7.0.0) - A plugin to run pyppeteer in pytest. + A plugin to run pyppeteer in pytest :pypi:`pytest-pyq` *last release*: Mar 10, 2020, @@ -5586,7 +8404,7 @@ This list contains 963 plugins. Pytest fixture "q" for pyq :pypi:`pytest-pyramid` - *last release*: Oct 15, 2021, + *last release*: Oct 11, 2023, *status*: 5 - Production/Stable, *requires*: pytest @@ -5599,13 +8417,34 @@ This list contains 963 plugins. Pyramid server fixture for py.test + :pypi:`pytest-pyreport` + *last release*: Feb 03, 2024, + *status*: N/A, + *requires*: pytest + + PyReport is a lightweight reporting plugin for Pytest that provides concise HTML report + :pypi:`pytest-pyright` - *last release*: Aug 16, 2021, + *last release*: Jan 26, 2024, *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + *requires*: pytest >=7.0.0 Pytest plugin for type checking code with Pyright + :pypi:`pytest-pyspec` + *last release*: Jan 02, 2024, + *status*: N/A, + *requires*: pytest (>=7.2.1,<8.0.0) + + A plugin that transforms the pytest output into a result similar to the RSpec. It enables the use of docstrings to display results and also enables the use of the prefixes "describe", "with" and "it". + + :pypi:`pytest-pystack` + *last release*: Jan 04, 2024, + *status*: N/A, + *requires*: pytest >=3.5.0 + + Plugin to run pystack after a timeout for a test suite. + :pypi:`pytest-pytestrail` *last release*: Aug 27, 2020, *status*: 4 - Beta, @@ -5613,10 +8452,17 @@ This list contains 963 plugins. Pytest plugin for interaction with TestRail + :pypi:`pytest-pythonhashseed` + *last release*: Feb 25, 2024, + *status*: 4 - Beta, + *requires*: pytest>=3.0.0 + + Pytest plugin to set PYTHONHASHSEED env var. + :pypi:`pytest-pythonpath` - *last release*: Aug 22, 2018, + *last release*: Feb 10, 2022, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest (<7,>=2.5.2) pytest plugin for adding to the PYTHONPATH from command line or configs. @@ -5627,6 +8473,27 @@ This list contains 963 plugins. pytest plugin for a better developer experience when working with the PyTorch test suite + :pypi:`pytest-pyvenv` + *last release*: Feb 27, 2024, + *status*: N/A, + *requires*: pytest ; extra == 'test' + + A package for create venv in tests + + :pypi:`pytest-pyvista` + *last release*: Sep 29, 2023, + *status*: 4 - Beta, + *requires*: pytest>=3.5.0 + + Pytest-pyvista package + + :pypi:`pytest-qaseio` + *last release*: Sep 12, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.2.2,<8.0.0) + + Pytest plugin for Qase.io integration + :pypi:`pytest-qasync` *last release*: Jul 12, 2021, *status*: 4 - Beta, @@ -5635,16 +8502,16 @@ This list contains 963 plugins. Pytest support for qasync. :pypi:`pytest-qatouch` - *last release*: Jun 26, 2021, + *last release*: Feb 14, 2023, *status*: 4 - Beta, *requires*: pytest (>=6.2.0) Pytest plugin for uploading test results to your QA Touch Testrun. :pypi:`pytest-qgis` - *last release*: Nov 25, 2021, + *last release*: Nov 29, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=6.2.3) + *requires*: pytest >=6.0 A pytest plugin for testing QGIS python plugins @@ -5663,9 +8530,9 @@ This list contains 963 plugins. pytest plugin to generate test result QR codes :pypi:`pytest-qt` - *last release*: Jun 13, 2021, + *last release*: Feb 07, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.0.0) + *requires*: pytest pytest support for PyQt and PySide applications @@ -5684,21 +8551,28 @@ This list contains 963 plugins. A plugin for pytest to manage expected test failures :pypi:`pytest-quickcheck` - *last release*: Nov 15, 2020, + *last release*: Nov 05, 2022, *status*: 4 - Beta, - *requires*: pytest (<6.0.0,>=4.0) + *requires*: pytest (>=4.0) pytest plugin to generate random data inspired by QuickCheck + :pypi:`pytest_quickify` + *last release*: Jun 14, 2019, + *status*: N/A, + *requires*: pytest + + Run test suites with pytest-quickify. + :pypi:`pytest-rabbitmq` - *last release*: Jun 02, 2021, + *last release*: Jul 05, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.0.0) + *requires*: pytest (>=6.2) RabbitMQ process and client fixtures for pytest :pypi:`pytest-race` - *last release*: Nov 21, 2016, + *last release*: Jun 07, 2022, *status*: 4 - Beta, *requires*: N/A @@ -5711,8 +8585,15 @@ This list contains 963 plugins. pytest plugin to implement PEP712 + :pypi:`pytest-rail` + *last release*: May 02, 2022, + *status*: N/A, + *requires*: pytest (>=3.6) + + pytest plugin for creating TestRail runs and adding results + :pypi:`pytest-railflow-testrail-reporter` - *last release*: Dec 02, 2021, + *last release*: Jun 29, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -5733,7 +8614,7 @@ This list contains 963 plugins. Simple pytest plugin to look for regex in Exceptions :pypi:`pytest-raisin` - *last release*: Jun 25, 2020, + *last release*: Feb 06, 2022, *status*: N/A, *requires*: pytest @@ -5747,7 +8628,7 @@ This list contains 963 plugins. py.test plugin to randomize tests :pypi:`pytest-randomly` - *last release*: Nov 30, 2021, + *last release*: Aug 15, 2023, *status*: 5 - Production/Stable, *requires*: pytest @@ -5768,30 +8649,44 @@ This list contains 963 plugins. Randomise the order in which pytest tests are run with some control over the randomness :pypi:`pytest-random-order` - *last release*: Nov 30, 2018, + *last release*: Jan 20, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.0.0) + *requires*: pytest >=3.0.0 Randomise the order in which pytest tests are run with some control over the randomness + :pypi:`pytest-ranking` + *last release*: Mar 18, 2024, + *status*: 4 - Beta, + *requires*: pytest >=7.4.3 + + A Pytest plugin for automatically prioritizing/ranking tests to speed up failure detection + :pypi:`pytest-readme` - *last release*: Dec 28, 2014, + *last release*: Sep 02, 2022, *status*: 5 - Production/Stable, *requires*: N/A Test your README.md file :pypi:`pytest-reana` - *last release*: Nov 22, 2021, + *last release*: Mar 14, 2024, *status*: 3 - Alpha, *requires*: N/A Pytest fixtures for REANA. + :pypi:`pytest-recorder` + *last release*: Nov 21, 2023, + *status*: N/A, + *requires*: N/A + + Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. + :pypi:`pytest-recording` - *last release*: Jul 08, 2021, + *last release*: Dec 06, 2023, *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + *requires*: pytest>=3.5.0 A pytest plugin that allows you recording of network interactions via VCR.py @@ -5803,14 +8698,14 @@ This list contains 963 plugins. Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal :pypi:`pytest-redis` - *last release*: Nov 03, 2021, + *last release*: Apr 19, 2023, *status*: 5 - Production/Stable, - *requires*: pytest + *requires*: pytest (>=6.2) Redis fixtures and fixture factories for Pytest. :pypi:`pytest-redislite` - *last release*: Sep 19, 2021, + *last release*: Apr 05, 2022, *status*: 4 - Beta, *requires*: pytest @@ -5837,19 +8732,33 @@ This list contains 963 plugins. Conveniently run pytest with a dot-formatted test reference. + :pypi:`pytest-regex` + *last release*: May 29, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + Select pytest tests with regular expressions + + :pypi:`pytest-regex-dependency` + *last release*: Jun 12, 2022, + *status*: N/A, + *requires*: pytest + + Management of Pytest dependencies via regex patterns + :pypi:`pytest-regressions` - *last release*: Jan 27, 2021, + *last release*: Aug 31, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.5.0) + *requires*: pytest >=6.2.0 Easy to use fixtures to write regression tests. :pypi:`pytest-regtest` - *last release*: Jun 03, 2021, + *last release*: Feb 26, 2024, *status*: N/A, - *requires*: N/A + *requires*: pytest>7.2 - pytest plugin for regression tests + pytest plugin for snapshot regression testing :pypi:`pytest-relative-order` *last release*: May 17, 2021, @@ -5859,9 +8768,9 @@ This list contains 963 plugins. a pytest plugin that sorts tests using "before" and "after" markers :pypi:`pytest-relaxed` - *last release*: Jun 14, 2019, + *last release*: Mar 29, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (<5,>=3) + *requires*: pytest>=7 Relaxed test discovery/organization for pytest @@ -5873,21 +8782,21 @@ This list contains 963 plugins. Pytest plugin to create a temporary directory with remote files :pypi:`pytest-remotedata` - *last release*: Jul 20, 2019, - *status*: 3 - Alpha, - *requires*: pytest (>=3.1) + *last release*: Sep 26, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest >=4.6 Pytest plugin for controlling remote data access. :pypi:`pytest-remote-response` - *last release*: Jun 30, 2021, - *status*: 4 - Beta, + *last release*: Apr 26, 2023, + *status*: 5 - Production/Stable, *requires*: pytest (>=4.6) Pytest plugin for capturing and mocking connection requests. :pypi:`pytest-remove-stale-bytecode` - *last release*: Mar 04, 2020, + *last release*: Jul 07, 2023, *status*: 4 - Beta, *requires*: pytest @@ -5901,21 +8810,28 @@ This list contains 963 plugins. Reorder tests depending on their paths and names. :pypi:`pytest-repeat` - *last release*: Oct 31, 2020, + *last release*: Oct 09, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.6) + *requires*: pytest pytest plugin for repeating tests + :pypi:`pytest_repeater` + *last release*: Feb 09, 2018, + *status*: 1 - Planning, + *requires*: N/A + + py.test plugin for repeating single test multiple times. + :pypi:`pytest-replay` - *last release*: Jun 09, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=3.0.0) + *last release*: Jan 11, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests :pypi:`pytest-repo-health` - *last release*: Nov 23, 2021, + *last release*: Apr 17, 2023, *status*: 3 - Alpha, *requires*: pytest @@ -5929,19 +8845,26 @@ This list contains 963 plugins. Creates json report that is compatible with atom.io's linter message format :pypi:`pytest-reporter` - *last release*: Jul 22, 2021, + *last release*: Feb 28, 2024, *status*: 4 - Beta, *requires*: pytest Generate Pytest reports with templates :pypi:`pytest-reporter-html1` - *last release*: Jun 08, 2021, + *last release*: Feb 28, 2024, *status*: 4 - Beta, *requires*: N/A A basic HTML report template for Pytest + :pypi:`pytest-reporter-html-dots` + *last release*: Jan 22, 2023, + *status*: N/A, + *requires*: N/A + + A basic HTML report for pytest using Jinja2 template engine. + :pypi:`pytest-reportinfra` *last release*: Aug 11, 2019, *status*: 3 - Alpha, @@ -5957,9 +8880,9 @@ This list contains 963 plugins. A plugin to report summarized results in a table format :pypi:`pytest-reportlog` - *last release*: Dec 11, 2020, + *last release*: May 22, 2023, *status*: 3 - Alpha, - *requires*: pytest (>=5.2) + *requires*: pytest Replacement for the --resultlog option, focused in simplicity and extensibility @@ -5978,12 +8901,26 @@ This list contains 963 plugins. pytest plugin for adding tests' parameters to junit report :pypi:`pytest-reportportal` - *last release*: Jun 18, 2021, + *last release*: Mar 27, 2024, *status*: N/A, - *requires*: pytest (>=3.8.0) + *requires*: pytest>=3.8.0 Agent for Reporting results of tests to the Report Portal + :pypi:`pytest-report-stream` + *last release*: Oct 22, 2023, + *status*: 4 - Beta, + *requires*: N/A + + A pytest plugin which allows to stream test reports at runtime + + :pypi:`pytest-repo-structure` + *last release*: Mar 18, 2024, + *status*: 1 - Planning, + *requires*: N/A + + Pytest Repo Structure + :pypi:`pytest-reqs` *last release*: May 12, 2019, *status*: N/A, @@ -5998,8 +8935,29 @@ This list contains 963 plugins. A simple plugin to use with pytest + :pypi:`pytest-requestselapsed` + *last release*: Aug 14, 2022, + *status*: N/A, + *requires*: N/A + + collect and show http requests elapsed time + + :pypi:`pytest-requests-futures` + *last release*: Jul 06, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Pytest Plugin to Mock Requests Futures + + :pypi:`pytest-requires` + *last release*: Dec 21, 2021, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A pytest plugin to elegantly skip tests with optional requirements + :pypi:`pytest-reraise` - *last release*: Jun 17, 2021, + *last release*: Sep 20, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=4.6) @@ -6012,19 +8970,47 @@ This list contains 963 plugins. Re-run only changed files in specified branch + :pypi:`pytest-rerun-all` + *last release*: Nov 16, 2023, + *status*: 3 - Alpha, + *requires*: pytest (>=7.0.0) + + Rerun testsuite for a certain time or iterations + + :pypi:`pytest-rerunclassfailures` + *last release*: Mar 29, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest>=7.2 + + pytest rerun class failures plugin + :pypi:`pytest-rerunfailures` - *last release*: Sep 17, 2021, + *last release*: Mar 13, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=5.3) + *requires*: pytest >=7.2 pytest plugin to re-run tests to eliminate flaky failures + :pypi:`pytest-rerunfailures-all-logs` + *last release*: Mar 07, 2022, + *status*: 5 - Production/Stable, + *requires*: N/A + + pytest plugin to re-run tests to eliminate flaky failures + + :pypi:`pytest-reserial` + *last release*: Feb 08, 2024, + *status*: 4 - Beta, + *requires*: pytest + + Pytest fixture for recording and replaying serial port traffic. + :pypi:`pytest-resilient-circuits` - *last release*: Nov 15, 2021, + *last release*: Apr 03, 2024, *status*: N/A, - *requires*: N/A + *requires*: pytest~=4.6; python_version == "2.7" - Resilient Circuits fixtures for PyTest. + Resilient Circuits fixtures for PyTest :pypi:`pytest-resource` *last release*: Nov 14, 2018, @@ -6040,27 +9026,62 @@ This list contains 963 plugins. Provides path for uniform access to test resources in isolated directory + :pypi:`pytest-resource-usage` + *last release*: Nov 06, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest>=7.0.0 + + Pytest plugin for reporting running time and peak memory usage + :pypi:`pytest-responsemock` - *last release*: Oct 10, 2020, + *last release*: Mar 10, 2022, *status*: 5 - Production/Stable, *requires*: N/A Simplified requests calls mocking for pytest :pypi:`pytest-responses` - *last release*: Apr 26, 2021, + *last release*: Oct 11, 2022, *status*: N/A, *requires*: pytest (>=2.5) py.test integration for responses + :pypi:`pytest-rest-api` + *last release*: Aug 08, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.2,<8.0.0) + + + :pypi:`pytest-restrict` - *last release*: Aug 12, 2021, + *last release*: Jul 10, 2023, *status*: 5 - Production/Stable, *requires*: pytest Pytest plugin to restrict the test types allowed + :pypi:`pytest-result-log` + *last release*: Jan 10, 2024, + *status*: N/A, + *requires*: pytest>=7.2.0 + + A pytest plugin that records the start, end, and result information of each use case in a log file + + :pypi:`pytest-result-sender` + *last release*: Apr 20, 2023, + *status*: N/A, + *requires*: pytest>=7.3.1 + + + + :pypi:`pytest-resume` + *last release*: Apr 22, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.0) + + A Pytest plugin to resuming from the last run test + :pypi:`pytest-rethinkdb` *last release*: Jul 24, 2016, *status*: 4 - Beta, @@ -6068,13 +9089,62 @@ This list contains 963 plugins. A RethinkDB plugin for pytest. + :pypi:`pytest-retry` + *last release*: Feb 04, 2024, + *status*: N/A, + *requires*: pytest >=7.0.0 + + Adds the ability to retry flaky tests in CI environments + + :pypi:`pytest-retry-class` + *last release*: Mar 25, 2023, + *status*: N/A, + *requires*: pytest (>=5.3) + + A pytest plugin to rerun entire class on failure + + :pypi:`pytest-reusable-testcases` + *last release*: Apr 28, 2023, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-reverse` - *last release*: Aug 12, 2021, + *last release*: Jul 10, 2023, *status*: 5 - Production/Stable, *requires*: pytest Pytest plugin to reverse test order. + :pypi:`pytest-rich` + *last release*: Mar 03, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=7.0) + + Leverage rich for richer test session output + + :pypi:`pytest-richer` + *last release*: Oct 27, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + Pytest plugin providing a Rich based reporter. + + :pypi:`pytest-rich-reporter` + *last release*: Feb 17, 2022, + *status*: 1 - Planning, + *requires*: pytest (>=5.0.0) + + A pytest plugin using Rich for beautiful test result formatting. + + :pypi:`pytest-richtrace` + *last release*: Jun 20, 2023, + *status*: N/A, + *requires*: N/A + + A pytest plugin that displays the names and information of the pytest hook functions as they are executed. + :pypi:`pytest-ringo` *last release*: Sep 27, 2017, *status*: 3 - Alpha, @@ -6082,6 +9152,13 @@ This list contains 963 plugins. pytest plugin to test webapplications using the Ringo webframework + :pypi:`pytest-rmsis` + *last release*: Aug 10, 2022, + *status*: N/A, + *requires*: pytest (>=5.3.5) + + Sycronise pytest results to Jira RMsis + :pypi:`pytest-rng` *last release*: Aug 08, 2019, *status*: 5 - Production/Stable, @@ -6090,12 +9167,19 @@ This list contains 963 plugins. Fixtures for seeding tests and making randomness reproducible :pypi:`pytest-roast` - *last release*: Jul 29, 2021, + *last release*: Nov 09, 2022, *status*: 5 - Production/Stable, *requires*: pytest pytest plugin for ROAST configuration override and fixtures + :pypi:`pytest_robotframework` + *last release*: Mar 29, 2024, + *status*: N/A, + *requires*: pytest<9,>=7 + + a pytest plugin that can run both python and robotframework tests while generating robot reports for them + :pypi:`pytest-rocketchat` *last release*: Apr 18, 2021, *status*: 5 - Production/Stable, @@ -6118,14 +9202,14 @@ This list contains 963 plugins. Extend py.test for RPC OpenStack testing. :pypi:`pytest-rst` - *last release*: Sep 21, 2021, + *last release*: Jan 26, 2023, *status*: N/A, - *requires*: pytest + *requires*: N/A Test code from RST documents with pytest :pypi:`pytest-rt` - *last release*: Sep 04, 2021, + *last release*: May 05, 2022, *status*: N/A, *requires*: N/A @@ -6138,6 +9222,13 @@ This list contains 963 plugins. Coverage-based regression test selection (RTS) plugin for pytest + :pypi:`pytest-ruff` + *last release*: Mar 10, 2024, + *status*: 4 - Beta, + *requires*: pytest (>=5) + + pytest plugin to check ruff requirements. + :pypi:`pytest-run-changed` *last release*: Apr 02, 2021, *status*: 3 - Alpha, @@ -6152,20 +9243,41 @@ This list contains 963 plugins. implement a --failed option for pytest - :pypi:`pytest-runner` - *last release*: May 19, 2021, + :pypi:`pytest-run-subprocess` + *last release*: Nov 12, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=4.6) ; extra == 'testing' + *requires*: pytest + + Pytest Plugin for running and testing subprocesses. - Invoke py.test as distutils command with dependency resolution + :pypi:`pytest-runtime-types` + *last release*: Feb 09, 2023, + *status*: N/A, + *requires*: pytest + + Checks type annotations on runtime while running tests. :pypi:`pytest-runtime-xfail` *last release*: Aug 26, 2021, *status*: N/A, - *requires*: N/A + *requires*: pytest>=5.0.0 Call runtime_xfail() to mark running test as xfail. + :pypi:`pytest-runtime-yoyo` + *last release*: Jun 12, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.0) + + run case mark timeout + + :pypi:`pytest-saccharin` + *last release*: Oct 31, 2022, + *status*: 3 - Alpha, + *requires*: N/A + + pytest-saccharin is a updated fork of pytest-sugar, a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). + :pypi:`pytest-salt` *last release*: Jan 27, 2020, *status*: 4 - Beta, @@ -6181,9 +9293,9 @@ This list contains 963 plugins. A Pytest plugin that builds and creates docker containers :pypi:`pytest-salt-factories` - *last release*: Sep 16, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=6.0.0) + *last release*: Mar 22, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest>=7.0.0 Pytest Salt Plugin @@ -6222,8 +9334,15 @@ This list contains 963 plugins. + :pypi:`pytest_sauce` + *last release*: Jul 14, 2014, + *status*: 3 - Alpha, + *requires*: N/A + + pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs + :pypi:`pytest-sbase` - *last release*: Dec 03, 2021, + *last release*: Apr 14, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -6236,13 +9355,27 @@ This list contains 963 plugins. pytest plugin for test scenarios + :pypi:`pytest-schedule` + *last release*: Jan 07, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + The job of test scheduling for humans. + :pypi:`pytest-schema` - *last release*: Aug 31, 2020, + *last release*: Feb 16, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.5.0) + *requires*: pytest >=3.5.0 👍 Validate return values against a schema-like object in testing + :pypi:`pytest-screenshot-on-failure` + *last release*: Jul 21, 2023, + *status*: 4 - Beta, + *requires*: N/A + + Saves a screenshot when a test case from a pytest execution fails + :pypi:`pytest-securestore` *last release*: Nov 08, 2021, *status*: 4 - Beta, @@ -6258,21 +9391,28 @@ This list contains 963 plugins. A pytest plugin which allows to (de-)select tests from a file. :pypi:`pytest-selenium` - *last release*: Sep 19, 2020, + *last release*: Feb 01, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=5.0.0) + *requires*: pytest>=6.0.0 pytest plugin for Selenium + :pypi:`pytest-selenium-auto` + *last release*: Nov 07, 2023, + *status*: N/A, + *requires*: pytest >= 7.0.0 + + pytest plugin to automatically capture screenshots upon selenium webdriver events + :pypi:`pytest-seleniumbase` - *last release*: Dec 03, 2021, + *last release*: Apr 14, 2024, *status*: 5 - Production/Stable, *requires*: N/A A complete web automation framework for end-to-end testing. :pypi:`pytest-selenium-enhancer` - *last release*: Nov 26, 2020, + *last release*: Apr 29, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -6285,6 +9425,13 @@ This list contains 963 plugins. A pytest package implementing perceptualdiff for Selenium tests. + :pypi:`pytest-selfie` + *last release*: Apr 05, 2024, + *status*: N/A, + *requires*: pytest<9.0.0,>=8.0.0 + + A pytest plugin for selfie snapshot testing. + :pypi:`pytest-send-email` *last release*: Dec 04, 2019, *status*: N/A, @@ -6293,26 +9440,40 @@ This list contains 963 plugins. Send pytest execution result email :pypi:`pytest-sentry` - *last release*: Apr 21, 2021, + *last release*: Apr 05, 2024, *status*: N/A, *requires*: pytest A pytest plugin to send testrun information to Sentry.io + :pypi:`pytest-sequence-markers` + *last release*: May 23, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + Pytest plugin for sequencing markers for execution of tests + :pypi:`pytest-server-fixtures` - *last release*: May 28, 2019, + *last release*: Dec 19, 2023, *status*: 5 - Production/Stable, *requires*: pytest Extensible server fixures for py.test :pypi:`pytest-serverless` - *last release*: Nov 27, 2021, + *last release*: May 09, 2022, *status*: 4 - Beta, *requires*: N/A Automatically mocks resources from serverless.yml in pytest using moto. + :pypi:`pytest-servers` + *last release*: Mar 19, 2024, + *status*: 3 - Alpha, + *requires*: pytest>=6.2 + + pytest servers + :pypi:`pytest-services` *last release*: Oct 30, 2020, *status*: 6 - Mature, @@ -6341,6 +9502,13 @@ This list contains 963 plugins. pytest-session_to_file is a py.test plugin for capturing and saving to file the stdout of py.test. + :pypi:`pytest-setupinfo` + *last release*: Jan 23, 2023, + *status*: N/A, + *requires*: N/A + + Displaying setup info during pytest command run + :pypi:`pytest-sftpserver` *last release*: Sep 16, 2019, *status*: 4 - Beta, @@ -6355,13 +9523,34 @@ This list contains 963 plugins. + :pypi:`pytest-share-hdf` + *last release*: Sep 21, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + Plugin to save test data in HDF files and retrieve them for comparison + + :pypi:`pytest-sharkreport` + *last release*: Jul 11, 2022, + *status*: N/A, + *requires*: pytest (>=3.5) + + this is pytest report plugin. + :pypi:`pytest-shell` - *last release*: Nov 07, 2021, + *last release*: Mar 27, 2022, *status*: N/A, *requires*: N/A A pytest plugin to help with testing shell scripts / black box commands + :pypi:`pytest-shell-utilities` + *last release*: Feb 23, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest >=7.4.0 + + Pytest plugin to simplify running shell commands against the system + :pypi:`pytest-sheraf` *last release*: Feb 11, 2020, *status*: N/A, @@ -6370,9 +9559,9 @@ This list contains 963 plugins. Versatile ZODB abstraction layer - pytest fixtures :pypi:`pytest-sherlock` - *last release*: Nov 18, 2021, + *last release*: Aug 14, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.5.1) + *requires*: pytest >=3.5.1 pytest plugin help to find coupled tests @@ -6390,6 +9579,13 @@ This list contains 963 plugins. A goodie-bag of unix shell and environment tools for py.test + :pypi:`pytest-simbind` + *last release*: Mar 28, 2024, + *status*: N/A, + *requires*: pytest>=7.0.0 + + Pytest plugin to operate with objects generated by Simbind tool. + :pypi:`pytest-simplehttpserver` *last release*: Jun 24, 2021, *status*: 4 - Beta, @@ -6419,9 +9615,9 @@ This list contains 963 plugins. Allow for multiple processes to log to a single file :pypi:`pytest-skip-markers` - *last release*: Oct 04, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=6.0.0) + *last release*: Jan 04, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest >=7.1.0 Pytest Salt Plugin @@ -6440,12 +9636,19 @@ This list contains 963 plugins. Automatically skip tests that don't need to run! :pypi:`pytest-skip-slow` - *last release*: Sep 28, 2021, + *last release*: Feb 09, 2023, *status*: N/A, - *requires*: N/A + *requires*: pytest>=6.2.0 A pytest plugin to skip \`@pytest.mark.slow\` tests by default. + :pypi:`pytest-skipuntil` + *last release*: Nov 25, 2023, + *status*: 4 - Beta, + *requires*: pytest >=3.8.0 + + A simple pytest plugin to skip flapping test with deadline + :pypi:`pytest-slack` *last release*: Dec 15, 2020, *status*: 5 - Production/Stable, @@ -6460,6 +9663,27 @@ This list contains 963 plugins. A pytest plugin to skip \`@pytest.mark.slow\` tests by default. + :pypi:`pytest-slowest-first` + *last release*: Dec 11, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Sort tests by their last duration, slowest first + + :pypi:`pytest-slow-first` + *last release*: Jan 30, 2024, + *status*: 4 - Beta, + *requires*: pytest >=3.5.0 + + Prioritize running the slowest tests first. + + :pypi:`pytest-slow-last` + *last release*: Dec 10, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + Run tests in order of execution time (faster tests first) + :pypi:`pytest-smartcollect` *last release*: Oct 04, 2018, *status*: N/A, @@ -6474,6 +9698,13 @@ This list contains 963 plugins. Smart coverage plugin for pytest. + :pypi:`pytest-smell` + *last release*: Jun 26, 2022, + *status*: N/A, + *requires*: N/A + + Automated bad smell detection tool for Pytest + :pypi:`pytest-smtp` *last release*: Feb 20, 2021, *status*: N/A, @@ -6481,6 +9712,27 @@ This list contains 963 plugins. Send email with pytest execution result + :pypi:`pytest-smtp4dev` + *last release*: Jun 27, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + Plugin for smtp4dev API + + :pypi:`pytest-smtpd` + *last release*: May 15, 2023, + *status*: N/A, + *requires*: pytest + + An SMTP server for testing built on aiosmtpd + + :pypi:`pytest-smtp-test-server` + *last release*: Dec 03, 2023, + *status*: 2 - Pre-Alpha, + *requires*: pytest (>=7.4.3,<8.0.0) + + pytest plugin for using \`smtp-test-server\` as a fixture + :pypi:`pytest-snail` *last release*: Nov 04, 2019, *status*: 3 - Alpha, @@ -6496,7 +9748,14 @@ This list contains 963 plugins. py.test plugin for Snap-CI :pypi:`pytest-snapshot` - *last release*: Dec 02, 2021, + *last release*: Apr 23, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.0.0) + + A plugin for snapshot testing with pytest. + + :pypi:`pytest-snapshot-with-message-generator` + *last release*: Jul 25, 2023, *status*: 4 - Beta, *requires*: pytest (>=3.0.0) @@ -6509,13 +9768,27 @@ This list contains 963 plugins. + :pypi:`pytest-snowflake-bdd` + *last release*: Jan 05, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=6.2.0) + + Setup test data and run tests on snowflake in BDD style! + :pypi:`pytest-socket` - *last release*: Aug 28, 2021, + *last release*: Jan 28, 2024, *status*: 4 - Beta, - *requires*: pytest (>=3.6.3) + *requires*: pytest (>=6.2.5) Pytest Plugin to disable socket calls during tests + :pypi:`pytest-sofaepione` + *last release*: Aug 17, 2022, + *status*: N/A, + *requires*: N/A + + Test the installation of SOFA and the SofaEpione plugin. + :pypi:`pytest-soft-assertions` *last release*: May 05, 2020, *status*: 3 - Alpha, @@ -6523,6 +9796,13 @@ This list contains 963 plugins. + :pypi:`pytest-solidity` + *last release*: Jan 15, 2022, + *status*: 1 - Planning, + *requires*: pytest (<7,>=6.0.1) ; extra == 'tests' + + A PyTest library plugin for Solidity language. + :pypi:`pytest-solr` *last release*: May 11, 2020, *status*: 3 - Alpha, @@ -6530,6 +9810,13 @@ This list contains 963 plugins. Solr process and client fixtures for py.test. + :pypi:`pytest-sort` + *last release*: Jan 07, 2024, + *status*: N/A, + *requires*: pytest >=7.4.0 + + Tools for sorting test cases + :pypi:`pytest-sorter` *last release*: Apr 20, 2021, *status*: 4 - Beta, @@ -6537,6 +9824,13 @@ This list contains 963 plugins. A simple plugin to first execute tests that historically failed more + :pypi:`pytest-sosu` + *last release*: Aug 04, 2023, + *status*: 2 - Pre-Alpha, + *requires*: pytest + + Unofficial PyTest plugin for Sauce Labs + :pypi:`pytest-sourceorder` *last release*: Sep 01, 2021, *status*: 4 - Beta, @@ -6565,31 +9859,59 @@ This list contains 963 plugins. Library pytest-spec is a pytest plugin to display test execution output like a SPECIFICATION. + :pypi:`pytest-spec2md` + *last release*: Apr 10, 2024, + *status*: N/A, + *requires*: pytest>7.0 + + Library pytest-spec2md is a pytest plugin to create a markdown specification while running pytest. + + :pypi:`pytest-speed` + *last release*: Jan 22, 2023, + *status*: 3 - Alpha, + *requires*: pytest>=7 + + Modern benchmarking library for python with pytest integration. + :pypi:`pytest-sphinx` - *last release*: Aug 05, 2020, + *last release*: Apr 13, 2024, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest>=8.1.1 Doctest plugin for pytest with support for Sphinx-specific doctest-directives :pypi:`pytest-spiratest` - *last release*: Oct 13, 2021, + *last release*: Jan 01, 2024, *status*: N/A, *requires*: N/A - Exports unit tests as test runs in SpiraTest/Team/Plan + Exports unit tests as test runs in Spira (SpiraTest/Team/Plan) :pypi:`pytest-splinter` - *last release*: Dec 25, 2020, + *last release*: Sep 09, 2022, *status*: 6 - Mature, - *requires*: N/A + *requires*: pytest (>=3.0.0) Splinter plugin for pytest testing framework + :pypi:`pytest-splinter4` + *last release*: Feb 01, 2024, + *status*: 6 - Mature, + *requires*: pytest >=8.0.0 + + Pytest plugin for the splinter automation library + :pypi:`pytest-split` - *last release*: Nov 09, 2021, + *last release*: Jan 29, 2024, + *status*: 4 - Beta, + *requires*: pytest (>=5,<9) + + Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. + + :pypi:`pytest-split-ext` + *last release*: Sep 23, 2023, *status*: 4 - Beta, - *requires*: pytest (>=5,<7) + *requires*: pytest (>=5,<8) Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. @@ -6615,14 +9937,14 @@ This list contains 963 plugins. :pypi:`pytest-splunk-addon` - *last release*: Nov 29, 2021, + *last release*: Apr 19, 2024, *status*: N/A, - *requires*: pytest (>5.4.0,<6.3) + *requires*: pytest (>5.4.0,<8) A Dynamic test tool for Splunk Apps and Add-ons :pypi:`pytest-splunk-addon-ui-smartx` - *last release*: Oct 07, 2021, + *last release*: Mar 26, 2024, *status*: N/A, *requires*: N/A @@ -6649,6 +9971,20 @@ This list contains 963 plugins. pytest plugin with sqlalchemy related fixtures + :pypi:`pytest-sqlalchemy-mock` + *last release*: Mar 15, 2023, + *status*: 3 - Alpha, + *requires*: pytest (>=2.0) + + pytest sqlalchemy plugin for mock + + :pypi:`pytest-sqlalchemy-session` + *last release*: May 19, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.0) + + A pytest plugin for preserving test isolation that use SQLAlchemy. + :pypi:`pytest-sql-bigquery` *last release*: Dec 19, 2019, *status*: N/A, @@ -6656,10 +9992,24 @@ This list contains 963 plugins. Yet another SQL-testing framework for BigQuery provided by pytest plugin + :pypi:`pytest-sqlfluff` + *last release*: Dec 21, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A pytest plugin to use sqlfluff to enable format checking of sql files. + + :pypi:`pytest-squadcast` + *last release*: Feb 22, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Pytest report plugin for Squadcast + :pypi:`pytest-srcpaths` *last release*: Oct 15, 2021, *status*: N/A, - *requires*: N/A + *requires*: pytest>=6.2.0 Add paths to sys.path @@ -6677,6 +10027,20 @@ This list contains 963 plugins. Start pytest run from a given point + :pypi:`pytest-star-track-issue` + *last release*: Feb 20, 2024, + *status*: N/A, + *requires*: N/A + + A package to prevent Dependency Confusion attacks against Yandex. + + :pypi:`pytest-static` + *last release*: Jan 15, 2024, + *status*: 1 - Planning, + *requires*: pytest (>=7.4.3,<8.0.0) + + pytest-static + :pypi:`pytest-statsd` *last release*: Nov 30, 2018, *status*: 5 - Production/Stable, @@ -6705,6 +10069,13 @@ This list contains 963 plugins. Run a test suite one failing test at a time. + :pypi:`pytest-stf` + *last release*: Mar 25, 2024, + *status*: N/A, + *requires*: pytest>=5.0 + + pytest plugin for openSTF + :pypi:`pytest-stoq` *last release*: Feb 09, 2021, *status*: 4 - Beta, @@ -6712,6 +10083,13 @@ This list contains 963 plugins. A plugin to pytest stoq + :pypi:`pytest-store` + *last release*: Nov 16, 2023, + *status*: 3 - Alpha, + *requires*: pytest (>=7.0.0) + + Pytest plugin to store values from test runs + :pypi:`pytest-stress` *last release*: Dec 07, 2019, *status*: 4 - Beta, @@ -6720,7 +10098,7 @@ This list contains 963 plugins. A Pytest plugin that allows you to loop tests for a user defined amount of time. :pypi:`pytest-structlog` - *last release*: Sep 21, 2021, + *last release*: Mar 13, 2024, *status*: N/A, *requires*: pytest @@ -6754,54 +10132,68 @@ This list contains 963 plugins. A pytest plugin to organize long run tests (named studies) without interfering the regular tests + :pypi:`pytest-subinterpreter` + *last release*: Nov 25, 2023, + *status*: N/A, + *requires*: pytest>=7.0.0 + + Run pytest in a subinterpreter + :pypi:`pytest-subprocess` - *last release*: Nov 07, 2021, + *last release*: Jan 28, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=4.0.0) A plugin to fake subprocess for pytest :pypi:`pytest-subtesthack` - *last release*: Mar 02, 2021, + *last release*: Jul 16, 2022, *status*: N/A, *requires*: N/A A hack to explicitly set up and tear down fixtures. :pypi:`pytest-subtests` - *last release*: May 29, 2021, + *last release*: Mar 07, 2024, *status*: 4 - Beta, - *requires*: pytest (>=5.3.0) + *requires*: pytest >=7.0 unittest subTest() support and subtests fixture :pypi:`pytest-subunit` - *last release*: Aug 29, 2017, + *last release*: Sep 17, 2023, *status*: N/A, - *requires*: N/A + *requires*: pytest (>=2.3) pytest-subunit is a plugin for py.test which outputs testsresult in subunit format. :pypi:`pytest-sugar` - *last release*: Jul 06, 2020, - *status*: 3 - Alpha, - *requires*: N/A + *last release*: Feb 01, 2024, + *status*: 4 - Beta, + *requires*: pytest >=6.2.0 pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). - :pypi:`pytest-sugar-bugfix159` - *last release*: Nov 07, 2018, - *status*: 5 - Production/Stable, - *requires*: pytest (!=3.7.3,>=3.5); extra == 'testing' + :pypi:`pytest-suitemanager` + *last release*: Apr 28, 2023, + *status*: 4 - Beta, + *requires*: N/A - Workaround for https://github.com/Frozenball/pytest-sugar/issues/159 + A simple plugin to use with pytest - :pypi:`pytest-super-check` - *last release*: Aug 12, 2021, - *status*: 5 - Production/Stable, - *requires*: pytest + :pypi:`pytest-suite-timeout` + *last release*: Jan 26, 2024, + *status*: N/A, + *requires*: pytest>=7.0.0 + + A pytest plugin for ensuring max suite time + + :pypi:`pytest-supercov` + *last release*: Jul 02, 2023, + *status*: N/A, + *requires*: N/A - Pytest plugin to check your TestCase classes call super in setUp, tearDown, etc. + Pytest plugin for measuring explicit test-file to source-file coverage :pypi:`pytest-svn` *last release*: May 28, 2019, @@ -6817,8 +10209,36 @@ This list contains 963 plugins. pytest-symbols is a pytest plugin that adds support for passing test environment symbols into pytest tests. + :pypi:`pytest-synodic` + *last release*: Mar 09, 2024, + *status*: N/A, + *requires*: pytest>=8.0.2 + + Synodic Pytest utilities + + :pypi:`pytest-system-statistics` + *last release*: Feb 16, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=6.0.0) + + Pytest plugin to track and report system usage statistics + + :pypi:`pytest-system-test-plugin` + *last release*: Feb 03, 2022, + *status*: N/A, + *requires*: N/A + + Pyst - Pytest System-Test Plugin + + :pypi:`pytest_tagging` + *last release*: Apr 08, 2024, + *status*: N/A, + *requires*: pytest<8.0.0,>=7.1.3 + + a pytest plugin to tag tests + :pypi:`pytest-takeltest` - *last release*: Oct 13, 2021, + *last release*: Feb 15, 2023, *status*: N/A, *requires*: N/A @@ -6831,8 +10251,15 @@ This list contains 963 plugins. + :pypi:`pytest-tally` + *last release*: May 22, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=6.2.5) + + A Pytest plugin to generate realtime summary stats, and display them in-console using a text-based dashboard. + :pypi:`pytest-tap` - *last release*: Oct 27, 2021, + *last release*: Jul 15, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=3.0) @@ -6859,6 +10286,20 @@ This list contains 963 plugins. tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used + :pypi:`pytest-tcpclient` + *last release*: Nov 16, 2022, + *status*: N/A, + *requires*: pytest (<8,>=7.1.3) + + A pytest plugin for testing TCP clients + + :pypi:`pytest-tdd` + *last release*: Aug 18, 2023, + *status*: 4 - Beta, + *requires*: N/A + + run pytest on a python module + :pypi:`pytest-teamcity-logblock` *last release*: May 15, 2018, *status*: 4 - Beta, @@ -6873,6 +10314,13 @@ This list contains 963 plugins. Pytest to Telegram reporting plugin + :pypi:`pytest-telegram-notifier` + *last release*: Jun 27, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + Telegram notification plugin for Pytest + :pypi:`pytest-tempdir` *last release*: Oct 11, 2019, *status*: 4 - Beta, @@ -6880,8 +10328,15 @@ This list contains 963 plugins. Predictable and repeatable tempdir support. + :pypi:`pytest-terra-fixt` + *last release*: Sep 15, 2022, + *status*: N/A, + *requires*: pytest (==6.2.5) + + Terraform and Terragrunt fixtures for pytest + :pypi:`pytest-terraform` - *last release*: Nov 10, 2021, + *last release*: Jun 20, 2023, *status*: N/A, *requires*: pytest (>=6.0) @@ -6909,19 +10364,26 @@ This list contains 963 plugins. Test configuration plugin for pytest. :pypi:`pytest-testdirectory` - *last release*: Nov 06, 2018, + *last release*: May 02, 2023, *status*: 5 - Production/Stable, *requires*: pytest A py.test plugin providing temporary directories in unit tests. :pypi:`pytest-testdox` - *last release*: Oct 13, 2020, + *last release*: Jul 22, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.7.0) + *requires*: pytest (>=4.6.0) A testdox format reporter for pytest + :pypi:`pytest-test-grouping` + *last release*: Feb 01, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=2.5) + + A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. + :pypi:`pytest-test-groups` *last release*: Oct 25, 2016, *status*: 5 - Production/Stable, @@ -6930,9 +10392,23 @@ This list contains 963 plugins. A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. :pypi:`pytest-testinfra` - *last release*: Jun 20, 2021, + *last release*: Feb 15, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest >=6 + + Test infrastructures + + :pypi:`pytest-testinfra-jpic` + *last release*: Sep 21, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + Test infrastructures + + :pypi:`pytest-testinfra-winrm-transport` + *last release*: Sep 21, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (!=3.0.2) + *requires*: N/A Test infrastructures @@ -6944,9 +10420,30 @@ This list contains 963 plugins. pytest reporting plugin for testlink :pypi:`pytest-testmon` - *last release*: Oct 22, 2021, + *last release*: Feb 27, 2024, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest <9,>=5 + + selects tests affected by changed files and methods + + :pypi:`pytest-testmon-dev` + *last release*: Mar 30, 2023, + *status*: 4 - Beta, + *requires*: pytest (<8,>=5) + + selects tests affected by changed files and methods + + :pypi:`pytest-testmon-oc` + *last release*: Jun 01, 2022, + *status*: 4 - Beta, + *requires*: pytest (<8,>=5) + + nOly selects tests affected by changed files and methods + + :pypi:`pytest-testmon-skip-libraries` + *last release*: Mar 03, 2023, + *status*: 4 - Beta, + *requires*: pytest (<8,>=5) selects tests affected by changed files and methods @@ -6957,6 +10454,13 @@ This list contains 963 plugins. Plugin to use TestObject Suites with Pytest + :pypi:`pytest-testpluggy` + *last release*: Jan 07, 2022, + *status*: N/A, + *requires*: pytest + + set your encoding + :pypi:`pytest-testrail` *last release*: Aug 27, 2020, *status*: N/A, @@ -6965,21 +10469,14 @@ This list contains 963 plugins. pytest plugin for creating TestRail runs and adding results :pypi:`pytest-testrail2` - *last release*: Nov 17, 2020, - *status*: N/A, - *requires*: pytest (>=5) - - A small example package - - :pypi:`pytest-testrail-api` - *last release*: Nov 30, 2021, + *last release*: Feb 10, 2023, *status*: N/A, - *requires*: pytest (>=5.5) + *requires*: pytest (<8.0,>=7.2.0) - Плагин Pytest, для интеграции с TestRail + A pytest plugin to upload results to TestRail. :pypi:`pytest-testrail-api-client` - *last release*: Dec 03, 2021, + *last release*: Dec 14, 2021, *status*: N/A, *requires*: pytest @@ -7006,10 +10503,17 @@ This list contains 963 plugins. pytest plugin for creating TestRail runs and adding results + :pypi:`pytest-testrail-integrator` + *last release*: Aug 01, 2022, + *status*: N/A, + *requires*: pytest (>=6.2.5) + + Pytest plugin for sending report to testrail system. + :pypi:`pytest-testrail-ns` - *last release*: Oct 08, 2021, + *last release*: Aug 12, 2022, *status*: N/A, - *requires*: pytest (>=3.6) + *requires*: N/A pytest plugin for creating TestRail runs and adding results @@ -7027,13 +10531,27 @@ This list contains 963 plugins. + :pypi:`pytest-testrail-results` + *last release*: Mar 04, 2024, + *status*: N/A, + *requires*: pytest >=7.2.0 + + A pytest plugin to upload results to TestRail. + :pypi:`pytest-testreport` - *last release*: Nov 12, 2021, + *last release*: Dec 01, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) + :pypi:`pytest-testreport-new` + *last release*: Oct 07, 2023, + *status*: 4 - Beta, + *requires*: pytest >=3.5.0 + + + :pypi:`pytest-testslide` *last release*: Jan 07, 2021, *status*: 5 - Production/Stable, @@ -7049,19 +10567,26 @@ This list contains 963 plugins. Plugin for py.test to run relevant tests, based on naively checking if a test contains a reference to the symbol you supply :pypi:`pytest-test-utils` - *last release*: Nov 30, 2021, + *last release*: Feb 08, 2024, *status*: N/A, - *requires*: pytest (>=5) + *requires*: pytest >=3.9 :pypi:`pytest-tesults` - *last release*: Jul 31, 2021, + *last release*: Feb 15, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.5.0) + *requires*: pytest >=3.5.0 Tesults plugin for pytest + :pypi:`pytest-textual-snapshot` + *last release*: Aug 23, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.0.0) + + Snapshot testing for Textual apps + :pypi:`pytest-tezos` *last release*: Jan 16, 2020, *status*: 4 - Beta, @@ -7069,6 +10594,13 @@ This list contains 963 plugins. pytest-ligo + :pypi:`pytest-th2-bdd` + *last release*: May 13, 2022, + *status*: N/A, + *requires*: N/A + + pytest_th2_bdd + :pypi:`pytest-thawgun` *last release*: May 26, 2020, *status*: 3 - Alpha, @@ -7076,10 +10608,17 @@ This list contains 963 plugins. Pytest plugin for time travel + :pypi:`pytest-thread` + *last release*: Jul 07, 2023, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-threadleak` - *last release*: Sep 08, 2017, + *last release*: Jul 03, 2022, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest (>=3.1.1) Detects thread leaks @@ -7090,6 +10629,20 @@ This list contains 963 plugins. Ticking on tests + :pypi:`pytest-time` + *last release*: Jun 24, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + + + :pypi:`pytest-timeassert-ethan` + *last release*: Dec 25, 2023, + *status*: N/A, + *requires*: pytest + + execution duration + :pypi:`pytest-timeit` *last release*: Oct 13, 2016, *status*: 4 - Beta, @@ -7098,9 +10651,9 @@ This list contains 963 plugins. A pytest plugin to time test function runs :pypi:`pytest-timeout` - *last release*: Oct 11, 2021, + *last release*: Mar 07, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=5.0.0) + *requires*: pytest >=7.0.0 pytest plugin to abort hanging tests @@ -7112,35 +10665,56 @@ This list contains 963 plugins. Linux-only Pytest plugin to control durations of various test case execution phases :pypi:`pytest-timer` - *last release*: Jun 02, 2021, + *last release*: Dec 26, 2023, *status*: N/A, - *requires*: N/A + *requires*: pytest A timer plugin for pytest :pypi:`pytest-timestamper` - *last release*: Jun 06, 2021, + *last release*: Mar 27, 2024, *status*: N/A, *requires*: N/A Pytest plugin to add a timestamp prefix to the pytest output - :pypi:`pytest-tipsi-django` - *last release*: Nov 17, 2021, + :pypi:`pytest-timestamps` + *last release*: Sep 11, 2023, + *status*: N/A, + *requires*: pytest (>=7.3,<8.0) + + A simple plugin to view timestamps for each test + + :pypi:`pytest-tiny-api-client` + *last release*: Jan 04, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest + + The companion pytest plugin for tiny-api-client + + :pypi:`pytest-tinybird` + *last release*: Jun 26, 2023, *status*: 4 - Beta, - *requires*: pytest (>=6.0.0) + *requires*: pytest (>=3.8.0) + + A pytest plugin to report test results to tinybird + :pypi:`pytest-tipsi-django` + *last release*: Feb 05, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest>=6.0.0 + Better fixtures for django :pypi:`pytest-tipsi-testing` - *last release*: Nov 04, 2020, - *status*: 4 - Beta, - *requires*: pytest (>=3.3.0) + *last release*: Feb 04, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest>=3.3.0 Better fixtures management. Various helpers :pypi:`pytest-tldr` - *last release*: Mar 12, 2021, + *last release*: Oct 26, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) @@ -7153,13 +10727,41 @@ This list contains 963 plugins. Cloud Jira Test Management (TM4J) PyTest reporter plugin + :pypi:`pytest-tmnet` + *last release*: Mar 01, 2022, + *status*: N/A, + *requires*: N/A + + A small example package + + :pypi:`pytest-tmp-files` + *last release*: Dec 08, 2023, + *status*: N/A, + *requires*: pytest + + Utilities to create temporary file hierarchies in pytest. + + :pypi:`pytest-tmpfs` + *last release*: Aug 29, 2022, + *status*: N/A, + *requires*: pytest + + A pytest plugin that helps you on using a temporary filesystem for testing. + :pypi:`pytest-tmreport` - *last release*: Nov 17, 2021, + *last release*: Aug 12, 2022, *status*: N/A, *requires*: N/A this is a vue-element ui report for pytest + :pypi:`pytest-tmux` + *last release*: Apr 22, 2023, + *status*: 4 - Beta, + *requires*: N/A + + A pytest plugin that enables tmux driven tests + :pypi:`pytest-todo` *last release*: May 23, 2019, *status*: 4 - Beta, @@ -7188,6 +10790,20 @@ This list contains 963 plugins. Numerous useful plugins for pytest. + :pypi:`pytest-toolkit` + *last release*: Apr 13, 2024, + *status*: N/A, + *requires*: N/A + + Useful utils for testing + + :pypi:`pytest-tools` + *last release*: Oct 21, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Pytest tools + :pypi:`pytest-tornado` *last release*: Jun 17, 2020, *status*: 5 - Production/Stable, @@ -7216,6 +10832,13 @@ This list contains 963 plugins. py.test plugin for testing Python 3.5+ Tornado code + :pypi:`pytest-trace` + *last release*: Jun 19, 2022, + *status*: N/A, + *requires*: pytest (>=4.6) + + Save OpenTelemetry spans generated during testing + :pypi:`pytest-track` *last release*: Feb 26, 2021, *status*: 3 - Alpha, @@ -7224,9 +10847,9 @@ This list contains 963 plugins. :pypi:`pytest-translations` - *last release*: Nov 05, 2021, + *last release*: Sep 11, 2023, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest (>=7) Test your translation files. @@ -7259,12 +10882,19 @@ This list contains 963 plugins. py.test plugin for using the same _trial_temp working directory as trial :pypi:`pytest-trio` - *last release*: Oct 16, 2020, + *last release*: Nov 01, 2022, *status*: N/A, - *requires*: N/A + *requires*: pytest (>=7.2.0) Pytest plugin for trio + :pypi:`pytest-trytond` + *last release*: Nov 04, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=5) + + Pytest plugin for the Tryton server framework + :pypi:`pytest-tspwplib` *last release*: Jan 08, 2021, *status*: 4 - Beta, @@ -7272,6 +10902,13 @@ This list contains 963 plugins. A simple plugin to use with tspwplib + :pypi:`pytest-tst` + *last release*: Apr 27, 2022, + *status*: N/A, + *requires*: pytest (>=5.0.0) + + Customize pytest options, output and exit code to make it compatible with tst + :pypi:`pytest-tstcls` *last release*: Mar 23, 2020, *status*: 5 - Production/Stable, @@ -7279,15 +10916,57 @@ This list contains 963 plugins. Test Class Base + :pypi:`pytest-tui` + *last release*: Dec 08, 2023, + *status*: 4 - Beta, + *requires*: N/A + + Text User Interface (TUI) and HTML report for Pytest test runs + + :pypi:`pytest-tutorials` + *last release*: Mar 11, 2023, + *status*: N/A, + *requires*: N/A + + + + :pypi:`pytest-twilio-conversations-client-mock` + *last release*: Aug 02, 2022, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-twisted` - *last release*: Aug 30, 2021, + *last release*: Mar 19, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.3) + *requires*: pytest >=2.3 A twisted plugin for pytest. + :pypi:`pytest-typechecker` + *last release*: Feb 04, 2022, + *status*: N/A, + *requires*: pytest (>=6.2.5,<7.0.0) + + Run type checkers on specified test files + + :pypi:`pytest-typhoon-config` + *last release*: Apr 07, 2022, + *status*: 5 - Production/Stable, + *requires*: N/A + + A Typhoon HIL plugin that facilitates test parameter configuration at runtime + + :pypi:`pytest-typhoon-polarion` + *last release*: Feb 01, 2024, + *status*: 4 - Beta, + *requires*: N/A + + Typhoontest plugin for Siemens Polarion + :pypi:`pytest-typhoon-xray` - *last release*: Nov 03, 2021, + *last release*: Aug 15, 2023, *status*: 4 - Beta, *requires*: N/A @@ -7314,6 +10993,34 @@ This list contains 963 plugins. Text User Interface for running python tests + :pypi:`pytest-ui-failed-screenshot` + *last release*: Dec 06, 2022, + *status*: N/A, + *requires*: N/A + + UI自动测试失败时自动截图,并将截图加入到测试报告中 + + :pypi:`pytest-ui-failed-screenshot-allure` + *last release*: Dec 06, 2022, + *status*: N/A, + *requires*: N/A + + UI自动测试失败时自动截图,并将截图加入到Allure测试报告中 + + :pypi:`pytest-uncollect-if` + *last release*: Mar 24, 2024, + *status*: 4 - Beta, + *requires*: pytest>=6.2.0 + + A plugin to uncollect pytests tests rather than using skipif + + :pypi:`pytest-unflakable` + *last release*: Nov 12, 2023, + *status*: 4 - Beta, + *requires*: pytest >=6.2.0 + + Unflakable plugin for PyTest + :pypi:`pytest-unhandled-exception-exit-code` *last release*: Jun 22, 2020, *status*: 5 - Production/Stable, @@ -7321,6 +11028,13 @@ This list contains 963 plugins. Plugin for py.test set a different exit code on uncaught exceptions + :pypi:`pytest-unique` + *last release*: Sep 15, 2023, + *status*: N/A, + *requires*: pytest (>=7.4.2,<8.0.0) + + Pytest fixture to generate unique values. + :pypi:`pytest-unittest-filter` *last release*: Jan 12, 2019, *status*: 4 - Beta, @@ -7336,12 +11050,26 @@ This list contains 963 plugins. Run only unmarked tests :pypi:`pytest-unordered` - *last release*: Mar 28, 2021, + *last release*: Mar 13, 2024, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest >=7.0.0 Test equality of unordered collections in pytest + :pypi:`pytest-unstable` + *last release*: Sep 27, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Set a test as unstable to return 0 even if it failed + + :pypi:`pytest-unused-fixtures` + *last release*: Apr 08, 2024, + *status*: 4 - Beta, + *requires*: pytest>7.3.2 + + A pytest plugin to list unused fixtures after a test run. + :pypi:`pytest-upload-report` *last release*: Jun 18, 2021, *status*: 5 - Production/Stable, @@ -7350,9 +11078,9 @@ This list contains 963 plugins. pytest-upload-report is a plugin for pytest that upload your test report for test results. :pypi:`pytest-utils` - *last release*: Dec 04, 2021, + *last release*: Feb 02, 2023, *status*: 4 - Beta, - *requires*: pytest (>=6.2.5,<7.0.0) + *requires*: pytest (>=7.0.0,<8.0.0) Some helpers for pytest. @@ -7371,14 +11099,14 @@ This list contains 963 plugins. :pypi:`pytest-variables` - *last release*: Oct 23, 2019, + *last release*: Feb 01, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.4.2) + *requires*: pytest>=7.0.0 pytest plugin for providing variables to tests/fixtures :pypi:`pytest-variant` - *last release*: Jun 20, 2021, + *last release*: Jun 06, 2022, *status*: N/A, *requires*: N/A @@ -7392,9 +11120,9 @@ This list contains 963 plugins. Plugin for managing VCR.py cassettes :pypi:`pytest-vcr-delete-on-fail` - *last release*: Aug 13, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=6.2.2,<7.0.0) + *last release*: Feb 16, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest (>=8.0.0,<9.0.0) A pytest plugin that automates vcrpy cassettes deletion on test failure. @@ -7405,19 +11133,26 @@ This list contains 963 plugins. Test from HTTP interactions to dataframe processed. + :pypi:`pytest-vcs` + *last release*: Sep 22, 2022, + *status*: 4 - Beta, + *requires*: N/A + + + :pypi:`pytest-venv` - *last release*: Aug 04, 2020, + *last release*: Nov 23, 2023, *status*: 4 - Beta, *requires*: pytest py.test fixture for creating a virtual environment :pypi:`pytest-ver` - *last release*: Aug 30, 2021, - *status*: 2 - Pre-Alpha, - *requires*: N/A + *last release*: Feb 07, 2024, + *status*: 4 - Beta, + *requires*: pytest - Pytest module with Verification Report + Pytest module with Verification Protocol, Verification Report and Trace Matrix :pypi:`pytest-verbose-parametrize` *last release*: May 28, 2019, @@ -7440,6 +11175,20 @@ This list contains 963 plugins. Virtualenv fixture for py.test + :pypi:`pytest-visual` + *last release*: Nov 01, 2023, + *status*: 3 - Alpha, + *requires*: pytest >=7.0.0 + + + + :pypi:`pytest-vnc` + *last release*: Nov 06, 2023, + *status*: N/A, + *requires*: pytest + + VNC client for Pytest + :pypi:`pytest-voluptuous` *last release*: Jun 09, 2020, *status*: N/A, @@ -7454,6 +11203,13 @@ This list contains 963 plugins. A pytest plugin to easily enable debugging tests within Visual Studio Code + :pypi:`pytest-vscode-pycharm-cls` + *last release*: Feb 01, 2023, + *status*: N/A, + *requires*: pytest + + A PyTest helper to enable start remote debugger on test start or failure or when pytest.set_trace is used. + :pypi:`pytest-vts` *last release*: Jun 05, 2019, *status*: N/A, @@ -7461,6 +11217,13 @@ This list contains 963 plugins. pytest plugin for automatic recording of http stubbed tests + :pypi:`pytest-vulture` + *last release*: Jun 01, 2023, + *status*: N/A, + *requires*: pytest (>=7.0.0) + + A pytest plugin to checks dead code with vulture + :pypi:`pytest-vw` *last release*: Oct 07, 2015, *status*: 4 - Beta, @@ -7482,6 +11245,13 @@ This list contains 963 plugins. Pytest plugin for testing whatsapp bots with end to end tests + :pypi:`pytest-wake` + *last release*: Mar 20, 2024, + *status*: N/A, + *requires*: pytest + + + :pypi:`pytest-watch` *last release*: May 20, 2018, *status*: N/A, @@ -7490,11 +11260,18 @@ This list contains 963 plugins. Local continuous test runner with pytest and watchdog. :pypi:`pytest-watcher` - *last release*: Sep 18, 2021, - *status*: 3 - Alpha, + *last release*: Apr 01, 2024, + *status*: 4 - Beta, + *requires*: N/A + + Automatically rerun your tests on file modifications + + :pypi:`pytest_wdb` + *last release*: Jul 04, 2016, + *status*: N/A, *requires*: N/A - Continiously runs pytest on changes in \*.py files + Trace pytest tests with wdb to halt on error with --wdb. :pypi:`pytest-wdl` *last release*: Nov 17, 2020, @@ -7503,6 +11280,13 @@ This list contains 963 plugins. Pytest plugin for testing WDL workflows. + :pypi:`pytest-web3-data` + *last release*: Oct 04, 2023, + *status*: 4 - Beta, + *requires*: pytest + + A pytest plugin to fetch test data from IPFS HTTP gateways during pytest execution. + :pypi:`pytest-webdriver` *last release*: May 28, 2019, *status*: 5 - Production/Stable, @@ -7510,6 +11294,13 @@ This list contains 963 plugins. Selenium webdriver fixture for py.test + :pypi:`pytest-webtest-extras` + *last release*: Nov 13, 2023, + *status*: N/A, + *requires*: pytest >= 7.0.0 + + Pytest plugin to enhance pytest-html and allure reports of webtest projects by adding screenshots, comments and webpage sources. + :pypi:`pytest-wetest` *last release*: Nov 10, 2018, *status*: 4 - Beta, @@ -7517,6 +11308,13 @@ This list contains 963 plugins. Welian API Automation test framework pytest plugin + :pypi:`pytest-when` + *last release*: Mar 22, 2024, + *status*: N/A, + *requires*: pytest>=7.3.1 + + Utility which makes mocking more readable and controllable + :pypi:`pytest-whirlwind` *last release*: Jun 12, 2020, *status*: N/A, @@ -7545,6 +11343,13 @@ This list contains 963 plugins. Windows tray notifications for py.test results. + :pypi:`pytest-wiremock` + *last release*: Mar 27, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.1,<8.0.0) + + A pytest plugin for programmatically using wiremock in integration tests + :pypi:`pytest-with-docker` *last release*: Nov 09, 2021, *status*: N/A, @@ -7553,18 +11358,18 @@ This list contains 963 plugins. pytest with docker helpers. :pypi:`pytest-workflow` - *last release*: Dec 03, 2021, + *last release*: Mar 18, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=5.4.0) + *requires*: pytest >=7.0.0 A pytest plugin for configuring workflow/pipeline tests using YAML files :pypi:`pytest-xdist` - *last release*: Sep 21, 2021, + *last release*: Apr 19, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=6.0.0) + *requires*: pytest >=6.2.0 - pytest xdist plugin for distributed testing and loop-on-failing modes + pytest xdist plugin for distributed testing, most importantly across multiple CPUs :pypi:`pytest-xdist-debug-for-graingert` *last release*: Jul 24, 2019, @@ -7587,6 +11392,13 @@ This list contains 963 plugins. pytest plugin helps to reproduce failures for particular xdist node + :pypi:`pytest-xdist-worker-stats` + *last release*: Apr 16, 2024, + *status*: 4 - Beta, + *requires*: pytest>=7.0.0 + + A pytest plugin to list worker statistics after a xdist run. + :pypi:`pytest-xfaillist` *last release*: Sep 17, 2021, *status*: N/A, @@ -7601,6 +11413,13 @@ This list contains 963 plugins. Pytest fixtures providing data read from function, module or package related (x)files. + :pypi:`pytest-xiuyu` + *last release*: Jul 25, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + This is a pytest plugin + :pypi:`pytest-xlog` *last release*: May 31, 2020, *status*: 4 - Beta, @@ -7608,6 +11427,13 @@ This list contains 963 plugins. Extended logging for test and decorators + :pypi:`pytest-xlsx` + *last release*: Mar 22, 2024, + *status*: N/A, + *requires*: N/A + + pytest plugin for generating test cases by xlsx(excel) + :pypi:`pytest-xpara` *last release*: Oct 30, 2017, *status*: 3 - Alpha, @@ -7616,9 +11442,9 @@ This list contains 963 plugins. An extended parametrizing plugin of pytest. :pypi:`pytest-xprocess` - *last release*: Jul 28, 2021, + *last release*: Mar 31, 2024, *status*: 4 - Beta, - *requires*: pytest (>=2.8) + *requires*: pytest>=2.8 A pytest plugin for managing processes across test runs. @@ -7637,18 +11463,32 @@ This list contains 963 plugins. :pypi:`pytest-xray-server` - *last release*: Oct 27, 2021, + *last release*: May 03, 2022, *status*: 3 - Alpha, *requires*: pytest (>=5.3.1) + :pypi:`pytest-xskynet` + *last release*: Feb 20, 2024, + *status*: N/A, + *requires*: N/A + + A package to prevent Dependency Confusion attacks against Yandex. + :pypi:`pytest-xvfb` - *last release*: Jun 09, 2020, + *last release*: May 29, 2023, *status*: 4 - Beta, *requires*: pytest (>=2.8.1) - A pytest plugin to run Xvfb for tests. + A pytest plugin to run Xvfb (or Xephyr/Xvnc) for tests. + + :pypi:`pytest-xvirt` + *last release*: Oct 01, 2023, + *status*: 4 - Beta, + *requires*: pytest >=7.1.0 + + A pytest plugin to virtualize test. For example to transparently running them on a remote box. :pypi:`pytest-yaml` *last release*: Oct 05, 2018, @@ -7657,6 +11497,13 @@ This list contains 963 plugins. This plugin is used to load yaml output to your test using pytest framework. + :pypi:`pytest-yaml-sanmu` + *last release*: Apr 19, 2024, + *status*: N/A, + *requires*: pytest>=7.4.0 + + pytest plugin for generating test cases by yaml + :pypi:`pytest-yamltree` *last release*: Mar 02, 2020, *status*: 4 - Beta, @@ -7671,6 +11518,13 @@ This list contains 963 plugins. Run tests against wsgi apps defined in yaml + :pypi:`pytest-yaml-yoyo` + *last release*: Jun 19, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.0) + + http/https API run by yaml + :pypi:`pytest-yapf` *last release*: Jul 06, 2017, *status*: 4 - Beta, @@ -7679,9 +11533,9 @@ This list contains 963 plugins. Run yapf :pypi:`pytest-yapf3` - *last release*: Aug 03, 2020, + *last release*: Mar 29, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=5.4) + *requires*: pytest (>=7) Validate your Python file format with yapf @@ -7692,10 +11546,17 @@ This list contains 963 plugins. PyTest plugin to run tests concurrently, each \`yield\` switch context to other one + :pypi:`pytest-yls` + *last release*: Mar 30, 2024, + *status*: N/A, + *requires*: pytest<8.0.0,>=7.2.2 + + Pytest plugin to test the YLS as a whole. + :pypi:`pytest-yuk` *last release*: Mar 26, 2021, *status*: N/A, - *requires*: N/A + *requires*: pytest>=5.0.0 Display tests you are uneasy with, using 🤢/🤮 for pass/fail of tests marked with yuk. @@ -7714,15 +11575,50 @@ This list contains 963 plugins. OWASP ZAP plugin for py.test. :pypi:`pytest-zebrunner` - *last release*: Dec 02, 2021, + *last release*: Jan 08, 2024, *status*: 5 - Production/Stable, *requires*: pytest (>=4.5.0) Pytest connector for Zebrunner reporting + :pypi:`pytest-zeebe` + *last release*: Feb 01, 2024, + *status*: N/A, + *requires*: pytest (>=7.4.2,<8.0.0) + + Pytest fixtures for testing Camunda 8 processes using a Zeebe test engine. + + :pypi:`pytest-zest` + *last release*: Nov 17, 2022, + *status*: N/A, + *requires*: N/A + + Zesty additions to pytest. + + :pypi:`pytest-zhongwen-wendang` + *last release*: Mar 04, 2024, + *status*: 4 - Beta, + *requires*: N/A + + PyTest 中文文档 + :pypi:`pytest-zigzag` *last release*: Feb 27, 2019, *status*: 4 - Beta, *requires*: pytest (~=3.6) Extend py.test for RPC OpenStack testing. + + :pypi:`pytest-zulip` + *last release*: May 07, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Pytest report plugin for Zulip + + :pypi:`pytest-zy` + *last release*: Mar 24, 2024, + *status*: N/A, + *requires*: pytest~=7.2.0 + + 接口自动化测试框架 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/reference.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/reference.rst index 0d80c8068071d..4036b7d9912dc 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/reference.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/reference.rst @@ -1,3 +1,5 @@ +:tocdepth: 3 + .. _`api-reference`: API Reference @@ -57,11 +59,19 @@ pytest.fail .. autofunction:: pytest.fail(reason, [pytrace=True, msg=None]) +.. class:: pytest.fail.Exception + + The exception raised by :func:`pytest.fail`. + pytest.skip ~~~~~~~~~~~ .. autofunction:: pytest.skip(reason, [allow_module_level=False, msg=None]) +.. class:: pytest.skip.Exception + + The exception raised by :func:`pytest.skip`. + .. _`pytest.importorskip ref`: pytest.importorskip @@ -74,14 +84,24 @@ pytest.xfail .. autofunction:: pytest.xfail +.. class:: pytest.xfail.Exception + + The exception raised by :func:`pytest.xfail`. + pytest.exit ~~~~~~~~~~~ -.. autofunction:: pytest.exit(reason, [returncode=False, msg=None]) +.. autofunction:: pytest.exit(reason, [returncode=None, msg=None]) + +.. class:: pytest.exit.Exception + + The exception raised by :func:`pytest.exit`. pytest.main ~~~~~~~~~~~ +**Tutorial**: :ref:`pytest.main-usage` + .. autofunction:: pytest.main pytest.param @@ -92,7 +112,7 @@ pytest.param pytest.raises ~~~~~~~~~~~~~ -**Tutorial**: :ref:`assertraises`. +**Tutorial**: :ref:`assertraises` .. autofunction:: pytest.raises(expected_exception: Exception [, *, match]) :with: excinfo @@ -100,15 +120,15 @@ pytest.raises pytest.deprecated_call ~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`ensuring_function_triggers`. +**Tutorial**: :ref:`ensuring_function_triggers` -.. autofunction:: pytest.deprecated_call() +.. autofunction:: pytest.deprecated_call([match]) :with: pytest.register_assert_rewrite ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`assertion-rewriting`. +**Tutorial**: :ref:`assertion-rewriting` .. autofunction:: pytest.register_assert_rewrite @@ -123,7 +143,7 @@ pytest.warns pytest.freeze_includes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`freezing-pytest`. +**Tutorial**: :ref:`freezing-pytest` .. autofunction:: pytest.freeze_includes @@ -132,7 +152,7 @@ pytest.freeze_includes Marks ----- -Marks can be used apply meta data to *test functions* (but not fixtures), which can then be accessed by +Marks can be used to apply metadata to *test functions* (but not fixtures), which can then be accessed by fixtures or plugins. @@ -143,7 +163,7 @@ fixtures or plugins. pytest.mark.filterwarnings ~~~~~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`filterwarnings`. +**Tutorial**: :ref:`filterwarnings` Add warning filters to marked test items. @@ -160,8 +180,7 @@ Add warning filters to marked test items. .. code-block:: python @pytest.mark.filterwarnings("ignore:.*usage will be deprecated.*:DeprecationWarning") - def test_foo(): - ... + def test_foo(): ... .. _`pytest.mark.parametrize ref`: @@ -169,7 +188,7 @@ Add warning filters to marked test items. pytest.mark.parametrize ~~~~~~~~~~~~~~~~~~~~~~~ -:ref:`parametrize`. +**Tutorial**: :ref:`parametrize` This mark has the same signature as :py:meth:`pytest.Metafunc.parametrize`; see there. @@ -179,7 +198,7 @@ This mark has the same signature as :py:meth:`pytest.Metafunc.parametrize`; see pytest.mark.skip ~~~~~~~~~~~~~~~~ -:ref:`skip`. +**Tutorial**: :ref:`skip` Unconditionally skip a test function. @@ -193,7 +212,7 @@ Unconditionally skip a test function. pytest.mark.skipif ~~~~~~~~~~~~~~~~~~ -:ref:`skipif`. +**Tutorial**: :ref:`skipif` Skip a test function if a condition is ``True``. @@ -209,7 +228,7 @@ Skip a test function if a condition is ``True``. pytest.mark.usefixtures ~~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`usefixtures`. +**Tutorial**: :ref:`usefixtures` Mark a test function as using the given fixture names. @@ -231,26 +250,28 @@ Mark a test function as using the given fixture names. pytest.mark.xfail ~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`xfail`. +**Tutorial**: :ref:`xfail` Marks a test function as *expected to fail*. -.. py:function:: pytest.mark.xfail(condition=None, *, reason=None, raises=None, run=True, strict=False) +.. py:function:: pytest.mark.xfail(condition=False, *, reason=None, raises=None, run=True, strict=xfail_strict) - :type condition: bool or str - :param condition: + :keyword Union[bool, str] condition: Condition for marking the test function as xfail (``True/False`` or a - :ref:`condition string `). If a bool, you also have + :ref:`condition string `). If a ``bool``, you also have to specify ``reason`` (see :ref:`condition string `). :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 raises: + Exception class (or tuple of classes) expected to be raised by the test function; other exceptions will fail the test. + Note that subclasses of the classes passed will also result in a match (similar to how the ``except`` statement works). + :type raises: Type[:py:exc:`Exception`] + :keyword bool run: - If the test function should actually be executed. If ``False``, the function will always xfail and will + Whether 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). :keyword bool strict: - * If ``False`` (the default) the function will be shown in the terminal output as ``xfailed`` if it fails + * If ``False`` the function will be shown in the terminal output as ``xfailed`` if it fails and as ``xpass`` if it passes. In both cases this will not cause the test suite to fail as a whole. This is particularly useful to mark *flaky* tests (tests that fail at random) to be tackled later. * If ``True``, the function will be shown in the terminal output as ``xfailed`` if it fails, but if it @@ -258,6 +279,8 @@ Marks a test function as *expected to fail*. that are always failing and there should be a clear indication if they unexpectedly start to pass (for example a new release of a library fixes a known bug). + Defaults to :confval:`xfail_strict`, which is ``False`` by default. + Custom marks ~~~~~~~~~~~~ @@ -269,8 +292,7 @@ For example: .. code-block:: python @pytest.mark.timeout(10, "slow", method="thread") - def test_function(): - ... + def test_function(): ... Will create and attach a :class:`Mark ` object to the collected :class:`Item `, which can then be accessed by fixtures or hooks with @@ -287,17 +309,16 @@ Example for using multiple custom markers: @pytest.mark.timeout(10, "slow", method="thread") @pytest.mark.slow - def test_function(): - ... + def test_function(): ... -When :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` or :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers_with_node>` is used with multiple markers, the marker closest to the function will be iterated over first. The above example will result in ``@pytest.mark.slow`` followed by ``@pytest.mark.timeout(...)``. +When :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` or :meth:`Node.iter_markers_with_node <_pytest.nodes.Node.iter_markers_with_node>` is used with multiple markers, the marker closest to the function will be iterated over first. The above example will result in ``@pytest.mark.slow`` followed by ``@pytest.mark.timeout(...)``. .. _`fixtures-api`: Fixtures -------- -**Tutorial**: :ref:`fixture`. +**Tutorial**: :ref:`fixture` Fixtures are requested by test functions or other fixtures by declaring them as argument names. @@ -333,192 +354,96 @@ For more details, consult the full :ref:`fixtures docs `. :decorator: -.. fixture:: cache - -config.cache -~~~~~~~~~~~~ - -**Tutorial**: :ref:`cache`. - -The ``config.cache`` object allows other plugins and fixtures -to store and retrieve values across test runs. To access it from fixtures -request ``pytestconfig`` into your fixture and get it with ``pytestconfig.cache``. - -Under the hood, the cache plugin uses the simple -``dumps``/``loads`` API of the :py:mod:`json` stdlib module. - -``config.cache`` is an instance of :class:`pytest.Cache`: - -.. autoclass:: pytest.Cache() - :members: - - -.. fixture:: capsys - -capsys -~~~~~~ - -:ref:`captures`. - -.. autofunction:: _pytest.capture.capsys() - :no-auto-options: - - Returns an instance of :class:`CaptureFixture[str] `. - - Example: - - .. code-block:: python - - def test_output(capsys): - print("hello") - captured = capsys.readouterr() - assert captured.out == "hello\n" - -.. autoclass:: pytest.CaptureFixture() - :members: - - -.. fixture:: capsysbinary - -capsysbinary -~~~~~~~~~~~~ - -:ref:`captures`. - -.. autofunction:: _pytest.capture.capsysbinary() - :no-auto-options: - - Returns an instance of :class:`CaptureFixture[bytes] `. - - Example: - - .. code-block:: python - - def test_output(capsysbinary): - print("hello") - captured = capsysbinary.readouterr() - assert captured.out == b"hello\n" - - .. fixture:: capfd capfd ~~~~~~ -:ref:`captures`. +**Tutorial**: :ref:`captures` .. autofunction:: _pytest.capture.capfd() :no-auto-options: - Returns an instance of :class:`CaptureFixture[str] `. - - Example: - - .. code-block:: python - - def test_system_echo(capfd): - os.system('echo "hello"') - captured = capfd.readouterr() - assert captured.out == "hello\n" - .. fixture:: capfdbinary capfdbinary ~~~~~~~~~~~~ -:ref:`captures`. +**Tutorial**: :ref:`captures` .. autofunction:: _pytest.capture.capfdbinary() :no-auto-options: - Returns an instance of :class:`CaptureFixture[bytes] `. - - Example: - - .. code-block:: python - - def test_system_echo(capfdbinary): - os.system('echo "hello"') - captured = capfdbinary.readouterr() - assert captured.out == b"hello\n" - -.. fixture:: doctest_namespace - -doctest_namespace -~~~~~~~~~~~~~~~~~ - -:ref:`doctest`. +.. fixture:: caplog -.. autofunction:: _pytest.doctest.doctest_namespace() +caplog +~~~~~~ - Usually this fixture is used in conjunction with another ``autouse`` fixture: +**Tutorial**: :ref:`logging` - .. code-block:: python +.. autofunction:: _pytest.logging.caplog() + :no-auto-options: - @pytest.fixture(autouse=True) - def add_np(doctest_namespace): - doctest_namespace["np"] = numpy + Returns a :class:`pytest.LogCaptureFixture` instance. - For more details: :ref:`doctest_namespace`. +.. autoclass:: pytest.LogCaptureFixture() + :members: -.. fixture:: request +.. fixture:: capsys -request -~~~~~~~ +capsys +~~~~~~ -:ref:`request example`. +**Tutorial**: :ref:`captures` -The ``request`` fixture is a special fixture providing information of the requesting test function. +.. autofunction:: _pytest.capture.capsys() + :no-auto-options: -.. autoclass:: pytest.FixtureRequest() +.. autoclass:: pytest.CaptureFixture() :members: +.. fixture:: capsysbinary -.. fixture:: pytestconfig - -pytestconfig +capsysbinary ~~~~~~~~~~~~ -.. autofunction:: _pytest.fixtures.pytestconfig() - - -.. fixture:: record_property +**Tutorial**: :ref:`captures` -record_property -~~~~~~~~~~~~~~~~~~~ +.. autofunction:: _pytest.capture.capsysbinary() + :no-auto-options: -**Tutorial**: :ref:`record_property example`. -.. autofunction:: _pytest.junitxml.record_property() +.. fixture:: cache +config.cache +~~~~~~~~~~~~ -.. fixture:: record_testsuite_property +**Tutorial**: :ref:`cache` -record_testsuite_property -~~~~~~~~~~~~~~~~~~~~~~~~~ +The ``config.cache`` object allows other plugins and fixtures +to store and retrieve values across test runs. To access it from fixtures +request ``pytestconfig`` into your fixture and get it with ``pytestconfig.cache``. -**Tutorial**: :ref:`record_testsuite_property example`. +Under the hood, the cache plugin uses the simple +``dumps``/``loads`` API of the :py:mod:`json` stdlib module. -.. autofunction:: _pytest.junitxml.record_testsuite_property() +``config.cache`` is an instance of :class:`pytest.Cache`: +.. autoclass:: pytest.Cache() + :members: -.. fixture:: caplog -caplog -~~~~~~ - -:ref:`logging`. +.. fixture:: doctest_namespace -.. autofunction:: _pytest.logging.caplog() - :no-auto-options: +doctest_namespace +~~~~~~~~~~~~~~~~~ - Returns a :class:`pytest.LogCaptureFixture` instance. +**Tutorial**: :ref:`doctest` -.. autoclass:: pytest.LogCaptureFixture() - :members: +.. autofunction:: _pytest.doctest.doctest_namespace() .. fixture:: monkeypatch @@ -526,7 +451,7 @@ caplog monkeypatch ~~~~~~~~~~~ -:ref:`monkeypatching`. +**Tutorial**: :ref:`monkeypatching` .. autofunction:: _pytest.monkeypatch.monkeypatch() :no-auto-options: @@ -537,6 +462,14 @@ monkeypatch :members: +.. fixture:: pytestconfig + +pytestconfig +~~~~~~~~~~~~ + +.. autofunction:: _pytest.fixtures.pytestconfig() + + .. fixture:: pytester pytester @@ -573,18 +506,25 @@ To use it, include in your topmost ``conftest.py`` file: .. autoclass:: pytest.RecordedHookCall() :members: -.. fixture:: testdir -testdir -~~~~~~~ +.. fixture:: record_property -Identical to :fixture:`pytester`, but provides an instance whose methods return -legacy ``py.path.local`` objects instead when applicable. +record_property +~~~~~~~~~~~~~~~~~~~ -New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`. +**Tutorial**: :ref:`record_property example` -.. autoclass:: pytest.Testdir() - :members: +.. autofunction:: _pytest.junitxml.record_property() + + +.. fixture:: record_testsuite_property + +record_testsuite_property +~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Tutorial**: :ref:`record_testsuite_property example` + +.. autofunction:: _pytest.junitxml.record_testsuite_property() .. fixture:: recwarn @@ -600,11 +540,33 @@ recwarn .. autoclass:: pytest.WarningsRecorder() :members: -Each recorded warning is an instance of :class:`warnings.WarningMessage`. -.. note:: - ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated - differently; see :ref:`ensuring_function_triggers`. +.. fixture:: request + +request +~~~~~~~ + +**Example**: :ref:`request example` + +The ``request`` fixture is a special fixture providing information of the requesting test function. + +.. autoclass:: pytest.FixtureRequest() + :members: + + +.. fixture:: testdir + +testdir +~~~~~~~ + +Identical to :fixture:`pytester`, but provides an instance whose methods return +legacy ``py.path.local`` objects instead when applicable. + +New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`. + +.. autoclass:: pytest.Testdir() + :members: + :noindex: TimeoutExpired .. fixture:: tmp_path @@ -612,7 +574,7 @@ Each recorded warning is an instance of :class:`warnings.WarningMessage`. tmp_path ~~~~~~~~ -:ref:`tmp_path` +**Tutorial**: :ref:`tmp_path` .. autofunction:: _pytest.tmpdir.tmp_path() :no-auto-options: @@ -623,7 +585,7 @@ tmp_path tmp_path_factory ~~~~~~~~~~~~~~~~ -:ref:`tmp_path_factory example` +**Tutorial**: :ref:`tmp_path_factory example` .. _`tmp_path_factory factory api`: @@ -638,7 +600,7 @@ tmp_path_factory tmpdir ~~~~~~ -:ref:`tmpdir and tmpdir_factory` +**Tutorial**: :ref:`tmpdir and tmpdir_factory` .. autofunction:: _pytest.legacypath.LegacyTmpdirPlugin.tmpdir() :no-auto-options: @@ -649,7 +611,7 @@ tmpdir tmpdir_factory ~~~~~~~~~~~~~~ -:ref:`tmpdir and tmpdir_factory` +**Tutorial**: :ref:`tmpdir and tmpdir_factory` ``tmpdir_factory`` is an instance of :class:`~pytest.TempdirFactory`: @@ -662,12 +624,32 @@ tmpdir_factory Hooks ----- -:ref:`writing-plugins`. - -.. currentmodule:: _pytest.hookspec +**Tutorial**: :ref:`writing-plugins` Reference to all hooks which can be implemented by :ref:`conftest.py files ` and :ref:`plugins `. +@pytest.hookimpl +~~~~~~~~~~~~~~~~ + +.. function:: pytest.hookimpl + :decorator: + + pytest's decorator for marking functions as hook implementations. + + See :ref:`writinghooks` and :func:`pluggy.HookimplMarker`. + +@pytest.hookspec +~~~~~~~~~~~~~~~~ + +.. function:: pytest.hookspec + :decorator: + + pytest's decorator for marking functions as hook specifications. + + See :ref:`declaringhooks` and :func:`pluggy.HookspecMarker`. + +.. currentmodule:: _pytest.hookspec + Bootstrapping hooks ~~~~~~~~~~~~~~~~~~~ @@ -675,8 +657,6 @@ Bootstrapping hooks called for plugins registered early enough (internal and set .. hook:: pytest_load_initial_conftests .. autofunction:: pytest_load_initial_conftests -.. hook:: pytest_cmdline_preparse -.. autofunction:: pytest_cmdline_preparse .. hook:: pytest_cmdline_parse .. autofunction:: pytest_cmdline_parse .. hook:: pytest_cmdline_main @@ -714,6 +694,8 @@ Collection hooks .. autofunction:: pytest_collection .. hook:: pytest_ignore_collect .. autofunction:: pytest_ignore_collect +.. hook:: pytest_collect_directory +.. autofunction:: pytest_collect_directory .. hook:: pytest_collect_file .. autofunction:: pytest_collect_file .. hook:: pytest_pycollect_makemodule @@ -808,8 +790,6 @@ Session related reporting hooks: .. autofunction:: pytest_fixture_setup .. hook:: pytest_fixture_post_finalizer .. autofunction:: pytest_fixture_post_finalizer -.. hook:: pytest_warning_captured -.. autofunction:: pytest_warning_captured .. hook:: pytest_warning_recorded .. autofunction:: pytest_warning_recorded @@ -844,23 +824,16 @@ reporting or interaction with exceptions: .. autofunction:: pytest_leave_pdb -Objects -------- - -Full reference to objects accessible from :ref:`fixtures ` or :ref:`hooks `. - - -CallInfo -~~~~~~~~ - -.. autoclass:: pytest.CallInfo() - :members: +Collection tree objects +----------------------- +These are the collector and item classes (collectively called "nodes") which +make up the collection tree. -Class -~~~~~ +Node +~~~~ -.. autoclass:: pytest.Class() +.. autoclass:: _pytest.nodes.Node() :members: :show-inheritance: @@ -871,52 +844,52 @@ Collector :members: :show-inheritance: -CollectReport -~~~~~~~~~~~~~ +Item +~~~~ -.. autoclass:: pytest.CollectReport() +.. autoclass:: pytest.Item() :members: :show-inheritance: - :inherited-members: -Config -~~~~~~ +File +~~~~ -.. autoclass:: pytest.Config() +.. autoclass:: pytest.File() :members: + :show-inheritance: -ExceptionInfo -~~~~~~~~~~~~~ +FSCollector +~~~~~~~~~~~ -.. autoclass:: pytest.ExceptionInfo() +.. autoclass:: _pytest.nodes.FSCollector() :members: + :show-inheritance: +Session +~~~~~~~ -ExitCode -~~~~~~~~ - -.. autoclass:: pytest.ExitCode +.. autoclass:: pytest.Session() :members: + :show-inheritance: -File -~~~~ +Package +~~~~~~~ -.. autoclass:: pytest.File() +.. autoclass:: pytest.Package() :members: :show-inheritance: +Module +~~~~~~ -FixtureDef -~~~~~~~~~~ - -.. autoclass:: _pytest.fixtures.FixtureDef() +.. autoclass:: pytest.Module() :members: :show-inheritance: -FSCollector -~~~~~~~~~~~ +Class +~~~~~ -.. autoclass:: _pytest.nodes.FSCollector() +.. autoclass:: pytest.Class() :members: :show-inheritance: @@ -934,10 +907,64 @@ FunctionDefinition :members: :show-inheritance: -Item -~~~~ -.. autoclass:: pytest.Item() +Objects +------- + +Objects accessible from :ref:`fixtures ` or :ref:`hooks ` +or importable from ``pytest``. + + +CallInfo +~~~~~~~~ + +.. autoclass:: pytest.CallInfo() + :members: + +CollectReport +~~~~~~~~~~~~~ + +.. autoclass:: pytest.CollectReport() + :members: + :show-inheritance: + :inherited-members: + +Config +~~~~~~ + +.. autoclass:: pytest.Config() + :members: + +Dir +~~~ + +.. autoclass:: pytest.Dir() + :members: + +Directory +~~~~~~~~~ + +.. autoclass:: pytest.Directory() + :members: + +ExceptionInfo +~~~~~~~~~~~~~ + +.. autoclass:: pytest.ExceptionInfo() + :members: + + +ExitCode +~~~~~~~~ + +.. autoclass:: pytest.ExitCode + :members: + + +FixtureDef +~~~~~~~~~~ + +.. autoclass:: pytest.FixtureDef() :members: :show-inheritance: @@ -968,19 +995,6 @@ Metafunc .. autoclass:: pytest.Metafunc() :members: -Module -~~~~~~ - -.. autoclass:: pytest.Module() - :members: - :show-inheritance: - -Node -~~~~ - -.. autoclass:: _pytest.nodes.Node() - :members: - Parser ~~~~~~ @@ -1002,13 +1016,6 @@ PytestPluginManager :inherited-members: :show-inheritance: -Session -~~~~~~~ - -.. autoclass:: pytest.Session() - :members: - :show-inheritance: - TestReport ~~~~~~~~~~ @@ -1017,10 +1024,16 @@ TestReport :show-inheritance: :inherited-members: -_Result +TestShortLogReport +~~~~~~~~~~~~~~~~~~ + +.. autoclass:: pytest.TestShortLogReport() + :members: + +Result ~~~~~~~ -Result object used within :ref:`hook wrappers `, see :py:class:`_Result in the pluggy documentation ` for more information. +Result object used within :ref:`hook wrappers `, see :py:class:`Result in the pluggy documentation ` for more information. Stash ~~~~~ @@ -1108,11 +1121,24 @@ Environment Variables Environment variables that can be used to change pytest's behavior. +.. envvar:: CI + +When set (regardless of value), pytest acknowledges that is running in a CI process. Alternative to ``BUILD_NUMBER`` variable. + +.. envvar:: BUILD_NUMBER + +When set (regardless of value), pytest acknowledges that is running in a CI process. Alternative to CI variable. + .. 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. +.. envvar:: PYTEST_VERSION + +This environment variable is defined at the start of the pytest session and is undefined afterwards. +It contains the value of ``pytest.__version__``, and among other things can be used to easily check if a code is running from within a pytest run. + .. 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 @@ -1151,19 +1177,22 @@ When set to ``0``, pytest will not use color. .. envvar:: NO_COLOR -When set (regardless of value), pytest will not use color in terminal output. +When set to a non-empty string (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 `__ for other libraries supporting this community standard. .. envvar:: FORCE_COLOR -When set (regardless of value), pytest will use color in terminal output. +When set to a non-empty string (regardless of value), pytest will use color in terminal output. ``PY_COLORS`` and ``NO_COLOR`` take precedence over ``FORCE_COLOR``. Exceptions ---------- -.. autoclass:: pytest.UsageError() +.. autoexception:: pytest.UsageError() + :show-inheritance: + +.. autoexception:: pytest.FixtureLookupError() :show-inheritance: .. _`warnings ref`: @@ -1194,6 +1223,12 @@ Custom warnings generated in some situations such as improper usage or deprecate .. autoclass:: pytest.PytestExperimentalApiWarning :show-inheritance: +.. autoclass:: pytest.PytestReturnNotNoneWarning + :show-inheritance: + +.. autoclass:: pytest.PytestRemovedIn9Warning + :show-inheritance: + .. autoclass:: pytest.PytestUnhandledCoroutineWarning :show-inheritance: @@ -1215,9 +1250,10 @@ Consult the :ref:`internal-warnings` section in the documentation for more infor Configuration Options --------------------- -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`. +Here is a list of builtin configuration options that may be written in a ``pytest.ini`` (or ``.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 except for very simple use cases. ``.cfg`` @@ -1260,12 +1296,25 @@ passed multiple times. The expected format is ``name=value``. For example:: variables, that will be expanded. For more information about cache plugin please refer to :ref:`cache_provider`. +.. confval:: consider_namespace_packages + + Controls if pytest should attempt to identify `namespace packages `__ + when collecting Python modules. Default is ``False``. + + Set to ``True`` if the package you are testing is part of a namespace package. + + Only `native namespace packages `__ + are supported, with no plans to support `legacy namespace packages `__. + + .. versionadded:: 8.1 + .. confval:: console_output_style Sets the console output style while running tests: * ``classic``: classic pytest output. * ``progress``: like classic pytest output, but with a progress indicator. + * ``progress-even-when-capture-no``: allows the use of the progress indicator even when ``capture=no``. * ``count``: like progress, but shows progress as the number of tests completed instead of a percent. The default is ``progress``, but you can fallback to ``classic`` if you prefer or @@ -1516,7 +1565,7 @@ passed multiple times. The expected format is ``name=value``. For example:: - Sets a file name relative to the ``pytest.ini`` file where log messages should be written to, in addition + Sets a file name relative to the current working directory where log messages should be written to, in addition to the other logging facilities that are active. .. code-block:: ini @@ -1658,11 +1707,11 @@ passed multiple times. The expected format is ``name=value``. For example:: Additionally, ``pytest`` will attempt to intelligently identify and ignore a virtualenv by the presence of an activation script. Any directory deemed to be the root of a virtual environment will not be considered during test - collection unless ``‑‑collect‑in‑virtualenv`` is given. Note also that - ``norecursedirs`` takes precedence over ``‑‑collect‑in‑virtualenv``; e.g. if + collection unless ``--collect-in-virtualenv`` is given. Note also that + ``norecursedirs`` takes precedence over ``--collect-in-virtualenv``; e.g. if you intend to run tests in a virtualenv with a base directory that matches ``'.*'`` you *must* override ``norecursedirs`` in addition to using the - ``‑‑collect‑in‑virtualenv`` flag. + ``--collect-in-virtualenv`` flag. .. confval:: python_classes @@ -1742,6 +1791,11 @@ passed multiple times. The expected format is ``name=value``. For example:: [pytest] pythonpath = src1 src2 + .. note:: + + ``pythonpath`` does not affect some imports that happen very early, + most notably plugins loaded using the ``-p`` command line option. + .. confval:: required_plugins @@ -1758,11 +1812,12 @@ passed multiple times. The expected format is ``name=value``. For example:: .. confval:: testpaths - - Sets list of directories that should be searched for tests when no specific directories, files or test ids are given in the command line when executing pytest from the :ref:`rootdir ` directory. + File system paths may use shell-style wildcards, including the recursive + ``**`` pattern. + Useful when all project tests are in a known location to speed up test collection and to avoid picking up undesired tests by accident. @@ -1771,8 +1826,51 @@ passed multiple times. The expected format is ``name=value``. For example:: [pytest] testpaths = testing doc - This tells pytest to only look for tests in ``testing`` and ``doc`` - directories when executing from the root directory. + This configuration means that executing: + + .. code-block:: console + + pytest + + has the same practical effects as executing: + + .. code-block:: console + + pytest testing doc + + +.. confval:: tmp_path_retention_count + + + + How many sessions should we keep the `tmp_path` directories, + according to `tmp_path_retention_policy`. + + .. code-block:: ini + + [pytest] + tmp_path_retention_count = 3 + + Default: ``3`` + + +.. confval:: tmp_path_retention_policy + + + + Controls which directories created by the `tmp_path` fixture are kept around, + based on test outcome. + + * `all`: retains directories for all tests, regardless of the outcome. + * `failed`: retains directories only for tests with outcome `error` or `failed`. + * `none`: directories are always removed after each test ends, regardless of the outcome. + + .. code-block:: ini + + [pytest] + tmp_path_retention_policy = "all" + + Default: ``all`` .. confval:: usefixtures @@ -1788,6 +1886,32 @@ passed multiple times. The expected format is ``name=value``. For example:: clean_db +.. confval:: verbosity_assertions + + Set a verbosity level specifically for assertion related output, overriding the application wide level. + + .. code-block:: ini + + [pytest] + verbosity_assertions = 2 + + Defaults to application wide verbosity level (via the ``-v`` command-line option). A special value of + "auto" can be used to explicitly use the global verbosity level. + + +.. confval:: verbosity_test_cases + + Set a verbosity level specifically for test case execution related output, overriding the application wide level. + + .. code-block:: ini + + [pytest] + verbosity_test_cases = 2 + + Defaults to application wide verbosity level (via the ``-v`` command-line option). A special value of + "auto" can be used to explicitly use the global verbosity level. + + .. confval:: xfail_strict If set to ``True``, tests marked with ``@pytest.mark.xfail`` that actually succeed will by default fail the @@ -1815,8 +1939,8 @@ All the command-line flags can be obtained by running ``pytest --help``:: file_or_dir general: - -k EXPRESSION only run tests which match the given substring - expression. An expression is a python evaluatable + -k EXPRESSION Only run tests which match the given substring + expression. An expression is a Python evaluable expression where all names are substring-matched against test names and their parent classes. Example: -k 'test_method or test_other' matches all @@ -1830,93 +1954,98 @@ All the command-line flags can be obtained by running ``pytest --help``:: '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'. + -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. + -x, --exitfirst Exit instantly on first error or failed test --fixtures, --funcargs - show available fixtures, sorted by plugin appearance + 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. + --fixtures-per-test Show fixtures per test + --pdb Start the interactive Python debugger on errors or + KeyboardInterrupt --pdbcls=modulename:classname - specify a custom interactive Python debugger for use + Specify a custom interactive Python debugger for use with --pdb.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 + --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 + --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 + --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 + Show cache contents, don't perform collection or tests. Optional argument: glob (default: '*'). - --cache-clear remove all cache contents at start of test run. + --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 + With ``--lf``, determines whether to execute tests + when there are no previously (known) failures or + when no cached ``lastfailed`` data was found. + ``all`` (the default) runs the full test suite + again. ``none`` just emits a message about no known + failures and exits successfully. + --sw, --stepwise Exit on test failure and continue from last failing test next time --sw-skip, --stepwise-skip - ignore the first failing test but stop on the next - failing test. - implicitly enables --stepwise. + Ignore the first failing test but stop on the next + failing test. Implicitly enables --stepwise. - reporting: - --durations=N show N slowest setup/test durations (N=0 for all). + 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: + 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: 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). + Disable warnings summary + -l, --showlocals Show locals in tracebacks (disabled by default) + --no-showlocals Hide locals in tracebacks (negate --showlocals + passed through addopts) + --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). + failed tests. Default: 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 + is also enabled). Default: yes. + --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 markers not registered in the `markers` section of - the configuration file raise errors. - --strict (deprecated) alias to --strict-markers. - -c file load configuration from `file` instead of trying to + 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 Markers not registered in the `markers` section of + the configuration file raise errors + --strict (Deprecated) alias to --strict-markers + -c FILE, --config-file=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. + 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: @@ -1924,124 +2053,147 @@ All the command-line flags can be obtained by running ``pytest --help``:: '$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). + --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. + 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 + Prepend/append to sys.path when importing test + modules and conftest files. Default: 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 + Choose another output format for diffs on doctest failure - --doctest-glob=pat doctests file matching pattern, default: test*.txt + --doctest-glob=pat Doctests file matching pattern, default: test*.txt --doctest-ignore-import-errors - ignore doctest ImportErrors + Ignore doctest collection errors --doctest-continue-on-failure - for a given doctest, continue to run after the first + 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 + --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. + -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=[DEBUG_FILE_NAME] - store internal tracing debug information in this log - file. - This file is opened with 'w' and truncated as a - result, care advised. - Defaults to 'pytestdebug.log'. + Store internal tracing debug information in this log + file. This file is opened with 'w' and truncated as + a result, care advised. Default: pytestdebug.log. -o OVERRIDE_INI, --override-ini=OVERRIDE_INI - override ini option with "option=value" style, e.g. + 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. + --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-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 format used by the logging module --log-date-format=LOG_DATE_FORMAT - log date format as used by the logging module. + Log date format used by the logging module --log-cli-level=LOG_CLI_LEVEL - cli logging level. + CLI logging level --log-cli-format=LOG_CLI_FORMAT - log format as used by the logging module. + Log format 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 date format used by the logging module + --log-file=LOG_FILE Path to a file when logging will be written to + --log-file-mode={w,a} + Log file open mode --log-file-level=LOG_FILE_LEVEL - log file logging level. + Log file logging level --log-file-format=LOG_FILE_FORMAT - log format as used by the logging module. + Log format used by the logging module --log-file-date-format=LOG_FILE_DATE_FORMAT - log date format as used by the logging module. + Log date format 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. + --log-disable=LOGGER_DISABLE + Disable a logger by name. Can be passed multiple + times. - [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found: + [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg|pyproject.toml file found: - markers (linelist): markers for test functions + markers (linelist): Register new 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. + 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 on 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 + consider_namespace_packages (bool): + Consider namespace packages when resolving module + names during import + usefixtures (args): List of default fixtures to be used with this project - python_files (args): glob-style file patterns for Python test module + python_files (args): Glob-style file patterns for Python test module discovery python_classes (args): - prefixes or glob names for Python test class + Prefixes or glob names for Python test class discovery python_functions (args): - prefixes or glob names for Python test function and + 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 + 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 + Console output: "classic", or with additional progress information ("progress" (percentage) | - "count"). - xfail_strict (bool): default for the strict parameter of xfail markers + "count" | "progress-even-when-capture-no" (forces + progress even when capture=no) + verbosity_test_cases (string): + Specify a verbosity level for test case execution, + overriding the main level. Higher levels will + provide more detailed information about each test + case executed. + xfail_strict (bool): Default for the strict parameter of xfail markers when not given explicitly (default: False) + tmp_path_retention_count (string): + How many sessions should we keep the `tmp_path` + directories, according to + `tmp_path_retention_policy`. + tmp_path_retention_policy (string): + Controls which directories created by the `tmp_path` + fixture are kept around, based on test outcome. + (all/failed/none) enable_assertion_pass_hook (bool): - Enables the pytest_assertion_pass hook.Make sure to + Enables the pytest_assertion_pass hook. Make sure to delete any previously generated pyc cache files. + verbosity_assertions (string): + Specify a verbosity level for assertions, overriding + the main level. Higher levels will provide more + detailed explanation when an assertion fails. junit_suite_name (string): Test suite name for JUnit report junit_logging (string): @@ -2055,45 +2207,47 @@ All the command-line flags can be obtained by running ``pytest --help``:: junit_family (string): Emit XML for schema: one of legacy|xunit1|xunit2 doctest_optionflags (args): - option flags for doctests + 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 + 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"). + 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 + Default value for --log-cli-level log_cli_format (string): - default value for --log-cli-format + 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 + Default value for --log-cli-date-format + log_file (string): Default value for --log-file + log_file_mode (string): + Default value for --log-file-mode log_file_level (string): - default value for --log-file-level + Default value for --log-file-level log_file_format (string): - default value for --log-file-format + Default value for --log-file-format log_file_date_format (string): - default value for --log-file-date-format + Default value for --log-file-date-format log_auto_indent (string): - default value for --log-auto-indent + Default value for --log-auto-indent pythonpath (paths): Add paths to sys.path 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 + 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 + 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 + 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 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/w3c-import.log new file mode 100644 index 0000000000000..d4cd5eb800f66 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/customize.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/exit-codes.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/fixtures.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/plugin_list.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/reference/reference.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/requirements.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/requirements.txt index 5b49cb7fccc07..974988c8cf4b5 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/requirements.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/requirements.txt @@ -1,7 +1,11 @@ pallets-sphinx-themes -pluggy>=1.0 -pygments-pytest>=2.2.0 +pluggy>=1.5.0 +pygments-pytest>=2.3.0 sphinx-removed-in>=0.2.0 -sphinx>=3.1,<4 +sphinx>=7 sphinxcontrib-trio sphinxcontrib-svg2pdfconverter +# Pin packaging because it no longer handles 'latest' version, which +# is the version that is assigned to the docs. +# See https://github.com/pytest-dev/pytest/pull/10578#issuecomment-1348249045. +packaging <22 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/talks.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/talks.rst index 6843c82bab52a..b9b153a792e4f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/talks.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/talks.rst @@ -11,9 +11,16 @@ Books - `Python Testing with pytest, by Brian Okken (2017) `_. +- `Python Testing with pytest, Second Edition, by Brian Okken (2022) + `_. + Talks and blog postings --------------------------------------------- +- Training: `pytest - simple, rapid and fun testing with Python `_, Florian Bruhin, PyConDE 2022 + +- `pytest: Simple, rapid and fun testing with Python, `_ (@ 4:22:32), Florian Bruhin, WeAreDevelopers World Congress 2021 + - Webinar: `pytest: Test Driven Development für Python (German) `_, Florian Bruhin, via mylearning.ch, 2020 - Webinar: `Simplify Your Tests with Fixtures `_, Oliver Bestwalter, via JetBrains, 2020 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/w3c-import.log new file mode 100644 index 0000000000000..413dd3d9d4b00 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/w3c-import.log @@ -0,0 +1,43 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/Makefile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/adopt.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/backwards-compatibility.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/builtin.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/changelog.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/conf.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/contact.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/contents.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/contributing.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/deprecations.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/development_guide.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/funcarg_compare.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/funcargs.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/getting-started.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/historical-notes.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/history.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/license.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/naming20.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/pytest.ini +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/recwarn.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/requirements.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/sponsor.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/talks.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/tidelift.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/doc/en/yieldfixture.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/extra/get_issues.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/extra/get_issues.py index 4aaa3c3ec31c1..716233ccba178 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/extra/get_issues.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/extra/get_issues.py @@ -3,6 +3,7 @@ import requests + issues_url = "https://api.github.com/repos/pytest-dev/pytest/issues" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/extra/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/extra/w3c-import.log new file mode 100644 index 0000000000000..4e15d3feb1600 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/extra/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/extra/get_issues.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/pyproject.toml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/pyproject.toml index 5d32b755c7445..01acfbf7660a5 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/pyproject.toml +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/pyproject.toml @@ -1,15 +1,292 @@ +[project] +name = "pytest" +description = "pytest: simple powerful testing with Python" +readme = "README.rst" +keywords = [ + "test", + "unittest", +] +license = {text = "MIT"} +authors = [ + {name = "Holger Krekel"}, + {name = "Bruno Oliveira"}, + {name = "Ronny Pfannschmidt"}, + {name = "Floris Bruynooghe"}, + {name = "Brianna Laugher"}, + {name = "Florian Bruhin"}, + {name = "Others (See AUTHORS)"}, +] +requires-python = ">=3.8" +classifiers = [ + "Development Status :: 6 - Mature", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Operating System :: Unix", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Testing", + "Topic :: Utilities", +] +dynamic = [ + "version", +] +dependencies = [ + 'colorama; sys_platform == "win32"', + 'exceptiongroup>=1.0.0rc8; python_version < "3.11"', + "iniconfig", + "packaging", + "pluggy<2.0,>=1.5", + 'tomli>=1; python_version < "3.11"', +] +[project.optional-dependencies] +dev = [ + "argcomplete", + "attrs>=19.2", + "hypothesis>=3.56", + "mock", + "pygments>=2.7.2", + "requests", + "setuptools", + "xmlschema", +] +[project.urls] +Changelog = "https://docs.pytest.org/en/stable/changelog.html" +Homepage = "https://docs.pytest.org/en/latest/" +Source = "https://github.com/pytest-dev/pytest" +Tracker = "https://github.com/pytest-dev/pytest/issues" +Twitter = "https://twitter.com/pytestdotorg" +[project.scripts] +"py.test" = "pytest:console_main" +pytest = "pytest:console_main" + [build-system] +build-backend = "setuptools.build_meta" requires = [ - # sync with setup.py until we discard non-pep-517/518 - "setuptools>=45.0", - "setuptools-scm[toml]>=6.2.3", - "wheel", + "setuptools>=61", + "setuptools-scm[toml]>=6.2.3", ] -build-backend = "setuptools.build_meta" + +[tool.setuptools.package-data] +"_pytest" = ["py.typed"] +"pytest" = ["py.typed"] [tool.setuptools_scm] write_to = "src/_pytest/_version.py" +[tool.black] +target-version = ['py38'] + +[tool.ruff] +src = ["src"] +line-length = 88 + +[tool.ruff.format] +docstring-code-format = true + +[tool.ruff.lint] +select = [ + "B", # bugbear + "D", # pydocstyle + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "PYI", # flake8-pyi + "UP", # pyupgrade + "RUF", # ruff + "W", # pycodestyle + "PIE", # flake8-pie + "PGH004", # pygrep-hooks - Use specific rule codes when using noqa + "PLE", # pylint error + "PLW", # pylint warning + "PLR1714", # Consider merging multiple comparisons +] +ignore = [ + # bugbear ignore + "B004", # Using `hasattr(x, "__call__")` to test if x is callable is unreliable. + "B007", # Loop control variable `i` not used within loop body + "B009", # Do not call `getattr` with a constant attribute value + "B010", # [*] Do not call `setattr` with a constant attribute value. + "B011", # Do not `assert False` (`python -O` removes these calls) + "B028", # No explicit `stacklevel` keyword argument found + # pycodestyle ignore + # pytest can do weird low-level things, and we usually know + # what we're doing when we use type(..) is ... + "E721", # Do not compare types, use `isinstance()` + # pydocstyle ignore + "D100", # Missing docstring in public module + "D101", # Missing docstring in public class + "D102", # Missing docstring in public method + "D103", # Missing docstring in public function + "D104", # Missing docstring in public package + "D105", # Missing docstring in magic method + "D106", # Missing docstring in public nested class + "D107", # Missing docstring in `__init__` + "D209", # [*] Multi-line docstring closing quotes should be on a separate line + "D205", # 1 blank line required between summary line and description + "D400", # First line should end with a period + "D401", # First line of docstring should be in imperative mood + "D402", # First line should not be the function's signature + "D404", # First word of the docstring should not be "This" + "D415", # First line should end with a period, question mark, or exclamation point + # ruff ignore + "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` + # pylint ignore + "PLW0603", # Using the global statement + "PLW0120", # remove the else and dedent its contents + "PLW2901", # for loop variable overwritten by assignment target + "PLR5501", # Use `elif` instead of `else` then `if` +] + +[tool.ruff.lint.pycodestyle] +# In order to be able to format for 88 char in ruff format +max-line-length = 120 + +[tool.ruff.lint.pydocstyle] +convention = "pep257" + +[tool.ruff.lint.isort] +force-single-line = true +combine-as-imports = true +force-sort-within-sections = true +order-by-type = false +known-local-folder = ["pytest", "_pytest"] +lines-after-imports = 2 + +[tool.ruff.lint.per-file-ignores] +"src/_pytest/_py/**/*.py" = ["B", "PYI"] +"src/_pytest/_version.py" = ["I001"] +"testing/python/approx.py" = ["B015"] + +[tool.pylint.main] +# Maximum number of characters on a single line. +max-line-length = 120 +disable= [ + "abstract-method", + "arguments-differ", + "arguments-renamed", + "assigning-non-slot", + "attribute-defined-outside-init", + "bad-classmethod-argument", + "bad-mcs-method-argument", + "broad-exception-caught", + "broad-exception-raised", + "cell-var-from-loop", + "comparison-of-constants", + "comparison-with-callable", + "comparison-with-itself", + "condition-evals-to-constant", + "consider-using-dict-items", + "consider-using-enumerate", + "consider-using-from-import", + "consider-using-f-string", + "consider-using-in", + "consider-using-sys-exit", + "consider-using-ternary", + "consider-using-with", + "cyclic-import", + "disallowed-name", + "duplicate-code", + "eval-used", + "exec-used", + "expression-not-assigned", + "fixme", + "global-statement", + "implicit-str-concat", + "import-error", + "import-outside-toplevel", + "inconsistent-return-statements", + "invalid-bool-returned", + "invalid-name", + "invalid-repr-returned", + "invalid-str-returned", + "keyword-arg-before-vararg", + "line-too-long", + "method-hidden", + "misplaced-bare-raise", + "missing-docstring", + "missing-timeout", + "multiple-statements", + "no-else-break", + "no-else-continue", + "no-else-raise", + "no-else-return", + "no-member", + "no-name-in-module", + "no-self-argument", + "not-an-iterable", + "not-callable", + "pointless-exception-statement", + "pointless-statement", + "pointless-string-statement", + "protected-access", + "raise-missing-from", + "redefined-argument-from-local", + "redefined-builtin", + "redefined-outer-name", + "reimported", + "simplifiable-condition", + "simplifiable-if-expression", + "singleton-comparison", + "superfluous-parens", + "super-init-not-called", + "too-few-public-methods", + "too-many-ancestors", + "too-many-arguments", + "too-many-branches", + "too-many-function-args", + "too-many-instance-attributes", + "too-many-lines", + "too-many-locals", + "too-many-nested-blocks", + "too-many-public-methods", + "too-many-return-statements", + "too-many-statements", + "try-except-raise", + "typevar-name-incorrect-variance", + "unbalanced-tuple-unpacking", + "undefined-loop-variable", + "undefined-variable", + "unexpected-keyword-arg", + "unidiomatic-typecheck", + "unnecessary-comprehension", + "unnecessary-dunder-call", + "unnecessary-lambda", + "unnecessary-lambda-assignment", + "unpacking-non-sequence", + "unspecified-encoding", + "unsubscriptable-object", + "unused-argument", + "unused-import", + "unused-variable", + "used-before-assignment", + "use-dict-literal", + "use-implicit-booleaness-not-comparison", + "use-implicit-booleaness-not-len", + "useless-else-on-loop", + "useless-import-alias", + "useless-return", + "use-maxsplit-arg", + "using-constant-test", + "wrong-import-order", +] + +[tool.check-wheel-contents] +# check-wheel-contents is executed by the build-and-inspect-python-package action. +# W009: Wheel contains multiple toplevel library entries +ignore = "W009" + +[tool.pyproject-fmt] +indent = 4 + [tool.pytest.ini_options] minversion = "2.0" addopts = "-rfEX -p pytester --strict-markers" @@ -18,7 +295,12 @@ 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"] +norecursedirs = [ + "testing/example_scripts", + ".*", + "build", + "dist", +] xfail_strict = true filterwarnings = [ "error", @@ -28,8 +310,6 @@ filterwarnings = [ "default:the imp module is deprecated in favour of importlib:DeprecationWarning:nose.*", # distutils is deprecated in 3.10, scheduled for removal in 3.12 "ignore:The distutils package is deprecated:DeprecationWarning", - # 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 on execnet (pytest-xdist) @@ -40,6 +320,9 @@ filterwarnings = [ # 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 not yet fixed warnings for hook markers + "default:.*not marked using pytest.hook.*", + "ignore:.*not marked using pytest.hook.*::xdist.*", # ignore use of unregistered marks, because we use many to test the implementation "ignore::_pytest.warning_types.PytestUnknownMarkWarning", # https://github.com/benjaminp/six/issues/341 @@ -63,7 +346,6 @@ markers = [ "uses_pexpect", ] - [tool.towncrier] package = "pytest" package_dir = "src" @@ -112,5 +394,18 @@ template = "changelog/_template.rst" name = "Trivial/Internal Changes" showcontent = true -[tool.black] -target-version = ['py36'] +[tool.mypy] +files = ["src", "testing", "scripts"] +mypy_path = ["src"] +check_untyped_defs = true +disallow_any_generics = true +disallow_untyped_defs = true +ignore_missing_imports = 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 +warn_unused_ignores = true diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/generate-gh-release-notes.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/generate-gh-release-notes.py new file mode 100644 index 0000000000000..4222702d5d400 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/generate-gh-release-notes.py @@ -0,0 +1,67 @@ +# mypy: disallow-untyped-defs +""" +Script used to generate a Markdown file containing only the changelog entries of a specific pytest release, which +is then published as a GitHub Release during deploy (see workflows/deploy.yml). + +The script requires ``pandoc`` to be previously installed in the system -- we need to convert from RST (the format of +our CHANGELOG) into Markdown (which is required by GitHub Releases). + +Requires Python3.6+. +""" + +from pathlib import Path +import re +import sys +from typing import Sequence + +import pypandoc + + +def extract_changelog_entries_for(version: str) -> str: + 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+\w*) \(\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) == version: + 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: str) -> str: + result = pypandoc.convert_text( + text, "md", format="rst", extra_args=["--wrap=preserve"] + ) + assert isinstance(result, str), repr(result) + return result + + +def main(argv: Sequence[str]) -> int: + if len(argv) != 3: + print("Usage: generate-gh-release-notes VERSION FILE") + return 2 + + version, filename = argv[1:3] + print(f"Generating GitHub release notes for version {version}") + rst_body = extract_changelog_entries_for(version) + md_body = convert_rst_to_md(rst_body) + Path(filename).write_text(md_body, encoding="UTF-8") + print() + print(f"Done: {filename}") + print() + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/prepare-release-pr.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/prepare-release-pr.py index 7a80de7edaa2c..7dabbd3b328e6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/prepare-release-pr.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/prepare-release-pr.py @@ -1,3 +1,4 @@ +# mypy: disallow-untyped-defs """ This script is part of the pytest release process which is triggered manually in the Actions tab of the repository. @@ -12,9 +13,10 @@ **Token**: currently the token from the GitHub Actions is used, pushed with `pytest bot ` commit author. """ + import argparse -import re from pathlib import Path +import re from subprocess import check_call from subprocess import check_output from subprocess import run @@ -31,10 +33,22 @@ class InvalidFeatureRelease(Exception): SLUG = "pytest-dev/pytest" PR_BODY = """\ -Created automatically from manual trigger. +Created by the [prepare release pr]\ +(https://github.com/pytest-dev/pytest/actions/workflows/prepare-release-pr.yml) workflow. + +Once all builds pass and it has been **approved** by one or more maintainers, start the \ +[deploy](https://github.com/pytest-dev/pytest/actions/workflows/deploy.yml) workflow, using these parameters: + +* `Use workflow from`: `release-{version}`. +* `Release version`: `{version}`. + +Or execute on the command line: + +```console +gh workflow run deploy.yml -r release-{version} -f version={version} +``` -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. +After the workflow has been approved by a core maintainer, the package will be uploaded to PyPI automatically. """ @@ -66,7 +80,7 @@ def prepare_release_pr( ) except InvalidFeatureRelease as e: print(f"{Fore.RED}{e}") - raise SystemExit(1) + raise SystemExit(1) from None print(f"Version: {Fore.CYAN}{version}") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/release.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/release.py index 19fef4284285a..bcbc4262d0851 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/release.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/release.py @@ -1,4 +1,6 @@ +# mypy: disallow-untyped-defs """Invoke development tasks.""" + import argparse import os from pathlib import Path @@ -10,15 +12,15 @@ from colorama import init -def announce(version, template_name, doc_version): +def announce(version: str, template_name: str, doc_version: str) -> None: """Generates a new release announcement entry in the docs.""" # Get our list of authors - stdout = check_output(["git", "describe", "--abbrev=0", "--tags"]) - stdout = stdout.decode("utf-8") + stdout = check_output(["git", "describe", "--abbrev=0", "--tags"], encoding="UTF-8") last_version = stdout.strip() - stdout = check_output(["git", "log", f"{last_version}..HEAD", "--format=%aN"]) - stdout = stdout.decode("utf-8") + stdout = check_output( + ["git", "log", f"{last_version}..HEAD", "--format=%aN"], encoding="UTF-8" + ) contributors = { name @@ -61,7 +63,7 @@ def announce(version, template_name, doc_version): check_call(["git", "add", str(target)]) -def regen(version): +def regen(version: str) -> None: """Call regendoc tool to update examples and pytest output in the docs.""" print(f"{Fore.CYAN}[generate.regen] {Fore.RESET}Updating docs") check_call( @@ -70,7 +72,7 @@ def regen(version): ) -def fix_formatting(): +def fix_formatting() -> None: """Runs pre-commit in all files to ensure they are formatted correctly""" print( f"{Fore.CYAN}[generate.fix linting] {Fore.RESET}Fixing formatting using pre-commit" @@ -78,13 +80,15 @@ def fix_formatting(): call(["pre-commit", "run", "--all-files"]) -def check_links(): +def check_links() -> None: """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, template_name, doc_version, *, skip_check_links): +def pre_release( + version: str, template_name: str, doc_version: str, *, skip_check_links: bool +) -> None: """Generates new docs, release announcements and creates a local tag.""" announce(version, template_name, doc_version) regen(version) @@ -102,12 +106,12 @@ def pre_release(version, template_name, doc_version, *, skip_check_links): print("Please push your branch and open a PR.") -def changelog(version, write_out=False): +def changelog(version: str, write_out: bool = False) -> None: addopts = [] if write_out else ["--draft"] - check_call(["towncrier", "--yes", "--version", version] + addopts) + check_call(["towncrier", "--yes", "--version", version, *addopts]) -def main(): +def main() -> None: init(autoreset=True) parser = argparse.ArgumentParser() parser.add_argument("version", help="Release version") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/towncrier-draft-to-file.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/towncrier-draft-to-file.py index 81507b40b75fd..f771295a01ff1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/towncrier-draft-to-file.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/towncrier-draft-to-file.py @@ -1,13 +1,16 @@ -import sys +# mypy: disallow-untyped-defs from subprocess import call +import sys -def main(): +def main() -> int: """ - 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. + 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: + with open( + "doc/en/_changelog_towncrier_draft.rst", "w", encoding="utf-8" + ) as draft_file: return call(("towncrier", "--draft"), stdout=draft_file) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/update-plugin-list.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/update-plugin-list.py index c034c72420b02..6831fc984dd5a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/update-plugin-list.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/update-plugin-list.py @@ -1,23 +1,49 @@ +# mypy: disallow-untyped-defs import datetime import pathlib import re from textwrap import dedent from textwrap import indent +from typing import Any +from typing import Iterable +from typing import Iterator +from typing import TypedDict import packaging.version -import requests +import platformdirs +from requests_cache import CachedResponse +from requests_cache import CachedSession +from requests_cache import OriginalResponse +from requests_cache import SQLiteCache import tabulate -import wcwidth from tqdm import tqdm +import wcwidth + FILE_HEAD = r""" +.. Note this file is autogenerated by scripts/update-plugin-list.py - usually weekly via github action + .. _plugin-list: -Plugin List -=========== +Pytest Plugin List +================== + +Below is an automated compilation of ``pytest``` plugins available on `PyPI `_. +It includes PyPI projects whose names begin with ``pytest-`` or ``pytest_`` and a handful of manually selected projects. +Packages classified as inactive are excluded. + +For detailed insights into how this list is generated, +please refer to `the update script `_. + +.. warning:: + + Please be aware that this list is not a curated collection of projects + and does not undergo a systematic review process. + It serves purely as an informational resource to aid in the discovery of ``pytest`` plugins. + + Do not presume any endorsement from the ``pytest`` project or its developers, + and always conduct your own quality assessment before incorporating any of these plugins into your own projects. -PyPI projects that match "pytest-\*" are considered plugins and are listed -automatically. Packages classified as inactive are excluded. .. The following conditional uses a different format for this list when creating a PDF, because otherwise the table gets far too wide for the @@ -33,6 +59,12 @@ "Development Status :: 6 - Mature", "Development Status :: 7 - Inactive", ) +ADDITIONAL_PROJECTS = { # set of additional projects to consider as plugins + "logassert", + "logot", + "nuts", + "flask_fixture", +} def escape_rst(text: str) -> str: @@ -48,22 +80,62 @@ def escape_rst(text: str) -> str: return text -def iter_plugins(): - regex = r">([\d\w-]*)" - response = requests.get("https://pypi.org/simple") +def project_response_with_refresh( + session: CachedSession, name: str, last_serial: int +) -> OriginalResponse | CachedResponse: + """Get a http cached pypi project + + force refresh in case of last serial mismatch + """ + response = session.get(f"https://pypi.org/pypi/{name}/json") + if int(response.headers.get("X-PyPI-Last-Serial", -1)) != last_serial: + response = session.get(f"https://pypi.org/pypi/{name}/json", refresh=True) + return response + + +def get_session() -> CachedSession: + """Configures the requests-cache session""" + cache_path = platformdirs.user_cache_path("pytest-plugin-list") + cache_path.mkdir(exist_ok=True, parents=True) + cache_file = cache_path.joinpath("http_cache.sqlite3") + return CachedSession(backend=SQLiteCache(cache_file)) - matches = list( - match - for match in re.finditer(regex, response.text) - if match.groups()[0].startswith("pytest-") + +def pytest_plugin_projects_from_pypi(session: CachedSession) -> dict[str, int]: + response = session.get( + "https://pypi.org/simple", + headers={"Accept": "application/vnd.pypi.simple.v1+json"}, + refresh=True, ) + return { + name: p["_last-serial"] + for p in response.json()["projects"] + if ( + (name := p["name"]).startswith(("pytest-", "pytest_")) + or name in ADDITIONAL_PROJECTS + ) + } + + +class PluginInfo(TypedDict): + """Relevant information about a plugin to generate the summary.""" + + name: str + summary: str + last_release: str + status: str + requires: str + - for match in tqdm(matches, smoothing=0): - name = match.groups()[0] - response = requests.get(f"https://pypi.org/pypi/{name}/json") +def iter_plugins() -> Iterator[PluginInfo]: + session = get_session() + name_2_serial = pytest_plugin_projects_from_pypi(session) + + for name, last_serial in tqdm(name_2_serial.items(), smoothing=0): + response = project_response_with_refresh(session, name, last_serial) if response.status_code == 404: - # Some packages, like pytest-azurepipelines42, are included in https://pypi.org/simple but - # return 404 on the JSON API. Skip. + # Some packages, like pytest-azurepipelines42, are included in https://pypi.org/simple + # but return 404 on the JSON API. Skip. continue response.raise_for_status() info = response.json()["info"] @@ -78,11 +150,23 @@ def iter_plugins(): requires = "N/A" if info["requires_dist"]: for requirement in info["requires_dist"]: - if requirement == "pytest" or "pytest " in requirement: + if re.match(r"pytest(?![-.\w])", requirement): requires = requirement break + + def version_sort_key(version_string: str) -> Any: + """ + Return the sort key for the given version string + returned by the API. + """ + try: + return packaging.version.parse(version_string) + except packaging.version.InvalidVersion: + # Use a hard-coded pre-release version. + return packaging.version.Version("0.0.0alpha") + releases = response.json()["releases"] - for release in sorted(releases, key=packaging.version.parse, reverse=True): + for release in sorted(releases, key=version_sort_key, reverse=True): if releases[release]: release_date = datetime.date.fromisoformat( releases[release][-1]["upload_time_iso_8601"].split("T")[0] @@ -90,24 +174,25 @@ def iter_plugins(): last_release = release_date.strftime("%b %d, %Y") break name = f':pypi:`{info["name"]}`' - summary = escape_rst(info["summary"].replace("\n", "")) + summary = "" + if info["summary"]: + summary = escape_rst(info["summary"].replace("\n", "")) yield { "name": name, "summary": summary.strip(), - "last release": last_release, + "last_release": last_release, "status": status, "requires": requires, } -def plugin_definitions(plugins): +def plugin_definitions(plugins: Iterable[PluginInfo]) -> Iterator[str]: """Return RST for the plugin list that fits better on a vertical page.""" - for plugin in plugins: yield dedent( f""" {plugin['name']} - *last release*: {plugin["last release"]}, + *last release*: {plugin["last_release"]}, *status*: {plugin["status"]}, *requires*: {plugin["requires"]} @@ -116,18 +201,18 @@ def plugin_definitions(plugins): ) -def main(): - plugins = list(iter_plugins()) +def main() -> None: + plugins = [*iter_plugins()] reference_dir = pathlib.Path("doc", "en", "reference") plugin_list = reference_dir / "plugin_list.rst" - with plugin_list.open("w") as f: + with plugin_list.open("w", encoding="UTF-8") as f: f.write(FILE_HEAD) f.write(f"This list contains {len(plugins)} plugins.\n\n") f.write(".. only:: not latex\n\n") - wcwidth # reference library that must exist for tabulate to work + _ = wcwidth # reference library that must exist for tabulate to work plugin_table = tabulate.tabulate(plugins, headers="keys", tablefmt="rst") f.write(indent(plugin_table, " ")) f.write("\n\n") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/w3c-import.log new file mode 100644 index 0000000000000..1b821c72c4101 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/w3c-import.log @@ -0,0 +1,25 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/generate-gh-release-notes.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/prepare-release-pr.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/release.major.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/release.minor.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/release.patch.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/release.pre.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/release.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/towncrier-draft-to-file.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/scripts/update-plugin-list.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/__init__.py index 8a406c5c7512b..b694a5f244a28 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/__init__.py @@ -1,9 +1,10 @@ __all__ = ["__version__", "version_tuple"] try: - from ._version import version as __version__, version_tuple + from ._version import version as __version__ + from ._version import version_tuple except ImportError: # pragma: no cover # broken installation, we don't even try # unknown only works because we do poor mans version compare __version__ = "unknown" - version_tuple = (0, 0, "unknown") # type:ignore[assignment] + version_tuple = (0, 0, "unknown") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_argcomplete.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_argcomplete.py index 41d9d9407c773..c24f925202a17 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_argcomplete.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_argcomplete.py @@ -61,10 +61,11 @@ which should throw a KeyError: 'COMPLINE' (which is properly set by the global argcomplete script). """ + import argparse +from glob import glob import os import sys -from glob import glob from typing import Any from typing import List from typing import Optional @@ -78,15 +79,15 @@ def __init__(self, directories: bool = True) -> None: 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) + if os.sep in prefix[1:]: + prefix_dir = len(os.path.dirname(prefix) + os.sep) else: prefix_dir = 0 completion = [] globbed = [] if "*" not in prefix and "?" not in prefix: # We are on unix, otherwise no bash. - if not prefix or prefix[-1] == os.path.sep: + if not prefix or prefix[-1] == os.sep: globbed.extend(glob(prefix + ".*")) prefix += "*" globbed.extend(glob(prefix)) @@ -108,7 +109,6 @@ def __call__(self, prefix: str, **kwargs: Any) -> List[str]: def try_argcomplete(parser: argparse.ArgumentParser) -> None: argcomplete.autocomplete(parser, always_complete_options=False) - else: def try_argcomplete(parser: argparse.ArgumentParser) -> None: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/__init__.py index 511d0dde661df..b0a418e95555e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/__init__.py @@ -1,4 +1,5 @@ """Python inspection/code generation API.""" + from .code import Code from .code import ExceptionInfo from .code import filter_traceback @@ -9,6 +10,7 @@ from .source import getrawcode from .source import Source + __all__ = [ "Code", "ExceptionInfo", diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/code.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/code.py index 5b758a88480d5..ee6a5597c2ca8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/code.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/code.py @@ -1,13 +1,15 @@ +# mypy: allow-untyped-defs import ast +import dataclasses import inspect -import os -import re -import sys -import traceback from inspect import CO_VARARGS from inspect import CO_VARKEYWORDS from io import StringIO +import os from pathlib import Path +import re +import sys +import traceback from traceback import format_exception_only from types import CodeType from types import FrameType @@ -16,23 +18,24 @@ from typing import Callable from typing import ClassVar from typing import Dict +from typing import Final +from typing import final from typing import Generic from typing import Iterable from typing import List +from typing import Literal from typing import Mapping from typing import Optional from typing import overload from typing import Pattern from typing import Sequence from typing import Set +from typing import SupportsIndex from typing import Tuple from typing import Type -from typing import TYPE_CHECKING from typing import TypeVar from typing import Union -from weakref import ref -import attr import pluggy import _pytest @@ -43,18 +46,16 @@ from _pytest._io import TerminalWriter from _pytest._io.saferepr import safeformat from _pytest._io.saferepr import saferepr -from _pytest.compat import final from _pytest.compat import get_real_func from _pytest.deprecated import check_ispytest from _pytest.pathlib import absolutepath from _pytest.pathlib import bestrelpath -if TYPE_CHECKING: - from typing_extensions import Literal - from typing_extensions import SupportsIndex - from weakref import ReferenceType - _TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup + +_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] class Code: @@ -191,25 +192,25 @@ def getargs(self, var: bool = False): class TracebackEntry: """A single entry in a Traceback.""" - __slots__ = ("_rawentry", "_excinfo", "_repr_style") + __slots__ = ("_rawentry", "_repr_style") def __init__( self, rawentry: TracebackType, - excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None, + repr_style: Optional['Literal["short", "long"]'] = None, ) -> None: - self._rawentry = rawentry - self._excinfo = excinfo - self._repr_style: Optional['Literal["short", "long"]'] = None + self._rawentry: "Final" = rawentry + self._repr_style: "Final" = repr_style + + def with_repr_style( + self, repr_style: Optional['Literal["short", "long"]'] + ) -> "TracebackEntry": + return TracebackEntry(self._rawentry, repr_style) @property def lineno(self) -> int: return self._rawentry.tb_lineno - 1 - def set_repr_style(self, mode: "Literal['short', 'long']") -> None: - assert mode in ("short", "long") - self._repr_style = mode - @property def frame(self) -> Frame: return Frame(self._rawentry.tb_frame) @@ -269,7 +270,7 @@ def getsource( source = property(getsource) - def ishidden(self) -> bool: + def ishidden(self, excinfo: Optional["ExceptionInfo[BaseException]"]) -> bool: """Return True if the current frame has a var __tracebackhide__ resolving to True. @@ -278,9 +279,9 @@ def ishidden(self) -> bool: Mostly for internal use. """ - tbh: Union[ - bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool] - ] = False + tbh: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]] = ( + False + ) 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 @@ -293,7 +294,7 @@ def ishidden(self) -> bool: else: break if tbh and callable(tbh): - return tbh(None if self._excinfo is None else self._excinfo()) + return tbh(excinfo) return tbh def __str__(self) -> str: @@ -326,16 +327,14 @@ class Traceback(List[TracebackEntry]): 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 isinstance(tb, TracebackType): def f(cur: TracebackType) -> Iterable[TracebackEntry]: cur_: Optional[TracebackType] = cur while cur_ is not None: - yield TracebackEntry(cur_, excinfo=excinfo) + yield TracebackEntry(cur_) cur_ = cur_.tb_next super().__init__(f(tb)) @@ -375,16 +374,14 @@ def cut( continue if firstlineno is not None and x.frame.code.firstlineno != firstlineno: continue - return Traceback(x._rawentry, self._excinfo) + return Traceback(x._rawentry) return self @overload - def __getitem__(self, key: "SupportsIndex") -> TracebackEntry: - ... + def __getitem__(self, key: "SupportsIndex") -> TracebackEntry: ... @overload - def __getitem__(self, key: slice) -> "Traceback": - ... + def __getitem__(self, key: slice) -> "Traceback": ... def __getitem__( self, key: Union["SupportsIndex", slice] @@ -395,26 +392,27 @@ def __getitem__( return super().__getitem__(key) def filter( - self, fn: Callable[[TracebackEntry], bool] = lambda x: not x.ishidden() + self, + excinfo_or_fn: Union[ + "ExceptionInfo[BaseException]", + Callable[[TracebackEntry], bool], + ], + /, ) -> "Traceback": - """Return a Traceback instance with certain items removed + """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. + If the filter is an `ExceptionInfo`, removes all the ``TracebackEntry``s + which are hidden (see ishidden() above). - By default this removes all the TracebackEntries which are hidden - (see ishidden() above). + Otherwise, the filter 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. """ - return Traceback(filter(fn, self), self._excinfo) - - 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] + if isinstance(excinfo_or_fn, ExceptionInfo): + fn = lambda x: not x.ishidden(excinfo_or_fn) # noqa: E731 + else: + fn = excinfo_or_fn + return Traceback(filter(fn, self)) def recursionindex(self) -> Optional[int]: """Return the index of the frame/TracebackEntry where recursion originates if @@ -426,15 +424,14 @@ def recursionindex(self) -> Optional[int]: # which generates code objects that have hash/value equality # XXX needs a test key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno - # print "checking for recursion at", key values = cache.setdefault(key, []) + # Since Python 3.13 f_locals is a proxy, freeze it. + loc = dict(entry.frame.f_locals) if values: - f = entry.frame - loc = f.f_locals for otherloc in values: if otherloc == loc: return i - values.append(entry.frame.f_locals) + values.append(loc) return None @@ -442,7 +439,7 @@ def recursionindex(self) -> Optional[int]: @final -@attr.s(repr=False, init=False, auto_attribs=True) +@dataclasses.dataclass class ExceptionInfo(Generic[E]): """Wraps sys.exc_info() objects and offers help for navigating the traceback.""" @@ -466,22 +463,42 @@ def __init__( self._traceback = traceback @classmethod - def from_exc_info( + def from_exception( cls, - exc_info: Tuple[Type[E], E, TracebackType], + # Ignoring error: "Cannot use a covariant type variable as a parameter". + # This is OK to ignore because this class is (conceptually) readonly. + # See https://github.com/python/mypy/issues/7049. + exception: E, # type: ignore[misc] exprinfo: Optional[str] = None, ) -> "ExceptionInfo[E]": - """Return an ExceptionInfo for an existing exc_info tuple. + """Return an ExceptionInfo for an existing exception. - .. warning:: - - Experimental API + The exception must have a non-``None`` ``__traceback__`` attribute, + otherwise this function fails with an assertion error. This means that + the exception must have been raised, or added a traceback with the + :py:meth:`~BaseException.with_traceback()` method. :param exprinfo: A text string helping to determine if we should strip ``AssertionError`` from the output. Defaults to the exception message/``__str__()``. + + .. versionadded:: 7.4 """ + assert exception.__traceback__, ( + "Exceptions passed to ExcInfo.from_exception(...)" + " must have a non-None __traceback__." + ) + exc_info = (type(exception), exception, exception.__traceback__) + return cls.from_exc_info(exc_info, exprinfo) + + @classmethod + def from_exc_info( + cls, + exc_info: Tuple[Type[E], E, TracebackType], + exprinfo: Optional[str] = None, + ) -> "ExceptionInfo[E]": + """Like :func:`from_exception`, but using old-style exc_info tuple.""" _striptext = "" if exprinfo is None and isinstance(exc_info[1], AssertionError): exprinfo = getattr(exc_info[1], "msg", None) @@ -560,7 +577,7 @@ def typename(self) -> str: def traceback(self) -> Traceback: """The traceback.""" if self._traceback is None: - self._traceback = Traceback(self.tb, excinfo=ref(self)) + self._traceback = Traceback(self.tb) return self._traceback @traceback.setter @@ -570,9 +587,7 @@ def traceback(self, value: Traceback) -> None: def __repr__(self) -> str: if self._excinfo is None: return "" - return "<{} {} tblen={}>".format( - self.__class__.__name__, saferepr(self._excinfo[1]), len(self.traceback) - ) + return f"<{self.__class__.__name__} {saferepr(self._excinfo[1])} tblen={len(self.traceback)}>" def exconly(self, tryshort: bool = False) -> str: """Return the exception as a string. @@ -599,18 +614,25 @@ def errisinstance( """ return isinstance(self.value, exc) - def _getreprcrash(self) -> "ReprFileLocation": - exconly = self.exconly(tryshort=True) - entry = self.traceback.getcrashentry() - path, lineno = entry.frame.code.raw.co_filename, entry.lineno - return ReprFileLocation(path, lineno + 1, exconly) + def _getreprcrash(self) -> Optional["ReprFileLocation"]: + # Find last non-hidden traceback entry that led to the exception of the + # traceback, or None if all hidden. + for i in range(-1, -len(self.traceback) - 1, -1): + entry = self.traceback[i] + if not entry.ishidden(self): + path, lineno = entry.frame.code.raw.co_filename, entry.lineno + exconly = self.exconly(tryshort=True) + return ReprFileLocation(path, lineno + 1, exconly) + return None def getrepr( self, showlocals: bool = False, - style: "_TracebackStyle" = "long", + style: _TracebackStyle = "long", abspath: bool = False, - tbfilter: bool = True, + tbfilter: Union[ + bool, Callable[["ExceptionInfo[BaseException]"], Traceback] + ] = True, funcargs: bool = False, truncate_locals: bool = True, chain: bool = True, @@ -622,14 +644,20 @@ def getrepr( Ignored if ``style=="native"``. :param str style: - long|short|no|native|value traceback style. + long|short|line|no|native|value traceback style. :param bool abspath: If paths should be changed to absolute or left unchanged. - :param bool tbfilter: - Hide entries that contain a local variable ``__tracebackhide__==True``. - Ignored if ``style=="native"``. + :param tbfilter: + A filter for traceback entries. + + * If false, don't hide any entries. + * If true, hide internal entries and entries that contain a local + variable ``__tracebackhide__ = True``. + * If a callable, delegates the filtering to the callable. + + Ignored if ``style`` is ``"native"``. :param bool funcargs: Show fixtures ("funcargs" for legacy purposes) per traceback entry. @@ -646,12 +674,14 @@ def getrepr( """ if style == "native": return ReprExceptionInfo( - ReprTracebackNative( + reprtraceback=ReprTracebackNative( traceback.format_exception( - self.type, self.value, self.traceback[0]._rawentry + self.type, + self.value, + self.traceback[0]._rawentry if self.traceback else None, ) ), - self._getreprcrash(), + reprcrash=self._getreprcrash(), ) fmt = FormattedExcinfo( @@ -665,6 +695,25 @@ def getrepr( ) return fmt.repr_excinfo(self) + def _stringify_exception(self, exc: BaseException) -> str: + try: + notes = getattr(exc, "__notes__", []) + except KeyError: + # Workaround for https://github.com/python/cpython/issues/98778 on + # Python <= 3.9, and some 3.10 and 3.11 patch versions. + HTTPError = getattr(sys.modules.get("urllib.error", None), "HTTPError", ()) + if sys.version_info < (3, 12) and isinstance(exc, HTTPError): + notes = [] + else: + raise + + return "\n".join( + [ + str(exc), + *notes, + ] + ) + 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`. @@ -672,15 +721,81 @@ def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]": If it matches `True` is returned, otherwise an `AssertionError` is raised. """ __tracebackhide__ = True - 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)) + value = self._stringify_exception(self.value) + msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}" + if regexp == value: + msg += "\n Did you mean to `re.escape()` the regex?" + assert re.search(regexp, value), msg # Return True to allow for "assert excinfo.match()". return True + def _group_contains( + self, + exc_group: BaseExceptionGroup[BaseException], + expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]], + match: Union[str, Pattern[str], None], + target_depth: Optional[int] = None, + current_depth: int = 1, + ) -> bool: + """Return `True` if a `BaseExceptionGroup` contains a matching exception.""" + if (target_depth is not None) and (current_depth > target_depth): + # already descended past the target depth + return False + for exc in exc_group.exceptions: + if isinstance(exc, BaseExceptionGroup): + if self._group_contains( + exc, expected_exception, match, target_depth, current_depth + 1 + ): + return True + if (target_depth is not None) and (current_depth != target_depth): + # not at the target depth, no match + continue + if not isinstance(exc, expected_exception): + continue + if match is not None: + value = self._stringify_exception(exc) + if not re.search(match, value): + continue + return True + return False + + def group_contains( + self, + expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]], + *, + match: Union[str, Pattern[str], None] = None, + depth: Optional[int] = None, + ) -> bool: + """Check whether a captured exception group contains a matching exception. + + :param Type[BaseException] | Tuple[Type[BaseException]] expected_exception: + The expected exception type, or a tuple if one of multiple possible + exception types are expected. + + :param str | Pattern[str] | None match: + If specified, a string containing a regular expression, + or a regular expression object, that is tested against the string + representation of the exception and its `PEP-678 ` `__notes__` + using :func:`re.search`. -@attr.s(auto_attribs=True) + To match a literal string that may contain :ref:`special characters + `, the pattern can first be escaped with :func:`re.escape`. + + :param Optional[int] depth: + If `None`, will search for a matching exception at any nesting depth. + If >= 1, will only match an exception if it's at the specified depth (depth = 1 being + the exceptions contained within the topmost exception group). + + .. versionadded:: 8.0 + """ + msg = "Captured exception is not an instance of `BaseExceptionGroup`" + assert isinstance(self.value, BaseExceptionGroup), msg + msg = "`depth` must be >= 1 if specified" + assert (depth is None) or (depth >= 1), msg + return self._group_contains(self.value, expected_exception, match, depth) + + +@dataclasses.dataclass class FormattedExcinfo: """Presenting information about failing Functions and Generators.""" @@ -689,14 +804,14 @@ class FormattedExcinfo: fail_marker: ClassVar = "E" showlocals: bool = False - style: "_TracebackStyle" = "long" + style: _TracebackStyle = "long" abspath: bool = True - tbfilter: bool = True + tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True funcargs: bool = False truncate_locals: bool = True chain: bool = True - astcache: Dict[Union[str, Path], ast.AST] = attr.ib( - factory=dict, init=False, repr=False + astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field( + default_factory=dict, init=False, repr=False ) def _getindent(self, source: "Source") -> int: @@ -737,11 +852,13 @@ def get_source( ) -> List[str]: """Return formatted and marked up source lines.""" lines = [] - if source is None or line_index >= len(source.lines): + if source is not None and line_index < 0: + line_index += len(source) + if source is None or line_index >= len(source.lines) or line_index < 0: + # `line_index` could still be outside `range(len(source.lines))` if + # we're processing AST with pathological position attributes. source = Source("???") line_index = 0 - if line_index < 0: - line_index += len(source) space_prefix = " " if short: lines.append(space_prefix + source.lines[line_index].strip()) @@ -801,12 +918,16 @@ def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]: def repr_traceback_entry( self, - entry: TracebackEntry, + entry: Optional[TracebackEntry], excinfo: Optional[ExceptionInfo[BaseException]] = None, ) -> "ReprEntry": lines: List[str] = [] - style = entry._repr_style if entry._repr_style is not None else self.style - if style in ("short", "long"): + style = ( + entry._repr_style + if entry is not None and entry._repr_style is not None + else self.style + ) + if style in ("short", "long") and entry is not None: source = self._getentrysource(entry) if source is None: source = Source("???") @@ -847,25 +968,31 @@ def _makepath(self, path: Union[Path, str]) -> str: def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback": traceback = excinfo.traceback - if self.tbfilter: - traceback = traceback.filter() + if callable(self.tbfilter): + traceback = self.tbfilter(excinfo) + elif self.tbfilter: + traceback = traceback.filter(excinfo) if isinstance(excinfo.value, RecursionError): traceback, extraline = self._truncate_recursive_traceback(traceback) else: extraline = None + if not traceback: + if extraline is None: + extraline = "All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames." + entries = [self.repr_traceback_entry(None, excinfo)] + return ReprTraceback(entries, extraline, style=self.style) + last = traceback[-1] - entries = [] if self.style == "value": - reprentry = self.repr_traceback_entry(last, excinfo) - entries.append(reprentry) + entries = [self.repr_traceback_entry(last, excinfo)] 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) + entries = [ + self.repr_traceback_entry(entry, excinfo if last == entry else None) + for entry in traceback + ] return ReprTraceback(entries, extraline, style=self.style) def _truncate_recursive_traceback( @@ -890,13 +1017,8 @@ def _truncate_recursive_traceback( extraline: Optional[str] = ( "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n" " The following exception happened when comparing locals in the stack frame:\n" - " {exc_type}: {exc_msg}\n" - " Displaying first and last {max_frames} stack frames out of {total}." - ).format( - exc_type=type(e).__name__, - exc_msg=str(e), - max_frames=max_frames, - total=len(traceback), + f" {type(e).__name__}: {e!s}\n" + f" Displaying first and last {max_frames} stack frames out of {len(traceback)}." ) # Type ignored because adding two instances of a List subtype # currently incorrectly has type List instead of the subtype. @@ -922,11 +1044,24 @@ def repr_excinfo( seen: Set[int] = set() while e is not None and id(e) not in seen: seen.add(id(e)) + if excinfo_: - reprtraceback = self.repr_traceback(excinfo_) - reprcrash: Optional[ReprFileLocation] = ( - excinfo_._getreprcrash() if self.style != "value" else None - ) + # Fall back to native traceback as a temporary workaround until + # full support for exception groups added to ExceptionInfo. + # See https://github.com/pytest-dev/pytest/issues/9159 + if isinstance(e, BaseExceptionGroup): + reprtraceback: Union[ReprTracebackNative, ReprTraceback] = ( + ReprTracebackNative( + traceback.format_exception( + type(excinfo_.value), + excinfo_.value, + excinfo_.traceback[0]._rawentry, + ) + ) + ) + else: + 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. @@ -934,25 +1069,17 @@ def repr_excinfo( 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.from_exc_info((type(e), e, e.__traceback__)) - if e.__traceback__ - else None - ) + excinfo_ = ExceptionInfo.from_exception(e) 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.from_exc_info((type(e), e, e.__traceback__)) - if e.__traceback__ - else None - ) + excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None descr = "During handling of the above exception, another exception occurred:" else: e = None @@ -960,7 +1087,7 @@ def repr_excinfo( return ExceptionChainRepr(repr_chain) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class TerminalRepr: def __str__(self) -> str: # FYI this is called from pytest-xdist's serialization of exception @@ -978,14 +1105,14 @@ def toterminal(self, tw: TerminalWriter) -> None: # This class is abstract -- only subclasses are instantiated. -@attr.s(eq=False) +@dataclasses.dataclass(eq=False) class ExceptionRepr(TerminalRepr): # Provided by subclasses. - reprcrash: Optional["ReprFileLocation"] reprtraceback: "ReprTraceback" - - def __attrs_post_init__(self) -> None: - self.sections: List[Tuple[str, str, str]] = [] + reprcrash: Optional["ReprFileLocation"] + sections: List[Tuple[str, str, str]] = dataclasses.field( + init=False, default_factory=list + ) def addsection(self, name: str, content: str, sep: str = "-") -> None: self.sections.append((name, content, sep)) @@ -996,16 +1123,23 @@ def toterminal(self, tw: TerminalWriter) -> None: tw.line(content) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ExceptionChainRepr(ExceptionRepr): chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]] - def __attrs_post_init__(self) -> None: - super().__attrs_post_init__() + def __init__( + self, + chain: Sequence[ + Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]] + ], + ) -> None: # reprcrash and reprtraceback of the outermost (the newest) exception # in the chain. - self.reprtraceback = self.chain[-1][0] - self.reprcrash = self.chain[-1][1] + super().__init__( + reprtraceback=chain[-1][0], + reprcrash=chain[-1][1], + ) + self.chain = chain def toterminal(self, tw: TerminalWriter) -> None: for element in self.chain: @@ -1016,21 +1150,21 @@ def toterminal(self, tw: TerminalWriter) -> None: super().toterminal(tw) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprExceptionInfo(ExceptionRepr): reprtraceback: "ReprTraceback" - reprcrash: "ReprFileLocation" + reprcrash: Optional["ReprFileLocation"] def toterminal(self, tw: TerminalWriter) -> None: self.reprtraceback.toterminal(tw) super().toterminal(tw) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprTraceback(TerminalRepr): reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]] extraline: Optional[str] - style: "_TracebackStyle" + style: _TracebackStyle entrysep: ClassVar = "_ " @@ -1055,28 +1189,28 @@ def toterminal(self, tw: TerminalWriter) -> None: class ReprTracebackNative(ReprTraceback): def __init__(self, tblines: Sequence[str]) -> None: - self.style = "native" self.reprentries = [ReprEntryNative(tblines)] self.extraline = None + self.style = "native" -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprEntryNative(TerminalRepr): lines: Sequence[str] - style: ClassVar["_TracebackStyle"] = "native" + style: ClassVar[_TracebackStyle] = "native" def toterminal(self, tw: TerminalWriter) -> None: tw.write("".join(self.lines)) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprEntry(TerminalRepr): lines: Sequence[str] reprfuncargs: Optional["ReprFuncArgs"] reprlocals: Optional["ReprLocals"] reprfileloc: Optional["ReprFileLocation"] - style: "_TracebackStyle" + style: _TracebackStyle def _write_entry_lines(self, tw: TerminalWriter) -> None: """Write the source code portions of a list of traceback entries with syntax highlighting. @@ -1091,7 +1225,6 @@ def _write_entry_lines(self, tw: TerminalWriter) -> None: 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 @@ -1124,8 +1257,8 @@ def _write_entry_lines(self, tw: TerminalWriter) -> None: def toterminal(self, tw: TerminalWriter) -> None: if self.style == "short": - assert self.reprfileloc is not None - self.reprfileloc.toterminal(tw) + if self.reprfileloc: + self.reprfileloc.toterminal(tw) self._write_entry_lines(tw) if self.reprlocals: self.reprlocals.toterminal(tw, indent=" " * 8) @@ -1150,12 +1283,15 @@ def __str__(self) -> str: ) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprFileLocation(TerminalRepr): - path: str = attr.ib(converter=str) + path: str lineno: int message: str + def __post_init__(self) -> None: + self.path = str(self.path) + def toterminal(self, tw: TerminalWriter) -> None: # Filename and lineno output for each entry, using an output format # that most editors understand. @@ -1167,7 +1303,7 @@ def toterminal(self, tw: TerminalWriter) -> None: tw.line(f":{self.lineno}: {msg}") -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprLocals(TerminalRepr): lines: Sequence[str] @@ -1176,7 +1312,7 @@ def toterminal(self, tw: TerminalWriter, indent="") -> None: tw.line(indent + line) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprFuncArgs(TerminalRepr): args: Sequence[Tuple[str, object]] @@ -1211,7 +1347,7 @@ def getfslineno(obj: object) -> Tuple[Union[str, Path], int]: # 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] + obj = obj.place_as try: code = Code.from_function(obj) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/source.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/source.py index 208cfb80037a8..7fa577e03b34a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/source.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/source.py @@ -1,10 +1,10 @@ +# mypy: allow-untyped-defs import ast +from bisect import bisect_right import inspect import textwrap import tokenize import types -import warnings -from bisect import bisect_right from typing import Iterable from typing import Iterator from typing import List @@ -12,6 +12,7 @@ from typing import overload from typing import Tuple from typing import Union +import warnings class Source: @@ -46,12 +47,10 @@ def __eq__(self, other: object) -> bool: __hash__ = None # type: ignore @overload - def __getitem__(self, key: int) -> str: - ... + def __getitem__(self, key: int) -> str: ... @overload - def __getitem__(self, key: slice) -> "Source": - ... + def __getitem__(self, key: slice) -> "Source": ... def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]: if isinstance(key, int): @@ -149,8 +148,7 @@ def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[i values: List[int] = [] for x in ast.walk(node): if isinstance(x, (ast.stmt, ast.ExceptHandler)): - # Before Python 3.8, the lineno of a decorated class or function pointed at the decorator. - # Since Python 3.8, the lineno points to the class/def, so need to include the decorators. + # The lineno points to the class/def, so need to include the decorators. if isinstance(x, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): for d in x.decorator_list: values.append(d.lineno - 1) @@ -197,7 +195,9 @@ def getstatementrange_ast( # 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. - block_finder.started = source.lines[start][0].isspace() + block_finder.started = ( + bool(source.lines[start]) and source.lines[start][0].isspace() + ) it = ((x + "\n") for x in source.lines[start:end]) try: for tok in tokenize.generate_tokens(lambda: next(it)): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/w3c-import.log new file mode 100644 index 0000000000000..a904832ef2f21 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/code.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/source.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/pprint.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/pprint.py new file mode 100644 index 0000000000000..75e9a7123b567 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/pprint.py @@ -0,0 +1,676 @@ +# mypy: allow-untyped-defs +# This module was imported from the cpython standard library +# (https://github.com/python/cpython/) at commit +# c5140945c723ae6c4b7ee81ff720ac8ea4b52cfd (python3.12). +# +# +# Original Author: Fred L. Drake, Jr. +# fdrake@acm.org +# +# This is a simple little module I wrote to make life easier. I didn't +# see anything quite like it in the library, though I may have overlooked +# something. I wrote this when I was trying to read some heavily nested +# tuples with fairly non-descriptive content. This is modeled very much +# after Lisp/Scheme - style pretty-printing of lists. If you find it +# useful, thank small children who sleep at night. +import collections as _collections +import dataclasses as _dataclasses +from io import StringIO as _StringIO +import re +import types as _types +from typing import Any +from typing import Callable +from typing import Dict +from typing import IO +from typing import Iterator +from typing import List +from typing import Optional +from typing import Set +from typing import Tuple + + +class _safe_key: + """Helper function for key functions when sorting unorderable objects. + + The wrapped-object will fallback to a Py2.x style comparison for + unorderable types (sorting first comparing the type name and then by + the obj ids). Does not work recursively, so dict.items() must have + _safe_key applied to both the key and the value. + + """ + + __slots__ = ["obj"] + + def __init__(self, obj): + self.obj = obj + + def __lt__(self, other): + try: + return self.obj < other.obj + except TypeError: + return (str(type(self.obj)), id(self.obj)) < ( + str(type(other.obj)), + id(other.obj), + ) + + +def _safe_tuple(t): + """Helper function for comparing 2-tuples""" + return _safe_key(t[0]), _safe_key(t[1]) + + +class PrettyPrinter: + def __init__( + self, + indent: int = 4, + width: int = 80, + depth: Optional[int] = None, + ) -> None: + """Handle pretty printing operations onto a stream using a set of + configured parameters. + + indent + Number of spaces to indent for each level of nesting. + + width + Attempted maximum number of columns in the output. + + depth + The maximum depth to print out nested structures. + + """ + if indent < 0: + raise ValueError("indent must be >= 0") + if depth is not None and depth <= 0: + raise ValueError("depth must be > 0") + if not width: + raise ValueError("width must be != 0") + self._depth = depth + self._indent_per_level = indent + self._width = width + + def pformat(self, object: Any) -> str: + sio = _StringIO() + self._format(object, sio, 0, 0, set(), 0) + return sio.getvalue() + + def _format( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + objid = id(object) + if objid in context: + stream.write(_recursion(object)) + return + + p = self._dispatch.get(type(object).__repr__, None) + if p is not None: + context.add(objid) + p(self, object, stream, indent, allowance, context, level + 1) + context.remove(objid) + elif ( + _dataclasses.is_dataclass(object) + and not isinstance(object, type) + and object.__dataclass_params__.repr + and + # Check dataclass has generated repr method. + hasattr(object.__repr__, "__wrapped__") + and "__create_fn__" in object.__repr__.__wrapped__.__qualname__ + ): + context.add(objid) + self._pprint_dataclass( + object, stream, indent, allowance, context, level + 1 + ) + context.remove(objid) + else: + stream.write(self._repr(object, context, level)) + + def _pprint_dataclass( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + cls_name = object.__class__.__name__ + items = [ + (f.name, getattr(object, f.name)) + for f in _dataclasses.fields(object) + if f.repr + ] + stream.write(cls_name + "(") + self._format_namespace_items(items, stream, indent, allowance, context, level) + stream.write(")") + + _dispatch: Dict[ + Callable[..., str], + Callable[["PrettyPrinter", Any, IO[str], int, int, Set[int], int], None], + ] = {} + + def _pprint_dict( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + write = stream.write + write("{") + items = sorted(object.items(), key=_safe_tuple) + self._format_dict_items(items, stream, indent, allowance, context, level) + write("}") + + _dispatch[dict.__repr__] = _pprint_dict + + def _pprint_ordered_dict( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + if not len(object): + stream.write(repr(object)) + return + cls = object.__class__ + stream.write(cls.__name__ + "(") + self._pprint_dict(object, stream, indent, allowance, context, level) + stream.write(")") + + _dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict + + def _pprint_list( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + stream.write("[") + self._format_items(object, stream, indent, allowance, context, level) + stream.write("]") + + _dispatch[list.__repr__] = _pprint_list + + def _pprint_tuple( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + stream.write("(") + self._format_items(object, stream, indent, allowance, context, level) + stream.write(")") + + _dispatch[tuple.__repr__] = _pprint_tuple + + def _pprint_set( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + if not len(object): + stream.write(repr(object)) + return + typ = object.__class__ + if typ is set: + stream.write("{") + endchar = "}" + else: + stream.write(typ.__name__ + "({") + endchar = "})" + object = sorted(object, key=_safe_key) + self._format_items(object, stream, indent, allowance, context, level) + stream.write(endchar) + + _dispatch[set.__repr__] = _pprint_set + _dispatch[frozenset.__repr__] = _pprint_set + + def _pprint_str( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + write = stream.write + if not len(object): + write(repr(object)) + return + chunks = [] + lines = object.splitlines(True) + if level == 1: + indent += 1 + allowance += 1 + max_width1 = max_width = self._width - indent + for i, line in enumerate(lines): + rep = repr(line) + if i == len(lines) - 1: + max_width1 -= allowance + if len(rep) <= max_width1: + chunks.append(rep) + else: + # A list of alternating (non-space, space) strings + parts = re.findall(r"\S*\s*", line) + assert parts + assert not parts[-1] + parts.pop() # drop empty last part + max_width2 = max_width + current = "" + for j, part in enumerate(parts): + candidate = current + part + if j == len(parts) - 1 and i == len(lines) - 1: + max_width2 -= allowance + if len(repr(candidate)) > max_width2: + if current: + chunks.append(repr(current)) + current = part + else: + current = candidate + if current: + chunks.append(repr(current)) + if len(chunks) == 1: + write(rep) + return + if level == 1: + write("(") + for i, rep in enumerate(chunks): + if i > 0: + write("\n" + " " * indent) + write(rep) + if level == 1: + write(")") + + _dispatch[str.__repr__] = _pprint_str + + def _pprint_bytes( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + write = stream.write + if len(object) <= 4: + write(repr(object)) + return + parens = level == 1 + if parens: + indent += 1 + allowance += 1 + write("(") + delim = "" + for rep in _wrap_bytes_repr(object, self._width - indent, allowance): + write(delim) + write(rep) + if not delim: + delim = "\n" + " " * indent + if parens: + write(")") + + _dispatch[bytes.__repr__] = _pprint_bytes + + def _pprint_bytearray( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + write = stream.write + write("bytearray(") + self._pprint_bytes( + bytes(object), stream, indent + 10, allowance + 1, context, level + 1 + ) + write(")") + + _dispatch[bytearray.__repr__] = _pprint_bytearray + + def _pprint_mappingproxy( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + stream.write("mappingproxy(") + self._format(object.copy(), stream, indent, allowance, context, level) + stream.write(")") + + _dispatch[_types.MappingProxyType.__repr__] = _pprint_mappingproxy + + def _pprint_simplenamespace( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + if type(object) is _types.SimpleNamespace: + # The SimpleNamespace repr is "namespace" instead of the class + # name, so we do the same here. For subclasses; use the class name. + cls_name = "namespace" + else: + cls_name = object.__class__.__name__ + items = object.__dict__.items() + stream.write(cls_name + "(") + self._format_namespace_items(items, stream, indent, allowance, context, level) + stream.write(")") + + _dispatch[_types.SimpleNamespace.__repr__] = _pprint_simplenamespace + + def _format_dict_items( + self, + items: List[Tuple[Any, Any]], + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + if not items: + return + + write = stream.write + item_indent = indent + self._indent_per_level + delimnl = "\n" + " " * item_indent + for key, ent in items: + write(delimnl) + write(self._repr(key, context, level)) + write(": ") + self._format(ent, stream, item_indent, 1, context, level) + write(",") + + write("\n" + " " * indent) + + def _format_namespace_items( + self, + items: List[Tuple[Any, Any]], + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + if not items: + return + + write = stream.write + item_indent = indent + self._indent_per_level + delimnl = "\n" + " " * item_indent + for key, ent in items: + write(delimnl) + write(key) + write("=") + if id(ent) in context: + # Special-case representation of recursion to match standard + # recursive dataclass repr. + write("...") + else: + self._format( + ent, + stream, + item_indent + len(key) + 1, + 1, + context, + level, + ) + + write(",") + + write("\n" + " " * indent) + + def _format_items( + self, + items: List[Any], + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + if not items: + return + + write = stream.write + item_indent = indent + self._indent_per_level + delimnl = "\n" + " " * item_indent + + for item in items: + write(delimnl) + self._format(item, stream, item_indent, 1, context, level) + write(",") + + write("\n" + " " * indent) + + def _repr(self, object: Any, context: Set[int], level: int) -> str: + return self._safe_repr(object, context.copy(), self._depth, level) + + def _pprint_default_dict( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + rdf = self._repr(object.default_factory, context, level) + stream.write(f"{object.__class__.__name__}({rdf}, ") + self._pprint_dict(object, stream, indent, allowance, context, level) + stream.write(")") + + _dispatch[_collections.defaultdict.__repr__] = _pprint_default_dict + + def _pprint_counter( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + stream.write(object.__class__.__name__ + "(") + + if object: + stream.write("{") + items = object.most_common() + self._format_dict_items(items, stream, indent, allowance, context, level) + stream.write("}") + + stream.write(")") + + _dispatch[_collections.Counter.__repr__] = _pprint_counter + + def _pprint_chain_map( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + if not len(object.maps) or (len(object.maps) == 1 and not len(object.maps[0])): + stream.write(repr(object)) + return + + stream.write(object.__class__.__name__ + "(") + self._format_items(object.maps, stream, indent, allowance, context, level) + stream.write(")") + + _dispatch[_collections.ChainMap.__repr__] = _pprint_chain_map + + def _pprint_deque( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + stream.write(object.__class__.__name__ + "(") + if object.maxlen is not None: + stream.write("maxlen=%d, " % object.maxlen) + stream.write("[") + + self._format_items(object, stream, indent, allowance + 1, context, level) + stream.write("])") + + _dispatch[_collections.deque.__repr__] = _pprint_deque + + def _pprint_user_dict( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + self._format(object.data, stream, indent, allowance, context, level - 1) + + _dispatch[_collections.UserDict.__repr__] = _pprint_user_dict + + def _pprint_user_list( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + self._format(object.data, stream, indent, allowance, context, level - 1) + + _dispatch[_collections.UserList.__repr__] = _pprint_user_list + + def _pprint_user_string( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Set[int], + level: int, + ) -> None: + self._format(object.data, stream, indent, allowance, context, level - 1) + + _dispatch[_collections.UserString.__repr__] = _pprint_user_string + + def _safe_repr( + self, object: Any, context: Set[int], maxlevels: Optional[int], level: int + ) -> str: + typ = type(object) + if typ in _builtin_scalars: + return repr(object) + + r = getattr(typ, "__repr__", None) + + if issubclass(typ, dict) and r is dict.__repr__: + if not object: + return "{}" + objid = id(object) + if maxlevels and level >= maxlevels: + return "{...}" + if objid in context: + return _recursion(object) + context.add(objid) + components: List[str] = [] + append = components.append + level += 1 + for k, v in sorted(object.items(), key=_safe_tuple): + krepr = self._safe_repr(k, context, maxlevels, level) + vrepr = self._safe_repr(v, context, maxlevels, level) + append(f"{krepr}: {vrepr}") + context.remove(objid) + return "{%s}" % ", ".join(components) + + if (issubclass(typ, list) and r is list.__repr__) or ( + issubclass(typ, tuple) and r is tuple.__repr__ + ): + if issubclass(typ, list): + if not object: + return "[]" + format = "[%s]" + elif len(object) == 1: + format = "(%s,)" + else: + if not object: + return "()" + format = "(%s)" + objid = id(object) + if maxlevels and level >= maxlevels: + return format % "..." + if objid in context: + return _recursion(object) + context.add(objid) + components = [] + append = components.append + level += 1 + for o in object: + orepr = self._safe_repr(o, context, maxlevels, level) + append(orepr) + context.remove(objid) + return format % ", ".join(components) + + return repr(object) + + +_builtin_scalars = frozenset( + {str, bytes, bytearray, float, complex, bool, type(None), int} +) + + +def _recursion(object: Any) -> str: + return f"" + + +def _wrap_bytes_repr(object: Any, width: int, allowance: int) -> Iterator[str]: + current = b"" + last = len(object) // 4 * 4 + for i in range(0, len(object), 4): + part = object[i : i + 4] + candidate = current + part + if i == last: + width -= allowance + if len(repr(candidate)) > width: + if current: + yield repr(current) + current = part + else: + current = candidate + if current: + yield repr(current) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/saferepr.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/saferepr.py index e7ff5cab20368..9f33fced6769a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/saferepr.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/saferepr.py @@ -1,8 +1,5 @@ import pprint import reprlib -from typing import Any -from typing import Dict -from typing import IO from typing import Optional @@ -22,8 +19,8 @@ def _format_repr_exception(exc: BaseException, obj: object) -> str: raise except BaseException as exc: exc_info = f"unpresentable exception ({_try_repr_or_str(exc)})" - return "<[{} raised in repr()] {} object at 0x{:x}>".format( - exc_info, type(obj).__name__, id(obj) + return ( + f"<[{exc_info} raised in repr()] {type(obj).__name__} object at 0x{id(obj):x}>" ) @@ -41,7 +38,7 @@ class SafeRepr(reprlib.Repr): information on exceptions raised during the call. """ - def __init__(self, maxsize: Optional[int]) -> None: + def __init__(self, maxsize: Optional[int], use_ascii: bool = False) -> None: """ :param maxsize: If not None, will truncate the resulting repr to that specific size, using ellipsis @@ -54,10 +51,15 @@ def __init__(self, maxsize: Optional[int]) -> None: # truncation. self.maxstring = maxsize if maxsize is not None else 1_000_000_000 self.maxsize = maxsize + self.use_ascii = use_ascii def repr(self, x: object) -> str: try: - s = super().repr(x) + if self.use_ascii: + s = ascii(x) + else: + s = super().repr(x) + except (KeyboardInterrupt, SystemExit): raise except BaseException as exc: @@ -94,7 +96,9 @@ def safeformat(obj: object) -> str: DEFAULT_REPR_MAX_SIZE = 240 -def saferepr(obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE) -> str: +def saferepr( + obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False +) -> str: """Return a size-limited safe repr-string for the given object. Failing __repr__ functions of user instances will be represented @@ -104,50 +108,23 @@ def saferepr(obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE) -> str This function is a wrapper around the Repr/reprlib functionality of the stdlib. """ - 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) + return SafeRepr(maxsize, use_ascii).repr(obj) + + +def saferepr_unlimited(obj: object, use_ascii: bool = True) -> str: + """Return an unlimited-size safe repr-string for the given object. + + As with saferepr, failing __repr__ functions of user instances + will be represented with a short exception info. + + This function is a wrapper around simple repr. + + Note: a cleaner solution would be to alter ``saferepr``this way + when maxsize=None, but that might affect some other code. + """ + try: + if use_ascii: + return ascii(obj) + return repr(obj) + except Exception as exc: + return _format_repr_exception(exc, obj) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/terminalwriter.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/terminalwriter.py index 379035d858c92..deb6ecc3c942b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/terminalwriter.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/terminalwriter.py @@ -1,13 +1,16 @@ """Helper functions for writing to terminals and files.""" + import os import shutil import sys +from typing import final +from typing import Literal from typing import Optional from typing import Sequence from typing import TextIO +from ..compat import assert_never from .wcwidth import wcswidth -from _pytest.compat import final # This code was initially copied from py 1.8.1, file _io/terminalwriter.py. @@ -28,9 +31,9 @@ def should_do_markup(file: TextIO) -> bool: return True if os.environ.get("PY_COLORS") == "0": return False - if "NO_COLOR" in os.environ: + if os.environ.get("NO_COLOR"): return False - if "FORCE_COLOR" in os.environ: + if os.environ.get("FORCE_COLOR"): return True return ( hasattr(file, "isatty") and file.isatty() and os.environ.get("TERM") != "dumb" @@ -182,9 +185,7 @@ def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> No """ if indents and len(indents) != len(lines): raise ValueError( - "indents size ({}) should have same size as lines ({})".format( - len(indents), len(lines) - ) + f"indents size ({len(indents)}) should have same size as lines ({len(lines)})" ) if not indents: indents = [""] * len(lines) @@ -193,15 +194,24 @@ def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> No 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.""" + def _highlight( + self, source: str, lexer: Literal["diff", "python"] = "python" + ) -> str: + """Highlight the given source if we have markup support.""" from _pytest.config.exceptions import UsageError - if not self.hasmarkup or not self.code_highlight: + if not source or not self.hasmarkup or not self.code_highlight: return source + try: from pygments.formatters.terminal import TerminalFormatter - from pygments.lexers.python import PythonLexer + + if lexer == "python": + from pygments.lexers.python import PythonLexer as Lexer + elif lexer == "diff": + from pygments.lexers.diff import DiffLexer as Lexer + else: + assert_never(lexer) from pygments import highlight import pygments.util except ImportError: @@ -210,24 +220,32 @@ def _highlight(self, source: str) -> str: try: highlighted: str = highlight( source, - PythonLexer(), + Lexer(), TerminalFormatter( bg=os.getenv("PYTEST_THEME_MODE", "dark"), style=os.getenv("PYTEST_THEME"), ), ) - return highlighted - except pygments.util.ClassNotFound: + # pygments terminal formatter may add a newline when there wasn't one. + # We don't want this, remove. + if highlighted[-1] == "\n" and source[-1] != "\n": + highlighted = highlighted[:-1] + + # Some lexers will not set the initial color explicitly + # which may lead to the previous color being propagated to the + # start of the expression, so reset first. + return "\x1b[0m" + highlighted + except pygments.util.ClassNotFound as e: raise UsageError( "PYTEST_THEME environment variable had an invalid value: '{}'. " "Only valid pygment styles are allowed.".format( os.getenv("PYTEST_THEME") ) - ) - except pygments.util.OptionError: + ) from e + except pygments.util.OptionError as e: raise UsageError( "PYTEST_THEME_MODE environment variable had an invalid value: '{}'. " "The only allowed values are 'dark' and 'light'.".format( os.getenv("PYTEST_THEME_MODE") ) - ) + ) from e diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/w3c-import.log new file mode 100644 index 0000000000000..dda9543a7df01 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/pprint.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/saferepr.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/terminalwriter.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/wcwidth.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/wcwidth.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/wcwidth.py index e5c7bf4d8683c..53803133519fb 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/wcwidth.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/wcwidth.py @@ -1,5 +1,5 @@ -import unicodedata from functools import lru_cache +import unicodedata @lru_cache(100) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_py/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_py/__init__.py new file mode 100644 index 0000000000000..a6834b8285a56 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_py/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_py/error.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_py/error.py new file mode 100644 index 0000000000000..ab3a4ed318eac --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_py/error.py @@ -0,0 +1,111 @@ +"""create errno-specific classes for IO or os calls.""" + +from __future__ import annotations + +import errno +import os +import sys +from typing import Callable +from typing import TYPE_CHECKING +from typing import TypeVar + + +if TYPE_CHECKING: + from typing_extensions import ParamSpec + + P = ParamSpec("P") + +R = TypeVar("R") + + +class Error(EnvironmentError): + def __repr__(self) -> str: + return "{}.{} {!r}: {} ".format( + self.__class__.__module__, + self.__class__.__name__, + self.__class__.__doc__, + " ".join(map(str, self.args)), + # repr(self.args) + ) + + def __str__(self) -> str: + s = "[{}]: {}".format( + self.__class__.__doc__, + " ".join(map(str, self.args)), + ) + return s + + +_winerrnomap = { + 2: errno.ENOENT, + 3: errno.ENOENT, + 17: errno.EEXIST, + 18: errno.EXDEV, + 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailable + 22: errno.ENOTDIR, + 20: errno.ENOTDIR, + 267: errno.ENOTDIR, + 5: errno.EACCES, # anything better? +} + + +class ErrorMaker: + """lazily provides Exception classes for each possible POSIX errno + (as defined per the 'errno' module). All such instances + subclass EnvironmentError. + """ + + _errno2class: dict[int, type[Error]] = {} + + def __getattr__(self, name: str) -> type[Error]: + if name[0] == "_": + raise AttributeError(name) + eno = getattr(errno, name) + cls = self._geterrnoclass(eno) + setattr(self, name, cls) + return cls + + def _geterrnoclass(self, eno: int) -> type[Error]: + try: + return self._errno2class[eno] + except KeyError: + clsname = errno.errorcode.get(eno, "UnknownErrno%d" % (eno,)) + errorcls = type( + clsname, + (Error,), + {"__module__": "py.error", "__doc__": os.strerror(eno)}, + ) + self._errno2class[eno] = errorcls + return errorcls + + def checked_call( + self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs + ) -> R: + """Call a function and raise an errno-exception if applicable.""" + __tracebackhide__ = True + try: + return func(*args, **kwargs) + except Error: + raise + except OSError as value: + if not hasattr(value, "errno"): + raise + errno = value.errno + if sys.platform == "win32": + try: + cls = self._geterrnoclass(_winerrnomap[errno]) + except KeyError: + raise value + else: + # we are not on Windows, or we got a proper OSError + cls = self._geterrnoclass(errno) + + raise cls(f"{func.__name__}{args!r}") + + +_error_maker = ErrorMaker() +checked_call = _error_maker.checked_call + + +def __getattr__(attr: str) -> type[Error]: + return getattr(_error_maker, attr) # type: ignore[no-any-return] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_py/path.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_py/path.py new file mode 100644 index 0000000000000..9b4ec68950de1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_py/path.py @@ -0,0 +1,1477 @@ +# mypy: allow-untyped-defs +"""local path implementation.""" + +from __future__ import annotations + +import atexit +from contextlib import contextmanager +import fnmatch +import importlib.util +import io +import os +from os.path import abspath +from os.path import dirname +from os.path import exists +from os.path import isabs +from os.path import isdir +from os.path import isfile +from os.path import islink +from os.path import normpath +import posixpath +from stat import S_ISDIR +from stat import S_ISLNK +from stat import S_ISREG +import sys +from typing import Any +from typing import Callable +from typing import cast +from typing import Literal +from typing import overload +from typing import TYPE_CHECKING +import uuid +import warnings + +from . import error + + +# Moved from local.py. +iswin32 = sys.platform == "win32" or (getattr(os, "_name", False) == "nt") + + +class Checkers: + _depend_on_existence = "exists", "link", "dir", "file" + + def __init__(self, path): + self.path = path + + def dotfile(self): + return self.path.basename.startswith(".") + + def ext(self, arg): + if not arg.startswith("."): + arg = "." + arg + return self.path.ext == arg + + def basename(self, arg): + return self.path.basename == arg + + def basestarts(self, arg): + return self.path.basename.startswith(arg) + + def relto(self, arg): + return self.path.relto(arg) + + def fnmatch(self, arg): + return self.path.fnmatch(arg) + + def endswith(self, arg): + return str(self.path).endswith(arg) + + def _evaluate(self, kw): + from .._code.source import getrawcode + + for name, value in kw.items(): + invert = False + meth = None + try: + meth = getattr(self, name) + except AttributeError: + if name[:3] == "not": + invert = True + try: + meth = getattr(self, name[3:]) + except AttributeError: + pass + if meth is None: + raise TypeError(f"no {name!r} checker available for {self.path!r}") + try: + if getrawcode(meth).co_argcount > 1: + if (not meth(value)) ^ invert: + return False + else: + if bool(value) ^ bool(meth()) ^ invert: + return False + except (error.ENOENT, error.ENOTDIR, error.EBUSY): + # EBUSY feels not entirely correct, + # but its kind of necessary since ENOMEDIUM + # is not accessible in python + for name in self._depend_on_existence: + if name in kw: + if kw.get(name): + return False + name = "not" + name + if name in kw: + if not kw.get(name): + return False + return True + + _statcache: Stat + + def _stat(self) -> Stat: + try: + return self._statcache + except AttributeError: + try: + self._statcache = self.path.stat() + except error.ELOOP: + self._statcache = self.path.lstat() + return self._statcache + + def dir(self): + return S_ISDIR(self._stat().mode) + + def file(self): + return S_ISREG(self._stat().mode) + + def exists(self): + return self._stat() + + def link(self): + st = self.path.lstat() + return S_ISLNK(st.mode) + + +class NeverRaised(Exception): + pass + + +class Visitor: + def __init__(self, fil, rec, ignore, bf, sort): + if isinstance(fil, str): + fil = FNMatcher(fil) + if isinstance(rec, str): + self.rec: Callable[[LocalPath], bool] = FNMatcher(rec) + elif not hasattr(rec, "__call__") and rec: + self.rec = lambda path: True + else: + self.rec = rec + self.fil = fil + self.ignore = ignore + self.breadthfirst = bf + self.optsort = cast(Callable[[Any], Any], sorted) if sort else (lambda x: x) + + def gen(self, path): + try: + entries = path.listdir() + except self.ignore: + return + rec = self.rec + dirs = self.optsort( + [p for p in entries if p.check(dir=1) and (rec is None or rec(p))] + ) + if not self.breadthfirst: + for subdir in dirs: + for p in self.gen(subdir): + yield p + for p in self.optsort(entries): + if self.fil is None or self.fil(p): + yield p + if self.breadthfirst: + for subdir in dirs: + for p in self.gen(subdir): + yield p + + +class FNMatcher: + def __init__(self, pattern): + self.pattern = pattern + + def __call__(self, path): + pattern = self.pattern + + if ( + pattern.find(path.sep) == -1 + and iswin32 + and pattern.find(posixpath.sep) != -1 + ): + # Running on Windows, the pattern has no Windows path separators, + # and the pattern has one or more Posix path separators. Replace + # the Posix path separators with the Windows path separator. + pattern = pattern.replace(posixpath.sep, path.sep) + + if pattern.find(path.sep) == -1: + name = path.basename + else: + name = str(path) # path.strpath # XXX svn? + if not os.path.isabs(pattern): + pattern = "*" + path.sep + pattern + return fnmatch.fnmatch(name, pattern) + + +def map_as_list(func, iter): + return list(map(func, iter)) + + +class Stat: + if TYPE_CHECKING: + + @property + def size(self) -> int: ... + + @property + def mtime(self) -> float: ... + + def __getattr__(self, name: str) -> Any: + return getattr(self._osstatresult, "st_" + name) + + def __init__(self, path, osstatresult): + self.path = path + self._osstatresult = osstatresult + + @property + def owner(self): + if iswin32: + raise NotImplementedError("XXX win32") + import pwd + + entry = error.checked_call(pwd.getpwuid, self.uid) # type:ignore[attr-defined,unused-ignore] + return entry[0] + + @property + def group(self): + """Return group name of file.""" + if iswin32: + raise NotImplementedError("XXX win32") + import grp + + entry = error.checked_call(grp.getgrgid, self.gid) # type:ignore[attr-defined,unused-ignore] + return entry[0] + + def isdir(self): + return S_ISDIR(self._osstatresult.st_mode) + + def isfile(self): + return S_ISREG(self._osstatresult.st_mode) + + def islink(self): + self.path.lstat() + return S_ISLNK(self._osstatresult.st_mode) + + +def getuserid(user): + import pwd + + if not isinstance(user, int): + user = pwd.getpwnam(user)[2] # type:ignore[attr-defined,unused-ignore] + return user + + +def getgroupid(group): + import grp + + if not isinstance(group, int): + group = grp.getgrnam(group)[2] # type:ignore[attr-defined,unused-ignore] + return group + + +class LocalPath: + """Object oriented interface to os.path and other local filesystem + related information. + """ + + class ImportMismatchError(ImportError): + """raised on pyimport() if there is a mismatch of __file__'s""" + + sep = os.sep + + def __init__(self, path=None, expanduser=False): + """Initialize and return a local Path instance. + + Path can be relative to the current directory. + If path is None it defaults to the current working directory. + If expanduser is True, tilde-expansion is performed. + Note that Path instances always carry an absolute path. + Note also that passing in a local path object will simply return + the exact same path object. Use new() to get a new copy. + """ + if path is None: + self.strpath = error.checked_call(os.getcwd) + else: + try: + path = os.fspath(path) + except TypeError: + raise ValueError( + "can only pass None, Path instances " + "or non-empty strings to LocalPath" + ) + if expanduser: + path = os.path.expanduser(path) + self.strpath = abspath(path) + + if sys.platform != "win32": + + def chown(self, user, group, rec=0): + """Change ownership to the given user and group. + user and group may be specified by a number or + by a name. if rec is True change ownership + recursively. + """ + uid = getuserid(user) + gid = getgroupid(group) + if rec: + for x in self.visit(rec=lambda x: x.check(link=0)): + if x.check(link=0): + error.checked_call(os.chown, str(x), uid, gid) + error.checked_call(os.chown, str(self), uid, gid) + + def readlink(self) -> str: + """Return value of a symbolic link.""" + # https://github.com/python/mypy/issues/12278 + return error.checked_call(os.readlink, self.strpath) # type: ignore[arg-type,return-value,unused-ignore] + + def mklinkto(self, oldname): + """Posix style hard link to another name.""" + error.checked_call(os.link, str(oldname), str(self)) + + def mksymlinkto(self, value, absolute=1): + """Create a symbolic link with the given value (pointing to another name).""" + if absolute: + error.checked_call(os.symlink, str(value), self.strpath) + else: + base = self.common(value) + # with posix local paths '/' is always a common base + relsource = self.__class__(value).relto(base) + reldest = self.relto(base) + n = reldest.count(self.sep) + target = self.sep.join(("..",) * n + (relsource,)) + error.checked_call(os.symlink, target, self.strpath) + + def __div__(self, other): + return self.join(os.fspath(other)) + + __truediv__ = __div__ # py3k + + @property + def basename(self): + """Basename part of path.""" + return self._getbyspec("basename")[0] + + @property + def dirname(self): + """Dirname part of path.""" + return self._getbyspec("dirname")[0] + + @property + def purebasename(self): + """Pure base name of the path.""" + return self._getbyspec("purebasename")[0] + + @property + def ext(self): + """Extension of the path (including the '.').""" + return self._getbyspec("ext")[0] + + def read_binary(self): + """Read and return a bytestring from reading the path.""" + with self.open("rb") as f: + return f.read() + + def read_text(self, encoding): + """Read and return a Unicode string from reading the path.""" + with self.open("r", encoding=encoding) as f: + return f.read() + + def read(self, mode="r"): + """Read and return a bytestring from reading the path.""" + with self.open(mode) as f: + return f.read() + + def readlines(self, cr=1): + """Read and return a list of lines from the path. if cr is False, the + newline will be removed from the end of each line.""" + mode = "r" + + if not cr: + content = self.read(mode) + return content.split("\n") + else: + f = self.open(mode) + try: + return f.readlines() + finally: + f.close() + + def load(self): + """(deprecated) return object unpickled from self.read()""" + f = self.open("rb") + try: + import pickle + + return error.checked_call(pickle.load, f) + finally: + f.close() + + def move(self, target): + """Move this path to target.""" + if target.relto(self): + raise error.EINVAL(target, "cannot move path into a subdirectory of itself") + try: + self.rename(target) + except error.EXDEV: # invalid cross-device link + self.copy(target) + self.remove() + + def fnmatch(self, pattern): + """Return true if the basename/fullname matches the glob-'pattern'. + + valid pattern characters:: + + * matches everything + ? matches any single character + [seq] matches any character in seq + [!seq] matches any char not in seq + + If the pattern contains a path-separator then the full path + is used for pattern matching and a '*' is prepended to the + pattern. + + if the pattern doesn't contain a path-separator the pattern + is only matched against the basename. + """ + return FNMatcher(pattern)(self) + + def relto(self, relpath): + """Return a string which is the relative part of the path + to the given 'relpath'. + """ + if not isinstance(relpath, (str, LocalPath)): + raise TypeError(f"{relpath!r}: not a string or path object") + strrelpath = str(relpath) + if strrelpath and strrelpath[-1] != self.sep: + strrelpath += self.sep + # assert strrelpath[-1] == self.sep + # assert strrelpath[-2] != self.sep + strself = self.strpath + if sys.platform == "win32" or getattr(os, "_name", None) == "nt": + if os.path.normcase(strself).startswith(os.path.normcase(strrelpath)): + return strself[len(strrelpath) :] + elif strself.startswith(strrelpath): + return strself[len(strrelpath) :] + return "" + + def ensure_dir(self, *args): + """Ensure the path joined with args is a directory.""" + return self.ensure(*args, dir=True) + + def bestrelpath(self, dest): + """Return a string which is a relative path from self + (assumed to be a directory) to dest such that + self.join(bestrelpath) == dest and if not such + path can be determined return dest. + """ + try: + if self == dest: + return os.curdir + base = self.common(dest) + if not base: # can be the case on windows + return str(dest) + self2base = self.relto(base) + reldest = dest.relto(base) + if self2base: + n = self2base.count(self.sep) + 1 + else: + n = 0 + lst = [os.pardir] * n + if reldest: + lst.append(reldest) + target = dest.sep.join(lst) + return target + except AttributeError: + return str(dest) + + def exists(self): + return self.check() + + def isdir(self): + return self.check(dir=1) + + def isfile(self): + return self.check(file=1) + + def parts(self, reverse=False): + """Return a root-first list of all ancestor directories + plus the path itself. + """ + current = self + lst = [self] + while 1: + last = current + current = current.dirpath() + if last == current: + break + lst.append(current) + if not reverse: + lst.reverse() + return lst + + def common(self, other): + """Return the common part shared with the other path + or None if there is no common part. + """ + last = None + for x, y in zip(self.parts(), other.parts()): + if x != y: + return last + last = x + return last + + def __add__(self, other): + """Return new path object with 'other' added to the basename""" + return self.new(basename=self.basename + str(other)) + + def visit(self, fil=None, rec=None, ignore=NeverRaised, bf=False, sort=False): + """Yields all paths below the current one + + fil is a filter (glob pattern or callable), if not matching the + path will not be yielded, defaulting to None (everything is + returned) + + rec is a filter (glob pattern or callable) that controls whether + a node is descended, defaulting to None + + ignore is an Exception class that is ignoredwhen calling dirlist() + on any of the paths (by default, all exceptions are reported) + + bf if True will cause a breadthfirst search instead of the + default depthfirst. Default: False + + sort if True will sort entries within each directory level. + """ + yield from Visitor(fil, rec, ignore, bf, sort).gen(self) + + def _sortlist(self, res, sort): + if sort: + if hasattr(sort, "__call__"): + warnings.warn( + DeprecationWarning( + "listdir(sort=callable) is deprecated and breaks on python3" + ), + stacklevel=3, + ) + res.sort(sort) + else: + res.sort() + + def __fspath__(self): + return self.strpath + + def __hash__(self): + s = self.strpath + if iswin32: + s = s.lower() + return hash(s) + + def __eq__(self, other): + s1 = os.fspath(self) + try: + s2 = os.fspath(other) + except TypeError: + return False + if iswin32: + s1 = s1.lower() + try: + s2 = s2.lower() + except AttributeError: + return False + return s1 == s2 + + def __ne__(self, other): + return not (self == other) + + def __lt__(self, other): + return os.fspath(self) < os.fspath(other) + + def __gt__(self, other): + return os.fspath(self) > os.fspath(other) + + def samefile(self, other): + """Return True if 'other' references the same file as 'self'.""" + other = os.fspath(other) + if not isabs(other): + other = abspath(other) + if self == other: + return True + if not hasattr(os.path, "samefile"): + return False + return error.checked_call(os.path.samefile, self.strpath, other) + + def remove(self, rec=1, ignore_errors=False): + """Remove a file or directory (or a directory tree if rec=1). + if ignore_errors is True, errors while removing directories will + be ignored. + """ + if self.check(dir=1, link=0): + if rec: + # force remove of readonly files on windows + if iswin32: + self.chmod(0o700, rec=1) + import shutil + + error.checked_call( + shutil.rmtree, self.strpath, ignore_errors=ignore_errors + ) + else: + error.checked_call(os.rmdir, self.strpath) + else: + if iswin32: + self.chmod(0o700) + error.checked_call(os.remove, self.strpath) + + def computehash(self, hashtype="md5", chunksize=524288): + """Return hexdigest of hashvalue for this file.""" + try: + try: + import hashlib as mod + except ImportError: + if hashtype == "sha1": + hashtype = "sha" + mod = __import__(hashtype) + hash = getattr(mod, hashtype)() + except (AttributeError, ImportError): + raise ValueError(f"Don't know how to compute {hashtype!r} hash") + f = self.open("rb") + try: + while 1: + buf = f.read(chunksize) + if not buf: + return hash.hexdigest() + hash.update(buf) + finally: + f.close() + + def new(self, **kw): + """Create a modified version of this path. + the following keyword arguments modify various path parts:: + + a:/some/path/to/a/file.ext + xx drive + xxxxxxxxxxxxxxxxx dirname + xxxxxxxx basename + xxxx purebasename + xxx ext + """ + obj = object.__new__(self.__class__) + if not kw: + obj.strpath = self.strpath + return obj + drive, dirname, basename, purebasename, ext = self._getbyspec( + "drive,dirname,basename,purebasename,ext" + ) + if "basename" in kw: + if "purebasename" in kw or "ext" in kw: + raise ValueError("invalid specification %r" % kw) + else: + pb = kw.setdefault("purebasename", purebasename) + try: + ext = kw["ext"] + except KeyError: + pass + else: + if ext and not ext.startswith("."): + ext = "." + ext + kw["basename"] = pb + ext + + if "dirname" in kw and not kw["dirname"]: + kw["dirname"] = drive + else: + kw.setdefault("dirname", dirname) + kw.setdefault("sep", self.sep) + obj.strpath = normpath("{dirname}{sep}{basename}".format(**kw)) + return obj + + def _getbyspec(self, spec: str) -> list[str]: + """See new for what 'spec' can be.""" + res = [] + parts = self.strpath.split(self.sep) + + args = filter(None, spec.split(",")) + for name in args: + if name == "drive": + res.append(parts[0]) + elif name == "dirname": + res.append(self.sep.join(parts[:-1])) + else: + basename = parts[-1] + if name == "basename": + res.append(basename) + else: + i = basename.rfind(".") + if i == -1: + purebasename, ext = basename, "" + else: + purebasename, ext = basename[:i], basename[i:] + if name == "purebasename": + res.append(purebasename) + elif name == "ext": + res.append(ext) + else: + raise ValueError("invalid part specification %r" % name) + return res + + def dirpath(self, *args, **kwargs): + """Return the directory path joined with any given path arguments.""" + if not kwargs: + path = object.__new__(self.__class__) + path.strpath = dirname(self.strpath) + if args: + path = path.join(*args) + return path + return self.new(basename="").join(*args, **kwargs) + + def join(self, *args: os.PathLike[str], abs: bool = False) -> LocalPath: + """Return a new path by appending all 'args' as path + components. if abs=1 is used restart from root if any + of the args is an absolute path. + """ + sep = self.sep + strargs = [os.fspath(arg) for arg in args] + strpath = self.strpath + if abs: + newargs: list[str] = [] + for arg in reversed(strargs): + if isabs(arg): + strpath = arg + strargs = newargs + break + newargs.insert(0, arg) + # special case for when we have e.g. strpath == "/" + actual_sep = "" if strpath.endswith(sep) else sep + for arg in strargs: + arg = arg.strip(sep) + if iswin32: + # allow unix style paths even on windows. + arg = arg.strip("/") + arg = arg.replace("/", sep) + strpath = strpath + actual_sep + arg + actual_sep = sep + obj = object.__new__(self.__class__) + obj.strpath = normpath(strpath) + return obj + + def open(self, mode="r", ensure=False, encoding=None): + """Return an opened file with the given mode. + + If ensure is True, create parent directories if needed. + """ + if ensure: + self.dirpath().ensure(dir=1) + if encoding: + return error.checked_call( + io.open, + self.strpath, + mode, + encoding=encoding, + ) + return error.checked_call(open, self.strpath, mode) + + def _fastjoin(self, name): + child = object.__new__(self.__class__) + child.strpath = self.strpath + self.sep + name + return child + + def islink(self): + return islink(self.strpath) + + def check(self, **kw): + """Check a path for existence and properties. + + Without arguments, return True if the path exists, otherwise False. + + valid checkers:: + + file = 1 # is a file + file = 0 # is not a file (may not even exist) + dir = 1 # is a dir + link = 1 # is a link + exists = 1 # exists + + You can specify multiple checker definitions, for example:: + + path.check(file=1, link=1) # a link pointing to a file + """ + if not kw: + return exists(self.strpath) + if len(kw) == 1: + if "dir" in kw: + return not kw["dir"] ^ isdir(self.strpath) + if "file" in kw: + return not kw["file"] ^ isfile(self.strpath) + if not kw: + kw = {"exists": 1} + return Checkers(self)._evaluate(kw) + + _patternchars = set("*?[" + os.sep) + + def listdir(self, fil=None, sort=None): + """List directory contents, possibly filter by the given fil func + and possibly sorted. + """ + if fil is None and sort is None: + names = error.checked_call(os.listdir, self.strpath) + return map_as_list(self._fastjoin, names) + if isinstance(fil, str): + if not self._patternchars.intersection(fil): + child = self._fastjoin(fil) + if exists(child.strpath): + return [child] + return [] + fil = FNMatcher(fil) + names = error.checked_call(os.listdir, self.strpath) + res = [] + for name in names: + child = self._fastjoin(name) + if fil is None or fil(child): + res.append(child) + self._sortlist(res, sort) + return res + + def size(self) -> int: + """Return size of the underlying file object""" + return self.stat().size + + def mtime(self) -> float: + """Return last modification time of the path.""" + return self.stat().mtime + + def copy(self, target, mode=False, stat=False): + """Copy path to target. + + If mode is True, will copy permission from path to target. + If stat is True, copy permission, last modification + time, last access time, and flags from path to target. + """ + if self.check(file=1): + if target.check(dir=1): + target = target.join(self.basename) + assert self != target + copychunked(self, target) + if mode: + copymode(self.strpath, target.strpath) + if stat: + copystat(self, target) + else: + + def rec(p): + return p.check(link=0) + + for x in self.visit(rec=rec): + relpath = x.relto(self) + newx = target.join(relpath) + newx.dirpath().ensure(dir=1) + if x.check(link=1): + newx.mksymlinkto(x.readlink()) + continue + elif x.check(file=1): + copychunked(x, newx) + elif x.check(dir=1): + newx.ensure(dir=1) + if mode: + copymode(x.strpath, newx.strpath) + if stat: + copystat(x, newx) + + def rename(self, target): + """Rename this path to target.""" + target = os.fspath(target) + return error.checked_call(os.rename, self.strpath, target) + + def dump(self, obj, bin=1): + """Pickle object into path location""" + f = self.open("wb") + import pickle + + try: + error.checked_call(pickle.dump, obj, f, bin) + finally: + f.close() + + def mkdir(self, *args): + """Create & return the directory joined with args.""" + p = self.join(*args) + error.checked_call(os.mkdir, os.fspath(p)) + return p + + def write_binary(self, data, ensure=False): + """Write binary data into path. If ensure is True create + missing parent directories. + """ + if ensure: + self.dirpath().ensure(dir=1) + with self.open("wb") as f: + f.write(data) + + def write_text(self, data, encoding, ensure=False): + """Write text data into path using the specified encoding. + If ensure is True create missing parent directories. + """ + if ensure: + self.dirpath().ensure(dir=1) + with self.open("w", encoding=encoding) as f: + f.write(data) + + def write(self, data, mode="w", ensure=False): + """Write data into path. If ensure is True create + missing parent directories. + """ + if ensure: + self.dirpath().ensure(dir=1) + if "b" in mode: + if not isinstance(data, bytes): + raise ValueError("can only process bytes") + else: + if not isinstance(data, str): + if not isinstance(data, bytes): + data = str(data) + else: + data = data.decode(sys.getdefaultencoding()) + f = self.open(mode) + try: + f.write(data) + finally: + f.close() + + def _ensuredirs(self): + parent = self.dirpath() + if parent == self: + return self + if parent.check(dir=0): + parent._ensuredirs() + if self.check(dir=0): + try: + self.mkdir() + except error.EEXIST: + # race condition: file/dir created by another thread/process. + # complain if it is not a dir + if self.check(dir=0): + raise + return self + + def ensure(self, *args, **kwargs): + """Ensure that an args-joined path exists (by default as + a file). if you specify a keyword argument 'dir=True' + then the path is forced to be a directory path. + """ + p = self.join(*args) + if kwargs.get("dir", 0): + return p._ensuredirs() + else: + p.dirpath()._ensuredirs() + if not p.check(file=1): + p.open("wb").close() + return p + + @overload + def stat(self, raising: Literal[True] = ...) -> Stat: ... + + @overload + def stat(self, raising: Literal[False]) -> Stat | None: ... + + def stat(self, raising: bool = True) -> Stat | None: + """Return an os.stat() tuple.""" + if raising: + return Stat(self, error.checked_call(os.stat, self.strpath)) + try: + return Stat(self, os.stat(self.strpath)) + except KeyboardInterrupt: + raise + except Exception: + return None + + def lstat(self) -> Stat: + """Return an os.lstat() tuple.""" + return Stat(self, error.checked_call(os.lstat, self.strpath)) + + def setmtime(self, mtime=None): + """Set modification time for the given path. if 'mtime' is None + (the default) then the file's mtime is set to current time. + + Note that the resolution for 'mtime' is platform dependent. + """ + if mtime is None: + return error.checked_call(os.utime, self.strpath, mtime) + try: + return error.checked_call(os.utime, self.strpath, (-1, mtime)) + except error.EINVAL: + return error.checked_call(os.utime, self.strpath, (self.atime(), mtime)) + + def chdir(self): + """Change directory to self and return old current directory""" + try: + old = self.__class__() + except error.ENOENT: + old = None + error.checked_call(os.chdir, self.strpath) + return old + + @contextmanager + def as_cwd(self): + """ + Return a context manager, which changes to the path's dir during the + managed "with" context. + On __enter__ it returns the old dir, which might be ``None``. + """ + old = self.chdir() + try: + yield old + finally: + if old is not None: + old.chdir() + + def realpath(self): + """Return a new path which contains no symbolic links.""" + return self.__class__(os.path.realpath(self.strpath)) + + def atime(self): + """Return last access time of the path.""" + return self.stat().atime + + def __repr__(self): + return "local(%r)" % self.strpath + + def __str__(self): + """Return string representation of the Path.""" + return self.strpath + + def chmod(self, mode, rec=0): + """Change permissions to the given mode. If mode is an + integer it directly encodes the os-specific modes. + if rec is True perform recursively. + """ + if not isinstance(mode, int): + raise TypeError(f"mode {mode!r} must be an integer") + if rec: + for x in self.visit(rec=rec): + error.checked_call(os.chmod, str(x), mode) + error.checked_call(os.chmod, self.strpath, mode) + + def pypkgpath(self): + """Return the Python package path by looking for the last + directory upwards which still contains an __init__.py. + Return None if a pkgpath cannot be determined. + """ + pkgpath = None + for parent in self.parts(reverse=True): + if parent.isdir(): + if not parent.join("__init__.py").exists(): + break + if not isimportable(parent.basename): + break + pkgpath = parent + return pkgpath + + def _ensuresyspath(self, ensuremode, path): + if ensuremode: + s = str(path) + if ensuremode == "append": + if s not in sys.path: + sys.path.append(s) + else: + if s != sys.path[0]: + sys.path.insert(0, s) + + def pyimport(self, modname=None, ensuresyspath=True): + """Return path as an imported python module. + + If modname is None, look for the containing package + and construct an according module name. + The module will be put/looked up in sys.modules. + if ensuresyspath is True then the root dir for importing + the file (taking __init__.py files into account) will + be prepended to sys.path if it isn't there already. + If ensuresyspath=="append" the root dir will be appended + if it isn't already contained in sys.path. + if ensuresyspath is False no modification of syspath happens. + + Special value of ensuresyspath=="importlib" is intended + purely for using in pytest, it is capable only of importing + separate .py files outside packages, e.g. for test suite + without any __init__.py file. It effectively allows having + same-named test modules in different places and offers + mild opt-in via this option. Note that it works only in + recent versions of python. + """ + if not self.check(): + raise error.ENOENT(self) + + if ensuresyspath == "importlib": + if modname is None: + modname = self.purebasename + spec = importlib.util.spec_from_file_location(modname, str(self)) + if spec is None or spec.loader is None: + raise ImportError(f"Can't find module {modname} at location {self!s}") + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + pkgpath = None + if modname is None: + pkgpath = self.pypkgpath() + if pkgpath is not None: + pkgroot = pkgpath.dirpath() + names = self.new(ext="").relto(pkgroot).split(self.sep) + if names[-1] == "__init__": + names.pop() + modname = ".".join(names) + else: + pkgroot = self.dirpath() + modname = self.purebasename + + self._ensuresyspath(ensuresyspath, pkgroot) + __import__(modname) + mod = sys.modules[modname] + if self.basename == "__init__.py": + return mod # we don't check anything as we might + # be in a namespace package ... too icky to check + modfile = mod.__file__ + assert modfile is not None + if modfile[-4:] in (".pyc", ".pyo"): + modfile = modfile[:-1] + elif modfile.endswith("$py.class"): + modfile = modfile[:-9] + ".py" + if modfile.endswith(os.sep + "__init__.py"): + if self.basename != "__init__.py": + modfile = modfile[:-12] + try: + issame = self.samefile(modfile) + except error.ENOENT: + issame = False + if not issame: + ignore = os.getenv("PY_IGNORE_IMPORTMISMATCH") + if ignore != "1": + raise self.ImportMismatchError(modname, modfile, self) + return mod + else: + try: + return sys.modules[modname] + except KeyError: + # we have a custom modname, do a pseudo-import + import types + + mod = types.ModuleType(modname) + mod.__file__ = str(self) + sys.modules[modname] = mod + try: + with open(str(self), "rb") as f: + exec(f.read(), mod.__dict__) + except BaseException: + del sys.modules[modname] + raise + return mod + + def sysexec(self, *argv: os.PathLike[str], **popen_opts: Any) -> str: + """Return stdout text from executing a system child process, + where the 'self' path points to executable. + The process is directly invoked and not through a system shell. + """ + from subprocess import PIPE + from subprocess import Popen + + popen_opts.pop("stdout", None) + popen_opts.pop("stderr", None) + proc = Popen( + [str(self)] + [str(arg) for arg in argv], + **popen_opts, + stdout=PIPE, + stderr=PIPE, + ) + stdout: str | bytes + stdout, stderr = proc.communicate() + ret = proc.wait() + if isinstance(stdout, bytes): + stdout = stdout.decode(sys.getdefaultencoding()) + if ret != 0: + if isinstance(stderr, bytes): + stderr = stderr.decode(sys.getdefaultencoding()) + raise RuntimeError( + ret, + ret, + str(self), + stdout, + stderr, + ) + return stdout + + @classmethod + def sysfind(cls, name, checker=None, paths=None): + """Return a path object found by looking at the systems + underlying PATH specification. If the checker is not None + it will be invoked to filter matching paths. If a binary + cannot be found, None is returned + Note: This is probably not working on plain win32 systems + but may work on cygwin. + """ + if isabs(name): + p = local(name) + if p.check(file=1): + return p + else: + if paths is None: + if iswin32: + paths = os.environ["Path"].split(";") + if "" not in paths and "." not in paths: + paths.append(".") + try: + systemroot = os.environ["SYSTEMROOT"] + except KeyError: + pass + else: + paths = [ + path.replace("%SystemRoot%", systemroot) for path in paths + ] + else: + paths = os.environ["PATH"].split(":") + tryadd = [] + if iswin32: + tryadd += os.environ["PATHEXT"].split(os.pathsep) + tryadd.append("") + + for x in paths: + for addext in tryadd: + p = local(x).join(name, abs=True) + addext + try: + if p.check(file=1): + if checker: + if not checker(p): + continue + return p + except error.EACCES: + pass + return None + + @classmethod + def _gethomedir(cls): + try: + x = os.environ["HOME"] + except KeyError: + try: + x = os.environ["HOMEDRIVE"] + os.environ["HOMEPATH"] + except KeyError: + return None + return cls(x) + + # """ + # special class constructors for local filesystem paths + # """ + @classmethod + def get_temproot(cls): + """Return the system's temporary directory + (where tempfiles are usually created in) + """ + import tempfile + + return local(tempfile.gettempdir()) + + @classmethod + def mkdtemp(cls, rootdir=None): + """Return a Path object pointing to a fresh new temporary directory + (which we created ourselves). + """ + import tempfile + + if rootdir is None: + rootdir = cls.get_temproot() + path = error.checked_call(tempfile.mkdtemp, dir=str(rootdir)) + return cls(path) + + @classmethod + def make_numbered_dir( + cls, prefix="session-", rootdir=None, keep=3, lock_timeout=172800 + ): # two days + """Return unique directory with a number greater than the current + maximum one. The number is assumed to start directly after prefix. + if keep is true directories with a number less than (maxnum-keep) + will be removed. If .lock files are used (lock_timeout non-zero), + algorithm is multi-process safe. + """ + if rootdir is None: + rootdir = cls.get_temproot() + + nprefix = prefix.lower() + + def parse_num(path): + """Parse the number out of a path (if it matches the prefix)""" + nbasename = path.basename.lower() + if nbasename.startswith(nprefix): + try: + return int(nbasename[len(nprefix) :]) + except ValueError: + pass + + def create_lockfile(path): + """Exclusively create lockfile. Throws when failed""" + mypid = os.getpid() + lockfile = path.join(".lock") + if hasattr(lockfile, "mksymlinkto"): + lockfile.mksymlinkto(str(mypid)) + else: + fd = error.checked_call( + os.open, str(lockfile), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644 + ) + with os.fdopen(fd, "w") as f: + f.write(str(mypid)) + return lockfile + + def atexit_remove_lockfile(lockfile): + """Ensure lockfile is removed at process exit""" + mypid = os.getpid() + + def try_remove_lockfile(): + # in a fork() situation, only the last process should + # remove the .lock, otherwise the other processes run the + # risk of seeing their temporary dir disappear. For now + # we remove the .lock in the parent only (i.e. we assume + # that the children finish before the parent). + if os.getpid() != mypid: + return + try: + lockfile.remove() + except error.Error: + pass + + atexit.register(try_remove_lockfile) + + # compute the maximum number currently in use with the prefix + lastmax = None + while True: + maxnum = -1 + for path in rootdir.listdir(): + num = parse_num(path) + if num is not None: + maxnum = max(maxnum, num) + + # make the new directory + try: + udir = rootdir.mkdir(prefix + str(maxnum + 1)) + if lock_timeout: + lockfile = create_lockfile(udir) + atexit_remove_lockfile(lockfile) + except (error.EEXIST, error.ENOENT, error.EBUSY): + # race condition (1): another thread/process created the dir + # in the meantime - try again + # race condition (2): another thread/process spuriously acquired + # lock treating empty directory as candidate + # for removal - try again + # race condition (3): another thread/process tried to create the lock at + # the same time (happened in Python 3.3 on Windows) + # https://ci.appveyor.com/project/pytestbot/py/build/1.0.21/job/ffi85j4c0lqwsfwa + if lastmax == maxnum: + raise + lastmax = maxnum + continue + break + + def get_mtime(path): + """Read file modification time""" + try: + return path.lstat().mtime + except error.Error: + pass + + garbage_prefix = prefix + "garbage-" + + def is_garbage(path): + """Check if path denotes directory scheduled for removal""" + bn = path.basename + return bn.startswith(garbage_prefix) + + # prune old directories + udir_time = get_mtime(udir) + if keep and udir_time: + for path in rootdir.listdir(): + num = parse_num(path) + if num is not None and num <= (maxnum - keep): + try: + # try acquiring lock to remove directory as exclusive user + if lock_timeout: + create_lockfile(path) + except (error.EEXIST, error.ENOENT, error.EBUSY): + path_time = get_mtime(path) + if not path_time: + # assume directory doesn't exist now + continue + if abs(udir_time - path_time) < lock_timeout: + # assume directory with lockfile exists + # and lock timeout hasn't expired yet + continue + + # path dir locked for exclusive use + # and scheduled for removal to avoid another thread/process + # treating it as a new directory or removal candidate + garbage_path = rootdir.join(garbage_prefix + str(uuid.uuid4())) + try: + path.rename(garbage_path) + garbage_path.remove(rec=1) + except KeyboardInterrupt: + raise + except Exception: # this might be error.Error, WindowsError ... + pass + if is_garbage(path): + try: + path.remove(rec=1) + except KeyboardInterrupt: + raise + except Exception: # this might be error.Error, WindowsError ... + pass + + # make link... + try: + username = os.environ["USER"] # linux, et al + except KeyError: + try: + username = os.environ["USERNAME"] # windows + except KeyError: + username = "current" + + src = str(udir) + dest = src[: src.rfind("-")] + "-" + username + try: + os.unlink(dest) + except OSError: + pass + try: + os.symlink(src, dest) + except (OSError, AttributeError, NotImplementedError): + pass + + return udir + + +def copymode(src, dest): + """Copy permission from src to dst.""" + import shutil + + shutil.copymode(src, dest) + + +def copystat(src, dest): + """Copy permission, last modification time, + last access time, and flags from src to dst.""" + import shutil + + shutil.copystat(str(src), str(dest)) + + +def copychunked(src, dest): + chunksize = 524288 # half a meg of bytes + fsrc = src.open("rb") + try: + fdest = dest.open("wb") + try: + while 1: + buf = fsrc.read(chunksize) + if not buf: + break + fdest.write(buf) + finally: + fdest.close() + finally: + fsrc.close() + + +def isimportable(name): + if name and (name[0].isalpha() or name[0] == "_"): + name = name.replace("_", "") + return not name or name.isalnum() + + +local = LocalPath diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_py/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_py/w3c-import.log new file mode 100644 index 0000000000000..d96491ec28d96 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_py/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_py/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_py/error.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_py/path.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_version.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_version.py index 5515abadad7d3..1df004d997c6d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_version.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_version.py @@ -1,5 +1,5 @@ # coding: utf-8 # file generated by setuptools_scm # don't change, don't track in version control -version = '7.0.1' -version_tuple = (7, 0, 1) +version = '8.2.1' +version_tuple = (8, 2, 1) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/__init__.py index 480a26ad86718..21dd4a4a4bbcc 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/__init__.py @@ -1,4 +1,6 @@ +# mypy: allow-untyped-defs """Support for presenting detailed information in failing assertions.""" + import sys from typing import Any from typing import Generator @@ -15,6 +17,7 @@ from _pytest.config.argparsing import Parser from _pytest.nodes import Item + if TYPE_CHECKING: from _pytest.main import Session @@ -39,9 +42,17 @@ def pytest_addoption(parser: Parser) -> None: "enable_assertion_pass_hook", type="bool", default=False, - help="Enables the pytest_assertion_pass hook." + help="Enables the pytest_assertion_pass hook. " "Make sure to delete any previously generated pyc cache files.", ) + Config._add_verbosity_ini( + parser, + Config.VERBOSITY_ASSERTIONS, + help=( + "Specify a verbosity level for assertions, overriding the main level. " + "Higher levels will provide more detailed explanation when an assertion fails." + ), + ) def register_assert_rewrite(*names: str) -> None: @@ -53,7 +64,7 @@ def register_assert_rewrite(*names: str) -> None: actually imported, usually in your __init__.py if you are a plugin using a package. - :raises TypeError: If the given module names are not strings. + :param names: The module names to register. """ for name in names: if not isinstance(name, str): @@ -112,15 +123,14 @@ def pytest_collection(session: "Session") -> None: assertstate.hook.set_session(session) -@hookimpl(tryfirst=True, hookwrapper=True) -def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: +@hookimpl(wrapper=True, tryfirst=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: """Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks. 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. """ - ihook = item.ihook def callbinrepr(op, left: object, right: object) -> Optional[str]: @@ -162,10 +172,11 @@ def call_assertion_pass_hook(lineno: int, orig: str, expl: str) -> None: util._assertion_pass = call_assertion_pass_hook - yield - - util._reprcompare, util._assertion_pass = saved_assert_hooks - util._config = None + try: + return (yield) + finally: + util._reprcompare, util._assertion_pass = saved_assert_hooks + util._config = None def pytest_sessionfinish(session: "Session") -> None: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/rewrite.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/rewrite.py index 88ac6cab368d1..1e722f2ba1512 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/rewrite.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/rewrite.py @@ -1,5 +1,7 @@ """Rewrite assertion AST to produce nice error messages.""" + import ast +from collections import defaultdict import errno import functools import importlib.abc @@ -9,12 +11,12 @@ import itertools import marshal import os +from pathlib import Path +from pathlib import PurePath import struct import sys import tokenize import types -from pathlib import Path -from pathlib import PurePath from typing import Callable from typing import Dict from typing import IO @@ -32,27 +34,35 @@ 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.config import Config from _pytest.main import Session from _pytest.pathlib import absolutepath from _pytest.pathlib import fnmatch_ex from _pytest.stash import StashKey + +# fmt: off +from _pytest.assertion.util import format_explanation as _format_explanation # noqa:F401, isort:skip +# fmt:on + if TYPE_CHECKING: from _pytest.assertion import AssertionState -assertstate_key = StashKey["AssertionState"]() +class Sentinel: + pass + +assertstate_key = StashKey["AssertionState"]() # pytest caches rewritten pycs in pycache dirs PYTEST_TAG = f"{sys.implementation.cache_tag}-pytest-{version}" PYC_EXT = ".py" + (__debug__ and "c" or "o") PYC_TAIL = "." + PYTEST_TAG + PYC_EXT +# Special marker that denotes we have just left a scope definition +_SCOPE_END_MARKER = Sentinel() + class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader): """PEP302/PEP451 import hook which rewrites asserts.""" @@ -100,9 +110,6 @@ def find_spec( spec is None # this is a namespace package (without `__init__.py`) # there's nothing to rewrite there - # 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) @@ -183,7 +190,7 @@ def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool: for initial_path in self.session._initialpaths: # Make something as c:/projects/my_project/path.py -> # ['c:', 'projects', 'my_project', 'path.py'] - parts = str(initial_path).split(os.path.sep) + parts = str(initial_path).split(os.sep) # add 'path' to basenames to be checked. self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0]) @@ -193,7 +200,7 @@ def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool: return False # For matching the name it must be as if it was a filename. - path = PurePath(os.path.sep.join(parts) + ".py") + path = PurePath(*parts).with_suffix(".py") for pat in self.fnpats: # if the pattern contains subdirectories ("tests/**.py" for example) we can't bail out based @@ -277,8 +284,12 @@ def get_data(self, pathname: Union[str, bytes]) -> bytes: return f.read() if sys.version_info >= (3, 10): + if sys.version_info >= (3, 12): + from importlib.resources.abc import TraversableResources + else: + from importlib.abc import TraversableResources - def get_resource_reader(self, name: str) -> importlib.abc.TraversableResources: # type: ignore + def get_resource_reader(self, name: str) -> TraversableResources: if sys.version_info < (3, 11): from importlib.readers import FileReader else: @@ -295,9 +306,8 @@ def _write_pyc_fp( # import. However, there's little reason to deviate. fp.write(importlib.util.MAGIC_NUMBER) # https://www.python.org/dev/peps/pep-0552/ - if sys.version_info >= (3, 7): - flags = b"\x00\x00\x00\x00" - fp.write(flags) + flags = b"\x00\x00\x00\x00" + fp.write(flags) # 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 @@ -306,54 +316,29 @@ def _write_pyc_fp( 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(os.fspath(pyc), mode="wb", overwrite=True) as fp: - _write_pyc_fp(fp, source_stat, co) - except OSError as e: - state.trace(f"error writing pyc file at {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 - - -else: - - def _write_pyc( - state: "AssertionState", - co: types.CodeType, - source_stat: os.stat_result, - pyc: Path, - ) -> bool: - proc_pyc = f"{pyc}.{os.getpid()}" - try: - fp = open(proc_pyc, "wb") - except OSError as e: - state.trace(f"error writing pyc file at {proc_pyc}: errno={e.errno}") - return False - - try: +def _write_pyc( + state: "AssertionState", + co: types.CodeType, + source_stat: os.stat_result, + pyc: Path, +) -> bool: + proc_pyc = f"{pyc}.{os.getpid()}" + try: + with open(proc_pyc, "wb") as fp: _write_pyc_fp(fp, source_stat, co) - os.rename(proc_pyc, pyc) - except OSError as e: - state.trace(f"error writing pyc file at {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 + except OSError as e: + state.trace(f"error writing pyc file at {proc_pyc}: errno={e.errno}") + return False + + try: + os.replace(proc_pyc, pyc) + except OSError as e: + state.trace(f"error writing pyc file at {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 def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeType]: @@ -379,31 +364,29 @@ def _read_pyc( except OSError: return None with fp: - # https://www.python.org/dev/peps/pep-0552/ - has_flags = sys.version_info >= (3, 7) try: stat_result = os.stat(source) mtime = int(stat_result.st_mtime) size = stat_result.st_size - data = fp.read(16 if has_flags else 12) + data = fp.read(16) except OSError as e: trace(f"_read_pyc({source}): OSError {e}") return None # Check for invalid or out of date pyc file. - if len(data) != (16 if has_flags else 12): + if len(data) != (16): trace("_read_pyc(%s): invalid pyc (too short)" % source) return None if data[:4] != importlib.util.MAGIC_NUMBER: trace("_read_pyc(%s): invalid pyc (bad magic number)" % source) return None - if has_flags and data[4:8] != b"\x00\x00\x00\x00": + if data[4:8] != b"\x00\x00\x00\x00": trace("_read_pyc(%s): invalid pyc (unsupported flags)" % source) return None - mtime_data = data[8 if has_flags else 4 : 12 if has_flags else 8] + mtime_data = data[8:12] if int.from_bytes(mtime_data, "little") != mtime & 0xFFFFFFFF: trace("_read_pyc(%s): out of date" % source) return None - size_data = data[12 if has_flags else 8 : 16 if has_flags else 12] + size_data = data[12:16] if int.from_bytes(size_data, "little") != size & 0xFFFFFFFF: trace("_read_pyc(%s): invalid pyc (incorrect size)" % source) return None @@ -444,7 +427,10 @@ def _saferepr(obj: object) -> str: def _get_maxsize_for_saferepr(config: Optional[Config]) -> Optional[int]: """Get `maxsize` configuration for saferepr based on the given config object.""" - verbosity = config.getoption("verbose") if config is not None else 0 + if config is None: + verbosity = 0 + else: + verbosity = config.get_verbosity(Config.VERBOSITY_ASSERTIONS) if verbosity >= 2: return None if verbosity >= 1: @@ -598,7 +584,7 @@ def _write_and_reset() -> None: # multi-line assert with message elif lineno in seen_lines: lines[-1] = lines[-1][:offset] - # multi line assert with escapd newline before message + # multi line assert with escaped newline before message else: lines.append(line[:offset]) _write_and_reset() @@ -660,8 +646,14 @@ class AssertionRewriter(ast.NodeVisitor): .push_format_context() and .pop_format_context() which allows to build another %-formatted string while already building one. - This state is reset on every new assert statement visited and used - by the other visitors. + :scope: A tuple containing the current scope used for variables_overwrite. + + :variables_overwrite: A dict filled with references to variables + that change value within an assert. This happens when a variable is + reassigned with the walrus operator + + This state, except the variables_overwrite, is reset on every new assert + statement visited and used by the other visitors. """ def __init__( @@ -677,6 +669,10 @@ def __init__( else: self.enable_assertion_pass_hook = False self.source = source + self.scope: tuple[ast.AST, ...] = () + self.variables_overwrite: defaultdict[tuple[ast.AST, ...], Dict[str, str]] = ( + defaultdict(dict) + ) def run(self, mod: ast.Module) -> None: """Find all assert statements in *mod* and rewrite them.""" @@ -691,14 +687,15 @@ def run(self, mod: ast.Module) -> None: if doc is not None and self.is_rewrite_disabled(doc): return pos = 0 - lineno = 1 + item = None for item in mod.body: if ( expect_docstring and isinstance(item, ast.Expr) - and isinstance(item.value, ast.Str) + and isinstance(item.value, ast.Constant) + and isinstance(item.value.value, str) ): - doc = item.value.s + doc = item.value.value if self.is_rewrite_disabled(doc): return expect_docstring = False @@ -739,9 +736,17 @@ def run(self, mod: ast.Module) -> None: mod.body[pos:pos] = imports # Collect asserts. - nodes: List[ast.AST] = [mod] + self.scope = (mod,) + nodes: List[Union[ast.AST, Sentinel]] = [mod] while nodes: node = nodes.pop() + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + self.scope = tuple((*self.scope, node)) + nodes.append(_SCOPE_END_MARKER) + if node == _SCOPE_END_MARKER: + self.scope = self.scope[:-1] + continue + assert isinstance(node, ast.AST) for name, field in ast.iter_fields(node): if isinstance(field, list): new: List[ast.AST] = [] @@ -830,7 +835,7 @@ def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: current = self.stack.pop() if self.stack: self.explanation_specifiers = self.stack[-1] - keys = [ast.Str(key) for key in current.keys()] + keys = [ast.Constant(key) for key in current.keys()] 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)) @@ -854,9 +859,10 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: the expression is false. """ if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1: - from _pytest.warning_types import PytestAssertRewriteWarning import warnings + from _pytest.warning_types import PytestAssertRewriteWarning + # TODO: This assert should not be needed. assert self.module_path is not None warnings.warn_explicit( @@ -884,16 +890,16 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: 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)) + msg = self.pop_format_context(ast.Constant(explanation)) # Failed if assert_.msg: assertmsg = self.helper("_format_assertmsg", assert_.msg) gluestr = "\n>assert " else: - assertmsg = ast.Str("") + assertmsg = ast.Constant("") gluestr = "assert " - err_explanation = ast.BinOp(ast.Str(gluestr), ast.Add(), msg) + err_explanation = ast.BinOp(ast.Constant(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) @@ -909,15 +915,15 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: hook_call_pass = ast.Expr( self.helper( "_call_assertion_pass", - ast.Num(assert_.lineno), - ast.Str(orig), + ast.Constant(assert_.lineno), + ast.Constant(orig), fmt_pass, ) ) # 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], + [*self.expl_stmts, hook_call_pass], [], ) statements_pass = [hook_impl_test] @@ -929,7 +935,7 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: variables = [ ast.Name(name, ast.Store()) for name in self.format_variables ] - clear_format = ast.Assign(variables, ast.NameConstant(None)) + clear_format = ast.Assign(variables, ast.Constant(None)) self.statements.append(clear_format) else: # Original assertion rewriting @@ -940,9 +946,9 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: assertmsg = self.helper("_format_assertmsg", assert_.msg) explanation = "\n>assert " + explanation else: - assertmsg = ast.Str("") + assertmsg = ast.Constant("") explanation = "assert " + explanation - template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation)) + template = ast.BinOp(assertmsg, ast.Add(), ast.Constant(explanation)) msg = self.pop_format_context(template) fmt = self.helper("_format_explanation", msg) err_name = ast.Name("AssertionError", ast.Load()) @@ -954,7 +960,7 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: # 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, ast.NameConstant(None)) + clear = ast.Assign(variables, ast.Constant(None)) self.statements.append(clear) # Fix locations (line numbers/column offsets). for stmt in self.statements: @@ -962,14 +968,26 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: ast.copy_location(node, assert_) return self.statements + def visit_NamedExpr(self, name: ast.NamedExpr) -> Tuple[ast.NamedExpr, str]: + # This method handles the 'walrus operator' repr of the target + # name if it's a local variable or _should_repr_global_name() + # thinks it's acceptable. + locs = ast.Call(self.builtin("locals"), [], []) + target_id = name.target.id + inlocs = ast.Compare(ast.Constant(target_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.Constant(target_id)) + return name, self.explanation_param(expr) + 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"), [], []) - inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs]) + inlocs = ast.Compare(ast.Constant(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)) + expr = ast.IfExp(test, self.display(name), ast.Constant(name.id)) return name, self.explanation_param(expr) def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: @@ -986,12 +1004,26 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: if i: fail_inner: List[ast.stmt] = [] # cond is set in a prior loop iteration below - self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa + self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa: F821 self.expl_stmts = fail_inner + # Check if the left operand is a ast.NamedExpr and the value has already been visited + if ( + isinstance(v, ast.Compare) + and isinstance(v.left, ast.NamedExpr) + and v.left.target.id + in [ + ast_expr.id + for ast_expr in boolop.values[:i] + if hasattr(ast_expr, "id") + ] + ): + pytest_temp = self.variable() + self.variables_overwrite[self.scope][v.left.target.id] = v.left # type:ignore[assignment] + v.left.target.id = pytest_temp 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)) + expl_format = self.pop_format_context(ast.Constant(expl)) call = ast.Call(app, [expl_format], []) self.expl_stmts.append(ast.Expr(call)) if i < levels: @@ -1003,7 +1035,7 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: self.statements = body = inner self.statements = save self.expl_stmts = fail_save - expl_template = self.helper("_format_boolop", expl_list, ast.Num(is_or)) + expl_template = self.helper("_format_boolop", expl_list, ast.Constant(is_or)) expl = self.pop_format_context(expl_template) return ast.Name(res_var, ast.Load()), self.explanation_param(expl) @@ -1027,10 +1059,18 @@ def visit_Call(self, call: ast.Call) -> Tuple[ast.Name, str]: new_args = [] new_kwargs = [] for arg in call.args: + if isinstance(arg, ast.Name) and arg.id in self.variables_overwrite.get( + self.scope, {} + ): + arg = self.variables_overwrite[self.scope][arg.id] # type:ignore[assignment] res, expl = self.visit(arg) arg_expls.append(expl) new_args.append(res) for keyword in call.keywords: + if isinstance( + keyword.value, ast.Name + ) and keyword.value.id in self.variables_overwrite.get(self.scope, {}): + keyword.value = self.variables_overwrite[self.scope][keyword.value.id] # type:ignore[assignment] res, expl = self.visit(keyword.value) new_kwargs.append(ast.keyword(keyword.arg, res)) if keyword.arg: @@ -1063,6 +1103,13 @@ def visit_Attribute(self, attr: ast.Attribute) -> Tuple[ast.Name, str]: def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: self.push_format_context() + # We first check if we have overwritten a variable in the previous assert + if isinstance( + comp.left, ast.Name + ) and comp.left.id in self.variables_overwrite.get(self.scope, {}): + comp.left = self.variables_overwrite[self.scope][comp.left.id] # type:ignore[assignment] + if isinstance(comp.left, ast.NamedExpr): + self.variables_overwrite[self.scope][comp.left.target.id] = comp.left # type:ignore[assignment] left_res, left_expl = self.visit(comp.left) if isinstance(comp.left, (ast.Compare, ast.BoolOp)): left_expl = f"({left_expl})" @@ -1074,14 +1121,21 @@ def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: syms = [] results = [left_res] for i, op, next_operand in it: + if ( + isinstance(next_operand, ast.NamedExpr) + and isinstance(left_res, ast.Name) + and next_operand.target.id == left_res.id + ): + next_operand.target.id = self.variable() + self.variables_overwrite[self.scope][left_res.id] = next_operand # type:ignore[assignment] next_res, next_expl = self.visit(next_operand) if isinstance(next_operand, (ast.Compare, ast.BoolOp)): next_expl = f"({next_expl})" results.append(next_res) sym = BINOP_MAP[op.__class__] - syms.append(ast.Str(sym)) + syms.append(ast.Constant(sym)) expl = f"{left_expl} {sym} {next_expl}" - expls.append(ast.Str(expl)) + expls.append(ast.Constant(expl)) res_expr = ast.Compare(left_res, [op], [next_res]) self.statements.append(ast.Assign([store_names[i]], res_expr)) left_res, left_expl = next_res, next_expl @@ -1097,6 +1151,7 @@ def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: res: ast.expr = ast.BoolOp(ast.And(), load_names) else: res = load_names[0] + return res, self.explanation_param(self.pop_format_context(expl_call)) @@ -1116,7 +1171,10 @@ def try_makedirs(cache_dir: Path) -> bool: return False except OSError as e: # as of now, EROFS doesn't have an equivalent OSError-subclass - if e.errno == errno.EROFS: + # + # squashfuse_ll returns ENOSYS "OSError: [Errno 38] Function not + # implemented" for a read-only error + if e.errno in {errno.EROFS, errno.ENOSYS}: return False raise return True @@ -1124,7 +1182,7 @@ def try_makedirs(cache_dir: Path) -> bool: 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: + if sys.pycache_prefix: # given: # prefix = '/tmp/pycs' # path = '/home/user/proj/test_app.py' diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/truncate.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/truncate.py index ce148dca095af..4fdfd86a519f9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/truncate.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/truncate.py @@ -1,12 +1,14 @@ """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. +terminal lines, unless running with an assertions verbosity level of at least 2 or running on CI. """ + from typing import List from typing import Optional from _pytest.assertion import util +from _pytest.config import Config from _pytest.nodes import Item @@ -26,7 +28,7 @@ def truncate_if_required( def _should_truncate_item(item: Item) -> bool: """Whether or not this test item is eligible for truncation.""" - verbose = item.config.option.verbose + verbose = item.config.get_verbosity(Config.VERBOSITY_ASSERTIONS) return verbose < 2 and not util.running_on_ci() @@ -38,9 +40,9 @@ def _truncate_explanation( """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. + first, taking the truncation explanation into account. The remaining lines + will be replaced by a usage message. """ - if max_lines is None: max_lines = DEFAULT_MAX_LINES if max_chars is None: @@ -48,35 +50,57 @@ def _truncate_explanation( # Check if truncation required input_char_count = len("".join(input_lines)) - if len(input_lines) <= max_lines and input_char_count <= max_chars: + # The length of the truncation explanation depends on the number of lines + # removed but is at least 68 characters: + # The real value is + # 64 (for the base message: + # '...\n...Full output truncated (1 line hidden), use '-vv' to show")' + # ) + # + 1 (for plural) + # + int(math.log10(len(input_lines) - max_lines)) (number of hidden line, at least 1) + # + 3 for the '...' added to the truncated line + # But if there's more than 100 lines it's very likely that we're going to + # truncate, so we don't need the exact value using log10. + tolerable_max_chars = ( + max_chars + 70 # 64 + 1 (for plural) + 2 (for '99') + 3 for '...' + ) + # The truncation explanation add two lines to the output + tolerable_max_lines = max_lines + 2 + if ( + len(input_lines) <= tolerable_max_lines + and input_char_count <= tolerable_max_chars + ): return input_lines - - # Truncate first to max_lines, and then truncate to max_chars if max_chars - # is exceeded. + # Truncate first to max_lines, and then truncate to max_chars if necessary truncated_explanation = input_lines[:max_lines] - truncated_explanation = _truncate_by_char_count(truncated_explanation, max_chars) - - # Add ellipsis to final line - truncated_explanation[-1] = truncated_explanation[-1] + "..." + truncated_char = True + # We reevaluate the need to truncate chars following removal of some lines + if len("".join(truncated_explanation)) > tolerable_max_chars: + truncated_explanation = _truncate_by_char_count( + truncated_explanation, max_chars + ) + else: + truncated_char = False - # Append useful message to explanation truncated_line_count = len(input_lines) - len(truncated_explanation) - truncated_line_count += 1 # Account for the part-truncated final line - msg = "...Full output truncated" - if truncated_line_count == 1: - msg += f" ({truncated_line_count} line hidden)" + if truncated_explanation[-1]: + # Add ellipsis and take into account part-truncated final line + truncated_explanation[-1] = truncated_explanation[-1] + "..." + if truncated_char: + # It's possible that we did not remove any char from this line + truncated_line_count += 1 else: - msg += f" ({truncated_line_count} lines hidden)" - msg += f", {USAGE_MSG}" - truncated_explanation.extend(["", str(msg)]) - return truncated_explanation + # Add proper ellipsis when we were able to fit a full line exactly + truncated_explanation[-1] = "..." + return [ + *truncated_explanation, + "", + f"...Full output truncated ({truncated_line_count} line" + f"{'' if truncated_line_count == 1 else 's'} hidden), {USAGE_MSG}", + ] 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 - # Find point at which input length exceeds total allowed length iterated_char_count = 0 for iterated_index, input_line in enumerate(input_lines): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/util.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/util.py index 19f1089c20aa1..e49c42cfcf744 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/util.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/util.py @@ -1,4 +1,6 @@ +# mypy: allow-untyped-defs """Utilities for assertion debugging.""" + import collections.abc import os import pprint @@ -7,17 +9,21 @@ from typing import Callable from typing import Iterable from typing import List +from typing import Literal from typing import Mapping from typing import Optional +from typing import Protocol from typing import Sequence +from unicodedata import normalize -import _pytest._code from _pytest import outcomes -from _pytest._io.saferepr import _pformat_dispatch -from _pytest._io.saferepr import safeformat +import _pytest._code +from _pytest._io.pprint import PrettyPrinter from _pytest._io.saferepr import saferepr +from _pytest._io.saferepr import saferepr_unlimited from _pytest.config import Config + # The _reprcompare attribute on the util module is used by the new assertion # interpretation code and assertion rewriter to detect this plugin was # loaded and in turn call the hooks defined here as part of the @@ -32,6 +38,11 @@ _config: Optional[Config] = None +class _HighlightFunc(Protocol): + def __call__(self, source: str, lexer: Literal["diff", "python"] = "python") -> str: + """Apply highlighting to the given source.""" + + def format_explanation(explanation: str) -> str: r"""Format an explanation. @@ -131,51 +142,104 @@ def isiterable(obj: Any) -> bool: try: iter(obj) return not istext(obj) - except TypeError: + except Exception: return False -def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[str]]: +def has_default_eq( + obj: object, +) -> bool: + """Check if an instance of an object contains the default eq + + First, we check if the object's __eq__ attribute has __code__, + if so, we check the equally of the method code filename (__code__.co_filename) + to the default one generated by the dataclass and attr module + for dataclasses the default co_filename is , for attrs class, the __eq__ should contain "attrs eq generated" + """ + # inspired from https://github.com/willmcgugan/rich/blob/07d51ffc1aee6f16bd2e5a25b4e82850fb9ed778/rich/pretty.py#L68 + if hasattr(obj.__eq__, "__code__") and hasattr(obj.__eq__.__code__, "co_filename"): + code_filename = obj.__eq__.__code__.co_filename + + if isattrs(obj): + return "attrs generated eq" in code_filename + + return code_filename == "" # data class + return True + + +def assertrepr_compare( + config, op: str, left: Any, right: Any, use_ascii: bool = False +) -> Optional[List[str]]: """Return specialised explanations for some operators/operands.""" - verbose = config.getoption("verbose") + verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS) + + # Strings which normalize equal are often hard to distinguish when printed; use ascii() to make this easier. + # See issue #3246. + use_ascii = ( + isinstance(left, str) + and isinstance(right, str) + and normalize("NFD", left) == normalize("NFD", right) + ) + if verbose > 1: - left_repr = safeformat(left) - right_repr = safeformat(right) + left_repr = saferepr_unlimited(left, use_ascii=use_ascii) + right_repr = saferepr_unlimited(right, use_ascii=use_ascii) 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) + + left_repr = saferepr(left, maxsize=maxsize, use_ascii=use_ascii) + right_repr = saferepr(right, maxsize=maxsize, use_ascii=use_ascii) summary = f"{left_repr} {op} {right_repr}" + highlighter = config.get_terminal_writer()._highlight explanation = None try: if op == "==": - explanation = _compare_eq_any(left, right, verbose) + explanation = _compare_eq_any(left, right, highlighter, verbose) elif op == "not in": if istext(left) and istext(right): explanation = _notin_text(left, right, verbose) + elif op == "!=": + if isset(left) and isset(right): + explanation = ["Both sets are equal"] + elif op == ">=": + if isset(left) and isset(right): + explanation = _compare_gte_set(left, right, highlighter, verbose) + elif op == "<=": + if isset(left) and isset(right): + explanation = _compare_lte_set(left, right, highlighter, verbose) + elif op == ">": + if isset(left) and isset(right): + explanation = _compare_gt_set(left, right, highlighter, verbose) + elif op == "<": + if isset(left) and isset(right): + explanation = _compare_lt_set(left, right, highlighter, verbose) + except outcomes.Exit: raise except Exception: + repr_crash = _pytest._code.ExceptionInfo.from_current()._getreprcrash() explanation = [ - "(pytest_assertion plugin: representation of details failed: {}.".format( - _pytest._code.ExceptionInfo.from_current()._getreprcrash() - ), + f"(pytest_assertion plugin: representation of details failed: {repr_crash}.", " Probably an object has a faulty __repr__.)", ] if not explanation: return None - return [summary] + explanation + if explanation[0] != "": + explanation = ["", *explanation] + return [summary, *explanation] -def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]: +def _compare_eq_any( + left: Any, right: Any, highlighter: _HighlightFunc, verbose: int = 0 +) -> List[str]: explanation = [] if istext(left) and istext(right): explanation = _diff_text(left, right, verbose) @@ -188,25 +252,23 @@ def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]: other_side = right if isinstance(left, ApproxBase) else left explanation = approx_side._repr_compare(other_side) - elif type(left) == type(right) and ( + elif type(left) is type(right) and ( isdatacls(left) or isattrs(left) or isnamedtuple(left) ): # Note: unlike dataclasses/attrs, namedtuples compare only the # field values, not the type or field names. But this branch # intentionally only handles the same-type case, which was often # used in older code bases before dataclasses/attrs were available. - explanation = _compare_eq_cls(left, right, verbose) + explanation = _compare_eq_cls(left, right, highlighter, verbose) elif issequence(left) and issequence(right): - explanation = _compare_eq_sequence(left, right, verbose) + explanation = _compare_eq_sequence(left, right, highlighter, verbose) elif isset(left) and isset(right): - explanation = _compare_eq_set(left, right, verbose) + explanation = _compare_eq_set(left, right, highlighter, verbose) elif isdict(left) and isdict(right): - explanation = _compare_eq_dict(left, right, verbose) - elif verbose > 0: - explanation = _compare_eq_verbose(left, right) + explanation = _compare_eq_dict(left, right, highlighter, verbose) if isiterable(left) and isiterable(right): - expl = _compare_eq_iterable(left, right, verbose) + expl = _compare_eq_iterable(left, right, highlighter, verbose) explanation.extend(expl) return explanation @@ -241,8 +303,8 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: if i > 42: i -= 10 # Provide some context explanation += [ - "Skipping {} identical trailing " - "characters in diff, use -v to show".format(i) + f"Skipping {i} identical trailing " + "characters in diff, use -v to show" ] left = left[:-i] right = right[:-i] @@ -260,63 +322,40 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: return explanation -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: List[str] = [] - explanation += ["+" + line for line in left_lines] - explanation += ["-" + line for line in right_lines] - - return explanation - - -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 + left: Iterable[Any], + right: Iterable[Any], + highlighter: _HighlightFunc, + verbose: int = 0, ) -> List[str]: - if not verbose and not running_on_ci(): - return ["Use -v to get the full diff"] + if verbose <= 0 and not running_on_ci(): + return ["Use -v to get more diff"] # dynamic import to speedup pytest import difflib - 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) + left_formatting = PrettyPrinter().pformat(left).splitlines() + right_formatting = PrettyPrinter().pformat(right).splitlines() - explanation = ["Full diff:"] + 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.rstrip() for line in difflib.ndiff(right_formatting, left_formatting) + highlighter( + "\n".join( + line.rstrip() + for line in difflib.ndiff(right_formatting, left_formatting) + ), + lexer="diff", + ).splitlines() ) return explanation def _compare_eq_sequence( - left: Sequence[Any], right: Sequence[Any], verbose: int = 0 + left: Sequence[Any], + right: Sequence[Any], + highlighter: _HighlightFunc, + verbose: int = 0, ) -> List[str]: comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes) explanation: List[str] = [] @@ -339,7 +378,10 @@ def _compare_eq_sequence( left_value = left[i] right_value = right[i] - explanation += [f"At index {i} diff: {left_value!r} != {right_value!r}"] + explanation.append( + f"At index {i} diff:" + f" {highlighter(repr(left_value))} != {highlighter(repr(right_value))}" + ) break if comparing_bytes: @@ -359,34 +401,91 @@ def _compare_eq_sequence( extra = saferepr(right[len_left]) if len_diff == 1: - explanation += [f"{dir_with_more} contains one more item: {extra}"] + explanation += [ + f"{dir_with_more} contains one more item: {highlighter(extra)}" + ] else: explanation += [ "%s contains %d more items, first extra item: %s" - % (dir_with_more, len_diff, extra) + % (dir_with_more, len_diff, highlighter(extra)) ] return explanation def _compare_eq_set( - left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0 + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, ) -> List[str]: explanation = [] - diff_left = left - right - diff_right = right - left - if diff_left: - explanation.append("Extra items in the left set:") - for item in diff_left: - explanation.append(saferepr(item)) - if diff_right: - explanation.append("Extra items in the right set:") - for item in diff_right: - explanation.append(saferepr(item)) + explanation.extend(_set_one_sided_diff("left", left, right, highlighter)) + explanation.extend(_set_one_sided_diff("right", right, left, highlighter)) + return explanation + + +def _compare_gt_set( + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> List[str]: + explanation = _compare_gte_set(left, right, highlighter) + if not explanation: + return ["Both sets are equal"] + return explanation + + +def _compare_lt_set( + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> List[str]: + explanation = _compare_lte_set(left, right, highlighter) + if not explanation: + return ["Both sets are equal"] + return explanation + + +def _compare_gte_set( + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> List[str]: + return _set_one_sided_diff("right", right, left, highlighter) + + +def _compare_lte_set( + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> List[str]: + return _set_one_sided_diff("left", left, right, highlighter) + + +def _set_one_sided_diff( + posn: str, + set1: AbstractSet[Any], + set2: AbstractSet[Any], + highlighter: _HighlightFunc, +) -> List[str]: + explanation = [] + diff = set1 - set2 + if diff: + explanation.append(f"Extra items in the {posn} set:") + for item in diff: + explanation.append(highlighter(saferepr(item))) return explanation def _compare_eq_dict( - left: Mapping[Any, Any], right: Mapping[Any, Any], verbose: int = 0 + left: Mapping[Any, Any], + right: Mapping[Any, Any], + highlighter: _HighlightFunc, + verbose: int = 0, ) -> List[str]: explanation: List[str] = [] set_left = set(left) @@ -397,12 +496,16 @@ def _compare_eq_dict( explanation += ["Omitting %s identical items, use -vv to show" % len(same)] elif same: explanation += ["Common items:"] - explanation += pprint.pformat(same).splitlines() + explanation += highlighter(pprint.pformat(same)).splitlines() diff = {k for k in common if left[k] != right[k]} if diff: explanation += ["Differing items:"] for k in diff: - explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})] + explanation += [ + highlighter(saferepr({k: left[k]})) + + " != " + + highlighter(saferepr({k: right[k]})) + ] extra_left = set_left - set_right len_extra_left = len(extra_left) if len_extra_left: @@ -411,7 +514,7 @@ def _compare_eq_dict( % (len_extra_left, "" if len_extra_left == 1 else "s") ) explanation.extend( - pprint.pformat({k: left[k] for k in extra_left}).splitlines() + highlighter(pprint.pformat({k: left[k] for k in extra_left})).splitlines() ) extra_right = set_right - set_left len_extra_right = len(extra_right) @@ -421,15 +524,21 @@ def _compare_eq_dict( % (len_extra_right, "" if len_extra_right == 1 else "s") ) explanation.extend( - pprint.pformat({k: right[k] for k in extra_right}).splitlines() + highlighter(pprint.pformat({k: right[k] for k in extra_right})).splitlines() ) return explanation -def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]: +def _compare_eq_cls( + left: Any, right: Any, highlighter: _HighlightFunc, verbose: int +) -> List[str]: + if not has_default_eq(left): + return [] if isdatacls(left): - all_fields = left.__dataclass_fields__ - fields_to_check = [field for field, info in all_fields.items() if info.compare] + import dataclasses + + all_fields = dataclasses.fields(left) + fields_to_check = [info.name for info in all_fields if info.compare] elif isattrs(left): all_fields = left.__attrs_attrs__ fields_to_check = [field.name for field in all_fields if getattr(field, "eq")] @@ -454,21 +563,23 @@ def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]: explanation.append("Omitting %s identical items, use -vv to show" % len(same)) elif same: explanation += ["Matching attributes:"] - explanation += pprint.pformat(same).splitlines() + explanation += highlighter(pprint.pformat(same)).splitlines() if diff: explanation += ["Differing attributes:"] - explanation += pprint.pformat(diff).splitlines() + explanation += highlighter(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), + f"Drill down into differing attribute {field}:", + f"{indent}{field}: {highlighter(repr(field_left))} != {highlighter(repr(field_right))}", ] explanation += [ indent + line - for line in _compare_eq_any(field_left, field_right, verbose) + for line in _compare_eq_any( + field_left, field_right, highlighter, verbose + ) ] return explanation diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/w3c-import.log new file mode 100644 index 0000000000000..c5db3ba2ee19b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/rewrite.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/truncate.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/util.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/cacheprovider.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/cacheprovider.py index 681d02b4093be..a9cbb77cbbb57 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/cacheprovider.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/cacheprovider.py @@ -1,10 +1,15 @@ +# mypy: allow-untyped-defs """Implementation of the cache provider.""" + # This plugin was not named "cache" to avoid conflicts with the external # pytest-cache version. +import dataclasses import json import os from pathlib import Path +import tempfile from typing import Dict +from typing import final from typing import Generator from typing import Iterable from typing import List @@ -12,14 +17,11 @@ from typing import Set from typing import Union -import attr - from .pathlib import resolve_from_str from .pathlib import rm_rf from .reports import CollectReport from _pytest import nodes from _pytest._io import TerminalWriter -from _pytest.compat import final from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import hookimpl @@ -28,8 +30,8 @@ from _pytest.fixtures import fixture from _pytest.fixtures import FixtureRequest from _pytest.main import Session -from _pytest.python import Module -from _pytest.python import Package +from _pytest.nodes import Directory +from _pytest.nodes import File from _pytest.reports import TestReport @@ -53,10 +55,12 @@ @final -@attr.s(init=False, auto_attribs=True) +@dataclasses.dataclass class Cache: - _cachedir: Path = attr.ib(repr=False) - _config: Config = attr.ib(repr=False) + """Instance of the `cache` fixture.""" + + _cachedir: Path = dataclasses.field(repr=False) + _config: Config = dataclasses.field(repr=False) # Sub-directory under cache-dir for directories created by `mkdir()`. _CACHE_PREFIX_DIRS = "d" @@ -111,6 +115,7 @@ def warn(self, fmt: str, *, _ispytest: bool = False, **args: object) -> None: """ check_ispytest(_ispytest) import warnings + from _pytest.warning_types import PytestCacheWarning warnings.warn( @@ -119,6 +124,10 @@ def warn(self, fmt: str, *, _ispytest: bool = False, **args: object) -> None: stacklevel=3, ) + def _mkdir(self, path: Path) -> None: + self._ensure_cache_dir_and_supporting_files() + path.mkdir(exist_ok=True, parents=True) + def mkdir(self, name: str) -> Path: """Return a directory path object with the given name. @@ -137,7 +146,7 @@ def mkdir(self, name: str) -> Path: if len(path.parts) > 1: raise ValueError("name is not allowed to contain path separators") res = self._cachedir.joinpath(self._CACHE_PREFIX_DIRS, path) - res.mkdir(exist_ok=True, parents=True) + self._mkdir(res) return res def _getvaluepath(self, key: str) -> Path: @@ -157,7 +166,7 @@ def get(self, key: str, default): """ path = self._getvaluepath(key) try: - with path.open("r") as f: + with path.open("r", encoding="UTF-8") as f: return json.load(f) except (ValueError, OSError): return default @@ -174,36 +183,58 @@ def set(self, key: str, value: object) -> None: """ path = self._getvaluepath(key) try: - if path.parent.is_dir(): - cache_dir_exists_already = True - else: - cache_dir_exists_already = self._cachedir.exists() - path.parent.mkdir(exist_ok=True, parents=True) - except OSError: - self.warn("could not create cache path {path}", path=path, _ispytest=True) + self._mkdir(path.parent) + except OSError as exc: + self.warn( + f"could not create cache path {path}: {exc}", + _ispytest=True, + ) return - if not cache_dir_exists_already: - self._ensure_supporting_files() - data = json.dumps(value, indent=2) + data = json.dumps(value, ensure_ascii=False, indent=2) try: - f = path.open("w") - except OSError: - self.warn("cache could not write path {path}", path=path, _ispytest=True) + f = path.open("w", encoding="UTF-8") + except OSError as exc: + self.warn( + f"cache could not write path {path}: {exc}", + _ispytest=True, + ) else: with f: f.write(data) - 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 = "# Created by pytest automatically.\n*\n" - gitignore_path.write_text(msg, encoding="UTF-8") + def _ensure_cache_dir_and_supporting_files(self) -> None: + """Create the cache dir and its supporting files.""" + if self._cachedir.is_dir(): + return - cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG") - cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT) + self._cachedir.parent.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory( + prefix="pytest-cache-files-", + dir=self._cachedir.parent, + ) as newpath: + path = Path(newpath) + + # Reset permissions to the default, see #12308. + # Note: there's no way to get the current umask atomically, eek. + umask = os.umask(0o022) + os.umask(umask) + path.chmod(0o777 - umask) + + with open(path.joinpath("README.md"), "xt", encoding="UTF-8") as f: + f.write(README_CONTENT) + with open(path.joinpath(".gitignore"), "xt", encoding="UTF-8") as f: + f.write("# Created by pytest automatically.\n*\n") + with open(path.joinpath("CACHEDIR.TAG"), "xb") as f: + f.write(CACHEDIR_TAG_CONTENT) + + path.rename(self._cachedir) + # Create a directory in place of the one we just moved so that `TemporaryDirectory`'s + # cleanup doesn't complain. + # + # TODO: pass ignore_cleanup_errors=True when we no longer support python < 3.10. See + # https://github.com/python/cpython/issues/74168. Note that passing delete=False would + # do the wrong thing in case of errors and isn't supported until python 3.12. + path.mkdir() class LFPluginCollWrapper: @@ -211,34 +242,34 @@ def __init__(self, lfplugin: "LFPlugin") -> None: self.lfplugin = lfplugin self._collected_at_least_one_failure = False - @hookimpl(hookwrapper=True) - def pytest_make_collect_report(self, collector: nodes.Collector): - if isinstance(collector, Session): - out = yield - res: CollectReport = out.get_result() - + @hookimpl(wrapper=True) + def pytest_make_collect_report( + self, collector: nodes.Collector + ) -> Generator[None, CollectReport, CollectReport]: + res = yield + if isinstance(collector, (Session, Directory)): # Sort any lf-paths to the beginning. lf_paths = self.lfplugin._last_failed_paths + # Use stable sort to prioritize last failed. + def sort_key(node: Union[nodes.Item, nodes.Collector]) -> bool: + return node.path in lf_paths + res.result = sorted( res.result, - # use stable sort to priorize last failed - key=lambda x: x.path in lf_paths, + key=sort_key, reverse=True, ) - return - elif isinstance(collector, Module): + elif isinstance(collector, File): if collector.path 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 + return res self.lfplugin.config.pluginmanager.register( LFPluginCollSkipfiles(self.lfplugin), "lfplugin-collskip" ) @@ -254,8 +285,8 @@ def pytest_make_collect_report(self, collector: nodes.Collector): # Keep all sub-collectors. or isinstance(x, nodes.Collector) ] - return - yield + + return res class LFPluginCollSkipfiles: @@ -266,10 +297,7 @@ def __init__(self, lfplugin: "LFPlugin") -> None: def pytest_make_collect_report( self, collector: nodes.Collector ) -> Optional[CollectReport]: - # Packages are Modules, but _last_failed_paths only contains - # test-bearing paths and doesn't try to include the paths of their - # packages, so don't filter them. - if isinstance(collector, Module) and not isinstance(collector, Package): + if isinstance(collector, File): if collector.path not in self.lfplugin._last_failed_paths: self.lfplugin._skipped_files += 1 @@ -299,9 +327,14 @@ def __init__(self, config: Config) -> None: ) def get_last_failed_paths(self) -> Set[Path]: - """Return a set with all Paths()s of the previously failed nodeids.""" + """Return a set with all Paths of the previously failed nodeids and + their parents.""" rootpath = self.config.rootpath - result = {rootpath / nodeid.split("::")[0] for nodeid in self.lastfailed} + result = set() + for nodeid in self.lastfailed: + path = rootpath / nodeid.split("::")[0] + result.add(path) + result.update(path.parents) return {x for x in result if x.exists()} def pytest_report_collectionfinish(self) -> Optional[str]: @@ -324,14 +357,14 @@ def pytest_collectreport(self, report: CollectReport) -> None: else: self.lastfailed[report.nodeid] = True - @hookimpl(hookwrapper=True, tryfirst=True) + @hookimpl(wrapper=True, tryfirst=True) def pytest_collection_modifyitems( self, config: Config, items: List[nodes.Item] ) -> Generator[None, None, None]: - yield + res = yield if not self.active: - return + return res if self.lastfailed: previously_failed = [] @@ -358,15 +391,13 @@ def pytest_collection_modifyitems( noun = "failure" if self._previously_failed_count == 1 else "failures" suffix = " first" if self.config.getoption("failedfirst") else "" - self._report_status = "rerun previous {count} {noun}{suffix}".format( - count=self._previously_failed_count, suffix=suffix, noun=noun + self._report_status = ( + f"rerun previous {self._previously_failed_count} {noun}{suffix}" ) if self._skipped_files > 0: files_noun = "file" if self._skipped_files == 1 else "files" - self._report_status += " (skipped {files} {files_noun})".format( - files=self._skipped_files, files_noun=files_noun - ) + self._report_status += f" (skipped {self._skipped_files} {files_noun})" else: self._report_status = "no previously failed tests, " if self.config.getoption("last_failed_no_failures") == "none": @@ -376,6 +407,8 @@ def pytest_collection_modifyitems( else: self._report_status += "not deselecting items." + return res + def pytest_sessionfinish(self, session: Session) -> None: config = self.config if config.getoption("cacheshow") or hasattr(config, "workerinput"): @@ -396,11 +429,11 @@ def __init__(self, config: Config) -> None: assert config.cache is not None self.cached_nodeids = set(config.cache.get("cache/nodeids", [])) - @hookimpl(hookwrapper=True, tryfirst=True) + @hookimpl(wrapper=True, tryfirst=True) def pytest_collection_modifyitems( self, items: List[nodes.Item] ) -> Generator[None, None, None]: - yield + res = yield if self.active: new_items: Dict[str, nodes.Item] = {} @@ -418,8 +451,10 @@ def pytest_collection_modifyitems( else: self.cached_nodeids.update(item.nodeid for item in items) + return res + def _get_increasing_order(self, items: Iterable[nodes.Item]) -> List[nodes.Item]: - return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True) # type: ignore[no-any-return] + return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True) def pytest_sessionfinish(self) -> None: config = self.config @@ -440,7 +475,7 @@ def pytest_addoption(parser: Parser) -> None: "--last-failed", action="store_true", dest="lf", - help="rerun only the tests that failed " + help="Rerun only the tests that failed " "at the last run (or all if none failed)", ) group.addoption( @@ -448,7 +483,7 @@ def pytest_addoption(parser: Parser) -> None: "--failed-first", action="store_true", dest="failedfirst", - help="run all tests, but run the last failures first.\n" + help="Run all tests, but run the last failures first. " "This may re-order tests and thus lead to " "repeated fixture setup/teardown.", ) @@ -457,7 +492,7 @@ def pytest_addoption(parser: Parser) -> None: "--new-first", action="store_true", dest="newfirst", - help="run tests from new files first, then the rest of the tests " + help="Run tests from new files first, then the rest of the tests " "sorted by file mtime", ) group.addoption( @@ -466,7 +501,7 @@ def pytest_addoption(parser: Parser) -> None: nargs="?", dest="cacheshow", help=( - "show cache contents, don't perform collection or tests. " + "Show cache contents, don't perform collection or tests. " "Optional argument: glob (default: '*')." ), ) @@ -474,12 +509,12 @@ def pytest_addoption(parser: Parser) -> None: "--cache-clear", action="store_true", dest="cacheclear", - help="remove all cache contents at start of test run.", + help="Remove all cache contents at start of test run", ) cache_dir_default = ".pytest_cache" if "TOX_ENV_DIR" in os.environ: cache_dir_default = os.path.join(os.environ["TOX_ENV_DIR"], cache_dir_default) - parser.addini("cache_dir", default=cache_dir_default, help="cache directory path.") + parser.addini("cache_dir", default=cache_dir_default, help="Cache directory path") group.addoption( "--lfnf", "--last-failed-no-failures", @@ -487,12 +522,16 @@ def pytest_addoption(parser: Parser) -> None: dest="last_failed_no_failures", choices=("all", "none"), default="all", - help="which tests to run with no previously (known) failures.", + help="With ``--lf``, determines whether to execute tests when there " + "are no previously (known) failures or when no " + "cached ``lastfailed`` data was found. " + "``all`` (the default) runs the full test suite again. " + "``none`` just emits a message about no known failures and exits successfully.", ) def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: - if config.option.cacheshow: + if config.option.cacheshow and not config.option.help: from _pytest.main import wrap_session return wrap_session(config, cacheshow) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/capture.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/capture.py index 884f035e299e6..3f6a2510348dd 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/capture.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/capture.py @@ -1,23 +1,34 @@ +# mypy: allow-untyped-defs """Per-test stdout/stderr capturing mechanism.""" + +import abc +import collections import contextlib -import functools import io +from io import UnsupportedOperation import os import sys -from io import UnsupportedOperation from tempfile import TemporaryFile +from types import TracebackType from typing import Any from typing import AnyStr +from typing import BinaryIO +from typing import Final +from typing import final from typing import Generator from typing import Generic +from typing import Iterable from typing import Iterator +from typing import List +from typing import Literal +from typing import NamedTuple from typing import Optional from typing import TextIO from typing import Tuple +from typing import Type from typing import TYPE_CHECKING from typing import Union -from _pytest.compat import final from _pytest.config import Config from _pytest.config import hookimpl from _pytest.config.argparsing import Parser @@ -27,11 +38,10 @@ from _pytest.nodes import Collector from _pytest.nodes import File from _pytest.nodes import Item +from _pytest.reports import CollectReport -if TYPE_CHECKING: - from typing_extensions import Literal - _CaptureMethod = Literal["fd", "sys", "no", "tee-sys"] +_CaptureMethod = Literal["fd", "sys", "no", "tee-sys"] def pytest_addoption(parser: Parser) -> None: @@ -42,14 +52,14 @@ def pytest_addoption(parser: Parser) -> None: default="fd", metavar="method", choices=["fd", "sys", "no", "tee-sys"], - help="per-test capturing method: one of fd|sys|no|tee-sys.", + help="Per-test capturing method: one of fd|sys|no|tee-sys", ) group._addoption( "-s", action="store_const", const="no", dest="capture", - help="shortcut for --capture=no.", + help="Shortcut for --capture=no", ) @@ -68,8 +78,8 @@ def _colorama_workaround() -> None: pass -def _py36_windowsconsoleio_workaround(stream: TextIO) -> None: - """Workaround for Windows Unicode console handling on Python>=3.6. +def _windowsconsoleio_workaround(stream: TextIO) -> None: + """Workaround for Windows Unicode console handling. Python 3.6 implemented Unicode console handling for Windows. This works by reading/writing to the raw console handle using @@ -96,23 +106,22 @@ def _py36_windowsconsoleio_workaround(stream: TextIO) -> None: return # Bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666). - if not hasattr(stream, "buffer"): # type: ignore[unreachable] + if not hasattr(stream, "buffer"): # type: ignore[unreachable,unused-ignore] return - buffered = hasattr(stream.buffer, "raw") - raw_stdout = stream.buffer.raw if buffered else stream.buffer # type: ignore[attr-defined] + raw_stdout = stream.buffer.raw if hasattr(stream.buffer, "raw") else stream.buffer - if not isinstance(raw_stdout, io._WindowsConsoleIO): # type: ignore[attr-defined] + if not isinstance(raw_stdout, io._WindowsConsoleIO): # type: ignore[attr-defined,unused-ignore] return def _reopen_stdio(f, mode): - if not buffered and mode[0] == "w": + if not hasattr(stream.buffer, "raw") and mode[0] == "w": buffering = 0 else: buffering = -1 return io.TextIOWrapper( - open(os.dup(f.fileno()), mode, buffering), # type: ignore[arg-type] + open(os.dup(f.fileno()), mode, buffering), f.encoding, f.errors, f.newlines, @@ -124,11 +133,11 @@ def _reopen_stdio(f, mode): sys.stderr = _reopen_stdio(sys.stderr, "wb") -@hookimpl(hookwrapper=True) -def pytest_load_initial_conftests(early_config: Config): +@hookimpl(wrapper=True) +def pytest_load_initial_conftests(early_config: Config) -> Generator[None, None, None]: ns = early_config.known_args_namespace if ns.capture == "fd": - _py36_windowsconsoleio_workaround(sys.stdout) + _windowsconsoleio_workaround(sys.stdout) _colorama_workaround() pluginmanager = early_config.pluginmanager capman = CaptureManager(ns.capture) @@ -139,12 +148,16 @@ def pytest_load_initial_conftests(early_config: Config): # Finally trigger conftest loading but while capturing (issue #93). capman.start_global_capturing() - outcome = yield - capman.suspend_global_capture() - if outcome.excinfo is not None: + try: + try: + yield + finally: + capman.suspend_global_capture() + except BaseException: out, err = capman.read_global_capture() sys.stdout.write(out) sys.stderr.write(err) + raise # IO Helpers. @@ -185,53 +198,151 @@ def write(self, s: str) -> int: return self._other.write(s) -class DontReadFromInput: - encoding = None +class DontReadFromInput(TextIO): + @property + def encoding(self) -> str: + return sys.__stdin__.encoding - def read(self, *args): + def read(self, size: int = -1) -> str: raise OSError( "pytest: reading from stdin while output is captured! Consider using `-s`." ) readline = read - readlines = read - __next__ = read - def __iter__(self): + def __next__(self) -> str: + return self.readline() + + def readlines(self, hint: Optional[int] = -1) -> List[str]: + raise OSError( + "pytest: reading from stdin while output is captured! Consider using `-s`." + ) + + def __iter__(self) -> Iterator[str]: return self def fileno(self) -> int: raise UnsupportedOperation("redirected stdin is pseudofile, has no fileno()") + def flush(self) -> None: + raise UnsupportedOperation("redirected stdin is pseudofile, has no flush()") + def isatty(self) -> bool: return False def close(self) -> None: pass - @property - def buffer(self): + def readable(self) -> bool: + return False + + def seek(self, offset: int, whence: int = 0) -> int: + raise UnsupportedOperation("redirected stdin is pseudofile, has no seek(int)") + + def seekable(self) -> bool: + return False + + def tell(self) -> int: + raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()") + + def truncate(self, size: Optional[int] = None) -> int: + raise UnsupportedOperation("cannot truncate stdin") + + def write(self, data: str) -> int: + raise UnsupportedOperation("cannot write to stdin") + + def writelines(self, lines: Iterable[str]) -> None: + raise UnsupportedOperation("Cannot write to stdin") + + def writable(self) -> bool: + return False + + def __enter__(self) -> "DontReadFromInput": return self + def __exit__( + self, + type: Optional[Type[BaseException]], + value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + pass + + @property + def buffer(self) -> BinaryIO: + # The str/bytes doesn't actually matter in this type, so OK to fake. + return self # type: ignore[return-value] + # Capture classes. +class CaptureBase(abc.ABC, Generic[AnyStr]): + EMPTY_BUFFER: AnyStr + + @abc.abstractmethod + def __init__(self, fd: int) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def start(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def done(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def suspend(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def resume(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def writeorg(self, data: AnyStr) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def snap(self) -> AnyStr: + raise NotImplementedError() + + patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"} -class NoCapture: - EMPTY_BUFFER = None - __init__ = start = done = suspend = resume = lambda *args: None +class NoCapture(CaptureBase[str]): + EMPTY_BUFFER = "" + + def __init__(self, fd: int) -> None: + pass + + def start(self) -> None: + pass + def done(self) -> None: + pass -class SysCaptureBinary: + def suspend(self) -> None: + pass - EMPTY_BUFFER = b"" + def resume(self) -> None: + pass - def __init__(self, fd: int, tmpfile=None, *, tee: bool = False) -> None: + def snap(self) -> str: + return "" + + def writeorg(self, data: str) -> None: + pass + + +class SysCaptureBase(CaptureBase[AnyStr]): + def __init__( + self, fd: int, tmpfile: Optional[TextIO] = None, *, tee: bool = False + ) -> None: name = patchsysdict[fd] - self._old = getattr(sys, name) + self._old: TextIO = getattr(sys, name) self.name = name if tmpfile is None: if name == "stdin": @@ -271,14 +382,6 @@ def start(self) -> None: setattr(sys, self.name, self.tmpfile) self._state = "started" - 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": @@ -300,36 +403,43 @@ def resume(self) -> None: setattr(sys, self.name, self.tmpfile) self._state = "started" - def writeorg(self, data) -> None: + +class SysCaptureBinary(SysCaptureBase[bytes]): + EMPTY_BUFFER = b"" + + def snap(self) -> bytes: + 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 writeorg(self, data: bytes) -> None: self._assert_state("writeorg", ("started", "suspended")) self._old.flush() self._old.buffer.write(data) self._old.buffer.flush() -class SysCapture(SysCaptureBinary): - EMPTY_BUFFER = "" # type: ignore[assignment] +class SysCapture(SysCaptureBase[str]): + EMPTY_BUFFER = "" - def snap(self): + def snap(self) -> str: + self._assert_state("snap", ("started", "suspended")) + assert isinstance(self.tmpfile, CaptureIO) res = self.tmpfile.getvalue() self.tmpfile.seek(0) self.tmpfile.truncate() return res - def writeorg(self, data): + def writeorg(self, data: str) -> None: self._assert_state("writeorg", ("started", "suspended")) self._old.write(data) self._old.flush() -class FDCaptureBinary: - """Capture IO to/from a given OS-level file descriptor. - - snap() produces `bytes`. - """ - - EMPTY_BUFFER = b"" - +class FDCaptureBase(CaptureBase[AnyStr]): def __init__(self, targetfd: int) -> None: self.targetfd = targetfd @@ -354,8 +464,8 @@ def __init__(self, targetfd: int) -> None: self.targetfd_save = os.dup(targetfd) if targetfd == 0: - self.tmpfile = open(os.devnull) - self.syscapture = SysCapture(targetfd) + self.tmpfile = open(os.devnull, encoding="utf-8") + self.syscapture: CaptureBase[str] = SysCapture(targetfd) else: self.tmpfile = EncodedFile( TemporaryFile(buffering=0), @@ -367,17 +477,14 @@ def __init__(self, targetfd: int) -> None: if targetfd in patchsysdict: self.syscapture = SysCapture(targetfd, self.tmpfile) else: - self.syscapture = NoCapture() + self.syscapture = NoCapture(targetfd) self._state = "initialized" def __repr__(self) -> str: - return "<{} {} oldfd={} _state={!r} tmpfile={!r}>".format( - self.__class__.__name__, - self.targetfd, - self.targetfd_save, - self._state, - self.tmpfile, + return ( + f"<{self.__class__.__name__} {self.targetfd} oldfd={self.targetfd_save} " + f"_state={self._state!r} tmpfile={self.tmpfile!r}>" ) def _assert_state(self, op: str, states: Tuple[str, ...]) -> None: @@ -394,14 +501,6 @@ def start(self) -> None: self.syscapture.start() self._state = "started" - 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.""" @@ -434,22 +533,38 @@ def resume(self) -> None: os.dup2(self.tmpfile.fileno(), self.targetfd) self._state = "started" - def writeorg(self, data): + +class FDCaptureBinary(FDCaptureBase[bytes]): + """Capture IO to/from a given OS-level file descriptor. + + snap() produces `bytes`. + """ + + EMPTY_BUFFER = b"" + + def snap(self) -> bytes: + 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 writeorg(self, data: bytes) -> None: """Write to original file descriptor.""" self._assert_state("writeorg", ("started", "suspended")) os.write(self.targetfd_save, data) -class FDCapture(FDCaptureBinary): +class FDCapture(FDCaptureBase[str]): """Capture IO to/from a given OS-level file descriptor. snap() produces text. """ - # Ignore type because it doesn't match the type in the superclass (bytes). - EMPTY_BUFFER = "" # type: ignore + EMPTY_BUFFER = "" - def snap(self): + def snap(self) -> str: self._assert_state("snap", ("started", "suspended")) self.tmpfile.seek(0) res = self.tmpfile.read() @@ -457,85 +572,55 @@ def snap(self): self.tmpfile.truncate() return res - def writeorg(self, data): + def writeorg(self, data: str) -> None: """Write to original file descriptor.""" - super().writeorg(data.encode("utf-8")) # XXX use encoding of original stream + self._assert_state("writeorg", ("started", "suspended")) + # XXX use encoding of original stream + os.write(self.targetfd_save, data.encode("utf-8")) # MultiCapture -# 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`.""" - - __slots__ = ("out", "err") - - def __init__(self, out: AnyStr, err: AnyStr) -> None: - self.out: AnyStr = out - self.err: AnyStr = err - - 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) +# Generic NamedTuple only supported since Python 3.11. +if sys.version_info >= (3, 11) or TYPE_CHECKING: - def index(self, value) -> int: - return tuple(self).index(value) + @final + class CaptureResult(NamedTuple, Generic[AnyStr]): + """The result of :method:`caplog.readouterr() `.""" - def __eq__(self, other: object) -> bool: - if not isinstance(other, (CaptureResult, tuple)): - return NotImplemented - return tuple(self) == tuple(other) + out: AnyStr + err: AnyStr - def __hash__(self) -> int: - return hash(tuple(self)) +else: - def __lt__(self, other: object) -> bool: - if not isinstance(other, (CaptureResult, tuple)): - return NotImplemented - return tuple(self) < tuple(other) + class CaptureResult( + collections.namedtuple("CaptureResult", ["out", "err"]), # noqa: PYI024 + Generic[AnyStr], + ): + """The result of :method:`caplog.readouterr() `.""" - def __repr__(self) -> str: - return f"CaptureResult(out={self.out!r}, err={self.err!r})" + __slots__ = () class MultiCapture(Generic[AnyStr]): _state = None _in_suspended = False - def __init__(self, in_, out, err) -> None: - self.in_ = in_ - self.out = out - self.err = err + def __init__( + self, + in_: Optional[CaptureBase[AnyStr]], + out: Optional[CaptureBase[AnyStr]], + err: Optional[CaptureBase[AnyStr]], + ) -> None: + self.in_: Optional[CaptureBase[AnyStr]] = in_ + self.out: Optional[CaptureBase[AnyStr]] = out + self.err: Optional[CaptureBase[AnyStr]] = err def __repr__(self) -> str: - return "".format( - self.out, - self.err, - self.in_, - self._state, - self._in_suspended, + return ( + f"" ) def start_capturing(self) -> None: @@ -551,8 +636,10 @@ 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: + assert self.out is not None self.out.writeorg(out) if err: + assert self.err is not None self.err.writeorg(err) return out, err @@ -573,6 +660,7 @@ def resume_capturing(self) -> None: if self.err: self.err.resume() if self._in_suspended: + assert self.in_ is not None self.in_.resume() self._in_suspended = False @@ -595,10 +683,11 @@ def is_started(self) -> bool: def readouterr(self) -> CaptureResult[AnyStr]: out = self.out.snap() if self.out else "" err = self.err.snap() if self.err else "" - return CaptureResult(out, err) + # TODO: This type error is real, need to fix. + return CaptureResult(out, err) # type: ignore[arg-type] -def _get_multicapture(method: "_CaptureMethod") -> MultiCapture[str]: +def _get_multicapture(method: _CaptureMethod) -> MultiCapture[str]: if method == "fd": return MultiCapture(in_=FDCapture(0), out=FDCapture(1), err=FDCapture(2)) elif method == "sys": @@ -634,14 +723,15 @@ class CaptureManager: needed to ensure the fixtures take precedence over the global capture. """ - def __init__(self, method: "_CaptureMethod") -> None: - self._method = method + def __init__(self, method: _CaptureMethod) -> None: + self._method: Final = method self._global_capturing: Optional[MultiCapture[str]] = None self._capture_fixture: Optional[CaptureFixture[Any]] = None def __repr__(self) -> str: - return "".format( - self._method, self._global_capturing, self._capture_fixture + return ( + f"" ) def is_capturing(self) -> Union[str, bool]: @@ -697,9 +787,7 @@ def set_fixture(self, capture_fixture: "CaptureFixture[Any]") -> None: 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 - ) + f"cannot use {requested_fixture} and {current_fixture} at the same time" ) self._capture_fixture = capture_fixture @@ -754,41 +842,45 @@ def item_capture(self, when: str, item: Item) -> Generator[None, None, None]: self.deactivate_fixture() self.suspend_global_capture(in_=False) - out, err = self.read_global_capture() - item.add_report_section(when, "stdout", out) - item.add_report_section(when, "stderr", err) + out, err = self.read_global_capture() + item.add_report_section(when, "stdout", out) + item.add_report_section(when, "stderr", err) # Hooks - @hookimpl(hookwrapper=True) - def pytest_make_collect_report(self, collector: Collector): + @hookimpl(wrapper=True) + def pytest_make_collect_report( + self, collector: Collector + ) -> Generator[None, CollectReport, CollectReport]: if isinstance(collector, File): self.resume_global_capture() - outcome = yield - self.suspend_global_capture() + try: + rep = yield + finally: + 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 + rep = yield + return rep - @hookimpl(hookwrapper=True) + @hookimpl(wrapper=True) def pytest_runtest_setup(self, item: Item) -> Generator[None, None, None]: with self.item_capture("setup", item): - yield + return (yield) - @hookimpl(hookwrapper=True) + @hookimpl(wrapper=True) def pytest_runtest_call(self, item: Item) -> Generator[None, None, None]: with self.item_capture("call", item): - yield + return (yield) - @hookimpl(hookwrapper=True) + @hookimpl(wrapper=True) def pytest_runtest_teardown(self, item: Item) -> Generator[None, None, None]: with self.item_capture("teardown", item): - yield + return (yield) @hookimpl(tryfirst=True) def pytest_keyboard_interrupt(self) -> None: @@ -804,14 +896,18 @@ class CaptureFixture(Generic[AnyStr]): :fixture:`capfd` and :fixture:`capfdbinary` fixtures.""" def __init__( - self, captureclass, request: SubRequest, *, _ispytest: bool = False + self, + captureclass: Type[CaptureBase[AnyStr]], + request: SubRequest, + *, + _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) - self.captureclass = captureclass + self.captureclass: Type[CaptureBase[AnyStr]] = captureclass self.request = request self._capture: Optional[MultiCapture[AnyStr]] = None - self._captured_out = self.captureclass.EMPTY_BUFFER - self._captured_err = self.captureclass.EMPTY_BUFFER + self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER + self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER def _start(self) -> None: if self._capture is None: @@ -866,7 +962,9 @@ def _is_started(self) -> bool: @contextlib.contextmanager def disabled(self) -> Generator[None, None, None]: """Temporarily disable capturing while inside the ``with`` block.""" - capmanager = self.request.config.pluginmanager.getplugin("capturemanager") + capmanager: CaptureManager = self.request.config.pluginmanager.getplugin( + "capturemanager" + ) with capmanager.global_and_fixture_disabled(): yield @@ -876,14 +974,24 @@ def disabled(self) -> Generator[None, None, None]: @fixture def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: - """Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. + r"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsys.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text`` objects. + + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + .. code-block:: python + + def test_output(capsys): + print("hello") + captured = capsys.readouterr() + assert captured.out == "hello\n" """ - capman = request.config.pluginmanager.getplugin("capturemanager") - capture_fixture = CaptureFixture[str](SysCapture, request, _ispytest=True) + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(SysCapture, request, _ispytest=True) capman.set_fixture(capture_fixture) capture_fixture._start() yield capture_fixture @@ -893,14 +1001,24 @@ def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: @fixture def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]: - """Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. + r"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsysbinary.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``bytes`` objects. + + Returns an instance of :class:`CaptureFixture[bytes] `. + + Example: + .. code-block:: python + + def test_output(capsysbinary): + print("hello") + captured = capsysbinary.readouterr() + assert captured.out == b"hello\n" """ - capman = request.config.pluginmanager.getplugin("capturemanager") - capture_fixture = CaptureFixture[bytes](SysCaptureBinary, request, _ispytest=True) + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(SysCaptureBinary, request, _ispytest=True) capman.set_fixture(capture_fixture) capture_fixture._start() yield capture_fixture @@ -910,14 +1028,24 @@ def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, @fixture def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: - """Enable text capturing of writes to file descriptors ``1`` and ``2``. + r"""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. + + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + .. code-block:: python + + def test_system_echo(capfd): + os.system('echo "hello"') + captured = capfd.readouterr() + assert captured.out == "hello\n" """ - capman = request.config.pluginmanager.getplugin("capturemanager") - capture_fixture = CaptureFixture[str](FDCapture, request, _ispytest=True) + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(FDCapture, request, _ispytest=True) capman.set_fixture(capture_fixture) capture_fixture._start() yield capture_fixture @@ -927,14 +1055,25 @@ def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: @fixture def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]: - """Enable bytes capturing of writes to file descriptors ``1`` and ``2``. + r"""Enable bytes 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 ``byte`` objects. + + Returns an instance of :class:`CaptureFixture[bytes] `. + + Example: + .. code-block:: python + + def test_system_echo(capfdbinary): + os.system('echo "hello"') + captured = capfdbinary.readouterr() + assert captured.out == b"hello\n" + """ - capman = request.config.pluginmanager.getplugin("capturemanager") - capture_fixture = CaptureFixture[bytes](FDCaptureBinary, request, _ispytest=True) + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(FDCaptureBinary, request, _ispytest=True) capman.set_fixture(capture_fixture) capture_fixture._start() yield capture_fixture diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/compat.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/compat.py index 7703dee8c5ac9..614848e0dbaeb 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/compat.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/compat.py @@ -1,32 +1,24 @@ +# mypy: allow-untyped-defs """Python version compatibility code.""" + +from __future__ import annotations + +import dataclasses import enum import functools import inspect -import os -import sys -from contextlib import contextmanager from inspect import Parameter from inspect import signature +import os from pathlib import Path +import sys from typing import Any from typing import Callable -from typing import Generic -from typing import Optional -from typing import Tuple -from typing import TYPE_CHECKING -from typing import TypeVar -from typing import Union - -import attr -import py - -if TYPE_CHECKING: - from typing import NoReturn - from typing_extensions import Final +from typing import Final +from typing import NoReturn +import py -_T = TypeVar("_T") -_S = TypeVar("_S") #: constant to prepare valuing pylib path replacements/lazy proxies later on # intended for removal in pytest 8.0 or 9.0 @@ -37,7 +29,7 @@ # fmt: on -def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH: +def legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH: """Internal wrapper to prepare lazy proxies for legacy_path instances""" return LEGACY_PATH(path) @@ -47,18 +39,9 @@ def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH: # https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions class NotSetType(enum.Enum): token = 0 -NOTSET: "Final" = NotSetType.token # noqa: E305 +NOTSET: Final = NotSetType.token # fmt: on -if sys.version_info >= (3, 8): - from importlib import metadata as importlib_metadata -else: - import importlib_metadata # noqa: F401 - - -def _format_args(func: Callable[..., Any]) -> str: - return str(signature(func)) - def is_generator(func: object) -> bool: genfunc = inspect.isgeneratorfunction(func) @@ -70,7 +53,7 @@ def iscoroutinefunction(func: object) -> bool: 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 + Note: copied and modified from Python 3.5's builtin coroutines.py to avoid importing asyncio directly, which in turns also initializes the "logging" module as a side-effect (see issue #8). """ @@ -83,7 +66,7 @@ def is_async_function(func: object) -> bool: return iscoroutinefunction(func) or inspect.isasyncgenfunction(func) -def getlocation(function, curdir: Optional[str] = None) -> str: +def getlocation(function, curdir: str | os.PathLike[str] | None = None) -> str: function = get_real_func(function) fn = Path(inspect.getfile(function)) lineno = function.__code__.co_firstlineno @@ -117,12 +100,11 @@ def num_mock_patch_args(function) -> int: def getfuncargnames( - function: Callable[..., Any], + function: Callable[..., object], *, name: str = "", - is_method: bool = False, - cls: Optional[type] = None, -) -> Tuple[str, ...]: + cls: type | None = None, +) -> tuple[str, ...]: """Return the names of a function's mandatory arguments. Should return the names of all function arguments that: @@ -131,9 +113,8 @@ def getfuncargnames( * 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. + The cls arguments indicate that the function should be treated as a bound + method even though it's not unless the function is a static method. The name parameter should be the original name in which the function was collected. """ @@ -171,7 +152,7 @@ def getfuncargnames( # 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 ( + if ( # Not using `getattr` because we don't want to resolve the staticmethod. # Not using `cls.__dict__` because we want to check the entire MRO. cls @@ -186,18 +167,7 @@ def getfuncargnames( return arg_names -if sys.version_info < (3, 7): - - @contextmanager - def nullcontext(): - yield - - -else: - from contextlib import nullcontext as nullcontext # noqa: F401 - - -def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]: +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. @@ -217,25 +187,13 @@ def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]: ) -def _translate_non_printable(s: str) -> str: - return s.translate(_non_printable_ascii_translate_table) - - -STRING_TYPES = bytes, str - - -def _bytes_to_ascii(val: bytes) -> str: - return val.decode("ascii", "backslashreplace") - - -def ascii_escaped(val: Union[bytes, str]) -> str: +def ascii_escaped(val: 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' -> r'\xc3\xb4\xc5\xd6' - and escapes unicode objects into a sequence of escaped unicode - ids, e.g.: + and escapes strings into a sequence of escaped unicode ids, e.g.: r'4\nV\U00043efa\x0eMXWB\x1e\u3028\u15fd\xcd\U0007d944' @@ -246,13 +204,13 @@ def ascii_escaped(val: Union[bytes, str]) -> str: a UTF-8 string. """ if isinstance(val, bytes): - ret = _bytes_to_ascii(val) + ret = val.decode("ascii", "backslashreplace") else: ret = val.encode("unicode_escape").decode("ascii") - return _translate_non_printable(ret) + return ret.translate(_non_printable_ascii_translate_table) -@attr.s +@dataclasses.dataclass class _PytestWrapper: """Dummy wrapper around a function object for internal use only. @@ -261,7 +219,7 @@ class _PytestWrapper: decorator to issue warnings when the fixture function is called directly. """ - obj = attr.ib() + obj: Any def get_real_func(obj): @@ -284,9 +242,7 @@ def get_real_func(obj): 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) - ) + f"could not find real function of {saferepr(start_obj)}\nstopped at {saferepr(obj)}" ) if isinstance(obj, functools.partial): obj = obj.func @@ -339,47 +295,25 @@ def safe_isclass(obj: object) -> bool: return False -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 final(f): - return f - - -if sys.version_info >= (3, 8): - from functools import cached_property as cached_property -else: - from typing import overload - from typing import Type - - class cached_property(Generic[_S, _T]): - __slots__ = ("func", "__doc__") - - def __init__(self, func: Callable[[_S], _T]) -> None: - self.func = func - self.__doc__ = func.__doc__ +def get_user_id() -> int | None: + """Return the current process's real user id or None if it could not be + determined. - @overload - def __get__( - self, instance: None, owner: Optional[Type[_S]] = ... - ) -> "cached_property[_S, _T]": - ... - - @overload - def __get__(self, instance: _S, owner: Optional[Type[_S]] = ...) -> _T: - ... - - def __get__(self, instance, owner=None): - if instance is None: - return self - value = instance.__dict__[self.func.__name__] = self.func(instance) - return value + :return: The user id or None if it could not be determined. + """ + # mypy follows the version and platform checking expectation of PEP 484: + # https://mypy.readthedocs.io/en/stable/common_issues.html?highlight=platform#python-version-and-system-platform-checks + # Containment checks are too complex for mypy v1.5.0 and cause failure. + if sys.platform == "win32" or sys.platform == "emscripten": + # win32 does not have a getuid() function. + # Emscripten has a return 0 stub. + return None + else: + # On other platforms, a return value of -1 is assumed to indicate that + # the current process's real user id could not be determined. + ERROR = -1 + uid = os.getuid() + return uid if uid != ERROR else None # Perform exhaustiveness checking. @@ -413,5 +347,5 @@ def __get__(self, instance, owner=None): # previously. # # This also work for Enums (if you use `is` to compare) and Literals. -def assert_never(value: "NoReturn") -> "NoReturn": +def assert_never(value: NoReturn) -> NoReturn: assert False, f"Unhandled value: {value} ({type(value).__name__})" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/__init__.py index ebf6e1b950b80..3f46073ac4ad1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/__init__.py @@ -1,24 +1,29 @@ +# mypy: allow-untyped-defs """Command line options, ini-file and conftest.py processing.""" + import argparse import collections.abc -import contextlib import copy +import dataclasses import enum +from functools import lru_cache +import glob +import importlib.metadata import inspect import os +from pathlib import Path import re import shlex import sys -import types -import warnings -from functools import lru_cache -from pathlib import Path from textwrap import dedent -from types import TracebackType +import types +from types import FunctionType from typing import Any from typing import Callable from typing import cast from typing import Dict +from typing import Final +from typing import final from typing import Generator from typing import IO from typing import Iterable @@ -32,23 +37,26 @@ from typing import Type from typing import TYPE_CHECKING from typing import Union +import warnings -import attr +import pluggy from pluggy import HookimplMarker +from pluggy import HookimplOpts from pluggy import HookspecMarker +from pluggy import HookspecOpts from pluggy import PluginManager -import _pytest._code -import _pytest.deprecated -import _pytest.hookspec +from .compat import PathAwareHookProxy from .exceptions import PrintHelp as PrintHelp from .exceptions import UsageError as UsageError from .findpaths import determine_setup +from _pytest import __version__ +import _pytest._code 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 +import _pytest.deprecated +import _pytest.hookspec from _pytest.outcomes import fail from _pytest.outcomes import Skipped from _pytest.pathlib import absolutepath @@ -56,14 +64,17 @@ from _pytest.pathlib import import_path from _pytest.pathlib import ImportMode from _pytest.pathlib import resolve_package_path +from _pytest.pathlib import safe_exists from _pytest.stash import Stash from _pytest.warning_types import PytestConfigWarning +from _pytest.warning_types import warn_explicit_for -if TYPE_CHECKING: +if TYPE_CHECKING: + from .argparsing import Argument + from .argparsing import Parser from _pytest._code.code import _TracebackStyle from _pytest.terminal import TerminalReporter - from .argparsing import Argument _PluggyPlugin = object @@ -107,16 +118,14 @@ class ConftestImportFailure(Exception): def __init__( self, path: Path, - excinfo: Tuple[Type[Exception], Exception, TracebackType], + *, + cause: Exception, ) -> None: - super().__init__(path, excinfo) self.path = path - self.excinfo = excinfo + self.cause = cause def __str__(self) -> str: - return "{}: {} (from {})".format( - self.excinfo[0].__name__, self.excinfo[1], self.path - ) + return f"{type(self.cause).__name__}: {self.cause} (from {self.path})" def filter_traceback_for_conftest_import_failure( @@ -136,16 +145,20 @@ def main( ) -> Union[int, ExitCode]: """Perform an in-process test run. - :param args: List of command line arguments. + :param args: + List of command line arguments. If `None` or not given, defaults to reading + arguments directly from the process command line (:data:`sys.argv`). :param plugins: List of plugin objects to be auto-registered during initialization. :returns: An exit code. """ + old_pytest_version = os.environ.get("PYTEST_VERSION") try: + os.environ["PYTEST_VERSION"] = __version__ try: config = _prepareconfig(args, plugins) except ConftestImportFailure as e: - exc_info = ExceptionInfo.from_exc_info(e.excinfo) + exc_info = ExceptionInfo.from_exception(e.cause) tw = TerminalWriter(sys.stderr) tw.line(f"ImportError while loading conftest '{e.path}'.", red=True) exc_info.traceback = exc_info.traceback.filter( @@ -176,6 +189,11 @@ def main( for msg in e.args: tw.line(f"ERROR: {msg}\n", red=True) return ExitCode.USAGE_ERROR + finally: + if old_pytest_version is None: + os.environ.pop("PYTEST_VERSION", None) + else: + os.environ["PYTEST_VERSION"] = old_pytest_version def console_main() -> int: @@ -231,7 +249,8 @@ def directory_arg(path: str, optname: str) -> str: "helpconfig", # Provides -p. ) -default_plugins = essential_plugins + ( +default_plugins = ( + *essential_plugins, "python", "terminal", "debugging", @@ -243,7 +262,6 @@ def directory_arg(path: str, optname: str) -> str: "monkeypatch", "recwarn", "pastebin", - "nose", "assertion", "junitxml", "doctest", @@ -256,7 +274,8 @@ def directory_arg(path: str, optname: str) -> str: "logging", "reports", "python_path", - *(["unraisableexception", "threadexception"] if sys.version_info >= (3, 8) else []), + "unraisableexception", + "threadexception", "faulthandler", ) @@ -310,7 +329,9 @@ def _prepareconfig( elif isinstance(args, os.PathLike): args = [os.fspath(args)] elif not isinstance(args, list): - msg = "`args` parameter expected to be a list of strings, got: {!r} (type: {})" + msg = ( # type:ignore[unreachable] + "`args` parameter expected to be a list of strings, got: {!r} (type: {})" + ) raise TypeError(msg.format(args, type(args))) config = get_config(args, plugins) @@ -339,6 +360,38 @@ def _get_directory(path: Path) -> Path: return path +def _get_legacy_hook_marks( + method: Any, + hook_type: str, + opt_names: Tuple[str, ...], +) -> Dict[str, bool]: + if TYPE_CHECKING: + # abuse typeguard from importlib to avoid massive method type union thats lacking a alias + assert inspect.isroutine(method) + known_marks: Set[str] = {m.name for m in getattr(method, "pytestmark", [])} + must_warn: List[str] = [] + opts: Dict[str, bool] = {} + for opt_name in opt_names: + opt_attr = getattr(method, opt_name, AttributeError) + if opt_attr is not AttributeError: + must_warn.append(f"{opt_name}={opt_attr}") + opts[opt_name] = True + elif opt_name in known_marks: + must_warn.append(f"{opt_name}=True") + opts[opt_name] = True + else: + opts[opt_name] = False + if must_warn: + hook_opts = ", ".join(must_warn) + message = _pytest.deprecated.HOOK_LEGACY_MARKING.format( + type=hook_type, + fullname=method.__qualname__, + hook_opts=hook_opts, + ) + warn_explicit_for(cast(FunctionType, method), message) + return opts + + @final class PytestPluginManager(PluginManager): """A :py:class:`pluggy.PluginManager ` with @@ -353,13 +406,17 @@ def __init__(self) -> None: import _pytest.assertion super().__init__("pytest") - # The objects are module objects, only used generically. - self._conftest_plugins: Set[types.ModuleType] = set() - # State related to local conftest plugins. + # -- State related to local conftest plugins. + # All loaded conftest modules. + self._conftest_plugins: Set[types.ModuleType] = set() + # All conftest modules applicable for a directory. + # This includes the directory's own conftest modules as well + # as those of its parent directories. self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {} - self._conftestpath2mod: Dict[Path, types.ModuleType] = {} + # Cutoff directory above which conftests are no longer discovered. self._confcutdir: Optional[Path] = None + # If set, conftest loading is skipped. self._noconftest = False # _getconftestmodules()'s call to _get_directory() causes a stat @@ -367,8 +424,6 @@ def __init__(self) -> None: # session (#9478), often with the same path, so cache it. self._get_directory = lru_cache(256)(_get_directory) - self._duplicatepaths: Set[Path] = set() - # 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 @@ -398,50 +453,43 @@ def __init__(self) -> None: # Used to know when we are importing conftests after the pytest_configure stage. self._configured = False - def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str): + def parse_hookimpl_opts( + self, plugin: _PluggyPlugin, name: str + ) -> Optional[HookimplOpts]: + """:meta private:""" # pytest hooks are always prefixed with "pytest_", # so we avoid accessing possibly non-readable attributes # (see issue #1073). if not name.startswith("pytest_"): - return - # Ignore names which can not be hooks. + return None + # Ignore names which cannot be hooks. if name == "pytest_plugins": - return + return None - method = getattr(plugin, name) opts = super().parse_hookimpl_opts(plugin, name) + if opts is not None: + return opts + method = getattr(plugin, name) # Consider only actual functions for hooks (#3775). if not inspect.isroutine(method): - return - + return None # 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: - # TODO: DeprecationWarning, people should use hookimpl - # https://github.com/pytest-dev/pytest/issues/4562 - known_marks = {m.name for m in getattr(method, "pytestmark", [])} - - for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"): - opts.setdefault(name, hasattr(method, name) or name in known_marks) - return opts + return _get_legacy_hook_marks( # type: ignore[return-value] + method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper") + ) - def parse_hookspec_opts(self, module_or_class, name: str): + def parse_hookspec_opts(self, module_or_class, name: str) -> Optional[HookspecOpts]: + """:meta private:""" opts = super().parse_hookspec_opts(module_or_class, name) if opts is None: method = getattr(module_or_class, name) - if name.startswith("pytest_"): - # todo: deprecate hookspec hacks - # https://github.com/pytest-dev/pytest/issues/4562 - known_marks = {m.name for m in getattr(method, "pytestmark", [])} - opts = { - "firstresult": hasattr(method, "firstresult") - or "firstresult" in known_marks, - "historic": hasattr(method, "historic") - or "historic" in known_marks, - } + opts = _get_legacy_hook_marks( # type: ignore[assignment] + method, + "spec", + ("firstresult", "historic"), + ) return opts def register( @@ -457,15 +505,19 @@ def register( ) ) return None - ret: Optional[str] = super().register(plugin, name) - if ret: + plugin_name = super().register(plugin, name) + if plugin_name is not None: self.hook.pytest_plugin_registered.call_historic( - kwargs=dict(plugin=plugin, manager=self) + kwargs=dict( + plugin=plugin, + plugin_name=plugin_name, + manager=self, + ) ) if isinstance(plugin, types.ModuleType): self.consider_module(plugin) - return ret + return plugin_name def getplugin(self, name: str): # Support deprecated naming because plugins (xdist e.g.) use it. @@ -483,12 +535,14 @@ def pytest_configure(self, config: "Config") -> None: config.addinivalue_line( "markers", "tryfirst: mark a hook implementation function such that the " - "plugin machinery will try to call it first/as early as possible.", + "plugin machinery will try to call it first/as early as possible. " + "DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.", ) config.addinivalue_line( "markers", "trylast: mark a hook implementation function such that the " - "plugin machinery will try to call it last/as late as possible.", + "plugin machinery will try to call it last/as late as possible. " + "DEPRECATED, use @pytest.hookimpl(trylast=True) instead.", ) self._configured = True @@ -496,7 +550,16 @@ def pytest_configure(self, config: "Config") -> None: # Internal API for local conftest plugin handling. # def _set_initial_conftests( - self, namespace: argparse.Namespace, rootpath: Path + self, + args: Sequence[Union[str, Path]], + pyargs: bool, + noconftest: bool, + rootpath: Path, + confcutdir: Optional[Path], + invocation_dir: Path, + importmode: Union[ImportMode, str], + *, + consider_namespace_packages: bool, ) -> None: """Load initial conftest files given a preparsed "namespace". @@ -505,76 +568,120 @@ def _set_initial_conftests( All builtin and 3rd party plugins will have been loaded, however, so common options will not confuse our logic here. """ - current = Path.cwd() self._confcutdir = ( - absolutepath(current / namespace.confcutdir) - if namespace.confcutdir - else None + absolutepath(invocation_dir / confcutdir) if confcutdir else None ) - self._noconftest = namespace.noconftest - self._using_pyargs = namespace.pyargs - testpaths = namespace.file_or_dir + self._noconftest = noconftest + self._using_pyargs = pyargs foundanchor = False - for testpath in testpaths: - path = str(testpath) + for initial_path in args: + path = str(initial_path) # remove node-id syntax i = path.find("::") if i != -1: path = path[:i] - anchor = absolutepath(current / path) - if anchor.exists(): # we found some file object - self._try_load_conftest(anchor, namespace.importmode, rootpath) + anchor = absolutepath(invocation_dir / path) + + # Ensure we do not break if what appears to be an anchor + # is in fact a very long option (#10169, #11394). + if safe_exists(anchor): + self._try_load_conftest( + anchor, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) foundanchor = True if not foundanchor: - self._try_load_conftest(current, namespace.importmode, rootpath) + self._try_load_conftest( + invocation_dir, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) + + def _is_in_confcutdir(self, path: Path) -> bool: + """Whether to consider the given path to load conftests from.""" + if self._confcutdir is None: + return True + # The semantics here are literally: + # Do not load a conftest if it is found upwards from confcut dir. + # But this is *not* the same as: + # Load only conftests from confcutdir or below. + # At first glance they might seem the same thing, however we do support use cases where + # we want to load conftests that are not found in confcutdir or below, but are found + # in completely different directory hierarchies like packages installed + # in out-of-source trees. + # (see #9767 for a regression where the logic was inverted). + return path not in self._confcutdir.parents def _try_load_conftest( - self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path + self, + anchor: Path, + importmode: Union[str, ImportMode], + rootpath: Path, + *, + consider_namespace_packages: bool, ) -> None: - self._getconftestmodules(anchor, importmode, rootpath) + self._loadconftestmodules( + anchor, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) # let's also consider test* subdirs if anchor.is_dir(): for x in anchor.glob("test*"): if x.is_dir(): - self._getconftestmodules(x, importmode, rootpath) + self._loadconftestmodules( + x, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) - def _getconftestmodules( - self, path: Path, importmode: Union[str, ImportMode], rootpath: Path - ) -> List[types.ModuleType]: + def _loadconftestmodules( + self, + path: Path, + importmode: Union[str, ImportMode], + rootpath: Path, + *, + consider_namespace_packages: bool, + ) -> None: if self._noconftest: - return [] + return directory = self._get_directory(path) # Optimization: avoid repeated searches in the same directory. # Assumes always called with same importmode and rootpath. - existing_clist = self._dirpath2confmods.get(directory) - if existing_clist is not None: - return existing_clist + if directory in self._dirpath2confmods: + return - # 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. clist = [] - confcutdir_parents = self._confcutdir.parents if self._confcutdir else [] for parent in reversed((directory, *directory.parents)): - if parent in confcutdir_parents: - continue - conftestpath = parent / "conftest.py" - if conftestpath.is_file(): - mod = self._importconftest(conftestpath, importmode, rootpath) - clist.append(mod) + if self._is_in_confcutdir(parent): + conftestpath = parent / "conftest.py" + if conftestpath.is_file(): + mod = self._importconftest( + conftestpath, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) + clist.append(mod) self._dirpath2confmods[directory] = clist - return clist + + def _getconftestmodules(self, path: Path) -> Sequence[types.ModuleType]: + directory = self._get_directory(path) + return self._dirpath2confmods.get(directory, ()) def _rget_with_confmod( self, name: str, path: Path, - importmode: Union[str, ImportMode], - rootpath: Path, ) -> Tuple[types.ModuleType, Any]: - modules = self._getconftestmodules(path, importmode, rootpath=rootpath) + modules = self._getconftestmodules(path) for mod in reversed(modules): try: return mod, getattr(mod, name) @@ -583,41 +690,56 @@ def _rget_with_confmod( raise KeyError(name) def _importconftest( - self, conftestpath: Path, importmode: Union[str, ImportMode], rootpath: Path + self, + conftestpath: Path, + importmode: Union[str, ImportMode], + rootpath: Path, + *, + consider_namespace_packages: bool, ) -> 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 = conftestpath.resolve() - - with contextlib.suppress(KeyError): - return self._conftestpath2mod[key] - + conftestpath_plugin_name = str(conftestpath) + existing = self.get_plugin(conftestpath_plugin_name) + if existing is not None: + return cast(types.ModuleType, existing) + + # conftest.py files there are not in a Python package all have module + # name "conftest", and thus conflict with each other. Clear the existing + # before loading the new one, otherwise the existing one will be + # returned from the module cache. pkgpath = resolve_package_path(conftestpath) if pkgpath is None: - _ensure_removed_sysmodule(conftestpath.stem) + try: + del sys.modules[conftestpath.stem] + except KeyError: + pass try: - mod = import_path(conftestpath, mode=importmode, root=rootpath) + mod = import_path( + conftestpath, + mode=importmode, + root=rootpath, + consider_namespace_packages=consider_namespace_packages, + ) except Exception as e: assert e.__traceback__ is not None - exc_info = (type(e), e, e.__traceback__) - raise ConftestImportFailure(conftestpath, exc_info) from e + raise ConftestImportFailure(conftestpath, cause=e) from e self._check_non_top_pytest_plugins(mod, conftestpath) self._conftest_plugins.add(mod) - self._conftestpath2mod[key] = mod dirpath = conftestpath.parent if dirpath in self._dirpath2confmods: for path, mods in self._dirpath2confmods.items(): - if path and dirpath in path.parents or path == dirpath: - assert mod not in mods + if dirpath in path.parents or path == dirpath: + if mod in mods: + raise AssertionError( + f"While trying to load conftest path {conftestpath!s}, " + f"found that the module {mod} is already loaded with path {mod.__file__}. " + "This is not supposed to happen. Please report this issue to pytest." + ) mods.append(mod) self.trace(f"loading conftestmodule {mod!r}") - self.consider_conftest(mod) + self.consider_conftest(mod, registration_name=conftestpath_plugin_name) return mod def _check_non_top_pytest_plugins( @@ -666,6 +788,7 @@ def consider_preparse( parg = opt[2:] else: continue + parg = parg.strip() if exclude_only and not parg.startswith("no:"): continue self.consider_pluginarg(parg) @@ -687,18 +810,17 @@ def consider_pluginarg(self, arg: str) -> None: self.set_blocked("pytest_" + name) else: name = arg - # Unblock the plugin. None indicates that it has been blocked. - # There is no interface with pluggy for this. - if self._name2plugin.get(name, -1) is None: - del self._name2plugin[name] + # Unblock the plugin. + self.unblock(name) if not name.startswith("pytest_"): - if self._name2plugin.get("pytest_" + name, -1) is None: - del self._name2plugin["pytest_" + name] + self.unblock("pytest_" + name) self.import_plugin(arg, consider_entry_points=True) - def consider_conftest(self, conftestmodule: types.ModuleType) -> None: + def consider_conftest( + self, conftestmodule: types.ModuleType, registration_name: str + ) -> None: """:meta private:""" - self.register(conftestmodule, name=conftestmodule.__file__) + self.register(conftestmodule, name=registration_name) def consider_env(self) -> None: """:meta private:""" @@ -754,7 +876,7 @@ def import_plugin(self, modname: str, consider_entry_points: bool = False) -> No def _get_plugin_specs_as_list( - specs: Union[None, types.ModuleType, str, Sequence[str]] + specs: Union[None, types.ModuleType, str, Sequence[str]], ) -> List[str]: """Parse a plugins specification into a list of plugin names.""" # None means empty. @@ -775,13 +897,6 @@ def _get_plugin_specs_as_list( ) -def _ensure_removed_sysmodule(modname: str) -> None: - try: - del sys.modules[modname] - except KeyError: - pass - - class Notset: def __repr__(self): return "" @@ -830,7 +945,8 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: if is_simple_module: module_name, _ = os.path.splitext(fn) # we ignore "setup.py" at the root of the distribution - if module_name != "setup": + # as well as editable installation finder modules made by setuptools + if module_name != "setup" and not module_name.startswith("__editable__"): seen_some = True yield module_name elif is_package: @@ -854,10 +970,6 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: yield from _iter_rewritable_modules(new_package_files) -def _args_converter(args: Iterable[str]) -> Tuple[str, ...]: - return tuple(args) - - @final class Config: """Access to configuration values, pluginmanager and plugin hooks. @@ -871,7 +983,7 @@ class Config: """ @final - @attr.s(frozen=True, auto_attribs=True) + @dataclasses.dataclass(frozen=True) class InvocationParams: """Holds parameters passed during :func:`pytest.main`. @@ -887,20 +999,46 @@ class InvocationParams: Plugins accessing ``InvocationParams`` must be aware of that. """ - args: Tuple[str, ...] = attr.ib(converter=_args_converter) + args: Tuple[str, ...] """The command-line arguments as passed to :func:`pytest.main`.""" plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] """Extra plugins, might be `None`.""" dir: Path """The directory from which :func:`pytest.main` was invoked.""" + def __init__( + self, + *, + args: Iterable[str], + plugins: Optional[Sequence[Union[str, _PluggyPlugin]]], + dir: Path, + ) -> None: + object.__setattr__(self, "args", tuple(args)) + object.__setattr__(self, "plugins", plugins) + object.__setattr__(self, "dir", dir) + + class ArgsSource(enum.Enum): + """Indicates the source of the test arguments. + + .. versionadded:: 7.2 + """ + + #: Command line arguments. + ARGS = enum.auto() + #: Invocation directory. + INVOCATION_DIR = enum.auto() + INCOVATION_DIR = INVOCATION_DIR # backwards compatibility alias + #: 'testpaths' configuration value. + TESTPATHS = enum.auto() + def __init__( self, pluginmanager: PytestPluginManager, *, invocation_params: Optional[InvocationParams] = None, ) -> None: - from .argparsing import Parser, FILE_OR_DIR + from .argparsing import FILE_OR_DIR + from .argparsing import Parser if invocation_params is None: invocation_params = self.InvocationParams( @@ -940,10 +1078,8 @@ def __init__( # Deprecated alias. Was never public. Can be removed in a few releases. self._store = self.stash - from .compat import PathAwareHookProxy - self.trace = self.pluginmanager.trace.root.get("config") - self.hook = PathAwareHookProxy(self.pluginmanager.hook) + self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment] self._inicache: Dict[str, Any] = {} self._override_ini: Sequence[str] = () self._opt2dest: Dict[str, str] = {} @@ -953,6 +1089,8 @@ def __init__( self.hook.pytest_addoption.call_historic( kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager) ) + self.args_source = Config.ArgsSource.ARGS + self.args: List[str] = [] if TYPE_CHECKING: from _pytest.cacheprovider import Cache @@ -1001,9 +1139,10 @@ def _ensure_unconfigure(self) -> None: fin() def get_terminal_writer(self) -> TerminalWriter: - terminalreporter: TerminalReporter = self.pluginmanager.get_plugin( + terminalreporter: Optional[TerminalReporter] = self.pluginmanager.get_plugin( "terminalreporter" ) + assert terminalreporter is not None return terminalreporter._tw def pytest_cmdline_parse( @@ -1012,7 +1151,6 @@ def pytest_cmdline_parse( try: self.parse(args) except UsageError: - # Handle --version and --help here in a minimal fashion. # This gets done via helpconfig normally, but its # pytest_cmdline_main is not called in case of errors. @@ -1077,8 +1215,29 @@ def _processopt(self, opt: "Argument") -> None: @hookimpl(trylast=True) def pytest_load_initial_conftests(self, early_config: "Config") -> None: + # We haven't fully parsed the command line arguments yet, so + # early_config.args it not set yet. But we need it for + # discovering the initial conftests. So "pre-run" the logic here. + # It will be done for real in `parse()`. + args, args_source = early_config._decide_args( + args=early_config.known_args_namespace.file_or_dir, + pyargs=early_config.known_args_namespace.pyargs, + testpaths=early_config.getini("testpaths"), + invocation_dir=early_config.invocation_params.dir, + rootpath=early_config.rootpath, + warn=False, + ) self.pluginmanager._set_initial_conftests( - early_config.known_args_namespace, rootpath=early_config.rootpath + args=args, + pyargs=early_config.known_args_namespace.pyargs, + noconftest=early_config.known_args_namespace.noconftest, + rootpath=early_config.rootpath, + confcutdir=early_config.known_args_namespace.confcutdir, + invocation_dir=early_config.invocation_params.dir, + importmode=early_config.known_args_namespace.importmode, + consider_namespace_packages=early_config.getini( + "consider_namespace_packages" + ), ) def _initini(self, args: Sequence[str]) -> None: @@ -1086,21 +1245,21 @@ def _initini(self, args: Sequence[str]) -> None: args, namespace=copy.copy(self.option) ) rootpath, inipath, inicfg = determine_setup( - ns.inifilename, - ns.file_or_dir + unknown_args, + inifile=ns.inifilename, + args=ns.file_or_dir + unknown_args, rootdir_cmd_arg=ns.rootdir or None, - config=self, + invocation_dir=self.invocation_params.dir, ) 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("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", + "Plugins that must be present for pytest to run", type="args", default=[], ) @@ -1138,7 +1297,7 @@ def _mark_plugins_for_rewrite(self, hook) -> None: package_files = ( str(file) - for dist in importlib_metadata.distributions() + for dist in importlib.metadata.distributions() if any(ep.group == "pytest11" for ep in dist.entry_points) for file in dist.files or [] ) @@ -1158,6 +1317,51 @@ def _validate_args(self, args: List[str], via: str) -> List[str]: return args + def _decide_args( + self, + *, + args: List[str], + pyargs: bool, + testpaths: List[str], + invocation_dir: Path, + rootpath: Path, + warn: bool, + ) -> Tuple[List[str], ArgsSource]: + """Decide the args (initial paths/nodeids) to use given the relevant inputs. + + :param warn: Whether can issue warnings. + + :returns: The args and the args source. Guaranteed to be non-empty. + """ + if args: + source = Config.ArgsSource.ARGS + result = args + else: + if invocation_dir == rootpath: + source = Config.ArgsSource.TESTPATHS + if pyargs: + result = testpaths + else: + result = [] + for path in testpaths: + result.extend(sorted(glob.iglob(path, recursive=True))) + if testpaths and not result: + if warn: + warning_text = ( + "No files were found in testpaths; " + "consider removing or adjusting your testpaths configuration. " + "Searching recursively from the current directory instead." + ) + self.issue_config_time_warning( + PytestConfigWarning(warning_text), stacklevel=3 + ) + else: + result = [] + if not result: + source = Config.ArgsSource.INVOCATION_DIR + result = [str(invocation_dir)] + return result, source + def _preparse(self, args: List[str], addopts: bool = True) -> None: if addopts: env_addopts = os.environ.get("PYTEST_ADDOPTS", "") @@ -1191,13 +1395,11 @@ def _preparse(self, args: List[str], addopts: bool = True) -> None: self._validate_plugins() self._warn_about_skipped_plugins() - if self.known_args_namespace.strict: - self.issue_config_time_warning( - _pytest.deprecated.STRICT_OPTION, stacklevel=2 - ) - - if self.known_args_namespace.confcutdir is None and self.inipath is not None: - confcutdir = str(self.inipath.parent) + if self.known_args_namespace.confcutdir is None: + if self.inipath is not None: + confcutdir = str(self.inipath.parent) + else: + confcutdir = str(self.rootpath) self.known_args_namespace.confcutdir = confcutdir try: self.hook.pytest_load_initial_conftests( @@ -1214,12 +1416,14 @@ def _preparse(self, args: List[str], addopts: bool = True) -> None: else: raise - @hookimpl(hookwrapper=True) - def pytest_collection(self) -> Generator[None, None, None]: + @hookimpl(wrapper=True) + def pytest_collection(self) -> Generator[None, object, object]: # 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() + try: + return (yield) + finally: + self._validate_config_options() def _checkversion(self) -> None: import pytest @@ -1236,12 +1440,7 @@ def _checkversion(self) -> None: if Version(minver) > Version(pytest.__version__): raise pytest.UsageError( - "%s: 'minversion' requires pytest-%s, actual pytest-%s'" - % ( - self.inipath, - minver, - pytest.__version__, - ) + f"{self.inipath}: 'minversion' requires pytest-{minver}, actual pytest-{pytest.__version__}'" ) def _validate_config_options(self) -> None: @@ -1254,8 +1453,9 @@ def _validate_plugins(self) -> None: return # Imported lazily to improve start-up time. + from packaging.requirements import InvalidRequirement + from packaging.requirements import Requirement 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} @@ -1292,26 +1492,26 @@ def _get_unknown_ini_keys(self) -> List[str]: def parse(self, args: List[str], addopts: bool = True) -> None: # Parse given cmdline arguments into this config object. - assert not hasattr( - self, "args" + assert ( + self.args == [] ), "can only parse cmdline args at most once per Config object" 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 # type: ignore try: args = self._parser.parse_setoption( args, self.option, namespace=self.option ) - if not args: - if self.invocation_params.dir == self.rootpath: - args = self.getini("testpaths") - if not args: - args = [str(self.invocation_params.dir)] - self.args = args + self.args, self.args_source = self._decide_args( + args=args, + pyargs=self.known_args_namespace.pyargs, + testpaths=self.getini("testpaths"), + invocation_dir=self.invocation_params.dir, + rootpath=self.rootpath, + warn=True, + ) except PrintHelp: pass @@ -1319,7 +1519,7 @@ 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``. + function because it is not possible to have hook wrappers around ``pytest_configure``. This function is mainly intended for plugins that need to issue warnings during ``pytest_configure`` (or similar stages). @@ -1341,14 +1541,6 @@ def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None: 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], @@ -1369,6 +1561,27 @@ def addinivalue_line(self, name: str, line: str) -> None: def getini(self, name: str): """Return configuration value from an :ref:`ini file `. + If a configuration value is not defined in an + :ref:`ini file `, then the ``default`` value provided while + registering the configuration through + :func:`parser.addini ` will be returned. + Please note that you can even provide ``None`` as a valid + default value. + + If ``default`` is not provided while registering using + :func:`parser.addini `, then a default value + based on the ``type`` parameter passed to + :func:`parser.addini ` will be returned. + The default values based on ``type`` are: + ``paths``, ``pathlist``, ``args`` and ``linelist`` : empty list ``[]`` + ``bool`` : ``False`` + ``string`` : empty string ``""`` + + If neither the ``default`` nor the ``type`` parameter is passed + while registering the configuration through + :func:`parser.addini `, then the configuration + is treated as a string and a default empty string '' is returned. + If the specified name hasn't been registered through a prior :func:`parser.addini ` call (usually from a plugin), a ValueError is raised. @@ -1395,11 +1608,7 @@ def _getini(self, name: str): try: value = self.inicfg[name] except KeyError: - if default is not None: - return default - if type is None: - return "" - return [] + return default else: value = override_value # Coerce the values based on types. @@ -1418,9 +1627,11 @@ def _getini(self, name: str): # in this case, we already have a list ready to use. # if type == "paths": - # TODO: This assert is probably not valid in all cases. - assert self.inipath is not None - dp = self.inipath.parent + dp = ( + self.inipath.parent + if self.inipath is not None + else self.invocation_params.dir + ) input_values = shlex.split(value) if isinstance(value, str) else value return [dp / x for x in input_values] elif type == "args": @@ -1439,15 +1650,12 @@ def _getini(self, name: str): else: return self._getini_unknown_type(name, type, value) - def _getconftest_pathlist( - self, name: str, path: Path, rootpath: Path - ) -> Optional[List[Path]]: + def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]: try: - mod, relroots = self.pluginmanager._rget_with_confmod( - name, path, self.getoption("importmode"), rootpath - ) + mod, relroots = self.pluginmanager._rget_with_confmod(name, path) except KeyError: return None + assert mod.__file__ is not None modpath = Path(mod.__file__).parent values: List[Path] = [] for relroot in relroots: @@ -1469,9 +1677,7 @@ def _get_override_ini_value(self, name: str) -> Optional[str]: key, user_ini_value = ini_config.split("=", 1) except ValueError as e: raise UsageError( - "-o/--override-ini expects option=value style (got: {!r}).".format( - ini_config - ) + f"-o/--override-ini expects option=value style (got: {ini_config!r})." ) from e else: if key == name: @@ -1510,6 +1716,79 @@ def getvalueorskip(self, name: str, path=None): """Deprecated, use getoption(skip=True) instead.""" return self.getoption(name, skip=True) + #: Verbosity type for failed assertions (see :confval:`verbosity_assertions`). + VERBOSITY_ASSERTIONS: Final = "assertions" + #: Verbosity type for test case execution (see :confval:`verbosity_test_cases`). + VERBOSITY_TEST_CASES: Final = "test_cases" + _VERBOSITY_INI_DEFAULT: Final = "auto" + + def get_verbosity(self, verbosity_type: Optional[str] = None) -> int: + r"""Retrieve the verbosity level for a fine-grained verbosity type. + + :param verbosity_type: Verbosity type to get level for. If a level is + configured for the given type, that value will be returned. If the + given type is not a known verbosity type, the global verbosity + level will be returned. If the given type is None (default), the + global verbosity level will be returned. + + To configure a level for a fine-grained verbosity type, the + configuration file should have a setting for the configuration name + and a numeric value for the verbosity level. A special value of "auto" + can be used to explicitly use the global verbosity level. + + Example: + .. code-block:: ini + + # content of pytest.ini + [pytest] + verbosity_assertions = 2 + + .. code-block:: console + + pytest -v + + .. code-block:: python + + print(config.get_verbosity()) # 1 + print(config.get_verbosity(Config.VERBOSITY_ASSERTIONS)) # 2 + """ + global_level = self.option.verbose + assert isinstance(global_level, int) + if verbosity_type is None: + return global_level + + ini_name = Config._verbosity_ini_name(verbosity_type) + if ini_name not in self._parser._inidict: + return global_level + + level = self.getini(ini_name) + if level == Config._VERBOSITY_INI_DEFAULT: + return global_level + + return int(level) + + @staticmethod + def _verbosity_ini_name(verbosity_type: str) -> str: + return f"verbosity_{verbosity_type}" + + @staticmethod + def _add_verbosity_ini(parser: "Parser", verbosity_type: str, help: str) -> None: + """Add a output verbosity configuration option for the given output type. + + :param parser: Parser for command line arguments and ini-file values. + :param verbosity_type: Fine-grained verbosity category. + :param help: Description of the output this type controls. + + The value should be retrieved via a call to + :py:func:`config.get_verbosity(type) `. + """ + parser.addini( + Config._verbosity_ini_name(verbosity_type), + help=help, + type="string", + default=Config._VERBOSITY_INI_DEFAULT, + ) + def _warn_about_missing_assertion(self, mode: str) -> None: if not _assertion_supported(): if mode == "plain": @@ -1593,7 +1872,7 @@ def _strtobool(val: str) -> bool: @lru_cache(maxsize=50) def parse_warning_filter( arg: str, *, escape: bool -) -> Tuple[str, str, Type[Warning], str, int]: +) -> Tuple["warnings._ActionKind", str, Type[Warning], str, int]: """Parse a warnings filter string. This is copied from warnings._setoption with the following changes: @@ -1635,15 +1914,15 @@ def parse_warning_filter( parts.append("") action_, message, category_, module, lineno_ = (s.strip() for s in parts) try: - action: str = warnings._getaction(action_) # type: ignore[attr-defined] + action: "warnings._ActionKind" = warnings._getaction(action_) # type: ignore[attr-defined] except warnings._OptionError as e: - raise UsageError(error_template.format(error=str(e))) + raise UsageError(error_template.format(error=str(e))) from None try: category: Type[Warning] = _resolve_warning_category(category_) except Exception: exc_info = ExceptionInfo.from_current() exception_text = exc_info.getrepr(style="native") - raise UsageError(error_template.format(error=exception_text)) + raise UsageError(error_template.format(error=exception_text)) from None if message and escape: message = re.escape(message) if module and escape: @@ -1656,7 +1935,7 @@ def parse_warning_filter( except ValueError as e: raise UsageError( error_template.format(error=f"invalid lineno {lineno_!r}: {e}") - ) + ) from None else: lineno = 0 return action, message, category, module, lineno diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/argparsing.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/argparsing.py index b0bb3f168ff29..9006351af7284 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/argparsing.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/argparsing.py @@ -1,35 +1,38 @@ +# mypy: allow-untyped-defs import argparse +from gettext import gettext import os 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 final from typing import List +from typing import Literal from typing import Mapping +from typing import NoReturn from typing import Optional from typing import Sequence from typing import Tuple -from typing import TYPE_CHECKING from typing import Union import _pytest._io -from _pytest.compat import final from _pytest.config.exceptions import UsageError -from _pytest.deprecated import ARGUMENT_PERCENT_DEFAULT -from _pytest.deprecated import ARGUMENT_TYPE_STR -from _pytest.deprecated import ARGUMENT_TYPE_STR_CHOICE from _pytest.deprecated import check_ispytest -if TYPE_CHECKING: - from typing import NoReturn - from typing_extensions import Literal FILE_OR_DIR = "file_or_dir" +class NotSet: + def __repr__(self) -> str: + return "" + + +NOT_SET = NotSet() + + @final class Parser: """Parser for command line arguments and ini-file values. @@ -48,7 +51,7 @@ def __init__( _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) - self._anonymous = OptionGroup("custom options", parser=self, _ispytest=True) + self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True) self._groups: List[OptionGroup] = [] self._processopt = processopt self._usage = usage @@ -66,14 +69,15 @@ def getgroup( ) -> "OptionGroup": """Get (or create) a named option Group. - :name: Name of the option group. - :description: Long description for --help output. - :after: Name of another group, used for ordering --help output. + :param name: Name of the option group. + :param description: Long description for --help output. + :param after: Name of another group, used for ordering --help output. + :returns: The option group. The returned group object has an ``addoption`` method with the same signature as :func:`parser.addoption ` but will be shown in the respective group in the output of - ``pytest. --help``. + ``pytest --help``. """ for group in self._groups: if group.name == name: @@ -89,10 +93,11 @@ def getgroup( 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_argument()`` function of the - `argparse library `_ - accepts. + :param opts: + Option names, can be short or long options. + :param attrs: + Same attributes as the argparse library's :meth:`add_argument() + ` function accepts. After command line parsing, options are available on the pytest config object via ``config.option.NAME`` where ``NAME`` is usually set @@ -117,7 +122,7 @@ def _getparser(self) -> "MyOptionParser": from _pytest._argcomplete import filescompleter optparser = MyOptionParser(self, self.extra_info, prog=self.prog) - groups = self._groups + [self._anonymous] + groups = [*self._groups, self._anonymous] for group in groups: if group.options: desc = group.description or group.name @@ -148,7 +153,10 @@ def parse_known_args( args: Sequence[Union[str, "os.PathLike[str]"]], namespace: Optional[argparse.Namespace] = None, ) -> argparse.Namespace: - """Parse and return a namespace object with known arguments at this point.""" + """Parse the known arguments at this point. + + :returns: An argparse namespace object. + """ return self.parse_known_and_unknown_args(args, namespace=namespace)[0] def parse_known_and_unknown_args( @@ -156,8 +164,13 @@ def parse_known_and_unknown_args( args: Sequence[Union[str, "os.PathLike[str]"]], 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.""" + """Parse the known arguments at this point, and also return the + remaining unknown arguments. + + :returns: + A tuple containing an argparse namespace object for the known + arguments, and a list of the unknown arguments. + """ optparser = self._getparser() strargs = [os.fspath(x) for x in args] return optparser.parse_known_args(strargs, namespace=namespace) @@ -167,15 +180,15 @@ def addini( name: str, help: str, type: Optional[ - "Literal['string', 'paths', 'pathlist', 'args', 'linelist', 'bool']" + Literal["string", "paths", "pathlist", "args", "linelist", "bool"] ] = None, - default=None, + default: Any = NOT_SET, ) -> None: """Register an ini-file option. - :name: + :param name: Name of the ini-variable. - :type: + :param type: Type of the variable. Can be: * ``string``: a string @@ -185,21 +198,48 @@ def addini( * ``paths``: a list of :class:`pathlib.Path`, separated as in a shell * ``pathlist``: a list of ``py.path``, separated as in a shell + For ``paths`` and ``pathlist`` types, they are considered relative to the ini-file. + In case the execution is happening without an ini-file defined, + they will be considered relative to the current working directory (for example with ``--override-ini``). + .. versionadded:: 7.0 The ``paths`` variable type. + .. versionadded:: 8.1 + Use the current working directory to resolve ``paths`` and ``pathlist`` in the absence of an ini-file. + Defaults to ``string`` if ``None`` or not passed. - :default: + :param 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) `. """ assert type in (None, "string", "paths", "pathlist", "args", "linelist", "bool") + if default is NOT_SET: + default = get_ini_default_for_type(type) + self._inidict[name] = (help, type, default) self._ininames.append(name) +def get_ini_default_for_type( + type: Optional[Literal["string", "paths", "pathlist", "args", "linelist", "bool"]], +) -> Any: + """ + Used by addini to get the default value for a given ini-option type, when + default is not supplied. + """ + if type is None: + return "" + elif type in ("paths", "pathlist", "args", "linelist"): + return [] + elif type == "bool": + return False + else: + return "" + + class ArgumentError(Exception): """Raised if an Argument instance is created with invalid or inconsistent arguments.""" @@ -224,39 +264,15 @@ class Argument: 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: str, **attrs: Any) -> None: - """Store parms in private vars for use in add_argument.""" + """Store params in private vars for use in add_argument.""" self._attrs = attrs self._short_opts: List[str] = [] self._long_opts: List[str] = [] - if "%default" in (attrs.get("help") or ""): - warnings.warn(ARGUMENT_PERCENT_DEFAULT, stacklevel=3) try: - typ = attrs["type"] + self.type = attrs["type"] except KeyError: pass - else: - # This might raise a keyerror as well, don't want to catch that. - if isinstance(typ, str): - if typ == "choice": - warnings.warn( - ARGUMENT_TYPE_STR_CHOICE.format(typ=typ, names=names), - stacklevel=4, - ) - # argparse expects a type here take it from - # the type of the first element - attrs["type"] = type(attrs["choices"][0]) - else: - warnings.warn( - ARGUMENT_TYPE_STR.format(typ=typ, names=names), stacklevel=4 - ) - attrs["type"] = Argument._typ_map[typ] - # Used in test_parseopt -> test_parse_defaultgetter. - self.type = attrs["type"] - else: - self.type = typ try: # Attribute existence is tested in Config._processopt. self.default = attrs["default"] @@ -287,11 +303,6 @@ def attrs(self) -> Mapping[str, Any]: self._attrs[attr] = getattr(self, attr) except AttributeError: pass - if self._attrs.get("help"): - a = self._attrs["help"] - a = a.replace("%default", "%(default)s") - # a = a.replace('%prog', '%(prog)s') - self._attrs["help"] = a return self._attrs def _set_opt_strings(self, opts: Sequence[str]) -> None: @@ -354,24 +365,30 @@ def __init__( self.options: List[Argument] = [] self.parser = parser - def addoption(self, *optnames: str, **attrs: Any) -> None: + def addoption(self, *opts: str, **attrs: Any) -> None: """Add an option to this group. 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``. + + :param opts: + Option names, can be short or long options. + :param attrs: + Same attributes as the argparse library's :meth:`add_argument() + ` function accepts. """ - conflict = set(optnames).intersection( + conflict = set(opts).intersection( name for opt in self.options for name in opt.names() ) if conflict: raise ValueError("option names %s already added" % conflict) - option = Argument(*optnames, **attrs) + option = Argument(*opts, **attrs) self._addoption_instance(option, shortupper=False) - def _addoption(self, *optnames: str, **attrs: Any) -> None: - option = Argument(*optnames, **attrs) + def _addoption(self, *opts: str, **attrs: Any) -> None: + option = Argument(*opts, **attrs) self._addoption_instance(option, shortupper=True) def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None: @@ -398,18 +415,18 @@ def __init__( add_help=False, formatter_class=DropShorterLongHelpFormatter, allow_abbrev=False, + fromfile_prefix_chars="@", ) # 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 if extra_info else {} - def error(self, message: str) -> "NoReturn": + def error(self, message: str) -> NoReturn: """Transform argparse error message into UsageError.""" msg = f"{self.prog}: error: {message}" if hasattr(self._parser, "_config_source_hint"): - # Type ignored because the attribute is set dynamically. - msg = f"{msg} ({self._parser._config_source_hint})" # type: ignore + msg = f"{msg} ({self._parser._config_source_hint})" raise UsageError(self.format_usage() + msg) @@ -431,7 +448,7 @@ def parse_args( # type: ignore getattr(parsed, FILE_OR_DIR).extend(unrecognized) return parsed - if sys.version_info[:2] < (3, 9): # pragma: no cover + if sys.version_info < (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( @@ -439,7 +456,7 @@ def _parse_optional( ) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]: if not arg_string: return None - if not arg_string[0] in self.prefix_chars: + if arg_string[0] not in self.prefix_chars: return None if arg_string in self._option_string_actions: action = self._option_string_actions[arg_string] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/compat.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/compat.py index ba267d21505f7..2856d85d19550 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/compat.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/compat.py @@ -1,15 +1,20 @@ +from __future__ import annotations + import functools -import warnings from pathlib import Path -from typing import Optional +from typing import Any +from typing import Mapping +import warnings + +import pluggy from ..compat import LEGACY_PATH from ..compat import legacy_path from ..deprecated import HOOK_LEGACY_PATH_ARG -from _pytest.nodes import _check_path + # hookname: (Path, LEGACY_PATH) -imply_paths_hooks = { +imply_paths_hooks: Mapping[str, tuple[str, str]] = { "pytest_ignore_collect": ("collection_path", "path"), "pytest_collect_file": ("file_path", "path"), "pytest_pycollect_makemodule": ("module_path", "path"), @@ -18,6 +23,14 @@ } +def _check_path(path: Path, fspath: LEGACY_PATH) -> None: + if Path(fspath) != path: + raise ValueError( + f"Path({fspath!r}) != {path!r}\n" + "if both path and fspath are given they need to be equal" + ) + + class PathAwareHookProxy: """ this helper wraps around hook callers @@ -27,25 +40,24 @@ class PathAwareHookProxy: this may have to be changed later depending on bugs """ - def __init__(self, hook_caller): - self.__hook_caller = hook_caller + def __init__(self, hook_relay: pluggy.HookRelay) -> None: + self._hook_relay = hook_relay - def __dir__(self): - return dir(self.__hook_caller) + def __dir__(self) -> list[str]: + return dir(self._hook_relay) - def __getattr__(self, key, _wraps=functools.wraps): - hook = getattr(self.__hook_caller, key) + def __getattr__(self, key: str) -> pluggy.HookCaller: + hook: pluggy.HookCaller = getattr(self._hook_relay, key) if key not in imply_paths_hooks: self.__dict__[key] = hook return hook else: path_var, fspath_var = imply_paths_hooks[key] - @_wraps(hook) - def fixed_hook(**kw): - - path_value: Optional[Path] = kw.pop(path_var, None) - fspath_value: Optional[LEGACY_PATH] = kw.pop(fspath_var, None) + @functools.wraps(hook) + def fixed_hook(**kw: Any) -> Any: + path_value: Path | None = kw.pop(path_var, None) + fspath_value: LEGACY_PATH | None = kw.pop(fspath_var, None) if fspath_value is not None: warnings.warn( HOOK_LEGACY_PATH_ARG.format( @@ -66,6 +78,8 @@ def fixed_hook(**kw): kw[fspath_var] = fspath_value return hook(**kw) + fixed_hook.name = hook.name # type: ignore[attr-defined] + fixed_hook.spec = hook.spec # type: ignore[attr-defined] fixed_hook.__name__ = key self.__dict__[key] = fixed_hook - return fixed_hook + return fixed_hook # type: ignore[return-value] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/exceptions.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/exceptions.py index 4f1320e758d50..4031ea732f338 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/exceptions.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/exceptions.py @@ -1,4 +1,4 @@ -from _pytest.compat import final +from typing import final @final diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/findpaths.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/findpaths.py index 89ade5f23b91c..9909376de0f8a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/findpaths.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/findpaths.py @@ -1,12 +1,12 @@ import os from pathlib import Path +import sys 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 TYPE_CHECKING from typing import Union import iniconfig @@ -15,9 +15,7 @@ from _pytest.outcomes import fail from _pytest.pathlib import absolutepath from _pytest.pathlib import commonpath - -if TYPE_CHECKING: - from . import Config +from _pytest.pathlib import safe_exists def _parse_ini_config(path: Path) -> iniconfig.IniConfig: @@ -39,7 +37,6 @@ def load_config_dict_from_file( Return None if the file does not contain valid pytest configuration. """ - # Configuration from ini files are obtained from the [pytest] section, if present. if filepath.suffix == ".ini": iniconfig = _parse_ini_config(filepath) @@ -64,13 +61,16 @@ def load_config_dict_from_file( # '.toml' files are considered if they contain a [tool.pytest.ini_options] table. elif filepath.suffix == ".toml": - import tomli + if sys.version_info >= (3, 11): + import tomllib + else: + import tomli as tomllib toml_text = filepath.read_text(encoding="utf-8") try: - config = tomli.loads(toml_text) - except tomli.TOMLDecodeError as exc: - raise UsageError(str(exc)) from exc + config = tomllib.loads(toml_text) + except tomllib.TOMLDecodeError as exc: + raise UsageError(f"{filepath}: {exc}") from exc result = config.get("tool", {}).get("pytest", {}).get("ini_options", None) if result is not None: @@ -86,32 +86,42 @@ def make_scalar(v: object) -> Union[str, List[str]]: def locate_config( + invocation_dir: Path, 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", + ".pytest.ini", "pyproject.toml", "tox.ini", "setup.cfg", ] args = [x for x in args if not str(x).startswith("-")] if not args: - args = [Path.cwd()] + args = [invocation_dir] + found_pyproject_toml: Optional[Path] = None for arg in args: argpath = absolutepath(arg) for base in (argpath, *argpath.parents): for config_name in config_names: p = base / config_name if p.is_file(): + if p.name == "pyproject.toml" and found_pyproject_toml is None: + found_pyproject_toml = p ini_config = load_config_dict_from_file(p) if ini_config is not None: return base, p, ini_config + if found_pyproject_toml is not None: + return found_pyproject_toml.parent, found_pyproject_toml, {} return None, None, {} -def get_common_ancestor(paths: Iterable[Path]) -> Path: +def get_common_ancestor( + invocation_dir: Path, + paths: Iterable[Path], +) -> Path: common_ancestor: Optional[Path] = None for path in paths: if not path.exists(): @@ -128,7 +138,7 @@ def get_common_ancestor(paths: Iterable[Path]) -> Path: if shared is not None: common_ancestor = shared if common_ancestor is None: - common_ancestor = Path.cwd() + common_ancestor = invocation_dir elif common_ancestor.is_file(): common_ancestor = common_ancestor.parent return common_ancestor @@ -146,14 +156,6 @@ def get_dir_from_path(path: Path) -> Path: return path 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 = ( absolutepath(get_file_part_from_node_id(arg)) @@ -168,11 +170,24 @@ def safe_exists(path: Path) -> bool: def determine_setup( + *, inifile: Optional[str], args: Sequence[str], - rootdir_cmd_arg: Optional[str] = None, - config: Optional["Config"] = None, + rootdir_cmd_arg: Optional[str], + invocation_dir: Path, ) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]: + """Determine the rootdir, inifile and ini configuration values from the + command line arguments. + + :param inifile: + The `--inifile` command line argument, if given. + :param args: + The free command line arguments. + :param rootdir_cmd_arg: + The `--rootdir` command line argument, if given. + :param invocation_dir: + The working directory when pytest was invoked. + """ rootdir = None dirs = get_dirs_from_args(args) if inifile: @@ -182,8 +197,8 @@ def determine_setup( if rootdir_cmd_arg is None: rootdir = inipath_.parent else: - ancestor = get_common_ancestor(dirs) - rootdir, inipath, inicfg = locate_config([ancestor]) + ancestor = get_common_ancestor(invocation_dir, dirs) + rootdir, inipath, inicfg = locate_config(invocation_dir, [ancestor]) if rootdir is None and rootdir_cmd_arg is None: for possible_rootdir in (ancestor, *ancestor.parents): if (possible_rootdir / "setup.py").is_file(): @@ -191,23 +206,26 @@ def determine_setup( break else: if dirs != [ancestor]: - rootdir, inipath, inicfg = locate_config(dirs) + rootdir, inipath, inicfg = locate_config(invocation_dir, dirs) if rootdir is None: - if config is not None: - cwd = config.invocation_params.dir - else: - cwd = Path.cwd() - rootdir = get_common_ancestor([cwd, ancestor]) - is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/" - if is_fs_root: + rootdir = get_common_ancestor( + invocation_dir, [invocation_dir, ancestor] + ) + if is_fs_root(rootdir): rootdir = ancestor if rootdir_cmd_arg: rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg)) if not rootdir.is_dir(): raise UsageError( - "Directory '{}' not found. Check your '--rootdir' option.".format( - rootdir - ) + f"Directory '{rootdir}' not found. Check your '--rootdir' option." ) assert rootdir is not None return rootdir, inipath, inicfg or {} + + +def is_fs_root(p: Path) -> bool: + r""" + Return True if the given path is pointing to the root of the + file system ("/" on Unix and "C:\\" on Windows for example). + """ + return os.path.splitdrive(str(p))[1] == os.sep diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/w3c-import.log new file mode 100644 index 0000000000000..2d99ce4faa64c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/argparsing.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/compat.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/exceptions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/config/findpaths.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/debugging.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/debugging.py index 452fb18ac34f0..6ed0c5c7aeebf 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/debugging.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/debugging.py @@ -1,4 +1,6 @@ +# mypy: allow-untyped-defs """Interactive debugging with PDB, the Python Debugger.""" + import argparse import functools import sys @@ -12,6 +14,7 @@ from typing import Type from typing import TYPE_CHECKING from typing import Union +import unittest from _pytest import outcomes from _pytest._code import ExceptionInfo @@ -24,6 +27,7 @@ from _pytest.nodes import Node from _pytest.reports import BaseReport + if TYPE_CHECKING: from _pytest.capture import CaptureManager from _pytest.runner import CallInfo @@ -46,21 +50,21 @@ def pytest_addoption(parser: Parser) -> None: "--pdb", dest="usepdb", action="store_true", - help="start the interactive Python debugger on errors or KeyboardInterrupt.", + help="Start the interactive Python debugger on errors or KeyboardInterrupt", ) group._addoption( "--pdbcls", dest="usepdb_cls", metavar="modulename:classname", type=_validate_usepdb_cls, - help="specify a custom interactive Python debugger for use with --pdb." + help="Specify a custom interactive Python debugger for use with --pdb." "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb", ) group._addoption( "--trace", dest="trace", action="store_true", - help="Immediately break when running each test.", + help="Immediately break when running each test", ) @@ -151,9 +155,7 @@ def _import_pdb_cls(cls, capman: Optional["CaptureManager"]): def _get_pdb_wrapper_class(cls, pdb_cls, capman: Optional["CaptureManager"]): import _pytest.config - # Type ignored because mypy doesn't support "dynamic" - # inheritance like this. - class PytestPdbWrapper(pdb_cls): # type: ignore[valid-type,misc] + class PytestPdbWrapper(pdb_cls): _pytest_capman = capman _continued = False @@ -262,8 +264,7 @@ def _init_pdb(cls, method, *args, **kwargs): elif capturing: tw.sep( ">", - "PDB %s (IO-capturing turned off for %s)" - % (method, capturing), + f"PDB {method} (IO-capturing turned off for {capturing})", ) else: tw.sep(">", f"PDB {method}") @@ -293,7 +294,9 @@ def pytest_exception_interact( sys.stdout.write(out) sys.stdout.write(err) assert call.excinfo is not None - _enter_pdb(node, call.excinfo, report) + + if not isinstance(call.excinfo.value, unittest.SkipTest): + _enter_pdb(node, call.excinfo, report) def pytest_internalerror(self, excinfo: ExceptionInfo[BaseException]) -> None: tb = _postmortem_traceback(excinfo) @@ -301,10 +304,10 @@ def pytest_internalerror(self, excinfo: ExceptionInfo[BaseException]) -> None: class PdbTrace: - @hookimpl(hookwrapper=True) - def pytest_pyfunc_call(self, pyfuncitem) -> Generator[None, None, None]: + @hookimpl(wrapper=True) + def pytest_pyfunc_call(self, pyfuncitem) -> Generator[None, object, object]: wrap_pytest_function_for_tracing(pyfuncitem) - yield + return (yield) def wrap_pytest_function_for_tracing(pyfuncitem): @@ -374,7 +377,8 @@ def _postmortem_traceback(excinfo: ExceptionInfo[BaseException]) -> types.Traceb elif isinstance(excinfo.value, ConftestImportFailure): # A config.ConftestImportFailure is not useful for post_mortem. # Use the underlying exception instead: - return excinfo.value.excinfo[2] + assert excinfo.value.cause.__traceback__ is not None + return excinfo.value.cause.__traceback__ else: assert excinfo._excinfo is not None return excinfo._excinfo[2] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/deprecated.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/deprecated.py index 5248927113ee3..10811d158aaea 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/deprecated.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/deprecated.py @@ -8,13 +8,14 @@ :class:`PytestWarning`, or :class:`UnformattedWarning` in case of warnings which need to format their messages. """ + from warnings import warn from _pytest.warning_types import PytestDeprecationWarning -from _pytest.warning_types import PytestRemovedIn7Warning -from _pytest.warning_types import PytestRemovedIn8Warning +from _pytest.warning_types import PytestRemovedIn9Warning from _pytest.warning_types import UnformattedWarning + # 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 = { @@ -24,18 +25,6 @@ } -FILLFUNCARGS = UnformattedWarning( - PytestRemovedIn7Warning, - "{name} is deprecated, use " - "function._request._fillfixtures() instead if you cannot avoid reaching into internals.", -) - -PYTEST_COLLECT_MODULE = UnformattedWarning( - PytestRemovedIn7Warning, - "pytest.collect.{name} was moved to pytest.{name}\n" - "Please update to the new name.", -) - # This can be* removed pytest 8, but it's harmless and common, so no rush to remove. # * If you're in the future: "could have been". YIELD_FIXTURE = PytestDeprecationWarning( @@ -43,92 +32,37 @@ "Use @pytest.fixture instead; they are the same." ) -MINUS_K_DASH = PytestRemovedIn7Warning( - "The `-k '-expr'` syntax to -k is deprecated.\nUse `-k 'not expr'` instead." -) - -MINUS_K_COLON = PytestRemovedIn7Warning( - "The `-k 'expr:'` syntax to -k is deprecated.\n" - "Please open an issue if you use this and want a replacement." -) - -WARNING_CAPTURED_HOOK = PytestRemovedIn7Warning( - "The pytest_warning_captured is deprecated and will be removed in a future release.\n" - "Please use pytest_warning_recorded instead." -) - -WARNING_CMDLINE_PREPARSE_HOOK = PytestRemovedIn8Warning( - "The pytest_cmdline_preparse hook is deprecated and will be removed in a future release. \n" - "Please use pytest_load_initial_conftests hook instead." -) - -FSCOLLECTOR_GETHOOKPROXY_ISINITPATH = PytestRemovedIn8Warning( - "The gethookproxy() and isinitpath() methods of FSCollector and Package are deprecated; " - "use self.session.gethookproxy() and self.session.isinitpath() instead. " -) - -STRICT_OPTION = PytestRemovedIn8Warning( - "The --strict option is deprecated, use --strict-markers instead." -) - # This deprecation is never really meant to be removed. PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.") -UNITTEST_SKIP_DURING_COLLECTION = PytestRemovedIn8Warning( - "Raising unittest.SkipTest to skip tests during collection is deprecated. " - "Use pytest.skip() instead." -) - -ARGUMENT_PERCENT_DEFAULT = PytestRemovedIn8Warning( - 'pytest now uses argparse. "%default" should be changed to "%(default)s"', -) - -ARGUMENT_TYPE_STR_CHOICE = UnformattedWarning( - PytestRemovedIn8Warning, - "`type` argument to addoption() is the string {typ!r}." - " For choices this is optional and can be omitted, " - " but when supplied should be a type (for example `str` or `int`)." - " (options: {names})", -) - -ARGUMENT_TYPE_STR = UnformattedWarning( - PytestRemovedIn8Warning, - "`type` argument to addoption() is the string {typ!r}, " - " but when supplied should be a type (for example `str` or `int`)." - " (options: {names})", -) - HOOK_LEGACY_PATH_ARG = UnformattedWarning( - PytestRemovedIn8Warning, + PytestRemovedIn9Warning, "The ({pylib_path_arg}: py.path.local) argument is deprecated, please use ({pathlib_path_arg}: pathlib.Path)\n" "see https://docs.pytest.org/en/latest/deprecations.html" "#py-path-local-arguments-for-hooks-replaced-with-pathlib-path", ) NODE_CTOR_FSPATH_ARG = UnformattedWarning( - PytestRemovedIn8Warning, + PytestRemovedIn9Warning, "The (fspath: py.path.local) argument to {node_type_name} is deprecated. " "Please use the (path: pathlib.Path) argument instead.\n" "See https://docs.pytest.org/en/latest/deprecations.html" "#fspath-argument-for-node-constructors-replaced-with-pathlib-path", ) -WARNS_NONE_ARG = PytestRemovedIn8Warning( - "Passing None has been deprecated.\n" - "See https://docs.pytest.org/en/latest/how-to/capture-warnings.html" - "#additional-use-cases-of-warnings-in-tests" - " for alternatives in common use cases." -) - -KEYWORD_MSG_ARG = UnformattedWarning( - PytestRemovedIn8Warning, - "pytest.{func}(msg=...) is now deprecated, use pytest.{func}(reason=...) instead", +HOOK_LEGACY_MARKING = UnformattedWarning( + PytestDeprecationWarning, + "The hook{type} {fullname} uses old-style configuration options (marks or attributes).\n" + "Please use the pytest.hook{type}({hook_opts}) decorator instead\n" + " to configure the hooks.\n" + " See https://docs.pytest.org/en/latest/deprecations.html" + "#configuring-hook-specs-impls-using-markers", ) -INSTANCE_COLLECTOR = PytestRemovedIn8Warning( - "The pytest.Instance collector type is deprecated and is no longer used. " - "See https://docs.pytest.org/en/latest/deprecations.html#the-pytest-instance-collector", +MARKED_FIXTURE = PytestRemovedIn9Warning( + "Marks applied to fixtures have no effect\n" + "See docs: https://docs.pytest.org/en/stable/deprecations.html#applying-a-mark-to-a-fixture-function" ) # You want to make some `__init__` or function "private". diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/doctest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/doctest.py index 0784f431b8ed9..7fff99f37b5f9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/doctest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/doctest.py @@ -1,14 +1,16 @@ +# mypy: allow-untyped-defs """Discover and run doctests in modules and test files.""" + import bdb +from contextlib import contextmanager +import functools import inspect import os +from pathlib import Path import platform import sys import traceback import types -import warnings -from contextlib import contextmanager -from pathlib import Path from typing import Any from typing import Callable from typing import Dict @@ -22,8 +24,8 @@ from typing import Type from typing import TYPE_CHECKING from typing import Union +import warnings -import pytest from _pytest import outcomes from _pytest._code.code import ExceptionInfo from _pytest._code.code import ReprFileLocation @@ -32,16 +34,21 @@ from _pytest.compat import safe_getattr from _pytest.config import Config from _pytest.config.argparsing import Parser -from _pytest.fixtures import FixtureRequest +from _pytest.fixtures import fixture +from _pytest.fixtures import TopRequest from _pytest.nodes import Collector +from _pytest.nodes import Item from _pytest.outcomes import OutcomeException +from _pytest.outcomes import skip from _pytest.pathlib import fnmatch_ex -from _pytest.pathlib import import_path +from _pytest.python import Module from _pytest.python_api import approx from _pytest.warning_types import PytestWarning + if TYPE_CHECKING: import doctest + from typing import Self DOCTEST_REPORT_CHOICE_NONE = "none" DOCTEST_REPORT_CHOICE_CDIFF = "cdiff" @@ -66,26 +73,26 @@ def pytest_addoption(parser: Parser) -> None: parser.addini( "doctest_optionflags", - "option flags for doctests", + "Option flags for doctests", type="args", default=["ELLIPSIS"], ) parser.addini( - "doctest_encoding", "encoding used for doctest files", default="utf-8" + "doctest_encoding", "Encoding used for doctest files", default="utf-8" ) group = parser.getgroup("collect") group.addoption( "--doctest-modules", action="store_true", default=False, - help="run doctests in all .py modules", + help="Run doctests in all .py modules", dest="doctestmodules", ) group.addoption( "--doctest-report", type=str.lower, default="udiff", - help="choose another output format for diffs on doctest failure", + help="Choose another output format for diffs on doctest failure", choices=DOCTEST_REPORT_CHOICES, dest="doctestreport", ) @@ -94,21 +101,21 @@ def pytest_addoption(parser: Parser) -> None: action="append", default=[], metavar="pat", - help="doctests file matching pattern, default: test*.txt", + help="Doctests file matching pattern, default: test*.txt", dest="doctestglob", ) group.addoption( "--doctest-ignore-import-errors", action="store_true", default=False, - help="ignore doctest ImportErrors", + help="Ignore doctest collection errors", dest="doctest_ignore_import_errors", ) group.addoption( "--doctest-continue-on-failure", action="store_true", default=False, - help="for a given doctest, continue to run after the first failure", + help="For a given doctest, continue to run after the first failure", dest="doctest_continue_on_failure", ) @@ -128,11 +135,9 @@ def pytest_collect_file( if config.option.doctestmodules and not any( (_is_setup_py(file_path), _is_main_py(file_path)) ): - mod: DoctestModule = DoctestModule.from_parent(parent, path=file_path) - return mod + return DoctestModule.from_parent(parent, path=file_path) elif _is_doctest(config, file_path, parent): - txt: DoctestTextfile = DoctestTextfile.from_parent(parent, path=file_path) - return txt + return DoctestTextfile.from_parent(parent, path=file_path) return None @@ -246,46 +251,51 @@ def _get_runner( ) -class DoctestItem(pytest.Item): +class DoctestItem(Item): def __init__( self, name: str, parent: "Union[DoctestTextfile, DoctestModule]", - runner: Optional["doctest.DocTestRunner"] = None, - dtest: Optional["doctest.DocTest"] = None, + runner: "doctest.DocTestRunner", + dtest: "doctest.DocTest", ) -> None: super().__init__(name, parent) self.runner = runner self.dtest = dtest + + # Stuff needed for fixture support. self.obj = None - self.fixture_request: Optional[FixtureRequest] = None + fm = self.session._fixturemanager + fixtureinfo = fm.getfixtureinfo(node=self, func=None, cls=None) + self._fixtureinfo = fixtureinfo + self.fixturenames = fixtureinfo.names_closure + self._initrequest() @classmethod - def from_parent( # type: ignore + def from_parent( # type: ignore[override] cls, parent: "Union[DoctestTextfile, DoctestModule]", *, name: str, runner: "doctest.DocTestRunner", dtest: "doctest.DocTest", - ): + ) -> "Self": # incompatible signature due to imposed limits on subclass """The public named constructor.""" return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest) + def _initrequest(self) -> None: + self.funcargs: Dict[str, object] = {} + self._request = TopRequest(self, _ispytest=True) # type: ignore[arg-type] + def setup(self) -> None: - if self.dtest is not None: - self.fixture_request = _setup_fixtures(self) - globs = dict(getfixture=self.fixture_request.getfixturevalue) - for name, value in self.fixture_request.getfixturevalue( - "doctest_namespace" - ).items(): - globs[name] = value - self.dtest.globs.update(globs) + self._request._fillfixtures() + globs = dict(getfixture=self._request.getfixturevalue) + for name, value in self._request.getfixturevalue("doctest_namespace").items(): + globs[name] = value + self.dtest.globs.update(globs) 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: List["doctest.DocTestFailure"] = [] @@ -372,7 +382,6 @@ def repr_failure( # type: ignore[override] return ReprFailDoctest(reprlocation_lines) def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: - assert self.dtest is not None return self.path, self.dtest.lineno, "[doctest] %s" % self.name @@ -392,8 +401,8 @@ def _get_flag_lookup() -> Dict[str, int]: ) -def get_optionflags(parent): - optionflags_str = parent.config.getini("doctest_optionflags") +def get_optionflags(config: Config) -> int: + optionflags_str = config.getini("doctest_optionflags") flag_lookup_table = _get_flag_lookup() flag_acc = 0 for flag in optionflags_str: @@ -401,8 +410,8 @@ def get_optionflags(parent): return flag_acc -def _get_continue_on_failure(config): - continue_on_failure = config.getvalue("doctest_continue_on_failure") +def _get_continue_on_failure(config: Config) -> bool: + continue_on_failure: bool = 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. @@ -411,7 +420,7 @@ def _get_continue_on_failure(config): return continue_on_failure -class DoctestTextfile(pytest.Module): +class DoctestTextfile(Module): obj = None def collect(self) -> Iterable[DoctestItem]: @@ -425,7 +434,7 @@ def collect(self) -> Iterable[DoctestItem]: name = self.path.name globs = {"__name__": "__main__"} - optionflags = get_optionflags(self) + optionflags = get_optionflags(self.config) runner = _get_runner( verbose=False, @@ -449,7 +458,7 @@ def _check_all_skipped(test: "doctest.DocTest") -> None: all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples) if all_skipped: - pytest.skip("all tests skipped by +SKIP option") + skip("all tests skipped by +SKIP option") def _is_mocked(obj: object) -> bool: @@ -477,9 +486,9 @@ def _mock_aware_unwrap( 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 " + f"Got {e!r} when unwrapping {func!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), + "https://github.com/pytest-dev/pytest/issues/5080", PytestWarning, ) raise @@ -491,7 +500,7 @@ def _mock_aware_unwrap( inspect.unwrap = real_unwrap -class DoctestModule(pytest.Module): +class DoctestModule(Module): def collect(self) -> Iterable[DoctestItem]: import doctest @@ -528,29 +537,43 @@ def _find( if _is_mocked(obj): return with _patch_unwrap_mock_aware(): - # Type ignored because this is a private function. super()._find( # type:ignore[misc] tests, obj, name, module, source_lines, globs, seen ) - if self.path.name == "conftest.py": - module = self.config.pluginmanager._importconftest( - self.path, - self.config.getoption("importmode"), - rootpath=self.config.rootpath, - ) - else: - try: - module = import_path(self.path, root=self.config.rootpath) - except ImportError: - if self.config.getvalue("doctest_ignore_import_errors"): - pytest.skip("unable to import module %r" % self.path) - else: - raise + if sys.version_info < (3, 13): + + def _from_module(self, module, object): + """`cached_property` objects are never considered a part + of the 'current module'. As such they are skipped by doctest. + Here we override `_from_module` to check the underlying + function instead. https://github.com/python/cpython/issues/107995 + """ + if isinstance(object, functools.cached_property): + object = object.func + + # Type ignored because this is a private function. + return super()._from_module(module, object) # type: ignore[misc] + + else: # pragma: no cover + pass + + try: + module = self.obj + except Collector.CollectError: + if self.config.getvalue("doctest_ignore_import_errors"): + skip("unable to import module %r" % self.path) + else: + raise + + # While doctests currently don't support fixtures directly, we still + # need to pick up autouse fixtures. + self.session._fixturemanager.parsefactories(self) + # Uses internal doctest module parsing mechanism. finder = MockAwareDocTestFinder() - optionflags = get_optionflags(self) + optionflags = get_optionflags(self.config) runner = _get_runner( verbose=False, optionflags=optionflags, @@ -565,22 +588,6 @@ def _find( ) -def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest: - """Used by DoctestTextfile and DoctestItem to setup fixture information.""" - - def func() -> None: - pass - - doctest_item.funcargs = {} # type: ignore[attr-defined] - fm = doctest_item.session._fixturemanager - doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined] - node=doctest_item, func=func, cls=None, funcargs=False - ) - fixture_request = FixtureRequest(doctest_item, _ispytest=True) - fixture_request._fillfixtures() - return fixture_request - - def _init_checker_class() -> Type["doctest.OutputChecker"]: import doctest import re @@ -656,7 +663,7 @@ def _remove_unwanted_precision(self, want: str, got: str) -> str: precision = 0 if fraction is None else len(fraction) if exponent is not None: precision -= int(exponent) - if float(w.group()) == approx(float(g.group()), abs=10 ** -precision): + 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. @@ -727,8 +734,19 @@ def _get_report_choice(key: str) -> int: }[key] -@pytest.fixture(scope="session") +@fixture(scope="session") def doctest_namespace() -> Dict[str, Any]: """Fixture that returns a :py:class:`dict` that will be injected into the - namespace of doctests.""" + namespace of doctests. + + Usually this fixture is used in conjunction with another ``autouse`` fixture: + + .. code-block:: python + + @pytest.fixture(autouse=True) + def add_np(doctest_namespace): + doctest_namespace["np"] = numpy + + For more details: :ref:`doctest_namespace`. + """ return dict() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/faulthandler.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/faulthandler.py index aaee307ff2c6d..083bcb837399c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/faulthandler.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/faulthandler.py @@ -1,24 +1,22 @@ -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.stash import StashKey +import pytest -fault_handler_stderr_key = StashKey[TextIO]() -fault_handler_originally_enabled_key = StashKey[bool]() +fault_handler_original_stderr_fd_key = StashKey[int]() +fault_handler_stderr_fd_key = StashKey[int]() def pytest_addoption(parser: Parser) -> None: help = ( "Dump the traceback of all threads if a test takes " - "more than TIMEOUT seconds to finish." + "more than TIMEOUT seconds to finish" ) parser.addini("faulthandler_timeout", help, default=0.0) @@ -26,10 +24,16 @@ def pytest_addoption(parser: Parser) -> None: def pytest_configure(config: Config) -> None: import faulthandler - stderr_fd_copy = os.dup(get_stderr_fileno()) - config.stash[fault_handler_stderr_key] = open(stderr_fd_copy, "w") - config.stash[fault_handler_originally_enabled_key] = faulthandler.is_enabled() - faulthandler.enable(file=config.stash[fault_handler_stderr_key]) + # at teardown we want to restore the original faulthandler fileno + # but faulthandler has no api to return the original fileno + # so here we stash the stderr fileno to be used at teardown + # sys.stderr and sys.__stderr__ may be closed or patched during the session + # so we can't rely on their values being good at that point (#11572). + stderr_fileno = get_stderr_fileno() + if faulthandler.is_enabled(): + config.stash[fault_handler_original_stderr_fd_key] = stderr_fileno + config.stash[fault_handler_stderr_fd_key] = os.dup(stderr_fileno) + faulthandler.enable(file=config.stash[fault_handler_stderr_fd_key]) def pytest_unconfigure(config: Config) -> None: @@ -37,12 +41,13 @@ def pytest_unconfigure(config: Config) -> None: faulthandler.disable() # Close the dup file installed during pytest_configure. - if fault_handler_stderr_key in config.stash: - config.stash[fault_handler_stderr_key].close() - del config.stash[fault_handler_stderr_key] - if config.stash.get(fault_handler_originally_enabled_key, False): - # Re-enable the faulthandler if it was originally enabled. - faulthandler.enable(file=get_stderr_fileno()) + if fault_handler_stderr_fd_key in config.stash: + os.close(config.stash[fault_handler_stderr_fd_key]) + del config.stash[fault_handler_stderr_fd_key] + # Re-enable the faulthandler if it was originally enabled. + if fault_handler_original_stderr_fd_key in config.stash: + faulthandler.enable(config.stash[fault_handler_original_stderr_fd_key]) + del config.stash[fault_handler_original_stderr_fd_key] def get_stderr_fileno() -> int: @@ -53,7 +58,7 @@ def get_stderr_fileno() -> int: if fileno == -1: raise AttributeError() return fileno - except (AttributeError, io.UnsupportedOperation): + except (AttributeError, ValueError): # 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. @@ -64,20 +69,20 @@ def get_timeout_config_value(config: Config) -> float: return float(config.getini("faulthandler_timeout") or 0.0) -@pytest.hookimpl(hookwrapper=True, trylast=True) -def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: +@pytest.hookimpl(wrapper=True, trylast=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: timeout = get_timeout_config_value(item.config) - stderr = item.config.stash[fault_handler_stderr_key] - if timeout > 0 and stderr is not None: + if timeout > 0: import faulthandler + stderr = item.config.stash[fault_handler_stderr_fd_key] faulthandler.dump_traceback_later(timeout, file=stderr) try: - yield + return (yield) finally: faulthandler.cancel_dump_traceback_later() else: - yield + return (yield) @pytest.hookimpl(tryfirst=True) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/fixtures.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/fixtures.py index fddff931c513d..7fd63f937c193 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/fixtures.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/fixtures.py @@ -1,45 +1,46 @@ +# mypy: allow-untyped-defs +import abc +from collections import defaultdict +from collections import deque +import dataclasses import functools import inspect import os -import sys -import warnings -from collections import defaultdict -from collections import deque -from contextlib import suppress from pathlib import Path -from types import TracebackType +import sys +from typing import AbstractSet from typing import Any from typing import Callable from typing import cast from typing import Dict +from typing import Final +from typing import final from typing import Generator from typing import Generic from typing import Iterable from typing import Iterator from typing import List from typing import MutableMapping +from typing import NoReturn from typing import Optional from typing import overload from typing import Sequence from typing import Set from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar from typing import Union - -import attr +import warnings import _pytest from _pytest import nodes from _pytest._code import getfslineno +from _pytest._code import Source 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 assert_never -from _pytest.compat import final from _pytest.compat import get_real_func from _pytest.compat import get_real_method from _pytest.compat import getfuncargnames @@ -47,30 +48,35 @@ from _pytest.compat import getlocation from _pytest.compat import is_generator from _pytest.compat import NOTSET +from _pytest.compat import NotSetType from _pytest.compat import safe_getattr from _pytest.config import _PluggyPlugin from _pytest.config import Config +from _pytest.config import ExitCode from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest -from _pytest.deprecated import FILLFUNCARGS +from _pytest.deprecated import MARKED_FIXTURE from _pytest.deprecated import YIELD_FIXTURE from _pytest.mark import Mark from _pytest.mark import ParameterSet from _pytest.mark.structures import MarkDecorator from _pytest.outcomes import fail +from _pytest.outcomes import skip from _pytest.outcomes import TEST_OUTCOME from _pytest.pathlib import absolutepath from _pytest.pathlib import bestrelpath +from _pytest.scope import _ScopeName from _pytest.scope import HIGH_SCOPES from _pytest.scope import Scope -from _pytest.stash import StashKey + + +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup if TYPE_CHECKING: from typing import Deque - from typing import NoReturn - from _pytest.scope import _ScopeName from _pytest.main import Session from _pytest.python import CallSpec2 from _pytest.python import Function @@ -98,13 +104,13 @@ None, # Cache key. object, - # Exc info if raised. - Tuple[Type[BaseException], BaseException, TracebackType], + # Exception if raised. + BaseException, ], ] -@attr.s(frozen=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class PseudoFixtureDef(Generic[FixtureValue]): cached_result: "_FixtureCachedResult[FixtureValue]" _scope: Scope @@ -114,28 +120,25 @@ def pytest_sessionstart(session: "Session") -> None: session._fixturemanager = FixtureManager(session) -def get_scope_package(node, fixturedef: "FixtureDef[object]"): - import pytest +def get_scope_package( + node: nodes.Item, + fixturedef: "FixtureDef[object]", +) -> Optional[nodes.Node]: + from _pytest.python import Package - cls = pytest.Package - current = node - fixture_package_name = "{}/{}".format(fixturedef.baseid, "__init__.py") - while current and ( - type(current) is not cls or fixture_package_name != current.nodeid - ): - current = current.parent - if current is None: - return node.session - return current + for parent in node.iter_parents(): + if isinstance(parent, Package) and parent.nodeid == fixturedef.baseid: + return parent + return node.session -def get_scope_node( - node: nodes.Node, scope: Scope -) -> Optional[Union[nodes.Item, nodes.Collector]]: +def get_scope_node(node: nodes.Node, scope: Scope) -> Optional[nodes.Node]: import _pytest.python if scope is Scope.Function: - return node.getparent(nodes.Item) + # Type ignored because this is actually safe, see: + # https://github.com/python/mypy/issues/4717 + return node.getparent(nodes.Item) # type: ignore[type-abstract] elif scope is Scope.Class: return node.getparent(_pytest.python.Class) elif scope is Scope.Module: @@ -148,126 +151,52 @@ def get_scope_node( assert_never(scope) -# Used for storing artificial fixturedefs for direct parametrization. -name2pseudofixturedef_key = StashKey[Dict[str, "FixtureDef[Any]"]]() - - -def add_funcarg_pseudo_fixture_def( - collector: nodes.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: - # This function call does not have direct parametrization. - return - # Collect funcargs of all callspecs into a list of values. - arg2params: Dict[str, List[object]] = {} - arg2scope: Dict[str, Scope] = {} - for callspec in metafunc._calls: - for argname, argvalue in callspec.funcargs.items(): - assert argname not in callspec.params - callspec.params[argname] = argvalue - arg2params_list = arg2params.setdefault(argname, []) - callspec.indices[argname] = len(arg2params_list) - arg2params_list.append(argvalue) - if argname not in arg2scope: - scope = callspec._arg2scope.get(argname, Scope.Function) - arg2scope[argname] = scope - callspec.funcargs.clear() - - # 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 - # 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. - scope = arg2scope[argname] - node = None - if scope is not Scope.Function: - node = get_scope_node(collector, scope) - if node is None: - assert scope is Scope.Class and isinstance( - collector, _pytest.python.Module - ) - # Use module-level collector for class-scope (for now). - node = collector - if node is None: - name2pseudofixturedef = None - else: - default: Dict[str, FixtureDef[Any]] = {} - name2pseudofixturedef = node.stash.setdefault( - name2pseudofixturedef_key, default - ) - if name2pseudofixturedef is not None and argname in name2pseudofixturedef: - arg2fixturedefs[argname] = [name2pseudofixturedef[argname]] - else: - fixturedef = FixtureDef( - fixturemanager=fixturemanager, - baseid="", - argname=argname, - func=get_direct_param_fixture_func, - scope=arg2scope[argname], - params=valuelist, - unittest=False, - ids=None, - ) - arg2fixturedefs[argname] = [fixturedef] - if name2pseudofixturedef is not None: - name2pseudofixturedef[argname] = fixturedef - - def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]: """Return fixturemarker or None if it doesn't exist or raised exceptions.""" - try: - fixturemarker: Optional[FixtureFunctionMarker] = getattr( - obj, "_pytestfixturefunction", None - ) - 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 + return cast( + Optional[FixtureFunctionMarker], + safe_getattr(obj, "_pytestfixturefunction", None), + ) -# Parametrized fixture key, helper alias for code below. -_Key = Tuple[object, ...] +@dataclasses.dataclass(frozen=True) +class FixtureArgKey: + argname: str + param_index: int + scoped_item_path: Optional[Path] + item_cls: Optional[type] -def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_Key]: +def get_parametrized_fixture_keys( + item: nodes.Item, scope: Scope +) -> Iterator[FixtureArgKey]: """Return list of keys for all parametrized arguments which match the specified scope.""" assert scope is not Scope.Function try: - callspec = item.callspec # type: ignore[attr-defined] + callspec: CallSpec2 = item.callspec # type: ignore[attr-defined] except AttributeError: - pass - else: - cs: CallSpec2 = callspec - # cs.indices.items() is random order of argnames. Need to - # sort this so that different calls to - # get_parametrized_fixture_keys will be deterministic. - for argname, param_index in sorted(cs.indices.items()): - if cs._arg2scope[argname] != scope: - continue - if scope is Scope.Session: - key: _Key = (argname, param_index) - elif scope is Scope.Package: - key = (argname, param_index, item.path.parent) - elif scope is Scope.Module: - key = (argname, param_index, item.path) - elif scope is Scope.Class: - item_cls = item.cls # type: ignore[attr-defined] - key = (argname, param_index, item.path, item_cls) - else: - assert_never(scope) - yield key + return + for argname in callspec.indices: + if callspec._arg2scope[argname] != scope: + continue + + item_cls = None + if scope is Scope.Session: + scoped_item_path = None + elif scope is Scope.Package: + scoped_item_path = item.path + elif scope is Scope.Module: + scoped_item_path = item.path + elif scope is Scope.Class: + scoped_item_path = item.path + item_cls = item.cls # type: ignore[attr-defined] + else: + assert_never(scope) + + param_index = callspec.indices[argname] + yield FixtureArgKey(argname, param_index, scoped_item_path, item_cls) # Algorithm for sorting on a per-parametrized resource setup basis. @@ -277,19 +206,17 @@ def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_K def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]: - argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]] = {} - items_by_argkey: Dict[Scope, Dict[_Key, Deque[nodes.Item]]] = {} + argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]] = {} + items_by_argkey: Dict[Scope, Dict[FixtureArgKey, Deque[nodes.Item]]] = {} for scope in HIGH_SCOPES: - d: Dict[nodes.Item, Dict[_Key, None]] = {} - argkeys_cache[scope] = d - item_d: Dict[_Key, Deque[nodes.Item]] = defaultdict(deque) - items_by_argkey[scope] = item_d + scoped_argkeys_cache = argkeys_cache[scope] = {} + scoped_items_by_argkey = items_by_argkey[scope] = defaultdict(deque) for item in items: keys = dict.fromkeys(get_parametrized_fixture_keys(item, scope), None) if keys: - d[item] = keys + scoped_argkeys_cache[item] = keys for key in keys: - item_d[key].append(item) + scoped_items_by_argkey[key].append(item) items_dict = dict.fromkeys(items, None) return list( reorder_items_atscope(items_dict, argkeys_cache, items_by_argkey, Scope.Session) @@ -298,8 +225,8 @@ def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]: def fix_cache_order( item: nodes.Item, - argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]], - items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]], + argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]], + items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]], ) -> None: for scope in HIGH_SCOPES: for key in argkeys_cache[scope].get(item, []): @@ -308,13 +235,13 @@ def fix_cache_order( def reorder_items_atscope( items: Dict[nodes.Item, None], - argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]], - items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]], + argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]], + items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]], scope: Scope, ) -> Dict[nodes.Item, None]: if scope is Scope.Function or len(items) < 3: return items - ignore: Set[Optional[_Key]] = set() + ignore: Set[Optional[FixtureArgKey]] = set() items_deque = deque(items) items_done: Dict[nodes.Item, None] = {} scoped_items_by_argkey = items_by_argkey[scope] @@ -352,54 +279,35 @@ def reorder_items_atscope( return items_done -def _fillfuncargs(function: "Function") -> None: - """Fill missing fixtures for a test function, old public API (deprecated).""" - warnings.warn(FILLFUNCARGS.format(name="pytest._fillfuncargs()"), stacklevel=2) - _fill_fixtures_impl(function) - - -def fillfixtures(function: "Function") -> None: - """Fill missing fixtures for a test function (deprecated).""" - warnings.warn( - FILLFUNCARGS.format(name="_pytest.fixtures.fillfixtures()"), stacklevel=2 - ) - _fill_fixtures_impl(function) - - -def _fill_fixtures_impl(function: "Function") -> None: - """Internal implementation to fill fixtures on the given function object.""" - try: - request = function._request - except AttributeError: - # XXX this special code path is only expected to execute - # 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, _ispytest=True) - fm.session._setupstate.setup(function) - request._fillfixtures() - # Prune out funcargs for jstests. - function.funcargs = {name: function.funcargs[name] for name in fi.argnames} - else: - request._fillfixtures() +@dataclasses.dataclass(frozen=True) +class FuncFixtureInfo: + """Fixture-related information for a fixture-requesting item (e.g. test + function). + This is used to examine the fixtures which an item requests statically + (known during collection). This includes autouse fixtures, fixtures + requested by the `usefixtures` marker, fixtures requested in the function + parameters, and the transitive closure of these. -def get_direct_param_fixture_func(request): - return request.param + An item may also request fixtures dynamically (using `request.getfixturevalue`); + these are not reflected here. + """ + __slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs") -@attr.s(slots=True, auto_attribs=True) -class FuncFixtureInfo: - # Original function argument names. + # Fixture names that the item requests directly by function parameters. argnames: Tuple[str, ...] - # Argnames that function immediately requires. These include argnames + - # fixture names specified via usefixtures and via autouse=True in fixture - # definitions. + # Fixture names that the item immediately requires. These include + # argnames + fixture names specified via usefixtures and via autouse=True in + # fixture definitions. initialnames: Tuple[str, ...] + # The transitive closure of the fixture names that the item requires. + # Note: can't include dynamic dependencies (`request.getfixturevalue` calls). names_closure: List[str] + # A map from a fixture name in the transitive closure to the FixtureDefs + # matching the name which are applicable to this function. + # There may be multiple overriding fixtures with the same name. The + # sequence is ordered from furthest to closes to the function. name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]] def prune_dependency_tree(self) -> None: @@ -417,7 +325,7 @@ def prune_dependency_tree(self) -> None: 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 something 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 @@ -430,66 +338,83 @@ def prune_dependency_tree(self) -> None: self.names_closure[:] = sorted(closure, key=self.names_closure.index) -class FixtureRequest: - """A request for a fixture from a test or fixture function. +class FixtureRequest(abc.ABC): + """The type of the ``request`` fixture. - 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 a + ``param`` attribute in case the fixture is parametrized. """ - def __init__(self, pyfuncitem, *, _ispytest: bool = False) -> None: + def __init__( + self, + pyfuncitem: "Function", + fixturename: Optional[str], + arg2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]], + fixture_defs: Dict[str, "FixtureDef[Any]"], + *, + _ispytest: bool = False, + ) -> None: check_ispytest(_ispytest) - self._pyfuncitem = pyfuncitem #: Fixture for which this request is being performed. - self.fixturename: Optional[str] = None - self._scope = Scope.Function - self._fixture_defs: Dict[str, FixtureDef[Any]] = {} - fixtureinfo: FuncFixtureInfo = pyfuncitem._fixtureinfo - self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy() - self._arg2index: Dict[str, int] = {} - self._fixturemanager: FixtureManager = pyfuncitem.session._fixturemanager + self.fixturename: Final = fixturename + self._pyfuncitem: Final = pyfuncitem + # The FixtureDefs for each fixture name requested by this item. + # Starts from the statically-known fixturedefs resolved during + # collection. Dynamically requested fixtures (using + # `request.getfixturevalue("foo")`) are added dynamically. + self._arg2fixturedefs: Final = arg2fixturedefs + # The evaluated argnames so far, mapping to the FixtureDef they resolved + # to. + self._fixture_defs: Final = fixture_defs + # Notes on the type of `param`: + # -`request.param` is only defined in parametrized fixtures, and will raise + # AttributeError otherwise. Python typing has no notion of "undefined", so + # this cannot be reflected in the type. + # - Technically `param` is only (possibly) defined on SubRequest, not + # FixtureRequest, but the typing of that is still in flux so this cheats. + # - In the future we might consider using a generic for the param type, but + # for now just using Any. + self.param: Any + + @property + def _fixturemanager(self) -> "FixtureManager": + return self._pyfuncitem.session._fixturemanager + + @property + @abc.abstractmethod + def _scope(self) -> Scope: + raise NotImplementedError() @property - def scope(self) -> "_ScopeName": + def scope(self) -> _ScopeName: """Scope string, one of "function", "class", "module", "package", "session".""" return self._scope.value + @abc.abstractmethod + def _check_scope( + self, + requested_fixturedef: Union["FixtureDef[object]", PseudoFixtureDef[object]], + requested_scope: Scope, + ) -> None: + raise NotImplementedError() + @property def fixturenames(self) -> List[str]: """Names of all active fixtures in this request.""" - result = list(self._pyfuncitem._fixtureinfo.names_closure) + result = list(self._pyfuncitem.fixturenames) result.extend(set(self._fixture_defs).difference(result)) return result @property + @abc.abstractmethod def node(self): """Underlying collection node (depends on current request scope).""" - return self._getscopeitem(self._scope) - - 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 - # getfixturevalue(argname) usage which was naturally - # not known at parsing/collection time. - assert self._pyfuncitem.parent is not None - parentid = self._pyfuncitem.parent.nodeid - fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid) - # 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) - self._arg2index[argname] = index - return fixturedefs[index] + raise NotImplementedError() @property def config(self) -> Config: """The pytest config object associated with this request.""" - return self._pyfuncitem.config # type: ignore[no-any-return] + return self._pyfuncitem.config @property def function(self): @@ -512,26 +437,25 @@ def cls(self): @property def instance(self): """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) + if self.scope != "function": + return None + return getattr(self._pyfuncitem, "instance", None) @property def module(self): """Python module object where the test function was collected.""" if self.scope not in ("function", "class", "module"): raise AttributeError(f"module not available in {self.scope}-scoped context") - return self._pyfuncitem.getparent(_pytest.python.Module).obj + mod = self._pyfuncitem.getparent(_pytest.python.Module) + assert mod is not None + return mod.obj @property def path(self) -> Path: + """Path where the test function was collected.""" if self.scope not in ("function", "class", "module", "package"): raise AttributeError(f"path not available in {self.scope}-scoped context") - # TODO: Remove ignore once _pyfuncitem is properly typed. - return self._pyfuncitem.path # type: ignore + return self._pyfuncitem.path @property def keywords(self) -> MutableMapping[str, Any]: @@ -542,17 +466,13 @@ def keywords(self) -> MutableMapping[str, Any]: @property def session(self) -> "Session": """Pytest session object.""" - return self._pyfuncitem.session # type: ignore[no-any-return] + return self._pyfuncitem.session + @abc.abstractmethod 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: Callable[[], object], scope) -> None: - node = self._getscopeitem(scope) - node.addfinalizer(finalizer) + """Add finalizer/teardown function to be called without arguments after + the last test within the requesting test context finished execution.""" + raise NotImplementedError() def applymarker(self, marker: Union[str, MarkDecorator]) -> None: """Apply a marker to a single test function invocation. @@ -561,21 +481,17 @@ def applymarker(self, marker: Union[str, MarkDecorator]) -> None: on all function invocations. :param marker: - A :class:`pytest.MarkDecorator` object created by a call - to ``pytest.mark.NAME(...)``. + An object created by a call to ``pytest.mark.NAME(...)``. """ self.node.add_marker(marker) - def raiseerror(self, msg: Optional[str]) -> "NoReturn": - """Raise a FixtureLookupError with the given message.""" - raise self._fixturemanager.FixtureLookupError(None, self, msg) + def raiseerror(self, msg: Optional[str]) -> NoReturn: + """Raise a FixtureLookupError exception. - 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) + :param msg: + An optional custom error message. + """ + raise FixtureLookupError(None, self, msg) def getfixturevalue(self, argname: str) -> Any: """Dynamically run a named fixture function. @@ -585,186 +501,199 @@ def getfixturevalue(self, argname: str) -> Any: setup time, you may use this function to retrieve it inside a fixture or test function body. + This method can be used during the test setup phase or the test run + phase, but during the test teardown phase a fixture's value may not + be available. + + :param argname: + The fixture name. :raises pytest.FixtureLookupError: If the given fixture could not be found. """ + # Note that in addition to the use case described in the docstring, + # getfixturevalue() is also called by pytest itself during item and fixture + # setup to evaluate the fixtures that are requested statically + # (using function parameters, autouse, etc). + fixturedef = self._get_active_fixturedef(argname) - assert fixturedef.cached_result is not None + assert fixturedef.cached_result is not None, ( + f'The fixture value for "{argname}" is not available. ' + "This can happen when the fixture has already been torn down." + ) return fixturedef.cached_result[0] - def _get_active_fixturedef( - self, argname: str - ) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]: - try: - return self._fixture_defs[argname] - except KeyError: - try: - fixturedef = self._getnextfixturedef(argname) - except FixtureLookupError: - if argname == "request": - cached_result = (self, [0], None) - return PseudoFixtureDef(cached_result, Scope.Function) - raise - # 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 _iter_chain(self) -> Iterator["SubRequest"]: + """Yield all SubRequests in the chain, from self up. - def _get_fixturestack(self) -> List["FixtureDef[Any]"]: + Note: does *not* yield the TopRequest. + """ current = self - values: List[FixtureDef[Any]] = [] while isinstance(current, SubRequest): - values.append(current._fixturedef) # type: ignore[has-type] + yield current current = current._parent_request - values.reverse() - return values - 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. + def _get_active_fixturedef( + self, argname: str + ) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]: + if argname == "request": + cached_result = (self, [0], None) + return PseudoFixtureDef(cached_result, Scope.Function) - 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) - argname = fixturedef.argname - funcitem = self._pyfuncitem - scope = fixturedef._scope + # If we already finished computing a fixture by this name in this item, + # return it. + fixturedef = self._fixture_defs.get(argname) + if fixturedef is not None: + self._check_scope(fixturedef, fixturedef._scope) + return fixturedef + + # Find the appropriate fixturedef. + fixturedefs = self._arg2fixturedefs.get(argname, None) + if fixturedefs is None: + # We arrive here because of a dynamic call to + # getfixturevalue(argname) which was naturally + # not known at parsing/collection time. + fixturedefs = self._fixturemanager.getfixturedefs(argname, self._pyfuncitem) + if fixturedefs is not None: + self._arg2fixturedefs[argname] = fixturedefs + # No fixtures defined with this name. + if fixturedefs is None: + raise FixtureLookupError(argname, self) + # The are no fixtures with this name applicable for the function. + if not fixturedefs: + raise FixtureLookupError(argname, self) + # A fixture may override another fixture with the same name, e.g. a + # fixture in a module can override a fixture in a conftest, a fixture in + # a class can override a fixture in the module, and so on. + # An overriding fixture can request its own name (possibly indirectly); + # in this case it gets the value of the fixture it overrides, one level + # up. + # Check how many `argname`s deep we are, and take the next one. + # `fixturedefs` is sorted from furthest to closest, so use negative + # indexing to go in reverse. + index = -1 + for request in self._iter_chain(): + if request.fixturename == argname: + index -= 1 + # If already consumed all of the available levels, fail. + if -index > len(fixturedefs): + raise FixtureLookupError(argname, self) + fixturedef = fixturedefs[index] + + # Prepare a SubRequest object for calling the fixture. try: - param = funcitem.callspec.getparam(argname) - except (AttributeError, ValueError): + callspec = self._pyfuncitem.callspec + except AttributeError: + callspec = None + if callspec is not None and argname in callspec.params: + param = callspec.params[argname] + param_index = callspec.indices[argname] + # The parametrize invocation scope overrides the fixture's scope. + scope = callspec._arg2scope[argname] + else: param = NOTSET param_index = 0 - has_params = fixturedef.params is not None - fixtures_not_supported = getattr(funcitem, "nofuncargs", False) - if has_params and fixtures_not_supported: - msg = ( - "{name} does not support fixtures, maybe unittest.TestCase subclass?\n" - "Node id: {nodeid}\n" - "Function type: {typename}" - ).format( - name=funcitem.name, - nodeid=funcitem.nodeid, - typename=type(funcitem).__name__, - ) - fail(msg, pytrace=False) - if has_params: - frame = inspect.stack()[3] - frameinfo = inspect.getframeinfo(frame[0]) - source_path = absolutepath(frameinfo.filename) - source_lineno = frameinfo.lineno - try: - source_path_str = str( - source_path.relative_to(funcitem.config.rootpath) - ) - except ValueError: - source_path_str = str(source_path) - msg = ( - "The requested fixture has no parameter defined for test:\n" - " {}\n\n" - "Requested fixture '{}' defined in:\n{}" - "\n\nRequested here:\n{}:{}".format( - funcitem.nodeid, - fixturedef.argname, - getlocation(fixturedef.func, funcitem.config.rootpath), - 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. - with suppress(KeyError): - scope = funcitem.callspec._arg2scope[argname] - + scope = fixturedef._scope + self._check_fixturedef_without_param(fixturedef) + self._check_scope(fixturedef, scope) subrequest = SubRequest( self, scope, param, param_index, fixturedef, _ispytest=True ) - # Check if a higher-level scoped fixture accesses a lower level one. - subrequest._check_scope(argname, self._scope, scope) - try: - # Call the fixture function. - fixturedef.execute(request=subrequest) - finally: - self._schedule_finalizers(fixturedef, subrequest) + # Make sure the fixture value is cached, running it if it isn't + fixturedef.execute(request=subrequest) - def _schedule_finalizers( - self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest" - ) -> None: - # If fixture function failed it might have registered finalizers. - subrequest.node.addfinalizer(lambda: fixturedef.finish(request=subrequest)) + self._fixture_defs[argname] = fixturedef + return fixturedef + + def _check_fixturedef_without_param(self, fixturedef: "FixtureDef[object]") -> None: + """Check that this request is allowed to execute this fixturedef without + a param.""" + funcitem = self._pyfuncitem + has_params = fixturedef.params is not None + fixtures_not_supported = getattr(funcitem, "nofuncargs", False) + if has_params and fixtures_not_supported: + msg = ( + f"{funcitem.name} does not support fixtures, maybe unittest.TestCase subclass?\n" + f"Node id: {funcitem.nodeid}\n" + f"Function type: {type(funcitem).__name__}" + ) + fail(msg, pytrace=False) + if has_params: + frame = inspect.stack()[3] + frameinfo = inspect.getframeinfo(frame[0]) + source_path = absolutepath(frameinfo.filename) + source_lineno = frameinfo.lineno + try: + source_path_str = str(source_path.relative_to(funcitem.config.rootpath)) + except ValueError: + source_path_str = str(source_path) + location = getlocation(fixturedef.func, funcitem.config.rootpath) + msg = ( + "The requested fixture has no parameter defined for test:\n" + f" {funcitem.nodeid}\n\n" + f"Requested fixture '{fixturedef.argname}' defined in:\n" + f"{location}\n\n" + f"Requested here:\n" + f"{source_path_str}:{source_lineno}" + ) + fail(msg, pytrace=False) + + def _get_fixturestack(self) -> List["FixtureDef[Any]"]: + values = [request._fixturedef for request in self._iter_chain()] + values.reverse() + return values + + +@final +class TopRequest(FixtureRequest): + """The type of the ``request`` fixture in a test function.""" + + def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None: + super().__init__( + fixturename=None, + pyfuncitem=pyfuncitem, + arg2fixturedefs=pyfuncitem._fixtureinfo.name2fixturedefs.copy(), + fixture_defs={}, + _ispytest=_ispytest, + ) + + @property + def _scope(self) -> Scope: + return Scope.Function def _check_scope( self, - argname: str, - invoking_scope: Scope, + requested_fixturedef: Union["FixtureDef[object]", PseudoFixtureDef[object]], requested_scope: Scope, ) -> None: - if argname == "request": - return - if invoking_scope > requested_scope: - # Try to report something helpful. - text = "\n".join(self._factorytraceback()) - fail( - f"ScopeMismatch: You tried to access the {requested_scope.value} scoped " - f"fixture {argname} with a {invoking_scope.value} scoped request object, " - f"involved factories:\n{text}", - pytrace=False, - ) + # TopRequest always has function scope so always valid. + pass - def _factorytraceback(self) -> List[str]: - lines = [] - for fixturedef in self._get_fixturestack(): - factory = fixturedef.func - fs, lineno = getfslineno(factory) - if isinstance(fs, Path): - session: Session = self._pyfuncitem.session - p = bestrelpath(session.path, fs) - else: - p = fs - args = _format_args(factory) - lines.append("%s:%d: def %s%s" % (p, lineno + 1, factory.__name__, args)) - return lines - - def _getscopeitem( - self, scope: Union[Scope, "_ScopeName"] - ) -> Union[nodes.Item, nodes.Collector]: - if isinstance(scope, str): - scope = Scope(scope) - if scope is Scope.Function: - # This might also be a non-function Item despite its attribute name. - node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem - elif scope is Scope.Package: - # 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 is Scope.Class: - # 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 + @property + def node(self): + return self._pyfuncitem def __repr__(self) -> str: return "" % (self.node) + def _fillfixtures(self) -> None: + item = self._pyfuncitem + for argname in item.fixturenames: + if argname not in item.funcargs: + item.funcargs[argname] = self.getfixturevalue(argname) + + def addfinalizer(self, finalizer: Callable[[], object]) -> None: + self.node.addfinalizer(finalizer) + @final class SubRequest(FixtureRequest): - """A sub request for handling getting a fixture from a test function/fixture.""" + """The type of the ``request`` fixture in a fixture function requested + (transitively) by a test function.""" def __init__( self, - request: "FixtureRequest", + request: FixtureRequest, scope: Scope, param: Any, param_index: int, @@ -772,39 +701,76 @@ def __init__( *, _ispytest: bool = False, ) -> None: - check_ispytest(_ispytest) - self._parent_request = request - self.fixturename = fixturedef.argname + super().__init__( + pyfuncitem=request._pyfuncitem, + fixturename=fixturedef.argname, + fixture_defs=request._fixture_defs, + arg2fixturedefs=request._arg2fixturedefs, + _ispytest=_ispytest, + ) + self._parent_request: Final[FixtureRequest] = request + self._scope_field: Final = scope + self._fixturedef: Final[FixtureDef[object]] = fixturedef if param is not NOTSET: self.param = param - self.param_index = param_index - self._scope = scope - self._fixturedef = fixturedef - self._pyfuncitem = request._pyfuncitem - self._fixture_defs = request._fixture_defs - self._arg2fixturedefs = request._arg2fixturedefs - self._arg2index = request._arg2index - self._fixturemanager = request._fixturemanager + self.param_index: Final = param_index def __repr__(self) -> str: return f"" - 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.""" - self._fixturedef.addfinalizer(finalizer) + @property + def _scope(self) -> Scope: + return self._scope_field - def _schedule_finalizers( - self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest" + @property + def node(self): + scope = self._scope + if scope is Scope.Function: + # This might also be a non-function Item despite its attribute name. + node: Optional[nodes.Node] = self._pyfuncitem + elif scope is Scope.Package: + node = get_scope_package(self._pyfuncitem, self._fixturedef) + else: + node = get_scope_node(self._pyfuncitem, scope) + if node is None and scope is Scope.Class: + # Fallback to function item itself. + node = self._pyfuncitem + assert node, f'Could not obtain a node for scope "{scope}" for function {self._pyfuncitem!r}' + return node + + def _check_scope( + self, + requested_fixturedef: Union["FixtureDef[object]", PseudoFixtureDef[object]], + requested_scope: Scope, ) -> 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.fixturenames: - fixturedef.addfinalizer( - functools.partial(self._fixturedef.finish, request=self) + if isinstance(requested_fixturedef, PseudoFixtureDef): + return + if self._scope > requested_scope: + # Try to report something helpful. + argname = requested_fixturedef.argname + fixture_stack = "\n".join( + self._format_fixturedef_line(fixturedef) + for fixturedef in self._get_fixturestack() + ) + requested_fixture = self._format_fixturedef_line(requested_fixturedef) + fail( + f"ScopeMismatch: You tried to access the {requested_scope.value} scoped " + f"fixture {argname} with a {self._scope.value} scoped request object. " + f"Requesting fixture stack:\n{fixture_stack}\n" + f"Requested fixture:\n{requested_fixture}", + pytrace=False, ) - super()._schedule_finalizers(fixturedef, subrequest) + + def _format_fixturedef_line(self, fixturedef: "FixtureDef[object]") -> str: + factory = fixturedef.func + path, lineno = getfslineno(factory) + if isinstance(path, Path): + path = bestrelpath(self._pyfuncitem.session.path, path) + signature = inspect.signature(factory) + return f"{path}:{lineno + 1}: def {factory.__name__}{signature}" + + def addfinalizer(self, finalizer: Callable[[], object]) -> None: + self._fixturedef.addfinalizer(finalizer) @final @@ -847,14 +813,15 @@ def formatrepr(self) -> "FixtureLookupErrorRepr": if msg is None: fm = self.request._fixturemanager available = set() - parentid = self.request._pyfuncitem.parent.nodeid + parent = self.request._pyfuncitem.parent + assert parent is not None for name, fixturedefs in fm._arg2fixturedefs.items(): - faclist = list(fm._matchfactories(fixturedefs, parentid)) + faclist = list(fm._matchfactories(fixturedefs, parent)) if faclist: available.add(name) if self.argname in available: - msg = " recursive dependency involving fixture '{}' detected".format( - self.argname + msg = ( + f" recursive dependency involving fixture '{self.argname}' detected" ) else: msg = f"fixture '{self.argname}' not found" @@ -898,13 +865,6 @@ def toterminal(self, tw: TerminalWriter) -> None: tw.line("%s:%d" % (os.fspath(self.filename), self.firstlineno + 1)) -def fail_fixturefunc(fixturefunc, msg: str) -> "NoReturn": - fs, lineno = getfslineno(fixturefunc) - location = f"{fs}:{lineno + 1}" - source = _pytest._code.Source(fixturefunc) - fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False) - - def call_fixture_func( fixturefunc: "_FixtureFunc[FixtureValue]", request: FixtureRequest, kwargs ) -> FixtureValue: @@ -934,29 +894,33 @@ def _teardown_yield_fixture(fixturefunc, it) -> None: except StopIteration: pass else: - fail_fixturefunc(fixturefunc, "fixture function has more than one 'yield'") + fs, lineno = getfslineno(fixturefunc) + fail( + f"fixture function has more than one 'yield':\n\n" + f"{Source(fixturefunc).indent()}\n" + f"{fs}:{lineno + 1}", + pytrace=False, + ) def _eval_scope_callable( - scope_callable: "Callable[[str, Config], _ScopeName]", + scope_callable: Callable[[str, Config], _ScopeName], fixture_name: str, config: Config, -) -> "_ScopeName": +) -> _ScopeName: 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 - ) + f"Error evaluating {scope_callable} while defining fixture '{fixture_name}'.\n" + "Expected a function with the signature (*, fixture_name, config)" ) 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), + f"Expected {scope_callable} to return a 'str' while defining fixture '{fixture_name}', but it returned:\n" + f"{result!r}", pytrace=False, ) return result @@ -964,50 +928,73 @@ def _eval_scope_callable( @final class FixtureDef(Generic[FixtureValue]): - """A container for a factory definition.""" + """A container for a fixture definition. + + Note: At this time, only explicitly documented fields and methods are + considered public stable API. + """ def __init__( self, - fixturemanager: "FixtureManager", + config: Config, baseid: Optional[str], argname: str, func: "_FixtureFunc[FixtureValue]", - scope: Union[Scope, "_ScopeName", Callable[[str, Config], "_ScopeName"], None], + scope: Union[Scope, _ScopeName, Callable[[str, Config], _ScopeName], None], params: Optional[Sequence[object]], - unittest: bool = False, ids: Optional[ - Union[ - Tuple[Union[None, str, float, int, bool], ...], - Callable[[Any], Optional[object]], - ] + Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] ] = None, + *, + _ispytest: bool = False, ) -> None: - self._fixturemanager = fixturemanager - self.baseid = baseid or "" - self.has_location = baseid is not None - self.func = func - self.argname = argname + check_ispytest(_ispytest) + # The "base" node ID for the fixture. + # + # This is a node ID prefix. A fixture is only available to a node (e.g. + # a `Function` item) if the fixture's baseid is a nodeid of a parent of + # node. + # + # For a fixture found in a Collector's object (e.g. a `Module`s module, + # a `Class`'s class), the baseid is the Collector's nodeid. + # + # For a fixture found in a conftest plugin, the baseid is the conftest's + # directory path relative to the rootdir. + # + # For other plugins, the baseid is the empty string (always matches). + self.baseid: Final = baseid or "" + # Whether the fixture was found from a node or a conftest in the + # collection tree. Will be false for fixtures defined in non-conftest + # plugins. + self.has_location: Final = baseid is not None + # The fixture factory function. + self.func: Final = func + # The name by which the fixture may be requested. + self.argname: Final = argname if scope is None: scope = Scope.Function elif callable(scope): - scope = _eval_scope_callable(scope, argname, fixturemanager.config) - + scope = _eval_scope_callable(scope, argname, config) if isinstance(scope, str): scope = Scope.from_user( scope, descr=f"Fixture '{func.__name__}'", where=baseid ) - self._scope = scope - self.params: Optional[Sequence[object]] = params - self.argnames: Tuple[str, ...] = getfuncargnames( - func, name=argname, is_method=unittest - ) - self.unittest = unittest - self.ids = ids + self._scope: Final = scope + # If the fixture is directly parametrized, the parameter values. + self.params: Final = params + # If the fixture is directly parametrized, a tuple of explicit IDs to + # assign to the parameter values, or a callable to generate an ID given + # a parameter value. + self.ids: Final = ids + # The names requested by the fixtures. + self.argnames: Final = getfuncargnames(func, name=argname) + # If the fixture was executed, the current value of the fixture. + # Can change if the fixture is executed with different parameters. self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None - self._finalizers: List[Callable[[], object]] = [] + self._finalizers: Final[List[Callable[[], object]]] = [] @property - def scope(self) -> "_ScopeName": + def scope(self) -> _ScopeName: """Scope string, one of "function", "class", "module", "package", "session".""" return self._scope.value @@ -1015,47 +1002,55 @@ def addfinalizer(self, finalizer: Callable[[], object]) -> None: self._finalizers.append(finalizer) def finish(self, request: SubRequest) -> None: - exc = None - try: - while self._finalizers: - try: - func = self._finalizers.pop() - func() - 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.path) - 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. - self.cached_result = None - self._finalizers = [] + exceptions: List[BaseException] = [] + while self._finalizers: + fin = self._finalizers.pop() + try: + fin() + except BaseException as e: + exceptions.append(e) + node = request.node + node.ihook.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. + self.cached_result = None + self._finalizers.clear() + if len(exceptions) == 1: + raise exceptions[0] + elif len(exceptions) > 1: + msg = f'errors while tearing down fixture "{self.argname}" of {node}' + raise BaseExceptionGroup(msg, exceptions[::-1]) def execute(self, request: SubRequest) -> FixtureValue: - # Get required arguments and register our own finish() - # with their finalization. + """Return the value of this fixture, executing it if not cached.""" + # Ensure that the dependent fixtures requested by this fixture are loaded. + # This needs to be done before checking if we have a cached value, since + # if a dependent fixture has their cache invalidated, e.g. due to + # parametrization, they finalize themselves and fixtures depending on it + # (which will likely include this fixture) setting `self.cached_result = None`. + # See #4871 + requested_fixtures_that_should_finalize_us = [] 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)) - + # Saves requested fixtures in a list so we later can add our finalizer + # to them, ensuring that if a requested fixture gets torn down we get torn + # down first. This is generally handled by SetupState, but still currently + # needed when this fixture is not parametrized but depends on a parametrized + # fixture. + if not isinstance(fixturedef, PseudoFixtureDef): + requested_fixtures_that_should_finalize_us.append(fixturedef) + + # Check for (and return) cached value/exception. my_cache_key = self.cache_key(request) if self.cached_result is not None: + cache_key = self.cached_result[1] # 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) + exc = self.cached_result[2] + raise exc else: result = self.cached_result[0] return result @@ -1064,43 +1059,52 @@ def execute(self, request: SubRequest) -> FixtureValue: self.finish(request) assert self.cached_result is None - hook = self._fixturemanager.session.gethookproxy(request.node.path) - result = hook.pytest_fixture_setup(fixturedef=self, request=request) + # Add finalizer to requested fixtures we saved previously. + # We make sure to do this after checking for cached value to avoid + # adding our finalizer multiple times. (#12135) + finalizer = functools.partial(self.finish, request=request) + for parent_fixture in requested_fixtures_that_should_finalize_us: + parent_fixture.addfinalizer(finalizer) + + ihook = request.node.ihook + try: + # Setup the fixture, run the code in it, and cache the value + # in self.cached_result + result = ihook.pytest_fixture_setup(fixturedef=self, request=request) + finally: + # schedule our finalizer, even if the setup failed + request.node.addfinalizer(finalizer) + return result def cache_key(self, request: SubRequest) -> object: - return request.param_index if not hasattr(request, "param") else request.param + return getattr(request, "param", None) def __repr__(self) -> str: - return "".format( - self.argname, self.scope, self.baseid - ) + return f"" 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.""" + value.""" 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) # type: ignore[union-attr] - else: - # 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) # type: ignore[union-attr] + # The fixture function needs to be bound to the actual + # request.instance so that code working with "fixturedef" behaves + # as expected. + instance = request.instance + if 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( + instance, + fixturefunc.__self__.__class__, + ): + return fixturefunc + fixturefunc = getimfunc(fixturedef.func) + if fixturefunc != fixturedef.func: + fixturefunc = fixturefunc.__get__(instance) return fixturefunc @@ -1110,63 +1114,37 @@ def pytest_fixture_setup( """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 + kwargs[argname] = request.getfixturevalue(argname) fixturefunc = resolve_fixture_function(fixturedef, request) my_cache_key = fixturedef.cache_key(request) try: result = call_fixture_func(fixturefunc, request, kwargs) - except TEST_OUTCOME: - exc_info = sys.exc_info() - assert exc_info[0] is not None - fixturedef.cached_result = (None, my_cache_key, exc_info) + except TEST_OUTCOME as e: + if isinstance(e, skip.Exception): + # The test requested a fixture which caused a skip. + # Don't show the fixture as the skip location, as then the user + # wouldn't know which test skipped. + e._use_item_location = True + fixturedef.cached_result = (None, my_cache_key, e) raise fixturedef.cached_result = (result, my_cache_key, None) return result -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 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: FixtureFunction, fixture_marker: "FixtureFunctionMarker", ) -> FixtureFunction: """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.""" + name = fixture_marker.name or function.__name__ message = ( - 'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n' + f'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/explanation/fixtures.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): @@ -1180,38 +1158,40 @@ def result(*args, **kwargs): @final -@attr.s(frozen=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class FixtureFunctionMarker: scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" - params: Optional[Tuple[object, ...]] = attr.ib(converter=_params_converter) + params: Optional[Tuple[object, ...]] autouse: bool = False - ids: Union[ - Tuple[Union[None, str, float, int, bool], ...], - Callable[[Any], Optional[object]], - ] = attr.ib( - default=None, - converter=_ensure_immutable_ids, - ) + ids: Optional[ + Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] + ] = None name: Optional[str] = None + _ispytest: dataclasses.InitVar[bool] = False + + def __post_init__(self, _ispytest: bool) -> None: + check_ispytest(_ispytest) + 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): raise ValueError( - "fixture is being applied more than once to the same function" + f"@pytest.fixture is being applied more than once to the same function {function.__name__!r}" ) + if hasattr(function, "pytestmark"): + warnings.warn(MARKED_FIXTURE, stacklevel=2) + function = wrap_function_to_error_out_if_called_directly(function, self) name = self.name or function.__name__ if name == "request": location = getlocation(function) fail( - "'request' is a reserved word for fixtures, use another name:\n {}".format( - location - ), + f"'request' is a reserved word for fixtures, use another name:\n {location}", pytrace=False, ) @@ -1228,14 +1208,10 @@ def fixture( params: Optional[Iterable[object]] = ..., autouse: bool = ..., ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] + Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] ] = ..., name: Optional[str] = ..., -) -> FixtureFunction: - ... +) -> FixtureFunction: ... @overload @@ -1246,14 +1222,10 @@ def fixture( params: Optional[Iterable[object]] = ..., autouse: bool = ..., ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] + Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] ] = ..., name: Optional[str] = None, -) -> FixtureFunctionMarker: - ... +) -> FixtureFunctionMarker: ... def fixture( @@ -1263,10 +1235,7 @@ def fixture( params: Optional[Iterable[object]] = None, autouse: bool = False, ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] + Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] ] = None, name: Optional[str] = None, ) -> Union[FixtureFunctionMarker, FixtureFunction]: @@ -1308,7 +1277,7 @@ def fixture( the fixture. :param ids: - List of string ids each corresponding to the params so that they are + Sequence of 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. @@ -1322,10 +1291,11 @@ def fixture( """ fixture_marker = FixtureFunctionMarker( scope=scope, - params=params, + params=tuple(params) if params is not None else None, autouse=autouse, - ids=ids, + ids=None if ids is None else ids if callable(ids) else tuple(ids), name=name, + _ispytest=True, ) # Direct decoration. @@ -1381,8 +1351,60 @@ def pytest_addoption(parser: Parser) -> None: "usefixtures", type="args", default=[], - help="list of default fixtures to be used with this project", + help="List of default fixtures to be used with this project", + ) + group = parser.getgroup("general") + group.addoption( + "--fixtures", + "--funcargs", + action="store_true", + dest="showfixtures", + default=False, + help="Show available fixtures, sorted by plugin appearance " + "(fixtures with leading '_' are only shown with '-v')", ) + group.addoption( + "--fixtures-per-test", + action="store_true", + dest="show_fixtures_per_test", + default=False, + help="Show fixtures per test", + ) + + +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 _get_direct_parametrize_args(node: nodes.Node) -> Set[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. + + These things are done later as well when dealing with parametrization + so this could be improved. + """ + parametrize_argnames: Set[str] = set() + for marker in node.iter_markers(name="parametrize"): + if not marker.kwargs.get("indirect", False): + p_argnames, _ = ParameterSet._parse_parametrize_args( + *marker.args, **marker.kwargs + ) + parametrize_argnames.update(p_argnames) + return parametrize_argnames + + +def deduplicate_names(*seqs: Iterable[str]) -> Tuple[str, ...]: + """De-duplicate the sequence of names while keeping the original order.""" + # Ideally we would use a set, but it does not preserve insertion order. + return tuple(dict.fromkeys(name for seq in seqs for name in seq)) class FixtureManager: @@ -1416,92 +1438,99 @@ class FixtureManager: by a lookup of their FuncFixtureInfo. """ - FixtureLookupError = FixtureLookupError - FixtureLookupErrorRepr = FixtureLookupErrorRepr - def __init__(self, session: "Session") -> None: self.session = session self.config: Config = session.config - self._arg2fixturedefs: Dict[str, List[FixtureDef[Any]]] = {} - self._holderobjseen: Set[object] = set() + # Maps a fixture name (argname) to all of the FixtureDefs in the test + # suite/plugins defined with this name. Populated by parsefactories(). + # TODO: The order of the FixtureDefs list of each arg is significant, + # explain. + self._arg2fixturedefs: Final[Dict[str, List[FixtureDef[Any]]]] = {} + self._holderobjseen: Final[Set[object]] = set() # A mapping from a nodeid to a list of autouse fixtures it defines. - self._nodeid_autousenames: Dict[str, List[str]] = { + self._nodeid_autousenames: Final[Dict[str, List[str]]] = { "": self.config.getini("usefixtures"), } session.config.pluginmanager.register(self, "funcmanage") - 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. + def getfixtureinfo( + self, + node: nodes.Item, + func: Optional[Callable[..., object]], + cls: Optional[type], + ) -> FuncFixtureInfo: + """Calculate the :class:`FuncFixtureInfo` for an item. - Check https://github.com/pytest-dev/pytest/issues/5036. + If ``func`` is None, or if the item sets an attribute + ``nofuncargs = True``, then ``func`` is not examined at all. - These things are done later as well when dealing with parametrization - so this could be improved. + :param node: + The item requesting the fixtures. + :param func: + The item's function. + :param cls: + If the function is a method, the method's class. """ - parametrize_argnames: List[str] = [] - for marker in node.iter_markers(name="parametrize"): - if not marker.kwargs.get("indirect", False): - p_argnames, _ = ParameterSet._parse_parametrize_args( - *marker.args, **marker.kwargs - ) - parametrize_argnames.extend(p_argnames) - - return parametrize_argnames - - def getfixtureinfo( - self, node: nodes.Node, func, cls, funcargs: bool = True - ) -> FuncFixtureInfo: - if funcargs and not getattr(node, "nofuncargs", False): + if func is not None and not getattr(node, "nofuncargs", False): argnames = getfuncargnames(func, name=node.name, cls=cls) else: argnames = () + usefixturesnames = self._getusefixturesnames(node) + autousenames = self._getautousenames(node) + initialnames = deduplicate_names(autousenames, usefixturesnames, argnames) - usefixtures = tuple( - arg for mark in node.iter_markers(name="usefixtures") for arg in mark.args - ) - initialnames = usefixtures + argnames - fm = node.session._fixturemanager - initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure( - initialnames, node, ignore_args=self._get_direct_parametrize_args(node) + direct_parametrize_args = _get_direct_parametrize_args(node) + + names_closure, arg2fixturedefs = self.getfixtureclosure( + parentnode=node, + initialnames=initialnames, + ignore_args=direct_parametrize_args, ) + return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs) - def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None: - nodeid = None - try: - p = absolutepath(plugin.__file__) # type: ignore[attr-defined] - except AttributeError: - pass + def pytest_plugin_registered(self, plugin: _PluggyPlugin, plugin_name: str) -> None: + # Fixtures defined in conftest plugins are only visible to within the + # conftest's directory. This is unlike fixtures in non-conftest plugins + # which have global visibility. So for conftests, construct the base + # nodeid from the plugin name (which is the conftest path). + if plugin_name and plugin_name.endswith("conftest.py"): + # Note: we explicitly do *not* use `plugin.__file__` here -- The + # difference is that plugin_name has the correct capitalization on + # case-insensitive systems (Windows) and other normalization issues + # (issue #11816). + conftestpath = absolutepath(plugin_name) + try: + nodeid = str(conftestpath.parent.relative_to(self.config.rootpath)) + except ValueError: + nodeid = "" + if nodeid == ".": + nodeid = "" + if os.sep != nodes.SEP: + nodeid = nodeid.replace(os.sep, nodes.SEP) else: - # 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.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) + nodeid = None self.parsefactories(plugin, nodeid) - def _getautousenames(self, nodeid: str) -> Iterator[str]: - """Return the names of autouse fixtures applicable to nodeid.""" - for parentnodeid in nodes.iterparentnodeids(nodeid): - basenames = self._nodeid_autousenames.get(parentnodeid) + def _getautousenames(self, node: nodes.Node) -> Iterator[str]: + """Return the names of autouse fixtures applicable to node.""" + for parentnode in node.listchain(): + basenames = self._nodeid_autousenames.get(parentnode.nodeid) if basenames: yield from basenames + def _getusefixturesnames(self, node: nodes.Item) -> Iterator[str]: + """Return the names of usefixtures fixtures applicable to node.""" + for mark in node.iter_markers(name="usefixtures"): + yield from mark.args + def getfixtureclosure( self, - fixturenames: Tuple[str, ...], parentnode: nodes.Node, - ignore_args: Sequence[str] = (), - ) -> Tuple[Tuple[str, ...], List[str], Dict[str, Sequence[FixtureDef[Any]]]]: + initialnames: Tuple[str, ...], + ignore_args: AbstractSet[str], + ) -> Tuple[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 @@ -1509,20 +1538,7 @@ def getfixtureclosure( # to re-discover fixturedefs again for each fixturename # (discovering matching fixtures for a given name/node is expensive). - parentid = parentnode.nodeid - fixturenames_closure = list(self._getautousenames(parentid)) - - 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", - # which is a set of fixturenames the function immediately requests. We - # need to return it as well, so save this. - initialnames = tuple(fixturenames_closure) + fixturenames_closure = list(initialnames) arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {} lastlen = -1 @@ -1533,10 +1549,12 @@ def merge(otherlist: Iterable[str]) -> None: continue if argname in arg2fixturedefs: continue - fixturedefs = self.getfixturedefs(argname, parentid) + fixturedefs = self.getfixturedefs(argname, parentnode) if fixturedefs: arg2fixturedefs[argname] = fixturedefs - merge(fixturedefs[-1].argnames) + for arg in fixturedefs[-1].argnames: + if arg not in fixturenames_closure: + fixturenames_closure.append(arg) def sort_by_scope(arg_name: str) -> Scope: try: @@ -1547,7 +1565,7 @@ def sort_by_scope(arg_name: str) -> Scope: return fixturedefs[-1]._scope fixturenames_closure.sort(key=sort_by_scope, reverse=True) - return initialnames, fixturenames_closure, arg2fixturedefs + return fixturenames_closure, arg2fixturedefs def pytest_generate_tests(self, metafunc: "Metafunc") -> None: """Generate new tests based on parametrized fixtures used by the given metafunc""" @@ -1598,25 +1616,111 @@ def pytest_collection_modifyitems(self, items: List[nodes.Item]) -> None: # Separate parametrized setups. items[:] = reorder_items(items) + def _register_fixture( + self, + *, + name: str, + func: "_FixtureFunc[object]", + nodeid: Optional[str], + scope: Union[ + Scope, _ScopeName, Callable[[str, Config], _ScopeName], None + ] = "function", + params: Optional[Sequence[object]] = None, + ids: Optional[ + Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] + ] = None, + autouse: bool = False, + ) -> None: + """Register a fixture + + :param name: + The fixture's name. + :param func: + The fixture's implementation function. + :param nodeid: + The visibility of the fixture. The fixture will be available to the + node with this nodeid and its children in the collection tree. + None means that the fixture is visible to the entire collection tree, + e.g. a fixture defined for general use in a plugin. + :param scope: + The fixture's scope. + :param params: + The fixture's parametrization params. + :param ids: + The fixture's IDs. + :param autouse: + Whether this is an autouse fixture. + """ + fixture_def = FixtureDef( + config=self.config, + baseid=nodeid, + argname=name, + func=func, + scope=scope, + params=params, + ids=ids, + _ispytest=True, + ) + + faclist = self._arg2fixturedefs.setdefault(name, []) + if fixture_def.has_location: + faclist.append(fixture_def) + else: + # fixturedefs with no location are at the front + # so this inserts the current fixturedef after the + # existing fixturedefs from external plugins but + # before the fixturedefs provided in conftests. + i = len([f for f in faclist if not f.has_location]) + faclist.insert(i, fixture_def) + if autouse: + self._nodeid_autousenames.setdefault(nodeid or "", []).append(name) + + @overload def parsefactories( - self, node_or_obj, nodeid=NOTSET, unittest: bool = False + self, + node_or_obj: nodes.Node, + ) -> None: + raise NotImplementedError() + + @overload + def parsefactories( + self, + node_or_obj: object, + nodeid: Optional[str], ) -> None: + raise NotImplementedError() + + def parsefactories( + self, + node_or_obj: Union[nodes.Node, object], + nodeid: Union[str, NotSetType, None] = NOTSET, + ) -> None: + """Collect fixtures from a collection node or object. + + Found fixtures are parsed into `FixtureDef`s and saved. + + If `node_or_object` is a collection node (with an underlying Python + object), the node's object is traversed and the node's nodeid is used to + determine the fixtures' visibility. `nodeid` must not be specified in + this case. + + If `node_or_object` is an object (e.g. a plugin), the object is + traversed and the given `nodeid` is used to determine the fixtures' + visibility. `nodeid` must be specified in this case; None and "" mean + total visibility. + """ if nodeid is not NOTSET: holderobj = node_or_obj else: - holderobj = node_or_obj.obj + assert isinstance(node_or_obj, nodes.Node) + holderobj = cast(object, node_or_obj.obj) # type: ignore[attr-defined] + assert isinstance(node_or_obj.nodeid, str) nodeid = node_or_obj.nodeid if holderobj in self._holderobjseen: return self._holderobjseen.add(holderobj) - autousenames = [] for name in dir(holderobj): - # ugly workaround for one of the fspath deprecated property of node - # todo: safely generalize - if isinstance(holderobj, nodes.Node) and name == "fspath": - continue - # The attribute can be an arbitrary descriptor, so the attribute # access below can raise. safe_getatt() ignores such exceptions. obj = safe_getattr(holderobj, name, None) @@ -1633,54 +1737,176 @@ def parsefactories( # 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) + func = get_real_method(obj, holderobj) - fixture_def = FixtureDef( - fixturemanager=self, - baseid=nodeid, - argname=name, - func=obj, + self._register_fixture( + name=name, + nodeid=nodeid, + func=func, scope=marker.scope, params=marker.params, - unittest=unittest, ids=marker.ids, + autouse=marker.autouse, ) - faclist = self._arg2fixturedefs.setdefault(name, []) - if fixture_def.has_location: - faclist.append(fixture_def) - else: - # fixturedefs with no location are at the front - # so this inserts the current fixturedef after the - # existing fixturedefs from external plugins but - # before the fixturedefs provided in conftests. - i = len([f for f in faclist if not f.has_location]) - faclist.insert(i, fixture_def) - if marker.autouse: - autousenames.append(name) - - if autousenames: - self._nodeid_autousenames.setdefault(nodeid or "", []).extend(autousenames) - def getfixturedefs( - self, argname: str, nodeid: str + self, argname: str, node: nodes.Node ) -> Optional[Sequence[FixtureDef[Any]]]: - """Get a list of fixtures which are applicable to the given node id. + """Get FixtureDefs for a fixture name which are applicable + to a given node. + + Returns None if there are no fixtures at all defined with the given + name. (This is different from the case in which there are fixtures + with the given name, but none applicable to the node. In this case, + an empty result is returned). - :param str argname: Name of the fixture to search for. - :param str nodeid: Full node id of the requesting test. - :rtype: Sequence[FixtureDef] + :param argname: Name of the fixture to search for. + :param node: The requesting Node. """ try: fixturedefs = self._arg2fixturedefs[argname] except KeyError: return None - return tuple(self._matchfactories(fixturedefs, nodeid)) + return tuple(self._matchfactories(fixturedefs, node)) def _matchfactories( - self, fixturedefs: Iterable[FixtureDef[Any]], nodeid: str + self, fixturedefs: Iterable[FixtureDef[Any]], node: nodes.Node ) -> Iterator[FixtureDef[Any]]: - parentnodeids = set(nodes.iterparentnodeids(nodeid)) + parentnodeids = {n.nodeid for n in node.iter_parents()} for fixturedef in fixturedefs: if fixturedef.baseid in parentnodeids: yield fixturedef + + +def show_fixtures_per_test(config: Config) -> Union[int, ExitCode]: + from _pytest.main import wrap_session + + return wrap_session(config, _show_fixtures_per_test) + + +_PYTEST_DIR = Path(_pytest.__file__).parent + + +def _pretty_fixture_path(invocation_dir: Path, func) -> str: + loc = Path(getlocation(func, invocation_dir)) + prefix = Path("...", "_pytest") + try: + return str(prefix / loc.relative_to(_PYTEST_DIR)) + except ValueError: + return bestrelpath(invocation_dir, loc) + + +def _show_fixtures_per_test(config: Config, session: "Session") -> None: + import _pytest.config + + session.perform_collect() + invocation_dir = config.invocation_params.dir + tw = _pytest.config.create_terminal_writer(config) + verbose = config.getvalue("verbose") + + def get_best_relpath(func) -> str: + loc = getlocation(func, invocation_dir) + return bestrelpath(invocation_dir, Path(loc)) + + def write_fixture(fixture_def: FixtureDef[object]) -> None: + argname = fixture_def.argname + if verbose <= 0 and argname.startswith("_"): + return + prettypath = _pretty_fixture_path(invocation_dir, fixture_def.func) + tw.write(f"{argname}", green=True) + tw.write(f" -- {prettypath}", yellow=True) + tw.write("\n") + fixture_doc = inspect.getdoc(fixture_def.func) + if fixture_doc: + write_docstring( + tw, fixture_doc.split("\n\n")[0] if verbose <= 0 else fixture_doc + ) + else: + tw.line(" no docstring available", red=True) + + def write_item(item: nodes.Item) -> None: + # Not all items have _fixtureinfo attribute. + info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None) + if info is None or not info.name2fixturedefs: + # This test item does not use any fixtures. + return + tw.line() + tw.sep("-", f"fixtures used by {item.name}") + # TODO: Fix this type ignore. + tw.sep("-", f"({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. + write_fixture(fixturedefs[-1]) + + for session_item in session.items: + write_item(session_item) + + +def showfixtures(config: Config) -> Union[int, ExitCode]: + from _pytest.main import wrap_session + + return wrap_session(config, _showfixtures_main) + + +def _showfixtures_main(config: Config, session: "Session") -> None: + import _pytest.config + + session.perform_collect() + invocation_dir = config.invocation_params.dir + tw = _pytest.config.create_terminal_writer(config) + verbose = config.getvalue("verbose") + + fm = session._fixturemanager + + available = [] + seen: Set[Tuple[str, str]] = set() + + for argname, fixturedefs in fm._arg2fixturedefs.items(): + assert fixturedefs is not None + if not fixturedefs: + continue + for fixturedef in fixturedefs: + loc = getlocation(fixturedef.func, invocation_dir) + if (fixturedef.argname, loc) in seen: + continue + seen.add((fixturedef.argname, loc)) + available.append( + ( + len(fixturedef.baseid), + fixturedef.func.__module__, + _pretty_fixture_path(invocation_dir, fixturedef.func), + fixturedef.argname, + fixturedef, + ) + ) + + available.sort() + currentmodule = None + for baseid, module, prettypath, argname, fixturedef in available: + if currentmodule != module: + if not module.startswith("_pytest."): + tw.line() + tw.sep("-", f"fixtures defined from {module}") + currentmodule = module + if verbose <= 0 and argname.startswith("_"): + continue + tw.write(f"{argname}", green=True) + if fixturedef.scope != "function": + tw.write(" [%s scope]" % fixturedef.scope, cyan=True) + tw.write(f" -- {prettypath}", yellow=True) + tw.write("\n") + doc = inspect.getdoc(fixturedef.func) + if doc: + write_docstring(tw, doc.split("\n\n")[0] if verbose <= 0 else doc) + else: + tw.line(" no docstring available", red=True) + tw.line() + + +def write_docstring(tw: TerminalWriter, doc: str, indent: str = " ") -> None: + for line in doc.split("\n"): + tw.line(indent + line) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/freeze_support.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/freeze_support.py index 9f8ea231feddf..e03a6d1753d81 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/freeze_support.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/freeze_support.py @@ -1,5 +1,6 @@ """Provides a function to report all internal modules for using freezing tools.""" + import types from typing import Iterator from typing import List @@ -34,7 +35,7 @@ def _iter_all_modules( else: # Type ignored because typeshed doesn't define ModuleType.__path__ # (only defined on packages). - package_path = package.__path__ # type: ignore[attr-defined] + package_path = package.__path__ path, prefix = package_path[0], package.__name__ + "." for _, name, is_package in pkgutil.iter_modules([path]): if is_package: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/helpconfig.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/helpconfig.py index aca2cd391e427..37fbdf04d7e1e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/helpconfig.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/helpconfig.py @@ -1,16 +1,20 @@ +# mypy: allow-untyped-defs """Version info, help messages, tracing configuration.""" + +from argparse import Action import os import sys -from argparse import Action +from typing import Generator from typing import List from typing import Optional from typing import Union -import pytest from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import PrintHelp from _pytest.config.argparsing import Parser +from _pytest.terminal import TerminalReporter +import pytest class HelpAction(Action): @@ -49,7 +53,7 @@ def pytest_addoption(parser: Parser) -> None: action="count", default=0, dest="version", - help="display pytest version and information about plugins. " + help="Display pytest version and information about plugins. " "When given twice, also display information about plugins.", ) group._addoption( @@ -57,7 +61,7 @@ def pytest_addoption(parser: Parser) -> None: "--help", action=HelpAction, dest="help", - help="show help message and configuration info", + help="Show help message and configuration info", ) group._addoption( "-p", @@ -65,7 +69,7 @@ def pytest_addoption(parser: Parser) -> None: dest="plugins", default=[], metavar="name", - help="early-load given plugin module name or entry point (multi-allowed).\n" + help="Early-load given plugin module name or entry point (multi-allowed). " "To avoid loading of plugins, use the `no:` prefix, e.g. " "`no:doctest`.", ) @@ -74,7 +78,7 @@ def pytest_addoption(parser: Parser) -> None: "--trace-config", action="store_true", default=False, - help="trace considerations of conftest.py files.", + help="Trace considerations of conftest.py files", ) group.addoption( "--debug", @@ -83,34 +87,34 @@ def pytest_addoption(parser: Parser) -> None: const="pytestdebug.log", dest="debug", metavar="DEBUG_FILE_NAME", - help="store internal tracing debug information in this log file.\n" - "This file is opened with 'w' and truncated as a result, care advised.\n" - "Defaults to 'pytestdebug.log'.", + help="Store internal tracing debug information in this log file. " + "This file is opened with 'w' and truncated as a result, care advised. " + "Default: pytestdebug.log.", ) group._addoption( "-o", "--override-ini", dest="override_ini", action="append", - help='override ini option with "option=value" style, e.g. `-o xfail_strict=True -o cache_dir=cache`.', + help='Override ini option with "option=value" style, ' + "e.g. `-o xfail_strict=True -o cache_dir=cache`.", ) -@pytest.hookimpl(hookwrapper=True) -def pytest_cmdline_parse(): - outcome = yield - config: Config = outcome.get_result() +@pytest.hookimpl(wrapper=True) +def pytest_cmdline_parse() -> Generator[None, Config, Config]: + config = yield if config.option.debug: # --debug | --debug was provided. path = config.option.debug - debugfile = open(path, "w") + debugfile = open(path, "w", encoding="utf-8") debugfile.write( - "versions pytest-%s, " - "python-%s\ncwd=%s\nargs=%s\n\n" - % ( + "versions pytest-{}, " + "python-{}\ninvocation_dir={}\ncwd={}\nargs={}\n\n".format( pytest.__version__, ".".join(map(str, sys.version_info)), + config.invocation_params.dir, os.getcwd(), config.invocation_params.args, ) @@ -127,13 +131,13 @@ def unset_tracing() -> None: config.add_cleanup(unset_tracing) + return config + def showversion(config: Config) -> None: if config.option.version > 1: sys.stdout.write( - "This is pytest version {}, imported from {}\n".format( - pytest.__version__, pytest.__file__ - ) + f"This is pytest version {pytest.__version__}, imported from {pytest.__file__}\n" ) plugininfo = getpluginversioninfo(config) if plugininfo: @@ -158,12 +162,16 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: def showhelp(config: Config) -> None: import textwrap - reporter = config.pluginmanager.get_plugin("terminalreporter") + reporter: Optional[TerminalReporter] = config.pluginmanager.get_plugin( + "terminalreporter" + ) + assert reporter is not None tw = reporter._tw tw.write(config._parser.optparser.format_help()) tw.line() tw.line( - "[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:" + "[pytest] ini-options in the first " + "pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:" ) tw.line() @@ -203,12 +211,12 @@ def showhelp(config: Config) -> None: tw.line(indent + line) tw.line() - tw.line("environment variables:") + tw.line("Environment variables:") vars = [ - ("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"), + ("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"), ] for name, help in vars: tw.line(f" {name:<24} {help}") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/hookspec.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/hookspec.py index 79251315d8986..9ec9b3b5e1041 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/hookspec.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/hookspec.py @@ -1,5 +1,7 @@ +# mypy: allow-untyped-defs """Hook specifications for pytest plugins which are invoked by pytest itself and by builtin plugins.""" + from pathlib import Path from typing import Any from typing import Dict @@ -13,20 +15,21 @@ from pluggy import HookspecMarker -from _pytest.deprecated import WARNING_CAPTURED_HOOK -from _pytest.deprecated import WARNING_CMDLINE_PREPARSE_HOOK +from .deprecated import HOOK_LEGACY_PATH_ARG + if TYPE_CHECKING: import pdb + from typing import Literal import warnings - from typing_extensions import Literal + from _pytest._code.code import ExceptionInfo from _pytest._code.code import ExceptionRepr - from _pytest.code import ExceptionInfo + from _pytest.compat import LEGACY_PATH + from _pytest.config import _PluggyPlugin 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 @@ -34,15 +37,15 @@ from _pytest.nodes import Collector from _pytest.nodes import Item from _pytest.outcomes import Exit + from _pytest.python import Class 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 - from _pytest.compat import LEGACY_PATH + from _pytest.terminal import TestShortLogReport hookspec = HookspecMarker("pytest") @@ -55,26 +58,43 @@ @hookspec(historic=True) 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)``. + :func:`pluginmanager.add_hookspecs(module_or_class, prefix) `. - :param pytest.PytestPluginManager pluginmanager: The pytest plugin manager. + :param pluginmanager: The pytest plugin manager. .. note:: - This hook is incompatible with ``hookwrapper=True``. + This hook is incompatible with hook wrappers. + + Use in conftest plugins + ======================= + + If a conftest plugin implements this hook, it will be called immediately + when the conftest is registered. """ @hookspec(historic=True) def pytest_plugin_registered( - plugin: "_PluggyPlugin", manager: "PytestPluginManager" + plugin: "_PluggyPlugin", + plugin_name: str, + manager: "PytestPluginManager", ) -> None: """A new pytest plugin got registered. :param plugin: The plugin module or instance. - :param pytest.PytestPluginManager manager: pytest plugin manager. + :param plugin_name: The name by which the plugin is registered. + :param manager: The pytest plugin manager. .. note:: - This hook is incompatible with ``hookwrapper=True``. + This hook is incompatible with hook wrappers. + + Use in conftest plugins + ======================= + + If a conftest plugin implements this hook, it will be called immediately + when the conftest is registered, once for each plugin registered thus far + (including itself!), and for all plugins thereafter when they are + registered. """ @@ -83,21 +103,15 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> """Register argparse-style options and ini-style config values, called once at the beginning of a test run. - .. note:: - - This function should be implemented only in plugins or ``conftest.py`` - files situated at the tests root directory due to how pytest - :ref:`discovers plugins during startup `. - - :param pytest.Parser parser: + :param parser: To add command line options, call :py:func:`parser.addoption(...) `. To add ini-file values call :py:func:`parser.addini(...) `. - :param pytest.PytestPluginManager pluginmanager: - The 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 + :param pluginmanager: + The pytest plugin manager, which can be used to install :py:func:`~pytest.hookspec`'s + or :py:func:`~pytest.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 @@ -113,7 +127,15 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> attribute or can be retrieved as the ``pytestconfig`` fixture. .. note:: - This hook is incompatible with ``hookwrapper=True``. + This hook is incompatible with hook wrappers. + + Use in conftest plugins + ======================= + + If a conftest plugin implements this hook, it will be called immediately + when the conftest is registered. + + This hook is only called for :ref:`initial conftests `. """ @@ -121,16 +143,17 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> 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. + .. note:: + This hook is incompatible with hook wrappers. - After that, the hook is called for other conftest files as they are - imported. + :param config: The pytest config object. - .. note:: - This hook is incompatible with ``hookwrapper=True``. + Use in conftest plugins + ======================= - :param pytest.Config config: The pytest config object. + This hook is called for every :ref:`initial conftest ` file + after command line options have been parsed. After that, the hook is called + for other conftest files as they are registered. """ @@ -144,58 +167,59 @@ def pytest_configure(config: "Config") -> None: def pytest_cmdline_parse( pluginmanager: "PytestPluginManager", args: List[str] ) -> Optional["Config"]: - """Return an initialized config object, parsing the specified args. + """Return an initialized :class:`~pytest.Config`, parsing the specified args. Stops at first non-None result, see :ref:`firstresult`. .. note:: - This hook will only be called for plugin classes passed to the + This hook is only called for plugin classes passed to the ``plugins`` arg when using `pytest.main`_ to perform an in-process test run. - :param pytest.PytestPluginManager pluginmanager: The pytest plugin manager. - :param List[str] args: List of arguments passed on the command line. + :param pluginmanager: The pytest plugin manager. + :param args: List of arguments passed on the command line. + :returns: A pytest config object. + + Use in conftest plugins + ======================= + + This hook is not called for conftest files. """ -@hookspec(warn_on_impl=WARNING_CMDLINE_PREPARSE_HOOK) -def pytest_cmdline_preparse(config: "Config", args: List[str]) -> None: - """(**Deprecated**) modify command line arguments before option parsing. +def pytest_load_initial_conftests( + early_config: "Config", parser: "Parser", args: List[str] +) -> None: + """Called to implement the loading of :ref:`initial conftest files + ` ahead of command line option parsing. - This hook is considered deprecated and will be removed in a future pytest version. Consider - using :hook:`pytest_load_initial_conftests` instead. + :param early_config: The pytest config object. + :param args: Arguments passed on the command line. + :param parser: To add command line options. - .. note:: - This hook will not be called for ``conftest.py`` files, only for setuptools plugins. + Use in conftest plugins + ======================= - :param pytest.Config config: The pytest config object. - :param List[str] args: Arguments passed on the command line. + This hook is not called for conftest files. """ @hookspec(firstresult=True) 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. + """Called for performing the main command line action. - Stops at first non-None result, see :ref:`firstresult`. - - :param pytest.Config config: The pytest config object. - """ + The default implementation will invoke the configure hooks and + :hook:`pytest_runtestloop`. + Stops at first non-None result, see :ref:`firstresult`. -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. + :param config: The pytest config object. + :returns: The exit code. - .. note:: - This hook will not be called for ``conftest.py`` files, only for setuptools plugins. + Use in conftest plugins + ======================= - :param pytest.Config early_config: The pytest config object. - :param List[str] args: Arguments passed on the command line. - :param pytest.Parser parser: To add command line options. + This hook is only called for :ref:`initial conftests `. """ @@ -237,7 +261,12 @@ def pytest_collection(session: "Session") -> Optional[object]: for example the terminal plugin uses it to start displaying the collection counter (and returns `None`). - :param pytest.Session session: The pytest session object. + :param session: The pytest session object. + + Use in conftest plugins + ======================= + + This hook is only called for :ref:`initial conftests `. """ @@ -247,20 +276,37 @@ def pytest_collection_modifyitems( """Called after collection has been performed. May filter or re-order the items in-place. - :param pytest.Session session: The pytest session object. - :param pytest.Config config: The pytest config object. - :param List[pytest.Item] items: List of item objects. + :param session: The pytest session object. + :param config: The pytest config object. + :param items: List of item objects. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. """ def pytest_collection_finish(session: "Session") -> None: """Called after collection has been performed and modified. - :param pytest.Session session: The pytest session object. + :param session: The pytest session object. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. """ -@hookspec(firstresult=True) +@hookspec( + firstresult=True, + warn_on_impl_args={ + "path": HOOK_LEGACY_PATH_ARG.format( + pylib_path_arg="path", pathlib_path_arg="collection_path" + ), + }, +) def pytest_ignore_collect( collection_path: Path, path: "LEGACY_PATH", config: "Config" ) -> Optional[bool]: @@ -271,31 +317,84 @@ def pytest_ignore_collect( Stops at first non-None result, see :ref:`firstresult`. - :param pathlib.Path collection_path : The path to analyze. - :param LEGACY_PATH path: The path to analyze (deprecated). - :param pytest.Config config: The pytest config object. + :param collection_path: The path to analyze. + :param path: The path to analyze (deprecated). + :param config: The pytest config object. .. versionchanged:: 7.0.0 The ``collection_path`` parameter was added as a :class:`pathlib.Path` equivalent of the ``path`` parameter. The ``path`` parameter has been deprecated. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given collection path, only + conftest files in parent directories of the collection path are consulted + (if the path is a directory, its own conftest file is *not* consulted - a + directory cannot ignore itself!). + """ + + +@hookspec(firstresult=True) +def pytest_collect_directory(path: Path, parent: "Collector") -> "Optional[Collector]": + """Create a :class:`~pytest.Collector` for the given directory, or None if + not relevant. + + .. versionadded:: 8.0 + + For best results, the returned collector should be a subclass of + :class:`~pytest.Directory`, but this is not required. + + The new node needs to have the specified ``parent`` as a parent. + + Stops at first non-None result, see :ref:`firstresult`. + + :param path: The path to analyze. + + See :ref:`custom directory collectors` for a simple example of use of this + hook. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given collection path, only + conftest files in parent directories of the collection path are consulted + (if the path is a directory, its own conftest file is *not* consulted - a + directory cannot collect itself!). """ +@hookspec( + warn_on_impl_args={ + "path": HOOK_LEGACY_PATH_ARG.format( + pylib_path_arg="path", pathlib_path_arg="file_path" + ), + }, +) def pytest_collect_file( file_path: Path, path: "LEGACY_PATH", parent: "Collector" ) -> "Optional[Collector]": - """Create a Collector for the given path, or None if not relevant. + """Create a :class:`~pytest.Collector` for the given path, or None if not relevant. + + For best results, the returned collector should be a subclass of + :class:`~pytest.File`, but this is not required. The new node needs to have the specified ``parent`` as a parent. - :param pathlib.Path file_path: The path to analyze. - :param LEGACY_PATH path: The path to collect (deprecated). + :param file_path: The path to analyze. + :param path: The path to collect (deprecated). .. versionchanged:: 7.0.0 The ``file_path`` parameter was added as a :class:`pathlib.Path` equivalent of the ``path`` parameter. The ``path`` parameter has been deprecated. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given file path, only + conftest files in parent directories of the file path are consulted. """ @@ -303,21 +402,61 @@ def pytest_collect_file( def pytest_collectstart(collector: "Collector") -> None: - """Collector starts collecting.""" + """Collector starts collecting. + + :param collector: + The collector. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given collector, only + conftest files in the collector's directory and its parent directories are + consulted. + """ def pytest_itemcollected(item: "Item") -> None: - """We just collected a test item.""" + """We just collected a test item. + + :param item: + The item. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. + """ def pytest_collectreport(report: "CollectReport") -> None: - """Collector finished collecting.""" + """Collector finished collecting. + + :param report: + The collect report. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given collector, only + conftest files in the collector's directory and its parent directories are + consulted. + """ def pytest_deselected(items: Sequence["Item"]) -> None: """Called for deselected test items, e.g. by keyword. May be called multiple times. + + :param items: + The items. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. """ @@ -327,6 +466,16 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor a :class:`~pytest.CollectReport`. Stops at first non-None result, see :ref:`firstresult`. + + :param collector: + The collector. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given collector, only + conftest files in the collector's directory and its parent directories are + consulted. """ @@ -335,36 +484,66 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor # ------------------------------------------------------------------------- -@hookspec(firstresult=True) +@hookspec( + firstresult=True, + warn_on_impl_args={ + "path": HOOK_LEGACY_PATH_ARG.format( + pylib_path_arg="path", pathlib_path_arg="module_path" + ), + }, +) def pytest_pycollect_makemodule( module_path: Path, path: "LEGACY_PATH", parent ) -> Optional["Module"]: - """Return a Module collector or None for the given path. + """Return a :class:`pytest.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 + The :hook:`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`. - :param pathlib.Path module_path: The path of the module to collect. - :param LEGACY_PATH path: The path of the module to collect (deprecated). + :param module_path: The path of the module to collect. + :param path: The path of the module to collect (deprecated). .. versionchanged:: 7.0.0 The ``module_path`` parameter was added as a :class:`pathlib.Path` equivalent of the ``path`` parameter. The ``path`` parameter has been deprecated in favor of ``fspath``. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given parent collector, + only conftest files in the collector's directory and its parent directories + are consulted. """ @hookspec(firstresult=True) def pytest_pycollect_makeitem( - collector: "PyCollector", name: str, obj: object + collector: Union["Module", "Class"], 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`. + + :param collector: + The module/class collector. + :param name: + The name of the object in the module/class. + :param obj: + The object. + :returns: + The created items/collectors. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given collector, only + conftest files in the collector's directory and its parent directories + are consulted. """ @@ -373,11 +552,32 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: """Call underlying test function. Stops at first non-None result, see :ref:`firstresult`. + + :param pyfuncitem: + The function item. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only + conftest files in the item's directory and its parent directories + are consulted. """ def pytest_generate_tests(metafunc: "Metafunc") -> None: - """Generate (multiple) parametrized calls to a test function.""" + """Generate (multiple) parametrized calls to a test function. + + :param metafunc: + The :class:`~pytest.Metafunc` helper for the test function. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given function definition, + only conftest files in the functions's directory and its parent directories + are consulted. + """ @hookspec(firstresult=True) @@ -392,9 +592,14 @@ def pytest_make_parametrize_id( Stops at first non-None result, see :ref:`firstresult`. - :param pytest.Config config: The pytest config object. + :param config: The pytest config object. :param val: The parametrized value. - :param str argname: The automatic parameter name produced by pytest. + :param argname: The automatic parameter name produced by pytest. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. """ @@ -417,10 +622,15 @@ def pytest_runtestloop(session: "Session") -> Optional[object]: 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. + :param session: The pytest session object. Stops at first non-None result, see :ref:`firstresult`. The return value is not used, but only stops further processing. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. """ @@ -440,7 +650,7 @@ def pytest_runtest_protocol( - ``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 phase, if 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)`` @@ -459,6 +669,11 @@ def pytest_runtest_protocol( Stops at first non-None result, see :ref:`firstresult`. The return value is not used, but only stops further processing. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. """ @@ -469,8 +684,16 @@ def pytest_runtest_logstart( See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. - :param str nodeid: Full node ID of the item. - :param location: A tuple of ``(filename, lineno, testname)``. + :param nodeid: Full node ID of the item. + :param location: A tuple of ``(filename, lineno, testname)`` + where ``filename`` is a file path relative to ``config.rootpath`` + and ``lineno`` is 0-based. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. """ @@ -481,8 +704,16 @@ def pytest_runtest_logfinish( See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. - :param str nodeid: Full node ID of the item. - :param location: A tuple of ``(filename, lineno, testname)``. + :param nodeid: Full node ID of the item. + :param location: A tuple of ``(filename, lineno, testname)`` + where ``filename`` is a file path relative to ``config.rootpath`` + and ``lineno`` is 0-based. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. """ @@ -493,6 +724,15 @@ def pytest_runtest_setup(item: "Item") -> None: parents (which haven't been setup yet). This includes obtaining the values of fixtures required by the item (which haven't been obtained yet). + + :param item: + The item. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. """ @@ -500,6 +740,15 @@ def pytest_runtest_call(item: "Item") -> None: """Called to run the test for test item (the call phase). The default implementation calls ``item.runtest()``. + + :param item: + The item. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. """ @@ -511,11 +760,19 @@ def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None: includes running the teardown phase of fixtures required by the item (if they go out of scope). + :param item: + The item. :param nextitem: The scheduled-to-be-next test item (None if no further test item is scheduled). This argument is used to perform exact teardowns, i.e. calling just enough finalizers so that nextitem only needs to call setup functions. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. """ @@ -528,9 +785,16 @@ def pytest_runtest_makereport( See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. + :param item: The item. :param call: The :class:`~pytest.CallInfo` for the phase. Stops at first non-None result, see :ref:`firstresult`. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. """ @@ -539,6 +803,12 @@ def pytest_runtest_logreport(report: "TestReport") -> None: of the setup, call and teardown runtest phases of an item. See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. """ @@ -548,7 +818,17 @@ def pytest_report_to_serializable( 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.""" + sending over the wire, e.g. converted to JSON. + + :param config: The pytest config object. + :param report: The report. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. The exact details may depend + on the plugin which calls the hook. + """ @hookspec(firstresult=True) @@ -557,7 +837,16 @@ def pytest_report_from_serializable( data: Dict[str, Any], ) -> Optional[Union["CollectReport", "TestReport"]]: """Restore a report object previously serialized with - :hook:`pytest_report_to_serializable`.""" + :hook:`pytest_report_to_serializable`. + + :param config: The pytest config object. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. The exact details may depend + on the plugin which calls the hook. + """ # ------------------------------------------------------------------------- @@ -571,7 +860,12 @@ def pytest_fixture_setup( ) -> Optional[object]: """Perform fixture setup execution. - :returns: The return value of the call to the fixture function. + :param fixturedef: + The fixture definition object. + :param request: + The fixture request object. + :returns: + The return value of the call to the fixture function. Stops at first non-None result, see :ref:`firstresult`. @@ -579,6 +873,13 @@ def pytest_fixture_setup( If the fixture function returns None, other implementations of this hook function will continue to be called, according to the behavior of the :ref:`firstresult` option. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given fixture, only + conftest files in the fixture scope's directory and its parent directories + are consulted. """ @@ -587,7 +888,20 @@ def pytest_fixture_post_finalizer( ) -> None: """Called after fixture teardown, but before the cache is cleared, so the fixture result ``fixturedef.cached_result`` is still available (not - ``None``).""" + ``None``). + + :param fixturedef: + The fixture definition object. + :param request: + The fixture request object. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given fixture, only + conftest files in the fixture scope's directory and its parent directories + are consulted. + """ # ------------------------------------------------------------------------- @@ -599,7 +913,12 @@ 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.Session session: The pytest session object. + :param session: The pytest session object. + + Use in conftest plugins + ======================= + + This hook is only called for :ref:`initial conftests `. """ @@ -609,15 +928,25 @@ def pytest_sessionfinish( ) -> None: """Called after whole test run finished, right before returning the exit status to the system. - :param pytest.Session session: The pytest session object. - :param int exitstatus: The status which pytest will return to the system. + :param session: The pytest session object. + :param exitstatus: The status which pytest will return to the system. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. """ def pytest_unconfigure(config: "Config") -> None: """Called before test process is exited. - :param pytest.Config config: The pytest config object. + :param config: The pytest config object. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. """ @@ -636,7 +965,16 @@ def pytest_assertrepr_compare( *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: The pytest config object. + :param config: The pytest config object. + :param op: The operator, e.g. `"=="`, `"!="`, `"not in"`. + :param left: The left operand. + :param right: The right operand. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. """ @@ -661,10 +999,16 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No 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. + :param item: pytest item object of current test. + :param lineno: Line number of the assert statement. + :param orig: String with the original assertion. + :param expl: String with the assert explanation. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. """ @@ -673,14 +1017,21 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No # ------------------------------------------------------------------------- -def pytest_report_header( +@hookspec( + warn_on_impl_args={ + "startdir": HOOK_LEGACY_PATH_ARG.format( + pylib_path_arg="startdir", pathlib_path_arg="start_path" + ), + }, +) +def pytest_report_header( # type:ignore[empty-body] config: "Config", start_path: Path, startdir: "LEGACY_PATH" ) -> Union[str, List[str]]: """Return a string or list of strings to be displayed as header info for terminal reporting. - :param pytest.Config config: The pytest config object. - :param Path start_path: The starting dir. - :param LEGACY_PATH startdir: The starting dir (deprecated). + :param config: The pytest config object. + :param start_path: The starting dir. + :param startdir: The starting dir (deprecated). .. note:: @@ -689,20 +1040,26 @@ def pytest_report_header( If you want to have your line(s) displayed first, use :ref:`trylast=True `. - .. note:: - - This function should be implemented only in plugins or ``conftest.py`` - files situated at the tests root directory due to how pytest - :ref:`discovers plugins during startup `. - .. versionchanged:: 7.0.0 The ``start_path`` parameter was added as a :class:`pathlib.Path` equivalent of the ``startdir`` parameter. The ``startdir`` parameter has been deprecated. + + Use in conftest plugins + ======================= + + This hook is only called for :ref:`initial conftests `. """ -def pytest_report_collectionfinish( +@hookspec( + warn_on_impl_args={ + "startdir": HOOK_LEGACY_PATH_ARG.format( + pylib_path_arg="startdir", pathlib_path_arg="start_path" + ), + }, +) +def pytest_report_collectionfinish( # type:ignore[empty-body] config: "Config", start_path: Path, startdir: "LEGACY_PATH", @@ -715,9 +1072,9 @@ def pytest_report_collectionfinish( .. versionadded:: 3.2 - :param pytest.Config config: The pytest config object. - :param Path start_path: The starting dir. - :param LEGACY_PATH startdir: The starting dir (deprecated). + :param config: The pytest config object. + :param start_path: The starting dir. + :param startdir: The starting dir (deprecated). :param items: List of pytest items that are going to be executed; this list should not be modified. .. note:: @@ -731,13 +1088,18 @@ def pytest_report_collectionfinish( The ``start_path`` parameter was added as a :class:`pathlib.Path` equivalent of the ``startdir`` parameter. The ``startdir`` parameter has been deprecated. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. """ @hookspec(firstresult=True) -def pytest_report_teststatus( +def pytest_report_teststatus( # type:ignore[empty-body] report: Union["CollectReport", "TestReport"], config: "Config" -) -> Tuple[str, str, Union[str, Mapping[str, bool]]]: +) -> "TestShortLogReport | Tuple[str, str, Union[str, Tuple[str, Mapping[str, bool]]]]": """Return result-category, shortletter and verbose word for status reporting. @@ -756,8 +1118,14 @@ def pytest_report_teststatus( :param report: The report object whose status is to be returned. :param config: The pytest config object. + :returns: The test status. Stops at first non-None result, see :ref:`firstresult`. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. """ @@ -768,47 +1136,17 @@ def pytest_terminal_summary( ) -> None: """Add a section to terminal summary reporting. - :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: The pytest config object. + :param terminalreporter: The internal terminal reporter object. + :param exitstatus: The exit status that will be reported back to the OS. + :param config: The pytest config object. .. versionadded:: 4.2 The ``config`` parameter. - """ - - -@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 - - 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 - the same attributes as the parameters of :py:func:`warnings.showwarning`. - - :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 pytest.Item|None item: - The item being executed if ``when`` is ``"runtest"``, otherwise ``None``. + Use in conftest plugins + ======================= - :param tuple location: - When available, holds information about the execution context of the captured - warning (filename, linenumber, function). ``function`` evaluates to - when the execution context is at the module level. + Any conftest plugin can implement this hook. """ @@ -821,26 +1159,34 @@ def pytest_warning_recorded( ) -> 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`. + :param warning_message: + The captured warning. This is the same object produced by :class:`warnings.catch_warnings`, + and contains the same attributes as the parameters of :py:func:`warnings.showwarning`. - :param str when: + :param 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. + :param nodeid: + Full id of the item. Empty string for warnings that are not specific to + a particular node. - :param tuple|None location: + :param location: When available, holds information about the execution context of the captured warning (filename, linenumber, function). ``function`` evaluates to when the execution context is at the module level. .. versionadded:: 6.0 + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. If the warning is specific to a + particular node, only conftest files in parent directories of the node are + consulted. """ @@ -849,7 +1195,9 @@ def pytest_warning_recorded( # ------------------------------------------------------------------------- -def pytest_markeval_namespace(config: "Config") -> Dict[str, Any]: +def pytest_markeval_namespace( # type:ignore[empty-body] + config: "Config", +) -> Dict[str, Any]: """Called when constructing the globals dictionary used for evaluating string conditions in xfail/skipif markers. @@ -860,8 +1208,14 @@ def pytest_markeval_namespace(config: "Config") -> Dict[str, Any]: .. versionadded:: 6.2 - :param pytest.Config config: The pytest config object. + :param config: The pytest config object. :returns: A dictionary of additional globals to add. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in parent directories of the item are consulted. """ @@ -878,13 +1232,29 @@ def pytest_internalerror( Return True to suppress the fallback handling of printing an INTERNALERROR message directly to sys.stderr. + + :param excrepr: The exception repr object. + :param excinfo: The exception info. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. """ def pytest_keyboard_interrupt( excinfo: "ExceptionInfo[Union[KeyboardInterrupt, Exit]]", ) -> None: - """Called for keyboard interrupt.""" + """Called for keyboard interrupt. + + :param excinfo: The exception info. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. + """ def pytest_exception_interact( @@ -896,13 +1266,26 @@ def pytest_exception_interact( interactively handled. May be called during collection (see :hook:`pytest_make_collect_report`), - in which case ``report`` is a :class:`CollectReport`. + in which case ``report`` is a :class:`~pytest.CollectReport`. May be called during runtest of an item (see :hook:`pytest_runtest_protocol`), - in which case ``report`` is a :class:`TestReport`. + in which case ``report`` is a :class:`~pytest.TestReport`. This hook is not called if the exception that was raised is an internal exception like ``skip.Exception``. + + :param node: + The item or collector. + :param call: + The call information. Contains the exception. + :param report: + The collection or test report. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given node, only conftest + files in parent directories of the node are consulted. """ @@ -912,8 +1295,13 @@ def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None: Can be used by plugins to take special action just before the python debugger enters interactive mode. - :param pytest.Config config: The pytest config object. - :param pdb.Pdb pdb: The Pdb instance. + :param config: The pytest config object. + :param pdb: The Pdb instance. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. """ @@ -923,6 +1311,11 @@ def pytest_leave_pdb(config: "Config", pdb: "pdb.Pdb") -> None: Can be used by plugins to take special action just after the python debugger leaves interactive mode. - :param pytest.Config config: The pytest config object. - :param pdb.Pdb pdb: The Pdb instance. + :param config: The pytest config object. + :param pdb: The Pdb instance. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. """ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/junitxml.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/junitxml.py index 4af5fbab0c081..13fc9277aeca7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/junitxml.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/junitxml.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs """Report test results in JUnit-XML format, for use with Jenkins and build integration servers. @@ -6,12 +7,12 @@ 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 datetime import datetime import functools import os import platform import re -import xml.etree.ElementTree as ET -from datetime import datetime from typing import Callable from typing import Dict from typing import List @@ -19,8 +20,8 @@ from typing import Optional from typing import Tuple from typing import Union +import xml.etree.ElementTree as ET -import pytest from _pytest import nodes from _pytest import timing from _pytest._code.code import ExceptionRepr @@ -32,6 +33,7 @@ from _pytest.reports import TestReport from _pytest.stash import StashKey from _pytest.terminal import TerminalReporter +import pytest xml_key = StashKey["LogXML"]() @@ -59,7 +61,7 @@ def repl(matchobj: Match[str]) -> str: # 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]" + "[^\u0009\u000a\u000d\u0020-\u007e\u0080-\ud7ff\ue000-\ufffd\u10000-\u10ffff]" ) return re.sub(illegal_xml_re, repl, str(arg)) @@ -92,7 +94,7 @@ def __init__(self, nodeid: Union[str, TestReport], xml: "LogXML") -> None: self.xml = xml self.add_stats = self.xml.add_stats self.family = self.xml.family - self.duration = 0 + self.duration = 0.0 self.properties: List[Tuple[str, str]] = [] self.nodes: List[ET.Element] = [] self.attrs: Dict[str, str] = {} @@ -141,7 +143,7 @@ def record_testreport(self, testreport: TestReport) -> None: # Filter out attributes not permitted by this test family. # Including custom attributes because they are not valid here. temp_attrs = {} - for key in self.attrs.keys(): + for key in self.attrs: if key in families[self.family]["testcase"]: temp_attrs[key] = self.attrs[key] self.attrs = temp_attrs @@ -231,7 +233,7 @@ def append_error(self, report: TestReport) -> None: msg = f'failed on teardown with "{reason}"' else: msg = f'failed on setup with "{reason}"' - self._add_simple("error", msg, str(report.longrepr)) + self._add_simple("error", bin_xml_escape(msg), str(report.longrepr)) def append_skipped(self, report: TestReport) -> None: if hasattr(report, "wasxfail"): @@ -248,7 +250,9 @@ def append_skipped(self, report: TestReport) -> None: skipreason = skipreason[9:] details = f"{filename}:{lineno}: {skipreason}" - skipped = ET.Element("skipped", type="pytest.skip", message=skipreason) + skipped = ET.Element( + "skipped", type="pytest.skip", message=bin_xml_escape(skipreason) + ) skipped.text = bin_xml_escape(details) self.append(skipped) self.write_captured_output(report) @@ -258,7 +262,7 @@ def finalize(self) -> None: self.__dict__.clear() # Type ignored because mypy doesn't like overriding a method. # Also the return value doesn't match... - self.to_xml = lambda: data # type: ignore[assignment] + self.to_xml = lambda: data # type: ignore[method-assign] def _warn_incompatibility_with_xunit2( @@ -271,9 +275,7 @@ def _warn_incompatibility_with_xunit2( if xml is not None and xml.family not in ("xunit1", "legacy"): request.node.warn( PytestWarning( - "{fixture_name} is incompatible with junit_family '{family}' (use 'legacy' or 'xunit1')".format( - fixture_name=fixture_name, family=xml.family - ) + f"{fixture_name} is incompatible with junit_family '{xml.family}' (use 'legacy' or 'xunit1')" ) ) @@ -354,7 +356,10 @@ def test_foo(record_testsuite_property): record_testsuite_property("ARCH", "PPC") record_testsuite_property("STORAGE_TYPE", "CEPH") - ``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped. + :param name: + The property name. + :param value: + The property value. Will be converted to a string. .. warning:: @@ -362,17 +367,16 @@ def test_foo(record_testsuite_property): `pytest-xdist `__ plugin. See :issue:`7767` for details. """ - __tracebackhide__ = True def record_func(name: str, value: object) -> None: - """No-op function in case --junitxml was not passed in the command-line.""" + """No-op function in case --junit-xml was not passed in the command-line.""" __tracebackhide__ = True _check_record_param_type("name", name) xml = request.config.stash.get(xml_key, None) if xml is not None: - record_func = xml.add_global_property # noqa + record_func = xml.add_global_property return record_func @@ -386,7 +390,7 @@ def pytest_addoption(parser: Parser) -> None: metavar="path", type=functools.partial(filename_arg, optname="--junitxml"), default=None, - help="create junit-xml style report file at given path.", + help="Create junit-xml style report file at given path", ) group.addoption( "--junitprefix", @@ -394,7 +398,7 @@ def pytest_addoption(parser: Parser) -> None: action="store", metavar="str", default=None, - help="prepend prefix to classnames in junit-xml output", + help="Prepend prefix to classnames in junit-xml output", ) parser.addini( "junit_suite_name", "Test suite name for JUnit report", default="pytest" @@ -499,6 +503,10 @@ def finalize(self, report: TestReport) -> None: # Local hack to handle xdist report order. workernode = getattr(report, "node", None) reporter = self.node_reporters.pop((nodeid, workernode)) + + for propname, propvalue in report.user_properties: + reporter.add_property(propname, str(propvalue)) + if reporter is not None: reporter.finalize() @@ -596,9 +604,6 @@ def pytest_runtest_logreport(self, report: TestReport) -> None: reporter = self._opentestcase(report) reporter.write_captured_output(report) - for propname, propvalue in report.user_properties: - reporter.add_property(propname, str(propvalue)) - self.finalize(report) report_wid = getattr(report, "worker_id", None) report_ii = getattr(report, "item_index", None) @@ -620,7 +625,7 @@ def pytest_runtest_logreport(self, report: TestReport) -> None: 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: + if self.report_duration in {"total", report.when}: reporter = self.node_reporter(report) reporter.duration += getattr(report, "duration", 0.0) @@ -642,8 +647,8 @@ def pytest_sessionstart(self) -> None: def pytest_sessionfinish(self) -> None: dirname = os.path.dirname(os.path.abspath(self.logfile)) - if not os.path.isdir(dirname): - os.makedirs(dirname) + # exist_ok avoids filesystem race conditions between checking path existence and requesting creation + os.makedirs(dirname, exist_ok=True) with open(self.logfile, "w", encoding="utf-8") as logfile: suite_stop_time = timing.time() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/legacypath.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/legacypath.py index 37e8c24220e06..d9de65b1a5386 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/legacypath.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/legacypath.py @@ -1,17 +1,20 @@ +# mypy: allow-untyped-defs """Add backward compatibility support for the legacy py path type.""" + +import dataclasses +from pathlib import Path import shlex import subprocess -from pathlib import Path +from typing import Final +from typing import final from typing import List from typing import Optional from typing import TYPE_CHECKING from typing import Union -import attr from iniconfig import SectionWrapper from _pytest.cacheprovider import Cache -from _pytest.compat import final from _pytest.compat import LEGACY_PATH from _pytest.compat import legacy_path from _pytest.config import Config @@ -31,9 +34,8 @@ from _pytest.terminal import TerminalReporter from _pytest.tmpdir import TempPathFactory -if TYPE_CHECKING: - from typing_extensions import Final +if TYPE_CHECKING: import pexpect @@ -89,7 +91,6 @@ def chdir(self) -> None: return self._pytester.chdir() def finalize(self) -> None: - """See :meth:`Pytester._finalize`.""" return self._pytester._finalize() def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH: @@ -268,10 +269,17 @@ def testdir(pytester: Pytester) -> Testdir: @final -@attr.s(init=False, auto_attribs=True) +@dataclasses.dataclass class TempdirFactory: - """Backward compatibility wrapper that implements :class:``_pytest.compat.LEGACY_PATH`` - for :class:``TempPathFactory``.""" + """Backward compatibility wrapper that implements ``py.path.local`` + for :class:`TempPathFactory`. + + .. note:: + These days, it is preferred to use ``tmp_path_factory``. + + :ref:`About the tmpdir and tmpdir_factory fixtures`. + + """ _tmppath_factory: TempPathFactory @@ -282,11 +290,11 @@ def __init__( self._tmppath_factory = tmppath_factory def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH: - """Same as :meth:`TempPathFactory.mktemp`, but returns a ``_pytest.compat.LEGACY_PATH`` object.""" + """Same as :meth:`TempPathFactory.mktemp`, but returns a ``py.path.local`` object.""" return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve()) def getbasetemp(self) -> LEGACY_PATH: - """Backward compat wrapper for ``_tmppath_factory.getbasetemp``.""" + """Same as :meth:`TempPathFactory.getbasetemp`, but returns a ``py.path.local`` object.""" return legacy_path(self._tmppath_factory.getbasetemp().resolve()) @@ -307,11 +315,16 @@ def tmpdir(tmp_path: Path) -> LEGACY_PATH: By default, a new base temporary directory is created each test session, and old bases are removed after 3 sessions, to aid in debugging. If - ``--basetemp`` is used then it is cleared each session. See :ref:`base - temporary directory`. + ``--basetemp`` is used then it is cleared each session. See + :ref:`temporary directory location and retention`. The returned object is a `legacy_path`_ object. + .. note:: + These days, it is preferred to use ``tmp_path``. + + :ref:`About the tmpdir and tmpdir_factory fixtures`. + .. _legacy_path: https://py.readthedocs.io/en/latest/path.html """ return legacy_path(tmp_path) @@ -371,7 +384,7 @@ def Config_inifile(self: Config) -> Optional[LEGACY_PATH]: return legacy_path(str(self.inipath)) if self.inipath else None -def Session_stardir(self: Session) -> LEGACY_PATH: +def Session_startdir(self: Session) -> LEGACY_PATH: """The path from which pytest was invoked. Prefer to use ``startpath`` which is a :class:`pathlib.Path`. @@ -426,7 +439,7 @@ def pytest_load_initial_conftests(early_config: Config) -> None: mp.setattr(Config, "inifile", property(Config_inifile), raising=False) # Add Session.startdir property. - mp.setattr(Session, "startdir", property(Session_stardir), raising=False) + mp.setattr(Session, "startdir", property(Session_startdir), raising=False) # Add pathlist configuration type. mp.setattr(Config, "_getini_unknown_type", Config__getini_unknown_type) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/logging.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/logging.py index 31ad830107610..af5e443ced19b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/logging.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/logging.py @@ -1,26 +1,37 @@ +# mypy: allow-untyped-defs """Access and control log capturing.""" -import logging -import os -import re -import sys + from contextlib import contextmanager +from contextlib import nullcontext +from datetime import datetime +from datetime import timedelta +from datetime import timezone +import io from io import StringIO +import logging +from logging import LogRecord +import os from pathlib import Path +import re +from types import TracebackType from typing import AbstractSet from typing import Dict +from typing import final from typing import Generator +from typing import Generic from typing import List +from typing import Literal from typing import Mapping from typing import Optional from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING from typing import TypeVar from typing import Union 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 @@ -35,6 +46,11 @@ from _pytest.terminal import TerminalReporter +if TYPE_CHECKING: + logging_StreamHandler = logging.StreamHandler[StringIO] +else: + logging_StreamHandler = logging.StreamHandler + 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") @@ -46,7 +62,26 @@ def _remove_ansi_escape_sequences(text: str) -> str: return _ANSI_ESCAPE_SEQ.sub("", text) -class ColoredLevelFormatter(logging.Formatter): +class DatetimeFormatter(logging.Formatter): + """A logging formatter which formats record with + :func:`datetime.datetime.strftime` formatter instead of + :func:`time.strftime` in case of microseconds in format string. + """ + + def formatTime(self, record: LogRecord, datefmt: Optional[str] = None) -> str: + if datefmt and "%f" in datefmt: + ct = self.converter(record.created) + tz = timezone(timedelta(seconds=ct.tm_gmtoff), ct.tm_zone) + # Construct `datetime.datetime` object from `struct_time` + # and msecs information from `record` + # Using int() instead of round() to avoid it exceeding 1_000_000 and causing a ValueError (#11861). + dt = datetime(*ct[0:6], microsecond=int(record.msecs * 1000), tzinfo=tz) + return dt.strftime(datefmt) + # Use `logging.Formatter` for non-microsecond formats + return super().formatTime(record, datefmt) + + +class ColoredLevelFormatter(DatetimeFormatter): """A logging formatter which colorizes the %(levelname)..s part of the log format passed to __init__.""" @@ -83,7 +118,6 @@ def add_color_level(self, level: int, *color_opts: str) -> None: .. warning:: This is an experimental API. """ - assert self._fmt is not None levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt) if not levelname_fmt_match: @@ -150,7 +184,6 @@ def _get_auto_indent(auto_indent_option: Union[int, str, bool, None]) -> int: 0 (auto-indent turned off) or >0 (explicitly set indentation position). """ - if auto_indent_option is None: return 0 elif isinstance(auto_indent_option, bool): @@ -177,7 +210,7 @@ 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] + auto_indent = self._get_auto_indent(record.auto_indent) else: auto_indent = self._auto_indent @@ -212,7 +245,7 @@ def pytest_addoption(parser: Parser) -> None: def add_option_ini(option, dest, default=None, type=None, **kwargs): parser.addini( - dest, default=default, type=type, help="default value for " + option + dest, default=default, type=type, help="Default value for " + option ) group.addoption(option, dest=dest, **kwargs) @@ -222,8 +255,8 @@ def add_option_ini(option, dest, default=None, type=None, **kwargs): default=None, metavar="LEVEL", help=( - "level of messages to catch/display.\n" - "Not set by default, so it depends on the root/parent log handler's" + "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.' ), ) @@ -231,58 +264,65 @@ def add_option_ini(option, dest, default=None, type=None, **kwargs): "--log-format", dest="log_format", default=DEFAULT_LOG_FORMAT, - help="log format as used by the logging module.", + help="Log format used by the logging module", ) add_option_ini( "--log-date-format", dest="log_date_format", default=DEFAULT_LOG_DATE_FORMAT, - help="log date format as used by the logging module.", + help="Log date format used by the logging module", ) parser.addini( "log_cli", default=False, type="bool", - help='enable log display during test run (also known as "live logging").', + help='Enable log display during test run (also known as "live logging")', ) add_option_ini( - "--log-cli-level", dest="log_cli_level", default=None, help="cli logging level." + "--log-cli-level", dest="log_cli_level", default=None, help="CLI logging level" ) add_option_ini( "--log-cli-format", dest="log_cli_format", default=None, - help="log format as used by the logging module.", + help="Log format used by the logging module", ) add_option_ini( "--log-cli-date-format", dest="log_cli_date_format", default=None, - help="log date format as used by the logging module.", + help="Log date format used by the logging module", ) add_option_ini( "--log-file", dest="log_file", default=None, - help="path to a file when logging will be written to.", + help="Path to a file when logging will be written to", + ) + add_option_ini( + "--log-file-mode", + dest="log_file_mode", + default="w", + choices=["w", "a"], + help="Log file open mode", ) add_option_ini( "--log-file-level", dest="log_file_level", default=None, - help="log file logging level.", + help="Log file logging level", ) add_option_ini( "--log-file-format", dest="log_file_format", - default=DEFAULT_LOG_FORMAT, - help="log format as used by the logging module.", + default=None, + help="Log format used by the logging module", ) add_option_ini( "--log-file-date-format", dest="log_file_date_format", - default=DEFAULT_LOG_DATE_FORMAT, - help="log date format as used by the logging module.", + default=None, + help="Log date format used by the logging module", ) add_option_ini( "--log-auto-indent", @@ -290,13 +330,20 @@ def add_option_ini(option, dest, default=None, type=None, **kwargs): default=None, help="Auto-indent multiline messages passed to the logging module. Accepts true|on, false|off or an integer.", ) + group.addoption( + "--log-disable", + action="append", + default=[], + dest="logger_disable", + help="Disable a logger by name. Can be passed multiple times.", + ) _HandlerType = TypeVar("_HandlerType", bound=logging.Handler) # Not using @contextmanager for performance reasons. -class catching_logs: +class catching_logs(Generic[_HandlerType]): """Context manager that prepares the whole logging machinery properly.""" __slots__ = ("handler", "level", "orig_level") @@ -305,7 +352,7 @@ def __init__(self, handler: _HandlerType, level: Optional[int] = None) -> None: self.handler = handler self.level = level - def __enter__(self): + def __enter__(self) -> _HandlerType: root_logger = logging.getLogger() if self.level is not None: self.handler.setLevel(self.level) @@ -315,18 +362,21 @@ def __enter__(self): root_logger.setLevel(min(self.orig_level, self.level)) return self.handler - def __exit__(self, type, value, traceback): + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: 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): +class LogCaptureHandler(logging_StreamHandler): """A logging handler that stores log records and the log text.""" - stream: StringIO - def __init__(self) -> None: """Create a new log handler.""" super().__init__(StringIO()) @@ -341,6 +391,10 @@ def reset(self) -> None: self.records = [] self.stream = StringIO() + def clear(self) -> None: + self.records.clear() + self.stream = StringIO() + def handleError(self, record: logging.LogRecord) -> None: if logging.raiseExceptions: # Fail the test if the log message is bad (emit failed). @@ -360,11 +414,12 @@ def __init__(self, item: nodes.Node, *, _ispytest: bool = False) -> None: self._initial_handler_level: Optional[int] = None # Dict of log name -> log level. self._initial_logger_levels: Dict[Optional[str], int] = {} + self._initial_disabled_logging_level: Optional[int] = None def _finalize(self) -> None: """Finalize the fixture. - This restores the log levels changed by :meth:`set_level`. + This restores the log levels and the disabled logging levels changed by :meth:`set_level`. """ # Restore log levels. if self._initial_handler_level is not None: @@ -372,23 +427,26 @@ def _finalize(self) -> None: for logger_name, level in self._initial_logger_levels.items(): logger = logging.getLogger(logger_name) logger.setLevel(level) + # Disable logging at the original disabled logging level. + if self._initial_disabled_logging_level is not None: + logging.disable(self._initial_disabled_logging_level) + self._initial_disabled_logging_level = None @property def handler(self) -> LogCaptureHandler: - """Get the logging handler used by the fixture. - - :rtype: LogCaptureHandler - """ + """Get the logging handler used by the fixture.""" return self._item.stash[caplog_handler_key] - def get_records(self, when: str) -> List[logging.LogRecord]: + def get_records( + self, when: Literal["setup", "call", "teardown"] + ) -> 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". + :param 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] .. versionadded:: 3.4 """ @@ -436,17 +494,55 @@ def messages(self) -> List[str]: def clear(self) -> None: """Reset the list of log records and the captured log text.""" - self.handler.reset() + self.handler.clear() + + def _force_enable_logging( + self, level: Union[int, str], logger_obj: logging.Logger + ) -> int: + """Enable the desired logging level if the global level was disabled via ``logging.disabled``. + + Only enables logging levels greater than or equal to the requested ``level``. + + Does nothing if the desired ``level`` wasn't disabled. + + :param level: + The logger level caplog should capture. + All logging is enabled if a non-standard logging level string is supplied. + Valid level strings are in :data:`logging._nameToLevel`. + :param logger_obj: The logger object to check. + + :return: The original disabled logging level. + """ + original_disable_level: int = logger_obj.manager.disable + + if isinstance(level, str): + # Try to translate the level string to an int for `logging.disable()` + level = logging.getLevelName(level) + + if not isinstance(level, int): + # The level provided was not valid, so just un-disable all logging. + logging.disable(logging.NOTSET) + elif not logger_obj.isEnabledFor(level): + # Each level is `10` away from other levels. + # https://docs.python.org/3/library/logging.html#logging-levels + disable_level = max(level - 10, logging.NOTSET) + logging.disable(disable_level) + + return original_disable_level 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. + """Set the threshold level of a logger for the duration of a test. + + Logging messages which are less severe than this level will not be captured. .. 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. - :param int level: The level. - :param str logger: The logger to update. If not given, the root logger. + Will enable the requested logging level if it was disabled via :func:`logging.disable`. + + :param level: The level. + :param logger: The logger to update. If not given, the root logger. """ logger_obj = logging.getLogger(logger) # Save the original log-level to restore it during teardown. @@ -455,6 +551,9 @@ def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> Non if self._initial_handler_level is None: self._initial_handler_level = self.handler.level self.handler.setLevel(level) + initial_disabled_logging_level = self._force_enable_logging(level, logger_obj) + if self._initial_disabled_logging_level is None: + self._initial_disabled_logging_level = initial_disabled_logging_level @contextmanager def at_level( @@ -464,19 +563,39 @@ def at_level( 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. + Will enable the requested logging level if it was disabled via :func:`logging.disable`. + + :param level: The level. + :param logger: The logger to update. If not given, the root logger. """ logger_obj = logging.getLogger(logger) orig_level = logger_obj.level logger_obj.setLevel(level) handler_orig_level = self.handler.level self.handler.setLevel(level) + original_disable_level = self._force_enable_logging(level, logger_obj) try: yield finally: logger_obj.setLevel(orig_level) self.handler.setLevel(handler_orig_level) + logging.disable(original_disable_level) + + @contextmanager + def filtering(self, filter_: logging.Filter) -> Generator[None, None, None]: + """Context manager that temporarily adds the given filter to the caplog's + :meth:`handler` for the 'with' statement block, and removes that filter at the + end of the block. + + :param filter_: A custom :class:`logging.Filter` object. + + .. versionadded:: 7.5 + """ + self.handler.addFilter(filter_) + try: + yield + finally: + self.handler.removeFilter(filter_) @fixture @@ -513,9 +632,9 @@ def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[i except ValueError as e: # Python logging does not recognise this as a logging level raise UsageError( - "'{}' is not recognized as a logging level name for " - "'{}'. Please consider passing the " - "logging level num instead.".format(log_level, setting_name) + f"'{log_level}' is not recognized as a logging level name for " + f"'{setting_name}'. Please consider passing the " + "logging level num instead." ) from e @@ -549,20 +668,25 @@ def __init__(self, config: Config) -> None: self.report_handler.setFormatter(self.formatter) # File logging. - self.log_file_level = get_log_level_for_setting(config, "log_file_level") + self.log_file_level = get_log_level_for_setting( + config, "log_file_level", "log_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") + self.log_file_mode = get_option_ini(config, "log_file_mode") or "w" + self.log_file_handler = _FileHandler( + log_file, mode=self.log_file_mode, 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" ) - log_file_formatter = logging.Formatter( + log_file_formatter = DatetimeFormatter( log_file_format, datefmt=log_file_date_format ) self.log_file_handler.setFormatter(log_file_formatter) @@ -573,6 +697,8 @@ def __init__(self, config: Config) -> None: ) if self._log_cli_enabled(): terminal_reporter = config.pluginmanager.get_plugin("terminalreporter") + # Guaranteed by `_log_cli_enabled()`. + assert terminal_reporter is not None capture_manager = config.pluginmanager.get_plugin("capturemanager") # if capturemanager plugin is disabled, live logging still works. self.log_cli_handler: Union[ @@ -586,6 +712,15 @@ def __init__(self, config: Config) -> None: get_option_ini(config, "log_auto_indent"), ) self.log_cli_handler.setFormatter(log_cli_formatter) + self._disable_loggers(loggers_to_disable=config.option.logger_disable) + + def _disable_loggers(self, loggers_to_disable: List[str]) -> None: + if not loggers_to_disable: + return + + for name in loggers_to_disable: + logger = logging.getLogger(name) + logger.disabled = True def _create_formatter(self, log_format, log_date_format, auto_indent): # Color option doesn't exist if terminal plugin is disabled. @@ -597,7 +732,7 @@ def _create_formatter(self, log_format, log_date_format, auto_indent): create_terminal_writer(self._config), log_format, log_date_format ) else: - formatter = logging.Formatter(log_format, log_date_format) + formatter = DatetimeFormatter(log_format, log_date_format) formatter._style = PercentStyleMultiline( formatter._style._fmt, auto_indent=auto_indent @@ -621,22 +756,13 @@ def set_log_path(self, fname: str) -> None: if not fpath.parent.exists(): fpath.parent.mkdir(exist_ok=True, parents=True) - 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() + # https://github.com/python/mypy/issues/11193 + stream: io.TextIOWrapper = fpath.open(mode=self.log_file_mode, encoding="UTF-8") # type: ignore[assignment] + old_stream = self.log_file_handler.setStream(stream) if old_stream: - # https://github.com/python/typeshed/pull/5663 - old_stream.close() # type:ignore[attr-defined] + old_stream.close() - def _log_cli_enabled(self): + def _log_cli_enabled(self) -> bool: """Return whether live logging is enabled.""" enabled = self._config.getoption( "--log-cli-level" @@ -651,27 +777,26 @@ def _log_cli_enabled(self): return True - @hookimpl(hookwrapper=True, tryfirst=True) + @hookimpl(wrapper=True, tryfirst=True) def pytest_sessionstart(self) -> Generator[None, None, None]: self.log_cli_handler.set_when("sessionstart") 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 + return (yield) - @hookimpl(hookwrapper=True, tryfirst=True) + @hookimpl(wrapper=True, tryfirst=True) def pytest_collection(self) -> Generator[None, None, None]: self.log_cli_handler.set_when("collection") 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 + return (yield) - @hookimpl(hookwrapper=True) - def pytest_runtestloop(self, session: Session) -> Generator[None, None, None]: + @hookimpl(wrapper=True) + def pytest_runtestloop(self, session: Session) -> Generator[None, object, object]: if session.config.option.collectonly: - yield - return + return (yield) if self._log_cli_enabled() and self._config.getoption("verbose") < 1: # The verbose flag is needed to avoid messy test progress output. @@ -679,7 +804,7 @@ def pytest_runtestloop(self, session: Session) -> Generator[None, None, None]: 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. + return (yield) # Run all the tests. @hookimpl def pytest_runtest_logstart(self) -> None: @@ -704,12 +829,13 @@ def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None, None, Non item.stash[caplog_records_key][when] = caplog_handler.records item.stash[caplog_handler_key] = caplog_handler - yield - - log = report_handler.stream.getvalue().strip() - item.add_report_section(when, "log", log) + try: + yield + finally: + log = report_handler.stream.getvalue().strip() + item.add_report_section(when, "log", log) - @hookimpl(hookwrapper=True) + @hookimpl(wrapper=True) def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None, None, None]: self.log_cli_handler.set_when("setup") @@ -717,31 +843,33 @@ def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None, None, None]: item.stash[caplog_records_key] = empty yield from self._runtest_for(item, "setup") - @hookimpl(hookwrapper=True) + @hookimpl(wrapper=True) 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") - @hookimpl(hookwrapper=True) + @hookimpl(wrapper=True) def pytest_runtest_teardown(self, item: nodes.Item) -> Generator[None, None, None]: self.log_cli_handler.set_when("teardown") - yield from self._runtest_for(item, "teardown") - del item.stash[caplog_records_key] - del item.stash[caplog_handler_key] + try: + yield from self._runtest_for(item, "teardown") + finally: + del item.stash[caplog_records_key] + del item.stash[caplog_handler_key] @hookimpl def pytest_runtest_logfinish(self) -> None: self.log_cli_handler.set_when("finish") - @hookimpl(hookwrapper=True, tryfirst=True) + @hookimpl(wrapper=True, tryfirst=True) 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 + return (yield) @hookimpl def pytest_unconfigure(self) -> None: @@ -758,7 +886,7 @@ def handleError(self, record: logging.LogRecord) -> None: pass -class _LiveLoggingStreamHandler(logging.StreamHandler): +class _LiveLoggingStreamHandler(logging_StreamHandler): """A logging StreamHandler used by the live logging feature: it will write a newline before the first log message in each test. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/main.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/main.py index fea8179ca7d4b..716d5cf783b38 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/main.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/main.py @@ -1,30 +1,35 @@ """Core implementation of the testing process: init, session, runtest loop.""" + import argparse +import dataclasses import fnmatch import functools import importlib +import importlib.util import os -import sys from pathlib import Path +import sys +from typing import AbstractSet from typing import Callable from typing import Dict +from typing import final from typing import FrozenSet +from typing import Iterable from typing import Iterator from typing import List +from typing import Literal from typing import Optional from typing import overload from typing import Sequence -from typing import Set from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import Union +import warnings -import attr +import pluggy -import _pytest._code from _pytest import nodes -from _pytest.compat import final +import _pytest._code from _pytest.config import Config from _pytest.config import directory_arg from _pytest.config import ExitCode @@ -32,26 +37,29 @@ from _pytest.config import PytestPluginManager from _pytest.config import UsageError from _pytest.config.argparsing import Parser +from _pytest.config.compat import PathAwareHookProxy from _pytest.fixtures import FixtureManager from _pytest.outcomes import exit from _pytest.pathlib import absolutepath from _pytest.pathlib import bestrelpath from _pytest.pathlib import fnmatch_ex -from _pytest.pathlib import visit +from _pytest.pathlib import safe_exists +from _pytest.pathlib import scandir from _pytest.reports import CollectReport from _pytest.reports import TestReport from _pytest.runner import collect_one_node from _pytest.runner import SetupState +from _pytest.warning_types import PytestWarning if TYPE_CHECKING: - from typing_extensions import Literal + from typing import Self def pytest_addoption(parser: Parser) -> None: parser.addini( "norecursedirs", - "directory patterns to avoid for recursion", + "Directory patterns to avoid for recursion", type="args", default=[ "*.egg", @@ -67,26 +75,26 @@ def pytest_addoption(parser: Parser) -> None: ) parser.addini( "testpaths", - "directories to search for tests when no files or directories are given in the " - "command line.", + "Directories to search for tests when no files or directories are given on the " + "command line", type="args", default=[], ) - group = parser.getgroup("general", "running and selection options") + group = parser.getgroup("general", "Running and selection options") group._addoption( "-x", "--exitfirst", action="store_const", dest="maxfail", const=1, - help="exit instantly on first error or failed test.", + 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.", + help="Set which warnings to report, see -W option of Python itself", ) parser.addini( "filterwarnings", @@ -102,37 +110,40 @@ def pytest_addoption(parser: Parser) -> None: type=int, dest="maxfail", default=0, - help="exit after first num failures or errors.", + 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.", + help="Any warnings encountered while parsing the `pytest` section of the " + "configuration file raise errors", ) group._addoption( "--strict-markers", action="store_true", - help="markers not registered in the `markers` section of the configuration file raise errors.", + help="Markers not registered in the `markers` section of the configuration " + "file raise errors", ) group._addoption( "--strict", action="store_true", - help="(deprecated) alias to --strict-markers.", + help="(Deprecated) alias to --strict-markers", ) group._addoption( "-c", - metavar="file", + "--config-file", + metavar="FILE", type=str, dest="inifilename", - help="load configuration from `file` instead of trying to locate one of the implicit " - "configuration files.", + help="Load configuration from `FILE` instead of trying to locate one of the " + "implicit configuration files.", ) group._addoption( "--continue-on-collection-errors", action="store_true", default=False, dest="continue_on_collection_errors", - help="Force test execution even if collection errors occur.", + help="Force test execution even if collection errors occur", ) group._addoption( "--rootdir", @@ -149,30 +160,30 @@ def pytest_addoption(parser: Parser) -> None: "--collect-only", "--co", action="store_true", - help="only collect tests, don't execute them.", + help="Only collect tests, don't execute them", ) group.addoption( "--pyargs", action="store_true", - help="try to interpret all arguments as python packages.", + help="Try to interpret all arguments as Python packages", ) group.addoption( "--ignore", action="append", metavar="path", - help="ignore path during collection (multi-allowed).", + help="Ignore path during collection (multi-allowed)", ) group.addoption( "--ignore-glob", action="append", metavar="path", - help="ignore path pattern during collection (multi-allowed).", + help="Ignore path pattern during collection (multi-allowed)", ) group.addoption( "--deselect", action="append", metavar="nodeid_prefix", - help="deselect item (via node id prefix) during collection (multi-allowed).", + help="Deselect item (via node id prefix) during collection (multi-allowed)", ) group.addoption( "--confcutdir", @@ -180,14 +191,14 @@ def pytest_addoption(parser: Parser) -> None: default=None, metavar="dir", type=functools.partial(directory_arg, optname="--confcutdir"), - help="only load conftest.py's relative to specified dir.", + help="Only load conftest.py's relative to specified dir", ) group.addoption( "--noconftest", action="store_true", dest="noconftest", default=False, - help="Don't load any conftest.py files.", + help="Don't load any conftest.py files", ) group.addoption( "--keepduplicates", @@ -195,7 +206,7 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", dest="keepduplicates", default=False, - help="Keep duplicate tests.", + help="Keep duplicate tests", ) group.addoption( "--collect-in-virtualenv", @@ -209,8 +220,14 @@ def pytest_addoption(parser: Parser) -> None: 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.", + help="Prepend/append to sys.path when importing test modules and conftest " + "files. Default: prepend.", + ) + parser.addini( + "consider_namespace_packages", + type="bool", + default=False, + help="Consider namespace packages when resolving module names during import", ) group = parser.getgroup("debugconfig", "test session debugging and configuration") @@ -221,8 +238,8 @@ def pytest_addoption(parser: Parser) -> None: type=validate_basetemp, metavar="dir", help=( - "base temporary directory for this test run." - "(warning: this directory is removed if it exists)" + "Base temporary directory for this test run. " + "(Warning: this directory is removed if it exists.)" ), ) @@ -373,8 +390,11 @@ def _in_venv(path: Path) -> bool: def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]: + if collection_path.name == "__pycache__": + return True + ignore_paths = config._getconftest_pathlist( - "collect_ignore", path=collection_path.parent, rootpath=config.rootpath + "collect_ignore", path=collection_path.parent ) ignore_paths = ignore_paths or [] excludeopt = config.getoption("ignore") @@ -385,7 +405,7 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo return True ignore_globs = config._getconftest_pathlist( - "collect_ignore_glob", path=collection_path.parent, rootpath=config.rootpath + "collect_ignore_glob", path=collection_path.parent ) ignore_globs = ignore_globs or [] excludeglobopt = config.getoption("ignore_glob") @@ -398,9 +418,21 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo allow_in_venv = config.getoption("collect_in_virtualenv") if not allow_in_venv and _in_venv(collection_path): return True + + if collection_path.is_dir(): + norecursepatterns = config.getini("norecursedirs") + if any(fnmatch_ex(pat, collection_path) for pat in norecursepatterns): + return True + return None +def pytest_collect_directory( + path: Path, parent: nodes.Collector +) -> Optional[nodes.Collector]: + return Dir.from_parent(parent, path=path) + + def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> None: deselect_prefixes = tuple(config.getoption("deselect") or []) if not deselect_prefixes: @@ -420,11 +452,15 @@ def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> No class FSHookProxy: - def __init__(self, pm: PytestPluginManager, remove_mods) -> None: + def __init__( + self, + pm: PytestPluginManager, + remove_mods: AbstractSet[object], + ) -> None: self.pm = pm self.remove_mods = remove_mods - def __getattr__(self, name: str): + def __getattr__(self, name: str) -> pluggy.HookCaller: x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods) self.__dict__[name] = x return x @@ -440,8 +476,10 @@ class Failed(Exception): """Signals a stop as failed test run.""" -@attr.s(slots=True, auto_attribs=True) +@dataclasses.dataclass class _bestrelpath_cache(Dict[Path, str]): + __slots__ = ("path",) + path: Path def __missing__(self, path: Path) -> str: @@ -451,7 +489,63 @@ def __missing__(self, path: Path) -> str: @final -class Session(nodes.FSCollector): +class Dir(nodes.Directory): + """Collector of files in a file system directory. + + .. versionadded:: 8.0 + + .. note:: + + Python directories with an `__init__.py` file are instead collected by + :class:`~pytest.Package` by default. Both are :class:`~pytest.Directory` + collectors. + """ + + @classmethod + def from_parent( # type: ignore[override] + cls, + parent: nodes.Collector, + *, + path: Path, + ) -> "Self": + """The public constructor. + + :param parent: The parent collector of this Dir. + :param path: The directory's path. + """ + return super().from_parent(parent=parent, path=path) + + def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + config = self.config + col: Optional[nodes.Collector] + cols: Sequence[nodes.Collector] + ihook = self.ihook + for direntry in scandir(self.path): + if direntry.is_dir(): + path = Path(direntry.path) + if not self.session.isinitpath(path, with_parents=True): + if ihook.pytest_ignore_collect(collection_path=path, config=config): + continue + col = ihook.pytest_collect_directory(path=path, parent=self) + if col is not None: + yield col + + elif direntry.is_file(): + path = Path(direntry.path) + if not self.session.isinitpath(path): + if ihook.pytest_ignore_collect(collection_path=path, config=config): + continue + cols = ihook.pytest_collect_file(file_path=path, parent=self) + yield from cols + + +@final +class Session(nodes.Collector): + """The root of the collection tree. + + ``Session`` collects the initial paths given as arguments to pytest. + """ + Interrupted = Interrupted Failed = Failed # Set on the session by runner.pytest_sessionstart. @@ -462,6 +556,7 @@ class Session(nodes.FSCollector): def __init__(self, config: Config) -> None: super().__init__( + name="", path=config.rootpath, fspath=None, parent=None, @@ -471,10 +566,15 @@ def __init__(self, config: Config) -> None: ) self.testsfailed = 0 self.testscollected = 0 - self.shouldstop: Union[bool, str] = False - self.shouldfail: Union[bool, str] = False + self._shouldstop: Union[bool, str] = False + self._shouldfail: Union[bool, str] = False self.trace = config.trace.root.get("collection") self._initialpaths: FrozenSet[Path] = frozenset() + self._initialpaths_with_parents: FrozenSet[Path] = frozenset() + self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = [] + self._initial_parts: List[CollectionArgument] = [] + self._collection_cache: Dict[nodes.Collector, CollectReport] = {} + self.items: List[nodes.Item] = [] self._bestrelpathcache: Dict[Path, str] = _bestrelpath_cache(config.rootpath) @@ -494,6 +594,42 @@ def __repr__(self) -> str: self.testscollected, ) + @property + def shouldstop(self) -> Union[bool, str]: + return self._shouldstop + + @shouldstop.setter + def shouldstop(self, value: Union[bool, str]) -> None: + # The runner checks shouldfail and assumes that if it is set we are + # definitely stopping, so prevent unsetting it. + if value is False and self._shouldstop: + warnings.warn( + PytestWarning( + "session.shouldstop cannot be unset after it has been set; ignoring." + ), + stacklevel=2, + ) + return + self._shouldstop = value + + @property + def shouldfail(self) -> Union[bool, str]: + return self._shouldfail + + @shouldfail.setter + def shouldfail(self, value: Union[bool, str]) -> None: + # The runner checks shouldfail and assumes that if it is set we are + # definitely stopping, so prevent unsetting it. + if value is False and self._shouldfail: + warnings.warn( + PytestWarning( + "session.shouldfail cannot be unset after it has been set; ignoring." + ), + stacklevel=2, + ) + return + self._shouldfail = value + @property def startpath(self) -> Path: """The path from which pytest was invoked. @@ -525,80 +661,87 @@ def pytest_runtest_logreport( pytest_collectreport = pytest_runtest_logreport - def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool: + def isinitpath( + self, + path: Union[str, "os.PathLike[str]"], + *, + with_parents: bool = False, + ) -> bool: + """Is path an initial path? + + An initial path is a path explicitly given to pytest on the command + line. + + :param with_parents: + If set, also return True if the path is a parent of an initial path. + + .. versionchanged:: 8.0 + Added the ``with_parents`` parameter. + """ # Optimization: Path(Path(...)) is much slower than isinstance. path_ = path if isinstance(path, Path) else Path(path) - return path_ in self._initialpaths + if with_parents: + return path_ in self._initialpaths_with_parents + else: + return path_ in self._initialpaths - def gethookproxy(self, fspath: "os.PathLike[str]"): + def gethookproxy(self, fspath: "os.PathLike[str]") -> pluggy.HookRelay: # Optimization: Path(Path(...)) is much slower than isinstance. path = fspath if isinstance(fspath, Path) else Path(fspath) pm = self.config.pluginmanager # Check if we have the common case of running # hooks with all conftest.py files. - my_conftestmodules = pm._getconftestmodules( - path, - self.config.getoption("importmode"), - rootpath=self.config.rootpath, - ) + my_conftestmodules = pm._getconftestmodules(path) remove_mods = pm._conftest_plugins.difference(my_conftestmodules) + proxy: pluggy.HookRelay if remove_mods: - # One or more conftests are not in use at this fspath. - from .config.compat import PathAwareHookProxy - - proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods)) + # One or more conftests are not in use at this path. + proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods)) # type: ignore[arg-type,assignment] else: # All plugins are active for this fspath. proxy = self.config.hook return proxy - def _recurse(self, direntry: "os.DirEntry[str]") -> bool: - if direntry.name == "__pycache__": - return False - fspath = Path(direntry.path) - ihook = self.gethookproxy(fspath.parent) - if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): - return False - norecursepatterns = self.config.getini("norecursedirs") - if any(fnmatch_ex(pat, fspath) for pat in norecursepatterns): - return False - return True - - def _collectfile( - self, fspath: Path, handle_dupes: bool = True + def _collect_path( + self, + path: Path, + path_cache: Dict[Path, Sequence[nodes.Collector]], ) -> Sequence[nodes.Collector]: - assert ( - fspath.is_file() - ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( - fspath, fspath.is_dir(), fspath.exists(), fspath.is_symlink() - ) - ihook = self.gethookproxy(fspath) - if not self.isinitpath(fspath): - if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): - return () + """Create a Collector for the given path. - if handle_dupes: - keepduplicates = self.config.getoption("keepduplicates") - if not keepduplicates: - duplicate_paths = self.config.pluginmanager._duplicatepaths - if fspath in duplicate_paths: - return () - else: - duplicate_paths.add(fspath) + `path_cache` makes it so the same Collectors are returned for the same + path. + """ + if path in path_cache: + return path_cache[path] + + if path.is_dir(): + ihook = self.gethookproxy(path.parent) + col: Optional[nodes.Collector] = ihook.pytest_collect_directory( + path=path, parent=self + ) + cols: Sequence[nodes.Collector] = (col,) if col is not None else () + + elif path.is_file(): + ihook = self.gethookproxy(path) + cols = ihook.pytest_collect_file(file_path=path, parent=self) + + else: + # Broken symlink or invalid/missing file. + cols = () - return ihook.pytest_collect_file(file_path=fspath, parent=self) # type: ignore[no-any-return] + path_cache[path] = cols + return cols @overload def perform_collect( self, args: Optional[Sequence[str]] = ..., genitems: "Literal[True]" = ... - ) -> Sequence[nodes.Item]: - ... + ) -> Sequence[nodes.Item]: ... @overload def perform_collect( self, args: Optional[Sequence[str]] = ..., genitems: bool = ... - ) -> Sequence[Union[nodes.Item, nodes.Collector]]: - ... + ) -> Sequence[Union[nodes.Item, nodes.Collector]]: ... def perform_collect( self, args: Optional[Sequence[str]] = None, genitems: bool = True @@ -622,33 +765,44 @@ def perform_collect( self.trace("perform_collect", self, args) self.trace.root.indent += 1 - self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = [] - self._initial_parts: List[Tuple[Path, List[str]]] = [] - self.items: List[nodes.Item] = [] - hook = self.config.hook + self._notfound = [] + self._initial_parts = [] + self._collection_cache = {} + self.items = [] items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items try: initialpaths: List[Path] = [] + initialpaths_with_parents: List[Path] = [] for arg in args: - fspath, parts = resolve_collection_argument( + collection_argument = resolve_collection_argument( self.config.invocation_params.dir, arg, as_pypath=self.config.option.pyargs, ) - self._initial_parts.append((fspath, parts)) - initialpaths.append(fspath) + self._initial_parts.append(collection_argument) + initialpaths.append(collection_argument.path) + initialpaths_with_parents.append(collection_argument.path) + initialpaths_with_parents.extend(collection_argument.path.parents) self._initialpaths = frozenset(initialpaths) + self._initialpaths_with_parents = frozenset(initialpaths_with_parents) + 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 = f"(no name {arg!r} in any of {cols!r})" - errors.append(f"not found: {arg}\n{line}") + for arg, collectors in self._notfound: + if collectors: + errors.append( + f"not found: {arg}\n(no match in any of {collectors!r})" + ) + else: + errors.append(f"found no collectors for {arg}") + raise UsageError(*errors) + if not genitems: items = rep.result else: @@ -661,155 +815,147 @@ def perform_collect( session=self, config=self.config, items=items ) finally: + self._notfound = [] + self._initial_parts = [] + self._collection_cache = {} hook.pytest_collection_finish(session=self) - self.testscollected = len(items) - return items + if genitems: + self.testscollected = len(items) - def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: - from _pytest.python import Package + return items - # Keep track of any collected nodes in here, so we don't duplicate fixtures. - node_cache1: Dict[Path, Sequence[nodes.Collector]] = {} - node_cache2: Dict[Tuple[Type[nodes.Collector], Path], nodes.Collector] = {} + def _collect_one_node( + self, + node: nodes.Collector, + handle_dupes: bool = True, + ) -> Tuple[CollectReport, bool]: + if node in self._collection_cache and handle_dupes: + rep = self._collection_cache[node] + return rep, True + else: + rep = collect_one_node(node) + self._collection_cache[node] = rep + return rep, False - # Keep track of any collected collectors in matchnodes paths, so they - # are not collected more than once. - matchnodes_cache: Dict[Tuple[Type[nodes.Collector], str], CollectReport] = {} + def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: + # This is a cache for the root directories of the initial paths. + # We can't use collection_cache for Session because of its special + # role as the bootstrapping collector. + path_cache: Dict[Path, Sequence[nodes.Collector]] = {} - # Dirnames of pkgs with dunder-init files. - pkg_roots: Dict[str, Package] = {} + pm = self.config.pluginmanager - for argpath, names in self._initial_parts: - self.trace("processing argument", (argpath, names)) + for collection_argument in self._initial_parts: + self.trace("processing argument", collection_argument) 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 - confcutdir = pm._confcutdir - for parent in (argpath, *argpath.parents): - if confcutdir and parent in confcutdir.parents: - break + argpath = collection_argument.path + names = collection_argument.parts + module_name = collection_argument.module_name - if parent.is_dir(): - pkginit = parent / "__init__.py" - if pkginit.is_file() 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].path] = [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. + # resolve_collection_argument() ensures this. if argpath.is_dir(): assert not names, f"invalid arg {(argpath, names)!r}" - seen_dirs: Set[Path] = set() - for direntry in visit(str(argpath), self._recurse): - if not direntry.is_file(): - continue - - path = Path(direntry.path) - dirpath = path.parent - - if dirpath not in seen_dirs: - # Collect packages first. - seen_dirs.add(dirpath) - pkginit = dirpath / "__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): - key2 = (type(x), x.path) - if key2 in node_cache2: - yield node_cache2[key2] - else: - node_cache2[key2] = x - yield x + paths = [argpath] + # Add relevant parents of the path, from the root, e.g. + # /a/b/c.py -> [/, /a, /a/b, /a/b/c.py] + if module_name is None: + # Paths outside of the confcutdir should not be considered. + for path in argpath.parents: + if not pm._is_in_confcutdir(path): + break + paths.insert(0, path) else: - assert argpath.is_file() - - if argpath in node_cache1: - col = node_cache1[argpath] - else: - collect_root = pkg_roots.get(str(argpath.parent), self) - col = collect_root._collectfile(argpath, handle_dupes=False) - if col: - node_cache1[argpath] = col - - matching = [] - work: List[ - Tuple[Sequence[Union[nodes.Item, nodes.Collector]], Sequence[str]] - ] = [(col, names)] - 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:])) - 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)) + # For --pyargs arguments, only consider paths matching the module + # name. Paths beyond the package hierarchy are not included. + module_name_parts = module_name.split(".") + for i, path in enumerate(argpath.parents, 2): + if i > len(module_name_parts) or path.stem != module_name_parts[-i]: + break + paths.insert(0, path) + + # Start going over the parts from the root, collecting each level + # and discarding all nodes which don't match the level's part. + any_matched_in_initial_part = False + notfound_collectors = [] + work: List[ + Tuple[Union[nodes.Collector, nodes.Item], List[Union[Path, str]]] + ] = [(self, [*paths, *names])] + while work: + matchnode, matchparts = work.pop() + + # Pop'd all of the parts, this is a match. + if not matchparts: + yield matchnode + any_matched_in_initial_part = True continue - # If __init__.py was the only file requested, then the matched - # node will be the corresponding Package (by default), 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.name == "__init__.py" and isinstance(matching[0], Package): - 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 + # Should have been matched by now, discard. + if not isinstance(matchnode, nodes.Collector): continue - yield from matching + # Collect this level of matching. + # Collecting Session (self) is done directly to avoid endless + # recursion to this function. + subnodes: Sequence[Union[nodes.Collector, nodes.Item]] + if isinstance(matchnode, Session): + assert isinstance(matchparts[0], Path) + subnodes = matchnode._collect_path(matchparts[0], path_cache) + else: + # For backward compat, files given directly multiple + # times on the command line should not be deduplicated. + handle_dupes = not ( + len(matchparts) == 1 + and isinstance(matchparts[0], Path) + and matchparts[0].is_file() + ) + rep, duplicate = self._collect_one_node(matchnode, handle_dupes) + if not duplicate and not rep.passed: + # Report collection failures here to avoid failing to + # run some test specified in the command line because + # the module could not be imported (#134). + matchnode.ihook.pytest_collectreport(report=rep) + if not rep.passed: + continue + subnodes = rep.result + + # Prune this level. + any_matched_in_collector = False + for node in reversed(subnodes): + # Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`. + if isinstance(matchparts[0], Path): + is_match = node.path == matchparts[0] + if sys.platform == "win32" and not is_match: + # In case the file paths do not match, fallback to samefile() to + # account for short-paths on Windows (#11895). + same_file = os.path.samefile(node.path, matchparts[0]) + # We don't want to match links to the current node, + # otherwise we would match the same file more than once (#12039). + is_match = same_file and ( + os.path.islink(node.path) + == os.path.islink(matchparts[0]) + ) + + # Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`. + else: + # TODO: Remove parametrized workaround once collection structure contains + # parametrization. + is_match = ( + node.name == matchparts[0] + or node.name.split("[")[0] == matchparts[0] + ) + if is_match: + work.append((node, matchparts[1:])) + any_matched_in_collector = True + + if not any_matched_in_collector: + notfound_collectors.append(matchnode) + + if not any_matched_in_initial_part: + report_arg = "::".join((str(argpath), *names)) + self._notfound.append((report_arg, notfound_collectors)) self.trace.root.indent -= 1 @@ -822,33 +968,49 @@ def genitems( yield node else: assert isinstance(node, nodes.Collector) - rep = collect_one_node(node) + keepduplicates = self.config.getoption("keepduplicates") + # For backward compat, dedup only applies to files. + handle_dupes = not (keepduplicates and isinstance(node, nodes.File)) + rep, duplicate = self._collect_one_node(node, handle_dupes) + if duplicate and not keepduplicates: + return if rep.passed: for subnode in rep.result: yield from self.genitems(subnode) - node.ihook.pytest_collectreport(report=rep) + if not duplicate: + 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.""" +def search_pypath(module_name: str) -> Optional[str]: + """Search sys.path for the given a dotted module name, and return its file + system path if found.""" 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 + return None if spec is None or spec.origin is None or spec.origin == "namespace": - return module_name + return None elif spec.submodule_search_locations: return os.path.dirname(spec.origin) else: return spec.origin +@dataclasses.dataclass(frozen=True) +class CollectionArgument: + """A resolved collection argument.""" + + path: Path + parts: Sequence[str] + module_name: Optional[str] + + def resolve_collection_argument( invocation_path: Path, arg: str, *, as_pypath: bool = False -) -> Tuple[Path, List[str]]: +) -> CollectionArgument: """Parse path arguments optionally containing selection parts and return (fspath, names). Command-line arguments can point to files and/or directories, and optionally contain @@ -856,9 +1018,13 @@ def resolve_collection_argument( "pkg/tests/test_foo.py::TestClass::test_foo" - This function ensures the path exists, and returns a tuple: + This function ensures the path exists, and returns a resolved `CollectionArgument`: - (Path("/full/path/to/pkg/tests/test_foo.py"), ["TestClass", "test_foo"]) + CollectionArgument( + path=Path("/full/path/to/pkg/tests/test_foo.py"), + parts=["TestClass", "test_foo"], + module_name=None, + ) When as_pypath is True, expects that the command-line argument actually contains module paths instead of file-system paths: @@ -866,7 +1032,13 @@ def resolve_collection_argument( "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. + found module, which may look like this: + + CollectionArgument( + path=Path("/home/u/myvenv/lib/site-packages/pkg/tests/test_foo.py"), + parts=["TestClass", "test_foo"], + module_name="pkg.tests.test_foo", + ) If the path doesn't exist, raise UsageError. If the path is a directory and selection parts are present, raise UsageError. @@ -875,11 +1047,15 @@ def resolve_collection_argument( strpath, *parts = base.split("::") if parts: parts[-1] = f"{parts[-1]}{squacket}{rest}" + module_name = None if as_pypath: - strpath = search_pypath(strpath) + pyarg_strpath = search_pypath(strpath) + if pyarg_strpath is not None: + module_name = strpath + strpath = pyarg_strpath fspath = invocation_path / strpath fspath = absolutepath(fspath) - if not fspath.exists(): + if not safe_exists(fspath): msg = ( "module or package not found: {arg} (missing __init__.py?)" if as_pypath @@ -893,4 +1069,8 @@ def resolve_collection_argument( else "directory argument cannot contain :: selection parts: {arg}" ) raise UsageError(msg.format(arg=arg)) - return fspath, parts + return CollectionArgument( + path=fspath, + parts=parts, + module_name=module_name, + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/__init__.py index 7e082f2e6e08b..01d6e7165f2a2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/__init__.py @@ -1,5 +1,6 @@ """Generic mechanism for marking and selecting python functions.""" -import warnings + +import dataclasses from typing import AbstractSet from typing import Collection from typing import List @@ -7,8 +8,6 @@ from typing import TYPE_CHECKING from typing import Union -import attr - from .expression import Expression from .expression import ParseError from .structures import EMPTY_PARAMETERSET_OPTION @@ -23,10 +22,9 @@ 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.stash import StashKey + if TYPE_CHECKING: from _pytest.nodes import Item @@ -65,8 +63,8 @@ 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 marks: A single mark or a list of marks to be applied to this parameter set. + :param id: The id to attribute to this parameter set. """ return ParameterSet.param(*values, marks=marks, id=id) @@ -79,8 +77,8 @@ def pytest_addoption(parser: Parser) -> None: dest="keyword", default="", metavar="EXPRESSION", - help="only run tests which match the given substring expression. " - "An expression is a python evaluatable expression " + help="Only run tests which match the given substring expression. " + "An expression is a Python evaluable 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 " @@ -99,7 +97,7 @@ def pytest_addoption(parser: Parser) -> None: dest="markexpr", default="", metavar="MARKEXPR", - help="only run tests matching given mark expression.\n" + help="Only run tests matching given mark expression. " "For example: -m 'mark1 and not mark2'.", ) @@ -109,8 +107,8 @@ def pytest_addoption(parser: Parser) -> None: help="show markers (builtin, plugin and per-project ones).", ) - parser.addini("markers", "markers for test functions", "linelist") - parser.addini(EMPTY_PARAMETERSET_OPTION, "default marker for empty parametersets") + parser.addini("markers", "Register new markers for test functions", "linelist") + parser.addini(EMPTY_PARAMETERSET_OPTION, "Default marker for empty parametersets") @hookimpl(tryfirst=True) @@ -133,7 +131,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: return None -@attr.s(slots=True, auto_attribs=True) +@dataclasses.dataclass class KeywordMatcher: """A matcher for keywords. @@ -148,18 +146,27 @@ class KeywordMatcher: any item, as well as names directly assigned to test functions. """ + __slots__ = ("_names",) + _names: AbstractSet[str] @classmethod def from_item(cls, item: "Item") -> "KeywordMatcher": mapped_names = set() - # Add the names of the current item and any parent items. + # Add the names of the current item and any parent items, + # except the Session and root Directory's which are not + # interesting for matching. import pytest for node in item.listchain(): - if not isinstance(node, pytest.Session): - mapped_names.add(node.name) + if isinstance(node, pytest.Session): + continue + if isinstance(node, pytest.Directory) and isinstance( + node.parent, pytest.Session + ): + continue + mapped_names.add(node.name) # Add the names added as extra keywords to current or parent items. mapped_names.update(item.listextrakeywords()) @@ -189,27 +196,14 @@ def deselect_by_keyword(items: "List[Item]", config: Config) -> None: if not keywordexpr: return - if keywordexpr.startswith("-"): - # To be removed in pytest 8.0.0. - warnings.warn(MINUS_K_DASH, stacklevel=2) - keywordexpr = "not " + keywordexpr[1:] - selectuntil = False - if keywordexpr[-1:] == ":": - # To be removed in pytest 8.0.0. - warnings.warn(MINUS_K_COLON, stacklevel=2) - selectuntil = True - keywordexpr = keywordexpr[:-1] - expr = _parse_expression(keywordexpr, "Wrong expression passed to '-k'") remaining = [] deselected = [] for colitem in items: - if keywordexpr and not expr.evaluate(KeywordMatcher.from_item(colitem)): + if not expr.evaluate(KeywordMatcher.from_item(colitem)): deselected.append(colitem) else: - if selectuntil: - keywordexpr = None remaining.append(colitem) if deselected: @@ -217,13 +211,15 @@ def deselect_by_keyword(items: "List[Item]", config: Config) -> None: items[:] = remaining -@attr.s(slots=True, auto_attribs=True) +@dataclasses.dataclass class MarkMatcher: """A matcher for markers which are present. Tries to match on any marker names, attached to the given colitem. """ + __slots__ = ("own_mark_names",) + own_mark_names: AbstractSet[str] @classmethod @@ -273,8 +269,8 @@ def pytest_configure(config: Config) -> None: if empty_parameterset not in ("skip", "xfail", "fail_at_collect", None, ""): raise UsageError( - "{!s} must be one of skip, xfail or fail_at_collect" - " but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset) + f"{EMPTY_PARAMETERSET_OPTION!s} must be one of skip, xfail or fail_at_collect" + f" but it is {empty_parameterset!r}" ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/expression.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/expression.py index 92220d7723aa4..78b7fda696b57 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/expression.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/expression.py @@ -14,21 +14,18 @@ - 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 dataclasses import enum import re import types from typing import Callable from typing import Iterator from typing import Mapping +from typing import NoReturn from typing import Optional from typing import Sequence -from typing import TYPE_CHECKING - -import attr - -if TYPE_CHECKING: - from typing import NoReturn __all__ = [ @@ -47,8 +44,9 @@ class TokenType(enum.Enum): EOF = "end of input" -@attr.s(frozen=True, slots=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class Token: + __slots__ = ("type", "value", "pos") type: TokenType value: str pos: int @@ -117,7 +115,7 @@ def accept(self, type: TokenType, *, reject: bool = False) -> Optional[Token]: self.reject((type,)) return None - def reject(self, expected: Sequence[TokenType]) -> "NoReturn": + def reject(self, expected: Sequence[TokenType]) -> NoReturn: raise ParseError( self.current.pos + 1, "expected {}; got {}".format( @@ -135,7 +133,7 @@ def reject(self, expected: Sequence[TokenType]) -> "NoReturn": def expression(s: Scanner) -> ast.Expression: if s.accept(TokenType.EOF): - ret: ast.expr = ast.NameConstant(False) + ret: ast.expr = ast.Constant(False) else: ret = expr(s) s.accept(TokenType.EOF, reject=True) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/structures.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/structures.py index 0e42cd8de5f58..a6503bf1d4689 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/structures.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/structures.py @@ -1,9 +1,11 @@ +# mypy: allow-untyped-defs import collections.abc +import dataclasses import inspect -import warnings from typing import Any from typing import Callable from typing import Collection +from typing import final from typing import Iterable from typing import Iterator from typing import List @@ -19,19 +21,19 @@ from typing import TYPE_CHECKING from typing import TypeVar from typing import Union - -import attr +import warnings from .._code import getfslineno from ..compat import ascii_escaped -from ..compat import final from ..compat import NOTSET from ..compat import NotSetType from _pytest.config import Config from _pytest.deprecated import check_ispytest +from _pytest.deprecated import MARKED_FIXTURE from _pytest.outcomes import fail from _pytest.warning_types import PytestUnknownMarkWarning + if TYPE_CHECKING: from ..nodes import Node @@ -72,16 +74,11 @@ def get_empty_parameterset_mark( return mark -class ParameterSet( - NamedTuple( - "ParameterSet", - [ - ("values", Sequence[Union[object, NotSetType]]), - ("marks", Collection[Union["MarkDecorator", "Mark"]]), - ("id", Optional[str]), - ], - ) -): +class ParameterSet(NamedTuple): + values: Sequence[Union[object, NotSetType]] + marks: Collection[Union["MarkDecorator", "Mark"]] + id: Optional[str] + @classmethod def param( cls, @@ -116,7 +113,6 @@ def extract_from( Enforce tuple wrapping so single argument tuple values don't get decomposed and break tests. """ - if isinstance(parameterset, cls): return parameterset if force_tuple: @@ -131,12 +127,12 @@ def extract_from( @staticmethod def _parse_parametrize_args( - argnames: Union[str, List[str], Tuple[str, ...]], + argnames: Union[str, Sequence[str]], argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], *args, **kwargs, - ) -> Tuple[Union[List[str], Tuple[str, ...]], bool]: - if not isinstance(argnames, (tuple, list)): + ) -> Tuple[Sequence[str], bool]: + if isinstance(argnames, str): argnames = [x.strip() for x in argnames.split(",") if x.strip()] force_tuple = len(argnames) == 1 else: @@ -155,12 +151,12 @@ def _parse_parametrize_parameters( @classmethod def _for_parametrize( cls, - argnames: Union[str, List[str], Tuple[str, ...]], + argnames: Union[str, Sequence[str]], argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], func, config: Config, nodeid: str, - ) -> Tuple[Union[List[str], Tuple[str, ...]], List["ParameterSet"]]: + ) -> Tuple[Sequence[str], List["ParameterSet"]]: argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues) parameters = cls._parse_parametrize_parameters(argvalues, force_tuple) del argvalues @@ -196,8 +192,10 @@ def _for_parametrize( @final -@attr.s(frozen=True, init=False, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class Mark: + """A pytest mark.""" + #: Name of the mark. name: str #: Positional arguments of the mark decorator. @@ -206,9 +204,11 @@ class Mark: kwargs: Mapping[str, Any] #: Source Mark for ids with parametrize Marks. - _param_ids_from: Optional["Mark"] = attr.ib(default=None, repr=False) + _param_ids_from: Optional["Mark"] = dataclasses.field(default=None, repr=False) #: Resolved/generated ids with parametrize Marks. - _param_ids_generated: Optional[Sequence[str]] = attr.ib(default=None, repr=False) + _param_ids_generated: Optional[Sequence[str]] = dataclasses.field( + default=None, repr=False + ) def __init__( self, @@ -266,14 +266,14 @@ def combined_with(self, other: "Mark") -> "Mark": Markable = TypeVar("Markable", bound=Union[Callable[..., object], type]) -@attr.s(init=False, auto_attribs=True) +@dataclasses.dataclass 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:: @@ -342,7 +342,7 @@ def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator": # 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] + def __call__(self, arg: Markable) -> Markable: # type: ignore[overload-overlap] pass @overload @@ -355,21 +355,46 @@ def __call__(self, *args: object, **kwargs: object): func = args[0] is_class = inspect.isclass(func) if len(args) == 1 and (istestfunc(func) or is_class): - store_mark(func, self.mark) + store_mark(func, self.mark, stacklevel=3) return func return self.with_args(*args, **kwargs) -def get_unpacked_marks(obj: object) -> Iterable[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 get_unpacked_marks( + obj: Union[object, type], + *, + consider_mro: bool = True, +) -> List[Mark]: + """Obtain the unpacked marks that are stored on an object. + + If obj is a class and consider_mro is true, return marks applied to + this class and all of its super-classes in MRO order. If consider_mro + is false, only return marks applied directly to this class. + """ + if isinstance(obj, type): + if not consider_mro: + mark_lists = [obj.__dict__.get("pytestmark", [])] + else: + mark_lists = [ + x.__dict__.get("pytestmark", []) for x in reversed(obj.__mro__) + ] + mark_list = [] + for item in mark_lists: + if isinstance(item, list): + mark_list.extend(item) + else: + mark_list.append(item) + else: + mark_attribute = getattr(obj, "pytestmark", []) + if isinstance(mark_attribute, list): + mark_list = mark_attribute + else: + mark_list = [mark_attribute] + return list(normalize_mark_list(mark_list)) def normalize_mark_list( - mark_list: Iterable[Union[Mark, MarkDecorator]] + mark_list: Iterable[Union[Mark, MarkDecorator]], ) -> Iterable[Mark]: """ Normalize an iterable of Mark or MarkDecorator objects into a list of marks @@ -381,19 +406,25 @@ def normalize_mark_list( for mark in mark_list: mark_obj = getattr(mark, "mark", mark) if not isinstance(mark_obj, Mark): - raise TypeError(f"got {repr(mark_obj)} instead of Mark") + raise TypeError(f"got {mark_obj!r} instead of Mark") yield mark_obj -def store_mark(obj, mark: Mark) -> None: +def store_mark(obj, mark: Mark, *, stacklevel: int = 2) -> None: """Store a Mark on an object. This is used to implement the Mark declarations/decorators correctly. """ assert isinstance(mark, Mark), mark + + from ..fixtures import getfixturemarker + + if getfixturemarker(obj) is not None: + warnings.warn(MARKED_FIXTURE, stacklevel=stacklevel) + # Always reassign name to avoid updating pytestmark in a reference that # was only borrowed. - obj.pytestmark = [*get_unpacked_marks(obj), mark] + obj.pytestmark = [*get_unpacked_marks(obj, consider_mro=False), mark] # Typing for builtin pytest marks. This is cheating; it gives builtin marks @@ -402,13 +433,11 @@ def store_mark(obj, mark: Mark) -> None: from _pytest.scope import _ScopeName class _SkipMarkDecorator(MarkDecorator): - @overload # type: ignore[override,misc] - def __call__(self, arg: Markable) -> Markable: - ... + @overload # type: ignore[override,no-overload-impl] + def __call__(self, arg: Markable) -> Markable: ... @overload - def __call__(self, reason: str = ...) -> "MarkDecorator": - ... + def __call__(self, reason: str = ...) -> "MarkDecorator": ... class _SkipifMarkDecorator(MarkDecorator): def __call__( # type: ignore[override] @@ -416,30 +445,29 @@ def __call__( # type: ignore[override] condition: Union[str, bool] = ..., *conditions: Union[str, bool], reason: str = ..., - ) -> MarkDecorator: - ... + ) -> MarkDecorator: ... class _XfailMarkDecorator(MarkDecorator): - @overload # type: ignore[override,misc] - def __call__(self, arg: Markable) -> Markable: - ... + @overload # type: ignore[override,no-overload-impl] + def __call__(self, arg: Markable) -> Markable: ... @overload def __call__( self, - condition: Union[str, bool] = ..., + condition: Union[str, bool] = False, *conditions: Union[str, bool], reason: str = ..., run: bool = ..., - raises: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = ..., + raises: Union[ + None, Type[BaseException], Tuple[Type[BaseException], ...] + ] = ..., strict: bool = ..., - ) -> MarkDecorator: - ... + ) -> MarkDecorator: ... class _ParametrizeMarkDecorator(MarkDecorator): def __call__( # type: ignore[override] self, - argnames: Union[str, List[str], Tuple[str, ...]], + argnames: Union[str, Sequence[str]], argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], *, indirect: Union[bool, Sequence[str]] = ..., @@ -450,8 +478,7 @@ def __call__( # type: ignore[override] ] ] = ..., scope: Optional[_ScopeName] = ..., - ) -> MarkDecorator: - ... + ) -> MarkDecorator: ... class _UsefixturesMarkDecorator(MarkDecorator): def __call__(self, *fixtures: str) -> MarkDecorator: # type: ignore[override] @@ -471,9 +498,10 @@ class MarkGenerator: import pytest + @pytest.mark.slowtest def test_function(): - pass + pass applies a 'slowtest' :class:`Mark` on ``test_function``. """ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/w3c-import.log new file mode 100644 index 0000000000000..e646c9b5b9408 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/expression.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/structures.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/monkeypatch.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/monkeypatch.py index 31f95a95ab226..3f398df76b180 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/monkeypatch.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/monkeypatch.py @@ -1,23 +1,27 @@ +# mypy: allow-untyped-defs """Monkeypatching and mocking functionality.""" + +from contextlib import contextmanager import os import re import sys -import warnings -from contextlib import contextmanager from typing import Any +from typing import final from typing import Generator from typing import List +from typing import Mapping from typing import MutableMapping from typing import Optional from typing import overload from typing import Tuple from typing import TypeVar from typing import Union +import warnings -from _pytest.compat import final from _pytest.fixtures import fixture from _pytest.warning_types import PytestWarning + RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$") @@ -29,21 +33,26 @@ def monkeypatch() -> Generator["MonkeyPatch", None, None]: """A convenient fixture for monkey-patching. - The fixture provides these methods to modify objects, dictionaries or - os.environ:: + The fixture provides these methods to modify objects, dictionaries, or + :data:`os.environ`: - 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=None) - monkeypatch.delenv(name, raising=True) - monkeypatch.syspath_prepend(path) - monkeypatch.chdir(path) + * :meth:`monkeypatch.setattr(obj, name, value, raising=True) ` + * :meth:`monkeypatch.delattr(obj, name, raising=True) ` + * :meth:`monkeypatch.setitem(mapping, name, value) ` + * :meth:`monkeypatch.delitem(obj, name, raising=True) ` + * :meth:`monkeypatch.setenv(name, value, prepend=None) ` + * :meth:`monkeypatch.delenv(name, raising=True) ` + * :meth:`monkeypatch.syspath_prepend(path) ` + * :meth:`monkeypatch.chdir(path) ` + * :meth:`monkeypatch.context() ` 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. + fixture has finished. The ``raising`` parameter determines if a :class:`KeyError` + or :class:`AttributeError` will be raised if the set/deletion operation does not have the + specified target. + + To undo modifications done by the fixture in a contained scope, + use :meth:`context() `. """ mpatch = MonkeyPatch() yield mpatch @@ -55,7 +64,7 @@ def resolve(name: str) -> object: parts = name.split(".") used = parts.pop(0) - found = __import__(used) + found: object = __import__(used) for part in parts: used += "." + part try: @@ -83,9 +92,7 @@ def annotated_getattr(obj: object, name: str, ann: str) -> object: obj = getattr(obj, name) except AttributeError as e: raise AttributeError( - "{!r} object at {} has no attribute {!r}".format( - type(obj).__name__, ann, name - ) + f"{type(obj).__name__!r} object at {ann} has no attribute {name!r}" ) from e return obj @@ -115,7 +122,7 @@ class MonkeyPatch: Returned by the :fixture:`monkeypatch` fixture. - :versionchanged:: 6.2 + .. versionchanged:: 6.2 Can now also be used directly as `pytest.MonkeyPatch()`, for when the fixture is not available. In this case, use :meth:`with MonkeyPatch.context() as mp: ` or remember to call @@ -124,7 +131,7 @@ class MonkeyPatch: def __init__(self) -> None: self._setattr: List[Tuple[object, str, object]] = [] - self._setitem: List[Tuple[MutableMapping[Any, Any], object, object]] = [] + self._setitem: List[Tuple[Mapping[Any, Any], object, object]] = [] self._cwd: Optional[str] = None self._savesyspath: Optional[List[str]] = None @@ -135,7 +142,6 @@ def context(cls) -> Generator["MonkeyPatch", None, None]: which undoes any patching done inside the ``with`` block upon exit. Example: - .. code-block:: python import functools @@ -162,8 +168,7 @@ def setattr( name: object, value: Notset = ..., raising: bool = ..., - ) -> None: - ... + ) -> None: ... @overload def setattr( @@ -172,8 +177,7 @@ def setattr( name: str, value: object, raising: bool = ..., - ) -> None: - ... + ) -> None: ... def setattr( self, @@ -182,16 +186,40 @@ def setattr( value: object = notset, raising: bool = True, ) -> None: - """Set attribute value on target, memorizing the old value. + """ + Set attribute value on target, memorizing the old value. + + For example: - For convenience you can specify a string as ``target`` which + .. code-block:: python + + import os + + monkeypatch.setattr(os, "getcwd", lambda: "/") + + The code above replaces the :func:`os.getcwd` function by a ``lambda`` which + always returns ``"/"``. + + 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. For example, - ``monkeypatch.setattr("os.getcwd", lambda: "/")`` - would set the ``getcwd`` function of the ``os`` module. + being the attribute name: + + .. code-block:: python - Raises AttributeError if the attribute does not exist, unless + monkeypatch.setattr("os.getcwd", lambda: "/") + + Raises :class:`AttributeError` if the attribute does not exist, unless ``raising`` is set to False. + + **Where to patch** + + ``monkeypatch.setattr`` works by (temporarily) changing the object that a name points to with another one. + There can be many names pointing to any individual object, so for patching to work you must ensure + that you patch the name used by the system under test. + + See the section :ref:`Where to patch ` in the :mod:`unittest.mock` + docs for a complete explanation, which is meant for :func:`unittest.mock.patch` but + applies to ``monkeypatch.setattr`` as well. """ __tracebackhide__ = True import inspect @@ -261,12 +289,13 @@ def delattr( self._setattr.append((target, name, oldval)) delattr(target, name) - def setitem(self, dic: MutableMapping[K, V], name: K, value: V) -> None: + def setitem(self, dic: Mapping[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 + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + dic[name] = value # type: ignore[index] - def delitem(self, dic: MutableMapping[K, V], name: K, raising: bool = True) -> None: + def delitem(self, dic: Mapping[K, V], name: K, raising: bool = True) -> None: """Delete ``name`` from dict. Raises ``KeyError`` if it doesn't exist, unless ``raising`` is set to @@ -277,7 +306,8 @@ def delitem(self, dic: MutableMapping[K, V], name: K, raising: bool = True) -> N raise KeyError(name) else: self._setitem.append((dic, name, dic.get(name, notset))) - del dic[name] + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + del dic[name] # type: ignore[attr-defined] def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None: """Set environment variable ``name`` to ``value``. @@ -289,10 +319,8 @@ def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None: if not isinstance(value, str): warnings.warn( # type: ignore[unreachable] PytestWarning( - "Value of environment variable {name} type should be str, but got " - "{value!r} (type: {type}); converted to str implicitly".format( - name=name, value=value, type=type(value).__name__ - ) + f"Value of environment variable {name} type should be str, but got " + f"{value!r} (type: {type(value).__name__}); converted to str implicitly" ), stacklevel=2, ) @@ -312,7 +340,6 @@ def delenv(self, name: str, raising: bool = True) -> None: def syspath_prepend(self, path) -> None: """Prepend ``path`` to ``sys.path`` list of import locations.""" - if self._savesyspath is None: self._savesyspath = sys.path[:] sys.path.insert(0, str(path)) @@ -338,7 +365,8 @@ def syspath_prepend(self, path) -> None: def chdir(self, path: Union[str, "os.PathLike[str]"]) -> None: """Change the current working directory to the specified path. - Path can be a string or a path object. + :param path: + The path to change into. """ if self._cwd is None: self._cwd = os.getcwd() @@ -353,11 +381,14 @@ def undo(self) -> None: There is generally no need to call `undo()`, since it is called automatically during tear-down. - Note that the same `monkeypatch` fixture is used across a - single test function invocation. If `monkeypatch` is used both by - the test function itself and one of the test fixtures, - calling `undo()` will undo all of the changes made in - both functions. + .. note:: + The same `monkeypatch` fixture is used across a + single test function invocation. If `monkeypatch` is used both by + the test function itself and one of the test fixtures, + calling `undo()` will undo all of the changes made in + both functions. + + Prefer to use :meth:`context() ` instead. """ for obj, name, value in reversed(self._setattr): if value is not notset: @@ -368,11 +399,13 @@ def undo(self) -> None: for dictionary, key, value in reversed(self._setitem): if value is notset: try: - del dictionary[key] + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + del dictionary[key] # type: ignore[attr-defined] except KeyError: pass # Was already deleted, so we have the desired state. else: - dictionary[key] = value + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + dictionary[key] = value # type: ignore[index] self._setitem[:] = [] if self._savesyspath is not None: sys.path[:] = self._savesyspath diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/nodes.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/nodes.py index e49c1b003e034..974d756a2bee8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/nodes.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/nodes.py @@ -1,6 +1,9 @@ -import os -import warnings +# mypy: allow-untyped-defs +import abc +from functools import cached_property from inspect import signature +import os +import pathlib from pathlib import Path from typing import Any from typing import Callable @@ -9,6 +12,7 @@ from typing import Iterator from typing import List from typing import MutableMapping +from typing import NoReturn from typing import Optional from typing import overload from typing import Set @@ -17,16 +21,19 @@ from typing import TYPE_CHECKING from typing import TypeVar from typing import Union +import warnings + +import pluggy import _pytest._code 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._code.code import Traceback from _pytest.compat import LEGACY_PATH from _pytest.config import Config from _pytest.config import ConftestImportFailure -from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH +from _pytest.config.compat import _check_path from _pytest.deprecated import NODE_CTOR_FSPATH_ARG from _pytest.mark.structures import Mark from _pytest.mark.structures import MarkDecorator @@ -37,10 +44,13 @@ from _pytest.stash import Stash from _pytest.warning_types import PytestWarning + if TYPE_CHECKING: + from typing import Self + # Imported here due to circular import. - from _pytest.main import Session from _pytest._code.code import _TracebackStyle + from _pytest.main import Session SEP = "/" @@ -48,57 +58,7 @@ tracebackcutdir = Path(_pytest.__file__).parent -def iterparentnodeids(nodeid: str) -> Iterator[str]: - """Return the parent node IDs of a given node ID, inclusive. - - For the node ID - - "testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source" - - the result would be - - "" - "testing" - "testing/code" - "testing/code/test_excinfo.py" - "testing/code/test_excinfo.py::TestFormattedExcinfo" - "testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source" - - Note that / components are only considered until the first ::. - """ - pos = 0 - first_colons: Optional[int] = nodeid.find("::") - if first_colons == -1: - first_colons = None - # The root Session node - always present. - yield "" - # Eagerly consume SEP parts until first colons. - while True: - at = nodeid.find(SEP, pos, first_colons) - if at == -1: - break - if at > 0: - yield nodeid[:at] - pos = at + len(SEP) - # Eagerly consume :: parts. - while True: - at = nodeid.find("::", pos) - if at == -1: - break - if at > 0: - yield nodeid[:at] - pos = at + len("::") - # The node ID itself. - if nodeid: - yield nodeid - - -def _check_path(path: Path, fspath: LEGACY_PATH) -> None: - if Path(fspath) != path: - raise ValueError( - f"Path({fspath!r}) != {path!r}\n" - "if both path and fspath are given they need to be equal" - ) +_T = TypeVar("_T") def _imply_path( @@ -111,7 +71,7 @@ def _imply_path( NODE_CTOR_FSPATH_ARG.format( node_type_name=node_type.__name__, ), - stacklevel=3, + stacklevel=6, ) if path is not None: if fspath is not None: @@ -125,48 +85,63 @@ def _imply_path( _NodeType = TypeVar("_NodeType", bound="Node") -class NodeMeta(type): - def __call__(self, *k, **kw): +class NodeMeta(abc.ABCMeta): + """Metaclass used by :class:`Node` to enforce that direct construction raises + :class:`Failed`. + + This behaviour supports the indirection introduced with :meth:`Node.from_parent`, + the named constructor to be used instead of direct construction. The design + decision to enforce indirection with :class:`NodeMeta` was made as a + temporary aid for refactoring the collection tree, which was diagnosed to + have :class:`Node` objects whose creational patterns were overly entangled. + Once the refactoring is complete, this metaclass can be removed. + + See https://github.com/pytest-dev/pytest/projects/3 for an overview of the + progress on detangling the :class:`Node` classes. + """ + + def __call__(cls, *k, **kw) -> NoReturn: 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=f"{self.__module__}.{self.__name__}") + ).format(name=f"{cls.__module__}.{cls.__name__}") fail(msg, pytrace=False) - def _create(self, *k, **kw): + def _create(cls: Type[_T], *k, **kw) -> _T: try: - return super().__call__(*k, **kw) + return super().__call__(*k, **kw) # type: ignore[no-any-return,misc] except TypeError: - sig = signature(getattr(self, "__init__")) + sig = signature(getattr(cls, "__init__")) known_kw = {k: v for k, v in kw.items() if k in sig.parameters} from .warning_types import PytestDeprecationWarning warnings.warn( PytestDeprecationWarning( - f"{self} is not using a cooperative constructor and only takes {set(known_kw)}.\n" + f"{cls} is not using a cooperative constructor and only takes {set(known_kw)}.\n" "See https://docs.pytest.org/en/stable/deprecations.html" "#constructors-of-custom-pytest-node-subclasses-should-take-kwargs " "for more details." ) ) - return super().__call__(*k, **known_kw) + return super().__call__(*k, **known_kw) # type: ignore[no-any-return,misc] -class Node(metaclass=NodeMeta): - """Base class for Collector and Item, the components of the test - collection tree. +class Node(abc.ABC, metaclass=NodeMeta): + r"""Base class of :class:`Collector` and :class:`Item`, the components of + the test collection tree. - Collector subclasses have children; Items are leaf nodes. + ``Collector``\'s are the internal nodes of the tree, and ``Item``\'s are the + leaf nodes. """ # Implemented in the legacypath plugin. #: A ``LEGACY_PATH`` copy of the :attr:`path` attribute. Intended for usage #: for methods not migrated to ``pathlib.Path`` yet, such as - #: :meth:`Item.reportinfo`. Will be deprecated in a future release, prefer - #: using :attr:`path` instead. + #: :meth:`Item.reportinfo `. Will be deprecated in + #: a future release, prefer using :attr:`path` instead. fspath: LEGACY_PATH # Use __slots__ to make attribute access faster. @@ -193,7 +168,7 @@ def __init__( nodeid: Optional[str] = None, ) -> None: #: A unique name within the scope of the parent node. - self.name = name + self.name: str = name #: The parent collector node. self.parent = parent @@ -208,7 +183,7 @@ def __init__( if session: #: The pytest session this node is part of. - self.session = session + self.session: Session = session else: if not parent: raise TypeError("session or parent must be provided") @@ -217,7 +192,7 @@ def __init__( if path is None and fspath is None: path = getattr(parent, "path", None) #: Filesystem path where this node was collected from (can be None). - self.path: Path = _imply_path(type(self), path, fspath=fspath) + self.path: pathlib.Path = _imply_path(type(self), path, fspath=fspath) # The explicit annotation is to avoid publicly exposing NodeKeywords. #: Keywords/markers collected from all scopes. @@ -239,14 +214,12 @@ def __init__( #: A place where plugins can store information on the node for their #: own use. - #: - #: :type: Stash - self.stash = Stash() + self.stash: Stash = Stash() # Deprecated alias. Was never public. Can be removed in a few releases. self._store = self.stash @classmethod - def from_parent(cls, parent: "Node", **kw): + def from_parent(cls, parent: "Node", **kw) -> "Self": """Public constructor for Nodes. This indirection got introduced in order to enable removing @@ -264,7 +237,7 @@ def from_parent(cls, parent: "Node", **kw): return cls._create(parent=parent, **kw) @property - def ihook(self): + def ihook(self) -> pluggy.HookRelay: """fspath-sensitive hook proxy used to call pytest hooks.""" return self.session.gethookproxy(self.path) @@ -295,9 +268,7 @@ def warn(self, warning: Warning) -> None: # enforce type checks here to avoid getting a generic type error later otherwise. if not isinstance(warning, Warning): raise ValueError( - "warning must be an instance of Warning or subclass, got {!r}".format( - warning - ) + f"warning must be an instance of Warning or subclass, got {warning!r}" ) path, lineno = get_fslocation_from_item(self) assert lineno is not None @@ -324,9 +295,20 @@ def setup(self) -> None: def teardown(self) -> None: pass + def iter_parents(self) -> Iterator["Node"]: + """Iterate over all parent collectors starting from and including self + up to the root of the collection tree. + + .. versionadded:: 8.1 + """ + parent: Optional[Node] = self + while parent is not None: + yield parent + parent = parent.parent + def listchain(self) -> List["Node"]: - """Return list of all parent collectors up to self, starting from - the root of collection tree.""" + """Return a list of all parent collectors starting from the root of the + collection tree down to and including self.""" chain = [] item: Optional[Node] = self while item is not None: @@ -340,6 +322,8 @@ def add_marker( ) -> None: """Dynamically add a marker object to the node. + :param marker: + The marker. :param append: Whether to append the marker, or prepend it. """ @@ -361,6 +345,7 @@ def iter_markers(self, name: Optional[str] = None) -> Iterator[Mark]: """Iterate over all markers of the node. :param name: If given, filter the results by the name attribute. + :returns: An iterator of the markers of the node. """ return (x[1] for x in self.iter_markers_with_node(name=name)) @@ -372,18 +357,16 @@ def iter_markers_with_node( :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 node in self.iter_parents(): for mark in node.own_markers: if name is None or getattr(mark, "name", None) == name: yield node, mark @overload - def get_closest_marker(self, name: str) -> Optional[Mark]: - ... + def get_closest_marker(self, name: str) -> Optional[Mark]: ... @overload - def get_closest_marker(self, name: str, default: Mark) -> Mark: - ... + def get_closest_marker(self, name: str, default: Mark) -> Mark: ... def get_closest_marker( self, name: str, default: Optional[Mark] = None @@ -407,7 +390,8 @@ def listnames(self) -> List[str]: return [x.name for x in self.listchain()] def addfinalizer(self, fin: Callable[[], object]) -> None: - """Register a function to be called when this node is finalized. + """Register a function to be called without arguments 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(). @@ -415,16 +399,19 @@ def addfinalizer(self, fin: Callable[[], object]) -> None: self.session._setupstate.addfinalizer(fin, 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: Optional[Node] = self - while current and not isinstance(current, cls): - current = current.parent - assert current is None or isinstance(current, cls) - return current - - def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: - pass + """Get the closest parent node (including self) which is an instance of + the given class. + + :param cls: The node class to search for. + :returns: The node, if found. + """ + for node in self.iter_parents(): + if isinstance(node, cls): + return node + return None + + def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: + return excinfo.traceback def _repr_failure_py( self, @@ -434,19 +421,19 @@ def _repr_failure_py( from _pytest.fixtures import FixtureLookupError if isinstance(excinfo.value, ConftestImportFailure): - excinfo = ExceptionInfo.from_exc_info(excinfo.value.excinfo) + excinfo = ExceptionInfo.from_exception(excinfo.value.cause) if isinstance(excinfo.value, fail.Exception): if not excinfo.value.pytrace: style = "value" if isinstance(excinfo.value, FixtureLookupError): return excinfo.value.formatrepr() + + tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] if self.config.getoption("fulltrace", False): style = "long" + tbfilter = False else: - tb = _pytest._code.Traceback([excinfo.traceback[-1]]) - self._prunetraceback(excinfo) - if len(excinfo.traceback) == 0: - excinfo.traceback = tb + tbfilter = self._traceback_filter if style == "auto": style = "long" # XXX should excinfo.getrepr record all data and toterminal() process it? @@ -477,7 +464,7 @@ def _repr_failure_py( abspath=abspath, showlocals=self.config.getoption("showlocals", False), style=style, - tbfilter=False, # pruned already, or in --fulltrace mode. + tbfilter=tbfilter, truncate_locals=truncate_locals, ) @@ -500,9 +487,9 @@ def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[i * "location": a pair (path, lineno) * "obj": a Python object that the node wraps. - * "fspath": just a path + * "path": just a path - :rtype: A tuple of (str|Path, int) with filename and line number. + :rtype: A tuple of (str|Path, int) with filename and 0-based line number. """ # See Item.location. location: Optional[Tuple[str, Optional[int], str]] = getattr(node, "location", None) @@ -511,19 +498,22 @@ def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[i obj = getattr(node, "obj", None) if obj is not None: return getfslineno(obj) - return getattr(node, "fspath", "unknown location"), -1 + return getattr(node, "path", "unknown location"), -1 + +class Collector(Node, abc.ABC): + """Base class of all collectors. -class Collector(Node): - """Collector instances create children through collect() and thus - iteratively build a tree.""" + Collector create children through `collect()` and thus iteratively build + the collection tree. + """ class CollectError(Exception): """An error during collection, contains a custom message.""" + @abc.abstractmethod def collect(self) -> Iterable[Union["Item", "Collector"]]: - """Return a list of children (items and collectors) for this - collection node.""" + """Collect children (items and collectors) for this collector.""" raise NotImplementedError("abstract") # TODO: This omits the style= parameter which breaks Liskov Substitution. @@ -548,13 +538,14 @@ def repr_failure( # type: ignore[override] return self._repr_failure_py(excinfo, style=tbstyle) - def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: + def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: if hasattr(self, "path"): traceback = excinfo.traceback ntraceback = traceback.cut(path=self.path) if ntraceback == traceback: ntraceback = ntraceback.cut(excludepath=tracebackcutdir) - excinfo.traceback = ntraceback.filter() + return ntraceback.filter(excinfo) + return excinfo.traceback def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[str]: @@ -565,7 +556,9 @@ def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[ return None -class FSCollector(Collector): +class FSCollector(Collector, abc.ABC): + """Base class for filesystem collectors.""" + def __init__( self, fspath: Optional[LEGACY_PATH] = None, @@ -628,28 +621,38 @@ def from_parent( fspath: Optional[LEGACY_PATH] = None, path: Optional[Path] = None, **kw, - ): + ) -> "Self": """The public constructor.""" return super().from_parent(parent=parent, fspath=fspath, path=path, **kw) - def gethookproxy(self, fspath: "os.PathLike[str]"): - warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) - return self.session.gethookproxy(fspath) - - def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool: - warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) - return self.session.isinitpath(path) - -class File(FSCollector): +class File(FSCollector, abc.ABC): """Base class for collecting tests from a file. :ref:`non-python tests`. """ -class Item(Node): - """A basic test invocation item. +class Directory(FSCollector, abc.ABC): + """Base class for collecting files from a directory. + + A basic directory collector does the following: goes over the files and + sub-directories in the directory and creates collectors for them by calling + the hooks :hook:`pytest_collect_directory` and :hook:`pytest_collect_file`, + after checking that they are not ignored using + :hook:`pytest_ignore_collect`. + + The default directory collectors are :class:`~pytest.Dir` and + :class:`~pytest.Package`. + + .. versionadded:: 8.0 + + :ref:`custom directory collectors`. + """ + + +class Item(Node, abc.ABC): + """Base class of all test invocation items. Note that for a single function there might be multiple test invocation items. """ @@ -714,6 +717,7 @@ def _check_item_and_collector_diamond_inheritance(self) -> None: PytestWarning, ) + @abc.abstractmethod def runtest(self) -> None: """Run the test case for this item. @@ -746,7 +750,7 @@ def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str Returns a tuple with three elements: - The path of the test (default ``self.path``) - - The line number of the test (default ``None``) + - The 0-based line number of the test (default ``None``) - A name of the test to be shown (default ``""``) .. seealso:: :ref:`non-python tests` @@ -755,8 +759,13 @@ def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str @cached_property def location(self) -> Tuple[str, Optional[int], str]: + """ + Returns a tuple of ``(relfspath, lineno, testname)`` for this item + where ``relfspath`` is file path relative to ``config.rootpath`` + and lineno is a 0-based line number. + """ location = self.reportinfo() - path = absolutepath(os.fspath(location[0])) + path = absolutepath(location[0]) relfspath = self.session._node_location_to_relpath(path) assert type(location[2]) is str return (relfspath, location[1], location[2]) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/outcomes.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/outcomes.py index 25206fe0e85f1..f953dabe03d2f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/outcomes.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/outcomes.py @@ -1,28 +1,17 @@ """Exception classes and constants handling test outcomes as well as functions creating them.""" + import sys -import warnings from typing import Any from typing import Callable from typing import cast +from typing import NoReturn from typing import Optional +from typing import Protocol from typing import Type from typing import TypeVar -from _pytest.deprecated import KEYWORD_MSG_ARG - -TYPE_CHECKING = False # Avoid circular import through compat. - -if TYPE_CHECKING: - from typing import NoReturn - 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 +from .warning_types import PytestDeprecationWarning class OutcomeException(BaseException): @@ -114,8 +103,9 @@ def decorate(func: _F) -> _WithException[_F, _ET]: @_with_exception(Exit) def exit( - reason: str = "", returncode: Optional[int] = None, *, msg: Optional[str] = None -) -> "NoReturn": + reason: str = "", + returncode: Optional[int] = None, +) -> NoReturn: """Exit testing process. :param reason: @@ -123,30 +113,21 @@ def exit( only because `msg` is deprecated. :param returncode: - Return code to be used when exiting pytest. + Return code to be used when exiting pytest. None means the same as ``0`` (no error), same as :func:`sys.exit`. - :param msg: - Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. + :raises pytest.exit.Exception: + The exception that is raised. """ __tracebackhide__ = True - from _pytest.config import UsageError - - if reason and msg: - raise UsageError( - "cannot pass reason and msg to exit(), `msg` is deprecated, use `reason`." - ) - if not reason: - if msg is None: - raise UsageError("exit() requires a reason argument") - warnings.warn(KEYWORD_MSG_ARG.format(func="exit"), stacklevel=2) - reason = msg raise Exit(reason, returncode) @_with_exception(Skipped) def skip( - reason: str = "", *, allow_module_level: bool = False, msg: Optional[str] = None -) -> "NoReturn": + reason: 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 @@ -157,11 +138,15 @@ def skip( The message to show the user as reason for the skip. :param allow_module_level: - Allows this function to be called at module level, skipping the rest - of the module. Defaults to False. + Allows this function to be called at module level. + Raising the skip exception at module level will stop + the execution of the module and prevent the collection of all tests in the module, + even those defined before the `skip` call. + + Defaults to False. - :param msg: - Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. + :raises pytest.skip.Exception: + The exception that is raised. .. note:: It is better to use the :ref:`pytest.mark.skipif ref` marker when @@ -171,14 +156,11 @@ def skip( to skip a doctest statically. """ __tracebackhide__ = True - reason = _resolve_msg_to_reason("skip", reason, msg) raise Skipped(msg=reason, allow_module_level=allow_module_level) @_with_exception(Failed) -def fail( - reason: str = "", pytrace: bool = True, msg: Optional[str] = None -) -> "NoReturn": +def fail(reason: str = "", pytrace: bool = True) -> NoReturn: """Explicitly fail an executing test with the given message. :param reason: @@ -188,108 +170,137 @@ def fail( If False, msg represents the full failure information and no python traceback will be reported. - :param msg: - Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. + :raises pytest.fail.Exception: + The exception that is raised. """ __tracebackhide__ = True - reason = _resolve_msg_to_reason("fail", reason, msg) raise Failed(msg=reason, pytrace=pytrace) -def _resolve_msg_to_reason( - func_name: str, reason: str, msg: Optional[str] = None -) -> str: - """ - Handles converting the deprecated msg parameter if provided into - reason, raising a deprecation warning. This function will be removed - when the optional msg argument is removed from here in future. - - :param str func_name: - The name of the offending function, this is formatted into the deprecation message. - - :param str reason: - The reason= passed into either pytest.fail() or pytest.skip() - - :param str msg: - The msg= passed into either pytest.fail() or pytest.skip(). This will - be converted into reason if it is provided to allow pytest.skip(msg=) or - pytest.fail(msg=) to continue working in the interim period. - - :returns: - The value to use as reason. - - """ - __tracebackhide__ = True - if msg is not None: - - if reason: - from pytest import UsageError - - raise UsageError( - f"Passing both ``reason`` and ``msg`` to pytest.{func_name}(...) is not permitted." - ) - warnings.warn(KEYWORD_MSG_ARG.format(func=func_name), stacklevel=3) - reason = msg - return reason - - class XFailed(Failed): """Raised from an explicit call to pytest.xfail().""" @_with_exception(XFailed) -def xfail(reason: str = "") -> "NoReturn": +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). + No other code is executed after using ``xfail()`` (it is implemented + internally by raising an exception). + + :param reason: + The message to show the user as reason for the xfail. + .. 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. + + :raises pytest.xfail.Exception: + The exception that is raised. """ __tracebackhide__ = True raise XFailed(reason) def importorskip( - modname: str, minversion: Optional[str] = None, reason: Optional[str] = None + modname: str, + minversion: Optional[str] = None, + reason: Optional[str] = None, + *, + exc_type: Optional[Type[ImportError]] = None, ) -> Any: """Import and return the requested module ``modname``, or skip the current test if the module cannot be imported. - :param str modname: + :param modname: The name of the module to import. - :param str minversion: + :param 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: + :param reason: If given, this reason is shown as the message when the module cannot be imported. + :param exc_type: + The exception that should be captured in order to skip modules. + Must be :py:class:`ImportError` or a subclass. + + If the module can be imported but raises :class:`ImportError`, pytest will + issue a warning to the user, as often users expect the module not to be + found (which would raise :class:`ModuleNotFoundError` instead). + + This warning can be suppressed by passing ``exc_type=ImportError`` explicitly. + + See :ref:`import-or-skip-import-error` for details. + :returns: The imported module. This should be assigned to its canonical name. + :raises pytest.skip.Exception: + If the module cannot be imported. + Example:: docutils = pytest.importorskip("docutils") + + .. versionadded:: 8.2 + + The ``exc_type`` parameter. """ import warnings __tracebackhide__ = True compile(modname, "", "eval") # to catch syntaxerrors + # Until pytest 9.1, we will warn the user if we catch ImportError (instead of ModuleNotFoundError), + # as this might be hiding an installation/environment problem, which is not usually what is intended + # when using importorskip() (#11523). + # In 9.1, to keep the function signature compatible, we just change the code below to: + # 1. Use `exc_type = ModuleNotFoundError` if `exc_type` is not given. + # 2. Remove `warn_on_import` and the warning handling. + if exc_type is None: + exc_type = ImportError + warn_on_import_error = True + else: + warn_on_import_error = False + + skipped: Optional[Skipped] = None + warning: Optional[Warning] = None + with warnings.catch_warnings(): # 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. warnings.simplefilter("ignore") + try: __import__(modname) - except ImportError as exc: + except exc_type as exc: + # Do not raise or issue warnings inside the catch_warnings() block. if reason is None: reason = f"could not import {modname!r}: {exc}" - raise Skipped(reason, allow_module_level=True) from None + skipped = Skipped(reason, allow_module_level=True) + + if warn_on_import_error and not isinstance(exc, ModuleNotFoundError): + lines = [ + "", + f"Module '{modname}' was found, but when imported by pytest it raised:", + f" {exc!r}", + "In pytest 9.1 this warning will become an error by default.", + "You can fix the underlying problem, or alternatively overwrite this behavior and silence this " + "warning by passing exc_type=ImportError explicitly.", + "See https://docs.pytest.org/en/stable/deprecations.html#pytest-importorskip-default-behavior-regarding-importerror", + ] + warning = PytestDeprecationWarning("\n".join(lines)) + + if warning: + warnings.warn(warning, stacklevel=2) + if skipped: + raise skipped + mod = sys.modules[modname] if minversion is None: return mod @@ -300,8 +311,7 @@ def importorskip( if verattr is None or Version(verattr) < Version(minversion): raise Skipped( - "module %r has __version__ %r, required is: %r" - % (modname, verattr, minversion), + f"module {modname!r} has __version__ {verattr!r}, required is: {minversion!r}", allow_module_level=True, ) return mod diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pastebin.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pastebin.py index 385b3022cc0e9..533d78c9a2a4e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pastebin.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pastebin.py @@ -1,15 +1,17 @@ +# mypy: allow-untyped-defs """Submit failure or test session information to a pastebin service.""" -import tempfile + from io import StringIO +import tempfile 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.stash import StashKey from _pytest.terminal import TerminalReporter +import pytest pastebinfile_key = StashKey[IO[bytes]]() @@ -24,7 +26,7 @@ def pytest_addoption(parser: Parser) -> None: dest="pastebin", default=None, choices=["failed", "all"], - help="send failed|all info to bpaste.net pastebin service.", + help="Send failed|all info to bpaste.net pastebin service", ) @@ -73,8 +75,8 @@ def create_new_paste(contents: Union[str, bytes]) -> str: :returns: URL to the pasted contents, or an error message. """ import re - from urllib.request import urlopen from urllib.parse import urlencode + from urllib.request import urlopen params = {"code": contents, "lexer": "text", "expiry": "1week"} url = "https://bpa.st" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pathlib.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pathlib.py index b44753e1a415a..b11eea4e7ef75 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pathlib.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pathlib.py @@ -1,19 +1,16 @@ import atexit import contextlib -import fnmatch -import importlib.util -import itertools -import os -import shutil -import sys -import uuid -import warnings from enum import Enum from errno import EBADF from errno import ELOOP from errno import ENOENT from errno import ENOTDIR +import fnmatch from functools import partial +from importlib.machinery import ModuleSpec +import importlib.util +import itertools +import os from os.path import expanduser from os.path import expandvars from os.path import isabs @@ -21,20 +18,30 @@ from pathlib import Path from pathlib import PurePath from posixpath import sep as posix_sep +import shutil +import sys +import types from types import ModuleType +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 Type from typing import TypeVar from typing import Union +import uuid +import warnings from _pytest.compat import assert_never from _pytest.outcomes import skip from _pytest.warning_types import PytestWarning + LOCK_TIMEOUT = 60 * 60 * 24 * 3 @@ -52,7 +59,7 @@ ) -def _ignore_error(exception): +def _ignore_error(exception: Exception) -> bool: return ( getattr(exception, "errno", None) in _IGNORED_ERRORS or getattr(exception, "winerror", None) in _IGNORED_WINERRORS @@ -63,21 +70,33 @@ def get_lock_path(path: _AnyPurePath) -> _AnyPurePath: return path.joinpath(".lock") -def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool: +def on_rm_rf_error( + func: Optional[Callable[..., Any]], + path: str, + excinfo: Union[ + BaseException, + Tuple[Type[BaseException], BaseException, Optional[types.TracebackType]], + ], + *, + start_path: Path, +) -> bool: """Handle known read-only errors during rmtree. The returned value is used only by our own tests. """ - exctype, excvalue = exc[:2] + if isinstance(excinfo, BaseException): + exc = excinfo + else: + exc = excinfo[1] # 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): + if isinstance(exc, FileNotFoundError): return False - if not isinstance(excvalue, PermissionError): + if not isinstance(exc, PermissionError): warnings.warn( - PytestWarning(f"(rm_rf) error removing {path}\n{exctype}: {excvalue}") + PytestWarning(f"(rm_rf) error removing {path}\n{type(exc)}: {exc}") ) return False @@ -85,9 +104,7 @@ def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool: if func not in (os.open,): warnings.warn( PytestWarning( - "(rm_rf) unknown function {} when removing {}:\n{}: {}".format( - func, path, exctype, excvalue - ) + f"(rm_rf) unknown function {func} when removing {path}:\n{type(exc)}: {exc}" ) ) return False @@ -149,26 +166,29 @@ def rm_rf(path: Path) -> None: are read-only.""" path = ensure_extended_length_path(path) onerror = partial(on_rm_rf_error, start_path=path) - shutil.rmtree(str(path), onerror=onerror) + if sys.version_info >= (3, 12): + shutil.rmtree(str(path), onexc=onerror) + else: + shutil.rmtree(str(path), onerror=onerror) -def find_prefixed(root: Path, prefix: str) -> Iterator[Path]: - """Find all elements in root that begin with the prefix, case insensitive.""" +def find_prefixed(root: Path, prefix: str) -> Iterator["os.DirEntry[str]"]: + """Find all elements in root that begin with the prefix, case-insensitive.""" l_prefix = prefix.lower() - for x in root.iterdir(): + for x in os.scandir(root): if x.name.lower().startswith(l_prefix): yield x -def extract_suffixes(iter: Iterable[PurePath], prefix: str) -> Iterator[str]: +def extract_suffixes(iter: Iterable["os.DirEntry[str]"], 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:] + for entry in iter: + yield entry.name[p_len:] def find_suffixes(root: Path, prefix: str) -> Iterator[str]: @@ -176,7 +196,7 @@ def find_suffixes(root: Path, prefix: str) -> Iterator[str]: return extract_suffixes(find_prefixed(root, prefix), prefix) -def parse_num(maybe_num) -> int: +def parse_num(maybe_num: str) -> int: """Parse number path suffixes, returns -1 on error.""" try: return int(maybe_num) @@ -223,7 +243,7 @@ def make_numbered_dir(root: Path, prefix: str, mode: int = 0o700) -> Path: else: raise OSError( "could not create numbered dir with prefix " - "{prefix} in {root} after 10 tries".format(prefix=prefix, root=root) + f"{prefix} in {root} after 10 tries" ) @@ -244,7 +264,9 @@ def create_cleanup_lock(p: Path) -> Path: return lock_path -def register_cleanup_lock_removal(lock_path: Path, register=atexit.register): +def register_cleanup_lock_removal( + lock_path: Path, register: Any = atexit.register +) -> Any: """Register a cleanup function for removing a lock, by default on atexit.""" pid = os.getpid() @@ -327,23 +349,34 @@ 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) - numbers = map(parse_num, extract_suffixes(paths2, prefix)) - for path, number in zip(paths, numbers): + entries = find_prefixed(root, prefix) + entries, entries2 = itertools.tee(entries) + numbers = map(parse_num, extract_suffixes(entries2, prefix)) + for entry, number in zip(entries, numbers): if number <= max_delete: - yield path + yield Path(entry) + + +def cleanup_dead_symlinks(root: Path) -> None: + for left_dir in root.iterdir(): + if left_dir.is_symlink(): + if not left_dir.resolve().exists(): + left_dir.unlink() def cleanup_numbered_dir( root: Path, prefix: str, keep: int, consider_lock_dead_if_created_before: float ) -> None: """Cleanup for lock driven numbered directories.""" + if not root.exists(): + return 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) + cleanup_dead_symlinks(root) + def make_numbered_dir_with_cleanup( root: Path, @@ -357,8 +390,10 @@ def make_numbered_dir_with_cleanup( for i in range(10): try: p = make_numbered_dir(root, prefix, mode) - lock_path = create_cleanup_lock(p) - register_cleanup_lock_removal(lock_path) + # Only lock the current dir when keep is not 0 + if keep != 0: + lock_path = create_cleanup_lock(p) + register_cleanup_lock_removal(lock_path) except Exception as exc: e = exc else: @@ -426,10 +461,14 @@ def parts(s: str) -> Set[str]: return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))} -def symlink_or_skip(src, dst, **kwargs): +def symlink_or_skip( + src: Union["os.PathLike[str]", str], + dst: Union["os.PathLike[str]", str], + **kwargs: Any, +) -> None: """Make a symlink, or skip the test in case symlinks are not supported.""" try: - os.symlink(str(src), str(dst), **kwargs) + os.symlink(src, dst, **kwargs) except OSError as e: skip(f"symlinks not supported: {e}") @@ -452,71 +491,90 @@ class ImportPathMismatchError(ImportError): def import_path( - p: Union[str, "os.PathLike[str]"], + path: Union[str, "os.PathLike[str]"], *, mode: Union[str, ImportMode] = ImportMode.prepend, root: Path, + consider_namespace_packages: bool, ) -> ModuleType: - """Import and return a module from the given path, which can be a file (a module) or + """ + 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: + :param path: + Path to the file to import. - * `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__. + :param mode: + Controls the underlying import mechanism that will be used: - * `mode == ImportMode.append`: same as `prepend`, but the directory will be appended - to the end of `sys.path`, if not already in `sys.path`. + * 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 `importlib.import_module`. - * `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. + * ImportMode.append: same as `prepend`, but the directory will be appended + to the end of `sys.path`, if not already in `sys.path`. + + * ImportMode.importlib: uses more fine control mechanisms provided by `importlib` + to import the module, which avoids having to muck with `sys.path` at all. It effectively + allows having same-named test modules in different places. :param root: Used as an anchor when mode == ImportMode.importlib to obtain a unique name for the module being imported so it can safely be stored into ``sys.modules``. + :param consider_namespace_packages: + If True, consider namespace packages when resolving module names. + :raises ImportPathMismatchError: If after importing the given `path` and the module `__file__` are different. Only raised in `prepend` and `append` modes. """ + path = Path(path) mode = ImportMode(mode) - path = Path(p) - if not path.exists(): raise ImportError(path) if mode is ImportMode.importlib: - module_name = module_name_from_path(path, root) - - for meta_importer in sys.meta_path: - spec = meta_importer.find_spec(module_name, [str(path.parent)]) - if spec is not None: - break + # Try to import this module using the standard import mechanisms, but + # without touching sys.path. + try: + pkg_root, module_name = resolve_pkg_root_and_module_name( + path, consider_namespace_packages=consider_namespace_packages + ) + except CouldNotResolvePathError: + pass else: - spec = importlib.util.spec_from_file_location(module_name, str(path)) + # If the given module name is already in sys.modules, do not import it again. + with contextlib.suppress(KeyError): + return sys.modules[module_name] + + mod = _import_module_using_spec( + module_name, path, pkg_root, insert_modules=False + ) + if mod is not None: + return mod + + # Could not import the module with the current sys.path, so we fall back + # to importing the file as a single module, not being a part of a package. + module_name = module_name_from_path(path, root) + with contextlib.suppress(KeyError): + return sys.modules[module_name] - if spec is None: + mod = _import_module_using_spec( + module_name, path, path.parent, insert_modules=True + ) + if mod is None: raise ImportError(f"Can't find module {module_name} at location {path}") - mod = importlib.util.module_from_spec(spec) - sys.modules[module_name] = mod - spec.loader.exec_module(mod) # type: ignore[union-attr] - insert_missing_modules(sys.modules, module_name) 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 + try: + pkg_root, module_name = resolve_pkg_root_and_module_name( + path, consider_namespace_packages=consider_namespace_packages + ) + except CouldNotResolvePathError: + pkg_root, module_name = path.parent, 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 @@ -539,10 +597,13 @@ def import_path( ignore = os.environ.get("PY_IGNORE_IMPORTMISMATCH", "") if ignore != "1": module_file = mod.__file__ + if module_file is None: + raise ImportPathMismatchError(module_name, module_file, path) + 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"))] + if module_file.endswith(os.sep + "__init__.py"): + module_file = module_file[: -(len(os.sep + "__init__.py"))] try: is_same = _is_same(str(path), module_file) @@ -555,6 +616,80 @@ def import_path( return mod +def _import_module_using_spec( + module_name: str, module_path: Path, module_location: Path, *, insert_modules: bool +) -> Optional[ModuleType]: + """ + Tries to import a module by its canonical name, path to the .py file, and its + parent location. + + :param insert_modules: + If True, will call insert_missing_modules to create empty intermediate modules + for made-up module names (when importing test files not reachable from sys.path). + """ + # Checking with sys.meta_path first in case one of its hooks can import this module, + # such as our own assertion-rewrite hook. + for meta_importer in sys.meta_path: + spec = meta_importer.find_spec(module_name, [str(module_location)]) + if spec_matches_module_path(spec, module_path): + break + else: + spec = importlib.util.spec_from_file_location(module_name, str(module_path)) + + if spec_matches_module_path(spec, module_path): + assert spec is not None + # Attempt to import the parent module, seems is our responsibility: + # https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1308-L1311 + parent_module_name, _, name = module_name.rpartition(".") + parent_module: Optional[ModuleType] = None + if parent_module_name: + parent_module = sys.modules.get(parent_module_name) + if parent_module is None: + # Find the directory of this module's parent. + parent_dir = ( + module_path.parent.parent + if module_path.name == "__init__.py" + else module_path.parent + ) + # Consider the parent module path as its __init__.py file, if it has one. + parent_module_path = ( + parent_dir / "__init__.py" + if (parent_dir / "__init__.py").is_file() + else parent_dir + ) + parent_module = _import_module_using_spec( + parent_module_name, + parent_module_path, + parent_dir, + insert_modules=insert_modules, + ) + + # Find spec and import this module. + mod = importlib.util.module_from_spec(spec) + sys.modules[module_name] = mod + spec.loader.exec_module(mod) # type: ignore[union-attr] + + # Set this module as an attribute of the parent module (#12194). + if parent_module is not None: + setattr(parent_module, name, mod) + + if insert_modules: + insert_missing_modules(sys.modules, module_name) + return mod + + return None + + +def spec_matches_module_path( + module_spec: Optional[ModuleSpec], module_path: Path +) -> bool: + """Return true if the given ModuleSpec can be used to import the given module path.""" + if module_spec is None or module_spec.origin is None: + return False + + return Path(module_spec.origin) == module_path + + # Implement a special _is_same function on Windows which returns True if the two filenames # compare equal, to circumvent os.path.samefile returning False for mounts in UNC (#7678). if sys.platform.startswith("win"): @@ -562,7 +697,6 @@ def import_path( def _is_same(f1: str, f2: str) -> bool: return Path(f1) == Path(f2) or os.path.samefile(f1, f2) - else: def _is_same(f1: str, f2: str) -> bool: @@ -587,6 +721,16 @@ def module_name_from_path(path: Path, root: Path) -> str: # Use the parts for the relative path to the root path. path_parts = relative_path.parts + # Module name for packages do not contain the __init__ file, unless + # the `__init__.py` file is at the root. + if len(path_parts) >= 2 and path_parts[-1] == "__init__": + path_parts = path_parts[:-1] + + # Module names cannot contain ".", normalize them to "_". This prevents + # a directory having a "." in the name (".env.310" for example) causing extra intermediate modules. + # Also, important to replace "." at the start of paths, as those are considered relative imports. + path_parts = tuple(x.replace(".", "_") for x in path_parts) + return ".".join(path_parts) @@ -600,12 +744,30 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> """ module_parts = module_name.split(".") while module_name: - if module_name not in modules: - module = ModuleType( - module_name, - doc="Empty module created by pytest's importmode=importlib.", - ) - modules[module_name] = module + parent_module_name, _, child_name = module_name.rpartition(".") + if parent_module_name: + parent_module = modules.get(parent_module_name) + if parent_module is None: + try: + # If sys.meta_path is empty, calling import_module will issue + # a warning and raise ModuleNotFoundError. To avoid the + # warning, we check sys.meta_path explicitly and raise the error + # ourselves to fall back to creating a dummy module. + if not sys.meta_path: + raise ModuleNotFoundError + parent_module = importlib.import_module(parent_module_name) + except ModuleNotFoundError: + parent_module = ModuleType( + module_name, + doc="Empty module created by pytest's importmode=importlib.", + ) + modules[parent_module_name] = parent_module + + # Add child attribute to the parent that can reference the child + # modules. + if not hasattr(parent_module, child_name): + setattr(parent_module, child_name, modules[module_name]) + module_parts.pop(-1) module_name = ".".join(module_parts) @@ -614,12 +776,12 @@ 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. + Returns None if it cannot be determined. """ result = None for parent in itertools.chain((path,), path.parents): if parent.is_dir(): - if not parent.joinpath("__init__.py").is_file(): + if not (parent / "__init__.py").is_file(): break if not parent.name.isidentifier(): break @@ -627,42 +789,148 @@ def resolve_package_path(path: Path) -> Optional[Path]: return result +def resolve_pkg_root_and_module_name( + path: Path, *, consider_namespace_packages: bool = False +) -> Tuple[Path, str]: + """ + Return the path to the directory of the root package that contains the + given Python file, and its module name: + + src/ + app/ + __init__.py + core/ + __init__.py + models.py + + Passing the full path to `models.py` will yield Path("src") and "app.core.models". + + If consider_namespace_packages is True, then we additionally check upwards in the hierarchy + for namespace packages: + + https://packaging.python.org/en/latest/guides/packaging-namespace-packages + + Raises CouldNotResolvePathError if the given path does not belong to a package (missing any __init__.py files). + """ + pkg_root: Optional[Path] = None + pkg_path = resolve_package_path(path) + if pkg_path is not None: + pkg_root = pkg_path.parent + if consider_namespace_packages: + start = pkg_root if pkg_root is not None else path.parent + for candidate in (start, *start.parents): + module_name = compute_module_name(candidate, path) + if module_name and is_importable(module_name, path): + # Point the pkg_root to the root of the namespace package. + pkg_root = candidate + break + + if pkg_root is not None: + module_name = compute_module_name(pkg_root, path) + if module_name: + return pkg_root, module_name + + raise CouldNotResolvePathError(f"Could not resolve for {path}") + + +def is_importable(module_name: str, module_path: Path) -> bool: + """ + Return if the given module path could be imported normally by Python, akin to the user + entering the REPL and importing the corresponding module name directly, and corresponds + to the module_path specified. + + :param module_name: + Full module name that we want to check if is importable. + For example, "app.models". + + :param module_path: + Full path to the python module/package we want to check if is importable. + For example, "/projects/src/app/models.py". + """ + try: + # Note this is different from what we do in ``_import_module_using_spec``, where we explicitly search through + # sys.meta_path to be able to pass the path of the module that we want to import (``meta_importer.find_spec``). + # Using importlib.util.find_spec() is different, it gives the same results as trying to import + # the module normally in the REPL. + spec = importlib.util.find_spec(module_name) + except (ImportError, ValueError, ImportWarning): + return False + else: + return spec_matches_module_path(spec, module_path) + + +def compute_module_name(root: Path, module_path: Path) -> Optional[str]: + """Compute a module name based on a path and a root anchor.""" + try: + path_without_suffix = module_path.with_suffix("") + except ValueError: + # Empty paths (such as Path.cwd()) might break meta_path hooks (like our own assertion rewriter). + return None + + try: + relative = path_without_suffix.relative_to(root) + except ValueError: # pragma: no cover + return None + names = list(relative.parts) + if not names: + return None + if names[-1] == "__init__": + names.pop() + return ".".join(names) + + +class CouldNotResolvePathError(Exception): + """Custom exception raised by resolve_pkg_root_and_module_name.""" + + +def scandir( + path: Union[str, "os.PathLike[str]"], + sort_key: Callable[["os.DirEntry[str]"], object] = lambda entry: entry.name, +) -> List["os.DirEntry[str]"]: + """Scan a directory recursively, in breadth-first order. + + The returned entries are sorted according to the given key. + The default is to sort by name. + """ + entries = [] + with os.scandir(path) as s: + # Skip entries with symlink loops and other brokenness, so the caller + # doesn't have to deal with it. + for entry in s: + try: + entry.is_file() + except OSError as err: + if _ignore_error(err): + continue + raise + entries.append(entry) + entries.sort(key=sort_key) # type: ignore[arg-type] + return entries + + def visit( path: Union[str, "os.PathLike[str]"], recurse: Callable[["os.DirEntry[str]"], bool] ) -> Iterator["os.DirEntry[str]"]: """Walk a directory recursively, in breadth-first order. + The `recurse` predicate determines whether a directory is recursed. + Entries at each directory level are sorted. """ - - # Skip entries with symlink loops and other brokenness, so the caller doesn't - # have to deal with it. - entries = [] - for entry in os.scandir(path): - try: - entry.is_file() - except OSError as err: - if _ignore_error(err): - continue - raise - entries.append(entry) - - entries.sort(key=lambda entry: entry.name) - + entries = scandir(path) yield from entries - for entry in entries: if entry.is_dir() and recurse(entry): yield from visit(entry.path, recurse) -def absolutepath(path: Union[Path, str]) -> Path: +def absolutepath(path: "Union[str, os.PathLike[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))) + return Path(os.path.abspath(path)) def commonpath(path1: Path, path2: Path) -> Optional[Path]: @@ -706,19 +974,11 @@ def bestrelpath(directory: Path, dest: Path) -> str: ) -# Originates from py. path.local.copy(), with siginficant trims and adjustments. -# TODO(py38): Replace with shutil.copytree(..., symlinks=True, dirs_exist_ok=True) -def copytree(source: Path, target: Path) -> None: - """Recursively copy a source directory to target.""" - assert source.is_dir() - for entry in visit(source, recurse=lambda entry: not entry.is_symlink()): - x = Path(entry) - relpath = x.relative_to(source) - newx = target / relpath - newx.parent.mkdir(exist_ok=True) - if x.is_symlink(): - newx.symlink_to(os.readlink(x)) - elif x.is_file(): - shutil.copyfile(x, newx) - elif x.is_dir(): - newx.mkdir(exist_ok=True) +def safe_exists(p: Path) -> bool: + """Like Path.exists(), but account for input arguments that might be too long (#11394).""" + try: + return p.exists() + except (ValueError, OSError): + # ValueError: stat: path too long for Windows + # OSError: [WinError 123] The filename, directory name, or volume label syntax is incorrect + return False diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pytester.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pytester.py index 363a3727447bd..9ba8e6a818254 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pytester.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pytester.py @@ -1,28 +1,34 @@ +# mypy: allow-untyped-defs """(Disabled by default) support for testing pytest and pytest plugins. PYTEST_DONT_REWRITE """ + import collections.abc import contextlib +from fnmatch import fnmatch import gc import importlib +from io import StringIO +import locale import os +from pathlib import Path import platform import re import shutil import subprocess import sys import traceback -from fnmatch import fnmatch -from io import StringIO -from pathlib import Path from typing import Any from typing import Callable from typing import Dict +from typing import Final +from typing import final from typing import Generator from typing import IO from typing import Iterable from typing import List +from typing import Literal from typing import Optional from typing import overload from typing import Sequence @@ -39,7 +45,6 @@ from _pytest import timing from _pytest._code import Source from _pytest.capture import _get_multicapture -from _pytest.compat import final from _pytest.compat import NOTSET from _pytest.compat import NotSetType from _pytest.config import _PluggyPlugin @@ -60,7 +65,6 @@ from _pytest.outcomes import importorskip from _pytest.outcomes import skip from _pytest.pathlib import bestrelpath -from _pytest.pathlib import copytree from _pytest.pathlib import make_numbered_dir from _pytest.reports import CollectReport from _pytest.reports import TestReport @@ -69,9 +73,6 @@ if TYPE_CHECKING: - from typing_extensions import Final - from typing_extensions import Literal - import pexpect @@ -89,7 +90,7 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", dest="lsof", default=False, - help="run FD checks if lsof is available", + help="Run FD checks if lsof is available", ) parser.addoption( @@ -98,13 +99,13 @@ def pytest_addoption(parser: Parser) -> None: dest="runpytest", choices=("inprocess", "subprocess"), help=( - "run pytest sub runs in tests using an 'inprocess' " + "Run pytest sub runs in tests using an 'inprocess' " "or 'subprocess' (python -m main) method" ), ) parser.addini( - "pytester_example_dir", help="directory to take the pytester example files from" + "pytester_example_dir", help="Directory to take the pytester example files from" ) @@ -123,12 +124,18 @@ def pytest_configure(config: Config) -> None: class LsofFdLeakChecker: def get_open_files(self) -> List[Tuple[str, str]]: + if sys.version_info >= (3, 11): + # New in Python 3.11, ignores utf-8 mode + encoding = locale.getencoding() + else: + encoding = locale.getpreferredencoding(False) out = subprocess.run( ("lsof", "-Ffn0", "-p", str(os.getpid())), stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=True, - universal_newlines=True, + text=True, + encoding=encoding, ).stdout def isopen(line: str) -> bool: @@ -161,29 +168,31 @@ def matching_platform(self) -> bool: else: return True - @hookimpl(hookwrapper=True, tryfirst=True) - def pytest_runtest_protocol(self, item: Item) -> Generator[None, None, None]: + @hookimpl(wrapper=True, tryfirst=True) + def pytest_runtest_protocol(self, item: Item) -> Generator[None, object, object]: lines1 = self.get_open_files() - yield - if hasattr(sys, "pypy_version_info"): - gc.collect() - lines2 = self.get_open_files() - - 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 = [ - "***** %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(PytestWarning("\n".join(error))) + try: + return (yield) + finally: + if hasattr(sys, "pypy_version_info"): + gc.collect() + lines2 = self.get_open_files() + + 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 = [ + "***** %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 {}:{}: {} ".format(*item.location), + "See issue #2366", + ] + item.warn(PytestWarning("\n".join(error))) # used at least by pytest-xdist plugin @@ -237,8 +246,7 @@ def __repr__(self) -> str: if TYPE_CHECKING: # The class has undetermined attributes, this tells mypy about it. - def __getattr__(self, key: str): - ... + def __getattr__(self, key: str): ... @final @@ -281,7 +289,8 @@ def assert_contains(self, entries: Sequence[Tuple[str, str]]) -> None: __tracebackhide__ = True i = 0 entries = list(entries) - backlocals = sys._getframe(1).f_locals + # Since Python 3.13, f_locals is not a dict, but eval requires a dict. + backlocals = dict(sys._getframe(1).f_locals) while entries: name, check = entries.pop(0) for ind, call in enumerate(self.calls[i:]): @@ -319,15 +328,13 @@ def getcall(self, name: str) -> RecordedHookCall: def getreports( self, names: "Literal['pytest_collectreport']", - ) -> Sequence[CollectReport]: - ... + ) -> Sequence[CollectReport]: ... @overload def getreports( self, names: "Literal['pytest_runtest_logreport']", - ) -> Sequence[TestReport]: - ... + ) -> Sequence[TestReport]: ... @overload def getreports( @@ -336,8 +343,7 @@ def getreports( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: - ... + ) -> Sequence[Union[CollectReport, TestReport]]: ... def getreports( self, @@ -369,14 +375,12 @@ def matchreport( values.append(rep) if not values: raise ValueError( - "could not find test report matching %r: " - "no test reports at all!" % (inamepart,) + f"could not find test report matching {inamepart!r}: " + "no test reports at all!" ) if len(values) > 1: raise ValueError( - "found 2 or more testreports matching {!r}: {}".format( - inamepart, values - ) + f"found 2 or more testreports matching {inamepart!r}: {values}" ) return values[0] @@ -384,15 +388,13 @@ def matchreport( def getfailures( self, names: "Literal['pytest_collectreport']", - ) -> Sequence[CollectReport]: - ... + ) -> Sequence[CollectReport]: ... @overload def getfailures( self, names: "Literal['pytest_runtest_logreport']", - ) -> Sequence[TestReport]: - ... + ) -> Sequence[TestReport]: ... @overload def getfailures( @@ -401,8 +403,7 @@ def getfailures( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: - ... + ) -> Sequence[Union[CollectReport, TestReport]]: ... def getfailures( self, @@ -477,7 +478,9 @@ def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]: @fixture -def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pytester": +def pytester( + request: FixtureRequest, tmp_path_factory: TempPathFactory, monkeypatch: MonkeyPatch +) -> "Pytester": """ Facilities to write tests/configuration files, execute pytest in isolation, and match against expected output, perfect for black-box testing of pytest plugins. @@ -488,7 +491,7 @@ def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pyt It is particularly useful for testing plugins. It is similar to the :fixture:`tmp_path` fixture but provides methods which aid in testing pytest itself. """ - return Pytester(request, tmp_path_factory, _ispytest=True) + return Pytester(request, tmp_path_factory, monkeypatch, _ispytest=True) @fixture @@ -622,14 +625,6 @@ def assert_outcomes( ) -class CwdSnapshot: - def __init__(self) -> None: - self.__saved = os.getcwd() - - def restore(self) -> None: - os.chdir(self.__saved) - - class SysModulesSnapshot: def __init__(self, preserve: Optional[Callable[[str], bool]] = None) -> None: self.__preserve = preserve @@ -659,17 +654,7 @@ class Pytester: against expected output, perfect for black-box testing of pytest plugins. It attempts to isolate the test run from external factors as much as possible, modifying - the current working directory to ``path`` and environment variables during initialization. - - Attributes: - - :ivar Path path: temporary directory path used to create files/run tests from, etc. - - :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. + the current working directory to :attr:`path` and environment variables during initialization. """ __test__ = False @@ -683,6 +668,7 @@ def __init__( self, request: FixtureRequest, tmp_path_factory: TempPathFactory, + monkeypatch: MonkeyPatch, *, _ispytest: bool = False, ) -> None: @@ -697,16 +683,19 @@ def __init__( name = request.node.name self._name = name self._path: Path = tmp_path_factory.mktemp(name, numbered=True) + #: 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. self.plugins: 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) self._method = self._request.config.getoption("--runpytest") self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True) - self._monkeypatch = mp = MonkeyPatch() + self._monkeypatch = mp = monkeypatch + self.chdir() mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self._test_tmproot)) # Ensure no unexpected caching via tox. mp.delenv("TOX_ENV_DIR", raising=False) @@ -721,7 +710,7 @@ def __init__( @property def path(self) -> Path: - """Temporary directory where files are created and pytest is executed.""" + """Temporary directory path used to create files/run tests from, etc.""" return self._path def __repr__(self) -> str: @@ -737,8 +726,6 @@ def _finalize(self) -> None: """ self._sys_modules_snapshot.restore() self._sys_path_snapshot.restore() - self._cwd_snapshot.restore() - self._monkeypatch.undo() def __take_sys_modules_snapshot(self) -> SysModulesSnapshot: # Some zope modules used by twisted-related tests keep internal state @@ -753,8 +740,8 @@ def preserve_module(name): return SysModulesSnapshot(preserve=preserve_module) def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder: - """Create a new :py:class:`HookRecorder` for a PluginManager.""" - pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True) + """Create a new :class:`HookRecorder` for a :class:`PytestPluginManager`.""" + pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True) # type: ignore[attr-defined] self._request.addfinalizer(reprec.finish_recording) return reprec @@ -763,7 +750,7 @@ def chdir(self) -> None: This is done automatically upon instantiation. """ - os.chdir(self.path) + self._monkeypatch.chdir(self.path) def _makefile( self, @@ -774,6 +761,9 @@ def _makefile( ) -> Path: items = list(files.items()) + if ext is None: + raise TypeError("ext must not be None") + if ext and not ext.startswith("."): raise ValueError( f"pytester.makefile expects a file extension, try .{ext} instead of {ext}" @@ -802,7 +792,7 @@ def to_text(s: Union[Any, bytes]) -> str: def makefile(self, ext: str, *args: str, **kwargs: str) -> Path: r"""Create new text file(s) in the test directory. - :param str ext: + :param 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. @@ -811,9 +801,10 @@ def makefile(self, ext: str, *args: str, **kwargs: str) -> Path: :param kwargs: Each keyword is the name of a file, while the value of it will be written as contents of the file. + :returns: + The first created file. Examples: - .. code-block:: python pytester.makefile(".txt", "line1", "line2") @@ -830,11 +821,19 @@ def makefile(self, ext: str, *args: str, **kwargs: str) -> Path: return self._makefile(ext, args, kwargs) def makeconftest(self, source: str) -> Path: - """Write a contest.py file with 'source' as contents.""" + """Write a conftest.py file. + + :param source: The contents. + :returns: The conftest.py file. + """ return self.makepyfile(conftest=source) def makeini(self, source: str) -> Path: - """Write a tox.ini file with 'source' as contents.""" + """Write a tox.ini file. + + :param source: The contents. + :returns: The tox.ini file. + """ return self.makefile(".ini", tox=source) def getinicfg(self, source: str) -> SectionWrapper: @@ -843,7 +842,10 @@ def getinicfg(self, source: str) -> SectionWrapper: return IniConfig(str(p))["pytest"] def makepyprojecttoml(self, source: str) -> Path: - """Write a pyproject.toml file with 'source' as contents. + """Write a pyproject.toml file. + + :param source: The contents. + :returns: The pyproject.ini file. .. versionadded:: 6.0 """ @@ -856,7 +858,6 @@ def makepyfile(self, *args, **kwargs) -> Path: existing files. Examples: - .. code-block:: python def test_something(pytester): @@ -876,7 +877,6 @@ def maketxtfile(self, *args, **kwargs) -> Path: existing files. Examples: - .. code-block:: python def test_something(pytester): @@ -896,19 +896,28 @@ def syspathinsert( This is undone automatically when this object dies at the end of each test. + + :param path: + The path. """ if path is None: path = self.path self._monkeypatch.syspath_prepend(str(path)) - def mkdir(self, name: str) -> Path: - """Create a new (sub)directory.""" + def mkdir(self, name: Union[str, "os.PathLike[str]"]) -> Path: + """Create a new (sub)directory. + + :param name: + The name of the directory, relative to the pytester path. + :returns: + The created directory. + """ p = self.path / name p.mkdir() return p - def mkpydir(self, name: str) -> Path: + def mkpydir(self, name: Union[str, "os.PathLike[str]"]) -> Path: """Create a new python package. This creates a (sub)directory with an empty ``__init__.py`` file so it @@ -922,14 +931,15 @@ def mkpydir(self, name: str) -> Path: def copy_example(self, name: Optional[str] = None) -> Path: """Copy file from project's directory into the testdir. - :param str name: The name of the file to copy. - :return: path to the copied directory (inside ``self.path``). - + :param name: + The name of the file to copy. + :return: + Path to the copied directory (inside ``self.path``). """ - example_dir = self._request.config.getini("pytester_example_dir") - if example_dir is None: + example_dir_ = self._request.config.getini("pytester_example_dir") + if example_dir_ is None: raise ValueError("pytester_example_dir is unset, can't copy examples") - example_dir = self._request.config.rootpath / example_dir + example_dir: Path = self._request.config.rootpath / example_dir_ for extra_element in self._request.node.iter_markers("pytester_example_path"): assert extra_element.args @@ -952,7 +962,7 @@ def copy_example(self, name: Optional[str] = None) -> Path: example_path = example_dir.joinpath(name) if example_path.is_dir() and not example_path.joinpath("__init__.py").is_file(): - copytree(example_path, self.path) + shutil.copytree(example_path, self.path, symlinks=True, dirs_exist_ok=True) return self.path elif example_path.is_file(): result = self.path.joinpath(example_path.name) @@ -965,14 +975,16 @@ def copy_example(self, name: Optional[str] = None) -> Path: def getnode( self, config: Config, arg: Union[str, "os.PathLike[str]"] - ) -> Optional[Union[Collector, Item]]: - """Return the collection node of a file. + ) -> Union[Collector, Item]: + """Get the collection node of a file. - :param pytest.Config config: + :param config: A pytest config. See :py:meth:`parseconfig` and :py:meth:`parseconfigure` for creating it. - :param os.PathLike[str] arg: + :param arg: Path to the file. + :returns: + The node. """ session = Session.from_config(config) assert "::" not in str(arg) @@ -982,13 +994,18 @@ def getnode( config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) return res - def getpathnode(self, path: Union[str, "os.PathLike[str]"]): + def getpathnode( + self, path: Union[str, "os.PathLike[str]"] + ) -> Union[Collector, Item]: """Return the collection node of a file. This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to create the (configured) pytest Config instance. - :param os.PathLike[str] path: Path to the file. + :param path: + Path to the file. + :returns: + The node. """ path = Path(path) config = self.parseconfigure(path) @@ -1004,6 +1021,11 @@ def genitems(self, colitems: Sequence[Union[Item, Collector]]) -> List[Item]: This recurses into the collection node and returns a list of all the test items contained within. + + :param colitems: + The collection nodes. + :returns: + The collected items. """ session = colitems[0].session result: List[Item] = [] @@ -1017,7 +1039,7 @@ def runitem(self, source: str) -> Any: The calling test instance (class containing the test method) must 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`. + ``_pytest.runner.runtestprotocol``. """ # used from runner functional tests item = self.getitem(source) @@ -1037,11 +1059,11 @@ def inline_runsource(self, source: str, *cmdlineargs) -> HookRecorder: :param cmdlineargs: Any extra command line arguments to use. """ p = self.makepyfile(source) - values = list(cmdlineargs) + [p] + values = [*list(cmdlineargs), p] return self.inline_run(*values) def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]: - """Run ``pytest.main(['--collectonly'])`` in-process. + """Run ``pytest.main(['--collect-only'])`` 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 @@ -1190,15 +1212,16 @@ def _ensure_basetemp( return new_args def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config: - """Return a new pytest Config instance from given commandline args. + """Return a new pytest :class:`pytest.Config` instance from given + commandline args. - This invokes the pytest bootstrapping code in _pytest.config to create - a new :py:class:`_pytest.core.PluginManager` and call the - pytest_cmdline_parse hook to create a new - :py:class:`pytest.Config` instance. + This invokes the pytest bootstrapping code in _pytest.config to create a + new :py:class:`pytest.PytestPluginManager` and call the + :hook:`pytest_cmdline_parse` hook to create a new :class:`pytest.Config` + instance. - If :py:attr:`plugins` has been populated they should be plugin modules - to be registered with the PluginManager. + If :attr:`plugins` has been populated they should be plugin modules + to be registered with the plugin manager. """ import _pytest.config @@ -1216,7 +1239,8 @@ def parseconfigure(self, *args: Union[str, "os.PathLike[str]"]) -> Config: """Return a new pytest configured Config instance. Returns a new :py:class:`pytest.Config` instance like - :py:meth:`parseconfig`, but also calls the pytest_configure hook. + :py:meth:`parseconfig`, but also calls the :hook:`pytest_configure` + hook. """ config = self.parseconfig(*args) config._do_configure() @@ -1235,14 +1259,14 @@ def getitem( The module source. :param funcname: The name of the test function for which to return a test item. + :returns: + The 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{}\nitems: {}".format( - funcname, source, items - ) + assert 0, f"{funcname!r} item not found in module:\n{source}\nitems: {items}" def getitems(self, source: Union[str, "os.PathLike[str]"]) -> List[Item]: """Return all test items collected from the module. @@ -1364,7 +1388,7 @@ def run( :param stdin: Optional standard input. - - If it is :py:attr:`CLOSE_STDIN` (Default), then this method calls + - If it is ``CLOSE_STDIN`` (Default), then this method calls :py:class:`subprocess.Popen` with ``stdin=subprocess.PIPE``, and the standard input is closed immediately after the new command is started. @@ -1375,6 +1399,8 @@ def run( - Otherwise, it is passed through to :py:class:`subprocess.Popen`. For further information in this case, consult the document of the ``stdin`` parameter in :py:class:`subprocess.Popen`. + :returns: + The result. """ __tracebackhide__ = True @@ -1399,10 +1425,7 @@ def run( def handle_timeout() -> None: __tracebackhide__ = True - timeout_message = ( - "{seconds} second timeout expired running:" - " {command}".format(seconds=timeout, command=cmdargs) - ) + timeout_message = f"{timeout} second timeout expired running: {cmdargs}" popen.kill() popen.wait() @@ -1461,13 +1484,15 @@ def runpytest_subprocess( :param timeout: The period in seconds after which to timeout and raise :py:class:`Pytester.TimeoutExpired`. + :returns: + The result. """ __tracebackhide__ = True p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700) - args = ("--basetemp=%s" % p,) + args + args = ("--basetemp=%s" % p, *args) plugins = [x for x in self.plugins if isinstance(x, str)] if plugins: - args = ("-p", plugins[0]) + args + args = ("-p", plugins[0], *args) args = self._getpytestargs() + args return self.run(*args, timeout=timeout) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pytester_assertions.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pytester_assertions.py index 657e4db5fc318..d20c2bb599917 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pytester_assertions.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pytester_assertions.py @@ -1,4 +1,5 @@ """Helper plugin for pytester; should not be loaded on its own.""" + # This plugin contains assertions used by pytester. pytester cannot # contain them itself, since it is imported by the `pytest` module, # hence cannot be subject to assertion rewriting, which requires a diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/python.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/python.py index 0fd5702a5cc00..41a2fe39af32a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/python.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/python.py @@ -1,23 +1,27 @@ +# mypy: allow-untyped-defs """Python test discovery, setup and run of test functions.""" + +import abc +from collections import Counter +from collections import defaultdict +import dataclasses import enum import fnmatch +from functools import partial import inspect import itertools import os -import sys -import types -import warnings -from collections import Counter -from collections import defaultdict -from functools import partial from pathlib import Path +import types from typing import Any from typing import Callable from typing import Dict +from typing import final from typing import Generator from typing import Iterable from typing import Iterator from typing import List +from typing import Literal from typing import Mapping from typing import Optional from typing import Pattern @@ -26,8 +30,7 @@ from typing import Tuple from typing import TYPE_CHECKING from typing import Union - -import attr +import warnings import _pytest from _pytest import fixtures @@ -36,30 +39,26 @@ from _pytest._code import getfslineno from _pytest._code.code import ExceptionInfo from _pytest._code.code import TerminalRepr -from _pytest._io import TerminalWriter +from _pytest._code.code import Traceback from _pytest._io.saferepr import saferepr from _pytest.compat import ascii_escaped -from _pytest.compat import assert_never -from _pytest.compat import final from _pytest.compat import get_default_arg_names from _pytest.compat import get_real_func 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 LEGACY_PATH from _pytest.compat import NOTSET from _pytest.compat import safe_getattr from _pytest.compat import safe_isclass -from _pytest.compat import STRING_TYPES from _pytest.config import Config -from _pytest.config import ExitCode from _pytest.config import hookimpl from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest -from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH -from _pytest.deprecated import INSTANCE_COLLECTOR +from _pytest.fixtures import FixtureDef +from _pytest.fixtures import FixtureRequest from _pytest.fixtures import FuncFixtureInfo +from _pytest.fixtures import get_scope_node from _pytest.main import Session from _pytest.mark import MARK_GEN from _pytest.mark import ParameterSet @@ -69,80 +68,51 @@ from _pytest.mark.structures import normalize_mark_list from _pytest.outcomes import fail from _pytest.outcomes import skip -from _pytest.pathlib import bestrelpath from _pytest.pathlib import fnmatch_ex from _pytest.pathlib import import_path from _pytest.pathlib import ImportPathMismatchError -from _pytest.pathlib import parts -from _pytest.pathlib import visit +from _pytest.pathlib import scandir +from _pytest.scope import _ScopeName from _pytest.scope import Scope +from _pytest.stash import StashKey from _pytest.warning_types import PytestCollectionWarning +from _pytest.warning_types import PytestReturnNotNoneWarning from _pytest.warning_types import PytestUnhandledCoroutineWarning -if TYPE_CHECKING: - from typing_extensions import Literal - from _pytest.scope import _ScopeName - -_PYTEST_DIR = Path(_pytest.__file__).parent +if TYPE_CHECKING: + from typing import Self def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("general") - group.addoption( - "--fixtures", - "--funcargs", - action="store_true", - dest="showfixtures", - default=False, - help="show available fixtures, sorted by plugin appearance " - "(fixtures with leading '_' are only shown with '-v')", - ) - group.addoption( - "--fixtures-per-test", - action="store_true", - dest="show_fixtures_per_test", - default=False, - help="show fixtures per test", - ) parser.addini( "python_files", type="args", # NOTE: default is also used in AssertionRewritingHook. default=["test_*.py", "*_test.py"], - help="glob-style file patterns for Python test module discovery", + help="Glob-style file patterns for Python test module discovery", ) parser.addini( "python_classes", type="args", default=["Test"], - help="prefixes or glob names for Python test class discovery", + help="Prefixes or glob names for Python test class discovery", ) parser.addini( "python_functions", type="args", default=["test"], - help="prefixes or glob names for Python test function and method discovery", + help="Prefixes or glob names for Python test function and method discovery", ) parser.addini( "disable_test_id_escaping_and_forfeit_all_rights_to_community_support", type="bool", default=False, - help="disable string escape non-ascii characters, might cause unwanted " + help="Disable string escape non-ASCII characters, might cause unwanted " "side effects(use at your own risk)", ) -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: "Metafunc") -> None: for marker in metafunc.definition.iter_markers(name="parametrize"): metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker) @@ -192,14 +162,35 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: result = testfunction(**testargs) if hasattr(result, "__await__") or hasattr(result, "__aiter__"): async_warn_and_skip(pyfuncitem.nodeid) + elif result is not None: + warnings.warn( + PytestReturnNotNoneWarning( + f"Expected None, but {pyfuncitem.nodeid} returned {result!r}, which will be an error in a " + "future version of pytest. Did you mean to use `assert` instead of `return`?" + ) + ) return True +def pytest_collect_directory( + path: Path, parent: nodes.Collector +) -> Optional[nodes.Collector]: + pkginit = path / "__init__.py" + try: + has_pkginit = pkginit.is_file() + except PermissionError: + # See https://github.com/pytest-dev/pytest/issues/12120#issuecomment-2106349096. + return None + if has_pkginit: + return Package.from_parent(parent, path=path) + return None + + def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Optional["Module"]: if file_path.suffix == ".py": if not parent.session.isinitpath(file_path): if not path_matches_patterns( - file_path, parent.config.getini("python_files") + ["__init__.py"] + file_path, parent.config.getini("python_files") ): return None ihook = parent.session.gethookproxy(file_path) @@ -216,15 +207,14 @@ def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool: def pytest_pycollect_makemodule(module_path: Path, parent) -> "Module": - if module_path.name == "__init__.py": - pkg: Package = Package.from_parent(parent, path=module_path) - return pkg - mod: Module = Module.from_parent(parent, path=module_path) - return mod + return Module.from_parent(parent, path=module_path) @hookimpl(trylast=True) -def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object): +def pytest_pycollect_makeitem( + collector: Union["Module", "Class"], name: str, obj: object +) -> Union[None, nodes.Item, nodes.Collector, List[Union[nodes.Item, nodes.Collector]]]: + assert isinstance(collector, (Class, Module)), type(collector) # Nothing was collected elsewhere, let's do it here. if safe_isclass(obj): if collector.istestclass(obj, name): @@ -248,14 +238,15 @@ def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object): elif getattr(obj, "__test__", True): if is_generator(obj): res = Function.from_parent(collector, name=name) - reason = "yield tests were removed in pytest 4.0 - {name} will be ignored".format( - name=name + reason = ( + f"yield tests were removed in pytest 4.0 - {name} will be ignored" ) res.add_marker(MARK_GEN.xfail(run=False, reason=reason)) res.warn(PytestCollectionWarning(reason)) + return res else: - res = list(collector._genfunctions(name, obj)) - return res + return list(collector._genfunctions(name, obj)) + return None class PyobjMixin(nodes.Node): @@ -283,10 +274,10 @@ def instance(self): """Python instance object the function is bound to. Returns None if not a test method, e.g. for a standalone test function, - a staticmethod, a class or a module. + a class or a module. """ - node = self.getparent(Function) - return getattr(node.obj, "__self__", None) if node is not None else None + # Overridden by Function. + return None @property def obj(self): @@ -298,6 +289,9 @@ def obj(self): # used to avoid Function marker duplication if self._ALLOW_MARKERS: self.own_markers.extend(get_unpacked_marks(self.obj)) + # This assumes that `obj` is called before there is a chance + # to add custom keys to `self.keywords`, so no fear of overriding. + self.keywords.update((mark.name, mark) for mark in self.own_markers) return obj @obj.setter @@ -313,10 +307,8 @@ def _getobj(self): def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> str: """Return Python path relative to the containing module.""" - chain = self.listchain() - chain.reverse() parts = [] - for node in chain: + for node in self.iter_parents(): name = node.name if isinstance(node, Module): name = os.path.splitext(name)[0] @@ -330,19 +322,8 @@ def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: # XXX caching? - obj = self.obj - compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None) - if isinstance(compat_co_firstlineno, int): - # nose compatibility - file_path = sys.modules[obj.__module__].__file__ - if file_path.endswith(".pyc"): - file_path = file_path[:-1] - path: Union["os.PathLike[str]", str] = file_path - lineno = compat_co_firstlineno - else: - path, lineno = getfslineno(obj) + path, lineno = getfslineno(self.obj) modpath = self.getmodpath() - assert isinstance(lineno, int) return path, lineno, modpath @@ -351,7 +332,7 @@ def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str # hook is not called for them. # fmt: off class _EmptyClass: pass # noqa: E701 -IGNORED_ATTRIBUTES = frozenset.union( # noqa: E305 +IGNORED_ATTRIBUTES = frozenset.union( frozenset(), # Module. dir(types.ModuleType("empty_module")), @@ -366,7 +347,7 @@ class _EmptyClass: pass # noqa: E701 # fmt: on -class PyCollector(PyobjMixin, nodes.Collector): +class PyCollector(PyobjMixin, nodes.Collector, abc.ABC): def funcnamefilter(self, name: str) -> bool: return self._matches_prefix_or_glob_option("python_functions", name) @@ -384,8 +365,8 @@ def classnamefilter(self, name: str) -> bool: def istestfunction(self, obj: object, name: str) -> bool: if self.funcnamefilter(name) or self.isnosetest(obj): - if isinstance(obj, staticmethod): - # staticmethods need to be unwrapped. + if isinstance(obj, (staticmethod, classmethod)): + # staticmethods and classmethods need to be unwrapped. obj = safe_getattr(obj, "__func__", False) return callable(obj) and fixtures.getfixturemarker(obj) is None else: @@ -419,7 +400,7 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: for basecls in self.obj.__mro__: dicts.append(basecls.__dict__) - # In each class, nodes should be definition ordered. Since Python 3.6, + # In each class, nodes should be definition ordered. # __dict__ is definition ordered. seen: Set[str] = set() dict_values: List[List[Union[nodes.Item, nodes.Collector]]] = [] @@ -482,13 +463,11 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]: if not metafunc._calls: yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo) else: - # Add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs. - fm = self.session._fixturemanager - fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm) - - # Add_funcarg_pseudo_fixture_def may have shadowed some fixtures - # with direct parametrization, so make sure we update what the - # function really needs. + # Direct parametrizations taking place in module/class-specific + # `metafunc.parametrize` calls may have shadowed some fixtures, so make sure + # we update what the function really needs a.k.a its fixture closure. Note that + # direct parametrizations using `@pytest.mark.parametrize` have already been considered + # into making the closure using `ignore_args` arg to `getfixtureclosure`. fixtureinfo.prune_dependency_tree() for callspec in metafunc._calls: @@ -503,63 +482,110 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]: ) +def importtestmodule( + path: Path, + config: Config, +): + # We assume we are only called once per module. + importmode = config.getoption("--import-mode") + try: + mod = import_path( + path, + mode=importmode, + root=config.rootpath, + consider_namespace_packages=config.getini("consider_namespace_packages"), + ) + except SyntaxError as e: + raise nodes.Collector.CollectError( + ExceptionInfo.from_current().getrepr(style="short") + ) from e + except ImportPathMismatchError as e: + raise nodes.Collector.CollectError( + "import file mismatch:\n" + "imported module {!r} has this __file__ attribute:\n" + " {}\n" + "which is not the same as the test file we want to collect:\n" + " {}\n" + "HINT: remove __pycache__ / .pyc files and/or use a " + "unique basename for your test file modules".format(*e.args) + ) from e + except ImportError as e: + exc_info = ExceptionInfo.from_current() + if config.getoption("verbose") < 2: + exc_info.traceback = exc_info.traceback.filter(filter_traceback) + exc_repr = ( + exc_info.getrepr(style="short") + if exc_info.traceback + else exc_info.exconly() + ) + formatted_tb = str(exc_repr) + raise nodes.Collector.CollectError( + f"ImportError while importing test module '{path}'.\n" + "Hint: make sure your test modules/packages have valid Python names.\n" + "Traceback:\n" + f"{formatted_tb}" + ) from e + except skip.Exception as e: + if e.allow_module_level: + raise + raise nodes.Collector.CollectError( + "Using pytest.skip outside of a test will skip the entire module. " + "If that's your intention, pass `allow_module_level=True`. " + "If you want to skip a specific test or an entire class, " + "use the @pytest.mark.skip or @pytest.mark.skipif decorators." + ) from e + config.pluginmanager.consider_module(mod) + return mod + + class Module(nodes.File, PyCollector): - """Collector for test classes and functions.""" + """Collector for test classes and functions in a Python module.""" def _getobj(self): - return self._importtestmodule() + return importtestmodule(self.path, self.config) def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: - self._inject_setup_module_fixture() - self._inject_setup_function_fixture() + self._register_setup_module_fixture() + self._register_setup_function_fixture() self.session._fixturemanager.parsefactories(self) return super().collect() - def _inject_setup_module_fixture(self) -> None: - """Inject a hidden autouse, module scoped fixture into the collected module object + def _register_setup_module_fixture(self) -> None: + """Register an autouse, module-scoped fixture for 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). """ - has_nose = self.config.pluginmanager.has_plugin("nose") setup_module = _get_first_non_fixture_func( self.obj, ("setUpModule", "setup_module") ) - if setup_module is None and has_nose: - # The name "setup" is too common - only treat as fixture if callable. - setup_module = _get_first_non_fixture_func(self.obj, ("setup",)) - if not callable(setup_module): - setup_module = None teardown_module = _get_first_non_fixture_func( self.obj, ("tearDownModule", "teardown_module") ) - if teardown_module is None and has_nose: - teardown_module = _get_first_non_fixture_func(self.obj, ("teardown",)) - # Same as "setup" above - only treat as fixture if callable. - if not callable(teardown_module): - teardown_module = None if setup_module is None and teardown_module is None: return - @fixtures.fixture( - autouse=True, - scope="module", - # Use a unique name to speed up lookup. - name=f"_xunit_setup_module_fixture_{self.obj.__name__}", - ) def xunit_setup_module_fixture(request) -> Generator[None, None, None]: + module = request.module if setup_module is not None: - _call_with_optional_argument(setup_module, request.module) + _call_with_optional_argument(setup_module, module) yield if teardown_module is not None: - _call_with_optional_argument(teardown_module, request.module) + _call_with_optional_argument(teardown_module, module) - self.obj.__pytest_setup_module = xunit_setup_module_fixture + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_xunit_setup_module_fixture_{self.obj.__name__}", + func=xunit_setup_module_fixture, + nodeid=self.nodeid, + scope="module", + autouse=True, + ) - def _inject_setup_function_fixture(self) -> None: - """Inject a hidden autouse, function scoped fixture into the collected module object + def _register_setup_function_fixture(self) -> None: + """Register an autouse, function-scoped fixture for 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 @@ -572,75 +598,44 @@ def _inject_setup_function_fixture(self) -> None: if setup_function is None and teardown_function is None: return - @fixtures.fixture( - autouse=True, - scope="function", - # Use a unique name to speed up lookup. - name=f"_xunit_setup_function_fixture_{self.obj.__name__}", - ) 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 yield return + function = request.function if setup_function is not None: - _call_with_optional_argument(setup_function, request.function) + _call_with_optional_argument(setup_function, function) yield if teardown_function is not None: - _call_with_optional_argument(teardown_function, request.function) + _call_with_optional_argument(teardown_function, function) - self.obj.__pytest_setup_function = xunit_setup_function_fixture + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_xunit_setup_function_fixture_{self.obj.__name__}", + func=xunit_setup_function_fixture, + nodeid=self.nodeid, + scope="function", + autouse=True, + ) + + +class Package(nodes.Directory): + """Collector for files and directories in a Python packages -- directories + with an `__init__.py` file. + + .. note:: + + Directories without an `__init__.py` file are instead collected by + :class:`~pytest.Dir` by default. Both are :class:`~pytest.Directory` + collectors. + + .. versionchanged:: 8.0 + + Now inherits from :class:`~pytest.Directory`. + """ - def _importtestmodule(self): - # We assume we are only called once per module. - importmode = self.config.getoption("--import-mode") - try: - mod = import_path(self.path, mode=importmode, root=self.config.rootpath) - except SyntaxError as e: - raise self.CollectError( - 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" - " %s\n" - "which is not the same as the test file we want to collect:\n" - " %s\n" - "HINT: remove __pycache__ / .pyc files and/or use a " - "unique basename for your test file modules" % e.args - ) 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) - exc_repr = ( - exc_info.getrepr(style="short") - if exc_info.traceback - else exc_info.exconly() - ) - formatted_tb = str(exc_repr) - raise self.CollectError( - "ImportError while importing test module '{path}'.\n" - "Hint: make sure your test modules/packages have valid Python names.\n" - "Traceback:\n" - "{traceback}".format(path=self.path, traceback=formatted_tb) - ) from e - except skip.Exception as e: - if e.allow_module_level: - raise - raise self.CollectError( - "Using pytest.skip outside of a test will skip the entire module. " - "If that's your intention, pass `allow_module_level=True`. " - "If you want to skip a specific test or an entire class, " - "use the @pytest.mark.skip or @pytest.mark.skipif decorators." - ) from e - self.config.pluginmanager.consider_module(mod) - return mod - - -class Package(Module): def __init__( self, fspath: Optional[LEGACY_PATH], @@ -649,13 +644,12 @@ def __init__( config=None, session=None, nodeid=None, - path=Optional[Path], + path: Optional[Path] = None, ) -> None: # NOTE: Could be just the following, but kept as-is for compat. - # nodes.FSCollector.__init__(self, fspath, parent=parent) + # super().__init__(self, fspath, parent=parent) session = parent.session - nodes.FSCollector.__init__( - self, + super().__init__( fspath=fspath, path=path, parent=parent, @@ -663,98 +657,51 @@ def __init__( session=session, nodeid=nodeid, ) - self.name = self.path.parent.name def setup(self) -> None: + init_mod = importtestmodule(self.path / "__init__.py", self.config) + # 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") + init_mod, ("setUpModule", "setup_module") ) if setup_module is not None: - _call_with_optional_argument(setup_module, self.obj) + _call_with_optional_argument(setup_module, init_mod) teardown_module = _get_first_non_fixture_func( - self.obj, ("tearDownModule", "teardown_module") + init_mod, ("tearDownModule", "teardown_module") ) if teardown_module is not None: - func = partial(_call_with_optional_argument, teardown_module, self.obj) + func = partial(_call_with_optional_argument, teardown_module, init_mod) self.addfinalizer(func) - def gethookproxy(self, fspath: "os.PathLike[str]"): - warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) - return self.session.gethookproxy(fspath) - - def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> 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 - fspath = Path(direntry.path) - ihook = self.session.gethookproxy(fspath.parent) - if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): - return False - norecursepatterns = self.config.getini("norecursedirs") - if any(fnmatch_ex(pat, fspath) for pat in norecursepatterns): - return False - return True - - def _collectfile( - self, fspath: Path, handle_dupes: bool = True - ) -> Sequence[nodes.Collector]: - assert ( - fspath.is_file() - ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( - fspath, fspath.is_dir(), fspath.exists(), fspath.is_symlink() - ) - ihook = self.session.gethookproxy(fspath) - if not self.session.isinitpath(fspath): - if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): - return () - - if handle_dupes: - keepduplicates = self.config.getoption("keepduplicates") - if not keepduplicates: - duplicate_paths = self.config.pluginmanager._duplicatepaths - if fspath in duplicate_paths: - return () - else: - duplicate_paths.add(fspath) - - return ihook.pytest_collect_file(file_path=fspath, parent=self) # type: ignore[no-any-return] - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: - this_path = self.path.parent - init_module = this_path / "__init__.py" - if init_module.is_file() and path_matches_patterns( - init_module, self.config.getini("python_files") - ): - yield Module.from_parent(self, path=init_module) - pkg_prefixes: Set[Path] = set() - for direntry in visit(str(this_path), recurse=self._recurse): - path = Path(direntry.path) - - # We will visit our own __init__.py file, in which case we skip it. - if direntry.is_file(): - if direntry.name == "__init__.py" and path.parent == this_path: - continue + # Always collect __init__.py first. + def sort_key(entry: "os.DirEntry[str]") -> object: + return (entry.name != "__init__.py", entry.name) - parts_ = parts(direntry.path) - if any( - str(pkg_prefix) in parts_ and pkg_prefix / "__init__.py" != path - for pkg_prefix in pkg_prefixes - ): - continue - - if direntry.is_file(): - yield from self._collectfile(path) - elif not direntry.is_dir(): - # Broken symlink or invalid/missing file. - continue - elif path.joinpath("__init__.py").is_file(): - pkg_prefixes.add(path) + config = self.config + col: Optional[nodes.Collector] + cols: Sequence[nodes.Collector] + ihook = self.ihook + for direntry in scandir(self.path, sort_key): + if direntry.is_dir(): + path = Path(direntry.path) + if not self.session.isinitpath(path, with_parents=True): + if ihook.pytest_ignore_collect(collection_path=path, config=config): + continue + col = ihook.pytest_collect_directory(path=path, parent=self) + if col is not None: + yield col + + elif direntry.is_file(): + path = Path(direntry.path) + if not self.session.isinitpath(path): + if ihook.pytest_ignore_collect(collection_path=path, config=config): + continue + cols = ihook.pytest_collect_file(file_path=path, parent=self) + yield from cols def _call_with_optional_argument(func, arg) -> None: @@ -771,7 +718,8 @@ def _call_with_optional_argument(func, arg) -> None: def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[object]: """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.""" + xunit-style function, but only if not marked as a fixture to avoid calling it twice. + """ for name in names: meth: Optional[object] = getattr(obj, name, None) if meth is not None and fixtures.getfixturemarker(meth) is None: @@ -780,10 +728,10 @@ def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[o class Class(PyCollector): - """Collector for test methods.""" + """Collector for test methods (and nested classes) in a Python class.""" @classmethod - def from_parent(cls, parent, *, name, obj=None, **kw): + def from_parent(cls, parent, *, name, obj=None, **kw) -> "Self": # type: ignore[override] """The public constructor.""" return super().from_parent(name=name, parent=parent, **kw) @@ -797,9 +745,8 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: assert self.parent is not None self.warn( PytestCollectionWarning( - "cannot collect test class %r because it has a " - "__init__ constructor (from: %s)" - % (self.obj.__name__, self.parent.nodeid) + f"cannot collect test class {self.obj.__name__!r} because it has a " + f"__init__ constructor (from: {self.parent.nodeid})" ) ) return [] @@ -807,105 +754,83 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: assert self.parent is not None self.warn( PytestCollectionWarning( - "cannot collect test class %r because it has a " - "__new__ constructor (from: %s)" - % (self.obj.__name__, self.parent.nodeid) + f"cannot collect test class {self.obj.__name__!r} because it has a " + f"__new__ constructor (from: {self.parent.nodeid})" ) ) return [] - self._inject_setup_class_fixture() - self._inject_setup_method_fixture() + self._register_setup_class_fixture() + self._register_setup_method_fixture() self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid) return super().collect() - def _inject_setup_class_fixture(self) -> None: - """Inject a hidden autouse, class scoped fixture into the collected class object + def _register_setup_class_fixture(self) -> None: + """Register an 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_first_non_fixture_func(self.obj, ("setup_class",)) - teardown_class = getattr(self.obj, "teardown_class", None) + teardown_class = _get_first_non_fixture_func(self.obj, ("teardown_class",)) if setup_class is None and teardown_class is None: return - @fixtures.fixture( - autouse=True, - scope="class", - # Use a unique name to speed up lookup. - name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}", - ) - def xunit_setup_class_fixture(cls) -> Generator[None, None, None]: + def xunit_setup_class_fixture(request) -> Generator[None, None, None]: + cls = request.cls if setup_class is not None: func = getimfunc(setup_class) - _call_with_optional_argument(func, self.obj) + _call_with_optional_argument(func, cls) yield if teardown_class is not None: func = getimfunc(teardown_class) - _call_with_optional_argument(func, self.obj) + _call_with_optional_argument(func, cls) - self.obj.__pytest_setup_class = xunit_setup_class_fixture + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}", + func=xunit_setup_class_fixture, + nodeid=self.nodeid, + scope="class", + autouse=True, + ) - def _inject_setup_method_fixture(self) -> None: - """Inject a hidden autouse, function scoped fixture into the collected class object + def _register_setup_method_fixture(self) -> None: + """Register an 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 + Using a fixture to invoke these methods ensures we play nicely and unsurprisingly with other fixtures (#517). """ - has_nose = self.config.pluginmanager.has_plugin("nose") setup_name = "setup_method" setup_method = _get_first_non_fixture_func(self.obj, (setup_name,)) - if setup_method is None and has_nose: - setup_name = "setup" - setup_method = _get_first_non_fixture_func(self.obj, (setup_name,)) teardown_name = "teardown_method" - teardown_method = getattr(self.obj, teardown_name, None) - if teardown_method is None and has_nose: - teardown_name = "teardown" - teardown_method = getattr(self.obj, teardown_name, None) + teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,)) if setup_method is None and teardown_method is None: return - @fixtures.fixture( - autouse=True, - scope="function", - # Use a unique name to speed up lookup. - name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}", - ) - def xunit_setup_method_fixture(self, request) -> Generator[None, None, None]: + def xunit_setup_method_fixture(request) -> Generator[None, None, None]: + instance = request.instance method = request.function if setup_method is not None: - func = getattr(self, setup_name) + func = getattr(instance, setup_name) _call_with_optional_argument(func, method) yield if teardown_method is not None: - func = getattr(self, teardown_name) + func = getattr(instance, teardown_name) _call_with_optional_argument(func, method) - self.obj.__pytest_setup_method = xunit_setup_method_fixture - - -class InstanceDummy: - """Instance used to be a node type between Class and Function. It has been - removed in pytest 7.0. Some plugins exist which reference `pytest.Instance` - only to ignore it; this dummy class keeps them working. This will be removed - in pytest 8.""" - - pass - - -# Note: module __getattr__ only works on Python>=3.7. Unfortunately -# we can't provide this deprecation warning on Python 3.6. -def __getattr__(name: str) -> object: - if name == "Instance": - warnings.warn(INSTANCE_COLLECTOR, 2) - return InstanceDummy - raise AttributeError(f"module {__name__} has no attribute {name}") + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}", + func=xunit_setup_method_fixture, + nodeid=self.nodeid, + scope="function", + autouse=True, + ) def hasinit(obj: object) -> bool: @@ -923,7 +848,180 @@ def hasnew(obj: object) -> bool: @final -@attr.s(frozen=True, slots=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) +class IdMaker: + """Make IDs for a parametrization.""" + + __slots__ = ( + "argnames", + "parametersets", + "idfn", + "ids", + "config", + "nodeid", + "func_name", + ) + + # The argnames of the parametrization. + argnames: Sequence[str] + # The ParameterSets of the parametrization. + parametersets: Sequence[ParameterSet] + # Optionally, a user-provided callable to make IDs for parameters in a + # ParameterSet. + idfn: Optional[Callable[[Any], Optional[object]]] + # Optionally, explicit IDs for ParameterSets by index. + ids: Optional[Sequence[Optional[object]]] + # Optionally, the pytest config. + # Used for controlling ASCII escaping, and for calling the + # :hook:`pytest_make_parametrize_id` hook. + config: Optional[Config] + # Optionally, the ID of the node being parametrized. + # Used only for clearer error messages. + nodeid: Optional[str] + # Optionally, the ID of the function being parametrized. + # Used only for clearer error messages. + func_name: Optional[str] + + def make_unique_parameterset_ids(self) -> List[str]: + """Make a unique identifier for each ParameterSet, that may be used to + identify the parametrization in a node ID. + + Format is -...-[counter], where prm_x_token is + - user-provided id, if given + - else an id derived from the value, applicable for certain types + - else + The counter suffix is appended only in case a string wouldn't be unique + otherwise. + """ + resolved_ids = list(self._resolve_ids()) + # All IDs must be unique! + if len(resolved_ids) != len(set(resolved_ids)): + # Record the number of occurrences of each ID. + id_counts = Counter(resolved_ids) + # Map the ID to its next suffix. + id_suffixes: Dict[str, int] = defaultdict(int) + # Suffix non-unique IDs to make them unique. + for index, id in enumerate(resolved_ids): + if id_counts[id] > 1: + suffix = "" + if id and id[-1].isdigit(): + suffix = "_" + new_id = f"{id}{suffix}{id_suffixes[id]}" + while new_id in set(resolved_ids): + id_suffixes[id] += 1 + new_id = f"{id}{suffix}{id_suffixes[id]}" + resolved_ids[index] = new_id + id_suffixes[id] += 1 + assert len(resolved_ids) == len( + set(resolved_ids) + ), f"Internal error: {resolved_ids=}" + return resolved_ids + + def _resolve_ids(self) -> Iterable[str]: + """Resolve IDs for all ParameterSets (may contain duplicates).""" + for idx, parameterset in enumerate(self.parametersets): + if parameterset.id is not None: + # ID provided directly - pytest.param(..., id="...") + yield parameterset.id + elif self.ids and idx < len(self.ids) and self.ids[idx] is not None: + # ID provided in the IDs list - parametrize(..., ids=[...]). + yield self._idval_from_value_required(self.ids[idx], idx) + else: + # ID not provided - generate it. + yield "-".join( + self._idval(val, argname, idx) + for val, argname in zip(parameterset.values, self.argnames) + ) + + def _idval(self, val: object, argname: str, idx: int) -> str: + """Make an ID for a parameter in a ParameterSet.""" + idval = self._idval_from_function(val, argname, idx) + if idval is not None: + return idval + idval = self._idval_from_hook(val, argname) + if idval is not None: + return idval + idval = self._idval_from_value(val) + if idval is not None: + return idval + return self._idval_from_argname(argname, idx) + + def _idval_from_function( + self, val: object, argname: str, idx: int + ) -> Optional[str]: + """Try to make an ID for a parameter in a ParameterSet using the + user-provided id callable, if given.""" + if self.idfn is None: + return None + try: + id = self.idfn(val) + except Exception as e: + prefix = f"{self.nodeid}: " if self.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 + if id is None: + return None + return self._idval_from_value(id) + + def _idval_from_hook(self, val: object, argname: str) -> Optional[str]: + """Try to make an ID for a parameter in a ParameterSet by calling the + :hook:`pytest_make_parametrize_id` hook.""" + if self.config: + id: Optional[str] = self.config.hook.pytest_make_parametrize_id( + config=self.config, val=val, argname=argname + ) + return id + return None + + def _idval_from_value(self, val: object) -> Optional[str]: + """Try to make an ID for a parameter in a ParameterSet from its value, + if the value type is supported.""" + if isinstance(val, (str, bytes)): + return _ascii_escaped_by_config(val, self.config) + elif val is None or isinstance(val, (float, int, bool, complex)): + return str(val) + elif isinstance(val, Pattern): + return ascii_escaped(val.pattern) + elif val is NOTSET: + # Fallback to default. Note that NOTSET is an enum.Enum. + pass + elif isinstance(val, enum.Enum): + return str(val) + elif isinstance(getattr(val, "__name__", None), str): + # Name of a class, function, module, etc. + name: str = getattr(val, "__name__") + return name + return None + + def _idval_from_value_required(self, val: object, idx: int) -> str: + """Like _idval_from_value(), but fails if the type is not supported.""" + id = self._idval_from_value(val) + if id is not None: + return id + + # Fail. + if self.func_name is not None: + prefix = f"In {self.func_name}: " + elif self.nodeid is not None: + prefix = f"In {self.nodeid}: " + else: + prefix = "" + msg = ( + f"{prefix}ids contains unsupported value {saferepr(val)} (type: {type(val)!r}) at index {idx}. " + "Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__." + ) + fail(msg, pytrace=False) + + @staticmethod + def _idval_from_argname(argname: str, idx: int) -> str: + """Make an ID for a parameter in a ParameterSet from the argument name + and the index of the ParameterSet.""" + return str(argname) + str(idx) + + +@final +@dataclasses.dataclass(frozen=True) class CallSpec2: """A planned parameterized invocation of a test function. @@ -932,25 +1030,21 @@ class CallSpec2: and stored in item.callspec. """ - # arg name -> arg value which will be passed to the parametrized test - # function (direct parameterization). - funcargs: Dict[str, object] = attr.Factory(dict) - # arg name -> arg value which will be passed to a fixture of the same name - # (indirect parametrization). - params: Dict[str, object] = attr.Factory(dict) + # arg name -> arg value which will be passed to a fixture or pseudo-fixture + # of the same name. (indirect or direct parametrization respectively) + params: Dict[str, object] = dataclasses.field(default_factory=dict) # arg name -> arg index. - indices: Dict[str, int] = attr.Factory(dict) + indices: Dict[str, int] = dataclasses.field(default_factory=dict) # Used for sorting parametrized resources. - _arg2scope: Dict[str, Scope] = attr.Factory(dict) + _arg2scope: Mapping[str, Scope] = dataclasses.field(default_factory=dict) # Parts which will be added to the item's name in `[..]` separated by "-". - _idlist: List[str] = attr.Factory(list) + _idlist: Sequence[str] = dataclasses.field(default_factory=tuple) # Marks which will be applied to the item. - marks: List[Mark] = attr.Factory(list) + marks: List[Mark] = dataclasses.field(default_factory=list) def setmulti( self, *, - valtypes: Mapping[str, "Literal['params', 'funcargs']"], argnames: Iterable[str], valset: Iterable[object], id: str, @@ -958,28 +1052,20 @@ def setmulti( scope: Scope, param_index: int, ) -> "CallSpec2": - funcargs = self.funcargs.copy() params = self.params.copy() indices = self.indices.copy() - arg2scope = self._arg2scope.copy() + arg2scope = dict(self._arg2scope) for arg, val in zip(argnames, valset): - if arg in params or arg in funcargs: - raise ValueError(f"duplicate {arg!r}") - valtype_for_arg = valtypes[arg] - if valtype_for_arg == "params": - params[arg] = val - elif valtype_for_arg == "funcargs": - funcargs[arg] = val - else: - assert_never(valtype_for_arg) + if arg in params: + raise ValueError(f"duplicate parametrization of {arg!r}") + params[arg] = val indices[arg] = param_index arg2scope[arg] = scope return CallSpec2( - funcargs=funcargs, params=params, - arg2scope=arg2scope, indices=indices, - idlist=[*self._idlist, id], + _arg2scope=arg2scope, + _idlist=[*self._idlist, id], marks=[*self.marks, *normalize_mark_list(marks)], ) @@ -994,6 +1080,14 @@ def id(self) -> str: return "-".join(self._idlist) +def get_direct_param_fixture_func(request: FixtureRequest) -> Any: + return request.param + + +# Used for storing pseudo fixturedefs for direct parametrization. +name2pseudofixturedef_key = StashKey[Dict[str, FixtureDef[Any]]]() + + @final class Metafunc: """Objects passed to the :hook:`pytest_generate_tests` hook. @@ -1040,16 +1134,13 @@ def __init__( def parametrize( self, - argnames: Union[str, List[str], Tuple[str, ...]], + argnames: Union[str, Sequence[str]], argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], indirect: Union[bool, Sequence[str]] = False, ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] + Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]] ] = None, - scope: "Optional[_ScopeName]" = None, + scope: Optional[_ScopeName] = None, *, _param_mark: Optional[Mark] = None, ) -> None: @@ -1058,8 +1149,9 @@ def parametrize( during the collection phase. If you need to setup expensive resources see about setting indirect to do it rather than at test setup time. - Can be called multiple times, in which case each call parametrizes all - previous parametrizations, e.g. + Can be called multiple times per test function (but only on different + argument names), in which case each call parametrizes all previous + parametrizations, e.g. :: @@ -1114,7 +1206,7 @@ def parametrize( It will also override any fixture-function defined scope, allowing to set a dynamic scope using test context or configuration. """ - argnames, parameters = ParameterSet._for_parametrize( + argnames, parametersets = ParameterSet._for_parametrize( argnames, argvalues, self.function, @@ -1138,30 +1230,81 @@ def parametrize( self._validate_if_using_arg_names(argnames, indirect) - arg_values_types = self._resolve_arg_value_types(argnames, indirect) - # 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 + ids = self._resolve_parameter_set_ids( + argnames, ids, parametersets, 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) + # Add funcargs as fixturedefs to fixtureinfo.arg2fixturedefs by registering + # artificial "pseudo" FixtureDef's so that later at test execution time we can + # rely on a proper FixtureDef to exist for fixture setup. + node = None + # 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. + if scope_ is not Scope.Function: + collector = self.definition.parent + assert collector is not None + node = get_scope_node(collector, scope_) + if node is None: + # If used class scope and there is no class, use module-level + # collector (for now). + if scope_ is Scope.Class: + assert isinstance(collector, Module) + node = collector + # If used package scope and there is no package, use session + # (for now). + elif scope_ is Scope.Package: + node = collector.session + else: + assert False, f"Unhandled missing scope: {scope}" + if node is None: + name2pseudofixturedef = None + else: + default: Dict[str, FixtureDef[Any]] = {} + name2pseudofixturedef = node.stash.setdefault( + name2pseudofixturedef_key, default + ) + arg_directness = self._resolve_args_directness(argnames, indirect) + for argname in argnames: + if arg_directness[argname] == "indirect": + continue + if name2pseudofixturedef is not None and argname in name2pseudofixturedef: + fixturedef = name2pseudofixturedef[argname] + else: + fixturedef = FixtureDef( + config=self.config, + baseid="", + argname=argname, + func=get_direct_param_fixture_func, + scope=scope_, + params=None, + ids=None, + _ispytest=True, + ) + if name2pseudofixturedef is not None: + name2pseudofixturedef[argname] = fixturedef + self._arg2fixturedefs[argname] = [fixturedef] + # 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. newcalls = [] for callspec in self._calls or [CallSpec2()]: - for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)): + for param_index, (param_id, param_set) in enumerate( + zip(ids, parametersets) + ): newcallspec = callspec.setmulti( - valtypes=arg_values_types, argnames=argnames, valset=param_set.values, id=param_id, @@ -1172,27 +1315,29 @@ def parametrize( newcalls.append(newcallspec) self._calls = newcalls - def _resolve_arg_ids( + def _resolve_parameter_set_ids( self, argnames: Sequence[str], ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] + Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]] ], - parameters: Sequence[ParameterSet], + parametersets: Sequence[ParameterSet], nodeid: str, ) -> List[str]: - """Resolve the actual ids for the given argnames, based on the ``ids`` parameter given - to ``parametrize``. + """Resolve the actual ids for the given parameter sets. - :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] - :returns: The list of ids for each argname given. + :param argnames: + Argument names passed to ``parametrize()``. + :param ids: + The `ids` parameter of the ``parametrize()`` call (see docs). + :param parametersets: + The parameter sets, each containing a set of values corresponding + to ``argnames``. + :param nodeid str: + The nodeid of the definition item that generated this + parametrization. + :returns: + List with ids for each parameter set given. """ if ids is None: idfn = None @@ -1202,15 +1347,24 @@ def _resolve_arg_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) + ids_ = self._validate_ids(ids, parametersets, self.function.__name__) + id_maker = IdMaker( + argnames, + parametersets, + idfn, + ids_, + self.config, + nodeid=nodeid, + func_name=self.function.__name__, + ) + return id_maker.make_unique_parameterset_ids() def _validate_ids( self, - ids: Iterable[Union[None, str, float, int, bool]], - parameters: Sequence[ParameterSet], + ids: Iterable[Optional[object]], + parametersets: Sequence[ParameterSet], func_name: str, - ) -> List[Union[None, str]]: + ) -> List[Optional[object]]: try: num_ids = len(ids) # type: ignore[arg-type] except TypeError: @@ -1218,69 +1372,53 @@ def _validate_ids( iter(ids) except TypeError as e: raise TypeError("ids must be a callable or an iterable") from e - num_ids = len(parameters) + num_ids = len(parametersets) # num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849 - if num_ids != len(parameters) and num_ids != 0: + if num_ids != len(parametersets) 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 + fail(msg.format(func_name, len(parametersets), num_ids), pytrace=False) + + return list(itertools.islice(ids, num_ids)) - def _resolve_arg_value_types( + def _resolve_args_directness( self, argnames: Sequence[str], indirect: Union[bool, 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. + ) -> Dict[str, Literal["indirect", "direct"]]: + """Resolve if each parametrized argument must be considered an indirect + parameter to a fixture of the same name, or a direct parameter to the + parametrized 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. + :param argnames: + List of argument names passed to ``parametrize()``. + :param indirect: + Same as the ``indirect`` parameter of ``parametrize()``. + :returns + A dict mapping each arg name to either "indirect" or "direct". """ + arg_directness: Dict[str, Literal["indirect", "direct"]] if isinstance(indirect, bool): - valtypes: Dict[str, Literal["params", "funcargs"]] = dict.fromkeys( - argnames, "params" if indirect else "funcargs" + arg_directness = dict.fromkeys( + argnames, "indirect" if indirect else "direct" ) elif isinstance(indirect, Sequence): - valtypes = dict.fromkeys(argnames, "funcargs") + arg_directness = dict.fromkeys(argnames, "direct") for arg in indirect: if arg not in argnames: fail( - "In {}: indirect fixture '{}' doesn't exist".format( - self.function.__name__, arg - ), + f"In {self.function.__name__}: indirect fixture '{arg}' doesn't exist", pytrace=False, ) - valtypes[arg] = "params" + arg_directness[arg] = "indirect" else: fail( - "In {func}: expected Sequence or boolean for indirect, got {type}".format( - type=type(indirect).__name__, func=self.function.__name__ - ), + f"In {self.function.__name__}: expected Sequence or boolean" + f" for indirect, got {type(indirect).__name__}", pytrace=False, ) - return valtypes + return arg_directness def _validate_if_using_arg_names( self, @@ -1299,9 +1437,7 @@ def _validate_if_using_arg_names( if arg not in self.fixturenames: if arg in default_arg_names: fail( - "In {}: function already takes an argument '{}' with a default value".format( - func_name, arg - ), + f"In {func_name}: function already takes an argument '{arg}' with a default value", pytrace=False, ) else: @@ -1337,7 +1473,7 @@ def _find_parametrized_scope( if all_arguments_are_fixtures: fixturedefs = arg2fixturedefs or {} used_scopes = [ - fixturedef[0]._scope + fixturedef[-1]._scope for name, fixturedef in fixturedefs.items() if name in argnames ] @@ -1360,239 +1496,8 @@ def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) - 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: - prefix = f"{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: Optional[str] = config.hook.pytest_make_parametrize_id( - config=config, val=val, argname=argname - ) - if hook_id: - return hook_id - - if isinstance(val, STRING_TYPES): - return _ascii_escaped_by_config(val, config) - elif val is None or isinstance(val, (float, int, bool, complex)): - return str(val) - elif isinstance(val, Pattern): - return ascii_escaped(val.pattern) - elif val is NOTSET: - # Fallback to default. Note that NOTSET is an enum.Enum. - pass - elif isinstance(val, enum.Enum): - return str(val) - elif isinstance(getattr(val, "__name__", None), str): - # Name of a class, function, module, etc. - name: str = getattr(val, "__name__") - return name - return str(argname) + str(idx) - - -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 - id = None if ids is None or idx >= len(ids) else ids[idx] - if id is None: - this_id = [ - _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(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) - ] - - # 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: Dict[str, int] = defaultdict(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] = f"{test_id}{test_id_suffixes[test_id]}" - test_id_suffixes[test_id] += 1 - - return resolved_ids - - -def _pretty_fixture_path(func) -> str: - cwd = Path.cwd() - loc = Path(getlocation(func, str(cwd))) - prefix = Path("...", "_pytest") - try: - return str(prefix / loc.relative_to(_PYTEST_DIR)) - except ValueError: - return bestrelpath(cwd, loc) - - -def show_fixtures_per_test(config): - from _pytest.main import wrap_session - - return wrap_session(config, _show_fixtures_per_test) - - -def _show_fixtures_per_test(config: Config, session: Session) -> None: - import _pytest.config - - session.perform_collect() - curdir = Path.cwd() - tw = _pytest.config.create_terminal_writer(config) - verbose = config.getvalue("verbose") - - def get_best_relpath(func) -> str: - loc = getlocation(func, str(curdir)) - return bestrelpath(curdir, Path(loc)) - - def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None: - argname = fixture_def.argname - if verbose <= 0 and argname.startswith("_"): - return - prettypath = _pretty_fixture_path(fixture_def.func) - tw.write(f"{argname}", green=True) - tw.write(f" -- {prettypath}", yellow=True) - tw.write("\n") - fixture_doc = inspect.getdoc(fixture_def.func) - if fixture_doc: - write_docstring( - tw, fixture_doc.split("\n\n")[0] if verbose <= 0 else fixture_doc - ) - else: - tw.line(" no docstring available", red=True) - - def write_item(item: nodes.Item) -> None: - # Not all items have _fixtureinfo attribute. - info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None) - if info is None or not info.name2fixturedefs: - # This test item does not use any fixtures. - return - tw.line() - tw.sep("-", f"fixtures used by {item.name}") - # TODO: Fix this type ignore. - tw.sep("-", f"({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. - write_fixture(fixturedefs[-1]) - - for session_item in session.items: - write_item(session_item) - - -def showfixtures(config: Config) -> Union[int, ExitCode]: - from _pytest.main import wrap_session - - return wrap_session(config, _showfixtures_main) - - -def _showfixtures_main(config: Config, session: Session) -> None: - import _pytest.config - - session.perform_collect() - curdir = Path.cwd() - tw = _pytest.config.create_terminal_writer(config) - verbose = config.getvalue("verbose") - - fm = session._fixturemanager - - available = [] - seen: Set[Tuple[str, str]] = set() - - for argname, fixturedefs in fm._arg2fixturedefs.items(): - assert fixturedefs is not None - if not fixturedefs: - continue - for fixturedef in fixturedefs: - loc = getlocation(fixturedef.func, str(curdir)) - if (fixturedef.argname, loc) in seen: - continue - seen.add((fixturedef.argname, loc)) - available.append( - ( - len(fixturedef.baseid), - fixturedef.func.__module__, - _pretty_fixture_path(fixturedef.func), - fixturedef.argname, - fixturedef, - ) - ) - - available.sort() - currentmodule = None - for baseid, module, prettypath, argname, fixturedef in available: - if currentmodule != module: - if not module.startswith("_pytest."): - tw.line() - tw.sep("-", f"fixtures defined from {module}") - currentmodule = module - if verbose <= 0 and argname.startswith("_"): - continue - tw.write(f"{argname}", green=True) - if fixturedef.scope != "function": - tw.write(" [%s scope]" % fixturedef.scope, cyan=True) - tw.write(f" -- {prettypath}", yellow=True) - tw.write("\n") - doc = inspect.getdoc(fixturedef.func) - if doc: - write_docstring(tw, doc.split("\n\n")[0] if verbose <= 0 else doc) - else: - tw.line(" no docstring available", red=True) - tw.line() - - -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. + """Item responsible for setting up and executing a Python test function. :param name: The full function name, including any decorations like those @@ -1602,7 +1507,7 @@ class Function(PyobjMixin, nodes.Item): :param config: The pytest Config object. :param callspec: - If given, this is function has been parametrized and the callspec contains + If given, this 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, @@ -1630,7 +1535,7 @@ def __init__( config: Optional[Config] = None, callspec: Optional[CallSpec2] = None, callobj=NOTSET, - keywords=None, + keywords: Optional[Mapping[str, Any]] = None, session: Optional[Session] = None, fixtureinfo: Optional[FuncFixtureInfo] = None, originalname: Optional[str] = None, @@ -1638,7 +1543,8 @@ def __init__( super().__init__(name, parent, config=config, session=session) if callobj is not NOTSET: - self.obj = callobj + self._obj = callobj + self._instance = getattr(callobj, "__self__", None) #: Original function name, without any decorations (for example #: parametrization adds a ``"[...]"`` suffix to function names), used to access @@ -1651,60 +1557,68 @@ def __init__( # 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: self.callspec = callspec - # this is total hostile and a mess - # keywords are broken by design by now - # this will be redeemed later - for mark in callspec.marks: - # feel free to cry, this was broken for years before - # and keywords can't fix it per design - self.keywords[mark.name] = mark - self.own_markers.extend(normalize_mark_list(callspec.marks)) - if keywords: - self.keywords.update(keywords) + self.own_markers.extend(callspec.marks) # todo: this is a hell of a hack # https://github.com/pytest-dev/pytest/issues/4569 - - self.keywords.update( - { - mark.name: True - for mark in self.iter_markers() - if mark.name not in self.keywords - } - ) + # Note: the order of the updates is important here; indicates what + # takes priority (ctor argument over function attributes over markers). + # Take own_markers only; NodeKeywords handles parent traversal on its own. + self.keywords.update((mark.name, mark) for mark in self.own_markers) + self.keywords.update(self.obj.__dict__) + if keywords: + self.keywords.update(keywords) if fixtureinfo is None: - fixtureinfo = self.session._fixturemanager.getfixtureinfo( - self, self.obj, self.cls, funcargs=True - ) + fm = self.session._fixturemanager + fixtureinfo = fm.getfixtureinfo(self, self.obj, self.cls) self._fixtureinfo: FuncFixtureInfo = fixtureinfo self.fixturenames = fixtureinfo.names_closure self._initrequest() + # todo: determine sound type limitations @classmethod - def from_parent(cls, parent, **kw): # todo: determine sound type limitations + def from_parent(cls, parent, **kw) -> "Self": """The public constructor.""" return super().from_parent(parent=parent, **kw) def _initrequest(self) -> None: self.funcargs: Dict[str, object] = {} - self._request = fixtures.FixtureRequest(self, _ispytest=True) + self._request = fixtures.TopRequest(self, _ispytest=True) @property def function(self): """Underlying python 'function' object.""" return getimfunc(self.obj) - def _getobj(self): - assert self.parent is not None + @property + def instance(self): + try: + return self._instance + except AttributeError: + if isinstance(self.parent, Class): + # Each Function gets a fresh class instance. + self._instance = self._getinstance() + else: + self._instance = None + return self._instance + + def _getinstance(self): if isinstance(self.parent, Class): # Each Function gets a fresh class instance. - parent_obj = self.parent.newinstance() + return self.parent.newinstance() + else: + return None + + def _getobj(self): + instance = self.instance + if instance is not None: + parent_obj = instance else: + assert self.parent is not None parent_obj = self.parent.obj # type: ignore[attr-defined] return getattr(parent_obj, self.originalname) @@ -1720,7 +1634,7 @@ def runtest(self) -> None: def setup(self) -> None: self._request._fillfixtures() - def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: + def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False): code = _pytest._code.Code.from_function(get_real_func(self.obj)) path, firstlineno = code.path, code.firstlineno @@ -1732,14 +1646,22 @@ def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: ntraceback = ntraceback.filter(filter_traceback) if not ntraceback: ntraceback = traceback + ntraceback = ntraceback.filter(excinfo) - 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") + if len(ntraceback) > 2: + ntraceback = Traceback( + ( + ntraceback[0], + *(t.with_repr_style("short") for t in ntraceback[1:-1]), + ntraceback[-1], + ) + ) + + return ntraceback + return excinfo.traceback # TODO: Type ignored -- breaks Liskov Substitution. def repr_failure( # type: ignore[override] @@ -1753,10 +1675,8 @@ def repr_failure( # type: ignore[override] class FunctionDefinition(Function): - """ - This class is a step gap solution until we evolve to have actual function definition nodes - and manage to get rid of ``metafunc``. - """ + """This class is a stop gap solution until we evolve to have actual function + definition nodes and manage to get rid of ``metafunc``.""" def runtest(self) -> None: raise RuntimeError("function definitions are not supposed to be run as tests") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/python_api.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/python_api.py index cb72fde1e1f75..7d89fdd809ee3 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/python_api.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/python_api.py @@ -1,14 +1,16 @@ -import math -import pprint +# mypy: allow-untyped-defs +from collections.abc import Collection from collections.abc import Sized from decimal import Decimal +import math from numbers import Complex +import pprint from types import TracebackType from typing import Any from typing import Callable from typing import cast -from typing import Generic -from typing import Iterable +from typing import ContextManager +from typing import final from typing import List from typing import Mapping from typing import Optional @@ -21,23 +23,12 @@ from typing import TypeVar from typing import Union -if TYPE_CHECKING: - from numpy import ndarray - - import _pytest._code -from _pytest.compat import final -from _pytest.compat import STRING_TYPES from _pytest.outcomes import fail -def _non_numeric_type_error(value, at: Optional[str]) -> TypeError: - at_str = f" at {at}" if at else "" - return TypeError( - "cannot make approximate comparisons to non-numeric values: {!r} {}".format( - value, at_str - ) - ) +if TYPE_CHECKING: + from numpy import ndarray def _compare_approx( @@ -131,12 +122,13 @@ def _check_type(self) -> None: # a numeric type. For this reason, the default is to do nothing. The # classes that deal with sequences should reimplement this method to # raise if there are any non-numeric elements in the sequence. - pass -def _recursive_list_map(f, x): - if isinstance(x, list): - return [_recursive_list_map(f, xi) for xi in x] +def _recursive_sequence_map(f, x): + """Recursively map a function over a sequence of arbitrary depth""" + if isinstance(x, (list, tuple)): + seq_type = type(x) + return seq_type(_recursive_sequence_map(f, xi) for xi in x) else: return f(x) @@ -145,10 +137,12 @@ class ApproxNumpy(ApproxBase): """Perform approximate comparisons where the expected value is numpy array.""" def __repr__(self) -> str: - list_scalars = _recursive_list_map(self._approx_scalar, self.expected.tolist()) + list_scalars = _recursive_sequence_map( + self._approx_scalar, self.expected.tolist() + ) return f"approx({list_scalars!r})" - def _repr_compare(self, other_side: "ndarray") -> List[str]: + def _repr_compare(self, other_side: Union["ndarray", List[Any]]) -> List[str]: import itertools import math @@ -165,14 +159,18 @@ def get_value_from_nested_list( return value np_array_shape = self.expected.shape - approx_side_as_list = _recursive_list_map( + approx_side_as_seq = _recursive_sequence_map( self._approx_scalar, self.expected.tolist() ) - if np_array_shape != other_side.shape: + # convert other_side to numpy array to ensure shape attribute is available + other_side_as_array = _as_numpy_array(other_side) + assert other_side_as_array is not None + + if np_array_shape != other_side_as_array.shape: return [ "Impossible to compare arrays with different shapes.", - f"Shapes: {np_array_shape} and {other_side.shape}", + f"Shapes: {np_array_shape} and {other_side_as_array.shape}", ] number_of_elements = self.expected.size @@ -180,8 +178,8 @@ def get_value_from_nested_list( max_rel_diff = -math.inf different_ids = [] for index in itertools.product(*(range(i) for i in np_array_shape)): - approx_value = get_value_from_nested_list(approx_side_as_list, index) - other_value = get_value_from_nested_list(other_side, index) + approx_value = get_value_from_nested_list(approx_side_as_seq, index) + other_value = get_value_from_nested_list(other_side_as_array, index) if approx_value != other_value: abs_diff = abs(approx_value.expected - other_value) max_abs_diff = max(max_abs_diff, abs_diff) @@ -194,8 +192,8 @@ def get_value_from_nested_list( message_data = [ ( str(index), - str(get_value_from_nested_list(other_side, index)), - str(get_value_from_nested_list(approx_side_as_list, index)), + str(get_value_from_nested_list(other_side_as_array, index)), + str(get_value_from_nested_list(approx_side_as_seq, index)), ) for index in different_ids ] @@ -244,9 +242,7 @@ class ApproxMapping(ApproxBase): with numeric values (the keys can be anything).""" def __repr__(self) -> str: - return "approx({!r})".format( - {k: self._approx_scalar(v) for k, v in self.expected.items()} - ) + return f"approx({({k: self._approx_scalar(v) for k, v in self.expected.items()})!r})" def _repr_compare(self, other_side: Mapping[object, float]) -> List[str]: import math @@ -263,13 +259,20 @@ def _repr_compare(self, other_side: Mapping[object, float]) -> List[str]: approx_side_as_map.items(), other_side.values() ): if approx_value != other_value: - max_abs_diff = max( - max_abs_diff, abs(approx_value.expected - other_value) - ) - max_rel_diff = max( - max_rel_diff, - abs((approx_value.expected - other_value) / approx_value.expected), - ) + if approx_value.expected is not None and other_value is not None: + max_abs_diff = max( + max_abs_diff, abs(approx_value.expected - other_value) + ) + if approx_value.expected == 0.0: + max_rel_diff = math.inf + else: + max_rel_diff = max( + max_rel_diff, + abs( + (approx_value.expected - other_value) + / approx_value.expected + ), + ) different_ids.append(approx_key) message_data = [ @@ -307,20 +310,17 @@ def _check_type(self) -> None: raise TypeError(msg.format(key, value, pprint.pformat(self.expected))) -class ApproxSequencelike(ApproxBase): +class ApproxSequenceLike(ApproxBase): """Perform approximate comparisons where the expected value is a sequence of numbers.""" def __repr__(self) -> str: seq_type = type(self.expected) - if seq_type not in (tuple, list, set): + if seq_type not in (tuple, list): seq_type = list - return "approx({!r})".format( - seq_type(self._approx_scalar(x) for x in self.expected) - ) + return f"approx({seq_type(self._approx_scalar(x) for x in self.expected)!r})" def _repr_compare(self, other_side: Sequence[float]) -> List[str]: import math - import numpy as np if len(self.expected) != len(other_side): return [ @@ -328,7 +328,7 @@ def _repr_compare(self, other_side: Sequence[float]) -> List[str]: f"Lengths: {len(self.expected)} and {len(other_side)}", ] - approx_side_as_map = _recursive_list_map(self._approx_scalar, self.expected) + approx_side_as_map = _recursive_sequence_map(self._approx_scalar, self.expected) number_of_elements = len(approx_side_as_map) max_abs_diff = -math.inf @@ -341,7 +341,7 @@ def _repr_compare(self, other_side: Sequence[float]) -> List[str]: abs_diff = abs(approx_value.expected - other_value) max_abs_diff = max(max_abs_diff, abs_diff) if other_value == 0.0: - max_rel_diff = np.inf + max_rel_diff = math.inf else: max_rel_diff = max(max_rel_diff, abs_diff / abs(other_value)) different_ids.append(i) @@ -397,7 +397,7 @@ def __repr__(self) -> str: # tolerances, i.e. non-numerics and infinities. Need to call abs to # handle complex numbers, e.g. (inf + 1j). if (not isinstance(self.expected, (Complex, Decimal))) or math.isinf( - abs(self.expected) # type: ignore[arg-type] + abs(self.expected) ): return str(self.expected) @@ -441,8 +441,8 @@ def __eq__(self, actual) -> bool: # Allow the user to control whether NaNs are considered equal to each # other or not. The abs() calls are for compatibility with complex # numbers. - if math.isnan(abs(self.expected)): # type: ignore[arg-type] - return self.nan_ok and math.isnan(abs(actual)) # type: ignore[arg-type] + if math.isnan(abs(self.expected)): + return self.nan_ok and math.isnan(abs(actual)) # Infinity shouldn't be approximately equal to anything but itself, but # if there's a relative tolerance, it will be infinite and infinity @@ -450,11 +450,11 @@ def __eq__(self, actual) -> bool: # case would have been short circuited above, so here we can just # return false if the expected value is infinite. The abs() call is # for compatibility with complex numbers. - if math.isinf(abs(self.expected)): # type: ignore[arg-type] + if math.isinf(abs(self.expected)): return False # Return true if the two numbers are within the tolerance. - result: bool = abs(self.expected - actual) <= self.tolerance + result: bool = abs(self.expected - actual) <= self.tolerance # type: ignore[arg-type] return result # Ignore type because of https://github.com/python/mypy/issues/4266. @@ -516,10 +516,10 @@ class ApproxDecimal(ApproxScalar): 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 + """Assert that two numbers (or two ordered sequences of numbers) are equal to each other within some tolerance. - Due to the :std:doc:`tutorial/floatingpoint`, numbers that we + Due to the :doc:`python:tutorial/floatingpoint`, numbers that we would intuitively expect to be equal are not always so:: >>> 0.1 + 0.2 == 0.3 @@ -548,16 +548,11 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: >>> 0.1 + 0.2 == approx(0.3) True - The same syntax also works for sequences of numbers:: + The same syntax also works for ordered sequences of numbers:: >>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6)) True - Dictionary *values*:: - - >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6}) - True - ``numpy`` arrays:: >>> import numpy as np # doctest: +SKIP @@ -570,6 +565,20 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: >>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3) # doctest: +SKIP True + Only ordered sequences are supported, because ``approx`` needs + to infer the relative position of the sequences without ambiguity. This means + ``sets`` and other unordered sequences are not supported. + + Finally, dictionary *values* can also be compared:: + + >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6}) + True + + The comparison will be true if both mappings have the same keys and their + respective values match the expected tolerances. + + **Tolerances** + By default, ``approx`` considers numbers within a relative tolerance of ``1e-6`` (i.e. one part in a million) of its expected value to be equal. This treatment would lead to surprising results if the expected value was @@ -659,6 +668,11 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: specialised test helpers in :std:doc:`numpy:reference/routines.testing` if you need support for comparisons, NaNs, or ULP-based tolerances. + To match strings using regex, you can use + `Matches `_ + from the + `re_assert package `_. + .. warning:: .. versionchanged:: 3.2 @@ -683,7 +697,6 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: ``approx`` falls back to strict equality for nonnumeric types instead of raising ``TypeError``. """ - # Delegate the comparison to a class that knows how to deal with the type # of the expected value (e.g. int, float, list, dict, numpy.array, etc). # @@ -709,12 +722,14 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: expected = _as_numpy_array(expected) cls = ApproxNumpy elif ( - isinstance(expected, Iterable) + hasattr(expected, "__getitem__") and isinstance(expected, Sized) - # Type ignored because the error is wrong -- not unreachable. - and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable] + and not isinstance(expected, (str, bytes)) ): - cls = ApproxSequencelike + cls = ApproxSequenceLike + elif isinstance(expected, Collection) and not isinstance(expected, (str, bytes)): + msg = f"pytest.approx() only supports ordered sequences, but got: {expected!r}" + raise TypeError(msg) else: cls = ApproxScalar @@ -758,8 +773,7 @@ def raises( expected_exception: Union[Type[E], Tuple[Type[E], ...]], *, match: Optional[Union[str, Pattern[str]]] = ..., -) -> "RaisesContext[E]": - ... +) -> "RaisesContext[E]": ... @overload @@ -768,38 +782,41 @@ def raises( func: Callable[..., Any], *args: Any, **kwargs: Any, -) -> _pytest._code.ExceptionInfo[E]: - ... +) -> _pytest._code.ExceptionInfo[E]: ... def raises( 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. + r"""Assert that a code block/function call raises an exception type, or one of its subclasses. + + :param expected_exception: + The expected exception type, or a tuple if one of multiple possible + exception types are expected. Note that subclasses of the passed exceptions + will also match. - :kwparam match: + :kwparam str | re.Pattern[str] | None 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 :py:func:`re.search`. To match a literal - string that may contain :std:ref:`special characters `, the pattern can - first be escaped with :py:func:`re.escape`. + representation of the exception and its :pep:`678` `__notes__` + using :func:`re.search`. - (This is only used when :py:func:`pytest.raises` is used as a context manager, + To match a literal string that may contain :ref:`special characters + `, the pattern can first be escaped with :func:`re.escape`. + + (This is only used when ``pytest.raises`` is used as a context manager, and passed through to the function otherwise. - When using :py:func:`pytest.raises` as a function, you can use: + When using ``pytest.raises`` as a function, you can use: ``pytest.raises(Exc, func, match="passed on").match("my pattern")``.) - .. currentmodule:: _pytest._code - Use ``pytest.raises`` as a context manager, which will capture the exception of the given - type:: + type, or any of its subclasses:: >>> import pytest >>> with pytest.raises(ZeroDivisionError): ... 1/0 - If the code block does not raise the expected exception (``ZeroDivisionError`` in the example + If the code block does not raise the expected exception (:class:`ZeroDivisionError` in the example above), or no exception at all, the check will fail instead. You can also use the keyword argument ``match`` to assert that the @@ -811,6 +828,14 @@ def raises( >>> with pytest.raises(ValueError, match=r'must be \d+$'): ... raise ValueError("value must be 42") + The ``match`` argument searches the formatted exception string, which includes any + `PEP-678 `__ ``__notes__``: + + >>> with pytest.raises(ValueError, match=r"had a note added"): # doctest: +SKIP + ... e = ValueError("value must be 42") + ... e.add_note("had a note added") + ... raise e + The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the details of the captured exception:: @@ -819,6 +844,20 @@ def raises( >>> assert exc_info.type is ValueError >>> assert exc_info.value.args[0] == "value must be 42" + .. warning:: + + Given that ``pytest.raises`` matches subclasses, be wary of using it to match :class:`Exception` like this:: + + with pytest.raises(Exception): # Careful, this will catch ANY exception raised. + some_function() + + Because :class:`Exception` is the base class of almost all exceptions, it is easy for this to hide + real bugs, where the user wrote this expecting a specific exception, but some other exception is being + raised due to a bug introduced during a refactoring. + + Avoid using ``pytest.raises`` to catch :class:`Exception` unless certain that you really want to catch + **any** exception raised. + .. note:: When using ``pytest.raises`` as a context manager, it's worthwhile to @@ -831,7 +870,7 @@ def raises( >>> with pytest.raises(ValueError) as exc_info: ... if value > 10: ... raise ValueError("value must be <= 10") - ... assert exc_info.type is ValueError # this will not execute + ... assert exc_info.type is ValueError # This will not execute. Instead, the following approach must be taken (note the difference in scope):: @@ -850,6 +889,10 @@ def raises( See :ref:`parametrizing_conditional_raising` for an example. + .. seealso:: + + :ref:`assertraises` for more examples and detailed discussion. + **Legacy form** It is possible to specify a callable by passing a to-be-called lambda:: @@ -885,11 +928,17 @@ def raises( """ __tracebackhide__ = True + if not expected_exception: + raise ValueError( + f"Expected an exception type or a tuple of exception types, but got `{expected_exception!r}`. " + f"Raising exceptions is already understood as failing the test, so you don't need " + f"any special code to say 'this should never raise an exception'." + ) if isinstance(expected_exception, type): - excepted_exceptions: Tuple[Type[E], ...] = (expected_exception,) + expected_exceptions: Tuple[Type[E], ...] = (expected_exception,) else: - excepted_exceptions = expected_exception - for exc in excepted_exceptions: + expected_exceptions = expected_exception + for exc in expected_exceptions: if not isinstance(exc, type) or not issubclass(exc, BaseException): msg = "expected exception must be a BaseException type, not {}" # type: ignore[unreachable] not_a = exc.__name__ if isinstance(exc, type) else type(exc).__name__ @@ -912,11 +961,7 @@ def raises( try: func(*args[1:], **kwargs) 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__) - ) + return _pytest._code.ExceptionInfo.from_exception(e) fail(message) @@ -925,7 +970,7 @@ def raises( @final -class RaisesContext(Generic[E]): +class RaisesContext(ContextManager[_pytest._code.ExceptionInfo[E]]): def __init__( self, expected_exception: Union[Type[E], Tuple[Type[E], ...]], diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/recwarn.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/recwarn.py index 175b571a80c4f..63e7a4bd6dc98 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/recwarn.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/recwarn.py @@ -1,9 +1,12 @@ +# mypy: allow-untyped-defs """Record warnings during test function execution.""" + +from pprint import pformat import re -import warnings from types import TracebackType from typing import Any from typing import Callable +from typing import final from typing import Generator from typing import Iterator from typing import List @@ -14,11 +17,11 @@ from typing import Type from typing import TypeVar from typing import Union +import warnings -from _pytest.compat import final from _pytest.deprecated import check_ispytest -from _pytest.deprecated import WARNS_NONE_ARG from _pytest.fixtures import fixture +from _pytest.outcomes import Exit from _pytest.outcomes import fail @@ -29,7 +32,7 @@ def recwarn() -> Generator["WarningsRecorder", None, None]: """Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. - See https://docs.python.org/library/how-to/capture-warnings.html for information + See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information on warning categories. """ wrec = WarningsRecorder(_ispytest=True) @@ -41,19 +44,17 @@ def recwarn() -> Generator["WarningsRecorder", None, None]: @overload def deprecated_call( *, match: Optional[Union[str, Pattern[str]]] = ... -) -> "WarningsRecorder": - ... +) -> "WarningsRecorder": ... @overload -def deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) -> T: - ... +def deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) -> T: ... def deprecated_call( func: Optional[Callable[..., Any]] = None, *args: Any, **kwargs: Any ) -> Union["WarningsRecorder", Any]: - """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning``. + """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning`` or ``FutureWarning``. This function can be used as a context manager:: @@ -78,8 +79,10 @@ def deprecated_call( """ __tracebackhide__ = True if func is not None: - args = (func,) + args - return warns((DeprecationWarning, PendingDeprecationWarning), *args, **kwargs) + args = (func, *args) + return warns( + (DeprecationWarning, PendingDeprecationWarning, FutureWarning), *args, **kwargs + ) @overload @@ -87,8 +90,7 @@ def warns( expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = ..., *, match: Optional[Union[str, Pattern[str]]] = ..., -) -> "WarningsChecker": - ... +) -> "WarningsChecker": ... @overload @@ -97,8 +99,7 @@ def warns( func: Callable[..., T], *args: Any, **kwargs: Any, -) -> T: - ... +) -> T: ... def warns( @@ -109,15 +110,15 @@ def warns( ) -> Union["WarningsChecker", Any]: r"""Assert that code raises a particular class of warning. - Specifically, the parameter ``expected_warning`` can be a warning class or - sequence of warning classes, and the inside the ``with`` block must issue a warning of that class or - classes. + Specifically, the parameter ``expected_warning`` can be a warning class or tuple + of warning classes, and the code inside the ``with`` block must issue at least one + warning of that class or classes. - This helper produces a list of :class:`warnings.WarningMessage` objects, - one for each warning raised. + This helper produces a list of :class:`warnings.WarningMessage` objects, one for + each warning emitted (regardless of whether it is an ``expected_warning`` or not). + Since pytest 8.0, unmatched warnings are also re-emitted when the context closes. - This function can be used as a context manager, or any of the other ways - :func:`pytest.raises` can be used:: + This function can be used as a context manager:: >>> import pytest >>> with pytest.warns(RuntimeWarning): @@ -132,20 +133,30 @@ def warns( >>> with pytest.warns(UserWarning, match=r'must be \d+$'): ... warnings.warn("value must be 42", UserWarning) - >>> with pytest.warns(UserWarning, match=r'must be \d+$'): - ... warnings.warn("this is not here", UserWarning) + >>> with pytest.warns(UserWarning): # catch re-emitted warning + ... with pytest.warns(UserWarning, match=r'must be \d+$'): + ... warnings.warn("this is not here", UserWarning) Traceback (most recent call last): ... Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted... + **Using with** ``pytest.mark.parametrize`` + + When using :ref:`pytest.mark.parametrize ref` it is possible to parametrize tests + such that some runs raise a warning and others do not. + + This could be achieved in the same way as with exceptions, see + :ref:`parametrizing_conditional_raising` for an example. + """ __tracebackhide__ = True if not args: if kwargs: - msg = "Unexpected keyword arguments passed to pytest.warns: " - msg += ", ".join(sorted(kwargs)) - msg += "\nUse context-manager form instead?" - raise TypeError(msg) + argnames = ", ".join(sorted(kwargs)) + raise TypeError( + f"Unexpected keyword arguments passed to pytest.warns: {argnames}" + "\nUse context-manager form instead?" + ) return WarningsChecker(expected_warning, match_expr=match, _ispytest=True) else: func = args[0] @@ -155,16 +166,22 @@ def warns( return func(*args[1:], **kwargs) -class WarningsRecorder(warnings.catch_warnings): +class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg] """A context manager to record raised warnings. + Each recorded warning is an instance of :class:`warnings.WarningMessage`. + Adapted from `warnings.catch_warnings`. + + .. note:: + ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated + differently; see :ref:`ensuring_function_triggers`. + """ def __init__(self, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) - # Type ignored due to the way typeshed handles warnings.catch_warnings. - super().__init__(record=True) # type: ignore[call-arg] + super().__init__(record=True) self._entered = False self._list: List[warnings.WarningMessage] = [] @@ -186,12 +203,23 @@ def __len__(self) -> int: return len(self._list) def pop(self, cls: Type[Warning] = Warning) -> "warnings.WarningMessage": - """Pop the first recorded warning, raise exception if not exists.""" + """Pop the first recorded warning which is an instance of ``cls``, + but not an instance of a child class of any other match. + Raises ``AssertionError`` if there is no match. + """ + best_idx: Optional[int] = None for i, w in enumerate(self._list): - if issubclass(w.category, cls): - return self._list.pop(i) + if w.category == cls: + return self._list.pop(i) # exact match, stop looking + if issubclass(w.category, cls) and ( + best_idx is None + or not issubclass(w.category, self._list[best_idx].category) + ): + best_idx = i + if best_idx is not None: + return self._list.pop(best_idx) __tracebackhide__ = True - raise AssertionError("%r not found in warning list" % cls) + raise AssertionError(f"{cls!r} not found in warning list") def clear(self) -> None: """Clear the list of recorded warnings.""" @@ -202,7 +230,7 @@ def clear(self) -> None: def __enter__(self) -> "WarningsRecorder": # type: ignore if self._entered: __tracebackhide__ = True - raise RuntimeError("Cannot enter %r twice" % self) + raise RuntimeError(f"Cannot enter {self!r} twice") _list = super().__enter__() # record=True means it's None. assert _list is not None @@ -218,7 +246,7 @@ def __exit__( ) -> None: if not self._entered: __tracebackhide__ = True - raise RuntimeError("Cannot exit %r without entering first" % self) + raise RuntimeError(f"Cannot exit {self!r} without entering first") super().__exit__(exc_type, exc_val, exc_tb) @@ -231,9 +259,7 @@ def __exit__( class WarningsChecker(WarningsRecorder): def __init__( self, - expected_warning: Optional[ - Union[Type[Warning], Tuple[Type[Warning], ...]] - ] = Warning, + expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning, match_expr: Optional[Union[str, Pattern[str]]] = None, *, _ispytest: bool = False, @@ -242,15 +268,14 @@ def __init__( super().__init__(_ispytest=True) msg = "exceptions must be derived from Warning, not %s" - if expected_warning is None: - warnings.warn(WARNS_NONE_ARG, stacklevel=4) - expected_warning_tup = None - elif isinstance(expected_warning, tuple): + if isinstance(expected_warning, tuple): for exc in expected_warning: if not issubclass(exc, Warning): raise TypeError(msg % type(exc)) expected_warning_tup = expected_warning - elif issubclass(expected_warning, Warning): + elif isinstance(expected_warning, type) and issubclass( + expected_warning, Warning + ): expected_warning_tup = (expected_warning,) else: raise TypeError(msg % type(expected_warning)) @@ -258,6 +283,12 @@ def __init__( self.expected_warning = expected_warning_tup self.match_expr = match_expr + def matches(self, warning: warnings.WarningMessage) -> bool: + assert self.expected_warning is not None + return issubclass(warning.category, self.expected_warning) and bool( + self.match_expr is None or re.search(self.match_expr, str(warning.message)) + ) + def __exit__( self, exc_type: Optional[Type[BaseException]], @@ -268,29 +299,68 @@ def __exit__( __tracebackhide__ = True - # only check if we're not currently handling an exception - 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 - fail( - "DID NOT WARN. No warnings of type {} were emitted. " - "The list of emitted warnings is: {}.".format( - self.expected_warning, [each.message for each in self] - ) + # BaseExceptions like pytest.{skip,fail,xfail,exit} or Ctrl-C within + # pytest.warns should *not* trigger "DID NOT WARN" and get suppressed + # when the warning doesn't happen. Control-flow exceptions should always + # propagate. + if exc_val is not None and ( + not isinstance(exc_val, Exception) + # Exit is an Exception, not a BaseException, for some reason. + or isinstance(exc_val, Exit) + ): + return + + def found_str() -> str: + return pformat([record.message for record in self], indent=2) + + try: + if not any(issubclass(w.category, self.expected_warning) for w in self): + fail( + f"DID NOT WARN. No warnings of type {self.expected_warning} were emitted.\n" + f" Emitted warnings: {found_str()}." + ) + elif not any(self.matches(w) for w in self): + fail( + f"DID NOT WARN. No warnings of type {self.expected_warning} matching the regex were emitted.\n" + f" Regex: {self.match_expr}\n" + f" Emitted warnings: {found_str()}." + ) + finally: + # Whether or not any warnings matched, we want to re-emit all unmatched warnings. + for w in self: + if not self.matches(w): + warnings.warn_explicit( + message=w.message, + category=w.category, + filename=w.filename, + lineno=w.lineno, + module=w.__module__, + source=w.source, ) - elif self.match_expr is not None: - for r in self: - if issubclass(r.category, self.expected_warning): - if re.compile(self.match_expr).search(str(r.message)): - break - else: - fail( - "DID NOT WARN. No warnings of type {} matching" - " ('{}') were emitted. The list of emitted warnings" - " is: {}.".format( - self.expected_warning, - self.match_expr, - [each.message for each in self], - ) - ) + + # Currently in Python it is possible to pass other types than an + # `str` message when creating `Warning` instances, however this + # causes an exception when :func:`warnings.filterwarnings` is used + # to filter those warnings. See + # https://github.com/python/cpython/issues/103577 for a discussion. + # While this can be considered a bug in CPython, we put guards in + # pytest as the error message produced without this check in place + # is confusing (#10865). + for w in self: + if type(w.message) is not UserWarning: + # If the warning was of an incorrect type then `warnings.warn()` + # creates a UserWarning. Any other warning must have been specified + # explicitly. + continue + if not w.message.args: + # UserWarning() without arguments must have been specified explicitly. + continue + msg = w.message.args[0] + if isinstance(msg, str): + continue + # It's possible that UserWarning was explicitly specified, and + # its first argument was not a string. But that case can't be + # distinguished from an invalid type. + raise TypeError( + f"Warning must be str or Warning, got {msg!r} (type {type(msg).__name__})" + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/reports.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/reports.py index a68e68bc526f5..70f3212ce7bad 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/reports.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/reports.py @@ -1,12 +1,18 @@ -import os +# mypy: allow-untyped-defs +import dataclasses from io import StringIO +import os from pprint import pprint from typing import Any from typing import cast from typing import Dict +from typing import final from typing import Iterable from typing import Iterator from typing import List +from typing import Literal +from typing import Mapping +from typing import NoReturn from typing import Optional from typing import Tuple from typing import Type @@ -14,8 +20,6 @@ from typing import TypeVar from typing import Union -import attr - from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo from _pytest._code.code import ExceptionRepr @@ -28,16 +32,13 @@ from _pytest._code.code import ReprTraceback from _pytest._code.code import TerminalRepr from _pytest._io import TerminalWriter -from _pytest.compat import final from _pytest.config import Config from _pytest.nodes import Collector from _pytest.nodes import Item from _pytest.outcomes import skip -if TYPE_CHECKING: - from typing import NoReturn - from typing_extensions import Literal +if TYPE_CHECKING: from _pytest.runner import CallInfo @@ -46,7 +47,7 @@ def getworkerinfoline(node): return node._workerinfocache except AttributeError: d = node.workerinfo - ver = "%s.%s.%s" % d["version_info"][:3] + ver = "{}.{}.{}".format(*d["version_info"][:3]) node._workerinfocache = s = "[{}] {} -- Python {} {}".format( d["id"], d["sysplatform"], ver, d["executable"] ) @@ -64,15 +65,14 @@ class BaseReport: ] sections: List[Tuple[str, str]] nodeid: str - outcome: "Literal['passed', 'failed', 'skipped']" + outcome: Literal["passed", "failed", "skipped"] def __init__(self, **kw: Any) -> None: self.__dict__.update(kw) if TYPE_CHECKING: # Can have arbitrary fields given to __init__(). - def __getattr__(self, key: str) -> Any: - ... + def __getattr__(self, key: str) -> Any: ... def toterminal(self, out: TerminalWriter) -> None: if hasattr(self, "node"): @@ -228,7 +228,7 @@ def _from_json(cls: Type[_R], reportdict: Dict[str, object]) -> _R: def _report_unserialization_failure( type_name: str, report_class: Type[BaseReport], reportdict -) -> "NoReturn": +) -> NoReturn: url = "https://github.com/pytest-dev/pytest/issues" stream = StringIO() pprint("-" * 100, stream=stream) @@ -249,19 +249,24 @@ class TestReport(BaseReport): """ __test__ = False + # Defined by skipping plugin. + # xfail reason if xfailed, otherwise not defined. Use hasattr to distinguish. + wasxfail: str def __init__( self, nodeid: str, location: Tuple[str, Optional[int], str], - keywords, - outcome: "Literal['passed', 'failed', 'skipped']", + keywords: Mapping[str, Any], + outcome: Literal["passed", "failed", "skipped"], longrepr: Union[ None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr ], - when: "Literal['setup', 'call', 'teardown']", + when: Literal["setup", "call", "teardown"], sections: Iterable[Tuple[str, str]] = (), duration: float = 0, + start: float = 0, + stop: float = 0, user_properties: Optional[Iterable[Tuple[str, object]]] = None, **extra, ) -> None: @@ -271,11 +276,13 @@ def __init__( #: 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. + #: The filesystempath may be relative to ``config.rootdir``. + #: The line number is 0-based. self.location: Tuple[str, Optional[int], str] = location #: A name -> value dictionary containing all keywords and #: markers associated with a test invocation. - self.keywords = keywords + self.keywords: Mapping[str, Any] = keywords #: Test outcome, always one of "passed", "failed", "skipped". self.outcome = outcome @@ -297,22 +304,31 @@ def __init__( self.sections = list(sections) #: Time it took to run just the test. - self.duration = duration + self.duration: float = duration + + #: The system time when the call started, in seconds since the epoch. + self.start: float = start + #: The system time when the call ended, in seconds since the epoch. + self.stop: float = stop self.__dict__.update(extra) def __repr__(self) -> str: - return "<{} {!r} when={!r} outcome={!r}>".format( - self.__class__.__name__, self.nodeid, self.when, self.outcome - ) + return f"<{self.__class__.__name__} {self.nodeid!r} when={self.when!r} outcome={self.outcome!r}>" @classmethod def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": - """Create and fill a TestReport with standard item and call info.""" + """Create and fill a TestReport with standard item and call info. + + :param item: The item. + :param call: The call info. + """ when = call.when # Remove "collect" from the Literal type -- only for collection calls. assert when != "collect" duration = call.duration + start = call.start + stop = call.stop keywords = {x: 1 for x in item.keywords} excinfo = call.excinfo sections = [] @@ -332,6 +348,9 @@ def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": elif isinstance(excinfo.value, skip.Exception): outcome = "skipped" r = excinfo._getreprcrash() + assert ( + r is not None + ), "There should always be a traceback entry for skipping a test." if excinfo.value._use_item_location: path, line = item.reportinfo()[:2] assert line is not None @@ -357,6 +376,8 @@ def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": when, sections, duration, + start, + stop, user_properties=item.user_properties, ) @@ -402,13 +423,13 @@ def __init__( self.__dict__.update(extra) @property - def location(self): + def location( # type:ignore[override] + self, + ) -> Optional[Tuple[str, Optional[int], str]]: return (self.fspath, None, self.fspath) def __repr__(self) -> str: - return "".format( - self.nodeid, len(self.result), self.outcome - ) + return f"" class CollectErrorRepr(TerminalRepr): @@ -420,7 +441,7 @@ def toterminal(self, out: TerminalWriter) -> None: def pytest_report_to_serializable( - report: Union[CollectReport, TestReport] + report: Union[CollectReport, TestReport], ) -> Optional[Dict[str, Any]]: if isinstance(report, (TestReport, CollectReport)): data = report._to_json() @@ -452,17 +473,17 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]: """ def serialize_repr_entry( - entry: Union[ReprEntry, ReprEntryNative] + entry: Union[ReprEntry, ReprEntryNative], ) -> Dict[str, Any]: - data = attr.asdict(entry) + data = dataclasses.asdict(entry) for key, value in data.items(): if hasattr(value, "__dict__"): - data[key] = attr.asdict(value) + data[key] = dataclasses.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 = dataclasses.asdict(reprtraceback) result["reprentries"] = [ serialize_repr_entry(x) for x in reprtraceback.reprentries ] @@ -472,7 +493,7 @@ def serialize_repr_crash( reprcrash: Optional[ReprFileLocation], ) -> Optional[Dict[str, Any]]: if reprcrash is not None: - return attr.asdict(reprcrash) + return dataclasses.asdict(reprcrash) else: return None @@ -568,7 +589,6 @@ def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]): and "reprcrash" in reportdict["longrepr"] and "reprtraceback" in reportdict["longrepr"] ): - reprtraceback = deserialize_repr_traceback( reportdict["longrepr"]["reprtraceback"] ) @@ -585,11 +605,14 @@ def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]): description, ) ) - exception_info: Union[ - ExceptionChainRepr, ReprExceptionInfo - ] = ExceptionChainRepr(chain) + exception_info: Union[ExceptionChainRepr, ReprExceptionInfo] = ( + ExceptionChainRepr(chain) + ) else: - exception_info = ReprExceptionInfo(reprtraceback, reprcrash) + exception_info = ReprExceptionInfo( + reprtraceback=reprtraceback, + reprcrash=reprcrash, + ) for section in reportdict["longrepr"]["sections"]: exception_info.addsection(*section) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/runner.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/runner.py index e43dd2dc81826..d15a682f97907 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/runner.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/runner.py @@ -1,13 +1,17 @@ +# mypy: allow-untyped-defs """Basic collect and runtest protocol implementations.""" + import bdb +import dataclasses import os import sys -import warnings from typing import Callable from typing import cast from typing import Dict +from typing import final from typing import Generic from typing import List +from typing import Literal from typing import Optional from typing import Tuple from typing import Type @@ -15,8 +19,6 @@ from typing import TypeVar from typing import Union -import attr - from .reports import BaseReport from .reports import CollectErrorRepr from .reports import CollectReport @@ -25,11 +27,10 @@ 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.config.argparsing import Parser from _pytest.deprecated import check_ispytest -from _pytest.deprecated import UNITTEST_SKIP_DURING_COLLECTION from _pytest.nodes import Collector +from _pytest.nodes import Directory from _pytest.nodes import Item from _pytest.nodes import Node from _pytest.outcomes import Exit @@ -37,9 +38,11 @@ from _pytest.outcomes import Skipped from _pytest.outcomes import TEST_OUTCOME -if TYPE_CHECKING: - from typing_extensions import Literal +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup + +if TYPE_CHECKING: from _pytest.main import Session from _pytest.terminal import TerminalReporter @@ -48,14 +51,14 @@ def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("terminal reporting", "reporting", after="general") + group = parser.getgroup("terminal reporting", "Reporting", after="general") group.addoption( "--durations", action="store", type=int, default=None, metavar="N", - help="show N slowest setup/test durations (N=0 for all).", + help="Show N slowest setup/test durations (N=0 for all)", ) group.addoption( "--durations-min", @@ -63,7 +66,8 @@ def pytest_addoption(parser: Parser) -> None: type=float, default=0.005, metavar="N", - help="Minimal duration in seconds for inclusion in slowest list. Default 0.005", + help="Minimal duration in seconds for inclusion in slowest list. " + "Default: 0.005.", ) @@ -81,7 +85,7 @@ def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None: dlist.append(rep) if not dlist: return - dlist.sort(key=lambda x: x.duration, reverse=True) # type: ignore[no-any-return] + dlist.sort(key=lambda x: x.duration, reverse=True) if not durations: tr.write_sep("=", "slowest durations") else: @@ -92,8 +96,7 @@ def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None: if verbose < 2 and rep.duration < durations_min: tr.write_line("") tr.write_line( - "(%s durations < %gs hidden. Use -vv to show these durations.)" - % (len(dlist) - i, durations_min) + f"({len(dlist) - i} durations < {durations_min:g}s hidden. Use -vv to show these durations.)" ) break tr.write_line(f"{rep.duration:02.2f}s {rep.when:<8} {rep.nodeid}") @@ -162,6 +165,8 @@ def pytest_runtest_call(item: Item) -> None: del sys.last_type del sys.last_value del sys.last_traceback + if sys.version_info >= (3, 12, 0): + del sys.last_exc # type: ignore[attr-defined] except AttributeError: pass try: @@ -170,6 +175,8 @@ def pytest_runtest_call(item: Item) -> None: # Store trace info to allow postmortem debugging sys.last_type = type(e) sys.last_value = e + if sys.version_info >= (3, 12, 0): + sys.last_exc = e # type: ignore[attr-defined] assert e.__traceback__ is not None # Skip *this* frame sys.last_traceback = e.__traceback__.tb_next @@ -183,7 +190,7 @@ def pytest_runtest_teardown(item: Item, nextitem: Optional[Item]) -> None: def _update_current_test_var( - item: Item, when: Optional["Literal['setup', 'call', 'teardown']"] + item: Item, when: Optional[Literal["setup", "call", "teardown"]] ) -> None: """Update :envvar:`PYTEST_CURRENT_TEST` to reflect the current item and stage. @@ -216,15 +223,28 @@ def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str def call_and_report( - item: Item, when: "Literal['setup', 'call', 'teardown']", log: bool = True, **kwds + item: Item, when: Literal["setup", "call", "teardown"], log: bool = True, **kwds ) -> TestReport: - call = call_runtest_hook(item, when, **kwds) - hook = item.ihook - report: TestReport = hook.pytest_runtest_makereport(item=item, call=call) + ihook = item.ihook + if when == "setup": + runtest_hook: Callable[..., None] = ihook.pytest_runtest_setup + elif when == "call": + runtest_hook = ihook.pytest_runtest_call + elif when == "teardown": + runtest_hook = ihook.pytest_runtest_teardown + else: + assert False, f"Unhandled runtest hook case: {when}" + reraise: Tuple[Type[BaseException], ...] = (Exit,) + if not item.config.getoption("usepdb", False): + reraise += (KeyboardInterrupt,) + call = CallInfo.from_call( + lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise + ) + report: TestReport = ihook.pytest_runtest_makereport(item=item, call=call) if log: - hook.pytest_runtest_logreport(report=report) + ihook.pytest_runtest_logreport(report=report) if check_interactive_exception(call, report): - hook.pytest_exception_interact(node=item, call=call, report=report) + ihook.pytest_exception_interact(node=item, call=call, report=report) return report @@ -243,30 +263,11 @@ def check_interactive_exception(call: "CallInfo[object]", report: BaseReport) -> return True -def call_runtest_hook( - item: Item, when: "Literal['setup', 'call', 'teardown']", **kwds -) -> "CallInfo[None]": - if when == "setup": - ihook: Callable[..., None] = item.ihook.pytest_runtest_setup - elif when == "call": - ihook = item.ihook.pytest_runtest_call - elif when == "teardown": - ihook = item.ihook.pytest_runtest_teardown - else: - assert False, f"Unhandled runtest hook case: {when}" - reraise: Tuple[Type[BaseException], ...] = (Exit,) - if not item.config.getoption("usepdb", False): - reraise += (KeyboardInterrupt,) - return CallInfo.from_call( - lambda: ihook(item=item, **kwds), when=when, reraise=reraise - ) - - TResult = TypeVar("TResult", covariant=True) @final -@attr.s(repr=False, init=False, auto_attribs=True) +@dataclasses.dataclass class CallInfo(Generic[TResult]): """Result/Exception info of a function invocation.""" @@ -280,7 +281,7 @@ class CallInfo(Generic[TResult]): #: The call duration, in seconds. duration: float #: The context of invocation: "collect", "setup", "call" or "teardown". - when: "Literal['collect', 'setup', 'call', 'teardown']" + when: Literal["collect", "setup", "call", "teardown"] def __init__( self, @@ -289,7 +290,7 @@ def __init__( start: float, stop: float, duration: float, - when: "Literal['collect', 'setup', 'call', 'teardown']", + when: Literal["collect", "setup", "call", "teardown"], *, _ispytest: bool = False, ) -> None: @@ -317,8 +318,8 @@ def result(self) -> TResult: @classmethod def from_call( cls, - func: "Callable[[], TResult]", - when: "Literal['collect', 'setup', 'call', 'teardown']", + func: Callable[[], TResult], + when: Literal["collect", "setup", "call", "teardown"], reraise: Optional[ Union[Type[BaseException], Tuple[Type[BaseException], ...]] ] = None, @@ -368,7 +369,28 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport: def pytest_make_collect_report(collector: Collector) -> CollectReport: - call = CallInfo.from_call(lambda: list(collector.collect()), "collect") + def collect() -> List[Union[Item, Collector]]: + # Before collecting, if this is a Directory, load the conftests. + # If a conftest import fails to load, it is considered a collection + # error of the Directory collector. This is why it's done inside of the + # CallInfo wrapper. + # + # Note: initial conftests are loaded early, not here. + if isinstance(collector, Directory): + collector.config.pluginmanager._loadconftestmodules( + collector.path, + collector.config.getoption("importmode"), + rootpath=collector.config.rootpath, + consider_namespace_packages=collector.config.getini( + "consider_namespace_packages" + ), + ) + + return list(collector.collect()) + + call = CallInfo.from_call( + collect, "collect", reraise=(KeyboardInterrupt, SystemExit) + ) longrepr: Union[None, Tuple[str, int, str], str, TerminalRepr] = None if not call.excinfo: outcome: Literal["passed", "skipped", "failed"] = "passed" @@ -376,14 +398,8 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport: 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 + skip_exceptions.append(unittest.SkipTest) if isinstance(call.excinfo.value, tuple(skip_exceptions)): - if unittest is not None and isinstance( - call.excinfo.value, unittest.SkipTest # type: ignore[attr-defined] - ): - warnings.warn(UNITTEST_SKIP_DURING_COLLECTION, stacklevel=2) - outcome = "skipped" r_ = collector._repr_failure_py(call.excinfo, "line") assert isinstance(r_, ExceptionChainRepr), repr(r_) @@ -518,22 +534,29 @@ def teardown_exact(self, nextitem: Optional[Item]) -> None: stack is torn down. """ needed_collectors = nextitem and nextitem.listchain() or [] - exc = None + exceptions: List[BaseException] = [] while self.stack: if list(self.stack.keys()) == needed_collectors[: len(self.stack)]: break node, (finalizers, _) = self.stack.popitem() + these_exceptions = [] while finalizers: fin = finalizers.pop() try: fin() except TEST_OUTCOME 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 + these_exceptions.append(e) + + if len(these_exceptions) == 1: + exceptions.extend(these_exceptions) + elif these_exceptions: + msg = f"errors while tearing down {node!r}" + exceptions.append(BaseExceptionGroup(msg, these_exceptions[::-1])) + + if len(exceptions) == 1: + raise exceptions[0] + elif exceptions: + raise BaseExceptionGroup("errors during test teardown", exceptions[::-1]) if nextitem is None: assert not self.stack diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/scope.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/scope.py index 7a746fb9fa9e9..2c6e23208f249 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/scope.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/scope.py @@ -7,15 +7,14 @@ Also this makes the module light to import, as it should. """ + from enum import Enum from functools import total_ordering +from typing import Literal from typing import Optional -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from typing_extensions import Literal - _ScopeName = Literal["session", "package", "module", "class", "function"] +_ScopeName = Literal["session", "package", "module", "class", "function"] @total_ordering @@ -33,11 +32,11 @@ class Scope(Enum): """ # Scopes need to be listed from lower to higher. - Function: "_ScopeName" = "function" - Class: "_ScopeName" = "class" - Module: "_ScopeName" = "module" - Package: "_ScopeName" = "package" - Session: "_ScopeName" = "session" + Function: _ScopeName = "function" + Class: _ScopeName = "class" + Module: _ScopeName = "module" + Package: _ScopeName = "package" + Session: _ScopeName = "session" def next_lower(self) -> "Scope": """Return the next lower scope.""" @@ -60,7 +59,7 @@ def __lt__(self, other: "Scope") -> bool: @classmethod def from_user( - cls, scope_name: "_ScopeName", descr: str, where: Optional[str] = None + cls, scope_name: _ScopeName, descr: str, where: Optional[str] = None ) -> "Scope": """ Given a scope name from the user, return the equivalent Scope enum. Should be used diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/setuponly.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/setuponly.py index 531131ce7262f..39ab28b466b9c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/setuponly.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/setuponly.py @@ -2,7 +2,6 @@ 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 @@ -10,6 +9,7 @@ from _pytest.fixtures import FixtureDef from _pytest.fixtures import SubRequest from _pytest.scope import Scope +import pytest def pytest_addoption(parser: Parser) -> None: @@ -18,47 +18,52 @@ def pytest_addoption(parser: Parser) -> None: "--setuponly", "--setup-only", action="store_true", - help="only setup fixtures, do not execute tests.", + help="Only setup fixtures, do not execute tests", ) group.addoption( "--setupshow", "--setup-show", action="store_true", - help="show setup of fixtures while executing tests.", + help="Show setup of fixtures while executing tests", ) -@pytest.hookimpl(hookwrapper=True) +@pytest.hookimpl(wrapper=True) def pytest_fixture_setup( fixturedef: FixtureDef[object], request: SubRequest -) -> Generator[None, None, None]: - yield - 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): - param = fixturedef.ids(request.param) +) -> Generator[None, object, object]: + try: + return (yield) + finally: + 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): + param = fixturedef.ids(request.param) + else: + param = fixturedef.ids[request.param_index] else: - param = fixturedef.ids[request.param_index] - else: - param = request.param - fixturedef.cached_param = param # type: ignore[attr-defined] - _show_fixture_action(fixturedef, "SETUP") + param = request.param + fixturedef.cached_param = param # type: ignore[attr-defined] + _show_fixture_action(fixturedef, request.config, "SETUP") -def pytest_fixture_post_finalizer(fixturedef: FixtureDef[object]) -> None: +def pytest_fixture_post_finalizer( + fixturedef: FixtureDef[object], request: SubRequest +) -> None: if fixturedef.cached_result is not None: - config = fixturedef._fixturemanager.config + config = request.config if config.option.setupshow: - _show_fixture_action(fixturedef, "TEARDOWN") + _show_fixture_action(fixturedef, request.config, "TEARDOWN") if hasattr(fixturedef, "cached_param"): - del fixturedef.cached_param # type: ignore[attr-defined] + del fixturedef.cached_param -def _show_fixture_action(fixturedef: FixtureDef[object], msg: str) -> None: - config = fixturedef._fixturemanager.config +def _show_fixture_action( + fixturedef: FixtureDef[object], config: Config, msg: str +) -> None: capman = config.pluginmanager.getplugin("capturemanager") if capman: capman.suspend_global_capture() @@ -69,7 +74,7 @@ def _show_fixture_action(fixturedef: FixtureDef[object], msg: str) -> None: scope_indent = list(reversed(Scope)).index(fixturedef._scope) tw.write(" " * 2 * scope_indent) tw.write( - "{step} {scope} {fixture}".format( + "{step} {scope} {fixture}".format( # noqa: UP032 (Readability) step=msg.ljust(8), # align the output to TEARDOWN scope=fixturedef.scope[0].upper(), fixture=fixturedef.argname, @@ -82,7 +87,7 @@ def _show_fixture_action(fixturedef: FixtureDef[object], msg: str) -> None: tw.write(" (fixtures used: {})".format(", ".join(deps))) if hasattr(fixturedef, "cached_param"): - tw.write(f"[{saferepr(fixturedef.cached_param, maxsize=42)}]") # type: ignore[attr-defined] + tw.write(f"[{saferepr(fixturedef.cached_param, maxsize=42)}]") tw.flush() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/setupplan.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/setupplan.py index 9ba81ccaf0a4f..13c0df84ea1bf 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/setupplan.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/setupplan.py @@ -1,12 +1,12 @@ 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 +import pytest def pytest_addoption(parser: Parser) -> None: @@ -15,8 +15,8 @@ def pytest_addoption(parser: Parser) -> None: "--setupplan", "--setup-plan", action="store_true", - help="show what fixtures and tests would be executed but " - "don't execute anything.", + help="Show what fixtures and tests would be executed but " + "don't execute anything", ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/skipping.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/skipping.py index ac7216f8385f9..188dcae3f1c3f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/skipping.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/skipping.py @@ -1,16 +1,17 @@ +# mypy: allow-untyped-defs """Support for skip/xfail functions and markers.""" + +from collections.abc import Mapping +import dataclasses import os import platform import sys import traceback -from collections.abc import Mapping from typing import Generator from typing import Optional from typing import Tuple from typing import Type -import attr - from _pytest.config import Config from _pytest.config import hookimpl from _pytest.config.argparsing import Parser @@ -20,6 +21,7 @@ from _pytest.outcomes import skip from _pytest.outcomes import xfail from _pytest.reports import BaseReport +from _pytest.reports import TestReport from _pytest.runner import CallInfo from _pytest.stash import StashKey @@ -31,12 +33,12 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", dest="runxfail", default=False, - help="report the results of xfail tests as if they were not marked", + help="Report the results of xfail tests as if they were not marked", ) parser.addini( "xfail_strict", - "default for the strict parameter of xfail " + "Default for the strict parameter of xfail " "markers when not given explicitly (default: False)", default=False, type="bool", @@ -104,13 +106,11 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, ): if not isinstance(dictionary, Mapping): raise ValueError( - "pytest_markeval_namespace() needs to return a dict, got {!r}".format( - dictionary - ) + f"pytest_markeval_namespace() needs to return a dict, got {dictionary!r}" ) globals_.update(dictionary) if hasattr(item, "obj"): - globals_.update(item.obj.__globals__) # type: ignore[attr-defined] + globals_.update(item.obj.__globals__) try: filename = f"<{mark.name} condition>" condition_code = compile(condition, filename, "eval") @@ -157,7 +157,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, return result, reason -@attr.s(slots=True, frozen=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class Skip: """The result of evaluate_skip_marks().""" @@ -192,10 +192,12 @@ def evaluate_skip_marks(item: Item) -> Optional[Skip]: return None -@attr.s(slots=True, frozen=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class Xfail: """The result of evaluate_xfail_marks().""" + __slots__ = ("reason", "run", "strict", "raises") + reason: str run: bool strict: bool @@ -242,7 +244,7 @@ def pytest_runtest_setup(item: Item) -> None: xfail("[NOTRUN] " + xfailed.reason) -@hookimpl(hookwrapper=True) +@hookimpl(wrapper=True) def pytest_runtest_call(item: Item) -> Generator[None, None, None]: xfailed = item.stash.get(xfailed_key, None) if xfailed is None: @@ -251,18 +253,20 @@ def pytest_runtest_call(item: Item) -> Generator[None, None, None]: 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.stash.get(xfailed_key, None) - if xfailed is None: - item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item) + try: + return (yield) + finally: + # The test run may have added an xfail mark dynamically. + xfailed = item.stash.get(xfailed_key, None) + if xfailed is None: + item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item) -@hookimpl(hookwrapper=True) -def pytest_runtest_makereport(item: Item, call: CallInfo[None]): - outcome = yield - rep = outcome.get_result() +@hookimpl(wrapper=True) +def pytest_runtest_makereport( + item: Item, call: CallInfo[None] +) -> Generator[None, TestReport, TestReport]: + rep = yield xfailed = item.stash.get(xfailed_key, None) if item.config.option.runxfail: pass # don't interfere @@ -285,6 +289,7 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]): else: rep.outcome = "passed" rep.wasxfail = xfailed.reason + return rep def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/stash.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/stash.py index e61d75b95f7d9..a4b829fc6dd07 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/stash.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/stash.py @@ -19,6 +19,8 @@ class StashKey(Generic[T]): A ``StashKey`` is associated with the type ``T`` of the value of the key. A ``StashKey`` is unique and cannot conflict with another key. + + .. versionadded:: 7.0 """ __slots__ = () @@ -61,6 +63,8 @@ class Stash: some_str = stash[some_str_key] # The static type of some_bool is bool. some_bool = stash[some_bool_key] + + .. versionadded:: 7.0 """ __slots__ = ("_storage",) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/stepwise.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/stepwise.py index 4d95a96b8727f..92d3a297e0d33 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/stepwise.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/stepwise.py @@ -2,12 +2,13 @@ from typing import Optional from typing import TYPE_CHECKING -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 +import pytest + if TYPE_CHECKING: from _pytest.cacheprovider import Cache @@ -23,7 +24,7 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", default=False, dest="stepwise", - help="exit on test failure and continue from last failing test next time", + help="Exit on test failure and continue from last failing test next time", ) group.addoption( "--sw-skip", @@ -31,15 +32,15 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", default=False, dest="stepwise_skip", - help="ignore the first failing test but stop on the next failing test.\n" - "implicitly enables --stepwise.", + help="Ignore the first failing test but stop on the next failing test. " + "Implicitly enables --stepwise.", ) @pytest.hookimpl def pytest_configure(config: Config) -> None: if config.option.stepwise_skip: - # allow --stepwise-skip to work on it's own merits. + # allow --stepwise-skip to work on its own merits. config.option.stepwise = True if config.getoption("stepwise"): config.pluginmanager.register(StepwisePlugin(config), "stepwiseplugin") @@ -48,6 +49,10 @@ def pytest_configure(config: Config) -> None: def pytest_sessionfinish(session: Session) -> None: if not session.config.getoption("stepwise"): assert session.config.cache is not None + if hasattr(session.config, "workerinput"): + # Do not update cache if this process is a xdist worker to prevent + # race conditions (#10641). + return # Clear the list of failing tests if the plugin is not active. session.config.cache.set(STEPWISE_CACHE_DIR, []) @@ -119,4 +124,8 @@ def pytest_report_collectionfinish(self) -> Optional[str]: return None def pytest_sessionfinish(self) -> None: + if hasattr(self.config, "workerinput"): + # Do not update cache if this process is a xdist worker to prevent + # race conditions (#10641). + return self.cache.set(STEPWISE_CACHE_DIR, self.lastfailed) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/terminal.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/terminal.py index ccbd84d7d7164..724d5c54d2f16 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/terminal.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/terminal.py @@ -1,24 +1,29 @@ +# mypy: allow-untyped-defs """Terminal reporting of the full testing process. This is a good source for looking at the various reporting hooks. """ + import argparse +from collections import Counter +import dataclasses import datetime +from functools import partial import inspect +from pathlib import Path import platform import sys -import warnings -from collections import Counter -from functools import partial -from pathlib import Path +import textwrap from typing import Any from typing import Callable -from typing import cast from typing import ClassVar from typing import Dict +from typing import final from typing import Generator from typing import List +from typing import Literal from typing import Mapping +from typing import NamedTuple from typing import Optional from typing import Sequence from typing import Set @@ -26,17 +31,18 @@ from typing import Tuple from typing import TYPE_CHECKING from typing import Union +import warnings -import attr import pluggy -import _pytest._version from _pytest import nodes from _pytest import timing from _pytest._code import ExceptionInfo from _pytest._code.code import ExceptionRepr +from _pytest._io import TerminalWriter from _pytest._io.wcwidth import wcswidth -from _pytest.compat import final +import _pytest._version +from _pytest.assertion.util import running_on_ci from _pytest.config import _PluggyPlugin from _pytest.config import Config from _pytest.config import ExitCode @@ -50,9 +56,8 @@ from _pytest.reports import CollectReport from _pytest.reports import TestReport -if TYPE_CHECKING: - from typing_extensions import Literal +if TYPE_CHECKING: from _pytest.main import Session @@ -109,29 +114,49 @@ def __call__( namespace.quiet = getattr(namespace, "quiet", 0) + 1 +class TestShortLogReport(NamedTuple): + """Used to store the test status result category, shortletter and verbose word. + For example ``"rerun", "R", ("RERUN", {"yellow": True})``. + + :ivar category: + The class of result, for example ``“passed”``, ``“skipped”``, ``“error”``, or the empty string. + + :ivar letter: + The short letter shown as testing progresses, for example ``"."``, ``"s"``, ``"E"``, or the empty string. + + :ivar word: + Verbose word is shown as testing progresses in verbose mode, for example ``"PASSED"``, ``"SKIPPED"``, + ``"ERROR"``, or the empty string. + """ + + category: str + letter: str + word: Union[str, Tuple[str, Mapping[str, bool]]] + + def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("terminal reporting", "reporting", after="general") + group = parser.getgroup("terminal reporting", "Reporting", after="general") group._addoption( "-v", "--verbose", action="count", default=0, dest="verbose", - help="increase verbosity.", + help="Increase verbosity", ) group._addoption( "--no-header", action="store_true", default=False, dest="no_header", - help="disable header", + help="Disable header", ) group._addoption( "--no-summary", action="store_true", default=False, dest="no_summary", - help="disable summary", + help="Disable summary", ) group._addoption( "-q", @@ -139,14 +164,14 @@ def pytest_addoption(parser: Parser) -> None: action=MoreQuietAction, default=0, dest="verbose", - help="decrease verbosity.", + help="Decrease verbosity", ) group._addoption( "--verbosity", dest="verbose", type=int, default=0, - help="set verbosity. Default is 0.", + help="Set verbosity. Default: 0.", ) group._addoption( "-r", @@ -154,7 +179,7 @@ def pytest_addoption(parser: Parser) -> None: dest="reportchars", default=_REPORTCHARS_DEFAULT, metavar="chars", - help="show extra test summary info as specified by chars: (f)ailed, " + 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. " "(w)arnings are enabled by default (see --disable-warnings), " @@ -166,7 +191,7 @@ def pytest_addoption(parser: Parser) -> None: default=False, dest="disable_warnings", action="store_true", - help="disable warnings summary", + help="Disable warnings summary", ) group._addoption( "-l", @@ -174,7 +199,13 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", dest="showlocals", default=False, - help="show locals in tracebacks (disabled by default).", + help="Show locals in tracebacks (disabled by default)", + ) + group._addoption( + "--no-showlocals", + action="store_false", + dest="showlocals", + help="Hide locals in tracebacks (negate --showlocals passed through addopts)", ) group._addoption( "--tb", @@ -183,7 +214,7 @@ def pytest_addoption(parser: Parser) -> None: dest="tbstyle", default="auto", choices=["auto", "long", "short", "no", "line", "native"], - help="traceback print mode (auto/long/short/line/native/no).", + help="Traceback print mode (auto/long/short/line/native/no)", ) group._addoption( "--show-capture", @@ -192,14 +223,14 @@ def pytest_addoption(parser: Parser) -> None: choices=["no", "stdout", "stderr", "log", "all"], default="all", help="Controls how captured stdout/stderr/log is shown on failed tests. " - "Default is 'all'.", + "Default: all.", ) group._addoption( "--fulltrace", "--full-trace", action="store_true", default=False, - help="don't cut any tracebacks (default is to cut).", + help="Don't cut any tracebacks (default is to cut)", ) group._addoption( "--color", @@ -208,20 +239,31 @@ def pytest_addoption(parser: Parser) -> None: dest="color", default="auto", choices=["yes", "no", "auto"], - help="color terminal output (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)", + help="Whether code should be highlighted (only if --color is also enabled). " + "Default: yes.", ) parser.addini( "console_output_style", - help='console output: "classic", or with additional progress information ("progress" (percentage) | "count").', + help='Console output: "classic", or with additional progress information ' + '("progress" (percentage) | "count" | "progress-even-when-capture-no" (forces ' + "progress even when capture=no)", default="progress", ) + Config._add_verbosity_ini( + parser, + Config.VERBOSITY_TEST_CASES, + help=( + "Specify a verbosity level for test case execution, overriding the main level. " + "Higher levels will provide more detailed information about each test case executed." + ), + ) def pytest_configure(config: Config) -> None: @@ -277,7 +319,7 @@ def pytest_report_teststatus(report: BaseReport) -> Tuple[str, str, str]: return outcome, letter, outcome.upper() -@attr.s(auto_attribs=True) +@dataclasses.dataclass class WarningReport: """Simple structure to hold warnings information captured by ``pytest_warning_recorded``. @@ -334,16 +376,21 @@ def __init__(self, config: Config, file: Optional[TextIO] = None) -> None: self._already_displayed_warnings: Optional[int] = None self._keyboardinterrupt_memo: Optional[ExceptionRepr] = None - def _determine_show_progress_info(self) -> "Literal['progress', 'count', False]": + 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": + # do not show progress if we are not capturing output (#3038) unless explicitly + # overridden by progress-even-when-capture-no + if ( + self.config.getoption("capture", "no") == "no" + and self.config.getini("console_output_style") + != "progress-even-when-capture-no" + ): return False # do not show progress if we are showing fixture setup/teardown if self.config.getoption("setupshow", False): return False cfg: str = self.config.getini("console_output_style") - if cfg == "progress": + if cfg in {"progress", "progress-even-when-capture-no"}: return "progress" elif cfg == "count": return "count" @@ -370,7 +417,7 @@ def no_summary(self) -> bool: @property def showfspath(self) -> bool: if self._showfspath is None: - return self.verbosity >= 0 + return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) >= 0 return self._showfspath @showfspath.setter @@ -379,7 +426,7 @@ def showfspath(self, value: Optional[bool]) -> None: @property def showlongtestinfo(self) -> bool: - return self.verbosity > 0 + return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) > 0 def hasopt(self, char: str) -> bool: char = {"xfailed": "x", "skipped": "s"}.get(char, char) @@ -410,6 +457,28 @@ def ensure_newline(self) -> None: self._tw.line() self.currentfspath = None + def wrap_write( + self, + content: str, + *, + flush: bool = False, + margin: int = 8, + line_sep: str = "\n", + **markup: bool, + ) -> None: + """Wrap message with margin for progress info.""" + width_of_current_line = self._tw.width_of_current_line + wrapped = line_sep.join( + textwrap.wrap( + " " * width_of_current_line + content, + width=self._screen_width - margin, + drop_whitespace=True, + replace_whitespace=False, + ), + ) + wrapped = wrapped[width_of_current_line:] + self._tw.write(wrapped, flush=flush, **markup) + def write(self, content: str, *, flush: bool = False, **markup: bool) -> None: self._tw.write(content, flush=flush, **markup) @@ -509,10 +578,11 @@ def pytest_runtest_logstart( def pytest_runtest_logreport(self, report: TestReport) -> None: self._tests_ran = True rep = report - res: Tuple[ - str, str, Union[str, Tuple[str, Mapping[str, bool]]] - ] = self.config.hook.pytest_report_teststatus(report=rep, config=self.config) - category, letter, word = res + + res = TestShortLogReport( + *self.config.hook.pytest_report_teststatus(report=rep, config=self.config) + ) + category, letter, word = res.category, res.letter, res.word if not isinstance(word, tuple): markup = None else: @@ -534,7 +604,7 @@ def pytest_runtest_logreport(self, report: TestReport) -> None: markup = {"yellow": True} else: markup = {} - if self.verbosity <= 0: + if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0: self._tw.write(letter, **markup) else: self._progress_nodeids_reported.add(rep.nodeid) @@ -542,15 +612,21 @@ def pytest_runtest_logreport(self, report: TestReport) -> None: if not running_xdist: self.write_ensure_prefix(line, word, **markup) if rep.skipped or hasattr(report, "wasxfail"): - available_width = ( - (self._tw.fullwidth - self._tw.width_of_current_line) - - len(" [100%]") - - 1 - ) reason = _get_raw_skip_reason(rep) - reason_ = _format_trimmed(" ({})", reason, available_width) - if reason and reason_ is not None: - self._tw.write(reason_) + if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) < 2: + available_width = ( + (self._tw.fullwidth - self._tw.width_of_current_line) + - len(" [100%]") + - 1 + ) + formatted_reason = _format_trimmed( + " ({})", reason, available_width + ) + else: + formatted_reason = f" ({reason})" + + if reason and formatted_reason is not None: + self.wrap_write(formatted_reason) if self._show_progress_info: self._write_progress_information_filling_space() else: @@ -574,7 +650,10 @@ def _is_last_item(self) -> bool: def pytest_runtest_logfinish(self, nodeid: str) -> None: assert self._session - if self.verbosity <= 0 and self._show_progress_info: + if ( + self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0 + and self._show_progress_info + ): if self._show_progress_info == "count": num_tests = self._session.testscollected progress_length = len(f" [{num_tests}/{num_tests}]") @@ -605,8 +684,8 @@ def _get_progress_information_message(self) -> str: return f" [ {collected} / {collected} ]" else: if collected: - return " [{:3d}%]".format( - len(self._progress_nodeids_reported) * 100 // collected + return ( + f" [{len(self._progress_nodeids_reported) * 100 // collected:3d}%]" ) return " [100%]" @@ -657,7 +736,7 @@ def report_collect(self, final: bool = False) -> None: errors = len(self.stats.get("error", [])) skipped = len(self.stats.get("skipped", [])) deselected = len(self.stats.get("deselected", [])) - selected = self._numcollected - errors - skipped - deselected + selected = self._numcollected - deselected line = "collected " if final else "collecting " line += ( str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s") @@ -668,7 +747,7 @@ def report_collect(self, final: bool = False) -> None: line += " / %d deselected" % deselected if skipped: line += " / %d skipped" % skipped - if self._numcollected > selected > 0: + if self._numcollected > selected: line += " / %d selected" % selected if self.isatty: self.rewrite(line, bold=True, erase=True) @@ -691,9 +770,7 @@ def pytest_sessionstart(self, session: "Session") -> None: if pypy_version_info: verinfo = ".".join(map(str, pypy_version_info[:3])) msg += f"[pypy-{verinfo}-{pypy_version_info[3]}]" - msg += ", pytest-{}, pluggy-{}".format( - _pytest._version.version, pluggy.__version__ - ) + msg += f", pytest-{_pytest._version.version}, pluggy-{pluggy.__version__}" if ( self.verbosity > 0 or self.config.option.debug @@ -717,16 +794,14 @@ def _write_report_lines_from_hooks( self.write_line(line) def pytest_report_header(self, config: Config) -> List[str]: - line = "rootdir: %s" % config.rootpath + result = [f"rootdir: {config.rootpath}"] if config.inipath: - line += ", configfile: " + bestrelpath(config.rootpath, config.inipath) - - testpaths: List[str] = config.getini("testpaths") - if config.invocation_params.dir == config.rootpath and config.args == testpaths: - line += ", testpaths: {}".format(", ".join(testpaths)) + result.append("configfile: " + bestrelpath(config.rootpath, config.inipath)) - result = [line] + if config.args_source == Config.ArgsSource.TESTPATHS: + testpaths: List[str] = config.getini("testpaths") + result.append("testpaths: {}".format(", ".join(testpaths))) plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: @@ -756,8 +831,9 @@ def pytest_collection_finish(self, session: "Session") -> None: rep.toterminal(self._tw) def _printcollecteditems(self, items: Sequence[Item]) -> None: - if self.config.option.verbose < 0: - if self.config.option.verbose < -1: + test_cases_verbosity = self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) + if test_cases_verbosity < 0: + if test_cases_verbosity < -1: counts = Counter(item.nodeid.split("::", 1)[0] for item in items) for name, count in sorted(counts.items()): self._tw.line("%s: %d" % (name, count)) @@ -777,19 +853,18 @@ def _printcollecteditems(self, items: Sequence[Item]) -> None: stack.append(col) indent = (len(stack) - 1) * " " self._tw.line(f"{indent}{col}") - if self.config.option.verbose >= 1: + if test_cases_verbosity >= 1: 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)) - @hookimpl(hookwrapper=True) + @hookimpl(wrapper=True) def pytest_sessionfinish( self, session: "Session", exitstatus: Union[int, ExitCode] - ): - outcome = yield - outcome.get_result() + ) -> Generator[None, None, None]: + result = yield self._tw.line("") summary_exit_codes = ( ExitCode.OK, @@ -810,17 +885,22 @@ def pytest_sessionfinish( elif session.shouldstop: self.write_sep("!", str(session.shouldstop), red=True) self.summary_stats() + return result - @hookimpl(hookwrapper=True) + @hookimpl(wrapper=True) def pytest_terminal_summary(self) -> Generator[None, None, None]: self.summary_errors() self.summary_failures() + self.summary_xfailures() self.summary_warnings() self.summary_passes() - yield - self.short_test_summary() - # Display any extra warnings from teardown here (if any). - self.summary_warnings() + self.summary_xpasses() + try: + return (yield) + finally: + self.short_test_summary() + # Display any extra warnings from teardown here (if any). + self.summary_warnings() def pytest_keyboard_interrupt(self, excinfo: ExceptionInfo[BaseException]) -> None: self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True) @@ -944,12 +1024,20 @@ def collapsed_location_report(reports: List[WarningReport]) -> str: ) def summary_passes(self) -> None: + self.summary_passes_combined("passed", "PASSES", "P") + + def summary_xpasses(self) -> None: + self.summary_passes_combined("xpassed", "XPASSES", "X") + + def summary_passes_combined( + self, which_reports: str, sep_title: str, needed_opt: str + ) -> None: if self.config.option.tbstyle != "no": - if self.hasopt("P"): - reports: List[TestReport] = self.getreports("passed") + if self.hasopt(needed_opt): + reports: List[TestReport] = self.getreports(which_reports) if not reports: return - self.write_sep("=", "PASSES") + self.write_sep("=", sep_title) for rep in reports: if rep.sections: msg = self._getfailureheadline(rep) @@ -983,21 +1071,30 @@ def print_teardown_sections(self, rep: TestReport) -> None: self._tw.line(content) def summary_failures(self) -> None: + self.summary_failures_combined("failed", "FAILURES") + + def summary_xfailures(self) -> None: + self.summary_failures_combined("xfailed", "XFAILURES", "x") + + def summary_failures_combined( + self, which_reports: str, sep_title: str, needed_opt: Optional[str] = None + ) -> None: if self.config.option.tbstyle != "no": - reports: List[BaseReport] = self.getreports("failed") - if not reports: - return - self.write_sep("=", "FAILURES") - if self.config.option.tbstyle == "line": - for rep in reports: - line = self._getcrashline(rep) - self.write_line(line) - else: - for rep in reports: - msg = self._getfailureheadline(rep) - self.write_sep("_", msg, red=True, bold=True) - self._outrep_summary(rep) - self._handle_teardown_sections(rep.nodeid) + if not needed_opt or self.hasopt(needed_opt): + reports: List[BaseReport] = self.getreports(which_reports) + if not reports: + return + self.write_sep("=", sep_title) + if self.config.option.tbstyle == "line": + for rep in reports: + line = self._getcrashline(rep) + self.write_line(line) + else: + for rep in reports: + msg = self._getfailureheadline(rep) + self.write_sep("_", msg, red=True, bold=True) + self._outrep_summary(rep) + self._handle_teardown_sections(rep.nodeid) def summary_errors(self) -> None: if self.config.option.tbstyle != "no": @@ -1068,33 +1165,46 @@ def short_test_summary(self) -> None: if not self.reportchars: return - def show_simple(stat, lines: List[str]) -> None: + def show_simple(lines: List[str], *, stat: str) -> None: failed = self.stats.get(stat, []) if not failed: return - termwidth = self._tw.fullwidth config = self.config for rep in failed: - line = _get_line_with_reprcrash_message(config, rep, termwidth) + color = _color_for_type.get(stat, _color_for_type_default) + line = _get_line_with_reprcrash_message( + config, rep, self._tw, {color: True} + ) lines.append(line) 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(f"{verbose_word} {pos}") + markup_word = self._tw.markup( + verbose_word, **{_color_for_type["warnings"]: True} + ) + nodeid = _get_node_id_with_markup(self._tw, self.config, rep) + line = f"{markup_word} {nodeid}" reason = rep.wasxfail if reason: - lines.append(" " + str(reason)) + line += " - " + str(reason) + + lines.append(line) 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) + markup_word = self._tw.markup( + verbose_word, **{_color_for_type["warnings"]: True} + ) + nodeid = _get_node_id_with_markup(self._tw, self.config, rep) + line = f"{markup_word} {nodeid}" reason = rep.wasxfail - lines.append(f"{verbose_word} {pos} {reason}") + if reason: + line += " - " + str(reason) + lines.append(line) def show_skipped(lines: List[str]) -> None: skipped: List[CollectReport] = self.stats.get("skipped", []) @@ -1102,24 +1212,27 @@ def show_skipped(lines: List[str]) -> None: if not fskips: return verbose_word = skipped[0]._get_verbose_word(self.config) + markup_word = self._tw.markup( + verbose_word, **{_color_for_type["warnings"]: True} + ) + prefix = "Skipped: " for num, fspath, lineno, reason in fskips: - if reason.startswith("Skipped: "): - reason = reason[9:] + if reason.startswith(prefix): + reason = reason[len(prefix) :] if lineno is not None: lines.append( - "%s [%d] %s:%d: %s" - % (verbose_word, num, fspath, lineno, reason) + "%s [%d] %s:%d: %s" % (markup_word, num, fspath, lineno, reason) ) else: - lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason)) + lines.append("%s [%d] %s: %s" % (markup_word, num, fspath, reason)) REPORTCHAR_ACTIONS: Mapping[str, Callable[[List[str]], None]] = { "x": show_xfailed, "X": show_xpassed, - "f": partial(show_simple, "failed"), + "f": partial(show_simple, stat="failed"), "s": show_skipped, - "p": partial(show_simple, "passed"), - "E": partial(show_simple, "error"), + "p": partial(show_simple, stat="passed"), + "E": partial(show_simple, stat="error"), } lines: List[str] = [] @@ -1129,7 +1242,7 @@ def show_skipped(lines: List[str]) -> None: action(lines) if lines: - self.write_sep("=", "short test summary info") + self.write_sep("=", "short test summary info", cyan=True, bold=True) for line in lines: self.write_line(line) @@ -1154,7 +1267,7 @@ def _determine_main_color(self, unknown_type_seen: bool) -> str: def _set_main_color(self) -> None: unknown_types: List[str] = [] - for found_type in self.stats.keys(): + for found_type in self.stats: 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) @@ -1243,9 +1356,14 @@ def _build_collect_only_summary_stats_line( return parts, main_color -def _get_pos(config: Config, rep: BaseReport): +def _get_node_id_with_markup(tw: TerminalWriter, config: Config, rep: BaseReport): nodeid = config.cwd_relative_nodeid(rep.nodeid) - return nodeid + path, *parts = nodeid.split("::") + if parts: + parts_markup = tw.markup("::".join(parts), bold=True) + return path + "::" + parts_markup + else: + return path def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str]: @@ -1274,13 +1392,14 @@ def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str def _get_line_with_reprcrash_message( - config: Config, rep: BaseReport, termwidth: int + config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: Dict[str, bool] ) -> str: """Get summary line for a report, trying to add reprcrash message.""" verbose_word = rep._get_verbose_word(config) - pos = _get_pos(config, rep) + word = tw.markup(verbose_word, **word_markup) + node = _get_node_id_with_markup(tw, config, rep) - line = f"{verbose_word} {pos}" + line = f"{word} {node}" line_width = wcswidth(line) try: @@ -1289,8 +1408,11 @@ def _get_line_with_reprcrash_message( except AttributeError: pass else: - available_width = termwidth - line_width - msg = _format_trimmed(" - {}", msg, available_width) + if running_on_ci() or config.option.verbose >= 2: + msg = f" - {msg}" + else: + available_width = tw.fullwidth - line_width + msg = _format_trimmed(" - {}", msg, available_width) if msg is not None: line += msg @@ -1354,7 +1476,7 @@ def _plugin_nameversions(plugininfo) -> List[str]: values: List[str] = [] for plugin, dist in plugininfo: # Gets us name and version! - name = "{dist.project_name}-{dist.version}".format(dist=dist) + name = f"{dist.project_name}-{dist.version}" # Questionable convenience, but it keeps things short. if name.startswith("pytest-"): name = name[7:] @@ -1379,7 +1501,7 @@ def _get_raw_skip_reason(report: TestReport) -> str: The string is just the part given by the user. """ if hasattr(report, "wasxfail"): - reason = cast(str, report.wasxfail) + reason = report.wasxfail if reason.startswith("reason: "): reason = reason[len("reason: ") :] return reason diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/threadexception.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/threadexception.py index 43341e739a0e4..09faf661b91ee 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/threadexception.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/threadexception.py @@ -1,12 +1,12 @@ import threading import traceback -import warnings from types import TracebackType from typing import Any from typing import Callable from typing import Generator from typing import Optional from typing import Type +import warnings import pytest @@ -59,30 +59,34 @@ def __exit__( def thread_exception_runtest_hook() -> Generator[None, None, None]: with catch_threading_exception() as cm: - yield - if cm.args: - thread_name = "" if cm.args.thread is None else cm.args.thread.name - msg = f"Exception in thread {thread_name}\n\n" - msg += "".join( - traceback.format_exception( - cm.args.exc_type, - cm.args.exc_value, - cm.args.exc_traceback, + try: + yield + finally: + if cm.args: + thread_name = ( + "" if cm.args.thread is None else cm.args.thread.name + ) + msg = f"Exception in thread {thread_name}\n\n" + msg += "".join( + traceback.format_exception( + cm.args.exc_type, + cm.args.exc_value, + cm.args.exc_traceback, + ) ) - ) - warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) + warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) -@pytest.hookimpl(hookwrapper=True, trylast=True) +@pytest.hookimpl(wrapper=True, trylast=True) def pytest_runtest_setup() -> Generator[None, None, None]: yield from thread_exception_runtest_hook() -@pytest.hookimpl(hookwrapper=True, tryfirst=True) +@pytest.hookimpl(wrapper=True, tryfirst=True) def pytest_runtest_call() -> Generator[None, None, None]: yield from thread_exception_runtest_hook() -@pytest.hookimpl(hookwrapper=True, tryfirst=True) +@pytest.hookimpl(wrapper=True, tryfirst=True) def pytest_runtest_teardown() -> Generator[None, None, None]: yield from thread_exception_runtest_hook() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/timing.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/timing.py index 925163a585840..0541dc8e0a1b1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/timing.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/timing.py @@ -5,8 +5,10 @@ Fixture "mock_timing" 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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/tmpdir.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/tmpdir.py index f901fd5727c55..72efed3e87a17 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/tmpdir.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/tmpdir.py @@ -1,40 +1,63 @@ +# mypy: allow-untyped-defs """Support for providing temporary directories to test functions.""" + +import dataclasses import os +from pathlib import Path import re -import sys +from shutil import rmtree import tempfile -from pathlib import Path +from typing import Any +from typing import Dict +from typing import final +from typing import Generator +from typing import Literal from typing import Optional +from typing import Union -import attr - +from .pathlib import cleanup_dead_symlinks from .pathlib import LOCK_TIMEOUT from .pathlib import make_numbered_dir from .pathlib import make_numbered_dir_with_cleanup from .pathlib import rm_rf -from _pytest.compat import final +from _pytest.compat import get_user_id from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config import hookimpl +from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest from _pytest.fixtures import fixture from _pytest.fixtures import FixtureRequest from _pytest.monkeypatch import MonkeyPatch +from _pytest.nodes import Item +from _pytest.reports import TestReport +from _pytest.stash import StashKey + + +tmppath_result_key = StashKey[Dict[str, bool]]() +RetentionType = Literal["all", "failed", "none"] @final -@attr.s(init=False) +@dataclasses.dataclass class TempPathFactory: """Factory for temporary directories under the common base temp directory. The base directory can be configured using the ``--basetemp`` option. """ - _given_basetemp = attr.ib(type=Optional[Path]) - _trace = attr.ib() - _basetemp = attr.ib(type=Optional[Path]) + _given_basetemp: Optional[Path] + # pluggy TagTracerSub, not currently exposed, so Any. + _trace: Any + _basetemp: Optional[Path] + _retention_count: int + _retention_policy: RetentionType def __init__( self, given_basetemp: Optional[Path], + retention_count: int, + retention_policy: RetentionType, trace, basetemp: Optional[Path] = None, *, @@ -49,6 +72,8 @@ def __init__( # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012). self._given_basetemp = Path(os.path.abspath(str(given_basetemp))) self._trace = trace + self._retention_count = retention_count + self._retention_policy = retention_policy self._basetemp = basetemp @classmethod @@ -63,9 +88,23 @@ def from_config( :meta private: """ check_ispytest(_ispytest) + count = int(config.getini("tmp_path_retention_count")) + if count < 0: + raise ValueError( + f"tmp_path_retention_count must be >= 0. Current input: {count}." + ) + + policy = config.getini("tmp_path_retention_policy") + if policy not in ("all", "failed", "none"): + raise ValueError( + f"tmp_path_retention_policy must be either all, failed, none. Current input: {policy}." + ) + return cls( given_basetemp=config.option.basetemp, trace=config.trace.get("tmpdir"), + retention_count=count, + retention_policy=policy, _ispytest=True, ) @@ -100,7 +139,11 @@ def mktemp(self, basename: str, numbered: bool = True) -> Path: return p def getbasetemp(self) -> Path: - """Return the base temporary directory, creating it if needed.""" + """Return the base temporary directory, creating it if needed. + + :returns: + The base temporary directory. + """ if self._basetemp is not None: return self._basetemp @@ -129,23 +172,23 @@ def getbasetemp(self) -> Path: # Also, to keep things private, fixup any world-readable temp # rootdir's permissions. Historically 0o755 was used, so we can't # just error out on this, at least for a while. - if sys.platform != "win32": - uid = os.getuid() + uid = get_user_id() + if uid is not None: rootdir_stat = rootdir.stat() - # getuid shouldn't fail, but cpython defines such a case. - # Let's hope for the best. - if uid != -1: - if rootdir_stat.st_uid != uid: - raise OSError( - f"The temporary directory {rootdir} is not owned by the current user. " - "Fix this and try again." - ) - if (rootdir_stat.st_mode & 0o077) != 0: - os.chmod(rootdir, rootdir_stat.st_mode & ~0o077) + if rootdir_stat.st_uid != uid: + raise OSError( + f"The temporary directory {rootdir} is not owned by the current user. " + "Fix this and try again." + ) + if (rootdir_stat.st_mode & 0o077) != 0: + os.chmod(rootdir, rootdir_stat.st_mode & ~0o077) + keep = self._retention_count + if self._retention_policy == "none": + keep = 0 basetemp = make_numbered_dir_with_cleanup( prefix="pytest-", root=rootdir, - keep=3, + keep=keep, lock_timeout=LOCK_TIMEOUT, mode=0o700, ) @@ -158,11 +201,12 @@ def getbasetemp(self) -> Path: def get_user() -> Optional[str]: """Return the current user name, or None if getuser() does not work in the current environment (see #1010).""" - import getpass - try: + # In some exotic environments, getpass may not be importable. + import getpass + return getpass.getuser() - except (ImportError, KeyError): + except (ImportError, OSError, KeyError): return None @@ -179,6 +223,21 @@ def pytest_configure(config: Config) -> None: mp.setattr(config, "_tmp_path_factory", _tmp_path_factory, raising=False) +def pytest_addoption(parser: Parser) -> None: + parser.addini( + "tmp_path_retention_count", + help="How many sessions should we keep the `tmp_path` directories, according to `tmp_path_retention_policy`.", + default=3, + ) + + parser.addini( + "tmp_path_retention_policy", + help="Controls which directories created by the `tmp_path` fixture are kept around, based on test outcome. " + "(all/failed/none)", + default="all", + ) + + @fixture(scope="session") def tmp_path_factory(request: FixtureRequest) -> TempPathFactory: """Return a :class:`pytest.TempPathFactory` instance for the test session.""" @@ -195,17 +254,69 @@ def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path: @fixture -def tmp_path(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> Path: +def tmp_path( + request: FixtureRequest, tmp_path_factory: TempPathFactory +) -> Generator[Path, None, None]: """Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. By default, a new base temporary directory is created each test session, - and old bases are removed after 3 sessions, to aid in debugging. If - ``--basetemp`` is used then it is cleared each session. See :ref:`base - temporary directory`. + and old bases are removed after 3 sessions, to aid in debugging. + This behavior can be configured with :confval:`tmp_path_retention_count` and + :confval:`tmp_path_retention_policy`. + If ``--basetemp`` is used then it is cleared each session. See + :ref:`temporary directory location and retention`. The returned object is a :class:`pathlib.Path` object. """ + path = _mk_tmp(request, tmp_path_factory) + yield path + + # Remove the tmpdir if the policy is "failed" and the test passed. + tmp_path_factory: TempPathFactory = request.session.config._tmp_path_factory # type: ignore + policy = tmp_path_factory._retention_policy + result_dict = request.node.stash[tmppath_result_key] + + if policy == "failed" and result_dict.get("call", True): + # We do a "best effort" to remove files, but it might not be possible due to some leaked resource, + # permissions, etc, in which case we ignore it. + rmtree(path, ignore_errors=True) + + del request.node.stash[tmppath_result_key] + + +def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]): + """After each session, remove base directory if all the tests passed, + the policy is "failed", and the basetemp is not specified by a user. + """ + tmp_path_factory: TempPathFactory = session.config._tmp_path_factory + basetemp = tmp_path_factory._basetemp + if basetemp is None: + return + + policy = tmp_path_factory._retention_policy + if ( + exitstatus == 0 + and policy == "failed" + and tmp_path_factory._given_basetemp is None + ): + if basetemp.is_dir(): + # We do a "best effort" to remove files, but it might not be possible due to some leaked resource, + # permissions, etc, in which case we ignore it. + rmtree(basetemp, ignore_errors=True) + + # Remove dead symlinks. + if basetemp.is_dir(): + cleanup_dead_symlinks(basetemp) + - return _mk_tmp(request, tmp_path_factory) +@hookimpl(wrapper=True, tryfirst=True) +def pytest_runtest_makereport( + item: Item, call +) -> Generator[None, TestReport, TestReport]: + rep = yield + assert rep.when is not None + empty: Dict[str, bool] = {} + item.stash.setdefault(tmppath_result_key, empty)[rep.when] = rep.passed + return rep diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/unittest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/unittest.py index 0315168b04464..8f1791bf744f1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/unittest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/unittest.py @@ -1,4 +1,6 @@ +# mypy: allow-untyped-defs """Discover and run std-library "unittest" style tests.""" + import sys import traceback import types @@ -14,8 +16,6 @@ from typing import Union import _pytest._code -import pytest -from _pytest.compat import getimfunc from _pytest.compat import is_async_function from _pytest.config import hookimpl from _pytest.fixtures import FixtureRequest @@ -27,12 +27,17 @@ from _pytest.outcomes import xfail from _pytest.python import Class from _pytest.python import Function -from _pytest.python import PyCollector +from _pytest.python import Module from _pytest.runner import CallInfo -from _pytest.scope import Scope +import pytest + + +if sys.version_info[:2] < (3, 11): + from exceptiongroup import ExceptionGroup if TYPE_CHECKING: import unittest + import twisted.trial.unittest _SysExcInfoType = Union[ @@ -42,7 +47,7 @@ def pytest_pycollect_makeitem( - collector: PyCollector, name: str, obj: object + collector: Union[Module, Class], name: str, obj: object ) -> Optional["UnitTestCase"]: # Has unittest been imported and is obj a subclass of its TestCase? try: @@ -53,8 +58,7 @@ def pytest_pycollect_makeitem( except Exception: return None # Yes, so let's collect it. - item: UnitTestCase = UnitTestCase.from_parent(collector, name=name, obj=obj) - return item + return UnitTestCase.from_parent(collector, name=name, obj=obj) class UnitTestCase(Class): @@ -62,6 +66,14 @@ class UnitTestCase(Class): # to declare that our children do not support funcargs. nofuncargs = True + def newinstance(self): + # TestCase __init__ takes the method (test) name. The TestCase + # constructor treats the name "runTest" as a special no-op, so it can be + # used when a dummy instance is needed. While unittest.TestCase has a + # default, some subclasses omit the default (#9610), so always supply + # it. + return self.obj("runTest") + def collect(self) -> Iterable[Union[Item, Collector]]: from unittest import TestLoader @@ -71,143 +83,139 @@ def collect(self) -> Iterable[Union[Item, Collector]]: skipped = _is_skipped(cls) if not skipped: - self._inject_setup_teardown_fixtures(cls) - self._inject_setup_class_fixture() + self._register_unittest_setup_method_fixture(cls) + self._register_unittest_setup_class_fixture(cls) + self._register_setup_class_fixture() + + self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid) - self.session._fixturemanager.parsefactories(self, unittest=True) loader = TestLoader() foundsomething = False for name in loader.getTestCaseNames(self.obj): x = getattr(self.obj, name) if not getattr(x, "__test__", True): continue - funcobj = getimfunc(x) - yield TestCaseFunction.from_parent(self, name=name, callobj=funcobj) + yield TestCaseFunction.from_parent(self, name=name) foundsomething = True if not foundsomething: runtest = getattr(self.obj, "runTest", None) if runtest is not None: ut = sys.modules.get("twisted.trial.unittest", None) - # Type ignored because `ut` is an opaque module. - if ut is None or runtest != ut.TestCase.runTest: # type: ignore + if ut is None or runtest != ut.TestCase.runTest: yield TestCaseFunction.from_parent(self, name="runTest") - 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).""" - class_fixture = _make_xunit_fixture( - cls, - "setUpClass", - "tearDownClass", - "doClassCleanups", - scope=Scope.Class, - pass_self=False, - ) - if class_fixture: - cls.__pytest_class_setup = class_fixture # type: ignore[attr-defined] - - method_fixture = _make_xunit_fixture( - cls, - "setup_method", - "teardown_method", - None, - scope=Scope.Function, - pass_self=True, - ) - if method_fixture: - cls.__pytest_method_setup = method_fixture # type: ignore[attr-defined] - - -def _make_xunit_fixture( - obj: type, - setup_name: str, - teardown_name: str, - cleanup_name: Optional[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 - - if cleanup_name: - cleanup = getattr(obj, cleanup_name, lambda *args: None) - else: - - def cleanup(*args): - pass - - @pytest.fixture( - scope=scope.value, - autouse=True, - # Use a unique name to speed up lookup. - name=f"_unittest_{setup_name}_fixture_{obj.__qualname__}", - ) - def fixture(self, request: FixtureRequest) -> Generator[None, None, None]: - if _is_skipped(self): - reason = self.__unittest_skip_why__ - raise pytest.skip.Exception(reason, _use_item_location=True) - if setup is not None: - try: - if pass_self: - setup(self, request.function) - else: + def _register_unittest_setup_class_fixture(self, cls: type) -> None: + """Register an auto-use fixture to invoke setUpClass and + tearDownClass (#517).""" + setup = getattr(cls, "setUpClass", None) + teardown = getattr(cls, "tearDownClass", None) + if setup is None and teardown is None: + return None + cleanup = getattr(cls, "doClassCleanups", lambda: None) + + def process_teardown_exceptions() -> None: + # tearDown_exceptions is a list set in the class containing exc_infos for errors during + # teardown for the class. + exc_infos = getattr(cls, "tearDown_exceptions", None) + if not exc_infos: + return + exceptions = [exc for (_, exc, _) in exc_infos] + # If a single exception, raise it directly as this provides a more readable + # error (hopefully this will improve in #12255). + if len(exceptions) == 1: + raise exceptions[0] + else: + raise ExceptionGroup("Unittest class cleanup errors", exceptions) + + def unittest_setup_class_fixture( + request: FixtureRequest, + ) -> Generator[None, None, None]: + cls = request.cls + if _is_skipped(cls): + reason = cls.__unittest_skip_why__ + raise pytest.skip.Exception(reason, _use_item_location=True) + if setup is not None: + try: setup() - # unittest does not call the cleanup function for every BaseException, so we - # follow this here. - except Exception: - if pass_self: - cleanup(self) - else: + # unittest does not call the cleanup function for every BaseException, so we + # follow this here. + except Exception: cleanup() - - raise - yield - try: - if teardown is not None: - if pass_self: - teardown(self, request.function) - else: + process_teardown_exceptions() + raise + yield + try: + if teardown is not None: teardown() - finally: - if pass_self: - cleanup(self) - else: + finally: cleanup() + process_teardown_exceptions() + + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_unittest_setUpClass_fixture_{cls.__qualname__}", + func=unittest_setup_class_fixture, + nodeid=self.nodeid, + scope="class", + autouse=True, + ) - return fixture + def _register_unittest_setup_method_fixture(self, cls: type) -> None: + """Register an auto-use fixture to invoke setup_method and + teardown_method (#517).""" + setup = getattr(cls, "setup_method", None) + teardown = getattr(cls, "teardown_method", None) + if setup is None and teardown is None: + return None + + def unittest_setup_method_fixture( + request: FixtureRequest, + ) -> Generator[None, None, None]: + self = request.instance + if _is_skipped(self): + reason = self.__unittest_skip_why__ + raise pytest.skip.Exception(reason, _use_item_location=True) + if setup is not None: + setup(self, request.function) + yield + if teardown is not None: + teardown(self, request.function) + + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_unittest_setup_method_fixture_{cls.__qualname__}", + func=unittest_setup_method_fixture, + nodeid=self.nodeid, + scope="function", + autouse=True, + ) class TestCaseFunction(Function): nofuncargs = True _excinfo: Optional[List[_pytest._code.ExceptionInfo[BaseException]]] = None - _testcase: Optional["unittest.TestCase"] = None - def _getobj(self): - assert self.parent is not None - # Unlike a regular Function in a Class, where `item.obj` returns - # a *bound* method (attached to an instance), TestCaseFunction's - # `obj` returns an *unbound* method (not attached to an instance). - # This inconsistency is probably not desirable, but needs some - # consideration before changing. - return getattr(self.parent.obj, self.originalname) # type: ignore[attr-defined] + def _getinstance(self): + assert isinstance(self.parent, UnitTestCase) + return self.parent.obj(self.name) + + # Backward compat for pytest-django; can be removed after pytest-django + # updates + some slack. + @property + def _testcase(self): + return self.instance def setup(self) -> None: # A bound method to be called during teardown() if set (see 'runtest()'). self._explicit_tearDown: Optional[Callable[[], None]] = 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() + super().setup() def teardown(self) -> None: + super().teardown() if self._explicit_tearDown is not None: self._explicit_tearDown() self._explicit_tearDown = None - self._testcase = None self._obj = None def startTest(self, testcase: "unittest.TestCase") -> None: @@ -217,11 +225,13 @@ 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[BaseException].from_exc_info(rawexcinfo) # type: ignore[arg-type] + excinfo = _pytest._code.ExceptionInfo[BaseException].from_exc_info( + rawexcinfo # type: ignore[arg-type] + ) # Invoke the attributes to trigger storing the traceback # trial causes some issue there. - excinfo.value - excinfo.traceback + _ = excinfo.value + _ = excinfo.traceback except TypeError: try: try: @@ -237,7 +247,7 @@ def _addexcinfo(self, rawexcinfo: "_SysExcInfoType") -> None: except BaseException: fail( "ERROR: Unknown Incompatible Exception " - "representation:\n%r" % (rawexcinfo,), + f"representation:\n{rawexcinfo!r}", pytrace=False, ) except KeyboardInterrupt: @@ -298,17 +308,20 @@ def addSuccess(self, testcase: "unittest.TestCase") -> None: def stopTest(self, testcase: "unittest.TestCase") -> None: pass + def addDuration(self, testcase: "unittest.TestCase", elapsed: float) -> None: + pass + def runtest(self) -> None: from _pytest.debugging import maybe_wrap_pytest_function_for_tracing - assert self._testcase is not None + testcase = self.instance + assert testcase is not None maybe_wrap_pytest_function_for_tracing(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] + testcase(result=self) else: # When --pdb is given, we want to postpone calling tearDown() otherwise # when entering the pdb prompt, tearDown() would have probably cleaned up @@ -316,27 +329,31 @@ def runtest(self) -> None: # 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 consider if the test itself is skipped, or the whole class. + assert isinstance(self.parent, UnitTestCase) + skipped = _is_skipped(self.obj) or _is_skipped(self.parent.obj) + if self.config.getoption("usepdb") and not skipped: + self._explicit_tearDown = testcase.tearDown + setattr(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) + setattr(testcase, self.name, self.obj) try: - self._testcase(result=self) # type: ignore[arg-type] + testcase(result=self) finally: - delattr(self._testcase, self.name) + delattr(testcase, self.name) - def _prunetraceback( + def _traceback_filter( self, excinfo: _pytest._code.ExceptionInfo[BaseException] - ) -> None: - super()._prunetraceback(excinfo) - traceback = excinfo.traceback.filter( - lambda x: not x.frame.f_globals.get("__unittest") + ) -> _pytest._code.Traceback: + traceback = super()._traceback_filter(excinfo) + ntraceback = traceback.filter( + lambda x: not x.frame.f_globals.get("__unittest"), ) - if traceback: - excinfo.traceback = traceback + if not ntraceback: + ntraceback = traceback + return ntraceback @hookimpl(tryfirst=True) @@ -354,11 +371,7 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None: # its own nose.SkipTest. For unittest TestCases, SkipTest is already # handled internally, and doesn't reach here. unittest = sys.modules.get("unittest") - if ( - unittest - and call.excinfo - and isinstance(call.excinfo.value, unittest.SkipTest) # type: ignore[attr-defined] - ): + if unittest and call.excinfo and isinstance(call.excinfo.value, unittest.SkipTest): excinfo = call.excinfo call2 = CallInfo[None].from_call( lambda: pytest.skip(str(excinfo.value)), call.when @@ -367,14 +380,21 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None: # Twisted trial support. +classImplements_has_run = False -@hookimpl(hookwrapper=True) -def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: +@hookimpl(wrapper=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules: ut: Any = sys.modules["twisted.python.failure"] + global classImplements_has_run Failure__init__ = ut.Failure.__init__ - check_testcase_implements_trial_reporter() + if not classImplements_has_run: + from twisted.trial.itrial import IReporter + from zope.interface import classImplements + + classImplements(TestCaseFunction, IReporter) + classImplements_has_run = True def excstore( self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None @@ -393,20 +413,13 @@ def excstore( Failure__init__(self, exc_value, exc_type, exc_tb) ut.Failure.__init__ = excstore - yield - ut.Failure.__init__ = Failure__init__ + try: + res = yield + finally: + ut.Failure.__init__ = Failure__init__ else: - yield - - -def check_testcase_implements_trial_reporter(done: List[int] = []) -> None: - if done: - return - from zope.interface import classImplements - from twisted.trial.itrial import IReporter - - classImplements(TestCaseFunction, IReporter) - done.append(1) + res = yield + return res def _is_skipped(obj) -> bool: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/unraisableexception.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/unraisableexception.py index fcb5d8237c16e..f649267abf107 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/unraisableexception.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/unraisableexception.py @@ -1,12 +1,12 @@ import sys import traceback -import warnings from types import TracebackType from typing import Any from typing import Callable from typing import Generator from typing import Optional from typing import Type +import warnings import pytest @@ -61,33 +61,35 @@ def __exit__( def unraisable_exception_runtest_hook() -> Generator[None, None, None]: with catch_unraisable_exception() as cm: - yield - if cm.unraisable: - if cm.unraisable.err_msg is not None: - err_msg = cm.unraisable.err_msg - else: - err_msg = "Exception ignored in" - msg = f"{err_msg}: {cm.unraisable.object!r}\n\n" - msg += "".join( - traceback.format_exception( - cm.unraisable.exc_type, - cm.unraisable.exc_value, - cm.unraisable.exc_traceback, + try: + yield + finally: + if cm.unraisable: + if cm.unraisable.err_msg is not None: + err_msg = cm.unraisable.err_msg + else: + err_msg = "Exception ignored in" + msg = f"{err_msg}: {cm.unraisable.object!r}\n\n" + msg += "".join( + traceback.format_exception( + cm.unraisable.exc_type, + cm.unraisable.exc_value, + cm.unraisable.exc_traceback, + ) ) - ) - warnings.warn(pytest.PytestUnraisableExceptionWarning(msg)) + warnings.warn(pytest.PytestUnraisableExceptionWarning(msg)) -@pytest.hookimpl(hookwrapper=True, tryfirst=True) +@pytest.hookimpl(wrapper=True, tryfirst=True) def pytest_runtest_setup() -> Generator[None, None, None]: yield from unraisable_exception_runtest_hook() -@pytest.hookimpl(hookwrapper=True, tryfirst=True) +@pytest.hookimpl(wrapper=True, tryfirst=True) def pytest_runtest_call() -> Generator[None, None, None]: yield from unraisable_exception_runtest_hook() -@pytest.hookimpl(hookwrapper=True, tryfirst=True) +@pytest.hookimpl(wrapper=True, tryfirst=True) def pytest_runtest_teardown() -> Generator[None, None, None]: yield from unraisable_exception_runtest_hook() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/w3c-import.log new file mode 100644 index 0000000000000..af3b067776a86 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/w3c-import.log @@ -0,0 +1,62 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_argcomplete.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/_version.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/cacheprovider.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/capture.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/compat.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/debugging.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/deprecated.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/doctest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/faulthandler.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/fixtures.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/freeze_support.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/helpconfig.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/hookspec.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/junitxml.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/legacypath.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/logging.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/main.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/monkeypatch.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/nodes.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/outcomes.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pastebin.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pathlib.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/py.typed +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pytester.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/pytester_assertions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/python.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/python_api.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/python_path.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/recwarn.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/reports.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/runner.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/scope.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/setuponly.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/setupplan.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/skipping.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/stash.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/stepwise.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/terminal.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/threadexception.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/timing.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/tmpdir.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/unittest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/unraisableexception.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/warning_types.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/warnings.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/warning_types.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/warning_types.py index 2a97a31978982..a5884f295827f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/warning_types.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/warning_types.py @@ -1,11 +1,12 @@ +import dataclasses +import inspect +from types import FunctionType from typing import Any +from typing import final from typing import Generic from typing import Type from typing import TypeVar - -import attr - -from _pytest.compat import final +import warnings class PytestWarning(UserWarning): @@ -48,16 +49,14 @@ class PytestDeprecationWarning(PytestWarning, DeprecationWarning): __module__ = "pytest" -@final -class PytestRemovedIn7Warning(PytestDeprecationWarning): - """Warning class for features that will be removed in pytest 7.""" +class PytestRemovedIn9Warning(PytestDeprecationWarning): + """Warning class for features that will be removed in pytest 9.""" __module__ = "pytest" -@final -class PytestRemovedIn8Warning(PytestDeprecationWarning): - """Warning class for features that will be removed in pytest 8.""" +class PytestReturnNotNoneWarning(PytestWarning): + """Warning emitted when a test function is returning value other than None.""" __module__ = "pytest" @@ -74,15 +73,11 @@ class PytestExperimentalApiWarning(PytestWarning, FutureWarning): @classmethod def simple(cls, apiname: str) -> "PytestExperimentalApiWarning": - return cls( - "{apiname} is an experimental api that may change over time".format( - apiname=apiname - ) - ) + return cls(f"{apiname} is an experimental api that may change over time") @final -class PytestUnhandledCoroutineWarning(PytestWarning): +class PytestUnhandledCoroutineWarning(PytestReturnNotNoneWarning): """Warning emitted for an unhandled coroutine. A coroutine was encountered when collecting test functions, but was not @@ -129,7 +124,7 @@ class PytestUnhandledThreadExceptionWarning(PytestWarning): @final -@attr.s(auto_attribs=True) +@dataclasses.dataclass class UnformattedWarning(Generic[_W]): """A warning meant to be formatted during runtime. @@ -143,3 +138,28 @@ class UnformattedWarning(Generic[_W]): def format(self, **kwargs: Any) -> _W: """Return an instance of the warning category, formatted with given kwargs.""" return self.category(self.template.format(**kwargs)) + + +def warn_explicit_for(method: FunctionType, message: PytestWarning) -> None: + """ + Issue the warning :param:`message` for the definition of the given :param:`method` + + this helps to log warnings for functions defined prior to finding an issue with them + (like hook wrappers being marked in a legacy mechanism) + """ + lineno = method.__code__.co_firstlineno + filename = inspect.getfile(method) + module = method.__module__ + mod_globals = method.__globals__ + try: + warnings.warn_explicit( + message, + type(message), + filename=filename, + module=module, + registry=mod_globals.setdefault("__warningregistry__", {}), + lineno=lineno, + ) + except Warning as w: + # If warnings are errors (e.g. -Werror), location information gets lost, so we add it to the message. + raise type(w)(f"{w}\n at {filename}:{lineno}") from None diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/warnings.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/warnings.py index c0c946cbde5c1..22590892f8d8a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/warnings.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/_pytest/warnings.py @@ -1,20 +1,18 @@ -import sys -import warnings +# mypy: allow-untyped-defs from contextlib import contextmanager +import sys from typing import Generator +from typing import Literal from typing import Optional -from typing import TYPE_CHECKING +import warnings -import pytest 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 - -if TYPE_CHECKING: - from typing_extensions import Literal +import pytest def pytest_configure(config: Config) -> None: @@ -29,7 +27,7 @@ def pytest_configure(config: Config) -> None: def catch_warnings_for_item( config: Config, ihook, - when: "Literal['config', 'collect', 'runtest']", + when: Literal["config", "collect", "runtest"], item: Optional[Item], ) -> Generator[None, None, None]: """Context manager that catches warnings generated in the contained execution block. @@ -49,7 +47,8 @@ def catch_warnings_for_item( warnings.filterwarnings("always", category=DeprecationWarning) warnings.filterwarnings("always", category=PendingDeprecationWarning) - warnings.filterwarnings("error", category=pytest.PytestRemovedIn7Warning) + # To be enabled in pytest 9.0.0. + # warnings.filterwarnings("error", category=pytest.PytestRemovedIn9Warning) apply_warning_filters(config_filters, cmdline_filters) @@ -60,25 +59,18 @@ def catch_warnings_for_item( for arg in mark.args: 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, - location=None, - ) - ) - ihook.pytest_warning_recorded.call_historic( - kwargs=dict( - warning_message=warning_message, - nodeid=nodeid, - when=when, - location=None, + try: + yield + finally: + for warning_message in log: + 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: warnings.WarningMessage) -> str: @@ -91,27 +83,44 @@ def warning_record_to_str(warning_message: warnings.WarningMessage) -> str: warning_message.lineno, warning_message.line, ) + if warning_message.source is not None: + try: + import tracemalloc + except ImportError: + pass + else: + tb = tracemalloc.get_object_traceback(warning_message.source) + if tb is not None: + formatted_tb = "\n".join(tb.format()) + # Use a leading new line to better separate the (large) output + # from the traceback to the previous warning text. + msg += f"\nObject allocated at:\n{formatted_tb}" + else: + # No need for a leading new line. + url = "https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings" + msg += "Enable tracemalloc to get traceback where the object was allocated.\n" + msg += f"See {url} for more info." return msg -@pytest.hookimpl(hookwrapper=True, tryfirst=True) -def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: +@pytest.hookimpl(wrapper=True, tryfirst=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: with catch_warnings_for_item( config=item.config, ihook=item.ihook, when="runtest", item=item ): - yield + return (yield) -@pytest.hookimpl(hookwrapper=True, tryfirst=True) -def pytest_collection(session: Session) -> Generator[None, None, None]: +@pytest.hookimpl(wrapper=True, tryfirst=True) +def pytest_collection(session: Session) -> Generator[None, object, object]: config = session.config with catch_warnings_for_item( config=config, ihook=config.hook, when="collect", item=None ): - yield + return (yield) -@pytest.hookimpl(hookwrapper=True) +@pytest.hookimpl(wrapper=True) def pytest_terminal_summary( terminalreporter: TerminalReporter, ) -> Generator[None, None, None]: @@ -119,23 +128,23 @@ def pytest_terminal_summary( with catch_warnings_for_item( config=config, ihook=config.hook, when="config", item=None ): - yield + return (yield) -@pytest.hookimpl(hookwrapper=True) +@pytest.hookimpl(wrapper=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 + return (yield) -@pytest.hookimpl(hookwrapper=True) +@pytest.hookimpl(wrapper=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 + return (yield) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/py.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/py.py new file mode 100644 index 0000000000000..d1c39d203a8e5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/py.py @@ -0,0 +1,13 @@ +# shim for pylib going away +# if pylib is installed this file will get skipped +# (`py/__init__.py` has higher precedence) +import sys + +import _pytest._py.error as error +import _pytest._py.path as path + + +sys.modules["py.error"] = error +sys.modules["py.path"] = path + +__all__ = ["error", "path"] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/pytest/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/pytest/__init__.py index 6050fd112481d..c6b6de827e960 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/pytest/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/pytest/__init__.py @@ -1,6 +1,6 @@ # PYTHON_ARGCOMPLETE_OK """pytest: unit and functional testing with Python.""" -from . import collect + from _pytest import __version__ from _pytest import version_tuple from _pytest._code import ExceptionInfo @@ -19,8 +19,9 @@ from _pytest.config.argparsing import OptionGroup from _pytest.config.argparsing import Parser from _pytest.debugging import pytestPDB as __pytestPDB -from _pytest.fixtures import _fillfuncargs +from _pytest.doctest import DoctestItem from _pytest.fixtures import fixture +from _pytest.fixtures import FixtureDef from _pytest.fixtures import FixtureLookupError from _pytest.fixtures import FixtureRequest from _pytest.fixtures import yield_fixture @@ -28,6 +29,7 @@ from _pytest.legacypath import TempdirFactory from _pytest.legacypath import Testdir from _pytest.logging import LogCaptureFixture +from _pytest.main import Dir from _pytest.main import Session from _pytest.mark import Mark from _pytest.mark import MARK_GEN as mark @@ -36,6 +38,7 @@ from _pytest.mark import param from _pytest.monkeypatch import MonkeyPatch from _pytest.nodes import Collector +from _pytest.nodes import Directory from _pytest.nodes import File from _pytest.nodes import Item from _pytest.outcomes import exit @@ -63,6 +66,7 @@ from _pytest.runner import CallInfo from _pytest.stash import Stash from _pytest.stash import StashKey +from _pytest.terminal import TestShortLogReport from _pytest.tmpdir import TempPathFactory from _pytest.warning_types import PytestAssertRewriteWarning from _pytest.warning_types import PytestCacheWarning @@ -70,38 +74,41 @@ from _pytest.warning_types import PytestConfigWarning from _pytest.warning_types import PytestDeprecationWarning from _pytest.warning_types import PytestExperimentalApiWarning -from _pytest.warning_types import PytestRemovedIn7Warning -from _pytest.warning_types import PytestRemovedIn8Warning +from _pytest.warning_types import PytestRemovedIn9Warning +from _pytest.warning_types import PytestReturnNotNoneWarning from _pytest.warning_types import PytestUnhandledCoroutineWarning from _pytest.warning_types import PytestUnhandledThreadExceptionWarning from _pytest.warning_types import PytestUnknownMarkWarning from _pytest.warning_types import PytestUnraisableExceptionWarning from _pytest.warning_types import PytestWarning + set_trace = __pytestPDB.set_trace __all__ = [ "__version__", - "_fillfuncargs", "approx", "Cache", "CallInfo", "CaptureFixture", "Class", "cmdline", - "collect", "Collector", "CollectReport", "Config", "console_main", "deprecated_call", + "Dir", + "Directory", + "DoctestItem", "exit", "ExceptionInfo", "ExitCode", "fail", "File", "fixture", + "FixtureDef", "FixtureLookupError", "FixtureRequest", "freeze_includes", @@ -131,8 +138,8 @@ "PytestConfigWarning", "PytestDeprecationWarning", "PytestExperimentalApiWarning", - "PytestRemovedIn7Warning", - "PytestRemovedIn8Warning", + "PytestRemovedIn9Warning", + "PytestReturnNotNoneWarning", "Pytester", "PytestPluginManager", "PytestUnhandledCoroutineWarning", @@ -154,18 +161,10 @@ "TempPathFactory", "Testdir", "TestReport", + "TestShortLogReport", "UsageError", "WarningsRecorder", "warns", "xfail", "yield_fixture", ] - - -def __getattr__(name: str) -> object: - if name == "Instance": - # The import emits a deprecation warning. - from _pytest.python import Instance - - return Instance - raise AttributeError(f"module {__name__} has no attribute {name}") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/pytest/__main__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/pytest/__main__.py index b170152937b38..e4cb67d5dd52c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/pytest/__main__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/pytest/__main__.py @@ -1,5 +1,7 @@ """The pytest entry point.""" + import pytest + if __name__ == "__main__": raise SystemExit(pytest.console_main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/pytest/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/pytest/w3c-import.log new file mode 100644 index 0000000000000..a0bd308397714 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/pytest/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/pytest/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/pytest/__main__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/pytest/py.typed diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/w3c-import.log new file mode 100644 index 0000000000000..8bede2545164c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/src/py.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/_py/test_local.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/_py/test_local.py new file mode 100644 index 0000000000000..1b5b344551c17 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/_py/test_local.py @@ -0,0 +1,1581 @@ +# mypy: allow-untyped-defs +import contextlib +import multiprocessing +import os +import sys +import time +from unittest import mock +import warnings + +from py import error +from py.path import local + +import pytest + + +@contextlib.contextmanager +def ignore_encoding_warning(): + with warnings.catch_warnings(): + if sys.version_info > (3, 10): + warnings.simplefilter("ignore", EncodingWarning) + yield + + +class CommonFSTests: + def test_constructor_equality(self, path1): + p = path1.__class__(path1) + assert p == path1 + + def test_eq_nonstring(self, path1): + p1 = path1.join("sampledir") + p2 = path1.join("sampledir") + assert p1 == p2 + + def test_new_identical(self, path1): + assert path1 == path1.new() + + def test_join(self, path1): + p = path1.join("sampledir") + strp = str(p) + assert strp.endswith("sampledir") + assert strp.startswith(str(path1)) + + def test_join_normalized(self, path1): + newpath = path1.join(path1.sep + "sampledir") + strp = str(newpath) + assert strp.endswith("sampledir") + assert strp.startswith(str(path1)) + newpath = path1.join((path1.sep * 2) + "sampledir") + strp = str(newpath) + assert strp.endswith("sampledir") + assert strp.startswith(str(path1)) + + def test_join_noargs(self, path1): + newpath = path1.join() + assert path1 == newpath + + def test_add_something(self, path1): + p = path1.join("sample") + p = p + "dir" + assert p.check() + assert p.exists() + assert p.isdir() + assert not p.isfile() + + def test_parts(self, path1): + newpath = path1.join("sampledir", "otherfile") + par = newpath.parts()[-3:] + assert par == [path1, path1.join("sampledir"), newpath] + + revpar = newpath.parts(reverse=True)[:3] + assert revpar == [newpath, path1.join("sampledir"), path1] + + def test_common(self, path1): + other = path1.join("sampledir") + x = other.common(path1) + assert x == path1 + + # def test_parents_nonexisting_file(self, path1): + # newpath = path1 / 'dirnoexist' / 'nonexisting file' + # par = list(newpath.parents()) + # assert par[:2] == [path1 / 'dirnoexist', path1] + + def test_basename_checks(self, path1): + newpath = path1.join("sampledir") + assert newpath.check(basename="sampledir") + assert newpath.check(notbasename="xyz") + assert newpath.basename == "sampledir" + + def test_basename(self, path1): + newpath = path1.join("sampledir") + assert newpath.check(basename="sampledir") + assert newpath.basename, "sampledir" + + def test_dirname(self, path1): + newpath = path1.join("sampledir") + assert newpath.dirname == str(path1) + + def test_dirpath(self, path1): + newpath = path1.join("sampledir") + assert newpath.dirpath() == path1 + + def test_dirpath_with_args(self, path1): + newpath = path1.join("sampledir") + assert newpath.dirpath("x") == path1.join("x") + + def test_newbasename(self, path1): + newpath = path1.join("samplefile") + newbase = newpath.new(basename="samplefile2") + assert newbase.basename == "samplefile2" + assert newbase.dirpath() == newpath.dirpath() + + def test_not_exists(self, path1): + assert not path1.join("does_not_exist").check() + assert path1.join("does_not_exist").check(exists=0) + + def test_exists(self, path1): + assert path1.join("samplefile").check() + assert path1.join("samplefile").check(exists=1) + assert path1.join("samplefile").exists() + assert path1.join("samplefile").isfile() + assert not path1.join("samplefile").isdir() + + def test_dir(self, path1): + # print repr(path1.join("sampledir")) + assert path1.join("sampledir").check(dir=1) + assert path1.join("samplefile").check(notdir=1) + assert not path1.join("samplefile").check(dir=1) + assert path1.join("samplefile").exists() + assert not path1.join("samplefile").isdir() + assert path1.join("samplefile").isfile() + + def test_fnmatch_file(self, path1): + assert path1.join("samplefile").check(fnmatch="s*e") + assert path1.join("samplefile").fnmatch("s*e") + assert not path1.join("samplefile").fnmatch("s*x") + assert not path1.join("samplefile").check(fnmatch="s*x") + + # def test_fnmatch_dir(self, path1): + + # pattern = path1.sep.join(['s*file']) + # sfile = path1.join("samplefile") + # assert sfile.check(fnmatch=pattern) + + def test_relto(self, path1): + p = path1.join("sampledir", "otherfile") + assert p.relto(path1) == p.sep.join(["sampledir", "otherfile"]) + assert p.check(relto=path1) + assert path1.check(notrelto=p) + assert not path1.check(relto=p) + + def test_bestrelpath(self, path1): + curdir = path1 + sep = curdir.sep + s = curdir.bestrelpath(curdir) + assert s == "." + s = curdir.bestrelpath(curdir.join("hello", "world")) + assert s == "hello" + sep + "world" + + s = curdir.bestrelpath(curdir.dirpath().join("sister")) + assert s == ".." + sep + "sister" + assert curdir.bestrelpath(curdir.dirpath()) == ".." + + assert curdir.bestrelpath("hello") == "hello" + + def test_relto_not_relative(self, path1): + l1 = path1.join("bcde") + l2 = path1.join("b") + assert not l1.relto(l2) + assert not l2.relto(l1) + + def test_listdir(self, path1): + p = path1.listdir() + assert path1.join("sampledir") in p + assert path1.join("samplefile") in p + with pytest.raises(error.ENOTDIR): + path1.join("samplefile").listdir() + + def test_listdir_fnmatchstring(self, path1): + p = path1.listdir("s*dir") + assert len(p) + assert p[0], path1.join("sampledir") + + def test_listdir_filter(self, path1): + p = path1.listdir(lambda x: x.check(dir=1)) + assert path1.join("sampledir") in p + assert path1.join("samplefile") not in p + + def test_listdir_sorted(self, path1): + p = path1.listdir(lambda x: x.check(basestarts="sample"), sort=True) + assert path1.join("sampledir") == p[0] + assert path1.join("samplefile") == p[1] + assert path1.join("samplepickle") == p[2] + + def test_visit_nofilter(self, path1): + lst = [] + for i in path1.visit(): + lst.append(i.relto(path1)) + assert "sampledir" in lst + assert path1.sep.join(["sampledir", "otherfile"]) in lst + + def test_visit_norecurse(self, path1): + lst = [] + for i in path1.visit(None, lambda x: x.basename != "sampledir"): + lst.append(i.relto(path1)) + assert "sampledir" in lst + assert path1.sep.join(["sampledir", "otherfile"]) not in lst + + @pytest.mark.parametrize( + "fil", + ["*dir", "*dir", pytest.mark.skip("sys.version_info <" " (3,6)")(b"*dir")], + ) + def test_visit_filterfunc_is_string(self, path1, fil): + lst = [] + for i in path1.visit(fil): + lst.append(i.relto(path1)) + assert len(lst), 2 + assert "sampledir" in lst + assert "otherdir" in lst + + def test_visit_ignore(self, path1): + p = path1.join("nonexisting") + assert list(p.visit(ignore=error.ENOENT)) == [] + + def test_visit_endswith(self, path1): + p = [] + for i in path1.visit(lambda x: x.check(endswith="file")): + p.append(i.relto(path1)) + assert path1.sep.join(["sampledir", "otherfile"]) in p + assert "samplefile" in p + + def test_cmp(self, path1): + path1 = path1.join("samplefile") + path2 = path1.join("samplefile2") + assert (path1 < path2) == ("samplefile" < "samplefile2") + assert not (path1 < path1) + + def test_simple_read(self, path1): + with ignore_encoding_warning(): + x = path1.join("samplefile").read("r") + assert x == "samplefile\n" + + def test_join_div_operator(self, path1): + newpath = path1 / "/sampledir" / "/test//" + newpath2 = path1.join("sampledir", "test") + assert newpath == newpath2 + + def test_ext(self, path1): + newpath = path1.join("sampledir.ext") + assert newpath.ext == ".ext" + newpath = path1.join("sampledir") + assert not newpath.ext + + def test_purebasename(self, path1): + newpath = path1.join("samplefile.py") + assert newpath.purebasename == "samplefile" + + def test_multiple_parts(self, path1): + newpath = path1.join("samplefile.py") + dirname, purebasename, basename, ext = newpath._getbyspec( + "dirname,purebasename,basename,ext" + ) + assert str(path1).endswith(dirname) # be careful with win32 'drive' + assert purebasename == "samplefile" + assert basename == "samplefile.py" + assert ext == ".py" + + def test_dotted_name_ext(self, path1): + newpath = path1.join("a.b.c") + ext = newpath.ext + assert ext == ".c" + assert newpath.ext == ".c" + + def test_newext(self, path1): + newpath = path1.join("samplefile.py") + newext = newpath.new(ext=".txt") + assert newext.basename == "samplefile.txt" + assert newext.purebasename == "samplefile" + + def test_readlines(self, path1): + fn = path1.join("samplefile") + with ignore_encoding_warning(): + contents = fn.readlines() + assert contents == ["samplefile\n"] + + def test_readlines_nocr(self, path1): + fn = path1.join("samplefile") + with ignore_encoding_warning(): + contents = fn.readlines(cr=0) + assert contents == ["samplefile", ""] + + def test_file(self, path1): + assert path1.join("samplefile").check(file=1) + + def test_not_file(self, path1): + assert not path1.join("sampledir").check(file=1) + assert path1.join("sampledir").check(file=0) + + def test_non_existent(self, path1): + assert path1.join("sampledir.nothere").check(dir=0) + assert path1.join("sampledir.nothere").check(file=0) + assert path1.join("sampledir.nothere").check(notfile=1) + assert path1.join("sampledir.nothere").check(notdir=1) + assert path1.join("sampledir.nothere").check(notexists=1) + assert not path1.join("sampledir.nothere").check(notfile=0) + + # pattern = path1.sep.join(['s*file']) + # sfile = path1.join("samplefile") + # assert sfile.check(fnmatch=pattern) + + def test_size(self, path1): + url = path1.join("samplefile") + assert url.size() > len("samplefile") + + def test_mtime(self, path1): + url = path1.join("samplefile") + assert url.mtime() > 0 + + def test_relto_wrong_type(self, path1): + with pytest.raises(TypeError): + path1.relto(42) + + def test_load(self, path1): + p = path1.join("samplepickle") + obj = p.load() + assert type(obj) is dict + assert obj.get("answer", None) == 42 + + def test_visit_filesonly(self, path1): + p = [] + for i in path1.visit(lambda x: x.check(file=1)): + p.append(i.relto(path1)) + assert "sampledir" not in p + assert path1.sep.join(["sampledir", "otherfile"]) in p + + def test_visit_nodotfiles(self, path1): + p = [] + for i in path1.visit(lambda x: x.check(dotfile=0)): + p.append(i.relto(path1)) + assert "sampledir" in p + assert path1.sep.join(["sampledir", "otherfile"]) in p + assert ".dotfile" not in p + + def test_visit_breadthfirst(self, path1): + lst = [] + for i in path1.visit(bf=True): + lst.append(i.relto(path1)) + for i, p in enumerate(lst): + if path1.sep in p: + for j in range(i, len(lst)): + assert path1.sep in lst[j] + break + else: + pytest.fail("huh") + + def test_visit_sort(self, path1): + lst = [] + for i in path1.visit(bf=True, sort=True): + lst.append(i.relto(path1)) + for i, p in enumerate(lst): + if path1.sep in p: + break + assert lst[:i] == sorted(lst[:i]) + assert lst[i:] == sorted(lst[i:]) + + def test_endswith(self, path1): + def chk(p): + return p.check(endswith="pickle") + + assert not chk(path1) + assert not chk(path1.join("samplefile")) + assert chk(path1.join("somepickle")) + + def test_copy_file(self, path1): + otherdir = path1.join("otherdir") + initpy = otherdir.join("__init__.py") + copied = otherdir.join("copied") + initpy.copy(copied) + try: + assert copied.check() + s1 = initpy.read_text(encoding="utf-8") + s2 = copied.read_text(encoding="utf-8") + assert s1 == s2 + finally: + if copied.check(): + copied.remove() + + def test_copy_dir(self, path1): + otherdir = path1.join("otherdir") + copied = path1.join("newdir") + try: + otherdir.copy(copied) + assert copied.check(dir=1) + assert copied.join("__init__.py").check(file=1) + s1 = otherdir.join("__init__.py").read_text(encoding="utf-8") + s2 = copied.join("__init__.py").read_text(encoding="utf-8") + assert s1 == s2 + finally: + if copied.check(dir=1): + copied.remove(rec=1) + + def test_remove_file(self, path1): + d = path1.ensure("todeleted") + assert d.check() + d.remove() + assert not d.check() + + def test_remove_dir_recursive_by_default(self, path1): + d = path1.ensure("to", "be", "deleted") + assert d.check() + p = path1.join("to") + p.remove() + assert not p.check() + + def test_ensure_dir(self, path1): + b = path1.ensure_dir("001", "002") + assert b.basename == "002" + assert b.isdir() + + def test_mkdir_and_remove(self, path1): + tmpdir = path1 + with pytest.raises(error.EEXIST): + tmpdir.mkdir("sampledir") + new = tmpdir.join("mktest1") + new.mkdir() + assert new.check(dir=1) + new.remove() + + new = tmpdir.mkdir("mktest") + assert new.check(dir=1) + new.remove() + assert tmpdir.join("mktest") == new + + def test_move_file(self, path1): + p = path1.join("samplefile") + newp = p.dirpath("moved_samplefile") + p.move(newp) + try: + assert newp.check(file=1) + assert not p.check() + finally: + dp = newp.dirpath() + if hasattr(dp, "revert"): + dp.revert() + else: + newp.move(p) + assert p.check() + + def test_move_dir(self, path1): + source = path1.join("sampledir") + dest = path1.join("moveddir") + source.move(dest) + assert dest.check(dir=1) + assert dest.join("otherfile").check(file=1) + assert not source.join("sampledir").check() + + def test_fspath_protocol_match_strpath(self, path1): + assert path1.__fspath__() == path1.strpath + + def test_fspath_func_match_strpath(self, path1): + from os import fspath + + assert fspath(path1) == path1.strpath + + @pytest.mark.skip("sys.version_info < (3,6)") + def test_fspath_open(self, path1): + f = path1.join("opentestfile") + open(f) + + @pytest.mark.skip("sys.version_info < (3,6)") + def test_fspath_fsencode(self, path1): + from os import fsencode + + assert fsencode(path1) == fsencode(path1.strpath) + + +def setuptestfs(path): + if path.join("samplefile").check(): + return + # print "setting up test fs for", repr(path) + samplefile = path.ensure("samplefile") + samplefile.write_text("samplefile\n", encoding="utf-8") + + execfile = path.ensure("execfile") + execfile.write_text("x=42", encoding="utf-8") + + execfilepy = path.ensure("execfile.py") + execfilepy.write_text("x=42", encoding="utf-8") + + 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_text("from .b import stuff as result\n", encoding="utf-8") + module_b = otherdir.ensure("b.py") + module_b.write_text('stuff="got it"\n', encoding="utf-8") + module_c = otherdir.ensure("c.py") + module_c.write_text( + """import py; +import otherdir.a +value = otherdir.a.result +""", + encoding="utf-8", + ) + module_d = otherdir.ensure("d.py") + module_d.write_text( + """import py; +from otherdir import a +value2 = a.result +""", + encoding="utf-8", + ) + + +win32only = pytest.mark.skipif( + "not (sys.platform == 'win32' or getattr(os, '_name', None) == 'nt')" +) +skiponwin32 = pytest.mark.skipif( + "sys.platform == 'win32' or getattr(os, '_name', None) == 'nt'" +) + +ATIME_RESOLUTION = 0.01 + + +@pytest.fixture(scope="session") +def path1(tmpdir_factory): + path = tmpdir_factory.mktemp("path") + setuptestfs(path) + yield path + assert path.join("samplefile").check() + + +@pytest.fixture +def fake_fspath_obj(request): + class FakeFSPathClass: + def __init__(self, path): + self._path = path + + def __fspath__(self): + return self._path + + return FakeFSPathClass(os.path.join("this", "is", "a", "fake", "path")) + + +def batch_make_numbered_dirs(rootdir, repeats): + for i in range(repeats): + dir_ = local.make_numbered_dir(prefix="repro-", rootdir=rootdir) + file_ = dir_.join("foo") + file_.write_text("%s" % i, encoding="utf-8") + actual = int(file_.read_text(encoding="utf-8")) + assert ( + actual == i + ), f"int(file_.read_text(encoding='utf-8')) is {actual} instead of {i}" + dir_.join(".lock").remove(ignore_errors=True) + return True + + +class TestLocalPath(CommonFSTests): + def test_join_normpath(self, tmpdir): + assert tmpdir.join(".") == tmpdir + p = tmpdir.join("../%s" % tmpdir.basename) + assert p == tmpdir + p = tmpdir.join("..//%s/" % tmpdir.basename) + assert p == tmpdir + + @skiponwin32 + def test_dirpath_abs_no_abs(self, tmpdir): + p = tmpdir.join("foo") + assert p.dirpath("/bar") == tmpdir.join("bar") + assert tmpdir.dirpath("/bar", abs=True) == local("/bar") + + def test_gethash(self, tmpdir): + from hashlib import md5 + from hashlib import sha1 as sha + + fn = tmpdir.join("testhashfile") + data = b"hello" + fn.write(data, mode="wb") + assert fn.computehash("md5") == md5(data).hexdigest() + assert fn.computehash("sha1") == sha(data).hexdigest() + with pytest.raises(ValueError): + fn.computehash("asdasd") + + def test_remove_removes_readonly_file(self, tmpdir): + readonly_file = tmpdir.join("readonly").ensure() + readonly_file.chmod(0) + readonly_file.remove() + assert not readonly_file.check(exists=1) + + def test_remove_removes_readonly_dir(self, tmpdir): + readonly_dir = tmpdir.join("readonlydir").ensure(dir=1) + readonly_dir.chmod(int("500", 8)) + readonly_dir.remove() + assert not readonly_dir.check(exists=1) + + def test_remove_removes_dir_and_readonly_file(self, tmpdir): + readonly_dir = tmpdir.join("readonlydir").ensure(dir=1) + readonly_file = readonly_dir.join("readonlyfile").ensure() + readonly_file.chmod(0) + readonly_dir.remove() + assert not readonly_dir.check(exists=1) + + def test_remove_routes_ignore_errors(self, tmpdir, monkeypatch): + lst = [] + monkeypatch.setattr("shutil.rmtree", lambda *args, **kwargs: lst.append(kwargs)) + tmpdir.remove() + assert not lst[0]["ignore_errors"] + for val in (True, False): + lst[:] = [] + tmpdir.remove(ignore_errors=val) + assert lst[0]["ignore_errors"] == val + + def test_initialize_curdir(self): + assert str(local()) == os.getcwd() + + @skiponwin32 + def test_chdir_gone(self, path1): + p = path1.ensure("dir_to_be_removed", dir=1) + p.chdir() + p.remove() + pytest.raises(error.ENOENT, local) + assert path1.chdir() is None + assert os.getcwd() == str(path1) + + with pytest.raises(error.ENOENT): + with p.as_cwd(): + raise NotImplementedError + + @skiponwin32 + def test_chdir_gone_in_as_cwd(self, path1): + p = path1.ensure("dir_to_be_removed", dir=1) + p.chdir() + p.remove() + + with path1.as_cwd() as old: + assert old is None + + def test_as_cwd(self, path1): + dir = path1.ensure("subdir", dir=1) + old = local() + with dir.as_cwd() as x: + assert x == old + assert local() == dir + assert os.getcwd() == str(old) + + def test_as_cwd_exception(self, path1): + old = local() + dir = path1.ensure("subdir", dir=1) + with pytest.raises(ValueError): + with dir.as_cwd(): + raise ValueError() + assert old == local() + + def test_initialize_reldir(self, path1): + with path1.as_cwd(): + p = local("samplefile") + assert p.check() + + def test_tilde_expansion(self, monkeypatch, tmpdir): + monkeypatch.setenv("HOME", str(tmpdir)) + p = local("~", expanduser=True) + assert p == os.path.expanduser("~") + + @pytest.mark.skipif( + not sys.platform.startswith("win32"), reason="case-insensitive only on windows" + ) + def test_eq_hash_are_case_insensitive_on_windows(self): + a = local("/some/path") + b = local("/some/PATH") + assert a == b + assert hash(a) == hash(b) + assert a in {b} + assert a in {b: "b"} + + def test_eq_with_strings(self, path1): + path1 = path1.join("sampledir") + path2 = str(path1) + assert path1 == path2 + assert path2 == path1 + path3 = path1.join("samplefile") + assert path3 != path2 + assert path2 != path3 + + def test_eq_with_none(self, path1): + assert path1 != None # noqa: E711 + + def test_eq_non_ascii_unicode(self, path1): + path2 = path1.join("temp") + path3 = path1.join("ação") + path4 = path1.join("ディレクトリ") + + assert path2 != path3 + assert path2 != path4 + assert path4 != path3 + + def test_gt_with_strings(self, path1): + path2 = path1.join("sampledir") + path3 = str(path1.join("ttt")) + assert path3 > path2 + assert path2 < path3 + assert path2 < "ttt" + assert "ttt" > path2 + path4 = path1.join("aaa") + lst = [path2, path4, path3] + assert sorted(lst) == [path4, path2, path3] + + def test_open_and_ensure(self, path1): + p = path1.join("sub1", "sub2", "file") + with p.open("w", ensure=1, encoding="utf-8") as f: + f.write("hello") + assert p.read_text(encoding="utf-8") == "hello" + + def test_write_and_ensure(self, path1): + p = path1.join("sub1", "sub2", "file") + p.write_text("hello", ensure=1, encoding="utf-8") + assert p.read_text(encoding="utf-8") == "hello" + + @pytest.mark.parametrize("bin", (False, True)) + def test_dump(self, tmpdir, bin): + path = tmpdir.join("dumpfile%s" % int(bin)) + try: + d = {"answer": 42} + path.dump(d, bin=bin) + f = path.open("rb+") + import pickle + + dnew = pickle.load(f) + assert d == dnew + finally: + f.close() + + def test_setmtime(self): + import tempfile + import time + + try: + fd, name = tempfile.mkstemp() + os.close(fd) + except AttributeError: + name = tempfile.mktemp() + open(name, "w").close() + try: + mtime = int(time.time()) - 100 + path = local(name) + assert path.mtime() != mtime + path.setmtime(mtime) + assert path.mtime() == mtime + path.setmtime() + assert path.mtime() != mtime + finally: + os.remove(name) + + def test_normpath(self, path1): + new1 = path1.join("/otherdir") + new2 = path1.join("otherdir") + assert str(new1) == str(new2) + + def test_mkdtemp_creation(self): + d = local.mkdtemp() + try: + assert d.check(dir=1) + finally: + d.remove(rec=1) + + def test_tmproot(self): + d = local.mkdtemp() + tmproot = local.get_temproot() + try: + assert d.check(dir=1) + assert d.dirpath() == tmproot + finally: + d.remove(rec=1) + + def test_chdir(self, tmpdir): + old = local() + try: + res = tmpdir.chdir() + assert str(res) == str(old) + assert os.getcwd() == str(tmpdir) + finally: + old.chdir() + + def test_ensure_filepath_withdir(self, tmpdir): + newfile = tmpdir.join("test1", "test") + newfile.ensure() + assert newfile.check(file=1) + newfile.write_text("42", encoding="utf-8") + newfile.ensure() + s = newfile.read_text(encoding="utf-8") + assert s == "42" + + def test_ensure_filepath_withoutdir(self, tmpdir): + newfile = tmpdir.join("test1file") + t = newfile.ensure() + assert t == newfile + assert newfile.check(file=1) + + def test_ensure_dirpath(self, tmpdir): + newfile = tmpdir.join("test1", "testfile") + t = newfile.ensure(dir=1) + assert t == newfile + assert newfile.check(dir=1) + + def test_ensure_non_ascii_unicode(self, tmpdir): + newfile = tmpdir.join("ação", "ディレクトリ") + t = newfile.ensure(dir=1) + assert t == newfile + assert newfile.check(dir=1) + + @pytest.mark.xfail(run=False, reason="unreliable est for long filenames") + def test_long_filenames(self, tmpdir): + if sys.platform == "win32": + pytest.skip("win32: work around needed for path length limit") + # see http://codespeak.net/pipermail/py-dev/2008q2/000922.html + + # testing paths > 260 chars (which is Windows' limitation, but + # depending on how the paths are used), but > 4096 (which is the + # Linux' limitation) - the behaviour of paths with names > 4096 chars + # is undetermined + newfilename = "/test" * 60 # type:ignore[unreachable,unused-ignore] + l1 = tmpdir.join(newfilename) + l1.ensure(file=True) + l1.write_text("foo", encoding="utf-8") + l2 = tmpdir.join(newfilename) + assert l2.read_text(encoding="utf-8") == "foo" + + def test_visit_depth_first(self, tmpdir): + tmpdir.ensure("a", "1") + tmpdir.ensure("b", "2") + p3 = tmpdir.ensure("breadth") + lst = list(tmpdir.visit(lambda x: x.check(file=1))) + assert len(lst) == 3 + # check that breadth comes last + assert lst[2] == p3 + + def test_visit_rec_fnmatch(self, tmpdir): + p1 = tmpdir.ensure("a", "123") + tmpdir.ensure(".b", "345") + lst = list(tmpdir.visit("???", rec="[!.]*")) + assert len(lst) == 1 + # check that breadth comes last + assert lst[0] == p1 + + def test_fnmatch_file_abspath(self, tmpdir): + b = tmpdir.join("a", "b") + assert b.fnmatch(os.sep.join("ab")) + pattern = os.sep.join([str(tmpdir), "*", "b"]) + assert b.fnmatch(pattern) + + def test_sysfind(self): + name = sys.platform == "win32" and "cmd" or "test" + x = local.sysfind(name) + assert x.check(file=1) + assert local.sysfind("jaksdkasldqwe") is None + assert local.sysfind(name, paths=[]) is None + x2 = local.sysfind(name, paths=[x.dirpath()]) + assert x2 == x + + def test_fspath_protocol_other_class(self, fake_fspath_obj): + # py.path is always absolute + py_path = local(fake_fspath_obj) + str_path = fake_fspath_obj.__fspath__() + assert py_path.check(endswith=str_path) + assert py_path.join(fake_fspath_obj).strpath == os.path.join( + py_path.strpath, str_path + ) + + @pytest.mark.xfail( + reason="#11603", raises=(error.EEXIST, error.ENOENT), strict=False + ) + def test_make_numbered_dir_multiprocess_safe(self, tmpdir): + # https://github.com/pytest-dev/py/issues/30 + with multiprocessing.Pool() as pool: + results = [ + pool.apply_async(batch_make_numbered_dirs, [tmpdir, 100]) + for _ in range(20) + ] + for r in results: + assert r.get() + + +class TestExecutionOnWindows: + pytestmark = win32only + + def test_sysfind_bat_exe_before(self, tmpdir, monkeypatch): + monkeypatch.setenv("PATH", str(tmpdir), prepend=os.pathsep) + tmpdir.ensure("hello") + h = tmpdir.ensure("hello.bat") + x = local.sysfind("hello") + assert x == h + + +class TestExecution: + pytestmark = skiponwin32 + + def test_sysfind_no_permission_ignored(self, monkeypatch, tmpdir): + noperm = tmpdir.ensure("noperm", dir=True) + monkeypatch.setenv("PATH", str(noperm), prepend=":") + noperm.chmod(0) + try: + assert local.sysfind("jaksdkasldqwe") is None + finally: + noperm.chmod(0o644) + + def test_sysfind_absolute(self): + x = local.sysfind("test") + assert x.check(file=1) + y = local.sysfind(str(x)) + assert y.check(file=1) + assert y == x + + def test_sysfind_multiple(self, tmpdir, monkeypatch): + monkeypatch.setenv( + "PATH", "{}:{}".format(tmpdir.ensure("a"), tmpdir.join("b")), prepend=":" + ) + tmpdir.ensure("b", "a") + x = local.sysfind("a", checker=lambda x: x.dirpath().basename == "b") + assert x.basename == "a" + assert x.dirpath().basename == "b" + assert local.sysfind("a", checker=lambda x: None) is None + + def test_sysexec(self): + x = local.sysfind("ls") + out = x.sysexec("-a") + for x in local().listdir(): + assert out.find(x.basename) != -1 + + def test_sysexec_failing(self): + try: + from py._process.cmdexec import ExecutionFailed # py library + except ImportError: + ExecutionFailed = RuntimeError # py vendored + x = local.sysfind("false") + with pytest.raises(ExecutionFailed): + x.sysexec("aksjdkasjd") + + def test_make_numbered_dir(self, tmpdir): + tmpdir.ensure("base.not_an_int", dir=1) + for i in range(10): + numdir = local.make_numbered_dir( + prefix="base.", rootdir=tmpdir, keep=2, lock_timeout=0 + ) + assert numdir.check() + assert numdir.basename == "base.%d" % i + if i >= 1: + assert numdir.new(ext=str(i - 1)).check() + if i >= 2: + assert numdir.new(ext=str(i - 2)).check() + if i >= 3: + assert not numdir.new(ext=str(i - 3)).check() + + def test_make_numbered_dir_case(self, tmpdir): + """make_numbered_dir does not make assumptions on the underlying + filesystem based on the platform and will assume it _could_ be case + insensitive. + + See issues: + - https://github.com/pytest-dev/pytest/issues/708 + - https://github.com/pytest-dev/pytest/issues/3451 + """ + d1 = local.make_numbered_dir( + prefix="CAse.", + rootdir=tmpdir, + keep=2, + lock_timeout=0, + ) + d2 = local.make_numbered_dir( + prefix="caSE.", + rootdir=tmpdir, + keep=2, + lock_timeout=0, + ) + assert str(d1).lower() != str(d2).lower() + assert str(d2).endswith(".1") + + def test_make_numbered_dir_NotImplemented_Error(self, tmpdir, monkeypatch): + def notimpl(x, y): + raise NotImplementedError(42) + + monkeypatch.setattr(os, "symlink", notimpl) + x = tmpdir.make_numbered_dir(rootdir=tmpdir, lock_timeout=0) + assert x.relto(tmpdir) + assert x.check() + + def test_locked_make_numbered_dir(self, tmpdir): + for i in range(10): + numdir = local.make_numbered_dir(prefix="base2.", rootdir=tmpdir, keep=2) + assert numdir.check() + assert numdir.basename == "base2.%d" % i + for j in range(i): + assert numdir.new(ext=str(j)).check() + + def test_error_preservation(self, path1): + pytest.raises(EnvironmentError, path1.join("qwoeqiwe").mtime) + pytest.raises(EnvironmentError, path1.join("qwoeqiwe").read) + + # def test_parentdirmatch(self): + # local.parentdirmatch('std', startmodule=__name__) + # + + +class TestImport: + @pytest.fixture(autouse=True) + def preserve_sys(self): + with mock.patch.dict(sys.modules): + with mock.patch.object(sys, "path", list(sys.path)): + yield + + def test_pyimport(self, path1): + obj = path1.join("execfile.py").pyimport() + assert obj.x == 42 + assert obj.__name__ == "execfile" + + def test_pyimport_renamed_dir_creates_mismatch(self, tmpdir, monkeypatch): + p = tmpdir.ensure("a", "test_x123.py") + p.pyimport() + tmpdir.join("a").move(tmpdir.join("b")) + with pytest.raises(tmpdir.ImportMismatchError): + tmpdir.join("b", "test_x123.py").pyimport() + + # Errors can be ignored. + monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "1") + tmpdir.join("b", "test_x123.py").pyimport() + + # PY_IGNORE_IMPORTMISMATCH=0 does not ignore error. + monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "0") + with pytest.raises(tmpdir.ImportMismatchError): + tmpdir.join("b", "test_x123.py").pyimport() + + def test_pyimport_messy_name(self, tmpdir): + # http://bitbucket.org/hpk42/py-trunk/issue/129 + path = tmpdir.ensure("foo__init__.py") + path.pyimport() + + def test_pyimport_dir(self, tmpdir): + p = tmpdir.join("hello_123") + p_init = p.ensure("__init__.py") + m = p.pyimport() + assert m.__name__ == "hello_123" + m = p_init.pyimport() + assert m.__name__ == "hello_123" + + def test_pyimport_execfile_different_name(self, path1): + obj = path1.join("execfile.py").pyimport(modname="0x.y.z") + assert obj.x == 42 + assert obj.__name__ == "0x.y.z" + + def test_pyimport_a(self, path1): + otherdir = path1.join("otherdir") + mod = otherdir.join("a.py").pyimport() + assert mod.result == "got it" + assert mod.__name__ == "otherdir.a" + + def test_pyimport_b(self, path1): + otherdir = path1.join("otherdir") + mod = otherdir.join("b.py").pyimport() + assert mod.stuff == "got it" + assert mod.__name__ == "otherdir.b" + + def test_pyimport_c(self, path1): + otherdir = path1.join("otherdir") + mod = otherdir.join("c.py").pyimport() + assert mod.value == "got it" + + def test_pyimport_d(self, path1): + otherdir = path1.join("otherdir") + mod = otherdir.join("d.py").pyimport() + assert mod.value2 == "got it" + + def test_pyimport_and_import(self, tmpdir): + tmpdir.ensure("xxxpackage", "__init__.py") + mod1path = tmpdir.ensure("xxxpackage", "module1.py") + mod1 = mod1path.pyimport() + assert mod1.__name__ == "xxxpackage.module1" + from xxxpackage import module1 + + assert module1 is mod1 + + def test_pyimport_check_filepath_consistency(self, monkeypatch, tmpdir): + name = "pointsback123" + ModuleType = type(os) + p = tmpdir.ensure(name + ".py") + with monkeypatch.context() as mp: + for ending in (".pyc", "$py.class", ".pyo"): + mod = ModuleType(name) + pseudopath = tmpdir.ensure(name + ending) + mod.__file__ = str(pseudopath) + mp.setitem(sys.modules, name, mod) + newmod = p.pyimport() + assert mod == newmod + mod = ModuleType(name) + pseudopath = tmpdir.ensure(name + "123.py") + mod.__file__ = str(pseudopath) + monkeypatch.setitem(sys.modules, name, mod) + excinfo = pytest.raises(pseudopath.ImportMismatchError, p.pyimport) + modname, modfile, orig = excinfo.value.args + assert modname == name + assert modfile == pseudopath + assert orig == p + assert issubclass(pseudopath.ImportMismatchError, ImportError) + + def test_issue131_pyimport_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 = p1.pyimport() + m2 = p2.pyimport() + 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 + file1.pyimport(ensuresyspath="append") + assert str(root1) == sys.path[-1] + assert str(root1) not in sys.path[:-1] + + +class TestImportlibImport: + OPTS = {"ensuresyspath": "importlib"} + + def test_pyimport(self, path1): + obj = path1.join("execfile.py").pyimport(**self.OPTS) + assert obj.x == 42 + assert obj.__name__ == "execfile" + + def test_pyimport_dir_fails(self, tmpdir): + p = tmpdir.join("hello_123") + p.ensure("__init__.py") + with pytest.raises(ImportError): + p.pyimport(**self.OPTS) + + def test_pyimport_execfile_different_name(self, path1): + obj = path1.join("execfile.py").pyimport(modname="0x.y.z", **self.OPTS) + assert obj.x == 42 + assert obj.__name__ == "0x.y.z" + + def test_pyimport_relative_import_fails(self, path1): + otherdir = path1.join("otherdir") + with pytest.raises(ImportError): + otherdir.join("a.py").pyimport(**self.OPTS) + + def test_pyimport_doesnt_use_sys_modules(self, tmpdir): + p = tmpdir.ensure("file738jsk.py") + mod = p.pyimport(**self.OPTS) + assert mod.__name__ == "file738jsk" + assert "file738jsk" not in sys.modules + + +def test_pypkgdir(tmpdir): + pkg = tmpdir.ensure("pkg1", dir=1) + pkg.ensure("__init__.py") + pkg.ensure("subdir/__init__.py") + assert pkg.pypkgpath() == pkg + assert pkg.join("subdir", "__init__.py").pypkgpath() == pkg + + +def test_pypkgdir_unimportable(tmpdir): + pkg = tmpdir.ensure("pkg1-1", dir=1) # unimportable + pkg.ensure("__init__.py") + subdir = pkg.ensure("subdir/__init__.py").dirpath() + assert subdir.pypkgpath() == subdir + assert subdir.ensure("xyz.py").pypkgpath() == subdir + assert not pkg.pypkgpath() + + +def test_isimportable(): + try: + from py.path import isimportable # py vendored version + except ImportError: + from py._path.local import isimportable # py library + + assert not isimportable("") + assert isimportable("x") + assert isimportable("x1") + assert isimportable("x_1") + assert isimportable("_") + assert isimportable("_1") + assert not isimportable("x-1") + assert not isimportable("x:1") + + +def test_homedir_from_HOME(monkeypatch): + path = os.getcwd() + monkeypatch.setenv("HOME", path) + assert local._gethomedir() == local(path) + + +def test_homedir_not_exists(monkeypatch): + monkeypatch.delenv("HOME", raising=False) + monkeypatch.delenv("HOMEDRIVE", raising=False) + homedir = local._gethomedir() + assert homedir is None + + +def test_samefile(tmpdir): + assert tmpdir.samefile(tmpdir) + p = tmpdir.ensure("hello") + assert p.samefile(p) + with p.dirpath().as_cwd(): + assert p.samefile(p.basename) + if sys.platform == "win32": + p1 = p.__class__(str(p).lower()) + p2 = p.__class__(str(p).upper()) + assert p1.samefile(p2) + + +@pytest.mark.skipif(not hasattr(os, "symlink"), reason="os.symlink not available") +def test_samefile_symlink(tmpdir): + p1 = tmpdir.ensure("foo.txt") + p2 = tmpdir.join("linked.txt") + try: + os.symlink(str(p1), str(p2)) + except (OSError, NotImplementedError) as e: + # on Windows this might fail if the user doesn't have special symlink permissions + # pypy3 on Windows doesn't implement os.symlink and raises NotImplementedError + pytest.skip(str(e.args[0])) + + assert p1.samefile(p2) + + +def test_listdir_single_arg(tmpdir): + tmpdir.ensure("hello") + assert tmpdir.listdir("hello")[0].basename == "hello" + + +def test_mkdtemp_rootdir(tmpdir): + dtmp = local.mkdtemp(rootdir=tmpdir) + assert tmpdir.listdir() == [dtmp] + + +class TestWINLocalPath: + pytestmark = win32only + + def test_owner_group_not_implemented(self, path1): + with pytest.raises(NotImplementedError): + _ = path1.stat().owner + with pytest.raises(NotImplementedError): + _ = path1.stat().group + + def test_chmod_simple_int(self, path1): + mode = path1.stat().mode + # Ensure that we actually change the mode to something different. + path1.chmod(mode == 0 and 1 or 0) + try: + print(path1.stat().mode) + print(mode) + assert path1.stat().mode != mode + finally: + path1.chmod(mode) + assert path1.stat().mode == mode + + def test_path_comparison_lowercase_mixed(self, path1): + t1 = path1.join("a_path") + t2 = path1.join("A_path") + assert t1 == t1 + assert t1 == t2 + + def test_relto_with_mixed_case(self, path1): + t1 = path1.join("a_path", "fiLe") + t2 = path1.join("A_path") + assert t1.relto(t2) == "fiLe" + + def test_allow_unix_style_paths(self, path1): + t1 = path1.join("a_path") + assert t1 == str(path1) + "\\a_path" + t1 = path1.join("a_path/") + assert t1 == str(path1) + "\\a_path" + t1 = path1.join("dir/a_path") + assert t1 == str(path1) + "\\dir\\a_path" + + def test_sysfind_in_currentdir(self, path1): + cmd = local.sysfind("cmd") + root = cmd.new(dirname="", basename="") # c:\ in most installations + with root.as_cwd(): + x = local.sysfind(cmd.relto(root)) + assert x.check(file=1) + + def test_fnmatch_file_abspath_posix_pattern_on_win32(self, tmpdir): + # path-matching patterns might contain a posix path separator '/' + # Test that we can match that pattern on windows. + import posixpath + + b = tmpdir.join("a", "b") + assert b.fnmatch(posixpath.sep.join("ab")) + pattern = posixpath.sep.join([str(tmpdir), "*", "b"]) + assert b.fnmatch(pattern) + + +class TestPOSIXLocalPath: + pytestmark = skiponwin32 + + def test_hardlink(self, tmpdir): + linkpath = tmpdir.join("test") + filepath = tmpdir.join("file") + filepath.write_text("Hello", encoding="utf-8") + nlink = filepath.stat().nlink + linkpath.mklinkto(filepath) + assert filepath.stat().nlink == nlink + 1 + + def test_symlink_are_identical(self, tmpdir): + filepath = tmpdir.join("file") + filepath.write_text("Hello", encoding="utf-8") + linkpath = tmpdir.join("test") + linkpath.mksymlinkto(filepath) + assert linkpath.readlink() == str(filepath) + + def test_symlink_isfile(self, tmpdir): + linkpath = tmpdir.join("test") + filepath = tmpdir.join("file") + filepath.write_text("", encoding="utf-8") + linkpath.mksymlinkto(filepath) + assert linkpath.check(file=1) + assert not linkpath.check(link=0, file=1) + assert linkpath.islink() + + def test_symlink_relative(self, tmpdir): + linkpath = tmpdir.join("test") + filepath = tmpdir.join("file") + filepath.write_text("Hello", encoding="utf-8") + linkpath.mksymlinkto(filepath, absolute=False) + assert linkpath.readlink() == "file" + assert filepath.read_text(encoding="utf-8") == linkpath.read_text( + encoding="utf-8" + ) + + def test_symlink_not_existing(self, tmpdir): + linkpath = tmpdir.join("testnotexisting") + assert not linkpath.check(link=1) + assert linkpath.check(link=0) + + def test_relto_with_root(self, path1, tmpdir): + y = path1.join("x").relto(local("/")) + assert y[0] == str(path1)[1] + + def test_visit_recursive_symlink(self, tmpdir): + linkpath = tmpdir.join("test") + linkpath.mksymlinkto(tmpdir) + visitor = tmpdir.visit(None, lambda x: x.check(link=0)) + assert list(visitor) == [linkpath] + + def test_symlink_isdir(self, tmpdir): + linkpath = tmpdir.join("test") + linkpath.mksymlinkto(tmpdir) + assert linkpath.check(dir=1) + assert not linkpath.check(link=0, dir=1) + + def test_symlink_remove(self, tmpdir): + linkpath = tmpdir.join("test") + linkpath.mksymlinkto(linkpath) # point to itself + assert linkpath.check(link=1) + linkpath.remove() + assert not linkpath.check() + + def test_realpath_file(self, tmpdir): + linkpath = tmpdir.join("test") + filepath = tmpdir.join("file") + filepath.write_text("", encoding="utf-8") + linkpath.mksymlinkto(filepath) + realpath = linkpath.realpath() + assert realpath.basename == "file" + + def test_owner(self, path1, tmpdir): + from grp import getgrgid # type:ignore[attr-defined,unused-ignore] + from pwd import getpwuid # type:ignore[attr-defined,unused-ignore] + + stat = path1.stat() + assert stat.path == path1 + + uid = stat.uid + gid = stat.gid + owner = getpwuid(uid)[0] + group = getgrgid(gid)[0] + + assert uid == stat.uid + assert owner == stat.owner + assert gid == stat.gid + assert group == stat.group + + def test_stat_helpers(self, tmpdir, monkeypatch): + path1 = tmpdir.ensure("file") + stat1 = path1.stat() + stat2 = tmpdir.stat() + assert stat1.isfile() + assert stat2.isdir() + assert not stat1.islink() + assert not stat2.islink() + + def test_stat_non_raising(self, tmpdir): + path1 = tmpdir.join("file") + pytest.raises(error.ENOENT, lambda: path1.stat()) + res = path1.stat(raising=False) + assert res is None + + def test_atime(self, tmpdir): + import time + + path = tmpdir.ensure("samplefile") + now = time.time() + atime1 = path.atime() + # we could wait here but timer resolution is very + # system dependent + path.read_binary() + time.sleep(ATIME_RESOLUTION) + atime2 = path.atime() + time.sleep(ATIME_RESOLUTION) + duration = time.time() - now + assert (atime2 - atime1) <= duration + + def test_commondir(self, path1): + # XXX This is here in local until we find a way to implement this + # using the subversion command line api. + p1 = path1.join("something") + p2 = path1.join("otherthing") + assert p1.common(p2) == path1 + assert p2.common(p1) == path1 + + def test_commondir_nocommon(self, path1): + # XXX This is here in local until we find a way to implement this + # using the subversion command line api. + p1 = path1.join("something") + p2 = local(path1.sep + "blabla") + assert p1.common(p2) == "/" + + def test_join_to_root(self, path1): + root = path1.parts()[0] + assert len(str(root)) == 1 + assert str(root.join("a")) == "/a" + + def test_join_root_to_root_with_no_abs(self, path1): + nroot = path1.join("/") + assert str(path1) == str(nroot) + assert path1 == nroot + + def test_chmod_simple_int(self, path1): + mode = path1.stat().mode + path1.chmod(int(mode / 2)) + try: + assert path1.stat().mode != mode + finally: + path1.chmod(mode) + assert path1.stat().mode == mode + + def test_chmod_rec_int(self, path1): + # XXX fragile test + def recfilter(x): + return x.check(dotfile=0, link=0) + + oldmodes = {} + for x in path1.visit(rec=recfilter): + oldmodes[x] = x.stat().mode + path1.chmod(int("772", 8), rec=recfilter) + try: + for x in path1.visit(rec=recfilter): + assert x.stat().mode & int("777", 8) == int("772", 8) + finally: + for x, y in oldmodes.items(): + x.chmod(y) + + def test_copy_archiving(self, tmpdir): + unicode_fn = "something-\342\200\223.txt" + f = tmpdir.ensure("a", unicode_fn) + a = f.dirpath() + oldmode = f.stat().mode + newmode = oldmode ^ 1 + f.chmod(newmode) + b = tmpdir.join("b") + a.copy(b, mode=True) + assert b.join(f.basename).stat().mode == newmode + + def test_copy_stat_file(self, tmpdir): + src = tmpdir.ensure("src") + dst = tmpdir.join("dst") + # a small delay before the copy + time.sleep(ATIME_RESOLUTION) + src.copy(dst, stat=True) + oldstat = src.stat() + newstat = dst.stat() + assert oldstat.mode == newstat.mode + assert (dst.atime() - src.atime()) < ATIME_RESOLUTION + assert (dst.mtime() - src.mtime()) < ATIME_RESOLUTION + + def test_copy_stat_dir(self, tmpdir): + test_files = ["a", "b", "c"] + src = tmpdir.join("src") + for f in test_files: + src.join(f).write_text(f, ensure=True, encoding="utf-8") + dst = tmpdir.join("dst") + # a small delay before the copy + time.sleep(ATIME_RESOLUTION) + src.copy(dst, stat=True) + for f in test_files: + oldstat = src.join(f).stat() + newstat = dst.join(f).stat() + assert (newstat.atime - oldstat.atime) < ATIME_RESOLUTION + assert (newstat.mtime - oldstat.mtime) < ATIME_RESOLUTION + assert oldstat.mode == newstat.mode + + def test_chown_identity(self, path1): + owner = path1.stat().owner + group = path1.stat().group + path1.chown(owner, group) + + def test_chown_dangling_link(self, path1): + owner = path1.stat().owner + group = path1.stat().group + x = path1.join("hello") + x.mksymlinkto("qlwkejqwlek") + try: + path1.chown(owner, group, rec=1) + finally: + x.remove(rec=0) + + def test_chown_identity_rec_mayfail(self, path1): + owner = path1.stat().owner + group = path1.stat().group + path1.chown(owner, group) + + +class TestUnicode: + def test_join_ensure(self, tmpdir, monkeypatch): + if "LANG" not in os.environ: + pytest.skip("cannot run test without locale") + x = local(tmpdir.strpath) + part = "hällo" + y = x.ensure(part) + assert x.join(part) == y + + def test_listdir(self, tmpdir): + if "LANG" not in os.environ: + pytest.skip("cannot run test without locale") + x = local(tmpdir.strpath) + part = "hällo" + y = x.ensure(part) + assert x.listdir(part)[0] == y + + @pytest.mark.xfail(reason="changing read/write might break existing usages") + def test_read_write(self, tmpdir): + x = tmpdir.join("hello") + part = "hällo" + with ignore_encoding_warning(): + x.write(part) + assert x.read() == part + x.write(part.encode(sys.getdefaultencoding())) + assert x.read() == part.encode(sys.getdefaultencoding()) + + +class TestBinaryAndTextMethods: + def test_read_binwrite(self, tmpdir): + x = tmpdir.join("hello") + part = "hällo" + part_utf8 = part.encode("utf8") + x.write_binary(part_utf8) + assert x.read_binary() == part_utf8 + s = x.read_text(encoding="utf8") + assert s == part + assert isinstance(s, str) + + def test_read_textwrite(self, tmpdir): + x = tmpdir.join("hello") + part = "hällo" + part_utf8 = part.encode("utf8") + x.write_text(part, encoding="utf8") + assert x.read_binary() == part_utf8 + assert x.read_text(encoding="utf8") == part + + def test_default_encoding(self, tmpdir): + x = tmpdir.join("hello") + # Can't use UTF8 as the default encoding (ASCII) doesn't support it + part = "hello" + x.write_text(part, "ascii") + s = x.read_text("ascii") + assert s == part + assert type(s) is type(part) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/_py/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/_py/w3c-import.log new file mode 100644 index 0000000000000..35c46bf200601 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/_py/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/_py/test_local.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/acceptance_test.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/acceptance_test.py index 8b8d4a4a6ed45..8f001bc240165 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/acceptance_test.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/acceptance_test.py @@ -1,14 +1,16 @@ +# mypy: allow-untyped-defs +import dataclasses +import importlib.metadata import os +from pathlib import Path +import subprocess import sys import types -import attr - -import pytest -from _pytest.compat import importlib_metadata from _pytest.config import ExitCode from _pytest.pathlib import symlink_or_skip from _pytest.pytester import Pytester +import pytest def prepend_pythonpath(*dirs) -> str: @@ -115,11 +117,11 @@ def test_early_load_setuptools_name( loaded = [] - @attr.s + @dataclasses.dataclass class DummyEntryPoint: - name = attr.ib() - module = attr.ib() - group = "pytest11" + name: str + module: str + group: str = "pytest11" def load(self): __import__(self.module) @@ -132,15 +134,15 @@ def load(self): DummyEntryPoint("mycov", "mycov_module"), ] - @attr.s + @dataclasses.dataclass class DummyDist: - entry_points = attr.ib() - files = () + entry_points: object + files: object = () def my_dists(): return (DummyDist(entry_points),) - monkeypatch.setattr(importlib_metadata, "distributions", my_dists) + monkeypatch.setattr(importlib.metadata, "distributions", my_dists) params = ("-p", "mycov") if load_cov_early else () pytester.runpytest_inprocess(*params) if load_cov_early: @@ -187,7 +189,7 @@ def test_not_collectable_arguments(self, pytester: Pytester) -> None: result.stderr.fnmatch_lines( [ f"ERROR: not found: {p2}", - f"(no name {str(p2)!r} in any of [[][]])", + "(no match in any of *)", "", ] ) @@ -269,7 +271,7 @@ def test_conftest_printing_shows_if_error(self, pytester: Pytester) -> None: def test_issue109_sibling_conftests_not_loaded(self, pytester: Pytester) -> None: sub1 = pytester.mkdir("sub1") sub2 = pytester.mkdir("sub2") - sub1.joinpath("conftest.py").write_text("assert 0") + sub1.joinpath("conftest.py").write_text("assert 0", encoding="utf-8") result = pytester.runpytest(sub2) assert result.ret == ExitCode.NO_TESTS_COLLECTED sub2.joinpath("__init__.py").touch() @@ -343,6 +345,45 @@ def test_func(i): assert res.ret == 0 res.stdout.fnmatch_lines(["*1 passed*"]) + def test_direct_addressing_selects_duplicates(self, pytester: Pytester) -> None: + p = pytester.makepyfile( + """ + import pytest + + @pytest.mark.parametrize("a", [1, 2, 10, 11, 2, 1, 12, 11]) + def test_func(a): + pass + """ + ) + result = pytester.runpytest(p) + result.assert_outcomes(failed=0, passed=8) + + def test_direct_addressing_selects_duplicates_1(self, pytester: Pytester) -> None: + p = pytester.makepyfile( + """ + import pytest + + @pytest.mark.parametrize("a", [1, 2, 10, 11, 2, 1, 12, 1_1,2_1]) + def test_func(a): + pass + """ + ) + result = pytester.runpytest(p) + result.assert_outcomes(failed=0, passed=9) + + def test_direct_addressing_selects_duplicates_2(self, pytester: Pytester) -> None: + p = pytester.makepyfile( + """ + import pytest + + @pytest.mark.parametrize("a", ["a","b","c","a","a1"]) + def test_func(a): + pass + """ + ) + result = pytester.runpytest(p) + result.assert_outcomes(failed=0, passed=5) + def test_direct_addressing_notfound(self, pytester: Pytester) -> None: p = pytester.makepyfile( """ @@ -469,7 +510,7 @@ def test_plugins_given_as_strings( assert "invalid" in str(excinfo.value) p = pytester.path.joinpath("test_test_plugins_given_as_strings.py") - p.write_text("def test_foo(): pass") + p.write_text("def test_foo(): pass", encoding="utf-8") mod = types.ModuleType("myplugin") monkeypatch.setitem(sys.modules, "myplugin", mod) assert pytest.main(args=[str(pytester.path)], plugins=["myplugin"]) == 0 @@ -501,6 +542,32 @@ def test_foo(data): res = pytester.runpytest(p) res.assert_outcomes(passed=3) + # Warning ignore because of: + # https://github.com/python/cpython/issues/85308 + # Can be removed once Python<3.12 support is dropped. + @pytest.mark.filterwarnings("ignore:'encoding' argument not specified") + def test_command_line_args_from_file( + self, pytester: Pytester, tmp_path: Path + ) -> None: + pytester.makepyfile( + test_file=""" + import pytest + + class TestClass: + @pytest.mark.parametrize("a", ["x","y"]) + def test_func(self, a): + pass + """ + ) + tests = [ + "test_file.py::TestClass::test_func[x]", + "test_file.py::TestClass::test_func[y]", + "-q", + ] + args_file = pytester.maketxtfile(tests="\n".join(tests)) + result = pytester.runpytest(f"@{args_file}") + result.assert_outcomes(failed=0, passed=2) + class TestInvocationVariants: def test_earlyinit(self, pytester: Pytester) -> None: @@ -589,7 +656,7 @@ def pytest_addoption(self, parser): def test_pyargs_importerror(self, pytester: Pytester, monkeypatch) -> None: monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", False) path = pytester.mkpydir("tpkg") - path.joinpath("test_hello.py").write_text("raise ImportError") + path.joinpath("test_hello.py").write_text("raise ImportError", encoding="utf-8") result = pytester.runpytest("--pyargs", "tpkg.test_hello", syspathinsert=True) assert result.ret != 0 @@ -599,10 +666,10 @@ def test_pyargs_importerror(self, pytester: Pytester, monkeypatch) -> None: def test_pyargs_only_imported_once(self, pytester: Pytester) -> None: pkg = pytester.mkpydir("foo") pkg.joinpath("test_foo.py").write_text( - "print('hello from test_foo')\ndef test(): pass" + "print('hello from test_foo')\ndef test(): pass", encoding="utf-8" ) pkg.joinpath("conftest.py").write_text( - "def pytest_configure(config): print('configuring')" + "def pytest_configure(config): print('configuring')", encoding="utf-8" ) result = pytester.runpytest( @@ -615,7 +682,7 @@ def test_pyargs_only_imported_once(self, pytester: Pytester) -> None: def test_pyargs_filename_looks_like_module(self, pytester: Pytester) -> None: pytester.path.joinpath("conftest.py").touch() - pytester.path.joinpath("t.py").write_text("def test(): pass") + pytester.path.joinpath("t.py").write_text("def test(): pass", encoding="utf-8") result = pytester.runpytest("--pyargs", "t.py") assert result.ret == ExitCode.OK @@ -624,8 +691,12 @@ def test_cmdline_python_package(self, pytester: Pytester, monkeypatch) -> None: monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", False) path = pytester.mkpydir("tpkg") - path.joinpath("test_hello.py").write_text("def test_hello(): pass") - path.joinpath("test_world.py").write_text("def test_world(): pass") + path.joinpath("test_hello.py").write_text( + "def test_hello(): pass", encoding="utf-8" + ) + path.joinpath("test_world.py").write_text( + "def test_world(): pass", encoding="utf-8" + ) result = pytester.runpytest("--pyargs", "tpkg") assert result.ret == 0 result.stdout.fnmatch_lines(["*2 passed*"]) @@ -664,13 +735,15 @@ def test_cmdline_python_namespace_package( ns = d.joinpath("ns_pkg") ns.mkdir() ns.joinpath("__init__.py").write_text( - "__import__('pkg_resources').declare_namespace(__name__)" + "__import__('pkg_resources').declare_namespace(__name__)", + encoding="utf-8", ) lib = ns.joinpath(dirname) lib.mkdir() lib.joinpath("__init__.py").touch() lib.joinpath(f"test_{dirname}.py").write_text( - f"def test_{dirname}(): pass\ndef test_other():pass" + f"def test_{dirname}(): pass\ndef test_other():pass", + encoding="utf-8", ) # The structure of the test directory is now: @@ -695,7 +768,18 @@ def test_cmdline_python_namespace_package( # mixed module and filenames: monkeypatch.chdir("world") - result = pytester.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world") + + # pgk_resources.declare_namespace has been deprecated in favor of implicit namespace packages. + # pgk_resources has been deprecated entirely. + # While we could change the test to use implicit namespace packages, seems better + # to still ensure the old declaration via declare_namespace still works. + ignore_w = ( + r"-Wignore:Deprecated call to `pkg_resources.declare_namespace", + r"-Wignore:pkg_resources is deprecated", + ) + result = pytester.runpytest( + "--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world", *ignore_w + ) assert result.ret == 0 result.stdout.fnmatch_lines( [ @@ -745,10 +829,10 @@ def test_cmdline_python_package_symlink( lib.mkdir() lib.joinpath("__init__.py").touch() lib.joinpath("test_bar.py").write_text( - "def test_bar(): pass\ndef test_other(a_fixture):pass" + "def test_bar(): pass\ndef test_other(a_fixture):pass", encoding="utf-8" ) lib.joinpath("conftest.py").write_text( - "import pytest\n@pytest.fixture\ndef a_fixture():pass" + "import pytest\n@pytest.fixture\ndef a_fixture():pass", encoding="utf-8" ) d_local = pytester.mkdir("symlink_root") @@ -873,7 +957,6 @@ def test_calls(self, pytester: Pytester, mock_timing) -> None: ) def test_calls_show_2(self, pytester: Pytester, mock_timing) -> None: - pytester.makepyfile(self.source) result = pytester.runpytest_inprocess("--durations=2") assert result.ret == 0 @@ -1038,14 +1121,14 @@ def test_fixture_values_leak(pytester: Pytester) -> None: """ pytester.makepyfile( """ - import attr + import dataclasses import gc import pytest import weakref - @attr.s - class SomeObj(object): - name = attr.ib() + @dataclasses.dataclass + class SomeObj: + name: str fix_of_test1_ref = None session_ref = None @@ -1150,7 +1233,6 @@ def test_usage_error_code(pytester: Pytester) -> None: assert result.ret == ExitCode.USAGE_ERROR -@pytest.mark.filterwarnings("default::pytest.PytestUnhandledCoroutineWarning") def test_warn_on_async_function(pytester: Pytester) -> None: # In the below we .close() the coroutine only to avoid # "RuntimeWarning: coroutine 'test_2' was never awaited" @@ -1167,7 +1249,7 @@ def test_3(): return coro """ ) - result = pytester.runpytest() + result = pytester.runpytest("-Wdefault") result.stdout.fnmatch_lines( [ "test_async.py::test_1", @@ -1183,7 +1265,6 @@ def test_3(): ) -@pytest.mark.filterwarnings("default::pytest.PytestUnhandledCoroutineWarning") def test_warn_on_async_gen_function(pytester: Pytester) -> None: pytester.makepyfile( test_async=""" @@ -1195,7 +1276,7 @@ def test_3(): return test_2() """ ) - result = pytester.runpytest() + result = pytester.runpytest("-Wdefault") result.stdout.fnmatch_lines( [ "test_async.py::test_1", @@ -1238,8 +1319,6 @@ def test(): " def check():", "> assert 1 == 2", "E assert 1 == 2", - "E +1", - "E -2", "", "pdb.py:2: AssertionError", "*= 1 failed in *", @@ -1270,8 +1349,7 @@ def test_simple(): result.stderr.fnmatch_lines(["*@this is stderr@*"]) # now ensure the output is in the junitxml - with open(pytester.path.joinpath("output.xml")) as f: - fullXml = f.read() + fullXml = pytester.path.joinpath("output.xml").read_text(encoding="utf-8") assert "@this is stdout@\n" in fullXml assert "@this is stderr@\n" in fullXml @@ -1295,3 +1373,107 @@ def test_no_brokenpipeerror_message(pytester: Pytester) -> None: # Cleanup. popen.stderr.close() + + +def test_function_return_non_none_warning(pytester: Pytester) -> None: + pytester.makepyfile( + """ + def test_stuff(): + return "something" + """ + ) + res = pytester.runpytest() + res.stdout.fnmatch_lines(["*Did you mean to use `assert` instead of `return`?*"]) + + +def test_doctest_and_normal_imports_with_importlib(pytester: Pytester) -> None: + """ + Regression test for #10811: previously import_path with ImportMode.importlib would + not return a module if already in sys.modules, resulting in modules being imported + multiple times, which causes problems with modules that have import side effects. + """ + # Uses the exact reproducer form #10811, given it is very minimal + # and illustrates the problem well. + pytester.makepyfile( + **{ + "pmxbot/commands.py": "from . import logging", + "pmxbot/logging.py": "", + "tests/__init__.py": "", + "tests/test_commands.py": """ + import importlib + from pmxbot import logging + + class TestCommands: + def test_boo(self): + assert importlib.import_module('pmxbot.logging') is logging + """, + } + ) + pytester.makeini( + """ + [pytest] + addopts= + --doctest-modules + --import-mode importlib + """ + ) + result = pytester.runpytest_subprocess() + result.stdout.fnmatch_lines("*1 passed*") + + +@pytest.mark.skip(reason="Test is not isolated") +def test_issue_9765(pytester: Pytester) -> None: + """Reproducer for issue #9765 on Windows + + https://github.com/pytest-dev/pytest/issues/9765 + """ + pytester.makepyprojecttoml( + """ + [tool.pytest.ini_options] + addopts = "-p my_package.plugin.my_plugin" + """ + ) + pytester.makepyfile( + **{ + "setup.py": ( + """ + from setuptools import setup + + if __name__ == '__main__': + setup(name='my_package', packages=['my_package', 'my_package.plugin']) + """ + ), + "my_package/__init__.py": "", + "my_package/conftest.py": "", + "my_package/test_foo.py": "def test(): pass", + "my_package/plugin/__init__.py": "", + "my_package/plugin/my_plugin.py": ( + """ + import pytest + + def pytest_configure(config): + + class SimplePlugin: + @pytest.fixture(params=[1, 2, 3]) + def my_fixture(self, request): + yield request.param + + config.pluginmanager.register(SimplePlugin()) + """ + ), + } + ) + + subprocess.run([sys.executable, "setup.py", "develop"], check=True) + try: + # We are using subprocess.run rather than pytester.run on purpose. + # pytester.run is adding the current directory to PYTHONPATH which avoids + # the bug. We also use pytest rather than python -m pytest for the same + # PYTHONPATH reason. + subprocess.run( + ["pytest", "my_package"], capture_output=True, check=True, text=True + ) + except subprocess.CalledProcessError as exc: + raise AssertionError( + f"pytest command failed:\n{exc.stdout=!s}\n{exc.stderr=!s}" + ) from exc diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/test_code.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/test_code.py index 33809528a06b0..57ab4cdfddb3d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/test_code.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/test_code.py @@ -1,15 +1,16 @@ +# mypy: allow-untyped-defs import re import sys from types import FrameType from unittest import mock -import pytest 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 +import pytest def test_ne() -> None: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/test_excinfo.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/test_excinfo.py index 61aa4406ad26e..e95510f92d676 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/test_excinfo.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/test_excinfo.py @@ -1,18 +1,19 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + +import fnmatch import importlib import io import operator +from pathlib import Path import queue +import re import sys import textwrap -from pathlib import Path from typing import Any -from typing import Dict -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union -import _pytest -import pytest +import _pytest._code from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo from _pytest._code.code import FormattedExcinfo @@ -22,11 +23,15 @@ from _pytest.pathlib import import_path from _pytest.pytester import LineMatcher from _pytest.pytester import Pytester +import pytest if TYPE_CHECKING: from _pytest._code.code import _TracebackStyle +if sys.version_info < (3, 11): + from exceptiongroup import ExceptionGroup + @pytest.fixture def limited_recursion_depth(): @@ -53,6 +58,20 @@ def test_excinfo_from_exc_info_simple() -> None: assert info.type == ValueError +def test_excinfo_from_exception_simple() -> None: + try: + raise ValueError + except ValueError as e: + assert e.__traceback__ is not None + info = _pytest._code.ExceptionInfo.from_exception(e) + assert info.type == ValueError + + +def test_excinfo_from_exception_missing_traceback_assertion() -> None: + with pytest.raises(AssertionError, match=r"must have.*__traceback__"): + _pytest._code.ExceptionInfo.from_exception(ValueError()) + + def test_excinfo_getstatement(): def g(): raise ValueError @@ -162,7 +181,7 @@ def test_traceback_cut(self) -> None: def test_traceback_cut_excludepath(self, pytester: Pytester) -> None: p = pytester.makepyfile("def f(): raise ValueError") with pytest.raises(ValueError) as excinfo: - import_path(p, root=pytester.path).f() # type: ignore[attr-defined] + import_path(p, root=pytester.path, consider_namespace_packages=False).f() basedir = Path(pytest.__file__).parent newtraceback = excinfo.traceback.cut(excludepath=basedir) for x in newtraceback: @@ -172,7 +191,7 @@ def test_traceback_cut_excludepath(self, pytester: Pytester) -> None: def test_traceback_filter(self): traceback = self.excinfo.traceback - ntraceback = traceback.filter() + ntraceback = traceback.filter(self.excinfo) assert len(ntraceback) == len(traceback) - 1 @pytest.mark.parametrize( @@ -203,7 +222,7 @@ def h(): excinfo = pytest.raises(ValueError, h) traceback = excinfo.traceback - ntraceback = traceback.filter() + ntraceback = traceback.filter(excinfo) print(f"old: {traceback!r}") print(f"new: {ntraceback!r}") @@ -219,7 +238,7 @@ def f(n): n += 1 f(n) - excinfo = pytest.raises(RuntimeError, f, 8) + excinfo = pytest.raises(RecursionError, f, 8) traceback = excinfo.traceback recindex = traceback.recursionindex() assert recindex == 3 @@ -276,7 +295,7 @@ def fail(): excinfo = pytest.raises(ValueError, fail) assert excinfo.traceback.recursionindex() is None - def test_traceback_getcrashentry(self): + def test_getreprcrash(self): def i(): __tracebackhide__ = True raise ValueError @@ -292,14 +311,13 @@ def f(): g() excinfo = pytest.raises(ValueError, f) - tb = excinfo.traceback - entry = tb.getcrashentry() + reprcrash = excinfo._getreprcrash() + assert reprcrash is not None co = _pytest._code.Code.from_function(h) - assert entry.frame.code.path == co.path - assert entry.lineno == co.firstlineno + 1 - assert entry.frame.code.name == "h" + assert reprcrash.path == str(co.path) + assert reprcrash.lineno == co.firstlineno + 1 + 1 - def test_traceback_getcrashentry_empty(self): + def test_getreprcrash_empty(self): def g(): __tracebackhide__ = True raise ValueError @@ -309,12 +327,7 @@ def f(): g() excinfo = pytest.raises(ValueError, f) - tb = excinfo.traceback - entry = tb.getcrashentry() - co = _pytest._code.Code.from_function(g) - assert entry.frame.code.path == co.path - assert entry.lineno == co.firstlineno + 2 - assert entry.frame.code.name == "g" + assert excinfo._getreprcrash() is None def test_excinfo_exconly(): @@ -361,12 +374,15 @@ def test_excinfo_no_sourcecode(): except ValueError: excinfo = _pytest._code.ExceptionInfo.from_current() s = str(excinfo.traceback[-1]) - assert s == " File '':1 in \n ???\n" + # TODO: Since Python 3.13b1 under pytest-xdist, the * is `import + # sys;exec(eval(sys.stdin.readline()))` (execnet bootstrap code) + # instead of `???` like before. Is this OK? + fnmatch.fnmatch(s, " File '':1 in \n *\n") def test_excinfo_no_python_sourcecode(tmp_path: Path) -> None: # XXX: simplified locally testable version - tmp_path.joinpath("test.txt").write_text("{{ h()}}:") + tmp_path.joinpath("test.txt").write_text("{{ h()}}:", encoding="utf-8") jinja2 = pytest.importorskip("jinja2") loader = jinja2.FileSystemLoader(str(tmp_path)) @@ -375,7 +391,7 @@ def test_excinfo_no_python_sourcecode(tmp_path: Path) -> None: 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 # shouldn't fail + _ = item.source # shouldn't fail if isinstance(item.path, Path) and item.path.name == "test.txt": assert str(item.source) == "{{ h()}}:" @@ -406,7 +422,7 @@ def test_codepath_Queue_example() -> None: def test_match_succeeds(): with pytest.raises(ZeroDivisionError) as excinfo: - 0 // 0 + _ = 0 // 0 excinfo.match(r".*zero.*") @@ -420,18 +436,106 @@ def test_division_zero(): excinfo.match(r'[123]+') """ ) - result = pytester.runpytest() + result = pytester.runpytest("--tb=short") assert result.ret != 0 - exc_msg = "Regex pattern '[[]123[]]+' does not match 'division by zero'." - result.stdout.fnmatch_lines([f"E * AssertionError: {exc_msg}"]) + match = [ + r"E .* AssertionError: Regex pattern did not match.", + r"E .* Regex: '\[123\]\+'", + r"E .* Input: 'division by zero'", + ] + result.stdout.re_match_lines(match) result.stdout.no_fnmatch_line("*__tracebackhide__ = True*") result = pytester.runpytest("--fulltrace") assert result.ret != 0 - result.stdout.fnmatch_lines( - ["*__tracebackhide__ = True*", f"E * AssertionError: {exc_msg}"] - ) + result.stdout.re_match_lines([r".*__tracebackhide__ = True.*", *match]) + + +class TestGroupContains: + def test_contains_exception_type(self) -> None: + exc_group = ExceptionGroup("", [RuntimeError()]) + with pytest.raises(ExceptionGroup) as exc_info: + raise exc_group + assert exc_info.group_contains(RuntimeError) + + def test_doesnt_contain_exception_type(self) -> None: + exc_group = ExceptionGroup("", [ValueError()]) + with pytest.raises(ExceptionGroup) as exc_info: + raise exc_group + assert not exc_info.group_contains(RuntimeError) + + def test_contains_exception_match(self) -> None: + exc_group = ExceptionGroup("", [RuntimeError("exception message")]) + with pytest.raises(ExceptionGroup) as exc_info: + raise exc_group + assert exc_info.group_contains(RuntimeError, match=r"^exception message$") + + def test_doesnt_contain_exception_match(self) -> None: + exc_group = ExceptionGroup("", [RuntimeError("message that will not match")]) + with pytest.raises(ExceptionGroup) as exc_info: + raise exc_group + assert not exc_info.group_contains(RuntimeError, match=r"^exception message$") + + def test_contains_exception_type_unlimited_depth(self) -> None: + exc_group = ExceptionGroup("", [ExceptionGroup("", [RuntimeError()])]) + with pytest.raises(ExceptionGroup) as exc_info: + raise exc_group + assert exc_info.group_contains(RuntimeError) + + def test_contains_exception_type_at_depth_1(self) -> None: + exc_group = ExceptionGroup("", [RuntimeError()]) + with pytest.raises(ExceptionGroup) as exc_info: + raise exc_group + assert exc_info.group_contains(RuntimeError, depth=1) + + def test_doesnt_contain_exception_type_past_depth(self) -> None: + exc_group = ExceptionGroup("", [ExceptionGroup("", [RuntimeError()])]) + with pytest.raises(ExceptionGroup) as exc_info: + raise exc_group + assert not exc_info.group_contains(RuntimeError, depth=1) + + def test_contains_exception_type_specific_depth(self) -> None: + exc_group = ExceptionGroup("", [ExceptionGroup("", [RuntimeError()])]) + with pytest.raises(ExceptionGroup) as exc_info: + raise exc_group + assert exc_info.group_contains(RuntimeError, depth=2) + + def test_contains_exception_match_unlimited_depth(self) -> None: + exc_group = ExceptionGroup( + "", [ExceptionGroup("", [RuntimeError("exception message")])] + ) + with pytest.raises(ExceptionGroup) as exc_info: + raise exc_group + assert exc_info.group_contains(RuntimeError, match=r"^exception message$") + + def test_contains_exception_match_at_depth_1(self) -> None: + exc_group = ExceptionGroup("", [RuntimeError("exception message")]) + with pytest.raises(ExceptionGroup) as exc_info: + raise exc_group + assert exc_info.group_contains( + RuntimeError, match=r"^exception message$", depth=1 + ) + + def test_doesnt_contain_exception_match_past_depth(self) -> None: + exc_group = ExceptionGroup( + "", [ExceptionGroup("", [RuntimeError("exception message")])] + ) + with pytest.raises(ExceptionGroup) as exc_info: + raise exc_group + assert not exc_info.group_contains( + RuntimeError, match=r"^exception message$", depth=1 + ) + + def test_contains_exception_match_specific_depth(self) -> None: + exc_group = ExceptionGroup( + "", [ExceptionGroup("", [RuntimeError("exception message")])] + ) + with pytest.raises(ExceptionGroup) as exc_info: + raise exc_group + assert exc_info.group_contains( + RuntimeError, match=r"^exception message$", depth=2 + ) class TestFormattedExcinfo: @@ -441,9 +545,11 @@ def importasmod(source): source = textwrap.dedent(source) modpath = tmp_path.joinpath("mod.py") tmp_path.joinpath("__init__.py").touch() - modpath.write_text(source) + modpath.write_text(source, encoding="utf-8") importlib.invalidate_caches() - return import_path(modpath, root=tmp_path) + return import_path( + modpath, root=tmp_path, consider_namespace_packages=False + ) return importasmod @@ -461,12 +567,30 @@ def f(x): assert lines[0] == "| def f(x):" assert lines[1] == " pass" + def test_repr_source_out_of_bounds(self): + pr = FormattedExcinfo() + source = _pytest._code.Source( + """\ + def f(x): + pass + """ + ).strip() + pr.flow_marker = "|" # type: ignore[misc] + + lines = pr.get_source(source, 100) + assert len(lines) == 1 + assert lines[0] == "| ???" + + lines = pr.get_source(source, -100) + assert len(lines) == 1 + assert lines[0] == "| ???" + def test_repr_source_excinfo(self) -> None: """Check if indentation is right.""" try: def f(): - 1 / 0 + _ = 1 / 0 f() @@ -483,7 +607,7 @@ def f(): print(line) assert lines == [ " def f():", - "> 1 / 0", + "> _ = 1 / 0", "E ZeroDivisionError: division by zero", ] @@ -520,7 +644,7 @@ def test_repr_source_failing_fullsource(self, monkeypatch) -> None: pr = FormattedExcinfo() try: - 1 / 0 + _ = 1 / 0 except ZeroDivisionError: excinfo = ExceptionInfo.from_current() @@ -596,7 +720,7 @@ def func1(): """ ) excinfo = pytest.raises(ValueError, mod.func1) - excinfo.traceback = excinfo.traceback.filter() + excinfo.traceback = excinfo.traceback.filter(excinfo) p = FormattedExcinfo() reprtb = p.repr_traceback_entry(excinfo.traceback[-1]) @@ -629,7 +753,7 @@ def func1(m, x, y, z): """ ) excinfo = pytest.raises(ValueError, mod.func1, "m" * 90, 5, 13, "z" * 120) - excinfo.traceback = excinfo.traceback.filter() + excinfo.traceback = excinfo.traceback.filter(excinfo) entry = excinfo.traceback[-1] p = FormattedExcinfo(funcargs=True) reprfuncargs = p.repr_args(entry) @@ -656,7 +780,7 @@ def func1(x, *y, **z): """ ) excinfo = pytest.raises(ValueError, mod.func1, "a", "b", c="d") - excinfo.traceback = excinfo.traceback.filter() + excinfo.traceback = excinfo.traceback.filter(excinfo) entry = excinfo.traceback[-1] p = FormattedExcinfo(funcargs=True) reprfuncargs = p.repr_args(entry) @@ -737,7 +861,11 @@ def entry(): reprtb = p.repr_traceback(excinfo) assert len(reprtb.reprentries) == 3 - def test_traceback_short_no_source(self, importasmod, monkeypatch) -> None: + def test_traceback_short_no_source( + self, + importasmod, + monkeypatch: pytest.MonkeyPatch, + ) -> None: mod = importasmod( """ def func1(): @@ -749,14 +877,14 @@ def entry(): excinfo = pytest.raises(ValueError, mod.entry) from _pytest._code.code import Code - monkeypatch.setattr(Code, "path", "bogus") - p = FormattedExcinfo(style="short") - reprtb = p.repr_traceback_entry(excinfo.traceback[-2]) - lines = reprtb.lines - last_p = FormattedExcinfo(style="short") - last_reprtb = last_p.repr_traceback_entry(excinfo.traceback[-1], excinfo) - last_lines = last_reprtb.lines - monkeypatch.undo() + with monkeypatch.context() as mp: + mp.setattr(Code, "path", "bogus") + p = FormattedExcinfo(style="short") + reprtb = p.repr_traceback_entry(excinfo.traceback[-2]) + lines = reprtb.lines + last_p = FormattedExcinfo(style="short") + last_reprtb = last_p.repr_traceback_entry(excinfo.traceback[-1], excinfo) + last_lines = last_reprtb.lines assert lines[0] == " func1()" assert last_lines[0] == ' raise ValueError("hello")' @@ -773,7 +901,7 @@ def entry(): ) excinfo = pytest.raises(ValueError, mod.entry) - styles: Tuple[_TracebackStyle, ...] = ("long", "short") + styles: tuple[_TracebackStyle, ...] = ("long", "short") for style in styles: p = FormattedExcinfo(style=style) reprtb = p.repr_traceback(excinfo) @@ -900,7 +1028,7 @@ def entry(): ) excinfo = pytest.raises(ValueError, mod.entry) - styles: Tuple[_TracebackStyle, ...] = ("short", "long", "no") + styles: tuple[_TracebackStyle, ...] = ("short", "long", "no") for style in styles: for showlocals in (True, False): repr = excinfo.getrepr(style=style, showlocals=showlocals) @@ -930,7 +1058,7 @@ def f(): """ ) excinfo = pytest.raises(ValueError, mod.f) - excinfo.traceback = excinfo.traceback.filter() + excinfo.traceback = excinfo.traceback.filter(excinfo) repr = excinfo.getrepr() repr.toterminal(tw_mock) assert tw_mock.lines[0] == "" @@ -964,7 +1092,7 @@ def f(): ) excinfo = pytest.raises(ValueError, mod.f) tmp_path.joinpath("mod.py").unlink() - excinfo.traceback = excinfo.traceback.filter() + excinfo.traceback = excinfo.traceback.filter(excinfo) repr = excinfo.getrepr() repr.toterminal(tw_mock) assert tw_mock.lines[0] == "" @@ -995,8 +1123,8 @@ def f(): """ ) excinfo = pytest.raises(ValueError, mod.f) - tmp_path.joinpath("mod.py").write_text("asdf") - excinfo.traceback = excinfo.traceback.filter() + tmp_path.joinpath("mod.py").write_text("asdf", encoding="utf-8") + excinfo.traceback = excinfo.traceback.filter(excinfo) repr = excinfo.getrepr() repr.toterminal(tw_mock) assert tw_mock.lines[0] == "" @@ -1052,9 +1180,7 @@ def f(): "funcargs": funcargs, "tbfilter": tbfilter, }, - id="style={},showlocals={},funcargs={},tbfilter={}".format( - style, showlocals, funcargs, tbfilter - ), + id=f"style={style},showlocals={showlocals},funcargs={funcargs},tbfilter={tbfilter}", ) for style in ["long", "short", "line", "no", "native", "value", "auto"] for showlocals in (True, False) @@ -1062,7 +1188,7 @@ def f(): for funcargs in (True, False) ], ) - def test_format_excinfo(self, reproptions: Dict[str, Any]) -> None: + def test_format_excinfo(self, reproptions: dict[str, Any]) -> None: def bar(): assert False, "some error" @@ -1093,9 +1219,11 @@ def i(): """ ) excinfo = pytest.raises(ValueError, mod.f) - excinfo.traceback = excinfo.traceback.filter() - excinfo.traceback[1].set_repr_style("short") - excinfo.traceback[2].set_repr_style("short") + excinfo.traceback = excinfo.traceback.filter(excinfo) + excinfo.traceback = _pytest._code.Traceback( + entry if i not in (1, 2) else entry.with_repr_style("short") + for i, entry in enumerate(excinfo.traceback) + ) r = excinfo.getrepr(style="long") r.toterminal(tw_mock) for line in tw_mock.lines: @@ -1216,7 +1344,7 @@ def test_exc_repr_chain_suppression(self, importasmod, mode, tw_mock): """ raise_suffix = " from None" if mode == "from_none" else "" mod = importasmod( - """ + f""" def f(): try: g() @@ -1224,9 +1352,7 @@ def f(): raise AttributeError(){raise_suffix} def g(): raise ValueError() - """.format( - raise_suffix=raise_suffix - ) + """ ) excinfo = pytest.raises(AttributeError, mod.f) r = excinfo.getrepr(style="long", chain=mode != "explicit_suppress") @@ -1238,9 +1364,7 @@ def g(): 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_mock.lines[5] == f"> raise AttributeError(){raise_suffix}" assert tw_mock.lines[6] == "E AttributeError" assert tw_mock.lines[7] == "" line = tw_mock.get_write_msg(8) @@ -1271,7 +1395,7 @@ def test_exc_chain_repr_without_traceback(self, importasmod, reason, description """ exc_handling_code = " from e" if reason == "cause" else "" mod = importasmod( - """ + f""" def f(): try: g() @@ -1279,9 +1403,7 @@ def f(): raise RuntimeError('runtime problem'){exc_handling_code} def g(): raise ValueError('invalid value') - """.format( - exc_handling_code=exc_handling_code - ) + """ ) with pytest.raises(RuntimeError) as excinfo: @@ -1361,14 +1483,14 @@ def f(): with pytest.raises(TypeError) as excinfo: mod.f() # previously crashed with `AttributeError: list has no attribute get` - excinfo.traceback.filter() + excinfo.traceback.filter(excinfo) @pytest.mark.parametrize("style", ["short", "long"]) @pytest.mark.parametrize("encoding", [None, "utf8", "utf16"]) def test_repr_traceback_with_unicode(style, encoding): if encoding is None: - msg: Union[str, bytes] = "☹" + msg: str | bytes = "☹" else: msg = "☹".encode(encoding) try: @@ -1397,7 +1519,7 @@ def test(tmp_path): result.stderr.no_fnmatch_line("*INTERNALERROR*") -def test_regression_nagative_line_index(pytester: Pytester) -> None: +def test_regression_negative_line_index(pytester: Pytester) -> None: """ With Python 3.10 alphas, there was an INTERNALERROR reported in https://github.com/pytest-dev/pytest/pull/8227 @@ -1466,5 +1588,203 @@ def __getattr__(self, attr): return getattr(self, "_" + attr) with pytest.raises(RuntimeError) as excinfo: - RecursionDepthError().trigger + _ = RecursionDepthError().trigger assert "maximum recursion" in str(excinfo.getrepr()) + + +def _exceptiongroup_common( + pytester: Pytester, + outer_chain: str, + inner_chain: str, + native: bool, +) -> None: + pre_raise = "exceptiongroup." if not native else "" + pre_catch = pre_raise if sys.version_info < (3, 11) else "" + filestr = f""" + {"import exceptiongroup" if not native else ""} + import pytest + + def f(): raise ValueError("From f()") + def g(): raise BaseException("From g()") + + def inner(inner_chain): + excs = [] + for callback in [f, g]: + try: + callback() + except BaseException as err: + excs.append(err) + if excs: + if inner_chain == "none": + raise {pre_raise}BaseExceptionGroup("Oops", excs) + try: + raise SyntaxError() + except SyntaxError as e: + if inner_chain == "from": + raise {pre_raise}BaseExceptionGroup("Oops", excs) from e + else: + raise {pre_raise}BaseExceptionGroup("Oops", excs) + + def outer(outer_chain, inner_chain): + try: + inner(inner_chain) + except {pre_catch}BaseExceptionGroup as e: + if outer_chain == "none": + raise + if outer_chain == "from": + raise IndexError() from e + else: + raise IndexError() + + + def test(): + outer("{outer_chain}", "{inner_chain}") + """ + pytester.makepyfile(test_excgroup=filestr) + result = pytester.runpytest() + match_lines = [] + if inner_chain in ("another", "from"): + match_lines.append(r"SyntaxError: ") + + match_lines += [ + r" + Exception Group Traceback (most recent call last):", + rf" \| {pre_catch}BaseExceptionGroup: Oops \(2 sub-exceptions\)", + r" \| ValueError: From f\(\)", + r" \| BaseException: From g\(\)", + r"=* short test summary info =*", + ] + if outer_chain in ("another", "from"): + match_lines.append(r"FAILED test_excgroup.py::test - IndexError") + else: + match_lines.append( + rf"FAILED test_excgroup.py::test - {pre_catch}BaseExceptionGroup: Oops \(2.*" + ) + result.stdout.re_match_lines(match_lines) + + +@pytest.mark.skipif( + sys.version_info < (3, 11), reason="Native ExceptionGroup not implemented" +) +@pytest.mark.parametrize("outer_chain", ["none", "from", "another"]) +@pytest.mark.parametrize("inner_chain", ["none", "from", "another"]) +def test_native_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None: + _exceptiongroup_common(pytester, outer_chain, inner_chain, native=True) + + +@pytest.mark.parametrize("outer_chain", ["none", "from", "another"]) +@pytest.mark.parametrize("inner_chain", ["none", "from", "another"]) +def test_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None: + # with py>=3.11 does not depend on exceptiongroup, though there is a toxenv for it + pytest.importorskip("exceptiongroup") + _exceptiongroup_common(pytester, outer_chain, inner_chain, native=False) + + +@pytest.mark.parametrize("tbstyle", ("long", "short", "auto", "line", "native")) +def test_all_entries_hidden(pytester: Pytester, tbstyle: str) -> None: + """Regression test for #10903.""" + pytester.makepyfile( + """ + def test(): + __tracebackhide__ = True + 1 / 0 + """ + ) + result = pytester.runpytest("--tb", tbstyle) + assert result.ret == 1 + if tbstyle != "line": + result.stdout.fnmatch_lines(["*ZeroDivisionError: division by zero"]) + if tbstyle not in ("line", "native"): + result.stdout.fnmatch_lines(["All traceback entries are hidden.*"]) + + +def test_hidden_entries_of_chained_exceptions_are_not_shown(pytester: Pytester) -> None: + """Hidden entries of chained exceptions are not shown (#1904).""" + p = pytester.makepyfile( + """ + def g1(): + __tracebackhide__ = True + str.does_not_exist + + def f3(): + __tracebackhide__ = True + 1 / 0 + + def f2(): + try: + f3() + except Exception: + g1() + + def f1(): + __tracebackhide__ = True + f2() + + def test(): + f1() + """ + ) + result = pytester.runpytest(str(p), "--tb=short") + assert result.ret == 1 + result.stdout.fnmatch_lines( + [ + "*.py:11: in f2", + " f3()", + "E ZeroDivisionError: division by zero", + "", + "During handling of the above exception, another exception occurred:", + "*.py:20: in test", + " f1()", + "*.py:13: in f2", + " g1()", + "E AttributeError:*'does_not_exist'", + ], + consecutive=True, + ) + + +def add_note(err: BaseException, msg: str) -> None: + """Adds a note to an exception inplace.""" + if sys.version_info < (3, 11): + err.__notes__ = [*getattr(err, "__notes__", []), msg] # type: ignore[attr-defined] + else: + err.add_note(msg) + + +@pytest.mark.parametrize( + "error,notes,match", + [ + (Exception("test"), [], "test"), + (AssertionError("foo"), ["bar"], "bar"), + (AssertionError("foo"), ["bar", "baz"], "bar"), + (AssertionError("foo"), ["bar", "baz"], "baz"), + (ValueError("foo"), ["bar", "baz"], re.compile(r"bar\nbaz", re.MULTILINE)), + (ValueError("foo"), ["bar", "baz"], re.compile(r"BAZ", re.IGNORECASE)), + ], +) +def test_check_error_notes_success( + error: Exception, notes: list[str], match: str +) -> None: + for note in notes: + add_note(error, note) + + with pytest.raises(Exception, match=match): + raise error + + +@pytest.mark.parametrize( + "error, notes, match", + [ + (Exception("test"), [], "foo"), + (AssertionError("foo"), ["bar"], "baz"), + (AssertionError("foo"), ["bar"], "foo\nbaz"), + ], +) +def test_check_error_notes_failure( + error: Exception, notes: list[str], match: str +) -> None: + for note in notes: + add_note(error, note) + + with pytest.raises(AssertionError): + with pytest.raises(type(error), match=match): + raise error diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/test_source.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/test_source.py index 9f7be5e245813..a00259976c443 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/test_source.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/test_source.py @@ -1,16 +1,14 @@ +# mypy: allow-untyped-defs # 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 -import ast import inspect import linecache import sys import textwrap from pathlib import Path -from types import CodeType from typing import Any from typing import Dict -from typing import Optional import pytest from _pytest._code import Code @@ -257,7 +255,7 @@ def g(): assert str(g_source).strip() == "def g():\n pass # pragma: no cover" -def test_getfuncsource_with_multine_string() -> None: +def test_getfuncsource_with_multiline_string() -> None: def f(): c = """while True: pass @@ -297,8 +295,8 @@ def method(self): """ ) path = tmp_path.joinpath("a.py") - path.write_text(str(source)) - mod: Any = import_path(path, root=tmp_path) + path.write_text(str(source), encoding="utf-8") + mod: Any = import_path(path, root=tmp_path, consider_namespace_packages=False) s2 = Source(mod.A) assert str(source).strip() == str(s2).strip() @@ -332,8 +330,7 @@ def test_findsource(monkeypatch) -> None: 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] + monkeypatch.setitem(linecache.cache, filename, (1, None, lines, filename)) src, lineno = findsource(co) assert src is not None @@ -373,7 +370,11 @@ class B: pass B.__name__ = B.__qualname__ = "B2" - assert getfslineno(B)[1] == -1 + # Since Python 3.13 this started working. + if sys.version_info >= (3, 13): + assert getfslineno(B)[1] != -1 + else: + assert getfslineno(B)[1] == -1 def test_code_of_object_instance_with_call() -> None: @@ -443,14 +444,9 @@ def test_comments() -> None: ''' for line in range(2, 6): assert str(getstatement(line, source)) == " x = 1" - if sys.version_info >= (3, 8) or hasattr(sys, "pypy_version_info"): - tqs_start = 8 - else: - tqs_start = 10 - assert str(getstatement(10, source)) == '"""' - for line in range(6, tqs_start): + for line in range(6, 8): assert str(getstatement(line, source)) == " assert False" - for line in range(tqs_start, 10): + for line in range(8, 10): assert str(getstatement(line, source)) == '"""\ncomment 4\n"""' diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/w3c-import.log new file mode 100644 index 0000000000000..4dfffee071d88 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/test_code.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/test_excinfo.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/code/test_source.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/conftest.py index 107aad86b25b6..b7e2d6111af7f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/conftest.py @@ -1,10 +1,14 @@ +# mypy: allow-untyped-defs +import dataclasses import re import sys +from typing import Generator from typing import List -import pytest from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester +import pytest + if sys.gettrace(): @@ -20,11 +24,31 @@ def restore_tracing(): sys.settrace(orig_trace) -@pytest.hookimpl(hookwrapper=True, tryfirst=True) -def pytest_collection_modifyitems(items): +@pytest.fixture(autouse=True) +def set_column_width(monkeypatch: pytest.MonkeyPatch) -> None: + """ + Force terminal width to 80: some tests check the formatting of --help, which is sensible + to terminal width. + """ + monkeypatch.setenv("COLUMNS", "80") + + +@pytest.fixture(autouse=True) +def reset_colors(monkeypatch: pytest.MonkeyPatch) -> None: + """ + Reset all color-related variables to prevent them from affecting internal pytest output + in tests that depend on it. + """ + monkeypatch.delenv("PY_COLORS", raising=False) + monkeypatch.delenv("NO_COLOR", raising=False) + monkeypatch.delenv("FORCE_COLOR", raising=False) + + +@pytest.hookimpl(wrapper=True, tryfirst=True) +def pytest_collection_modifyitems(items) -> Generator[None, None, None]: """Prefer faster tests. - Use a hookwrapper to do this in the beginning, so e.g. --ff still works + Use a hook wrapper to do this in the beginning, so e.g. --ff still works correctly. """ fast_items = [] @@ -61,7 +85,7 @@ def pytest_collection_modifyitems(items): items[:] = fast_items + neutral_items + slow_items + slowest_items - yield + return (yield) @pytest.fixture @@ -104,7 +128,7 @@ def get_write_msg(self, idx): @pytest.fixture -def dummy_yaml_custom_test(pytester: Pytester): +def dummy_yaml_custom_test(pytester: Pytester) -> None: """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 @@ -149,6 +173,9 @@ class ColorMapping: "red": "\x1b[31m", "green": "\x1b[32m", "yellow": "\x1b[33m", + "light-gray": "\x1b[90m", + "light-red": "\x1b[91m", + "light-green": "\x1b[92m", "bold": "\x1b[1m", "reset": "\x1b[0m", "kw": "\x1b[94m", @@ -157,8 +184,10 @@ class ColorMapping: "number": "\x1b[94m", "str": "\x1b[33m", "print": "\x1b[96m", + "endline": "\x1b[90m\x1b[39;49;00m", } RE_COLORS = {k: re.escape(v) for k, v in COLORS.items()} + NO_COLORS = {k: "" for k in COLORS.keys()} @classmethod def format(cls, lines: List[str]) -> List[str]: @@ -175,6 +204,11 @@ 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 strip_colors(cls, lines: List[str]) -> List[str]: + """Entirely remove every color code""" + return [line.format(**cls.NO_COLORS) for line in lines] + return ColorMapping @@ -191,20 +225,18 @@ def mock_timing(monkeypatch: MonkeyPatch): 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 + @dataclasses.dataclass class MockTiming: + _current_time: float = 1590150050.0 - _current_time = attr.ib(default=1590150050.0) - - def sleep(self, seconds): + def sleep(self, seconds: float) -> None: self._current_time += seconds - def time(self): + def time(self) -> float: return self._current_time - def patch(self): + def patch(self) -> None: from _pytest import timing monkeypatch.setattr(timing, "sleep", self.sleep) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/deprecated_test.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/deprecated_test.py index 9ac7fe1cacbc5..9e83a49d55499 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/deprecated_test.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/deprecated_test.py @@ -1,23 +1,15 @@ +# mypy: allow-untyped-defs +from pathlib import Path import re import sys -import warnings -from pathlib import Path -from unittest import mock -import pytest from _pytest import deprecated from _pytest.compat import legacy_path from _pytest.pytester import Pytester +import pytest from pytest import PytestDeprecationWarning -@pytest.mark.parametrize("attribute", pytest.collect.__all__) # type: ignore -# false positive due to dynamic attribute -def test_pytest_collect_module_deprecated(attribute) -> None: - with pytest.warns(DeprecationWarning, match=attribute): - getattr(pytest.collect, attribute) - - @pytest.mark.parametrize("plugin", sorted(deprecated.DEPRECATED_EXTERNAL_PLUGINS)) @pytest.mark.filterwarnings("default") def test_external_plugins_integrated(pytester: Pytester, plugin) -> None: @@ -28,96 +20,52 @@ def test_external_plugins_integrated(pytester: Pytester, plugin) -> None: pytester.parseconfig("-p", plugin) -def test_fillfuncargs_is_deprecated() -> None: - with pytest.warns( - pytest.PytestDeprecationWarning, - match=re.escape( - "pytest._fillfuncargs() is deprecated, use " - "function._request._fillfixtures() instead if you cannot avoid reaching into internals." - ), - ): - pytest._fillfuncargs(mock.Mock()) - - -def test_fillfixtures_is_deprecated() -> None: - import _pytest.fixtures - - with pytest.warns( - pytest.PytestDeprecationWarning, - match=re.escape( - "_pytest.fixtures.fillfixtures() is deprecated, use " - "function._request._fillfixtures() instead if you cannot avoid reaching into internals." - ), - ): - _pytest.fixtures.fillfixtures(mock.Mock()) +def test_hookspec_via_function_attributes_are_deprecated(): + from _pytest.config import PytestPluginManager + pm = PytestPluginManager() -def test_minus_k_dash_is_deprecated(pytester: Pytester) -> None: - threepass = pytester.makepyfile( - test_threepass=""" - def test_one(): assert 1 - def test_two(): assert 1 - def test_three(): assert 1 - """ - ) - result = pytester.runpytest("-k=-test_two", threepass) - result.stdout.fnmatch_lines(["*The `-k '-expr'` syntax*deprecated*"]) - - -def test_minus_k_colon_is_deprecated(pytester: Pytester) -> None: - threepass = pytester.makepyfile( - test_threepass=""" - def test_one(): assert 1 - def test_two(): assert 1 - def test_three(): assert 1 - """ - ) - result = pytester.runpytest("-k", "test_two:", threepass) - result.stdout.fnmatch_lines(["*The `-k 'expr:'` syntax*deprecated*"]) + class DeprecatedHookMarkerSpec: + def pytest_bad_hook(self): + pass + pytest_bad_hook.historic = False # type: ignore[attr-defined] -def test_fscollector_gethookproxy_isinitpath(pytester: Pytester) -> None: - module = pytester.getmodulecol( - """ - def test_foo(): pass - """, - withinit=True, + with pytest.warns( + PytestDeprecationWarning, + match=r"Please use the pytest\.hookspec\(historic=False\) decorator", + ) as recorder: + pm.add_hookspecs(DeprecatedHookMarkerSpec) + (record,) = recorder + assert ( + record.lineno + == DeprecatedHookMarkerSpec.pytest_bad_hook.__code__.co_firstlineno ) - assert isinstance(module, pytest.Module) - package = module.parent - assert isinstance(package, pytest.Package) + assert record.filename == __file__ - with pytest.warns(pytest.PytestDeprecationWarning, match="gethookproxy"): - package.gethookproxy(pytester.path) - with pytest.warns(pytest.PytestDeprecationWarning, match="isinitpath"): - package.isinitpath(pytester.path) +def test_hookimpl_via_function_attributes_are_deprecated(): + from _pytest.config import PytestPluginManager - # The methods on Session are *not* deprecated. - session = module.session - with warnings.catch_warnings(record=True) as rec: - session.gethookproxy(pytester.path) - session.isinitpath(pytester.path) - assert len(rec) == 0 + pm = PytestPluginManager() + class DeprecatedMarkImplPlugin: + def pytest_runtest_call(self): + pass -def test_strict_option_is_deprecated(pytester: Pytester) -> None: - """--strict is a deprecated alias to --strict-markers (#7530).""" - pytester.makepyfile( - """ - import pytest + pytest_runtest_call.tryfirst = True # type: ignore[attr-defined] - @pytest.mark.unknown - def test_foo(): pass - """ - ) - result = pytester.runpytest("--strict") - result.stdout.fnmatch_lines( - [ - "'unknown' not found in `markers` configuration option", - "*PytestRemovedIn8Warning: The --strict option is deprecated, use --strict-markers instead.", - ] + with pytest.warns( + PytestDeprecationWarning, + match=r"Please use the pytest.hookimpl\(tryfirst=True\)", + ) as recorder: + pm.register(DeprecatedMarkImplPlugin()) + (record,) = recorder + assert ( + record.lineno + == DeprecatedMarkImplPlugin.pytest_runtest_call.__code__.co_firstlineno ) + assert record.filename == __file__ def test_yield_fixture_is_deprecated() -> None: @@ -142,23 +90,6 @@ def __init__(self, foo: int, *, _ispytest: bool = False) -> None: PrivateInit(10, _ispytest=True) -def test_raising_unittest_skiptest_during_collection_is_deprecated( - pytester: Pytester, -) -> None: - pytester.makepyfile( - """ - import unittest - raise unittest.SkipTest() - """ - ) - result = pytester.runpytest() - result.stdout.fnmatch_lines( - [ - "*PytestRemovedIn8Warning: Raising unittest.SkipTest*", - ] - ) - - @pytest.mark.parametrize("hooktype", ["hook", "ihook"]) def test_hookproxy_warnings_for_pathlib(tmp_path, hooktype, request): path = legacy_path(tmp_path) @@ -190,121 +121,99 @@ def test_hookproxy_warnings_for_pathlib(tmp_path, hooktype, request): ) -def test_warns_none_is_deprecated(): - with pytest.warns( - PytestDeprecationWarning, - match=re.escape( - "Passing None has been deprecated.\n" - "See https://docs.pytest.org/en/latest/how-to/capture-warnings.html" - "#additional-use-cases-of-warnings-in-tests" - " for alternatives in common use cases." - ), - ): - with pytest.warns(None): # type: ignore[call-overload] - pass - - -class TestSkipMsgArgumentDeprecated: - def test_skip_with_msg_is_deprecated(self, pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - import pytest - - def test_skipping_msg(): - pytest.skip(msg="skippedmsg") - """ - ) - result = pytester.runpytest(p) - result.stdout.fnmatch_lines( - [ - "*PytestRemovedIn8Warning: pytest.skip(msg=...) is now deprecated, " - "use pytest.skip(reason=...) instead", - '*pytest.skip(msg="skippedmsg")*', - ] - ) - result.assert_outcomes(skipped=1, warnings=1) +def test_hookimpl_warnings_for_pathlib() -> None: + class Plugin: + def pytest_ignore_collect(self, path: object) -> None: + raise NotImplementedError() - def test_fail_with_msg_is_deprecated(self, pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - import pytest + def pytest_collect_file(self, path: object) -> None: + raise NotImplementedError() - def test_failing_msg(): - pytest.fail(msg="failedmsg") - """ - ) - result = pytester.runpytest(p) - result.stdout.fnmatch_lines( - [ - "*PytestRemovedIn8Warning: pytest.fail(msg=...) is now deprecated, " - "use pytest.fail(reason=...) instead", - '*pytest.fail(msg="failedmsg")', - ] - ) - result.assert_outcomes(failed=1, warnings=1) + def pytest_pycollect_makemodule(self, path: object) -> None: + raise NotImplementedError() - def test_exit_with_msg_is_deprecated(self, pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - import pytest + def pytest_report_header(self, startdir: object) -> str: + raise NotImplementedError() - def test_exit_msg(): - pytest.exit(msg="exitmsg") - """ - ) - result = pytester.runpytest(p) - result.stdout.fnmatch_lines( - [ - "*PytestRemovedIn8Warning: pytest.exit(msg=...) is now deprecated, " - "use pytest.exit(reason=...) instead", - ] - ) - result.assert_outcomes(warnings=1) + def pytest_report_collectionfinish(self, startdir: object) -> str: + raise NotImplementedError() - -def test_deprecation_of_cmdline_preparse(pytester: Pytester) -> None: - pytester.makeconftest( - """ - def pytest_cmdline_preparse(config, args): - ... - - """ - ) - result = pytester.runpytest() - result.stdout.fnmatch_lines( - [ - "*PytestRemovedIn8Warning: The pytest_cmdline_preparse hook is deprecated*", - "*Please use pytest_load_initial_conftests hook instead.*", - ] - ) + pm = pytest.PytestPluginManager() + with pytest.warns( + pytest.PytestRemovedIn9Warning, + match=r"py\.path\.local.* argument is deprecated", + ) as wc: + pm.register(Plugin()) + assert len(wc.list) == 5 def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None: mod = pytester.getmodulecol("") + class MyFile(pytest.File): + def collect(self): + raise NotImplementedError() + with pytest.warns( pytest.PytestDeprecationWarning, - match=re.escape("The (fspath: py.path.local) argument to File is deprecated."), + match=re.escape( + "The (fspath: py.path.local) argument to MyFile is deprecated." + ), ): - pytest.File.from_parent( + MyFile.from_parent( parent=mod.parent, fspath=legacy_path("bla"), ) -@pytest.mark.skipif( - sys.version_info < (3, 7), - reason="This deprecation can only be emitted on python>=3.7", -) -def test_importing_instance_is_deprecated(pytester: Pytester) -> None: +def test_fixture_disallow_on_marked_functions(): + """Test that applying @pytest.fixture to a marked function warns (#3364).""" with pytest.warns( - pytest.PytestDeprecationWarning, - match=re.escape("The pytest.Instance collector type is deprecated"), - ): - pytest.Instance + pytest.PytestRemovedIn9Warning, + match=r"Marks applied to fixtures have no effect", + ) as record: + + @pytest.fixture + @pytest.mark.parametrize("example", ["hello"]) + @pytest.mark.usefixtures("tmp_path") + def foo(): + raise NotImplementedError() + + # it's only possible to get one warning here because you're already prevented + # from applying @fixture twice + # ValueError("fixture is being applied more than once to the same function") + assert len(record) == 1 + +def test_fixture_disallow_marks_on_fixtures(): + """Test that applying a mark to a fixture warns (#3364).""" with pytest.warns( - pytest.PytestDeprecationWarning, - match=re.escape("The pytest.Instance collector type is deprecated"), - ): - from _pytest.python import Instance # noqa: F401 + pytest.PytestRemovedIn9Warning, + match=r"Marks applied to fixtures have no effect", + ) as record: + + @pytest.mark.parametrize("example", ["hello"]) + @pytest.mark.usefixtures("tmp_path") + @pytest.fixture + def foo(): + raise NotImplementedError() + + assert len(record) == 2 # one for each mark decorator + # should point to this file + assert all(rec.filename == __file__ for rec in record) + + +def test_fixture_disallowed_between_marks(): + """Test that applying a mark to a fixture warns (#3364).""" + with pytest.warns( + pytest.PytestRemovedIn9Warning, + match=r"Marks applied to fixtures have no effect", + ) as record: + + @pytest.mark.parametrize("example", ["hello"]) + @pytest.fixture + @pytest.mark.usefixtures("tmp_path") + def foo(): + raise NotImplementedError() + + assert len(record) == 2 # one for each mark decorator diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/acceptance/fixture_mock_integration.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/acceptance/fixture_mock_integration.py index 5b00ac90e1bea..d802a7f872823 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/acceptance/fixture_mock_integration.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/acceptance/fixture_mock_integration.py @@ -1,8 +1,11 @@ +# mypy: allow-untyped-defs """Reproduces issue #3774""" + from unittest import mock import pytest + config = {"mykey": "ORIGINAL"} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/acceptance/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/acceptance/w3c-import.log new file mode 100644 index 0000000000000..427b75e05feca --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/acceptance/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/acceptance/fixture_mock_integration.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/__init__.py index 9cd366295e766..58c41942d1ca1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/__init__.py @@ -1,2 +1,3 @@ +# mypy: allow-untyped-defs def test_init(): pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py index 8f2d73cfa4f2e..d88c001c2cc2a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py @@ -1,2 +1,3 @@ +# mypy: allow-untyped-defs def test_foo(): pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/w3c-import.log new file mode 100644 index 0000000000000..a6edfdd337662 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/w3c-import.log new file mode 100644 index 0000000000000..c6f16f440d087 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/pytest.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/conftest.py index 973ccc0c03003..bba5db8b2fde5 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/conftest.py @@ -1,2 +1,3 @@ +# mypy: allow-untyped-defs def pytest_ignore_collect(collection_path): return False diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py index f174823854e76..2809d0cc68956 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py @@ -1,2 +1,3 @@ +# mypy: allow-untyped-defs def test(): pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/w3c-import.log new file mode 100644 index 0000000000000..2fc2258585b35 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/w3c-import.log new file mode 100644 index 0000000000000..711f23529adfa --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/__init__.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/conftest.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py index e69de29bb2d1d..58c41942d1ca1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py @@ -0,0 +1,3 @@ +# mypy: allow-untyped-defs +def test_init(): + pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py index f174823854e76..d88c001c2cc2a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py @@ -1,2 +1,3 @@ -def test(): +# mypy: allow-untyped-defs +def test_foo(): pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/w3c-import.log new file mode 100644 index 0000000000000..4eba2ad912cf8 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/test_foo.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/test_foo.py index 8f2d73cfa4f2e..d88c001c2cc2a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/test_foo.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/test_foo.py @@ -1,2 +1,3 @@ +# mypy: allow-untyped-defs def test_foo(): pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/w3c-import.log new file mode 100644 index 0000000000000..3952c1452fe9c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/__init__.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/test_foo.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/conftest.py index 8973e4252d3ea..64bbeefac1dc0 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/conftest.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs def pytest_configure(config): import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/w3c-import.log new file mode 100644 index 0000000000000..79a91b0159c44 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/__init__.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/conftest.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/conftest.py new file mode 100644 index 0000000000000..fe1c743a6860c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/conftest.py @@ -0,0 +1,23 @@ +# mypy: allow-untyped-defs +# content of conftest.py +import json + +import pytest + + +class ManifestDirectory(pytest.Directory): + def collect(self): + manifest_path = self.path / "manifest.json" + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + ihook = self.ihook + for file in manifest["files"]: + yield from ihook.pytest_collect_file( + file_path=self.path / file, parent=self + ) + + +@pytest.hookimpl +def pytest_collect_directory(path, parent): + if path.joinpath("manifest.json").is_file(): + return ManifestDirectory.from_parent(parent=parent, path=path) + return None diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/pytest.ini b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/pytest.ini new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/manifest.json b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/manifest.json new file mode 100644 index 0000000000000..6ab6d0a522269 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/manifest.json @@ -0,0 +1,6 @@ +{ + "files": [ + "test_first.py", + "test_second.py" + ] +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/test_first.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/test_first.py new file mode 100644 index 0000000000000..890ca3dea38aa --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/test_first.py @@ -0,0 +1,4 @@ +# mypy: allow-untyped-defs +# content of test_first.py +def test_1(): + pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/test_second.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/test_second.py new file mode 100644 index 0000000000000..42108d5da846c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/test_second.py @@ -0,0 +1,4 @@ +# mypy: allow-untyped-defs +# content of test_second.py +def test_2(): + pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/test_third.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/test_third.py new file mode 100644 index 0000000000000..ede0f3e60256c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/test_third.py @@ -0,0 +1,4 @@ +# mypy: allow-untyped-defs +# content of test_third.py +def test_3(): + pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/w3c-import.log new file mode 100644 index 0000000000000..fd2f97634ff08 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/manifest.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/test_first.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/test_second.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/tests/test_third.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/w3c-import.log new file mode 100644 index 0000000000000..99eea3c89040a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/customdirectory/pytest.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py new file mode 100644 index 0000000000000..e026fe3d192e7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py @@ -0,0 +1,17 @@ +from dataclasses import dataclass +from dataclasses import field + + +def test_dataclasses() -> None: + @dataclass + class SimpleDataObject: + field_a: int = field() + field_b: str = field() + + def __eq__(self, __o: object) -> bool: + return super().__eq__(__o) + + left = SimpleDataObject(1, "b") + right = SimpleDataObject(1, "c") + + assert left == right diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_initvar.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_initvar.py new file mode 100644 index 0000000000000..d687fc2253045 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_initvar.py @@ -0,0 +1,13 @@ +# mypy: allow-untyped-defs +from dataclasses import dataclass +from dataclasses import InitVar + + +@dataclass +class Foo: + init_only: InitVar[int] + real_attr: int + + +def test_demonstrate(): + assert Foo(1, 2) == Foo(1, 3) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py index 0945790f00420..801aa0a732ed1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs from dataclasses import dataclass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/w3c-import.log new file mode 100644 index 0000000000000..00a82770d90ef --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_initvar.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/doctest/main_py/__main__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/doctest/main_py/__main__.py index e471d06d643b0..c8a124f54160c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/doctest/main_py/__main__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/doctest/main_py/__main__.py @@ -1,2 +1,3 @@ +# mypy: allow-untyped-defs def test_this_is_ignored(): assert True diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/doctest/main_py/test_normal_module.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/doctest/main_py/test_normal_module.py index 700cc9750cfab..26a4d90bc89c8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/doctest/main_py/test_normal_module.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/doctest/main_py/test_normal_module.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs def test_doc(): """ >>> 10 > 5 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/doctest/main_py/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/doctest/main_py/w3c-import.log new file mode 100644 index 0000000000000..5abeadbb98574 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/doctest/main_py/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/doctest/main_py/__main__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/doctest/main_py/test_normal_module.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/conftest.py index a7a5e9db80a34..fe1ae620aa636 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/conftest.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/test_foo.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/test_foo.py index f174823854e76..2809d0cc68956 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/test_foo.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/test_foo.py @@ -1,2 +1,3 @@ +# mypy: allow-untyped-defs def test(): pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/w3c-import.log new file mode 100644 index 0000000000000..a64c5e458cd18 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/test_foo.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/w3c-import.log new file mode 100644 index 0000000000000..1728e2613dca6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/__init__.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/conftest.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py index be5adbeb6e536..3a5d3ac33fe26 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/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/LayoutTests/imported/w3c/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 df36da1369b35..d0c4bdbdfd903 100644 --- a/LayoutTests/imported/w3c/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/LayoutTests/imported/w3c/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,2 +1,3 @@ +# mypy: allow-untyped-defs def test_1(arg1): pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/w3c-import.log new file mode 100644 index 0000000000000..d2c188bb88b63 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py +/LayoutTests/imported/w3c/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 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py index 00981c5dc12b2..a1f3b2d58b91c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/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/LayoutTests/imported/w3c/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 1c34f94acc478..45e9744786a4b 100644 --- a/LayoutTests/imported/w3c/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/LayoutTests/imported/w3c/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,2 +1,3 @@ +# mypy: allow-untyped-defs def test_2(arg2): pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/w3c-import.log new file mode 100644 index 0000000000000..1a5fd022f4697 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py +/LayoutTests/imported/w3c/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 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py index d1efcbb338c27..84e5256f07053 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py index 5dfd2f779579c..7f1769beb9b99 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py index 4e22ce5a1373d..ad26fdd8cdc62 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py index 0d891fbb503f8..9ee74a471863c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py @@ -1,2 +1,3 @@ +# mypy: allow-untyped-defs def test_spam(spam): assert spam == "spamspam" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/w3c-import.log new file mode 100644 index 0000000000000..c3ac82226e166 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/w3c-import.log new file mode 100644 index 0000000000000..60eee48e71383 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/__init__.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py index 5dfd2f779579c..7f1769beb9b99 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/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/LayoutTests/imported/w3c/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 46d1446f4702c..fa688f0a84486 100644 --- a/LayoutTests/imported/w3c/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/LayoutTests/imported/w3c/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,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/w3c-import.log new file mode 100644 index 0000000000000..d35b665c40d4b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/__init__.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py index 87a0c89411151..f78a57c322b9f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py index 0661cb301fc12..12e0e3e91d47b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py index 256b92a17ddfd..8b6e8697e0606 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py index e15dbd2ca45de..40587cf2bd1e2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py index b775203231f88..0cc8446d8eec2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/w3c-import.log new file mode 100644 index 0000000000000..e9223618d65b2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_fixture_named_request.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_fixture_named_request.py index 75514bf8b8c53..a2ab7ee330d38 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_fixture_named_request.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_fixture_named_request.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py index 055a1220b1c92..0f316f0e44984 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/w3c-import.log new file mode 100644 index 0000000000000..7759b43791c19 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_fixture_named_request.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/conftest.py index cb8f5d671ea85..bde5c0711ace5 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/conftest.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest @@ -11,4 +12,5 @@ def pytest_collect_file(file_path, parent): class MyItem(pytest.Item): - pass + def runtest(self): + raise NotImplementedError() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py index 56444d147480f..dd18e1741f01e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py @@ -1,2 +1,3 @@ +# mypy: allow-untyped-defs def test_hello(): pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/w3c-import.log new file mode 100644 index 0000000000000..01ad9bce41d34 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/__init__.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue_519.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue_519.py index e44367fca0468..39766164490f0 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue_519.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue_519.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pprint from typing import List from typing import Tuple @@ -22,13 +23,13 @@ def checked_order(): assert order == [ ("issue_519.py", "fix1", "arg1v1"), ("test_one[arg1v1-arg2v1]", "fix2", "arg2v1"), - ("test_two[arg1v1-arg2v1]", "fix2", "arg2v1"), ("test_one[arg1v1-arg2v2]", "fix2", "arg2v2"), + ("test_two[arg1v1-arg2v1]", "fix2", "arg2v1"), ("test_two[arg1v1-arg2v2]", "fix2", "arg2v2"), ("issue_519.py", "fix1", "arg1v2"), ("test_one[arg1v2-arg2v1]", "fix2", "arg2v1"), - ("test_two[arg1v2-arg2v1]", "fix2", "arg2v1"), ("test_one[arg1v2-arg2v2]", "fix2", "arg2v2"), + ("test_two[arg1v2-arg2v1]", "fix2", "arg2v1"), ("test_two[arg1v2-arg2v2]", "fix2", "arg2v2"), ] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py index 35a2c7b762896..d95ad0a83d9f3 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/w3c-import.log new file mode 100644 index 0000000000000..0849983cb51a5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/__init__.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/generate_folders.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/generate_folders.py index ff1eaf7d6bb87..17085e50b54e8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/generate_folders.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/generate_folders.py @@ -1,6 +1,8 @@ +# mypy: allow-untyped-defs import argparse import pathlib + HERE = pathlib.Path(__file__).parent TEST_CONTENT = (HERE / "template_test.py").read_bytes() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/template_test.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/template_test.py index 064ade190a10c..f50eb65525c88 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/template_test.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/template_test.py @@ -1,2 +1,3 @@ +# mypy: allow-untyped-defs def test_x(): pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/w3c-import.log new file mode 100644 index 0000000000000..fec551830f257 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/generate_folders.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/template_test.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/tmpdir/tmp_path_fixture.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/tmpdir/tmp_path_fixture.py index 8675eb2fa620d..4aa35faa0b656 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/tmpdir/tmp_path_fixture.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/tmpdir/tmp_path_fixture.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/tmpdir/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/tmpdir/w3c-import.log new file mode 100644 index 0000000000000..5e13d33578c6e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/tmpdir/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/tmpdir/tmp_path_fixture.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py index d421ce927c944..d66b66df5b73d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import unittest import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip.py index 93f79bb3b2e24..7550a0975769d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip.py @@ -1,4 +1,6 @@ +# mypy: allow-untyped-defs """Skipping an entire subclass with unittest.skip() should *not* call setUp from a base class.""" + import unittest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_class.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_class.py index 4f251dcba1781..48f7e476f40ae 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_class.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_class.py @@ -1,4 +1,6 @@ +# mypy: allow-untyped-defs """Skipping an entire subclass with unittest.skip() should *not* call setUpClass from a base class.""" + import unittest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_module.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_module.py index 98befbe510fd3..eee4263d22be7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_module.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_module.py @@ -1,4 +1,6 @@ +# mypy: allow-untyped-defs """setUpModule is always called, even if all tests in the module are skipped""" + import unittest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asyncio.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asyncio.py index 1cd2168604c49..a82ddaebcc3c3 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asyncio.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asyncio.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs from typing import List from unittest import IsolatedAsyncioTestCase diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asynctest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asynctest.py index fb26617067c72..e9b10171e8d2d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asynctest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asynctest.py @@ -1,4 +1,6 @@ +# mypy: allow-untyped-defs """Issue #7110""" + import asyncio from typing import List diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_plain_async.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_plain_async.py index 78dfece684ebb..2a4a66509a4cd 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_plain_async.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_plain_async.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import unittest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/w3c-import.log new file mode 100644 index 0000000000000..95a65a5720707 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_class.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_module.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asyncio.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asynctest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_plain_async.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/w3c-import.log new file mode 100644 index 0000000000000..f5788e4f4a4f2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue_519.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/junit-10.xsd +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/pytest.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message.py index 6985caa4407f3..be64a1ff2c801 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import warnings import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py index b8c11cb71c983..95fa795efe047 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import warnings import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py index 636d04a5505ac..5204fde8a85bd 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs from test_1 import func diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/w3c-import.log new file mode 100644 index 0000000000000..637f974917364 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/w3c-import.log new file mode 100644 index 0000000000000..79680c7b6eb09 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/examples/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/examples/w3c-import.log new file mode 100644 index 0000000000000..b0188f5d8bfc3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/examples/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/examples/test_issue519.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/create_executable.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/create_executable.py index 998df7b1ca727..fbfda2e5d9448 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/create_executable.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/create_executable.py @@ -1,11 +1,13 @@ """Generate an executable with pytest runner embedded using PyInstaller.""" + if __name__ == "__main__": - import pytest import subprocess + import pytest + hidden = [] for x in pytest.freeze_includes(): hidden.extend(["--hidden-import", x]) hidden.extend(["--hidden-import", "distutils"]) - args = ["pyinstaller", "--noconfirm"] + hidden + ["runtests_script.py"] + args = ["pyinstaller", "--noconfirm", *hidden, "runtests_script.py"] subprocess.check_call(" ".join(args), shell=True) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/runtests_script.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/runtests_script.py index 591863016acea..ef63a2d15b9d3 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/runtests_script.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/runtests_script.py @@ -5,6 +5,7 @@ if __name__ == "__main__": import sys + import pytest sys.exit(pytest.main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/tests/test_trivial.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/tests/test_trivial.py index 08a55552abbf6..425f29a649c76 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/tests/test_trivial.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/tests/test_trivial.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs def test_upper(): assert "foo".upper() == "FOO" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/tests/w3c-import.log new file mode 100644 index 0000000000000..9372db1ff9859 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/tests/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/tests/test_doctest.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/tests/test_trivial.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/tox_run.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/tox_run.py index 678a69c858a2c..7fd63cf1218f2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/tox_run.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/tox_run.py @@ -2,6 +2,7 @@ Called by tox.ini: uses the generated executable to run the tests in ./tests/ directory. """ + if __name__ == "__main__": import os import sys diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/w3c-import.log new file mode 100644 index 0000000000000..52085b65b7fd4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/create_executable.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/runtests_script.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/freeze/tox_run.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_pprint.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_pprint.py new file mode 100644 index 0000000000000..15fe6611280d8 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_pprint.py @@ -0,0 +1,406 @@ +from collections import ChainMap +from collections import Counter +from collections import defaultdict +from collections import deque +from collections import OrderedDict +from dataclasses import dataclass +import textwrap +from types import MappingProxyType +from types import SimpleNamespace +from typing import Any + +from _pytest._io.pprint import PrettyPrinter +import pytest + + +@dataclass +class EmptyDataclass: + pass + + +@dataclass +class DataclassWithOneItem: + foo: str + + +@dataclass +class DataclassWithTwoItems: + foo: str + bar: str + + +@pytest.mark.parametrize( + ("data", "expected"), + ( + pytest.param( + EmptyDataclass(), + "EmptyDataclass()", + id="dataclass-empty", + ), + pytest.param( + DataclassWithOneItem(foo="bar"), + """ + DataclassWithOneItem( + foo='bar', + ) + """, + id="dataclass-one-item", + ), + pytest.param( + DataclassWithTwoItems(foo="foo", bar="bar"), + """ + DataclassWithTwoItems( + foo='foo', + bar='bar', + ) + """, + id="dataclass-two-items", + ), + pytest.param( + {}, + "{}", + id="dict-empty", + ), + pytest.param( + {"one": 1}, + """ + { + 'one': 1, + } + """, + id="dict-one-item", + ), + pytest.param( + {"one": 1, "two": 2}, + """ + { + 'one': 1, + 'two': 2, + } + """, + id="dict-two-items", + ), + pytest.param(OrderedDict(), "OrderedDict()", id="ordereddict-empty"), + pytest.param( + OrderedDict({"one": 1}), + """ + OrderedDict({ + 'one': 1, + }) + """, + id="ordereddict-one-item", + ), + pytest.param( + OrderedDict({"one": 1, "two": 2}), + """ + OrderedDict({ + 'one': 1, + 'two': 2, + }) + """, + id="ordereddict-two-items", + ), + pytest.param( + [], + "[]", + id="list-empty", + ), + pytest.param( + [1], + """ + [ + 1, + ] + """, + id="list-one-item", + ), + pytest.param( + [1, 2], + """ + [ + 1, + 2, + ] + """, + id="list-two-items", + ), + pytest.param( + tuple(), + "()", + id="tuple-empty", + ), + pytest.param( + (1,), + """ + ( + 1, + ) + """, + id="tuple-one-item", + ), + pytest.param( + (1, 2), + """ + ( + 1, + 2, + ) + """, + id="tuple-two-items", + ), + pytest.param( + set(), + "set()", + id="set-empty", + ), + pytest.param( + {1}, + """ + { + 1, + } + """, + id="set-one-item", + ), + pytest.param( + {1, 2}, + """ + { + 1, + 2, + } + """, + id="set-two-items", + ), + pytest.param( + MappingProxyType({}), + "mappingproxy({})", + id="mappingproxy-empty", + ), + pytest.param( + MappingProxyType({"one": 1}), + """ + mappingproxy({ + 'one': 1, + }) + """, + id="mappingproxy-one-item", + ), + pytest.param( + MappingProxyType({"one": 1, "two": 2}), + """ + mappingproxy({ + 'one': 1, + 'two': 2, + }) + """, + id="mappingproxy-two-items", + ), + pytest.param( + SimpleNamespace(), + "namespace()", + id="simplenamespace-empty", + ), + pytest.param( + SimpleNamespace(one=1), + """ + namespace( + one=1, + ) + """, + id="simplenamespace-one-item", + ), + pytest.param( + SimpleNamespace(one=1, two=2), + """ + namespace( + one=1, + two=2, + ) + """, + id="simplenamespace-two-items", + ), + pytest.param( + defaultdict(str), "defaultdict(, {})", id="defaultdict-empty" + ), + pytest.param( + defaultdict(str, {"one": "1"}), + """ + defaultdict(, { + 'one': '1', + }) + """, + id="defaultdict-one-item", + ), + pytest.param( + defaultdict(str, {"one": "1", "two": "2"}), + """ + defaultdict(, { + 'one': '1', + 'two': '2', + }) + """, + id="defaultdict-two-items", + ), + pytest.param( + Counter(), + "Counter()", + id="counter-empty", + ), + pytest.param( + Counter("1"), + """ + Counter({ + '1': 1, + }) + """, + id="counter-one-item", + ), + pytest.param( + Counter("121"), + """ + Counter({ + '1': 2, + '2': 1, + }) + """, + id="counter-two-items", + ), + pytest.param(ChainMap(), "ChainMap({})", id="chainmap-empty"), + pytest.param( + ChainMap({"one": 1, "two": 2}), + """ + ChainMap( + { + 'one': 1, + 'two': 2, + }, + ) + """, + id="chainmap-one-item", + ), + pytest.param( + ChainMap({"one": 1}, {"two": 2}), + """ + ChainMap( + { + 'one': 1, + }, + { + 'two': 2, + }, + ) + """, + id="chainmap-two-items", + ), + pytest.param( + deque(), + "deque([])", + id="deque-empty", + ), + pytest.param( + deque([1]), + """ + deque([ + 1, + ]) + """, + id="deque-one-item", + ), + pytest.param( + deque([1, 2]), + """ + deque([ + 1, + 2, + ]) + """, + id="deque-two-items", + ), + pytest.param( + deque([1, 2], maxlen=3), + """ + deque(maxlen=3, [ + 1, + 2, + ]) + """, + id="deque-maxlen", + ), + pytest.param( + { + "chainmap": ChainMap({"one": 1}, {"two": 2}), + "counter": Counter("122"), + "dataclass": DataclassWithTwoItems(foo="foo", bar="bar"), + "defaultdict": defaultdict(str, {"one": "1", "two": "2"}), + "deque": deque([1, 2], maxlen=3), + "dict": {"one": 1, "two": 2}, + "list": [1, 2], + "mappingproxy": MappingProxyType({"one": 1, "two": 2}), + "ordereddict": OrderedDict({"one": 1, "two": 2}), + "set": {1, 2}, + "simplenamespace": SimpleNamespace(one=1, two=2), + "tuple": (1, 2), + }, + """ + { + 'chainmap': ChainMap( + { + 'one': 1, + }, + { + 'two': 2, + }, + ), + 'counter': Counter({ + '2': 2, + '1': 1, + }), + 'dataclass': DataclassWithTwoItems( + foo='foo', + bar='bar', + ), + 'defaultdict': defaultdict(, { + 'one': '1', + 'two': '2', + }), + 'deque': deque(maxlen=3, [ + 1, + 2, + ]), + 'dict': { + 'one': 1, + 'two': 2, + }, + 'list': [ + 1, + 2, + ], + 'mappingproxy': mappingproxy({ + 'one': 1, + 'two': 2, + }), + 'ordereddict': OrderedDict({ + 'one': 1, + 'two': 2, + }), + 'set': { + 1, + 2, + }, + 'simplenamespace': namespace( + one=1, + two=2, + ), + 'tuple': ( + 1, + 2, + ), + } + """, + id="deep-example", + ), + ), +) +def test_consistent_pretty_printer(data: Any, expected: str) -> None: + assert PrettyPrinter().pformat(data) == textwrap.dedent(expected).strip() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_saferepr.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_saferepr.py index 63d3af822b17a..5d270f1756c59 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_saferepr.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_saferepr.py @@ -1,7 +1,8 @@ -import pytest -from _pytest._io.saferepr import _pformat_dispatch +# mypy: allow-untyped-defs from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE from _pytest._io.saferepr import saferepr +from _pytest._io.saferepr import saferepr_unlimited +import pytest def test_simple_repr(): @@ -58,9 +59,7 @@ class BrokenReprException(Exception): 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) - ) + f"<[unpresentable exception ({exp_exc!s}) raised in repr()] BrokenRepr object at 0x{id(obj):x}>" ) @@ -80,7 +79,7 @@ def raise_exc(self, *args): raise self.exc_type(*args) raise self.exc_type - def __str__(self): + def __str__(self): # noqa: PLE0307 self.raise_exc("__str__") def __repr__(self): @@ -98,14 +97,12 @@ def __repr__(self): 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)) + f"<[unpresentable exception ({baseexc_str!r}) " + f"raised in repr()] BrokenObj object at 0x{id(obj):x}>" ) obj = BrokenObj(RaisingOnStrRepr([RaisingOnStrRepr([BaseException])])) assert saferepr(obj) == ( - "<[{!r} raised in repr()] BrokenObj object at 0x{:x}>".format( - baseexc_str, id(obj) - ) + f"<[{baseexc_str!r} raised in repr()] BrokenObj object at 0x{id(obj):x}>" ) with pytest.raises(KeyboardInterrupt): @@ -158,12 +155,6 @@ def test_unicode(): 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) @@ -179,3 +170,23 @@ def __repr__(self): assert saferepr(SomeClass()).startswith( "<[RuntimeError() raised in repr()] SomeClass object at 0x" ) + + +def test_saferepr_unlimited(): + dict5 = {f"v{i}": i for i in range(5)} + assert saferepr_unlimited(dict5) == "{'v0': 0, 'v1': 1, 'v2': 2, 'v3': 3, 'v4': 4}" + + dict_long = {f"v{i}": i for i in range(1_000)} + r = saferepr_unlimited(dict_long) + assert "..." not in r + assert "\n" not in r + + +def test_saferepr_unlimited_exc(): + class A: + def __repr__(self): + raise ValueError(42) + + assert saferepr_unlimited(A()).startswith( + "<[ValueError(42) raised in repr()] A object at 0x" + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_terminalwriter.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_terminalwriter.py index 4866c94a55884..afa8d5cae871c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_terminalwriter.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_terminalwriter.py @@ -1,15 +1,17 @@ +# mypy: allow-untyped-defs import io import os +from pathlib import Path import re import shutil import sys -from pathlib import Path from typing import Generator +from typing import Optional from unittest import mock -import pytest from _pytest._io import terminalwriter from _pytest.monkeypatch import MonkeyPatch +import pytest # These tests were initially copied from py 1.8.1. @@ -56,7 +58,7 @@ def test_terminalwriter_not_unicode() -> None: 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" + assert buffer.getvalue() == rb"hello \U0001f300 w\xf4rld \u05d0\u05d1\u05d2" win32 = int(sys.platform == "win32") @@ -164,53 +166,67 @@ def test_attr_hasmarkup() -> None: assert "\x1b[0m" in s -def assert_color_set(): +def assert_color(expected: bool, default: Optional[bool] = None) -> None: file = io.StringIO() - tw = terminalwriter.TerminalWriter(file) - assert tw.hasmarkup + if default is None: + default = not expected + file.isatty = lambda: default # type: ignore + tw = terminalwriter.TerminalWriter(file=file) + assert tw.hasmarkup is expected 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" + if expected: + assert len(s) > len("hello\n") + assert "\x1b[1m" in s + assert "\x1b[0m" in s + else: + 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() + assert_color(True) def test_should_not_do_markup_PY_COLORS_eq_0(monkeypatch: MonkeyPatch) -> None: monkeypatch.setitem(os.environ, "PY_COLORS", "0") - assert_color_not_set() + assert_color(False) def test_should_not_do_markup_NO_COLOR(monkeypatch: MonkeyPatch) -> None: monkeypatch.setitem(os.environ, "NO_COLOR", "1") - assert_color_not_set() + assert_color(False) def test_should_do_markup_FORCE_COLOR(monkeypatch: MonkeyPatch) -> None: monkeypatch.setitem(os.environ, "FORCE_COLOR", "1") - assert_color_set() + assert_color(True) -def test_should_not_do_markup_NO_COLOR_and_FORCE_COLOR( +@pytest.mark.parametrize( + ["NO_COLOR", "FORCE_COLOR", "expected"], + [ + ("1", "1", False), + ("", "1", True), + ("1", "", False), + ], +) +def test_NO_COLOR_and_FORCE_COLOR( monkeypatch: MonkeyPatch, + NO_COLOR: str, + FORCE_COLOR: str, + expected: bool, ) -> None: - monkeypatch.setitem(os.environ, "NO_COLOR", "1") - monkeypatch.setitem(os.environ, "FORCE_COLOR", "1") - assert_color_not_set() + monkeypatch.setitem(os.environ, "NO_COLOR", NO_COLOR) + monkeypatch.setitem(os.environ, "FORCE_COLOR", FORCE_COLOR) + assert_color(expected) + + +def test_empty_NO_COLOR_and_FORCE_COLOR_ignored(monkeypatch: MonkeyPatch) -> None: + monkeypatch.setitem(os.environ, "NO_COLOR", "") + monkeypatch.setitem(os.environ, "FORCE_COLOR", "") + assert_color(True, True) + assert_color(False, False) class TestTerminalWriterLineWidth: @@ -254,7 +270,7 @@ def test_combining(self) -> None: pytest.param( True, True, - "{kw}assert{hl-reset} {number}0{hl-reset}\n", + "{reset}{kw}assert{hl-reset} {number}0{hl-reset}{endline}\n", id="with markup and code_highlight", ), pytest.param( @@ -291,3 +307,17 @@ def test_code_highlight(has_markup, code_highlight, expected, color_mapping): match=re.escape("indents size (2) should have same size as lines (1)"), ): tw._write_source(["assert 0"], [" ", " "]) + + +def test_highlight_empty_source() -> None: + """Don't crash trying to highlight empty source code. + + Issue #11758. + """ + f = io.StringIO() + tw = terminalwriter.TerminalWriter(f) + tw.hasmarkup = True + tw.code_highlight = True + tw._write_source([]) + + assert f.getvalue() == "" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_wcwidth.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_wcwidth.py index 7cc74df5d0791..82503b8300c29 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_wcwidth.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_wcwidth.py @@ -1,6 +1,6 @@ -import pytest from _pytest._io.wcwidth import wcswidth from _pytest._io.wcwidth import wcwidth +import pytest @pytest.mark.parametrize( @@ -11,11 +11,11 @@ ("a", 1), ("1", 1), ("א", 1), - ("\u200B", 0), - ("\u1ABE", 0), + ("\u200b", 0), + ("\u1abe", 0), ("\u0591", 0), ("🉐", 2), - ("$", 2), + ("$", 2), # noqa: RUF001 ], ) def test_wcwidth(c: str, expected: int) -> None: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/w3c-import.log new file mode 100644 index 0000000000000..2c648db3449da --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_pprint.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_saferepr.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_terminalwriter.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/io/test_wcwidth.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/logging/test_fixture.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/logging/test_fixture.py index bcb20de580557..c1cfff632afa1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/logging/test_fixture.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/logging/test_fixture.py @@ -1,19 +1,36 @@ +# mypy: disable-error-code="attr-defined" +# mypy: disallow-untyped-defs import logging +from typing import Iterator -import pytest from _pytest.logging import caplog_records_key from _pytest.pytester import Pytester +import pytest + logger = logging.getLogger(__name__) sublogger = logging.getLogger(__name__ + ".baz") +@pytest.fixture(autouse=True) +def cleanup_disabled_logging() -> Iterator[None]: + """Simple fixture that ensures that a test doesn't disable logging. + + This is necessary because ``logging.disable()`` is global, so a test disabling logging + and not cleaning up after will break every test that runs after it. + + This behavior was moved to a fixture so that logging will be un-disabled even if the test fails an assertion. + """ + yield + logging.disable(logging.NOTSET) + + def test_fixture_help(pytester: Pytester) -> None: result = pytester.runpytest("--fixtures") result.stdout.fnmatch_lines(["*caplog*"]) -def test_change_level(caplog): +def test_change_level(caplog: pytest.LogCaptureFixture) -> None: caplog.set_level(logging.INFO) logger.debug("handler DEBUG level") logger.info("handler INFO level") @@ -28,10 +45,27 @@ def test_change_level(caplog): assert "CRITICAL" in caplog.text +def test_change_level_logging_disabled(caplog: pytest.LogCaptureFixture) -> None: + logging.disable(logging.CRITICAL) + assert logging.root.manager.disable == logging.CRITICAL + caplog.set_level(logging.WARNING) + logger.info("handler INFO level") + logger.warning("handler WARNING level") + + caplog.set_level(logging.CRITICAL, logger=sublogger.name) + sublogger.warning("logger SUB_WARNING level") + sublogger.critical("logger SUB_CRITICAL level") + + assert "INFO" not in caplog.text + assert "WARNING" in caplog.text + assert "SUB_WARNING" not in caplog.text + assert "SUB_CRITICAL" in caplog.text + + def test_change_level_undo(pytester: Pytester) -> 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). + Tests the logging output themselves (affected both by logger and handler levels). """ pytester.makepyfile( """ @@ -54,7 +88,36 @@ def test2(caplog): result.stdout.no_fnmatch_line("*log from test2*") -def test_change_level_undos_handler_level(pytester: Pytester) -> None: +def test_change_disabled_level_undo(pytester: Pytester) -> None: + """Ensure that '_force_enable_logging' in 'set_level' is undone after the end of the test. + + Tests the logging output themselves (affected by disabled logging level). + """ + pytester.makepyfile( + """ + import logging + + def test1(caplog): + logging.disable(logging.CRITICAL) + caplog.set_level(logging.INFO) + # using + operator here so fnmatch_lines doesn't match the code in the traceback + logging.info('log from ' + 'test1') + assert 0 + + def test2(caplog): + # using + operator here so fnmatch_lines doesn't match the code in the traceback + # use logging.warning because we need a level that will show up if logging.disabled + # isn't reset to ``CRITICAL`` after test1. + logging.warning('log from ' + 'test2') + assert 0 + """ + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines(["*log from test1*", "*2 failed in *"]) + result.stdout.no_fnmatch_line("*log from test2*") + + +def test_change_level_undoes_handler_level(pytester: Pytester) -> None: """Ensure that 'set_level' is undone after the end of the test (handler). Issue #7569. Tests the handler level specifically. @@ -82,7 +145,7 @@ def test3(caplog): result.assert_outcomes(passed=3) -def test_with_statement(caplog): +def test_with_statement_at_level(caplog: pytest.LogCaptureFixture) -> None: with caplog.at_level(logging.INFO): logger.debug("handler DEBUG level") logger.info("handler INFO level") @@ -97,7 +160,84 @@ def test_with_statement(caplog): assert "CRITICAL" in caplog.text -def test_log_access(caplog): +def test_with_statement_at_level_logging_disabled( + caplog: pytest.LogCaptureFixture, +) -> None: + logging.disable(logging.CRITICAL) + assert logging.root.manager.disable == logging.CRITICAL + with caplog.at_level(logging.WARNING): + logger.debug("handler DEBUG level") + logger.info("handler INFO level") + logger.warning("handler WARNING level") + logger.error("handler ERROR level") + logger.critical("handler CRITICAL level") + + assert logging.root.manager.disable == logging.INFO + + with caplog.at_level(logging.CRITICAL, logger=sublogger.name): + sublogger.warning("logger SUB_WARNING level") + sublogger.critical("logger SUB_CRITICAL level") + + assert "DEBUG" not in caplog.text + assert "INFO" not in caplog.text + assert "WARNING" in caplog.text + assert "ERROR" in caplog.text + assert " CRITICAL" in caplog.text + assert "SUB_WARNING" not in caplog.text + assert "SUB_CRITICAL" in caplog.text + assert logging.root.manager.disable == logging.CRITICAL + + +def test_with_statement_filtering(caplog: pytest.LogCaptureFixture) -> None: + class TestFilter(logging.Filter): + def filter(self, record: logging.LogRecord) -> bool: + record.msg = "filtered handler call" + return True + + with caplog.at_level(logging.INFO): + with caplog.filtering(TestFilter()): + logger.info("handler call") + logger.info("handler call") + + filtered_tuple, unfiltered_tuple = caplog.record_tuples + assert filtered_tuple == ("test_fixture", 20, "filtered handler call") + assert unfiltered_tuple == ("test_fixture", 20, "handler call") + + +@pytest.mark.parametrize( + "level_str,expected_disable_level", + [ + ("CRITICAL", logging.ERROR), + ("ERROR", logging.WARNING), + ("WARNING", logging.INFO), + ("INFO", logging.DEBUG), + ("DEBUG", logging.NOTSET), + ("NOTSET", logging.NOTSET), + ("NOTVALIDLEVEL", logging.NOTSET), + ], +) +def test_force_enable_logging_level_string( + caplog: pytest.LogCaptureFixture, level_str: str, expected_disable_level: int +) -> None: + """Test _force_enable_logging using a level string. + + ``expected_disable_level`` is one level below ``level_str`` because the disabled log level + always needs to be *at least* one level lower than the level that caplog is trying to capture. + """ + test_logger = logging.getLogger("test_str_level_force_enable") + # Emulate a testing environment where all logging is disabled. + logging.disable(logging.CRITICAL) + # Make sure all logging is disabled. + assert not test_logger.isEnabledFor(logging.CRITICAL) + # Un-disable logging for `level_str`. + caplog._force_enable_logging(level_str, test_logger) + # Make sure that the disabled level is now one below the requested logging level. + # We don't use `isEnabledFor` here because that also checks the level set by + # `logging.setLevel()` which is irrelevant to `logging.disable()`. + assert test_logger.manager.disable == expected_disable_level + + +def test_log_access(caplog: pytest.LogCaptureFixture) -> None: caplog.set_level(logging.INFO) logger.info("boo %s", "arg") assert caplog.records[0].levelname == "INFO" @@ -105,7 +245,7 @@ def test_log_access(caplog): assert "boo arg" in caplog.text -def test_messages(caplog): +def test_messages(caplog: pytest.LogCaptureFixture) -> None: caplog.set_level(logging.INFO) logger.info("boo %s", "arg") logger.info("bar %s\nbaz %s", "arg1", "arg2") @@ -126,14 +266,14 @@ def test_messages(caplog): assert "Exception" not in caplog.messages[-1] -def test_record_tuples(caplog): +def test_record_tuples(caplog: pytest.LogCaptureFixture) -> None: caplog.set_level(logging.INFO) logger.info("boo %s", "arg") assert caplog.record_tuples == [(__name__, logging.INFO, "boo arg")] -def test_unicode(caplog): +def test_unicode(caplog: pytest.LogCaptureFixture) -> None: caplog.set_level(logging.INFO) logger.info("bū") assert caplog.records[0].levelname == "INFO" @@ -141,7 +281,7 @@ def test_unicode(caplog): assert "bū" in caplog.text -def test_clear(caplog): +def test_clear(caplog: pytest.LogCaptureFixture) -> None: caplog.set_level(logging.INFO) logger.info("bū") assert len(caplog.records) @@ -152,7 +292,9 @@ def test_clear(caplog): @pytest.fixture -def logging_during_setup_and_teardown(caplog): +def logging_during_setup_and_teardown( + caplog: pytest.LogCaptureFixture, +) -> Iterator[None]: caplog.set_level("INFO") logger.info("a_setup_log") yield @@ -160,7 +302,17 @@ def logging_during_setup_and_teardown(caplog): assert [x.message for x in caplog.get_records("teardown")] == ["a_teardown_log"] -def test_caplog_captures_for_all_stages(caplog, logging_during_setup_and_teardown): +def private_assert_caplog_records_is_setup_call( + caplog: pytest.LogCaptureFixture, +) -> None: + # This reaches into private API, don't use this type of thing in real tests! + caplog_records = caplog._item.stash[caplog_records_key] + assert set(caplog_records) == {"setup", "call"} + + +def test_captures_for_all_stages( + caplog: pytest.LogCaptureFixture, logging_during_setup_and_teardown: None +) -> None: assert not caplog.records assert not caplog.get_records("call") logger.info("a_call_log") @@ -168,8 +320,27 @@ 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.stash[caplog_records_key]) == {"setup", "call"} + private_assert_caplog_records_is_setup_call(caplog) + + +def test_clear_for_call_stage( + caplog: pytest.LogCaptureFixture, logging_during_setup_and_teardown: None +) -> None: + logger.info("a_call_log") + assert [x.message for x in caplog.get_records("call")] == ["a_call_log"] + assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"] + private_assert_caplog_records_is_setup_call(caplog) + + caplog.clear() + + assert caplog.get_records("call") == [] + assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"] + private_assert_caplog_records_is_setup_call(caplog) + + logging.info("a_call_log_after_clear") + assert [x.message for x in caplog.get_records("call")] == ["a_call_log_after_clear"] + assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"] + private_assert_caplog_records_is_setup_call(caplog) def test_ini_controls_global_log_level(pytester: Pytester) -> None: @@ -195,11 +366,11 @@ def test_log_level_override(request, caplog): ) result = pytester.runpytest() - # make sure that that we get a '0' exit code for the testsuite + # make sure that we get a '0' exit code for the testsuite assert result.ret == 0 -def test_caplog_can_override_global_log_level(pytester: Pytester) -> None: +def test_can_override_global_log_level(pytester: Pytester) -> None: pytester.makepyfile( """ import pytest @@ -238,7 +409,7 @@ def test_log_level_override(request, caplog): assert result.ret == 0 -def test_caplog_captures_despite_exception(pytester: Pytester) -> None: +def test_captures_despite_exception(pytester: Pytester) -> None: pytester.makepyfile( """ import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/logging/test_reporting.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/logging/test_reporting.py index 323ff7b2446a3..7e592febf56fa 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/logging/test_reporting.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/logging/test_reporting.py @@ -1,14 +1,15 @@ +# mypy: allow-untyped-defs import io import os import re from typing import cast -import pytest from _pytest.capture import CaptureManager from _pytest.config import ExitCode from _pytest.fixtures import FixtureRequest from _pytest.pytester import Pytester from _pytest.terminal import TerminalReporter +import pytest def test_nothing_logged(pytester: Pytester) -> None: @@ -77,14 +78,14 @@ def test_foo(): assert "warning text going to logger" not in stdout assert "info text going to logger" not in stdout - # The log file should contain the warning and the error log messages and - # not the info one, because the default level of the root logger is - # WARNING. + # The log file should only contain the error log messages and + # not the warning or info ones, because the root logger is set to + # ERROR using --log-level=ERROR. assert os.path.isfile(log_file) - with open(log_file) as rfh: + with open(log_file, encoding="utf-8") as rfh: contents = rfh.read() assert "info text going to logger" not in contents - assert "warning text going to logger" in contents + assert "warning text going to logger" not in contents assert "error text going to logger" in contents @@ -176,13 +177,11 @@ def teardown_function(function): def test_log_cli_enabled_disabled(pytester: Pytester, enabled: bool) -> None: msg = "critical message logged by test" pytester.makepyfile( - """ + f""" import logging def test_log_cli(): - logging.critical("{}") - """.format( - msg - ) + logging.critical("{msg}") + """ ) if enabled: pytester.makeini( @@ -656,12 +655,79 @@ def test_log_file(request): # make sure that we get a '0' exit code for the testsuite assert result.ret == 0 assert os.path.isfile(log_file) - with open(log_file) as rfh: + with open(log_file, encoding="utf-8") as rfh: + contents = rfh.read() + assert "This log message will be shown" in contents + assert "This log message won't be shown" not in contents + + +def test_log_file_mode_cli(pytester: Pytester) -> None: + # Default log file level + pytester.makepyfile( + """ + import pytest + import logging + def test_log_file(request): + plugin = request.config.pluginmanager.getplugin('logging-plugin') + assert plugin.log_file_handler.level == logging.WARNING + logging.getLogger('catchlog').info("This log message won't be shown") + logging.getLogger('catchlog').warning("This log message will be shown") + print('PASSED') + """ + ) + + log_file = str(pytester.path.joinpath("pytest.log")) + + with open(log_file, mode="w", encoding="utf-8") as wfh: + wfh.write("A custom header\n") + + result = pytester.runpytest( + "-s", + f"--log-file={log_file}", + "--log-file-mode=a", + "--log-file-level=WARNING", + ) + + # fnmatch_lines does an assertion internally + result.stdout.fnmatch_lines(["test_log_file_mode_cli.py PASSED"]) + + # make sure that we get a '0' exit code for the testsuite + assert result.ret == 0 + assert os.path.isfile(log_file) + with open(log_file, encoding="utf-8") as rfh: contents = rfh.read() + assert "A custom header" in contents assert "This log message will be shown" in contents assert "This log message won't be shown" not in contents +def test_log_file_mode_cli_invalid(pytester: Pytester) -> None: + # Default log file level + pytester.makepyfile( + """ + import pytest + import logging + def test_log_file(request): + plugin = request.config.pluginmanager.getplugin('logging-plugin') + assert plugin.log_file_handler.level == logging.WARNING + logging.getLogger('catchlog').info("This log message won't be shown") + logging.getLogger('catchlog').warning("This log message will be shown") + """ + ) + + log_file = str(pytester.path.joinpath("pytest.log")) + + result = pytester.runpytest( + "-s", + f"--log-file={log_file}", + "--log-file-mode=b", + "--log-file-level=WARNING", + ) + + # make sure that we get a '4' exit code for the testsuite + assert result.ret == ExitCode.USAGE_ERROR + + def test_log_file_cli_level(pytester: Pytester) -> None: # Default log file level pytester.makepyfile( @@ -687,7 +753,7 @@ def test_log_file(request): # make sure that we get a '0' exit code for the testsuite assert result.ret == 0 assert os.path.isfile(log_file) - with open(log_file) as rfh: + with open(log_file, encoding="utf-8") as rfh: contents = rfh.read() assert "This log message will be shown" in contents assert "This log message won't be shown" not in contents @@ -709,13 +775,11 @@ def test_log_file_ini(pytester: Pytester) -> None: log_file = str(pytester.path.joinpath("pytest.log")) pytester.makeini( - """ + f""" [pytest] - log_file={} + log_file={log_file} log_file_level=WARNING - """.format( - log_file - ) + """ ) pytester.makepyfile( """ @@ -738,23 +802,62 @@ def test_log_file(request): # make sure that we get a '0' exit code for the testsuite assert result.ret == 0 assert os.path.isfile(log_file) - with open(log_file) as rfh: + with open(log_file, encoding="utf-8") as rfh: contents = rfh.read() assert "This log message will be shown" in contents assert "This log message won't be shown" not in contents -def test_log_file_ini_level(pytester: Pytester) -> None: +def test_log_file_mode_ini(pytester: Pytester) -> None: log_file = str(pytester.path.joinpath("pytest.log")) pytester.makeini( + f""" + [pytest] + log_file={log_file} + log_file_mode=a + log_file_level=WARNING """ + ) + pytester.makepyfile( + """ + import pytest + import logging + def test_log_file(request): + plugin = request.config.pluginmanager.getplugin('logging-plugin') + assert plugin.log_file_handler.level == logging.WARNING + logging.getLogger('catchlog').info("This log message won't be shown") + logging.getLogger('catchlog').warning("This log message will be shown") + print('PASSED') + """ + ) + + with open(log_file, mode="w", encoding="utf-8") as wfh: + wfh.write("A custom header\n") + + result = pytester.runpytest("-s") + + # fnmatch_lines does an assertion internally + result.stdout.fnmatch_lines(["test_log_file_mode_ini.py PASSED"]) + + assert result.ret == ExitCode.OK + assert os.path.isfile(log_file) + with open(log_file, encoding="utf-8") as rfh: + contents = rfh.read() + assert "A custom header" in contents + assert "This log message will be shown" in contents + assert "This log message won't be shown" not in contents + + +def test_log_file_ini_level(pytester: Pytester) -> None: + log_file = str(pytester.path.joinpath("pytest.log")) + + pytester.makeini( + f""" [pytest] - log_file={} + log_file={log_file} log_file_level = INFO - """.format( - log_file - ) + """ ) pytester.makepyfile( """ @@ -777,7 +880,7 @@ def test_log_file(request): # make sure that we get a '0' exit code for the testsuite assert result.ret == 0 assert os.path.isfile(log_file) - with open(log_file) as rfh: + with open(log_file, encoding="utf-8") as rfh: contents = rfh.read() assert "This log message will be shown" in contents assert "This log message won't be shown" not in contents @@ -787,13 +890,11 @@ def test_log_file_unicode(pytester: Pytester) -> None: log_file = str(pytester.path.joinpath("pytest.log")) pytester.makeini( - """ + f""" [pytest] - log_file={} + log_file={log_file} log_file_level = INFO - """.format( - log_file - ) + """ ) pytester.makepyfile( """\ @@ -830,9 +931,10 @@ def test_live_logging_suspends_capture( We parametrize the test to also make sure _LiveLoggingStreamHandler works correctly if no capture manager plugin is installed. """ - import logging import contextlib from functools import partial + import logging + from _pytest.logging import _LiveLoggingStreamHandler class MockCaptureManager: @@ -922,13 +1024,11 @@ def test_collection_logging_to_file(pytester: Pytester) -> None: log_file = str(pytester.path.joinpath("pytest.log")) pytester.makeini( - """ + f""" [pytest] - log_file={} + log_file={log_file} log_file_level = INFO - """.format( - log_file - ) + """ ) pytester.makepyfile( @@ -960,14 +1060,12 @@ def test_log_in_hooks(pytester: Pytester) -> None: log_file = str(pytester.path.joinpath("pytest.log")) pytester.makeini( - """ + f""" [pytest] - log_file={} + log_file={log_file} log_file_level = INFO log_cli=true - """.format( - log_file - ) + """ ) pytester.makeconftest( """ @@ -985,7 +1083,7 @@ def pytest_sessionfinish(session, exitstatus): ) result = pytester.runpytest() result.stdout.fnmatch_lines(["*sessionstart*", "*runtestloop*", "*sessionfinish*"]) - with open(log_file) as rfh: + with open(log_file, encoding="utf-8") as rfh: contents = rfh.read() assert "sessionstart" in contents assert "runtestloop" in contents @@ -996,14 +1094,12 @@ def test_log_in_runtest_logreport(pytester: Pytester) -> None: log_file = str(pytester.path.joinpath("pytest.log")) pytester.makeini( - """ + f""" [pytest] - log_file={} + log_file={log_file} log_file_level = INFO log_cli=true - """.format( - log_file - ) + """ ) pytester.makeconftest( """ @@ -1021,7 +1117,7 @@ def test_first(): """ ) pytester.runpytest() - with open(log_file) as rfh: + with open(log_file, encoding="utf-8") as rfh: contents = rfh.read() assert contents.count("logreport") == 3 @@ -1037,19 +1133,17 @@ def test_log_set_path(pytester: Pytester) -> None: """ ) pytester.makeconftest( - """ + f""" import os import pytest - @pytest.hookimpl(hookwrapper=True, tryfirst=True) + @pytest.hookimpl(wrapper=True, tryfirst=True) def pytest_runtest_setup(item): config = item.config logging_plugin = config.pluginmanager.get_plugin("logging-plugin") - report_file = os.path.join({}, item._request.node.name) + report_file = os.path.join({report_dir_base!r}, item._request.node.name) logging_plugin.set_log_path(report_file) - yield - """.format( - repr(report_dir_base) - ) + return (yield) + """ ) pytester.makepyfile( """ @@ -1065,12 +1159,72 @@ def test_second(): """ ) pytester.runpytest() - with open(os.path.join(report_dir_base, "test_first")) as rfh: + with open(os.path.join(report_dir_base, "test_first"), encoding="utf-8") as rfh: + content = rfh.read() + assert "message from test 1" in content + + with open(os.path.join(report_dir_base, "test_second"), encoding="utf-8") as rfh: + content = rfh.read() + assert "message from test 2" in content + + +def test_log_set_path_with_log_file_mode(pytester: Pytester) -> None: + report_dir_base = str(pytester.path) + + pytester.makeini( + """ + [pytest] + log_file_level = DEBUG + log_cli=true + log_file_mode=a + """ + ) + pytester.makeconftest( + f""" + import os + import pytest + @pytest.hookimpl(wrapper=True, tryfirst=True) + def pytest_runtest_setup(item): + config = item.config + logging_plugin = config.pluginmanager.get_plugin("logging-plugin") + report_file = os.path.join({report_dir_base!r}, item._request.node.name) + logging_plugin.set_log_path(report_file) + return (yield) + """ + ) + pytester.makepyfile( + """ + import logging + logger = logging.getLogger("testcase-logger") + def test_first(): + logger.info("message from test 1") + assert True + + def test_second(): + logger.debug("message from test 2") + assert True + """ + ) + + test_first_log_file = os.path.join(report_dir_base, "test_first") + test_second_log_file = os.path.join(report_dir_base, "test_second") + with open(test_first_log_file, mode="w", encoding="utf-8") as wfh: + wfh.write("A custom header for test 1\n") + + with open(test_second_log_file, mode="w", encoding="utf-8") as wfh: + wfh.write("A custom header for test 2\n") + + result = pytester.runpytest() + assert result.ret == ExitCode.OK + + with open(test_first_log_file, encoding="utf-8") as rfh: content = rfh.read() + assert "A custom header for test 1" in content assert "message from test 1" in content - with open(os.path.join(report_dir_base, "test_second")) as rfh: + with open(test_second_log_file, encoding="utf-8") as rfh: content = rfh.read() + assert "A custom header for test 2" in content assert "message from test 2" in content @@ -1165,3 +1319,228 @@ def test_log_file_cli_subdirectories_are_successfully_created( result = pytester.runpytest("--log-file=foo/bar/logf.log") assert "logf.log" in os.listdir(expected) assert result.ret == ExitCode.OK + + +def test_disable_loggers(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import logging + import os + disabled_log = logging.getLogger('disabled') + test_log = logging.getLogger('test') + def test_logger_propagation(caplog): + with caplog.at_level(logging.DEBUG): + disabled_log.warning("no log; no stderr") + test_log.debug("Visible text!") + assert caplog.record_tuples == [('test', 10, 'Visible text!')] + """ + ) + result = pytester.runpytest("--log-disable=disabled", "-s") + assert result.ret == ExitCode.OK + assert not result.stderr.lines + + +def test_disable_loggers_does_not_propagate(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import logging + import os + + parent_logger = logging.getLogger("parent") + child_logger = parent_logger.getChild("child") + + def test_logger_propagation_to_parent(caplog): + with caplog.at_level(logging.DEBUG): + parent_logger.warning("some parent logger message") + child_logger.warning("some child logger message") + assert len(caplog.record_tuples) == 1 + assert caplog.record_tuples[0][0] == "parent" + assert caplog.record_tuples[0][2] == "some parent logger message" + """ + ) + + result = pytester.runpytest("--log-disable=parent.child", "-s") + assert result.ret == ExitCode.OK + assert not result.stderr.lines + + +def test_log_disabling_works_with_log_cli(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import logging + disabled_log = logging.getLogger('disabled') + test_log = logging.getLogger('test') + + def test_log_cli_works(caplog): + test_log.info("Visible text!") + disabled_log.warning("This string will be suppressed.") + """ + ) + result = pytester.runpytest( + "--log-cli-level=DEBUG", + "--log-disable=disabled", + ) + assert result.ret == ExitCode.OK + result.stdout.fnmatch_lines( + "INFO test:test_log_disabling_works_with_log_cli.py:6 Visible text!" + ) + result.stdout.no_fnmatch_line( + "WARNING disabled:test_log_disabling_works_with_log_cli.py:7 This string will be suppressed." + ) + assert not result.stderr.lines + + +def test_without_date_format_log(pytester: Pytester) -> None: + """Check that date is not printed by default.""" + pytester.makepyfile( + """ + import logging + + logger = logging.getLogger(__name__) + + def test_foo(): + logger.warning('text') + assert False + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines( + ["WARNING test_without_date_format_log:test_without_date_format_log.py:6 text"] + ) + + +def test_date_format_log(pytester: Pytester) -> None: + """Check that log_date_format affects output.""" + pytester.makepyfile( + """ + import logging + + logger = logging.getLogger(__name__) + + def test_foo(): + logger.warning('text') + assert False + """ + ) + pytester.makeini( + """ + [pytest] + log_format=%(asctime)s; %(levelname)s; %(message)s + log_date_format=%Y-%m-%d %H:%M:%S + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.re_match_lines([r"^[0-9-]{10} [0-9:]{8}; WARNING; text"]) + + +def test_date_format_percentf_log(pytester: Pytester) -> None: + """Make sure that microseconds are printed in log.""" + pytester.makepyfile( + """ + import logging + + logger = logging.getLogger(__name__) + + def test_foo(): + logger.warning('text') + assert False + """ + ) + pytester.makeini( + """ + [pytest] + log_format=%(asctime)s; %(levelname)s; %(message)s + log_date_format=%Y-%m-%d %H:%M:%S.%f + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.re_match_lines([r"^[0-9-]{10} [0-9:]{8}.[0-9]{6}; WARNING; text"]) + + +def test_date_format_percentf_tz_log(pytester: Pytester) -> None: + """Make sure that timezone and microseconds are properly formatted together.""" + pytester.makepyfile( + """ + import logging + + logger = logging.getLogger(__name__) + + def test_foo(): + logger.warning('text') + assert False + """ + ) + pytester.makeini( + """ + [pytest] + log_format=%(asctime)s; %(levelname)s; %(message)s + log_date_format=%Y-%m-%d %H:%M:%S.%f%z + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.re_match_lines( + [r"^[0-9-]{10} [0-9:]{8}.[0-9]{6}[+-][0-9\.]+; WARNING; text"] + ) + + +def test_log_file_cli_fallback_options(pytester: Pytester) -> None: + """Make sure that fallback values for log-file formats and level works.""" + pytester.makepyfile( + """ + import logging + logger = logging.getLogger() + + def test_foo(): + logger.info('info text going to logger') + logger.warning('warning text going to logger') + logger.error('error text going to logger') + + assert 0 + """ + ) + log_file = str(pytester.path.joinpath("pytest.log")) + result = pytester.runpytest( + "--log-level=ERROR", + "--log-format=%(asctime)s %(message)s", + "--log-date-format=%H:%M", + "--log-file=pytest.log", + ) + assert result.ret == 1 + + # The log file should only contain the error log messages + # not the warning or info ones and the format and date format + # should match the formats provided using --log-format and --log-date-format + assert os.path.isfile(log_file) + with open(log_file, encoding="utf-8") as rfh: + contents = rfh.read() + assert re.match(r"[0-9]{2}:[0-9]{2} error text going to logger\s*", contents) + assert "info text going to logger" not in contents + assert "warning text going to logger" not in contents + assert "error text going to logger" in contents + + # Try with a different format and date format to make sure that the formats + # are being used + result = pytester.runpytest( + "--log-level=ERROR", + "--log-format=%(asctime)s : %(message)s", + "--log-date-format=%H:%M:%S", + "--log-file=pytest.log", + ) + assert result.ret == 1 + + # The log file should only contain the error log messages + # not the warning or info ones and the format and date format + # should match the formats provided using --log-format and --log-date-format + assert os.path.isfile(log_file) + with open(log_file, encoding="utf-8") as rfh: + contents = rfh.read() + assert re.match( + r"[0-9]{2}:[0-9]{2}:[0-9]{2} : error text going to logger\s*", contents + ) + assert "info text going to logger" not in contents + assert "warning text going to logger" not in contents + assert "error text going to logger" in contents diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/logging/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/logging/w3c-import.log new file mode 100644 index 0000000000000..1f27806871ad5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/logging/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/logging/test_fixture.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/logging/test_formatter.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/logging/test_reporting.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/bdd_wallet.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/bdd_wallet.py index 35927ea5875f1..2bdb15454241f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/bdd_wallet.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/bdd_wallet.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs from pytest_bdd import given from pytest_bdd import scenario from pytest_bdd import then diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest.ini b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest.ini index b42b07d145aba..3bacdef62ab94 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest.ini +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest.ini @@ -1,5 +1,6 @@ [pytest] addopts = --strict-markers +asyncio_mode = strict filterwarnings = error::pytest.PytestWarning ignore:.*.fspath is deprecated and will be replaced by .*.path.*:pytest.PytestDeprecationWarning diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_anyio_integration.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_anyio_integration.py index 65c2f59366344..383d7a0b5db3d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_anyio_integration.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_anyio_integration.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import anyio import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_asyncio_integration.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_asyncio_integration.py index 5d2a3faccfcc7..b216c4beecd6c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_asyncio_integration.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_asyncio_integration.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import asyncio import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_mock_integration.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_mock_integration.py index 740469d00fb97..5494c44270add 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_mock_integration.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_mock_integration.py @@ -1,2 +1,3 @@ +# mypy: allow-untyped-defs def test_mocker(mocker): mocker.MagicMock() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_trio_integration.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_trio_integration.py index 199f7850bc47c..60f48ec609b3e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_trio_integration.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_trio_integration.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import trio import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_twisted_integration.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_twisted_integration.py index 94748d036e5de..0dbf5faeb8a19 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_twisted_integration.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_twisted_integration.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest_twisted from twisted.internet.task import deferLater diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/requirements.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/requirements.txt index 90b253cc6d310..9e152f1191bbb 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/requirements.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/requirements.txt @@ -1,15 +1,17 @@ -anyio[curio,trio]==3.4.0 -django==3.2.9 -pytest-asyncio==0.16.0 -pytest-bdd==5.0.0 -pytest-cov==3.0.0 -pytest-django==4.5.1 +anyio[curio,trio]==4.3.0 +django==5.0.4 +pytest-asyncio==0.23.6 +# Temporarily not installed until pytest-bdd is fixed: +# https://github.com/pytest-dev/pytest/pull/11785 +# pytest-bdd==7.0.1 +pytest-cov==5.0.0 +pytest-django==4.8.0 pytest-flakes==4.0.5 -pytest-html==3.1.1 -pytest-mock==3.6.1 -pytest-rerunfailures==10.2 -pytest-sugar==0.9.4 +pytest-html==4.1.1 +pytest-mock==3.14.0 +pytest-rerunfailures==14.0 +pytest-sugar==1.0.0 pytest-trio==0.7.0 -pytest-twisted==1.13.4 -twisted==21.7.0 -pytest-xvfb==2.0.0 +pytest-twisted==1.14.1 +twisted==24.3.0 +pytest-xvfb==3.0.0 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/simple_integration.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/simple_integration.py index 20b2fc4b5bb7e..48089afcc7e95 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/simple_integration.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/simple_integration.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import pytest diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/w3c-import.log new file mode 100644 index 0000000000000..9435d161a8057 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/w3c-import.log @@ -0,0 +1,28 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/bdd_wallet.feature +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/bdd_wallet.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/django_settings.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest.ini +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_anyio_integration.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_asyncio_integration.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_mock_integration.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_trio_integration.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_twisted_integration.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/requirements.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/simple_integration.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/approx.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/approx.py index 0d411d8a6daad..968e88285121e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/approx.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/approx.py @@ -1,16 +1,19 @@ -import operator -import sys +# mypy: allow-untyped-defs from contextlib import contextmanager from decimal import Decimal from fractions import Fraction +from math import sqrt +import operator from operator import eq from operator import ne from typing import Optional -import pytest from _pytest.pytester import Pytester +from _pytest.python_api import _recursive_sequence_map +import pytest from pytest import approx + inf, nan = float("inf"), float("nan") @@ -36,9 +39,7 @@ def set_continue(self): 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() - ) + f"'{example.source.strip()}' evaluates to '{got.strip()}', not '{example.want.strip()}'" ) return MyDocTestRunner() @@ -93,13 +94,12 @@ def do_assert(lhs, rhs, expected_message, verbosity_level=0): class TestApprox: - def test_error_messages(self, assert_approx_raises_regex): - np = pytest.importorskip("numpy") - + def test_error_messages_native_dtypes(self, assert_approx_raises_regex): assert_approx_raises_regex( 2.0, 1.0, [ + "", " comparison failed", f" Obtained: {SOME_FLOAT}", f" Expected: {SOME_FLOAT} ± {SOME_FLOAT}", @@ -114,6 +114,7 @@ def test_error_messages(self, assert_approx_raises_regex): "c": 3000000.0, }, [ + r"", r" comparison failed. Mismatched elements: 2 / 3:", rf" Max absolute difference: {SOME_FLOAT}", rf" Max relative difference: {SOME_FLOAT}", @@ -123,10 +124,29 @@ def test_error_messages(self, assert_approx_raises_regex): ], ) + assert_approx_raises_regex( + {"a": 1.0, "b": None, "c": None}, + { + "a": None, + "b": 1000.0, + "c": None, + }, + [ + r"", + r" comparison failed. Mismatched elements: 2 / 3:", + r" Max absolute difference: -inf", + r" Max relative difference: -inf", + r" Index \| Obtained\s+\| Expected\s+", + rf" a \| {SOME_FLOAT} \| None", + rf" b \| None\s+\| {SOME_FLOAT} ± {SOME_FLOAT}", + ], + ) + assert_approx_raises_regex( [1.0, 2.0, 3.0, 4.0], [1.0, 3.0, 3.0, 5.0], [ + r"", r" comparison failed. Mismatched elements: 2 / 4:", rf" Max absolute difference: {SOME_FLOAT}", rf" Max relative difference: {SOME_FLOAT}", @@ -136,6 +156,36 @@ def test_error_messages(self, assert_approx_raises_regex): ], ) + assert_approx_raises_regex( + (1, 2.2, 4), + (1, 3.2, 4), + [ + r"", + r" comparison failed. Mismatched elements: 1 / 3:", + rf" Max absolute difference: {SOME_FLOAT}", + rf" Max relative difference: {SOME_FLOAT}", + r" Index \| Obtained\s+\| Expected ", + rf" 1 \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}", + ], + ) + + # Specific test for comparison with 0.0 (relative diff will be 'inf') + assert_approx_raises_regex( + [0.0], + [1.0], + [ + r"", + r" comparison failed. Mismatched elements: 1 / 1:", + rf" Max absolute difference: {SOME_FLOAT}", + r" Max relative difference: inf", + r" Index \| Obtained\s+\| Expected ", + rf"\s*0\s*\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}", + ], + ) + + def test_error_messages_numpy_dtypes(self, assert_approx_raises_regex): + np = pytest.importorskip("numpy") + a = np.linspace(0, 100, 20) b = np.linspace(0, 100, 20) a[10] += 0.5 @@ -143,6 +193,7 @@ def test_error_messages(self, assert_approx_raises_regex): a, b, [ + r"", r" comparison failed. Mismatched elements: 1 / 20:", rf" Max absolute difference: {SOME_FLOAT}", rf" Max relative difference: {SOME_FLOAT}", @@ -165,6 +216,7 @@ def test_error_messages(self, assert_approx_raises_regex): ] ), [ + r"", r" comparison failed. Mismatched elements: 3 / 8:", rf" Max absolute difference: {SOME_FLOAT}", rf" Max relative difference: {SOME_FLOAT}", @@ -176,22 +228,11 @@ def test_error_messages(self, assert_approx_raises_regex): ) # Specific test for comparison with 0.0 (relative diff will be 'inf') - assert_approx_raises_regex( - [0.0], - [1.0], - [ - r" comparison failed. Mismatched elements: 1 / 1:", - rf" Max absolute difference: {SOME_FLOAT}", - r" Max relative difference: inf", - r" Index \| Obtained\s+\| Expected ", - rf"\s*0\s*\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}", - ], - ) - assert_approx_raises_regex( np.array([0.0]), np.array([1.0]), [ + r"", r" comparison failed. Mismatched elements: 1 / 1:", rf" Max absolute difference: {SOME_FLOAT}", r" Max relative difference: inf", @@ -209,6 +250,7 @@ def test_error_messages_invalid_args(self, assert_approx_raises_regex): message = "\n".join(str(e.value).split("\n")[1:]) assert message == "\n".join( [ + " ", " Impossible to compare arrays with different shapes.", " Shapes: (2, 1) and (2, 2)", ] @@ -219,6 +261,7 @@ def test_error_messages_invalid_args(self, assert_approx_raises_regex): message = "\n".join(str(e.value).split("\n")[1:]) assert message == "\n".join( [ + " ", " Impossible to compare lists with different sizes.", " Lengths: 2 and 3", ] @@ -232,6 +275,7 @@ def test_error_messages_with_different_verbosity(self, assert_approx_raises_rege 2.0, 1.0, [ + "", " comparison failed", f" Obtained: {SOME_FLOAT}", f" Expected: {SOME_FLOAT} ± {SOME_FLOAT}", @@ -245,15 +289,15 @@ def test_error_messages_with_different_verbosity(self, assert_approx_raises_rege a, b, [ - r" comparison failed. Mismatched elements: 20 / 20:", - rf" Max absolute difference: {SOME_FLOAT}", - rf" Max relative difference: {SOME_FLOAT}", - r" Index \| Obtained\s+\| Expected", - rf" \(0,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}", - rf" \(1,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}", - rf" \(2,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}...", - "", - rf"\s*...Full output truncated \({SOME_INT} lines hidden\), use '-vv' to show", + r"^ $", + r"^ comparison failed. Mismatched elements: 20 / 20:$", + rf"^ Max absolute difference: {SOME_FLOAT}$", + rf"^ Max relative difference: {SOME_FLOAT}$", + r"^ Index \| Obtained\s+\| Expected\s+$", + rf"^ \(0,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}e-{SOME_INT}$", + rf"^ \(1,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}e-{SOME_INT}\.\.\.$", + "^ $", + rf"^ ...Full output truncated \({SOME_INT} lines hidden\), use '-vv' to show$", ], verbosity_level=0, ) @@ -262,6 +306,7 @@ def test_error_messages_with_different_verbosity(self, assert_approx_raises_rege a, b, [ + r" ", r" comparison failed. Mismatched elements: 20 / 20:", rf" Max absolute difference: {SOME_FLOAT}", rf" Max relative difference: {SOME_FLOAT}", @@ -615,6 +660,20 @@ def test_dict_nonnumeric(self): def test_dict_vs_other(self): assert 1 != approx({"a": 0}) + def test_dict_for_div_by_zero(self, assert_approx_raises_regex): + assert_approx_raises_regex( + {"foo": 42.0}, + {"foo": 0.0}, + [ + r"", + r" comparison failed. Mismatched elements: 1 / 1:", + rf" Max absolute difference: {SOME_FLOAT}", + r" Max relative difference: inf", + r" Index \| Obtained\s+\| Expected ", + rf" foo | {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}", + ], + ) + def test_numpy_array(self): np = pytest.importorskip("numpy") @@ -704,6 +763,23 @@ def test_numpy_array_wrong_shape(self): assert a12 != approx(a21) assert a21 != approx(a12) + def test_numpy_array_implicit_conversion(self): + np = pytest.importorskip("numpy") + + class ImplicitArray: + """Type which is implicitly convertible to a numpy array.""" + + def __init__(self, vals): + self.vals = vals + + def __array__(self, dtype=None, copy=None): + return np.array(self.vals) + + vec1 = ImplicitArray([1.0, 2.0, 3.0]) + vec2 = ImplicitArray([1.0, 2.0, 4.0]) + # see issue #12114 for test case + assert vec1 != approx(vec2) + def test_numpy_array_protocol(self): """ array-like objects such as tensorflow's DeviceArray are handled like ndarray. @@ -772,7 +848,7 @@ def test_foo(): def test_expected_value_type_error(self, x, name): with pytest.raises( TypeError, - match=fr"pytest.approx\(\) does not support nested {name}:", + match=rf"pytest.approx\(\) does not support nested {name}:", ): approx(x) @@ -810,7 +886,6 @@ def test_nonnumeric_false_if_unequal(self, x): assert 1.0 != approx([None]) assert None != approx([1.0]) # noqa: E711 - @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires ordered dicts") def test_nonnumeric_dict_repr(self): """Dicts with non-numerics and infinites have no tolerances""" x1 = {"foo": 1.0000005, "bar": None, "foobar": inf} @@ -860,13 +935,49 @@ def test_numpy_scalar_with_array(self): assert approx(expected, rel=5e-7, abs=0) == actual assert approx(expected, rel=5e-8, abs=0) != actual - def test_generic_sized_iterable_object(self): - class MySizedIterable: - def __iter__(self): - return iter([1, 2, 3, 4]) + def test_generic_ordered_sequence(self): + class MySequence: + def __getitem__(self, i): + return [1, 2, 3, 4][i] def __len__(self): return 4 - expected = MySizedIterable() - assert [1, 2, 3, 4] == approx(expected) + expected = MySequence() + assert [1, 2, 3, 4] == approx(expected, abs=1e-4) + + expected_repr = "approx([1 ± 1.0e-06, 2 ± 2.0e-06, 3 ± 3.0e-06, 4 ± 4.0e-06])" + assert repr(approx(expected)) == expected_repr + + def test_allow_ordered_sequences_only(self) -> None: + """pytest.approx() should raise an error on unordered sequences (#9692).""" + with pytest.raises(TypeError, match="only supports ordered sequences"): + assert {1, 2, 3} == approx({1, 2, 3}) + + +class TestRecursiveSequenceMap: + def test_map_over_scalar(self): + assert _recursive_sequence_map(sqrt, 16) == 4 + + def test_map_over_empty_list(self): + assert _recursive_sequence_map(sqrt, []) == [] + + def test_map_over_list(self): + assert _recursive_sequence_map(sqrt, [4, 16, 25, 676]) == [2, 4, 5, 26] + + def test_map_over_tuple(self): + assert _recursive_sequence_map(sqrt, (4, 16, 25, 676)) == (2, 4, 5, 26) + + def test_map_over_nested_lists(self): + assert _recursive_sequence_map(sqrt, [4, [25, 64], [[49]]]) == [ + 2, + [5, 8], + [[7]], + ] + + def test_map_over_mixed_sequence(self): + assert _recursive_sequence_map(sqrt, [4, (25, 64), [(49)]]) == [ + 2, + (5, 8), + [(7)], + ] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/collect.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/collect.py index ac3edd395ab83..745550f0775a9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/collect.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/collect.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs import os import sys import textwrap @@ -5,7 +6,6 @@ from typing import Dict import _pytest._code -import pytest from _pytest.config import ExitCode from _pytest.main import Session from _pytest.monkeypatch import MonkeyPatch @@ -13,6 +13,7 @@ from _pytest.pytester import Pytester from _pytest.python import Class from _pytest.python import Function +import pytest class TestModule: @@ -53,14 +54,13 @@ def test_import_prepend_append( monkeypatch.syspath_prepend(str(root1)) p.write_text( textwrap.dedent( - """\ + f"""\ import x456 def test(): - assert x456.__file__.startswith({!r}) - """.format( - str(root2) - ) - ) + assert x456.__file__.startswith({str(root2)!r}) + """ + ), + encoding="utf-8", ) with monkeypatch.context() as mp: mp.chdir(root2) @@ -775,13 +775,13 @@ def test_ordered_by_definition_order(self, pytester: Pytester) -> None: pytester.makepyfile( """\ class Test1: - def test_foo(): pass - def test_bar(): pass + def test_foo(self): pass + def test_bar(self): pass class Test2: - def test_foo(): pass + def test_foo(self): pass test_bar = Test1.test_bar class Test3(Test2): - def test_baz(): pass + def test_baz(self): pass """ ) result = pytester.runpytest("--collect-only") @@ -826,13 +826,14 @@ def test_customized_pymakemodule_issue205_subdir(self, pytester: Pytester) -> No textwrap.dedent( """\ import pytest - @pytest.hookimpl(hookwrapper=True) + @pytest.hookimpl(wrapper=True) def pytest_pycollect_makemodule(): - outcome = yield - mod = outcome.get_result() + mod = yield mod.obj.hello = "world" + return mod """ - ) + ), + encoding="utf-8", ) b.joinpath("test_module.py").write_text( textwrap.dedent( @@ -840,7 +841,8 @@ def pytest_pycollect_makemodule(): def test_hello(): assert hello == "world" """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run() reprec.assertoutcome(passed=1) @@ -852,16 +854,16 @@ def test_customized_pymakeitem(self, pytester: Pytester) -> None: textwrap.dedent( """\ import pytest - @pytest.hookimpl(hookwrapper=True) + @pytest.hookimpl(wrapper=True) def pytest_pycollect_makeitem(): - outcome = yield - if outcome.excinfo is None: - result = outcome.get_result() - if result: - for func in result: - func._some123 = "world" + result = yield + if result: + for func in result: + func._some123 = "world" + return result """ - ) + ), + encoding="utf-8", ) b.joinpath("test_module.py").write_text( textwrap.dedent( @@ -874,7 +876,8 @@ def obj(request): def test_hello(obj): assert obj == "world" """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run() reprec.assertoutcome(passed=1) @@ -897,25 +900,29 @@ def pytest_pycollect_makeitem(collector, name, obj): def test_issue2369_collect_module_fileext(self, pytester: Pytester) -> None: """Ensure we can collect files with weird file extensions as Python modules (#2369)""" - # We'll implement a little finder and loader to import files containing + # Implement a little meta path finder to import files containing # Python source code whose file extension is ".narf". pytester.makeconftest( """ - import sys, os, imp + import sys + import os.path + from importlib.util import spec_from_loader + from importlib.machinery import SourceFileLoader from _pytest.python import Module - class Loader(object): - def load_module(self, name): - return imp.load_source(name, name + ".narf") - class Finder(object): - def find_module(self, name, path=None): - if os.path.exists(name + ".narf"): - return Loader() - sys.meta_path.append(Finder()) + class MetaPathFinder: + def find_spec(self, fullname, path, target=None): + if os.path.exists(fullname + ".narf"): + return spec_from_loader( + fullname, + SourceFileLoader(fullname, fullname + ".narf"), + ) + sys.meta_path.append(MetaPathFinder()) def pytest_collect_file(file_path, parent): if file_path.suffix == ".narf": - return Module.from_parent(path=file_path, parent=parent)""" + return Module.from_parent(path=file_path, parent=parent) + """ ) pytester.makefile( ".narf", @@ -970,7 +977,8 @@ def pytest_runtest_call(item): def pytest_runtest_teardown(item): assert item.path.stem == "test_in_sub1" """ - ) + ), + encoding="utf-8", ) sub2.joinpath("conftest.py").write_text( textwrap.dedent( @@ -983,10 +991,11 @@ def pytest_runtest_call(item): def pytest_runtest_teardown(item): assert item.path.stem == "test_in_sub2" """ - ) + ), + encoding="utf-8", ) - sub1.joinpath("test_in_sub1.py").write_text("def test_1(): pass") - sub2.joinpath("test_in_sub2.py").write_text("def test_2(): pass") + sub1.joinpath("test_in_sub1.py").write_text("def test_1(): pass", encoding="utf-8") + sub2.joinpath("test_in_sub2.py").write_text("def test_2(): pass", encoding="utf-8") result = pytester.runpytest("-v", "-s") result.assert_outcomes(passed=2) @@ -1003,9 +1012,9 @@ 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[-1].ishidden(excinfo) assert excinfo.traceback[-2].frame.code.name == "test_skip_simple" - assert not excinfo.traceback[-2].ishidden() + assert not excinfo.traceback[-2].ishidden(excinfo) def test_traceback_argsetup(self, pytester: Pytester) -> None: pytester.makeconftest( @@ -1200,7 +1209,7 @@ def test_bar(self): classcol = pytester.collect_by_name(modcol, "TestClass") assert isinstance(classcol, Class) path, lineno, msg = classcol.reportinfo() - func = list(classcol.collect())[0] + func = next(iter(classcol.collect())) assert isinstance(func, Function) path, lineno, msg = func.reportinfo() @@ -1374,7 +1383,8 @@ def test_skip_duplicates_by_default(pytester: Pytester) -> None: def test_real(): pass """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest(str(a), str(a)) result.stdout.fnmatch_lines(["*collected 1 item*"]) @@ -1394,7 +1404,8 @@ def test_keep_duplicates(pytester: Pytester) -> None: def test_real(): pass """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest("--keep-duplicates", str(a), str(a)) result.stdout.fnmatch_lines(["*collected 2 item*"]) @@ -1407,10 +1418,15 @@ def test_package_collection_infinite_recursion(pytester: Pytester) -> None: def test_package_collection_init_given_as_argument(pytester: Pytester) -> None: - """Regression test for #3749""" + """Regression test for #3749, #8976, #9263, #9313. + + Specifying an __init__.py file directly should collect only the __init__.py + Module, not the entire package. + """ p = pytester.copy_example("collect/package_init_given_as_arg") - result = pytester.runpytest(p / "pkg" / "__init__.py") - result.stdout.fnmatch_lines(["*1 passed*"]) + items, hookrecorder = pytester.inline_genitems(p / "pkg" / "__init__.py") + assert len(items) == 1 + assert items[0].name == "test_init" def test_package_with_modules(pytester: Pytester) -> None: @@ -1439,8 +1455,12 @@ def test_package_with_modules(pytester: Pytester) -> None: sub2_test = sub2.joinpath("test") sub2_test.mkdir(parents=True) - sub1_test.joinpath("test_in_sub1.py").write_text("def test_1(): pass") - sub2_test.joinpath("test_in_sub2.py").write_text("def test_2(): pass") + sub1_test.joinpath("test_in_sub1.py").write_text( + "def test_1(): pass", encoding="utf-8" + ) + sub2_test.joinpath("test_in_sub2.py").write_text( + "def test_2(): pass", encoding="utf-8" + ) # Execute from . result = pytester.runpytest("-v", "-s") @@ -1484,10 +1504,117 @@ def test_package_ordering(pytester: Pytester) -> None: sub2_test = sub2.joinpath("test") sub2_test.mkdir(parents=True) - root.joinpath("Test_root.py").write_text("def test_1(): pass") - sub1.joinpath("Test_sub1.py").write_text("def test_2(): pass") - sub2_test.joinpath("test_sub2.py").write_text("def test_3(): pass") + root.joinpath("Test_root.py").write_text("def test_1(): pass", encoding="utf-8") + sub1.joinpath("Test_sub1.py").write_text("def test_2(): pass", encoding="utf-8") + sub2_test.joinpath("test_sub2.py").write_text( + "def test_3(): pass", encoding="utf-8" + ) # Execute from . result = pytester.runpytest("-v", "-s") result.assert_outcomes(passed=3) + + +def test_collection_hierarchy(pytester: Pytester) -> None: + """A general test checking that a filesystem hierarchy is collected as + expected in various scenarios. + + top/ + ├── aaa + │ ├── pkg + │ │ ├── __init__.py + │ │ └── test_pkg.py + │ └── test_aaa.py + ├── test_a.py + ├── test_b + │ ├── __init__.py + │ └── test_b.py + ├── test_c.py + └── zzz + ├── dir + │ └── test_dir.py + ├── __init__.py + └── test_zzz.py + """ + pytester.makepyfile( + **{ + "top/aaa/test_aaa.py": "def test_it(): pass", + "top/aaa/pkg/__init__.py": "", + "top/aaa/pkg/test_pkg.py": "def test_it(): pass", + "top/test_a.py": "def test_it(): pass", + "top/test_b/__init__.py": "", + "top/test_b/test_b.py": "def test_it(): pass", + "top/test_c.py": "def test_it(): pass", + "top/zzz/__init__.py": "", + "top/zzz/test_zzz.py": "def test_it(): pass", + "top/zzz/dir/test_dir.py": "def test_it(): pass", + } + ) + + full = [ + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + ] + result = pytester.runpytest("--collect-only") + result.stdout.fnmatch_lines(full, consecutive=True) + result = pytester.runpytest("top", "--collect-only") + result.stdout.fnmatch_lines(full, consecutive=True) + result = pytester.runpytest("top", "top", "--collect-only") + result.stdout.fnmatch_lines(full, consecutive=True) + + result = pytester.runpytest( + "top/aaa", "top/aaa/pkg", "--collect-only", "--keep-duplicates" + ) + result.stdout.fnmatch_lines( + [ + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + ], + consecutive=True, + ) + + result = pytester.runpytest( + "top/aaa/pkg", "top/aaa", "--collect-only", "--keep-duplicates" + ) + result.stdout.fnmatch_lines( + [ + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + ], + consecutive=True, + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/fixtures.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/fixtures.py index f29ca1dfa5904..741cf7dcf42cc 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/fixtures.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/fixtures.py @@ -1,17 +1,18 @@ +# mypy: allow-untyped-defs import os +from pathlib import Path import sys import textwrap -from pathlib import Path -import pytest -from _pytest import fixtures from _pytest.compat import getfuncargnames from _pytest.config import ExitCode -from _pytest.fixtures import FixtureRequest +from _pytest.fixtures import deduplicate_names +from _pytest.fixtures import TopRequest from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import get_public_names from _pytest.pytester import Pytester from _pytest.python import Function +import pytest def test_getfuncargnames_functions(): @@ -103,10 +104,6 @@ class T: @pytest.mark.pytester_example_path("fixtures/fill_fixtures") class TestFillFixtures: - def test_fillfuncargs_exposed(self): - # used by oejskit, kept for compatibility - assert pytest._fillfuncargs == fixtures._fillfuncargs - def test_funcarg_lookupfails(self, pytester: Pytester) -> None: pytester.copy_example() result = pytester.runpytest() # "--collect-only") @@ -291,7 +288,8 @@ def spam(request): def spam(): return 'spam' """ - ) + ), + encoding="utf-8", ) testfile = subdir.joinpath("test_spam.py") testfile.write_text( @@ -300,7 +298,8 @@ def spam(): def test_spam(spam): assert spam == "spam" """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) @@ -363,7 +362,8 @@ def spam(): def spam(request): return request.param """ - ) + ), + encoding="utf-8", ) testfile = subdir.joinpath("test_spam.py") testfile.write_text( @@ -375,7 +375,8 @@ def test_spam(spam): assert spam == params['spam'] params['spam'] += 1 """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest() result.stdout.fnmatch_lines(["*3 passed*"]) @@ -407,7 +408,8 @@ def spam(): def spam(request): return request.param """ - ) + ), + encoding="utf-8", ) testfile = subdir.joinpath("test_spam.py") testfile.write_text( @@ -419,7 +421,8 @@ def test_spam(spam): assert spam == params['spam'] params['spam'] += 1 """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest() result.stdout.fnmatch_lines(["*3 passed*"]) @@ -657,7 +660,7 @@ def test_func(something): pass """ ) assert isinstance(item, Function) - req = fixtures.FixtureRequest(item, _ispytest=True) + req = TopRequest(item, _ispytest=True) assert req.function == item.obj assert req.keywords == item.keywords assert hasattr(req.module, "test_func") @@ -697,10 +700,9 @@ def test_method(self, something): """ ) (item1,) = pytester.genitems([modcol]) + assert isinstance(item1, Function) assert item1.name == "test_method" - arg2fixturedefs = fixtures.FixtureRequest( - item1, _ispytest=True - )._arg2fixturedefs + arg2fixturedefs = TopRequest(item1, _ispytest=True)._arg2fixturedefs assert len(arg2fixturedefs) == 1 assert arg2fixturedefs["something"][0].argname == "something" @@ -710,7 +712,7 @@ def test_method(self, something): ) def test_request_garbage(self, pytester: Pytester) -> None: try: - import xdist # noqa + import xdist # noqa: F401 except ImportError: pass else: @@ -930,8 +932,9 @@ def test_request_subrequest_addfinalizer_exceptions( self, pytester: Pytester ) -> None: """ - Ensure exceptions raised during teardown by a finalizer are suppressed - until all finalizers are called, re-raising the first exception (#2440) + Ensure exceptions raised during teardown by finalizers are suppressed + until all finalizers are called, then re-raised together in an + exception group (#2440) """ pytester.makepyfile( """ @@ -958,14 +961,23 @@ def test_second(): """ ) result = pytester.runpytest() + result.assert_outcomes(passed=2, errors=1) result.stdout.fnmatch_lines( - ["*Exception: Error in excepts fixture", "* 2 passed, 1 error in *"] + [ + ' | *ExceptionGroup: errors while tearing down fixture "subrequest" of (2 sub-exceptions)', # noqa: E501 + " +-+---------------- 1 ----------------", + " | Exception: Error in something fixture", + " +---------------- 2 ----------------", + " | Exception: Error in excepts fixture", + " +------------------------------------", + ], ) def test_request_getmodulepath(self, pytester: Pytester) -> None: modcol = pytester.getmodulecol("def test_somefunc(): pass") (item,) = pytester.genitems([modcol]) - req = fixtures.FixtureRequest(item, _ispytest=True) + assert isinstance(item, Function) + req = TopRequest(item, _ispytest=True) assert req.path == modcol.path def test_request_fixturenames(self, pytester: Pytester) -> None: @@ -1041,10 +1053,11 @@ def test_fixtures_sub_subdir_normalize_sep(self, pytester: Pytester) -> None: def arg1(): pass """ - ) + ), + encoding="utf-8", ) p = b.joinpath("test_module.py") - p.write_text("def test_func(arg1): pass") + p.write_text("def test_func(arg1): pass", encoding="utf-8") result = pytester.runpytest(p, "--fixtures") assert result.ret == 0 result.stdout.fnmatch_lines( @@ -1122,7 +1135,8 @@ def test_func2(self, something): pass """ ) - req1 = fixtures.FixtureRequest(item1, _ispytest=True) + assert isinstance(item1, Function) + req1 = TopRequest(item1, _ispytest=True) assert "xfail" not in item1.keywords req1.applymarker(pytest.mark.xfail) assert "xfail" in item1.keywords @@ -1233,8 +1247,9 @@ def test_add(arg2): result = pytester.runpytest() result.stdout.fnmatch_lines( [ - "*ScopeMismatch*involved factories*", + "*ScopeMismatch*Requesting fixture stack*", "test_receives_funcargs_scope_mismatch.py:6: def arg2(arg1)", + "Requested fixture:", "test_receives_funcargs_scope_mismatch.py:2: def arg1()", "*1 error*", ] @@ -1260,7 +1275,13 @@ def test_add(arg1, arg2): ) result = pytester.runpytest() result.stdout.fnmatch_lines( - ["*ScopeMismatch*involved factories*", "* def arg2*", "*1 error*"] + [ + "*ScopeMismatch*Requesting fixture stack*", + "* def arg2(arg1)", + "Requested fixture:", + "* def arg1()", + "*1 error*", + ], ) def test_invalid_scope(self, pytester: Pytester) -> None: @@ -1283,7 +1304,7 @@ def test_nothing(badscope): @pytest.mark.parametrize("scope", ["function", "session"]) def test_parameters_without_eq_semantics(self, scope, pytester: Pytester) -> None: pytester.makepyfile( - """ + f""" class NoEq1: # fails on `a == b` statement def __eq__(self, _): raise RuntimeError @@ -1305,9 +1326,7 @@ def test1(no_eq): def test2(no_eq): pass - """.format( - scope=scope - ) + """ ) result = pytester.runpytest() result.stdout.fnmatch_lines(["*4 passed*"]) @@ -1570,7 +1589,7 @@ def test_parsefactories_conftest(self, pytester: Pytester) -> None: """ def test_hello(item, fm): for name in ("fm", "hello", "item"): - faclist = fm.getfixturedefs(name, item.nodeid) + faclist = fm.getfixturedefs(name, item) assert len(faclist) == 1 fac = faclist[0] assert fac.func.__name__ == name @@ -1594,7 +1613,7 @@ class TestClass(object): def hello(self, request): return "class" def test_hello(self, item, fm): - faclist = fm.getfixturedefs("hello", item.nodeid) + faclist = fm.getfixturedefs("hello", item) print(faclist) assert len(faclist) == 3 @@ -1621,7 +1640,8 @@ def test_parsefactories_relative_node_ids( def one(): return 1 """ - ) + ), + encoding="utf-8", ) package.joinpath("test_x.py").write_text( textwrap.dedent( @@ -1629,7 +1649,8 @@ def one(): def test_x(one): assert one == 1 """ - ) + ), + encoding="utf-8", ) sub = package.joinpath("sub") sub.mkdir() @@ -1642,7 +1663,8 @@ def test_x(one): def one(): return 2 """ - ) + ), + encoding="utf-8", ) sub.joinpath("test_y.py").write_text( textwrap.dedent( @@ -1650,7 +1672,8 @@ def one(): def test_x(one): assert one == 2 """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run() reprec.assertoutcome(passed=2) @@ -1675,7 +1698,8 @@ def setup_module(): def teardown_module(): values[:] = [] """ - ) + ), + encoding="utf-8", ) package.joinpath("test_x.py").write_text( textwrap.dedent( @@ -1684,7 +1708,8 @@ def teardown_module(): def test_x(): assert values == ["package"] """ - ) + ), + encoding="utf-8", ) package = pytester.mkdir("package2") package.joinpath("__init__.py").write_text( @@ -1696,7 +1721,8 @@ def setup_module(): def teardown_module(): values[:] = [] """ - ) + ), + encoding="utf-8", ) package.joinpath("test_x.py").write_text( textwrap.dedent( @@ -1705,7 +1731,8 @@ def teardown_module(): def test_x(): assert values == ["package2"] """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run() reprec.assertoutcome(passed=2) @@ -1718,7 +1745,7 @@ def test_package_fixture_complex(self, pytester: Pytester) -> None: ) pytester.syspathinsert(pytester.path.name) package = pytester.mkdir("package") - package.joinpath("__init__.py").write_text("") + package.joinpath("__init__.py").write_text("", encoding="utf-8") package.joinpath("conftest.py").write_text( textwrap.dedent( """\ @@ -1735,7 +1762,8 @@ def two(): yield values values.pop() """ - ) + ), + encoding="utf-8", ) package.joinpath("test_x.py").write_text( textwrap.dedent( @@ -1746,7 +1774,8 @@ def test_package_autouse(): def test_package(one): assert values == ["package-auto", "package"] """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run() reprec.assertoutcome(passed=2) @@ -1790,7 +1819,7 @@ def test_parsefactories_conftest(self, pytester: Pytester) -> None: """ from _pytest.pytester import get_public_names def test_check_setup(item, fm): - autousenames = list(fm._getautousenames(item.nodeid)) + autousenames = list(fm._getautousenames(item)) assert len(get_public_names(autousenames)) == 2 assert "perfunction2" in autousenames assert "perfunction" in autousenames @@ -1896,8 +1925,12 @@ def hello(): """ ) conftest.rename(a.joinpath(conftest.name)) - a.joinpath("test_something.py").write_text("def test_func(): pass") - b.joinpath("test_otherthing.py").write_text("def test_func(): pass") + a.joinpath("test_something.py").write_text( + "def test_func(): pass", encoding="utf-8" + ) + b.joinpath("test_otherthing.py").write_text( + "def test_func(): pass", encoding="utf-8" + ) result = pytester.runpytest() result.stdout.fnmatch_lines( """ @@ -1943,7 +1976,8 @@ def app(): import sys sys._myapp = "hello" """ - ) + ), + encoding="utf-8", ) sub = pkgdir.joinpath("tests") sub.mkdir() @@ -1956,7 +1990,8 @@ def app(): def test_app(): assert sys._myapp == "hello" """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run("-s") reprec.assertoutcome(passed=1) @@ -2081,9 +2116,7 @@ def test_2(self): reprec = pytester.inline_run("-v", "-s", "--confcutdir", pytester.path) reprec.assertoutcome(passed=8) config = reprec.getcalls("pytest_unconfigure")[0].config - values = config.pluginmanager._getconftestmodules( - p, importmode="prepend", rootpath=pytester.path - )[0].values + values = config.pluginmanager._getconftestmodules(p)[0].values assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2 def test_scope_ordering(self, pytester: Pytester) -> None: @@ -2180,7 +2213,7 @@ def test_arg(arg2): pass def test_check(): assert values == ["new1", "new2", "fin2", "fin1"] - """ + """ # noqa: UP031 (python syntax issues) % locals() ) reprec = pytester.inline_run("-s") @@ -2442,6 +2475,33 @@ def test_1(arg): ["*ScopeMismatch*You tried*function*session*request*"] ) + def test_scope_mismatch_already_computed_dynamic(self, pytester: Pytester) -> None: + pytester.makepyfile( + test_it=""" + import pytest + + @pytest.fixture(scope="function") + def fixfunc(): pass + + @pytest.fixture(scope="module") + def fixmod(fixfunc): pass + + def test_it(request, fixfunc): + request.getfixturevalue("fixmod") + """, + ) + + result = pytester.runpytest() + assert result.ret == ExitCode.TESTS_FAILED + result.stdout.fnmatch_lines( + [ + "*ScopeMismatch*Requesting fixture stack*", + "test_it.py:6: def fixmod(fixfunc)", + "Requested fixture:", + "test_it.py:3: def fixfunc()", + ] + ) + def test_dynamic_scope(self, pytester: Pytester) -> None: pytester.makeconftest( """ @@ -2688,12 +2748,12 @@ def test2(reprovision): """ test_dynamic_parametrized_ordering.py::test[flavor1-vxlan] PASSED test_dynamic_parametrized_ordering.py::test2[flavor1-vxlan] PASSED - test_dynamic_parametrized_ordering.py::test[flavor2-vxlan] PASSED - test_dynamic_parametrized_ordering.py::test2[flavor2-vxlan] PASSED - test_dynamic_parametrized_ordering.py::test[flavor2-vlan] PASSED - test_dynamic_parametrized_ordering.py::test2[flavor2-vlan] PASSED test_dynamic_parametrized_ordering.py::test[flavor1-vlan] PASSED test_dynamic_parametrized_ordering.py::test2[flavor1-vlan] PASSED + test_dynamic_parametrized_ordering.py::test[flavor2-vlan] PASSED + test_dynamic_parametrized_ordering.py::test2[flavor2-vlan] PASSED + test_dynamic_parametrized_ordering.py::test[flavor2-vxlan] PASSED + test_dynamic_parametrized_ordering.py::test2[flavor2-vxlan] PASSED """ ) @@ -2886,7 +2946,7 @@ def test_fixture_finalizer(self, pytester: Pytester) -> None: def browser(request): def finalize(): - sys.stdout.write_text('Finalized') + sys.stdout.write_text('Finalized', encoding='utf-8') request.addfinalizer(finalize) return {} """ @@ -2904,7 +2964,8 @@ def browser(browser): def test_browser(browser): assert browser['visited'] is True """ - ) + ), + encoding="utf-8", ) reprec = pytester.runpytest("-s") for test in ["test_browser"]: @@ -3042,7 +3103,7 @@ def test_baz(base, fix2): pass def test_other(): pass - """ + """ # noqa: UP031 (python syntax issues) % {"scope": scope} ) reprec = pytester.inline_run("-lvs") @@ -3242,7 +3303,7 @@ def myscoped(request): assert request.config def test_func(): pass - """ + """ # noqa: UP031 (python syntax issues) % (scope, ok.split(), error.split()) ) reprec = pytester.inline_run("-l") @@ -3263,7 +3324,7 @@ def arg(request): assert request.config def test_func(arg): pass - """ + """ # noqa: UP031 (python syntax issues) % (scope, ok.split(), error.split()) ) reprec = pytester.inline_run() @@ -3342,6 +3403,10 @@ def test_funcarg_compat(self, pytester: Pytester) -> None: config = pytester.parseconfigure("--funcargs") assert config.option.showfixtures + def test_show_help(self, pytester: Pytester) -> None: + result = pytester.runpytest("--fixtures", "--help") + assert not result.ret + def test_show_fixtures(self, pytester: Pytester) -> None: result = pytester.runpytest("--fixtures") result.stdout.fnmatch_lines( @@ -3855,7 +3920,8 @@ def test_non_relative_path(self, pytester: Pytester) -> None: def fix_with_param(request): return request.param """ - ) + ), + encoding="utf-8", ) testfile = tests_dir.joinpath("test_foos.py") @@ -3867,7 +3933,8 @@ def fix_with_param(request): def test_foo(request): request.getfixturevalue('fix_with_param') """ - ) + ), + encoding="utf-8", ) os.chdir(tests_dir) @@ -3983,7 +4050,8 @@ def test_func(m1): """ ) items, _ = pytester.inline_genitems() - request = FixtureRequest(items[0], _ispytest=True) + assert isinstance(items[0], Function) + request = TopRequest(items[0], _ispytest=True) assert request.fixturenames == "m1 f1".split() def test_func_closure_with_native_fixtures( @@ -4031,7 +4099,8 @@ def test_foo(f1, p1, m1, f2, s1): pass """ ) items, _ = pytester.inline_genitems() - request = FixtureRequest(items[0], _ispytest=True) + assert isinstance(items[0], Function) + request = TopRequest(items[0], _ispytest=True) # order of fixtures based on their scope and position in the parameter list assert ( request.fixturenames @@ -4058,7 +4127,8 @@ def test_func(f1, m1): """ ) items, _ = pytester.inline_genitems() - request = FixtureRequest(items[0], _ispytest=True) + assert isinstance(items[0], Function) + request = TopRequest(items[0], _ispytest=True) assert request.fixturenames == "m1 f1".split() def test_func_closure_scopes_reordered(self, pytester: Pytester) -> None: @@ -4091,7 +4161,8 @@ def test_func(self, f2, f1, c1, m1, s1): """ ) items, _ = pytester.inline_genitems() - request = FixtureRequest(items[0], _ispytest=True) + assert isinstance(items[0], Function) + request = TopRequest(items[0], _ispytest=True) assert request.fixturenames == "s1 m1 c1 f2 f1".split() def test_func_closure_same_scope_closer_root_first( @@ -4133,7 +4204,8 @@ def test_func(m_test, f1): } ) items, _ = pytester.inline_genitems() - request = FixtureRequest(items[0], _ispytest=True) + assert isinstance(items[0], Function) + request = TopRequest(items[0], _ispytest=True) assert request.fixturenames == "p_sub m_conf m_sub m_test f1".split() def test_func_closure_all_scopes_complex(self, pytester: Pytester) -> None: @@ -4177,7 +4249,8 @@ def test_func(self, f2, f1, m2): """ ) items, _ = pytester.inline_genitems() - request = FixtureRequest(items[0], _ispytest=True) + assert isinstance(items[0], Function) + request = TopRequest(items[0], _ispytest=True) assert request.fixturenames == "s1 p1 m1 m2 c1 f2 f1".split() def test_multiple_packages(self, pytester: Pytester) -> None: @@ -4196,7 +4269,7 @@ def test_multiple_packages(self, pytester: Pytester) -> None: └── test_2.py """ root = pytester.mkdir("root") - root.joinpath("__init__.py").write_text("values = []") + root.joinpath("__init__.py").write_text("values = []", encoding="utf-8") sub1 = root.joinpath("sub1") sub1.mkdir() sub1.joinpath("__init__.py").touch() @@ -4211,7 +4284,8 @@ def fix(): yield values assert values.pop() == "pre-sub1" """ - ) + ), + encoding="utf-8", ) sub1.joinpath("test_1.py").write_text( textwrap.dedent( @@ -4220,7 +4294,8 @@ def fix(): def test_1(fix): assert values == ["pre-sub1"] """ - ) + ), + encoding="utf-8", ) sub2 = root.joinpath("sub2") sub2.mkdir() @@ -4236,7 +4311,8 @@ def fix(): yield values assert values.pop() == "pre-sub2" """ - ) + ), + encoding="utf-8", ) sub2.joinpath("test_2.py").write_text( textwrap.dedent( @@ -4245,7 +4321,8 @@ def fix(): def test_2(fix): assert values == ["pre-sub2"] """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run() reprec.assertoutcome(passed=2) @@ -4294,6 +4371,27 @@ def fix(): assert fix() == 1 +def test_fixture_double_decorator(pytester: Pytester) -> None: + """Check if an error is raised when using @pytest.fixture twice.""" + pytester.makepyfile( + """ + import pytest + + @pytest.fixture + @pytest.fixture + def fixt(): + pass + """ + ) + result = pytester.runpytest() + result.assert_outcomes(errors=1) + result.stdout.fnmatch_lines( + [ + "E * ValueError: @pytest.fixture is being applied more than once to the same function 'fixt'" + ] + ) + + def test_fixture_param_shadowing(pytester: Pytester) -> None: """Parametrized arguments would be shadowed if a fixture with the same name also exists (#5036)""" pytester.makepyfile( @@ -4343,7 +4441,7 @@ def test_fixture_named_request(pytester: Pytester) -> None: result.stdout.fnmatch_lines( [ "*'request' is a reserved word for fixtures, use another name:", - " *test_fixture_named_request.py:5", + " *test_fixture_named_request.py:6", ] ) @@ -4472,3 +4570,236 @@ def test_fixt(custom): result.assert_outcomes(errors=1) result.stdout.fnmatch_lines([expected]) assert result.ret == ExitCode.TESTS_FAILED + + +def test_deduplicate_names() -> None: + items = deduplicate_names("abacd") + assert items == ("a", "b", "c", "d") + items = deduplicate_names((*items, "g", "f", "g", "e", "b")) + assert items == ("a", "b", "c", "d", "g", "f", "e") + + +def test_staticmethod_classmethod_fixture_instance(pytester: Pytester) -> None: + """Ensure that static and class methods get and have access to a fresh + instance. + + This also ensures `setup_method` works well with static and class methods. + + Regression test for #12065. + """ + pytester.makepyfile( + """ + import pytest + + class Test: + ran_setup_method = False + ran_fixture = False + + def setup_method(self): + assert not self.ran_setup_method + self.ran_setup_method = True + + @pytest.fixture(autouse=True) + def fixture(self): + assert not self.ran_fixture + self.ran_fixture = True + + def test_method(self): + assert self.ran_setup_method + assert self.ran_fixture + + @staticmethod + def test_1(request): + assert request.instance.ran_setup_method + assert request.instance.ran_fixture + + @classmethod + def test_2(cls, request): + assert request.instance.ran_setup_method + assert request.instance.ran_fixture + """ + ) + result = pytester.runpytest() + assert result.ret == ExitCode.OK + result.assert_outcomes(passed=3) + + +def test_scoped_fixture_caching(pytester: Pytester) -> None: + """Make sure setup and finalization is only run once when using scoped fixture + multiple times.""" + pytester.makepyfile( + """ + from __future__ import annotations + + from typing import Generator + + import pytest + executed: list[str] = [] + @pytest.fixture(scope="class") + def fixture_1() -> Generator[None, None, None]: + executed.append("fix setup") + yield + executed.append("fix teardown") + + + class TestFixtureCaching: + def test_1(self, fixture_1: None) -> None: + assert executed == ["fix setup"] + + def test_2(self, fixture_1: None) -> None: + assert executed == ["fix setup"] + + + def test_expected_setup_and_teardown() -> None: + assert executed == ["fix setup", "fix teardown"] + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + +def test_scoped_fixture_caching_exception(pytester: Pytester) -> None: + """Make sure setup & finalization is only run once for scoped fixture, with a cached exception.""" + pytester.makepyfile( + """ + from __future__ import annotations + + import pytest + executed_crash: list[str] = [] + + + @pytest.fixture(scope="class") + def fixture_crash(request: pytest.FixtureRequest) -> None: + executed_crash.append("fix_crash setup") + + def my_finalizer() -> None: + executed_crash.append("fix_crash teardown") + + request.addfinalizer(my_finalizer) + + raise Exception("foo") + + + class TestFixtureCachingException: + @pytest.mark.xfail + def test_crash_1(self, fixture_crash: None) -> None: + ... + + @pytest.mark.xfail + def test_crash_2(self, fixture_crash: None) -> None: + ... + + + def test_crash_expected_setup_and_teardown() -> None: + assert executed_crash == ["fix_crash setup", "fix_crash teardown"] + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + +def test_scoped_fixture_teardown_order(pytester: Pytester) -> None: + """ + Make sure teardowns happen in reverse order of setup with scoped fixtures, when + a later test only depends on a subset of scoped fixtures. + + Regression test for https://github.com/pytest-dev/pytest/issues/1489 + """ + pytester.makepyfile( + """ + from typing import Generator + + import pytest + + + last_executed = "" + + + @pytest.fixture(scope="module") + def fixture_1() -> Generator[None, None, None]: + global last_executed + assert last_executed == "" + last_executed = "fixture_1_setup" + yield + assert last_executed == "fixture_2_teardown" + last_executed = "fixture_1_teardown" + + + @pytest.fixture(scope="module") + def fixture_2() -> Generator[None, None, None]: + global last_executed + assert last_executed == "fixture_1_setup" + last_executed = "fixture_2_setup" + yield + assert last_executed == "run_test" + last_executed = "fixture_2_teardown" + + + def test_fixture_teardown_order(fixture_1: None, fixture_2: None) -> None: + global last_executed + assert last_executed == "fixture_2_setup" + last_executed = "run_test" + + + def test_2(fixture_1: None) -> None: + # This would previously queue an additional teardown of fixture_1, + # despite fixture_1's value being cached, which caused fixture_1 to be + # torn down before fixture_2 - violating the rule that teardowns should + # happen in reverse order of setup. + pass + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + +def test_subfixture_teardown_order(pytester: Pytester) -> None: + """ + Make sure fixtures don't re-register their finalization in parent fixtures multiple + times, causing ordering failure in their teardowns. + + Regression test for #12135 + """ + pytester.makepyfile( + """ + import pytest + + execution_order = [] + + @pytest.fixture(scope="class") + def fixture_1(): + ... + + @pytest.fixture(scope="class") + def fixture_2(fixture_1): + execution_order.append("setup 2") + yield + execution_order.append("teardown 2") + + @pytest.fixture(scope="class") + def fixture_3(fixture_1): + execution_order.append("setup 3") + yield + execution_order.append("teardown 3") + + class TestFoo: + def test_initialize_fixtures(self, fixture_2, fixture_3): + ... + + # This would previously reschedule fixture_2's finalizer in the parent fixture, + # causing it to be torn down before fixture 3. + def test_reschedule_fixture_2(self, fixture_2): + ... + + # Force finalization directly on fixture_1 + # Otherwise the cleanup would sequence 3&2 before 1 as normal. + @pytest.mark.parametrize("fixture_1", [None], indirect=["fixture_1"]) + def test_finalize_fixture_1(self, fixture_1): + ... + + def test_result(): + assert execution_order == ["setup 2", "setup 3", "teardown 3", "teardown 2"] + """ + ) + result = pytester.runpytest() + assert result.ret == 0 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/integration.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/integration.py index d138b726638ed..c20aaeed83995 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/integration.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/integration.py @@ -1,82 +1,9 @@ -from typing import Any - -import pytest -from _pytest import runner +# mypy: allow-untyped-defs from _pytest._code import getfslineno from _pytest.fixtures import getfixturemarker from _pytest.pytester import Pytester from _pytest.python import Function - - -class TestOEJSKITSpecials: - def test_funcarg_non_pycollectobj( - self, pytester: Pytester, recwarn - ) -> None: # rough jstests usage - pytester.makeconftest( - """ - import pytest - def pytest_pycollect_makeitem(collector, name, obj): - if name == "MyClass": - return MyCollector.from_parent(collector, name=name) - class MyCollector(pytest.Collector): - def reportinfo(self): - return self.path, 3, "xyz" - """ - ) - modcol = pytester.getmodulecol( - """ - import pytest - @pytest.fixture - def arg1(request): - return 42 - class MyClass(object): - pass - """ - ) - # this hook finds funcarg factories - rep = runner.collect_one_node(collector=modcol) - # TODO: Don't treat as Any. - clscol: Any = rep.result[0] - clscol.obj = lambda arg1: None - clscol.funcargs = {} - pytest._fillfuncargs(clscol) - assert clscol.funcargs["arg1"] == 42 - - def test_autouse_fixture( - self, pytester: Pytester, recwarn - ) -> None: # rough jstests usage - pytester.makeconftest( - """ - import pytest - def pytest_pycollect_makeitem(collector, name, obj): - if name == "MyClass": - return MyCollector.from_parent(collector, name=name) - class MyCollector(pytest.Collector): - def reportinfo(self): - return self.path, 3, "xyz" - """ - ) - modcol = pytester.getmodulecol( - """ - import pytest - @pytest.fixture(autouse=True) - def hello(): - pass - @pytest.fixture - def arg1(request): - return 42 - class MyClass(object): - pass - """ - ) - # this hook finds funcarg factories - rep = runner.collect_one_node(modcol) - # TODO: Don't treat as Any. - clscol: Any = rep.result[0] - clscol.obj = lambda: None - clscol.funcargs = {} - pytest._fillfuncargs(clscol) - assert not clscol.funcargs +import pytest def test_wrapped_getfslineno() -> None: @@ -116,9 +43,10 @@ def f(x): assert values == ("x",) def test_getfuncargnames_patching(self): - from _pytest.compat import getfuncargnames from unittest.mock import patch + from _pytest.compat import getfuncargnames + class T: def original(self, x, y, z): pass @@ -235,7 +163,7 @@ def mock_basename(path): @mock.patch("os.path.abspath") @mock.patch("os.path.normpath") @mock.patch("os.path.basename", new=mock_basename) - def test_someting(normpath, abspath, tmp_path): + def test_something(normpath, abspath, tmp_path): abspath.return_value = "this" os.path.normpath(os.path.abspath("hello")) normpath.assert_any_call("this") @@ -248,7 +176,7 @@ def test_someting(normpath, abspath, tmp_path): funcnames = [ call.report.location[2] for call in calls if call.report.when == "call" ] - assert funcnames == ["T.test_hello", "test_someting"] + assert funcnames == ["T.test_hello", "test_something"] def test_mock_sorting(self, pytester: Pytester) -> None: pytest.importorskip("mock", "1.0.1") @@ -482,22 +410,37 @@ def test_function_instance(pytester: Pytester) -> None: items = pytester.getitems( """ def test_func(): pass + class TestIt: def test_method(self): pass + @classmethod def test_class(cls): pass + @staticmethod def test_static(): pass """ ) - assert len(items) == 3 + assert len(items) == 4 + assert isinstance(items[0], Function) assert items[0].name == "test_func" assert items[0].instance is None + assert isinstance(items[1], Function) assert items[1].name == "test_method" assert items[1].instance is not None assert items[1].instance.__class__.__name__ == "TestIt" + + # Even class and static methods get an instance! + # This is the instance used for bound fixture methods, which + # class/staticmethod tests are perfectly able to request. assert isinstance(items[2], Function) - assert items[2].name == "test_static" - assert items[2].instance is None + assert items[2].name == "test_class" + assert items[2].instance is not None + + assert isinstance(items[3], Function) + assert items[3].name == "test_static" + assert items[3].instance is not None + + assert items[1].instance is not items[2].instance is not items[3].instance diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/metafunc.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/metafunc.py index fc0082eb6b98c..3d0058fa0a764 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/metafunc.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/metafunc.py @@ -1,3 +1,5 @@ +# mypy: allow-untyped-defs +import dataclasses import itertools import re import sys @@ -12,21 +14,19 @@ from typing import Tuple from typing import Union -import attr import hypothesis from hypothesis import strategies -import pytest from _pytest import fixtures from _pytest import python -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 Pytester -from _pytest.python import _idval -from _pytest.python import idmaker +from _pytest.python import Function +from _pytest.python import IdMaker from _pytest.scope import Scope +import pytest class TestMetafunc: @@ -35,19 +35,29 @@ def Metafunc(self, func, config=None) -> python.Metafunc: # on the funcarg level, so we don't need a full blown # initialization. class FuncFixtureInfoMock: - name2fixturedefs = None + name2fixturedefs: Dict[str, List[fixtures.FixtureDef[object]]] = {} def __init__(self, names): self.names_closure = names - @attr.s + @dataclasses.dataclass + class FixtureManagerMock: + config: Any + + @dataclasses.dataclass + class SessionMock: + _fixturemanager: FixtureManagerMock + + @dataclasses.dataclass class DefinitionMock(python.FunctionDefinition): - obj = attr.ib() - _nodeid = attr.ib() + _nodeid: str + obj: object names = getfuncargnames(func) fixtureinfo: Any = FuncFixtureInfoMock(names) - definition: Any = DefinitionMock._create(func, "mock::nodeid") + definition: Any = DefinitionMock._create(obj=func, _nodeid="mock::nodeid") + definition._fixtureinfo = fixtureinfo + definition.session = SessionMock(FixtureManagerMock({})) return python.Metafunc(definition, fixtureinfo, config, _ispytest=True) def test_no_funcargs(self) -> None: @@ -99,19 +109,19 @@ def gen() -> Iterator[Union[int, None, 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] == [ + metafunc.parametrize("x", [1, 2], ids=gen()) + assert [(x.params, 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: \) at index 2" + r"In func: ids contains unsupported value Exc\(from_gen\) \(type: \) at index 2. " + r"Supported types are: .*" ), ): - metafunc.parametrize("x", [1, 2, 3], ids=gen()) # type: ignore[arg-type] + metafunc.parametrize("x", [1, 2, 3], ids=gen()) def test_parametrize_bad_scope(self) -> None: def func(x): @@ -141,9 +151,9 @@ def test_find_parametrized_scope(self) -> None: """Unit test for _find_parametrized_scope (#3941).""" from _pytest.python import _find_parametrized_scope - @attr.s + @dataclasses.dataclass class DummyFixtureDef: - _scope = attr.ib() + _scope: Scope fixtures_defs = cast( Dict[str, Sequence[fixtures.FixtureDef[object]]], @@ -153,6 +163,7 @@ class DummyFixtureDef: module_fix=[DummyFixtureDef(Scope.Module)], class_fix=[DummyFixtureDef(Scope.Class)], func_fix=[DummyFixtureDef(Scope.Function)], + mixed_fix=[DummyFixtureDef(Scope.Module), DummyFixtureDef(Scope.Class)], ), ) @@ -189,6 +200,7 @@ def find_scope(argnames, indirect): ) == Scope.Module ) + assert find_scope(["mixed_fix"], indirect=True) == Scope.Class def test_parametrize_and_id(self) -> None: def func(x, y): @@ -286,7 +298,7 @@ class A: deadline=400.0 ) # very close to std deadline and CI boxes are not reliable in CPU power def test_idval_hypothesis(self, value) -> None: - escaped = _idval(value, "a", 6, None, nodeid=None, config=None) + escaped = IdMaker([], [], None, None, None, None, None)._idval(value, "a", 6) assert isinstance(escaped, str) escaped.encode("ascii") @@ -308,7 +320,10 @@ def test_unicode_idval(self) -> None: ), ] for val, expected in values: - assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected + assert ( + IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6) + == expected + ) def test_unicode_idval_with_config(self) -> None: """Unit test for expected behavior to obtain ids with @@ -336,7 +351,7 @@ def getini(self, name): ("ação", MockConfig({option: False}), "a\\xe7\\xe3o"), ] for val, config, expected in values: - actual = _idval(val, "a", 6, None, nodeid=None, config=config) + actual = IdMaker([], [], None, None, config, None, None)._idval(val, "a", 6) assert actual == expected def test_bytes_idval(self) -> None: @@ -349,7 +364,10 @@ def test_bytes_idval(self) -> None: ("αρά".encode(), r"\xce\xb1\xcf\x81\xce\xac"), ] for val, expected in values: - assert _idval(val, "a", 6, idfn=None, nodeid=None, config=None) == expected + assert ( + IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6) + == expected + ) def test_class_or_function_idval(self) -> None: """Unit test for the expected behavior to obtain ids for parametrized @@ -363,7 +381,10 @@ def test_function(): values = [(TestClass, "TestClass"), (test_function, "test_function")] for val, expected in values: - assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected + assert ( + IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6) + == expected + ) def test_notset_idval(self) -> None: """Test that a NOTSET value (used by an empty parameterset) generates @@ -371,29 +392,47 @@ def test_notset_idval(self) -> None: Regression test for #7686. """ - assert _idval(NOTSET, "a", 0, None, nodeid=None, config=None) == "a0" + assert ( + IdMaker([], [], None, None, None, None, None)._idval(NOTSET, "a", 0) == "a0" + ) def test_idmaker_autoname(self) -> None: """#250""" - result = idmaker( - ("a", "b"), [pytest.param("string", 1.0), pytest.param("st-ring", 2.0)] - ) + result = IdMaker( + ("a", "b"), + [pytest.param("string", 1.0), pytest.param("st-ring", 2.0)], + None, + None, + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["string-1.0", "st-ring-2.0"] - result = idmaker( - ("a", "b"), [pytest.param(object(), 1.0), pytest.param(object(), object())] - ) + result = IdMaker( + ("a", "b"), + [pytest.param(object(), 1.0), pytest.param(object(), object())], + None, + None, + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["a0-1.0", "a1-b1"] # unicode mixing, issue250 - result = idmaker(("a", "b"), [pytest.param({}, b"\xc3\xb4")]) + result = IdMaker( + ("a", "b"), [pytest.param({}, b"\xc3\xb4")], None, None, None, None, None + ).make_unique_parameterset_ids() assert result == ["a0-\\xc3\\xb4"] def test_idmaker_with_bytes_regex(self) -> None: - result = idmaker(("a"), [pytest.param(re.compile(b"foo"), 1.0)]) + result = IdMaker( + ("a"), [pytest.param(re.compile(b"foo"), 1.0)], None, None, None, None, None + ).make_unique_parameterset_ids() assert result == ["foo"] def test_idmaker_native_strings(self) -> None: - result = idmaker( + result = IdMaker( ("a", "b"), [ pytest.param(1.0, -1.1), @@ -410,7 +449,12 @@ def test_idmaker_native_strings(self) -> None: pytest.param(b"\xc3\xb4", "other"), pytest.param(1.0j, -2.0j), ], - ) + None, + None, + None, + None, + None, + ).make_unique_parameterset_ids() assert result == [ "1.0--1.1", "2--202", @@ -428,7 +472,7 @@ def test_idmaker_native_strings(self) -> None: ] def test_idmaker_non_printable_characters(self) -> None: - result = idmaker( + result = IdMaker( ("s", "n"), [ pytest.param("\x00", 1), @@ -438,23 +482,35 @@ def test_idmaker_non_printable_characters(self) -> None: pytest.param("\t", 5), pytest.param(b"\t", 6), ], - ) + None, + None, + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["\\x00-1", "\\x05-2", "\\x00-3", "\\x05-4", "\\t-5", "\\t-6"] def test_idmaker_manual_ids_must_be_printable(self) -> None: - result = idmaker( + result = IdMaker( ("s",), [ pytest.param("x00", id="hello \x00"), pytest.param("x05", id="hello \x05"), ], - ) + None, + None, + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["hello \\x00", "hello \\x05"] 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)]) + result = IdMaker( + ("a", "b"), [pytest.param(e.one, e.two)], None, None, None, None, None + ).make_unique_parameterset_ids() assert result == ["Foo.one-Foo.two"] def test_idmaker_idfn(self) -> None: @@ -465,15 +521,19 @@ def ids(val: object) -> Optional[str]: return repr(val) return None - result = idmaker( + result = IdMaker( ("a", "b"), [ pytest.param(10.0, IndexError()), pytest.param(20, KeyError()), pytest.param("three", [1, 2, 3]), ], - idfn=ids, - ) + ids, + None, + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["10.0-IndexError()", "20-KeyError()", "three-b2"] def test_idmaker_idfn_unique_names(self) -> None: @@ -482,15 +542,19 @@ def test_idmaker_idfn_unique_names(self) -> None: def ids(val: object) -> str: return "a" - result = idmaker( + result = IdMaker( ("a", "b"), [ pytest.param(10.0, IndexError()), pytest.param(20, KeyError()), pytest.param("three", [1, 2, 3]), ], - idfn=ids, - ) + ids, + None, + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["a-a0", "a-a1", "a-a2"] def test_idmaker_with_idfn_and_config(self) -> None: @@ -520,12 +584,15 @@ def getini(self, name): (MockConfig({option: False}), "a\\xe7\\xe3o"), ] for config, expected in values: - result = idmaker( + result = IdMaker( ("a",), [pytest.param("string")], - idfn=lambda _: "ação", - config=config, - ) + lambda _: "ação", + None, + config, + None, + None, + ).make_unique_parameterset_ids() assert result == [expected] def test_idmaker_with_ids_and_config(self) -> None: @@ -555,14 +622,18 @@ def getini(self, name): (MockConfig({option: False}), "a\\xe7\\xe3o"), ] for config, expected in values: - result = idmaker( - ("a",), - [pytest.param("string")], - ids=["ação"], - config=config, - ) + result = IdMaker( + ("a",), [pytest.param("string")], None, ["ação"], config, None, None + ).make_unique_parameterset_ids() assert result == [expected] + def test_idmaker_duplicated_empty_str(self) -> None: + """Regression test for empty strings parametrized more than once (#11563).""" + result = IdMaker( + ("a",), [pytest.param(""), pytest.param("")], None, None, None, None, None + ).make_unique_parameterset_ids() + assert result == ["0", "1"] + def test_parametrize_ids_exception(self, pytester: Pytester) -> None: """ :param pytester: the instance of Pytester class, a temporary @@ -617,23 +688,39 @@ def test_int(arg): ) def test_idmaker_with_ids(self) -> None: - result = idmaker( - ("a", "b"), [pytest.param(1, 2), pytest.param(3, 4)], ids=["a", None] - ) + result = IdMaker( + ("a", "b"), + [pytest.param(1, 2), pytest.param(3, 4)], + None, + ["a", None], + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["a", "3-4"] def test_idmaker_with_paramset_id(self) -> None: - result = idmaker( + result = IdMaker( ("a", "b"), [pytest.param(1, 2, id="me"), pytest.param(3, 4, id="you")], - ids=["a", None], - ) + None, + ["a", None], + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["me", "you"] 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"] - ) + result = IdMaker( + ("a"), + list(map(pytest.param, [1, 2, 3, 4, 5])), + None, + ["a", "a", "b", "c", "b"], + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["a0", "a1", "b0", "c", "b1"] def test_parametrize_indirect(self) -> None: @@ -646,8 +733,6 @@ def func(x, y): metafunc.parametrize("x", [1], indirect=True) metafunc.parametrize("y", [2, 3], indirect=True) assert len(metafunc._calls) == 2 - assert metafunc._calls[0].funcargs == {} - assert metafunc._calls[1].funcargs == {} assert metafunc._calls[0].params == dict(x=1, y=2) assert metafunc._calls[1].params == dict(x=1, y=3) @@ -659,8 +744,10 @@ def func(x, y): metafunc = self.Metafunc(func) metafunc.parametrize("x, y", [("a", "b")], indirect=["x"]) - assert metafunc._calls[0].funcargs == dict(y="b") - assert metafunc._calls[0].params == dict(x="a") + assert metafunc._calls[0].params == dict(x="a", y="b") + # Since `y` is a direct parameter, its pseudo-fixture would + # be registered. + assert list(metafunc._arg2fixturedefs.keys()) == ["y"] def test_parametrize_indirect_list_all(self) -> None: """#714""" @@ -670,8 +757,8 @@ def func(x, y): metafunc = self.Metafunc(func) metafunc.parametrize("x, y", [("a", "b")], indirect=["x", "y"]) - assert metafunc._calls[0].funcargs == {} assert metafunc._calls[0].params == dict(x="a", y="b") + assert list(metafunc._arg2fixturedefs.keys()) == [] def test_parametrize_indirect_list_empty(self) -> None: """#714""" @@ -681,8 +768,8 @@ def func(x, y): metafunc = self.Metafunc(func) metafunc.parametrize("x, y", [("a", "b")], indirect=[]) - assert metafunc._calls[0].funcargs == dict(x="a", y="b") - assert metafunc._calls[0].params == {} + assert metafunc._calls[0].params == dict(x="a", y="b") + assert list(metafunc._arg2fixturedefs.keys()) == ["x", "y"] def test_parametrize_indirect_wrong_type(self) -> None: def func(x, y): @@ -876,9 +963,9 @@ def test_parametrize_onearg(self) -> None: metafunc = self.Metafunc(lambda x: None) metafunc.parametrize("x", [1, 2]) assert len(metafunc._calls) == 2 - assert metafunc._calls[0].funcargs == dict(x=1) + assert metafunc._calls[0].params == dict(x=1) assert metafunc._calls[0].id == "1" - assert metafunc._calls[1].funcargs == dict(x=2) + assert metafunc._calls[1].params == dict(x=2) assert metafunc._calls[1].id == "2" def test_parametrize_onearg_indirect(self) -> None: @@ -893,11 +980,45 @@ 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 - assert metafunc._calls[0].funcargs == dict(x=1, y=2) + assert metafunc._calls[0].params == dict(x=1, y=2) assert metafunc._calls[0].id == "1-2" - assert metafunc._calls[1].funcargs == dict(x=3, y=4) + assert metafunc._calls[1].params == dict(x=3, y=4) assert metafunc._calls[1].id == "3-4" + def test_high_scoped_parametrize_reordering(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.mark.parametrize("arg2", [3, 4]) + @pytest.mark.parametrize("arg1", [0, 1, 2], scope='module') + def test1(arg1, arg2): + pass + + def test2(): + pass + + @pytest.mark.parametrize("arg1", [0, 1, 2], scope='module') + def test3(arg1): + pass + """ + ) + result = pytester.runpytest("--collect-only") + result.stdout.re_match_lines( + [ + r" ", + r" ", + r" ", + r" ", + r" ", + r" ", + r" ", + r" ", + r" ", + r" ", + ] + ) + def test_parametrize_multiple_times(self, pytester: Pytester) -> None: pytester.makepyfile( """ @@ -969,27 +1090,6 @@ def test_3(self, arg, arg2): """ ) - def test_format_args(self) -> None: - def function1(): - pass - - assert _format_args(function1) == "()" - - def function2(arg1): - pass - - assert _format_args(function2) == "(arg1)" - - def function3(arg1, arg2="qwe"): - pass - - assert _format_args(function3) == "(arg1, arg2='qwe')" - - def function4(arg1, *args, **kwargs): - pass - - assert _format_args(function4) == "(arg1, *args, **kwargs)" - class TestMetafuncFunctional: def test_attributes(self, pytester: Pytester) -> None: @@ -1272,7 +1372,7 @@ def test_parametrized_ids_invalid_type(self, pytester: Pytester) -> None: """ import pytest - @pytest.mark.parametrize("x, expected", [(1, 2), (3, 4), (5, 6)], ids=(None, 2, type)) + @pytest.mark.parametrize("x, expected", [(1, 2), (3, 4), (5, 6)], ids=(None, 2, OSError())) def test_ids_numbers(x,expected): assert x * 2 == expected """ @@ -1280,8 +1380,8 @@ def test_ids_numbers(x,expected): result = pytester.runpytest() result.stdout.fnmatch_lines( [ - "In test_ids_numbers: ids must be list of string/float/int/bool," - " found: (type: ) at index 2" + "In test_ids_numbers: ids contains unsupported value OSError() (type: ) at index 2. " + "Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__." ] ) @@ -1376,7 +1476,8 @@ def test_generate_tests_only_done_in_subdir(self, pytester: Pytester) -> None: def pytest_generate_tests(metafunc): assert metafunc.function.__name__ == "test_1" """ - ) + ), + encoding="utf-8", ) sub2.joinpath("conftest.py").write_text( textwrap.dedent( @@ -1384,10 +1485,15 @@ def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc): assert metafunc.function.__name__ == "test_2" """ - ) + ), + encoding="utf-8", + ) + sub1.joinpath("test_in_sub1.py").write_text( + "def test_1(): pass", encoding="utf-8" + ) + sub2.joinpath("test_in_sub2.py").write_text( + "def test_2(): pass", encoding="utf-8" ) - sub1.joinpath("test_in_sub1.py").write_text("def test_1(): pass") - sub2.joinpath("test_in_sub2.py").write_text("def test_2(): pass") result = pytester.runpytest("--keep-duplicates", "-v", "-s", sub1, sub2, sub1) result.assert_outcomes(passed=3) @@ -1420,7 +1526,7 @@ def test_foo(x): pass """ ) - result = pytester.runpytest("--collectonly") + result = pytester.runpytest("--collect-only") result.stdout.fnmatch_lines( [ "collected 0 items / 1 error", @@ -1435,6 +1541,115 @@ def test_foo(x): ] ) + @pytest.mark.parametrize("scope", ["class", "package"]) + def test_parametrize_missing_scope_doesnt_crash( + self, pytester: Pytester, scope: str + ) -> None: + """Doesn't crash when parametrize(scope=) is used without a + corresponding node.""" + pytester.makepyfile( + f""" + import pytest + + @pytest.mark.parametrize("x", [0], scope="{scope}") + def test_it(x): pass + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_parametrize_module_level_test_with_class_scope( + self, pytester: Pytester + ) -> None: + """ + Test that a class-scoped parametrization without a corresponding `Class` + gets module scope, i.e. we only create a single FixtureDef for it per module. + """ + module = pytester.makepyfile( + """ + import pytest + + @pytest.mark.parametrize("x", [0, 1], scope="class") + def test_1(x): + pass + + @pytest.mark.parametrize("x", [1, 2], scope="module") + def test_2(x): + pass + """ + ) + test_1_0, _, test_2_0, _ = pytester.genitems((pytester.getmodulecol(module),)) + + assert isinstance(test_1_0, Function) + assert test_1_0.name == "test_1[0]" + test_1_fixture_x = test_1_0._fixtureinfo.name2fixturedefs["x"][-1] + + assert isinstance(test_2_0, Function) + assert test_2_0.name == "test_2[1]" + test_2_fixture_x = test_2_0._fixtureinfo.name2fixturedefs["x"][-1] + + assert test_1_fixture_x is test_2_fixture_x + + def test_reordering_with_scopeless_and_just_indirect_parametrization( + self, pytester: Pytester + ) -> None: + pytester.makeconftest( + """ + import pytest + + @pytest.fixture(scope="package") + def fixture1(): + pass + """ + ) + pytester.makepyfile( + """ + import pytest + + @pytest.fixture(scope="module") + def fixture0(): + pass + + @pytest.fixture(scope="module") + def fixture1(fixture0): + pass + + @pytest.mark.parametrize("fixture1", [0], indirect=True) + def test_0(fixture1): + pass + + @pytest.fixture(scope="module") + def fixture(): + pass + + @pytest.mark.parametrize("fixture", [0], indirect=True) + def test_1(fixture): + pass + + def test_2(): + pass + + class Test: + @pytest.fixture(scope="class") + def fixture(self, fixture): + pass + + @pytest.mark.parametrize("fixture", [0], indirect=True) + def test_3(self, fixture): + pass + """ + ) + result = pytester.runpytest("-v") + assert result.ret == 0 + result.stdout.fnmatch_lines( + [ + "*test_0*", + "*test_1*", + "*test_2*", + "*test_3*", + ] + ) + class TestMetafuncFunctionalAuto: """Tests related to automatically find out the correct scope for @@ -1725,7 +1940,7 @@ def test_increment(n, expected): @pytest.mark.parametrize("strict", [True, False]) def test_xfail_passing_is_xpass(self, pytester: Pytester, strict: bool) -> None: - s = """ + s = f""" import pytest m = pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict}) @@ -1737,9 +1952,7 @@ def test_xfail_passing_is_xpass(self, pytester: Pytester, strict: bool) -> None: ]) def test_increment(n, expected): assert n + 1 == expected - """.format( - strict=strict - ) + """ pytester.makepyfile(s) reprec = pytester.inline_run() passed, failed = (2, 1) if strict else (3, 0) @@ -1790,7 +2003,7 @@ def test_limit(limit, myfixture): @pytest.mark.parametrize("strict", [True, False]) def test_parametrize_marked_value(self, pytester: Pytester, strict: bool) -> None: - s = """ + s = f""" import pytest @pytest.mark.parametrize(("n", "expected"), [ @@ -1805,9 +2018,7 @@ def test_parametrize_marked_value(self, pytester: Pytester, strict: bool) -> Non ]) def test_increment(n, expected): assert n + 1 == expected - """.format( - strict=strict - ) + """ pytester.makepyfile(s) reprec = pytester.inline_run() passed, failed = (0, 2) if strict else (2, 0) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/raises.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/raises.py index 2d62e91091b12..929865e31a099 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/raises.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/raises.py @@ -1,9 +1,10 @@ +# mypy: allow-untyped-defs import re import sys -import pytest from _pytest.outcomes import Failed from _pytest.pytester import Pytester +import pytest class TestRaises: @@ -19,6 +20,16 @@ def test_raises_function(self): excinfo = pytest.raises(ValueError, int, "hello") assert "invalid literal" in str(excinfo.value) + def test_raises_does_not_allow_none(self): + with pytest.raises(ValueError, match="Expected an exception type or"): + # We're testing that this invalid usage gives a helpful error, + # so we can ignore Mypy telling us that None is invalid. + pytest.raises(expected_exception=None) # type: ignore + + def test_raises_does_not_allow_empty_tuple(self): + with pytest.raises(ValueError, match="Expected an exception type or"): + pytest.raises(expected_exception=()) + def test_raises_callable_no_exception(self) -> None: class A: def __call__(self): @@ -82,13 +93,9 @@ def test_raise_wrong_exception_passes_by(): def test_does_not_raise(self, pytester: Pytester) -> None: pytester.makepyfile( """ - from contextlib import contextmanager + from contextlib import nullcontext as does_not_raise import pytest - @contextmanager - def does_not_raise(): - yield - @pytest.mark.parametrize('example_input,expectation', [ (3, does_not_raise()), (2, does_not_raise()), @@ -107,13 +114,9 @@ def test_division(example_input, expectation): def test_does_not_raise_does_raise(self, pytester: Pytester) -> None: pytester.makepyfile( """ - from contextlib import contextmanager + from contextlib import nullcontext as does_not_raise import pytest - @contextmanager - def does_not_raise(): - yield - @pytest.mark.parametrize('example_input,expectation', [ (0, does_not_raise()), (1, pytest.raises(ZeroDivisionError)), @@ -144,7 +147,7 @@ def test_no_raise_message(self) -> None: try: pytest.raises(ValueError, int, "0") except pytest.fail.Exception as e: - assert e.msg == f"DID NOT RAISE {repr(ValueError)}" + assert e.msg == f"DID NOT RAISE {ValueError!r}" else: assert False, "Expected pytest.raises.Exception" @@ -152,7 +155,7 @@ def test_no_raise_message(self) -> None: with pytest.raises(ValueError): pass except pytest.fail.Exception as e: - assert e.msg == f"DID NOT RAISE {repr(ValueError)}" + assert e.msg == f"DID NOT RAISE {ValueError!r}" else: assert False, "Expected pytest.raises.Exception" @@ -191,10 +194,12 @@ def test_raises_match(self) -> None: int("asdf") msg = "with base 16" - expr = "Regex pattern {!r} does not match \"invalid literal for int() with base 10: 'asdf'\".".format( - msg + expr = ( + "Regex pattern did not match.\n" + f" Regex: {msg!r}\n" + " Input: \"invalid literal for int() with base 10: 'asdf'\"" ) - with pytest.raises(AssertionError, match=re.escape(expr)): + with pytest.raises(AssertionError, match="(?m)" + re.escape(expr)): with pytest.raises(ValueError, match=msg): int("asdf", base=10) @@ -217,7 +222,7 @@ def test_match_failure_string_quoting(self): with pytest.raises(AssertionError, match="'foo"): raise AssertionError("'bar") (msg,) = excinfo.value.args - assert msg == 'Regex pattern "\'foo" does not match "\'bar".' + assert msg == '''Regex pattern did not match.\n Regex: "'foo"\n Input: "'bar"''' def test_match_failure_exact_string_message(self): message = "Oh here is a message with (42) numbers in parameters" @@ -226,9 +231,10 @@ def test_match_failure_exact_string_message(self): 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?" + "Regex pattern did not match.\n" + " Regex: 'Oh here is a message with (42) numbers in parameters'\n" + " Input: 'Oh here is a message with (42) numbers in parameters'\n" + " Did you mean to `re.escape()` the regex?" ) def test_raises_match_wrong_type(self): @@ -274,7 +280,7 @@ def __class__(self): def test_raises_context_manager_with_kwargs(self): with pytest.raises(TypeError) as excinfo: - with pytest.raises(Exception, foo="bar"): # type: ignore[call-overload] + with pytest.raises(OSError, foo="bar"): # type: ignore[call-overload] pass assert "Unexpected keyword arguments" in str(excinfo.value) @@ -296,3 +302,16 @@ class NotAnException: 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) + + def test_issue_11872(self) -> None: + """Regression test for #11872. + + urllib.error.HTTPError on Python<=3.9 raises KeyError instead of + AttributeError on invalid attribute access. + + https://github.com/python/cpython/issues/98778 + """ + from urllib.error import HTTPError + + with pytest.raises(HTTPError, match="Not Found"): + raise HTTPError(code=404, msg="Not Found", fp=None, hdrs=None, url="") # type: ignore [arg-type] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/w3c-import.log new file mode 100644 index 0000000000000..ffc48a7722ff3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/approx.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/collect.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/fixtures.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/integration.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/metafunc.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/raises.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/python/show_fixtures_per_test.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_argcomplete.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_argcomplete.py index 8c10e230b0c8a..0c41c0286a460 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_argcomplete.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_argcomplete.py @@ -1,9 +1,11 @@ +# mypy: allow-untyped-defs +from pathlib import Path import subprocess import sys -from pathlib import Path -import pytest from _pytest.monkeypatch import MonkeyPatch +import pytest + # Test for _argcomplete but not specific for any application. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_assertion.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_assertion.py index 2516ff1629e82..ef4e36644d9da 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_assertion.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_assertion.py @@ -1,32 +1,81 @@ -import collections +# mypy: allow-untyped-defs import sys import textwrap from typing import Any from typing import List from typing import MutableSequence +from typing import NamedTuple from typing import Optional import attr -import _pytest.assertion as plugin -import pytest from _pytest import outcomes +import _pytest.assertion as plugin from _pytest.assertion import truncate from _pytest.assertion import util +from _pytest.config import Config as _Config from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester +import pytest -def mock_config(verbose=0): +def mock_config(verbose: int = 0, assertion_override: Optional[int] = None): + class TerminalWriter: + def _highlight(self, source, lexer="python"): + return source + class Config: - def getoption(self, name): - if name == "verbose": + def get_terminal_writer(self): + return TerminalWriter() + + def get_verbosity(self, verbosity_type: Optional[str] = None) -> int: + if verbosity_type is None: + return verbose + if verbosity_type == _Config.VERBOSITY_ASSERTIONS: + if assertion_override is not None: + return assertion_override return verbose - raise KeyError("Not mocked out: %s" % name) + + raise KeyError(f"Not mocked out: {verbosity_type}") return Config() +class TestMockConfig: + SOME_VERBOSITY_LEVEL = 3 + SOME_OTHER_VERBOSITY_LEVEL = 10 + + def test_verbose_exposes_value(self): + config = mock_config(verbose=TestMockConfig.SOME_VERBOSITY_LEVEL) + + assert config.get_verbosity() == TestMockConfig.SOME_VERBOSITY_LEVEL + + def test_get_assertion_override_not_set_verbose_value(self): + config = mock_config(verbose=TestMockConfig.SOME_VERBOSITY_LEVEL) + + assert ( + config.get_verbosity(_Config.VERBOSITY_ASSERTIONS) + == TestMockConfig.SOME_VERBOSITY_LEVEL + ) + + def test_get_assertion_override_set_custom_value(self): + config = mock_config( + verbose=TestMockConfig.SOME_VERBOSITY_LEVEL, + assertion_override=TestMockConfig.SOME_OTHER_VERBOSITY_LEVEL, + ) + + assert ( + config.get_verbosity(_Config.VERBOSITY_ASSERTIONS) + == TestMockConfig.SOME_OTHER_VERBOSITY_LEVEL + ) + + def test_get_unsupported_type_error(self): + config = mock_config(verbose=TestMockConfig.SOME_VERBOSITY_LEVEL) + + with pytest.raises(KeyError): + config.get_verbosity("--- NOT A VERBOSITY LEVEL ---") + + class TestImportHookInstallation: @pytest.mark.parametrize("initial_conftest", [True, False]) @pytest.mark.parametrize("mode", ["plain", "rewrite"]) @@ -83,7 +132,7 @@ def test_dummy_failure(pytester): # how meta! "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", + "E Use -v to get more diff", ] ) # XXX: unstable output. @@ -133,11 +182,9 @@ def test_pytest_plugins_rewrite_module_names( """ plugins = '"ham"' if mode == "str" else '["ham"]' contents = { - "conftest.py": """ + "conftest.py": f""" pytest_plugins = {plugins} - """.format( - plugins=plugins - ), + """, "ham.py": """ import pytest """, @@ -199,8 +246,8 @@ def check(values, value): return check """, "mainwrapper.py": """\ + import importlib.metadata import pytest - from _pytest.compat import importlib_metadata class DummyEntryPoint(object): name = 'spam' @@ -220,7 +267,7 @@ class DummyDistInfo(object): def distributions(): return (DummyDistInfo(),) - importlib_metadata.distributions = distributions + importlib.metadata.distributions = distributions pytest.main() """, "test_foo.py": """\ @@ -344,6 +391,7 @@ def test_summary(self) -> None: def test_text_diff(self) -> None: assert callequal("spam", "eggs") == [ "'spam' == 'eggs'", + "", "- eggs", "+ spam", ] @@ -351,7 +399,7 @@ def test_text_diff(self) -> None: def test_text_skipping(self) -> None: lines = callequal("a" * 50 + "spam", "a" * 50 + "eggs") assert lines is not None - assert "Skipping" in lines[1] + assert "Skipping" in lines[2] for line in lines: assert "a" * 50 not in line @@ -375,8 +423,9 @@ def test_bytes_diff_normal(self) -> None: assert diff == [ "b'spam' == b'eggs'", + "", "At index 0 diff: b's' != b'e'", - "Use -v to get the full diff", + "Use -v to get more diff", ] def test_bytes_diff_verbose(self) -> None: @@ -384,7 +433,9 @@ def test_bytes_diff_verbose(self) -> None: 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'", @@ -403,11 +454,14 @@ def test_list(self) -> None: [0, 2], """ Full diff: - - [0, 2] + [ + 0, + - 2, ? ^ - + [0, 1] + + 1, ? ^ - """, + ] + """, id="lists", ), pytest.param( @@ -415,10 +469,12 @@ def test_list(self) -> None: {0: 2}, """ Full diff: - - {0: 2} - ? ^ - + {0: 1} - ? ^ + { + - 0: 2, + ? ^ + + 0: 1, + ? ^ + } """, id="dicts", ), @@ -427,10 +483,13 @@ def test_list(self) -> None: {0, 2}, """ Full diff: - - {0, 2} + { + 0, + - 2, ? ^ - + {0, 1} + + 1, ? ^ + } """, id="sets", ), @@ -444,11 +503,20 @@ def test_iterable_full_diff(self, left, right, expected) -> None: """ expl = callequal(left, right, verbose=0) assert expl is not None - assert expl[-1] == "Use -v to get the full diff" + assert expl[-1] == "Use -v to get more diff" 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_iterable_quiet(self) -> None: + expl = callequal([1, 2], [10, 2], verbose=-1) + assert expl == [ + "[1, 2] == [10, 2]", + "", + "At index 0 diff: 1 != 10", + "Use -v to get more diff", + ] + def test_iterable_full_diff_ci( self, monkeypatch: MonkeyPatch, pytester: Pytester ) -> None: @@ -466,7 +534,7 @@ def test_full_diff(): monkeypatch.delenv("CI", raising=False) result = pytester.runpytest() - result.stdout.fnmatch_lines(["E Use -v to get the full diff"]) + result.stdout.fnmatch_lines(["E Use -v to get more diff"]) def test_list_different_lengths(self) -> None: expl = callequal([0, 1], [0, 1, 2]) @@ -483,26 +551,30 @@ def test_list_wrap_for_multiple_lines(self) -> None: 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 + "',", + " '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 + "',", + " 'a',", + " 'b',", + " 'c',", + "+ '" + long_d + "',", " ]", ] @@ -515,36 +587,40 @@ def test_list_wrap_for_width_rewrap_same_length(self) -> None: diff = callequal(l1, l2, verbose=True) assert diff == [ "['aaaaaaaaaaa...cccccccccccc'] == ['bbbbbbbbbbb...aaaaaaaaaaaa']", + "", "At index 0 diff: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' != 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'", + "", "Full diff:", " [", - "+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", - " 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',", - " 'cccccccccccccccccccccccccccccc',", - "- 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", + "+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", + " 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',", + " 'cccccccccccccccccccccccccccccc',", + "- 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", " ]", ] def test_list_dont_wrap_strings(self) -> None: long_a = "a" * 10 - l1 = ["a"] + [long_a for _ in range(0, 7)] + l1 = ["a"] + [long_a for _ in range(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',", + "- 'should not get wrapped',", + "+ 'a',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", " ]", ] @@ -555,31 +631,45 @@ def test_dict_wrap(self) -> None: 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}}", - "? +++++++++++", + " {", + " 'common': 1,", + " 'env': {", + " 'env1': 1,", + "+ 'env2': 2,", + " },", + " }", ] long_a = "a" * 80 - sub = {"long_a": long_a, "sub1": {"long_a": "substring that gets wrapped " * 2}} + sub = {"long_a": long_a, "sub1": {"long_a": "substring that gets wrapped " * 3}} 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,", + " 'env': {", + " 'sub': {", + f" 'long_a': '{long_a}',", + " 'sub1': {", + " 'long_a': 'substring that gets wrapped substring that gets wrapped '", + " 'substring that gets wrapped ',", + " },", + " },", + " },", + "- 'new': 1,", " }", ] @@ -591,7 +681,7 @@ def test_dict(self) -> None: 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 lines[2].startswith("Omitting 1 identical item") assert "Common items" not in lines for line in lines[1:]: assert "b" not in line @@ -600,60 +690,109 @@ 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 lines[1] == "" + assert lines[2].startswith("Omitting 1 identical item") + assert lines[3].startswith("Differing items") + assert lines[4] == "{'a': 0} != {'a': 1}" assert "Common items" not in lines 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}" + assert lines[2].startswith("Common items:") + assert "Omitting" not in lines[2] + assert lines[3] == "{'b': 1}" def test_dict_different_items(self) -> None: lines = callequal({"a": 0}, {"b": 1, "c": 2}, verbose=2) assert lines == [ "{'a': 0} == {'b': 1, 'c': 2}", + "", "Left contains 1 more item:", "{'a': 0}", "Right contains 2 more items:", "{'b': 1, 'c': 2}", + "", "Full diff:", - "- {'b': 1, 'c': 2}", - "+ {'a': 0}", + " {", + "- 'b': 1,", + "? ^ ^", + "+ 'a': 0,", + "? ^ ^", + "- 'c': 2,", + " }", ] lines = callequal({"b": 1, "c": 2}, {"a": 0}, verbose=2) assert lines == [ "{'b': 1, 'c': 2} == {'a': 0}", + "", "Left contains 2 more items:", "{'b': 1, 'c': 2}", "Right contains 1 more item:", "{'a': 0}", + "", "Full diff:", - "- {'a': 0}", - "+ {'b': 1, 'c': 2}", + " {", + "- 'a': 0,", + "? ^ ^", + "+ 'b': 1,", + "? ^ ^", + "+ 'c': 2,", + " }", ] def test_sequence_different_items(self) -> None: lines = callequal((1, 2), (3, 4, 5), verbose=2) assert lines == [ "(1, 2) == (3, 4, 5)", + "", "At index 0 diff: 1 != 3", "Right contains one more item: 5", + "", "Full diff:", - "- (3, 4, 5)", - "+ (1, 2)", + " (", + "- 3,", + "? ^", + "+ 1,", + "? ^", + "- 4,", + "? ^", + "+ 2,", + "? ^", + "- 5,", + " )", ] lines = callequal((1, 2, 3), (4,), verbose=2) assert lines == [ "(1, 2, 3) == (4,)", + "", "At index 0 diff: 1 != 4", "Left contains 2 more items, first extra item: 2", + "", "Full diff:", - "- (4,)", - "+ (1, 2, 3)", + " (", + "- 4,", + "? ^", + "+ 1,", + "? ^", + "+ 2,", + "+ 3,", + " )", + ] + lines = callequal((1, 2, 3), (1, 20, 3), verbose=2) + assert lines == [ + "(1, 2, 3) == (1, 20, 3)", + "", + "At index 1 diff: 2 != 20", + "", + "Full diff:", + " (", + " 1,", + "- 20,", + "? -", + "+ 2,", + " 3,", + " )", ] def test_set(self) -> None: @@ -699,32 +838,6 @@ def test_list_tuples(self) -> None: assert expl is not None assert len(expl) > 1 - def test_repr_verbose(self) -> None: - class Nums: - def __init__(self, nums): - self.nums = nums - - def __repr__(self): - return str(self.nums) - - list_x = list(range(5000)) - list_y = list(range(5000)) - list_y[len(list_y) // 2] = 3 - nums_x = Nums(list_x) - nums_y = Nums(list_y) - - assert callequal(nums_x, nums_y) is None - - expl = callequal(nums_x, nums_y, verbose=1) - 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 expl is not None - assert "+" + repr(nums_x) in expl - assert "-" + repr(nums_y) in expl - def test_list_bad_repr(self) -> None: class A: def __repr__(self): @@ -737,11 +850,9 @@ def __repr__(self): assert expl is not None assert expl[0].startswith("{} == <[ValueError") assert "raised in repr" in expl[0] - assert expl[1:] == [ + assert expl[2:] == [ "(pytest_assertion plugin: representation of details failed:" - " {}:{}: ValueError: 42.".format( - __file__, A.__repr__.__code__.co_firstlineno + 1 - ), + f" {__file__}:{A.__repr__.__code__.co_firstlineno + 1}: ValueError: 42.", " Probably an object has a faulty __repr__.)", ] @@ -763,6 +874,7 @@ def test_repr_no_exc(self) -> None: def test_unicode(self) -> None: assert callequal("£€", "£") == [ "'£€' == '£'", + "", "- £", "+ £€", ] @@ -778,7 +890,7 @@ def __repr__(self): return "\xff" expl = callequal(A(), "1") - assert expl == ["ÿ == '1'", "- 1"] + assert expl == ["ÿ == '1'", "", "- 1"] def test_format_nonascii_explanation(self) -> None: assert util.format_explanation("λ") @@ -794,9 +906,28 @@ def test_mojibake(self) -> None: msg = "\n".join(expl) assert msg + def test_nfc_nfd_same_string(self) -> None: + # issue 3426 + left = "hyv\xe4" + right = "hyva\u0308" + expl = callequal(left, right) + assert expl == [ + r"'hyv\xe4' == 'hyva\u0308'", + "", + f"- {right!s}", + f"+ {left!s}", + ] + + expl = callequal(left, right, verbose=2) + assert expl == [ + r"'hyv\xe4' == 'hyva\u0308'", + "", + f"- {right!s}", + f"+ {left!s}", + ] + class TestAssert_reprcompare_dataclass: - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_dataclasses(self, pytester: Pytester) -> None: p = pytester.copy_example("dataclasses/test_compare_dataclasses.py") result = pytester.runpytest(p) @@ -808,14 +939,13 @@ def test_dataclasses(self, pytester: Pytester) -> None: "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", + "E field_b: 'b' != 'c'", + "E - c", + "E + b", ], consecutive=True, ) - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_recursive_dataclasses(self, pytester: Pytester) -> None: p = pytester.copy_example("dataclasses/test_compare_recursive_dataclasses.py") result = pytester.runpytest(p) @@ -829,12 +959,11 @@ def test_recursive_dataclasses(self, pytester: Pytester) -> None: "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", + "E ...Full output truncated (51 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, pytester: Pytester) -> None: p = pytester.copy_example("dataclasses/test_compare_recursive_dataclasses.py") result = pytester.runpytest(p, "-vv") @@ -854,8 +983,6 @@ def test_recursive_dataclasses_verbose(self, pytester: Pytester) -> None: "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'", @@ -867,7 +994,6 @@ def test_recursive_dataclasses_verbose(self, pytester: Pytester) -> None: consecutive=True, ) - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_dataclasses_verbose(self, pytester: Pytester) -> None: p = pytester.copy_example("dataclasses/test_compare_dataclasses_verbose.py") result = pytester.runpytest(p, "-vv") @@ -881,7 +1007,6 @@ def test_dataclasses_verbose(self, pytester: Pytester) -> None: ] ) - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_dataclasses_with_attribute_comparison_off( self, pytester: Pytester ) -> None: @@ -891,7 +1016,6 @@ def test_dataclasses_with_attribute_comparison_off( result = pytester.runpytest(p, "-vv") result.assert_outcomes(failed=0, passed=1) - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_comparing_two_different_data_classes(self, pytester: Pytester) -> None: p = pytester.copy_example( "dataclasses/test_compare_two_different_dataclasses.py" @@ -899,6 +1023,22 @@ def test_comparing_two_different_data_classes(self, pytester: Pytester) -> None: result = pytester.runpytest(p, "-vv") result.assert_outcomes(failed=0, passed=1) + def test_data_classes_with_custom_eq(self, pytester: Pytester) -> None: + p = pytester.copy_example( + "dataclasses/test_compare_dataclasses_with_custom_eq.py" + ) + # issue 9362 + result = pytester.runpytest(p, "-vv") + result.assert_outcomes(failed=1, passed=0) + result.stdout.no_re_match_line(".*Differing attributes.*") + + def test_data_classes_with_initvar(self, pytester: Pytester) -> None: + p = pytester.copy_example("dataclasses/test_compare_initvar.py") + # issue 9820 + result = pytester.runpytest(p, "-vv") + result.assert_outcomes(failed=1, passed=0) + result.stdout.no_re_match_line(".*AttributeError.*") + class TestAssert_reprcompare_attrsclass: def test_attrs(self) -> None: @@ -982,7 +1122,6 @@ class SimpleDataObject: right = SimpleDataObject(1, "b") lines = callequal(left, right, verbose=2) - print(lines) assert lines is not None assert lines[2].startswith("Matching attributes:") assert "Omitting" not in lines[1] @@ -1007,10 +1146,42 @@ class SimpleDataObjectTwo: lines = callequal(left, right) assert lines is None + def test_attrs_with_auto_detect_and_custom_eq(self) -> None: + @attr.s( + auto_detect=True + ) # attr.s doesn't ignore a custom eq if auto_detect=True + class SimpleDataObject: + field_a = attr.ib() + + def __eq__(self, other): # pragma: no cover + return super().__eq__(other) + + left = SimpleDataObject(1) + right = SimpleDataObject(2) + # issue 9362 + lines = callequal(left, right, verbose=2) + assert lines is None + + def test_attrs_with_custom_eq(self) -> None: + @attr.define(slots=False) + class SimpleDataObject: + field_a = attr.ib() + + def __eq__(self, other): # pragma: no cover + return super().__eq__(other) + + left = SimpleDataObject(1) + right = SimpleDataObject(2) + # issue 9362 + lines = callequal(left, right, verbose=2) + assert lines is None + class TestAssert_reprcompare_namedtuple: def test_namedtuple(self) -> None: - NT = collections.namedtuple("NT", ["a", "b"]) + class NT(NamedTuple): + a: Any + b: Any left = NT(1, "b") right = NT(1, "c") @@ -1027,12 +1198,17 @@ def test_namedtuple(self) -> None: " b: 'b' != 'c'", " - c", " + b", - "Use -v to get the full diff", + "Use -v to get more diff", ] def test_comparing_two_different_namedtuple(self) -> None: - NT1 = collections.namedtuple("NT1", ["a", "b"]) - NT2 = collections.namedtuple("NT2", ["a", "b"]) + class NT1(NamedTuple): + a: Any + b: Any + + class NT2(NamedTuple): + a: Any + b: Any left = NT1(1, "b") right = NT2(2, "b") @@ -1041,8 +1217,9 @@ def test_comparing_two_different_namedtuple(self) -> None: # Because the types are different, uses the generic sequence matcher. assert lines == [ "NT1(a=1, b='b') == NT2(a=2, b='b')", + "", "At index 0 diff: 1 != 2", - "Use -v to get the full diff", + "Use -v to get more diff", ] @@ -1151,30 +1328,55 @@ def test_doesnt_truncate_at_when_input_is_5_lines_and_LT_max_chars(self) -> None def test_truncates_at_8_lines_when_given_list_of_empty_strings(self) -> None: expl = ["" for x in range(50)] result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100) + assert len(result) != len(expl) assert result != expl assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG assert "Full output truncated" in result[-1] - assert "43 lines hidden" in result[-1] + assert "42 lines hidden" in result[-1] last_line_before_trunc_msg = result[-self.LINES_IN_TRUNCATION_MSG - 1] assert last_line_before_trunc_msg.endswith("...") def test_truncates_at_8_lines_when_first_8_lines_are_LT_max_chars(self) -> None: - expl = ["a" for x in range(100)] + total_lines = 100 + expl = ["a" for x in range(total_lines)] result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80) assert result != expl assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG assert "Full output truncated" in result[-1] - assert "93 lines hidden" in result[-1] + assert f"{total_lines - 8} lines hidden" in result[-1] last_line_before_trunc_msg = result[-self.LINES_IN_TRUNCATION_MSG - 1] assert last_line_before_trunc_msg.endswith("...") + def test_truncates_at_8_lines_when_there_is_one_line_to_remove(self) -> None: + """The number of line in the result is 9, the same number as if we truncated.""" + expl = ["a" for x in range(9)] + result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80) + assert result == expl + assert "truncated" not in result[-1] + + def test_truncates_edgecase_when_truncation_message_makes_the_result_longer_for_chars( + self, + ) -> None: + line = "a" * 10 + expl = [line, line] + result = truncate._truncate_explanation(expl, max_lines=10, max_chars=10) + assert result == [line, line] + + def test_truncates_edgecase_when_truncation_message_makes_the_result_longer_for_lines( + self, + ) -> None: + line = "a" * 10 + expl = [line, line] + result = truncate._truncate_explanation(expl, max_lines=1, max_chars=100) + assert result == [line, line] + def test_truncates_at_8_lines_when_first_8_lines_are_EQ_max_chars(self) -> None: - expl = ["a" * 80 for x in range(16)] + expl = [chr(97 + x) * 80 for x in range(16)] result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80) assert result != expl - assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG + assert len(result) == 16 - 8 + self.LINES_IN_TRUNCATION_MSG assert "Full output truncated" in result[-1] - assert "9 lines hidden" in result[-1] + assert "8 lines hidden" in result[-1] last_line_before_trunc_msg = result[-self.LINES_IN_TRUNCATION_MSG - 1] assert last_line_before_trunc_msg.endswith("...") @@ -1200,7 +1402,6 @@ def test_truncates_at_1_line_when_first_line_is_GT_max_chars(self) -> None: def test_full_output_truncated(self, monkeypatch, pytester: Pytester) -> None: """Test against full runpytest() output.""" - line_count = 7 line_len = 100 expected_truncated_lines = 2 @@ -1223,7 +1424,6 @@ def test_many_lines(): [ "*+ 1*", "*+ 3*", - "*+ 5*", "*truncated (%d lines hidden)*use*-vv*" % expected_truncated_lines, ] ) @@ -1267,6 +1467,7 @@ def test_rewritten(): def test_reprcompare_notin() -> None: assert callop("not in", "foo", "aaafoobbb") == [ "'foo' not in 'aaafoobbb'", + "", "'foo' is contained here:", " aaafoobbb", "? +++", @@ -1276,6 +1477,7 @@ def test_reprcompare_notin() -> None: def test_reprcompare_whitespaces() -> None: assert callequal("\r\n", "\n") == [ r"'\r\n' == '\n'", + "", r"Strings contain only whitespace, escaping them using repr()", r"- '\n'", r"+ '\r\n'", @@ -1283,72 +1485,104 @@ def test_reprcompare_whitespaces() -> None: ] -def test_pytest_assertrepr_compare_integration(pytester: Pytester) -> None: - pytester.makepyfile( +class TestSetAssertions: + @pytest.mark.parametrize("op", [">=", ">", "<=", "<", "=="]) + def test_set_extra_item(self, op, pytester: Pytester) -> None: + pytester.makepyfile( + f""" + def test_hello(): + x = set("hello x") + y = set("hello y") + assert x {op} y """ - def test_hello(): - x = set(range(100)) - y = x.copy() - y.remove(50) - assert x == y - """ - ) - result = pytester.runpytest() - result.stdout.fnmatch_lines( - [ - "*def test_hello():*", - "*assert x == y*", - "*E*Extra items*left*", - "*E*50*", - "*= 1 failed in*", - ] - ) + ) + + result = pytester.runpytest() + result.stdout.fnmatch_lines( + [ + "*def test_hello():*", + f"*assert x {op} y*", + ] + ) + if op in [">=", ">", "=="]: + result.stdout.fnmatch_lines( + [ + "*E*Extra items in the right set:*", + "*E*'y'", + ] + ) + if op in ["<=", "<", "=="]: + result.stdout.fnmatch_lines( + [ + "*E*Extra items in the left set:*", + "*E*'x'", + ] + ) + + @pytest.mark.parametrize("op", [">", "<", "!="]) + def test_set_proper_superset_equal(self, pytester: Pytester, op) -> None: + pytester.makepyfile( + f""" + def test_hello(): + x = set([1, 2, 3]) + y = x.copy() + assert x {op} y + """ + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines( + [ + "*def test_hello():*", + f"*assert x {op} y*", + "*E*Both sets are equal*", + ] + ) -def test_sequence_comparison_uses_repr(pytester: Pytester) -> None: - pytester.makepyfile( + def test_pytest_assertrepr_compare_integration(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def test_hello(): + x = set(range(100)) + y = x.copy() + y.remove(50) + assert x == y """ - def test_hello(): - x = set("hello x") - y = set("hello y") - assert x == y - """ - ) - result = pytester.runpytest() - result.stdout.fnmatch_lines( - [ - "*def test_hello():*", - "*assert x == y*", - "*E*Extra items*left*", - "*E*'x'*", - "*E*Extra items*right*", - "*E*'y'*", - ] - ) + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines( + [ + "*def test_hello():*", + "*assert x == y*", + "*E*Extra items*left*", + "*E*50*", + "*= 1 failed in*", + ] + ) def test_assertrepr_loaded_per_dir(pytester: Pytester) -> None: pytester.makepyfile(test_base=["def test_base(): assert 1 == 2"]) a = pytester.mkdir("a") - a.joinpath("test_a.py").write_text("def test_a(): assert 1 == 2") + a.joinpath("test_a.py").write_text("def test_a(): assert 1 == 2", encoding="utf-8") a.joinpath("conftest.py").write_text( - 'def pytest_assertrepr_compare(): return ["summary a"]' + 'def pytest_assertrepr_compare(): return ["summary a"]', encoding="utf-8" ) b = pytester.mkdir("b") - b.joinpath("test_b.py").write_text("def test_b(): assert 1 == 2") + b.joinpath("test_b.py").write_text("def test_b(): assert 1 == 2", encoding="utf-8") b.joinpath("conftest.py").write_text( - 'def pytest_assertrepr_compare(): return ["summary b"]' + 'def pytest_assertrepr_compare(): return ["summary b"]', encoding="utf-8" ) result = pytester.runpytest() result.stdout.fnmatch_lines( [ - "*def test_base():*", - "*E*assert 1 == 2*", "*def test_a():*", "*E*assert summary a*", "*def test_b():*", "*E*assert summary b*", + "*def test_base():*", + "*E*assert 1 == 2*", ] ) @@ -1513,9 +1747,9 @@ def test_something(): ) result = pytester.runpytest("--collect-only") result.stdout.fnmatch_lines( - """ - - """ + [ + " ", + ] ) @@ -1627,15 +1861,7 @@ def test_raising_repr(): """ ) result = pytester.runpytest() - if sys.version_info >= (3, 11): - # python 3.11 has native support for un-str-able exceptions - result.stdout.fnmatch_lines( - ["E AssertionError: "] - ) - else: - result.stdout.fnmatch_lines( - ["E AssertionError: "] - ) + result.stdout.fnmatch_lines(["E AssertionError: "]) def test_issue_1944(pytester: Pytester) -> None: @@ -1683,3 +1909,139 @@ def test(): "*= 1 failed in*", ] ) + + +def test_reprcompare_verbose_long() -> None: + a = {f"v{i}": i for i in range(11)} + b = a.copy() + b["v2"] += 10 + lines = callop("==", a, b, verbose=2) + assert lines is not None + assert lines[0] == ( + "{'v0': 0, 'v1': 1, 'v2': 2, 'v3': 3, 'v4': 4, 'v5': 5, " + "'v6': 6, 'v7': 7, 'v8': 8, 'v9': 9, 'v10': 10}" + " == " + "{'v0': 0, 'v1': 1, 'v2': 12, 'v3': 3, 'v4': 4, 'v5': 5, " + "'v6': 6, 'v7': 7, 'v8': 8, 'v9': 9, 'v10': 10}" + ) + + +@pytest.mark.parametrize("enable_colors", [True, False]) +@pytest.mark.parametrize( + ("test_code", "expected_lines"), + ( + ( + """ + def test(): + assert [0, 1] == [0, 2] + """, + [ + "{bold}{red}E At index 1 diff: {reset}{number}1{hl-reset}{endline} != {reset}{number}2*", + "{bold}{red}E {light-red}- 2,{hl-reset}{endline}{reset}", + "{bold}{red}E {light-green}+ 1,{hl-reset}{endline}{reset}", + ], + ), + ( + """ + def test(): + assert {f"number-is-{i}": i for i in range(1, 6)} == { + f"number-is-{i}": i for i in range(5) + } + """, + [ + "{bold}{red}E Common items:{reset}", + "{bold}{red}E {reset}{{{str}'{hl-reset}{str}number-is-1{hl-reset}{str}'{hl-reset}: {number}1*", + "{bold}{red}E Left contains 1 more item:{reset}", + "{bold}{red}E {reset}{{{str}'{hl-reset}{str}number-is-5{hl-reset}{str}'{hl-reset}: {number}5*", + "{bold}{red}E Right contains 1 more item:{reset}", + "{bold}{red}E {reset}{{{str}'{hl-reset}{str}number-is-0{hl-reset}{str}'{hl-reset}: {number}0*", + "{bold}{red}E {reset}{light-gray} {hl-reset} {{{endline}{reset}", + "{bold}{red}E {light-gray} {hl-reset} 'number-is-1': 1,{endline}{reset}", + "{bold}{red}E {light-green}+ 'number-is-5': 5,{hl-reset}{endline}{reset}", + ], + ), + ), +) +def test_comparisons_handle_colors( + pytester: Pytester, color_mapping, enable_colors, test_code, expected_lines +) -> None: + p = pytester.makepyfile(test_code) + result = pytester.runpytest( + f"--color={'yes' if enable_colors else 'no'}", "-vv", str(p) + ) + formatter = ( + color_mapping.format_for_fnmatch + if enable_colors + else color_mapping.strip_colors + ) + + result.stdout.fnmatch_lines(formatter(expected_lines), consecutive=False) + + +def test_fine_grained_assertion_verbosity(pytester: Pytester): + long_text = "Lorem ipsum dolor sit amet " * 10 + p = pytester.makepyfile( + f""" + def test_ok(): + pass + + + def test_words_fail(): + fruits1 = ["banana", "apple", "grapes", "melon", "kiwi"] + fruits2 = ["banana", "apple", "orange", "melon", "kiwi"] + assert fruits1 == fruits2 + + + def test_numbers_fail(): + number_to_text1 = {{str(x): x for x in range(5)}} + number_to_text2 = {{str(x * 10): x * 10 for x in range(5)}} + assert number_to_text1 == number_to_text2 + + + def test_long_text_fail(): + long_text = "{long_text}" + assert "hello world" in long_text + """ + ) + pytester.makeini( + """ + [pytest] + verbosity_assertions = 2 + """ + ) + result = pytester.runpytest(p) + + result.stdout.fnmatch_lines( + [ + f"{p.name} .FFF [100%]", + "E At index 2 diff: 'grapes' != 'orange'", + "E Full diff:", + "E [", + "E 'banana',", + "E 'apple',", + "E - 'orange',", + "E ? ^ ^^", + "E + 'grapes',", + "E ? ^ ^ +", + "E 'melon',", + "E 'kiwi',", + "E ]", + "E Full diff:", + "E {", + "E '0': 0,", + "E - '10': 10,", + "E ? - -", + "E + '1': 1,", + "E - '20': 20,", + "E ? - -", + "E + '2': 2,", + "E - '30': 30,", + "E ? - -", + "E + '3': 3,", + "E - '40': 40,", + "E ? - -", + "E + '4': 4,", + "E }", + f"E AssertionError: assert 'hello world' in '{long_text}'", + ] + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_assertrewrite.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_assertrewrite.py index 4417eb4350fef..ac93c57dbd74a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_assertrewrite.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_assertrewrite.py @@ -1,25 +1,27 @@ +# mypy: allow-untyped-defs import ast import errno +from functools import partial import glob import importlib import marshal import os +from pathlib import Path import py_compile import stat import sys import textwrap -import zipfile -from functools import partial -from pathlib import Path from typing import cast from typing import Dict +from typing import Generator from typing import List from typing import Mapping from typing import Optional from typing import Set +from unittest import mock +import zipfile import _pytest._code -import pytest from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE from _pytest.assertion import util from _pytest.assertion.rewrite import _get_assertion_exprs @@ -33,6 +35,7 @@ from _pytest.config import ExitCode from _pytest.pathlib import make_numbered_dir from _pytest.pytester import Pytester +import pytest def rewrite(src: str) -> ast.Module: @@ -129,9 +132,8 @@ def test_location_is_set(self) -> None: for n in [node, *ast.iter_child_nodes(node)]: assert n.lineno == 3 assert n.col_offset == 0 - if sys.version_info >= (3, 8): - assert n.end_lineno == 6 - assert n.end_col_offset == 3 + assert n.end_lineno == 6 + assert n.end_col_offset == 3 def test_dont_rewrite(self) -> None: s = """'PYTEST_DONT_REWRITE'\nassert 14""" @@ -158,7 +160,8 @@ def test_rewrites_plugin_as_a_package(self, pytester: Pytester) -> None: "def special_asserter():\n" " def special_assert(x, y):\n" " assert x == y\n" - " return special_assert\n" + " return special_assert\n", + encoding="utf-8", ) pytester.makeconftest('pytest_plugins = ["plugin"]') pytester.makepyfile("def test(special_asserter): special_asserter(1, 2)\n") @@ -171,7 +174,9 @@ def test_honors_pep_235(self, pytester: Pytester, monkeypatch) -> None: pytester.makepyfile(test_y="x = 1") xdir = pytester.mkdir("x") pytester.mkpydir(str(xdir.joinpath("test_Y"))) - xdir.joinpath("test_Y").joinpath("__init__.py").write_text("x = 2") + xdir.joinpath("test_Y").joinpath("__init__.py").write_text( + "x = 2", encoding="utf-8" + ) pytester.makepyfile( "import test_y\n" "import test_Y\n" @@ -195,23 +200,15 @@ def f2() -> None: assert getmsg(f2) == "assert False" def f3() -> None: - assert a_global # type: ignore[name-defined] # noqa + assert a_global # type: ignore[name-defined] # noqa: F821 assert getmsg(f3, {"a_global": False}) == "assert False" def f4() -> None: assert sys == 42 # type: ignore[comparison-overlap] - verbose = request.config.getoption("verbose") msg = getmsg(f4, {"sys": sys}) - if verbose > 0: - assert msg == ( - "assert == 42\n" - " +\n" - " -42" - ) - else: - assert msg == "assert sys == 42" + assert msg == "assert sys == 42" def f5() -> None: assert cls == 42 # type: ignore[name-defined] # noqa: F821 @@ -222,20 +219,7 @@ class X: msg = getmsg(f5, {"cls": X}) assert msg is not None lines = msg.splitlines() - if verbose > 1: - assert lines == [ - f"assert {X!r} == 42", - f" +{X!r}", - " -42", - ] - elif verbose > 0: - assert lines == [ - "assert .X'> == 42", - f" +{X!r}", - " -42", - ] - else: - assert lines == ["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.""" @@ -277,14 +261,11 @@ def f() -> None: msg = getmsg(f, {"cls": Y}) assert msg is not None lines = msg.splitlines() - if request.config.getoption("verbose") > 0: - assert lines == ["assert 3 == 2", " +3", " -2"] - else: - assert lines == [ - "assert 3 == 2", - " + where 3 = Y.foo", - " + where Y = cls()", - ] + assert lines == [ + "assert 3 == 2", + " + where 3 = Y.foo", + " + where Y = cls()", + ] def test_assert_already_has_message(self) -> None: def f(): @@ -448,7 +429,7 @@ def f1() -> None: def f2() -> None: x = 1 - assert x == 1 or x == 2 + assert x == 1 or x == 2 # noqa: PLR1714 getmsg(f2, must_pass=True) @@ -661,10 +642,7 @@ def f(): assert len(values) == 11 msg = getmsg(f) - if request.config.getoption("verbose") > 0: - 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, ...])" + assert msg == "assert 10 == 11\n + where 10 = len([0, 1, 2, 3, 4, 5, ...])" def test_custom_reprcompare(self, monkeypatch) -> None: def my_reprcompare1(op, left, right) -> str: @@ -708,6 +686,25 @@ def myany(x) -> bool: assert msg is not None assert " < 0" in msg + def test_assert_handling_raise_in__iter__(self, pytester: Pytester) -> None: + pytester.makepyfile( + """\ + class A: + def __iter__(self): + raise ValueError() + + def __eq__(self, o: object) -> bool: + return self is o + + def __repr__(self): + return "" + + assert A() == A() + """ + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines(["*E*assert == "]) + def test_formatchar(self) -> None: def f() -> None: assert "%test" == "test" # type: ignore[comparison-overlap] @@ -730,10 +727,7 @@ def __repr__(self): 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"] - else: - assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"] + assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"] def test_custom_repr_non_ascii(self) -> None: def f() -> None: @@ -754,7 +748,7 @@ def __repr__(self): class TestRewriteOnImport: def test_pycache_is_a_file(self, pytester: Pytester) -> None: - pytester.path.joinpath("__pycache__").write_text("Hello") + pytester.path.joinpath("__pycache__").write_text("Hello", encoding="utf-8") pytester.makepyfile( """ def test_rewritten(): @@ -787,11 +781,10 @@ def test_zipfile(self, pytester: Pytester) -> None: f.close() z.chmod(256) pytester.makepyfile( - """ + f""" import sys - sys.path.append(%r) + sys.path.append({z_fn!r}) import test_gum.test_lizard""" - % (z_fn,) ) assert pytester.runpytest().ret == ExitCode.NO_TESTS_COLLECTED @@ -902,7 +895,11 @@ def test_foo(): ) @pytest.mark.skipif('"__pypy__" in sys.modules') - def test_pyc_vs_pyo(self, pytester: Pytester, monkeypatch) -> None: + def test_pyc_vs_pyo( + self, + pytester: Pytester, + monkeypatch: pytest.MonkeyPatch, + ) -> None: pytester.makepyfile( """ import pytest @@ -912,13 +909,13 @@ def test_optimized(): ) p = make_numbered_dir(root=Path(pytester.path), prefix="runpytest-") tmp = "--basetemp=%s" % p - monkeypatch.setenv("PYTHONOPTIMIZE", "2") - monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False) - monkeypatch.delenv("PYTHONPYCACHEPREFIX", raising=False) - assert pytester.runpytest_subprocess(tmp).ret == 0 - tagged = "test_pyc_vs_pyo." + PYTEST_TAG - assert tagged + ".pyo" in os.listdir("__pycache__") - monkeypatch.undo() + with monkeypatch.context() as mp: + mp.setenv("PYTHONOPTIMIZE", "2") + mp.delenv("PYTHONDONTWRITEBYTECODE", raising=False) + mp.delenv("PYTHONPYCACHEPREFIX", raising=False) + assert pytester.runpytest_subprocess(tmp).ret == 0 + tagged = "test_pyc_vs_pyo." + PYTEST_TAG + assert tagged + ".pyo" in os.listdir("__pycache__") monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False) monkeypatch.delenv("PYTHONPYCACHEPREFIX", raising=False) assert pytester.runpytest_subprocess(tmp).ret == 1 @@ -931,7 +928,8 @@ def test_package(self, pytester: Pytester) -> None: pkg.joinpath("test_blah.py").write_text( """ def test_rewritten(): - assert "@py_builtins" in globals()""" + assert "@py_builtins" in globals()""", + encoding="utf-8", ) assert pytester.runpytest().ret == 0 @@ -1037,9 +1035,9 @@ def test_meta_path(): ) assert pytester.runpytest().ret == 0 - def test_write_pyc(self, pytester: Pytester, tmp_path, monkeypatch) -> None: - from _pytest.assertion.rewrite import _write_pyc + def test_write_pyc(self, pytester: Pytester, tmp_path) -> None: from _pytest.assertion import AssertionState + from _pytest.assertion.rewrite import _write_pyc config = pytester.parseconfig() state = AssertionState(config, "rewrite") @@ -1049,27 +1047,8 @@ def test_write_pyc(self, pytester: Pytester, tmp_path, monkeypatch) -> None: 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() - - monkeypatch.setattr("os.rename", raise_oserror) - - assert not _write_pyc(state, co, os.stat(source_path), pycpath) + with mock.patch.object(os, "replace", side_effect=OSError): + assert not _write_pyc(state, co, os.stat(source_path), pycpath) def test_resources_provider_for_loader(self, pytester: Pytester) -> None: """ @@ -1108,12 +1087,13 @@ def test_read_pyc(self, tmp_path: Path) -> None: an exception that is propagated to the caller. """ import py_compile + from _pytest.assertion.rewrite import _read_pyc source = tmp_path / "source.py" pyc = Path(str(source) + "c") - source.write_text("def test(): pass") + source.write_text("def test(): pass", encoding="utf-8") py_compile.compile(str(source), str(pyc)) contents = pyc.read_bytes() @@ -1139,15 +1119,12 @@ def test_read_pyc_success(self, tmp_path: Path, pytester: Pytester) -> None: fn = tmp_path / "source.py" pyc = Path(str(fn) + "c") - fn.write_text("def test(): assert True") + fn.write_text("def test(): assert True", encoding="utf-8") source_stat, co = _rewrite_test(fn, config) _write_pyc(state, co, source_stat, pyc) assert _read_pyc(fn, pyc, state.trace) is not None - @pytest.mark.skipif( - sys.version_info < (3, 7), reason="Only the Python 3.7 format for simplicity" - ) def test_read_pyc_more_invalid(self, tmp_path: Path) -> None: from _pytest.assertion.rewrite import _read_pyc @@ -1207,7 +1184,7 @@ def reloaded(): return False def rewrite_self(): - with open(__file__, 'w') as self: + with open(__file__, 'w', encoding='utf-8') as self: self.write('def reloaded(): return True') """, test_fun=""" @@ -1237,9 +1214,10 @@ def test_foo(self): data = pkgutil.get_data('foo.test_foo', 'data.txt') assert data == b'Hey' """ - ) + ), + encoding="utf-8", ) - path.joinpath("data.txt").write_text("Hey") + path.joinpath("data.txt").write_text("Hey", encoding="utf-8") result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) @@ -1315,8 +1293,284 @@ def test_simple_failure(): result.stdout.fnmatch_lines(["*E*assert (1 + 1) == 3"]) +class TestIssue10743: + def test_assertion_walrus_operator(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def my_func(before, after): + return before == after + + def change_value(value): + return value.lower() + + def test_walrus_conversion(): + a = "Hello" + assert not my_func(a, a := change_value(a)) + assert a == "hello" + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_dont_rewrite(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + 'PYTEST_DONT_REWRITE' + def my_func(before, after): + return before == after + + def change_value(value): + return value.lower() + + def test_walrus_conversion_dont_rewrite(): + a = "Hello" + assert not my_func(a, a := change_value(a)) + assert a == "hello" + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_inline_walrus_operator(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def my_func(before, after): + return before == after + + def test_walrus_conversion_inline(): + a = "Hello" + assert not my_func(a, a := a.lower()) + assert a == "hello" + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_inline_walrus_operator_reverse(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def my_func(before, after): + return before == after + + def test_walrus_conversion_reverse(): + a = "Hello" + assert my_func(a := a.lower(), a) + assert a == 'hello' + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_no_variable_name_conflict( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_conversion_no_conflict(): + a = "Hello" + assert a == (b := a.lower()) + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines(["*AssertionError: assert 'Hello' == 'hello'"]) + + def test_assertion_walrus_operator_true_assertion_and_changes_variable_value( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_conversion_succeed(): + a = "Hello" + assert a != (a := a.lower()) + assert a == 'hello' + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_fail_assertion(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def test_walrus_conversion_fails(): + a = "Hello" + assert a == (a := a.lower()) + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines(["*AssertionError: assert 'Hello' == 'hello'"]) + + def test_assertion_walrus_operator_boolean_composite( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_operator_change_boolean_value(): + a = True + assert a and True and ((a := False) is False) and (a is False) and ((a := None) is None) + assert a is None + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_compare_boolean_fails( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_operator_change_boolean_value(): + a = True + assert not (a and ((a := False) is False)) + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines(["*assert not (True and False is False)"]) + + def test_assertion_walrus_operator_boolean_none_fails( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_operator_change_boolean_value(): + a = True + assert not (a and ((a := None) is None)) + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines(["*assert not (True and None is None)"]) + + def test_assertion_walrus_operator_value_changes_cleared_after_each_test( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_operator_change_value(): + a = True + assert (a := None) is None + + def test_walrus_operator_not_override_value(): + a = True + assert a is True + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + +class TestIssue11028: + def test_assertion_walrus_operator_in_operand(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def test_in_string(): + assert (obj := "foo") in obj + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_in_operand_json_dumps( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + import json + + def test_json_encoder(): + assert (obj := "foo") in json.dumps(obj) + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_equals_operand_function( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def f(a): + return a + + def test_call_other_function_arg(): + assert (obj := "foo") == f(obj) + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_equals_operand_function_keyword_arg( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def f(a='test'): + return a + + def test_call_other_function_k_arg(): + assert (obj := "foo") == f(a=obj) + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_equals_operand_function_arg_as_function( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def f(a='test'): + return a + + def test_function_of_function(): + assert (obj := "foo") == f(f(obj)) + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_gt_operand_function( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def add_one(a): + return a + 1 + + def test_gt(): + assert (obj := 4) > add_one(obj) + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines(["*assert 4 > 5", "*where 5 = add_one(4)"]) + + +class TestIssue11239: + def test_assertion_walrus_different_test_cases(self, pytester: Pytester) -> None: + """Regression for (#11239) + + Walrus operator rewriting would leak to separate test cases if they used the same variables. + """ + pytester.makepyfile( + """ + def test_1(): + state = {"x": 2}.get("x") + assert state is not None + + def test_2(): + db = {"x": 2} + assert (state := db.get("x")) is not None + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + @pytest.mark.skipif( - sys.maxsize <= (2 ** 31 - 1), reason="Causes OverflowError on 32bit systems" + sys.maxsize <= (2**31 - 1), reason="Causes OverflowError on 32bit systems" ) @pytest.mark.parametrize("offset", [-1, +1]) def test_source_mtime_long_long(pytester: Pytester, offset) -> None: @@ -1335,7 +1589,7 @@ def test(): pass # use unsigned long timestamp which overflows signed long, # which was the cause of the bug # +1 offset also tests masking of 0xFFFFFFFF - timestamp = 2 ** 32 + offset + timestamp = 2**32 + offset os.utime(str(p), (timestamp, timestamp)) result = pytester.runpytest() assert result.ret == 0 @@ -1379,7 +1633,7 @@ class TestEarlyRewriteBailout: @pytest.fixture def hook( self, pytestconfig, monkeypatch, pytester: Pytester - ) -> AssertionRewritingHook: + ) -> Generator[AssertionRewritingHook, None, None]: """Returns a patched AssertionRewritingHook instance so we can configure its initial paths and track if PathFinder.find_spec has been called. """ @@ -1400,11 +1654,11 @@ def spy_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, "_find_spec", spy_find_spec) - hook.set_session(StubSession()) # type: ignore[arg-type] - pytester.syspathinsert() - return hook + with mock.patch.object(hook, "fnpats", ["test_*.py", "*_test.py"]): + monkeypatch.setattr(hook, "_find_spec", spy_find_spec) + hook.set_session(StubSession()) # type: ignore[arg-type] + pytester.syspathinsert() + yield hook def test_basic(self, pytester: Pytester, hook: AssertionRewritingHook) -> None: """ @@ -1454,9 +1708,9 @@ def test_simple_failure(): } ) pytester.syspathinsert("tests") - hook.fnpats[:] = ["tests/**.py"] - assert hook.find_spec("file") is not None - assert self.find_spec_calls == ["file"] + with mock.patch.object(hook, "fnpats", ["tests/**.py"]): + 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" @@ -1477,8 +1731,8 @@ def test_cwd_changed(self, pytester: Pytester, monkeypatch) -> None: import os import tempfile - with tempfile.TemporaryDirectory() as d: - os.chdir(d) + with tempfile.TemporaryDirectory() as newpath: + os.chdir(newpath) """, "test_test.py": """\ def test(): @@ -1602,10 +1856,10 @@ def test_simple(): result.assert_outcomes(passed=1) +# fmt: off @pytest.mark.parametrize( ("src", "expected"), ( - # fmt: off pytest.param(b"", {}, id="trivial"), pytest.param( b"def x(): assert 1\n", @@ -1682,9 +1936,9 @@ def test_simple(): {1: "5"}, id="no newline at end of file", ), - # fmt: on ), ) +# fmt: on def test_get_assertion_exprs(src, expected) -> None: assert _get_assertion_exprs(src) == expected @@ -1720,6 +1974,11 @@ def fake_mkdir(p, exist_ok=False, *, exc): monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=err)) assert not try_makedirs(p) + err = OSError() + err.errno = errno.ENOSYS + monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=err)) + assert not try_makedirs(p) + # unhandled OSError should raise err = OSError() err.errno = errno.ECHILD @@ -1741,16 +2000,10 @@ class TestPyCacheDir: ) def test_get_cache_dir(self, monkeypatch, prefix, source, expected) -> None: monkeypatch.delenv("PYTHONPYCACHEPREFIX", raising=False) - - if prefix is not None and sys.version_info < (3, 8): - pytest.skip("pycache_prefix not available in py<38") monkeypatch.setattr(sys, "pycache_prefix", prefix, raising=False) assert get_cache_dir(Path(source)) == Path(expected) - @pytest.mark.skipif( - sys.version_info < (3, 8), reason="pycache_prefix not available in py<38" - ) @pytest.mark.skipif( sys.version_info[:2] == (3, 9) and sys.platform.startswith("win"), reason="#9298", @@ -1786,9 +2039,7 @@ def test_foo(): 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 - ) + bar_init_pyc = get_cache_dir(bar_init) / f"__init__.{sys.implementation.cache_tag}.pyc" assert bar_init_pyc.is_file() @@ -1809,13 +2060,15 @@ class TestReprSizeVerbosity: ) def test_get_maxsize_for_saferepr(self, verbose: int, expected_size) -> None: class FakeConfig: - def getoption(self, name: str) -> int: - assert name == "verbose" + def get_verbosity(self, verbosity_type: Optional[str] = None) -> int: return verbose config = FakeConfig() assert _get_maxsize_for_saferepr(cast(Config, config)) == expected_size + def test_get_maxsize_for_saferepr_no_config(self) -> None: + assert _get_maxsize_for_saferepr(None) == DEFAULT_REPR_MAX_SIZE + def create_test_file(self, pytester: Pytester, size: int) -> None: pytester.makepyfile( f""" @@ -1839,3 +2092,17 @@ def test_max_increased_verbosity(self, pytester: Pytester) -> None: self.create_test_file(pytester, DEFAULT_REPR_MAX_SIZE * 10) result = pytester.runpytest("-vv") result.stdout.no_fnmatch_line("*xxx...xxx*") + + +class TestIssue11140: + def test_constant_not_picked_as_module_docstring(self, pytester: Pytester) -> None: + pytester.makepyfile( + """\ + 0 + + def test_foo(): + pass + """ + ) + result = pytester.runpytest() + assert result.ret == 0 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_cacheprovider.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_cacheprovider.py index cc6d547dfb1b5..ea662e87f0788 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_cacheprovider.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_cacheprovider.py @@ -1,14 +1,21 @@ +from enum import auto +from enum import Enum import os -import shutil from pathlib import Path +import shutil +from typing import Any from typing import Generator from typing import List +from typing import Sequence +from typing import Tuple -import pytest +from _pytest.compat import assert_never from _pytest.config import ExitCode from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester from _pytest.tmpdir import TempPathFactory +import pytest + pytest_plugins = ("pytester",) @@ -24,6 +31,21 @@ def test_config_cache_mkdir(self, pytester: Pytester) -> None: p = config.cache.mkdir("name") assert p.is_dir() + def test_cache_dir_permissions(self, pytester: Pytester) -> None: + """The .pytest_cache directory should have world-readable permissions + (depending on umask). + + Regression test for #12308. + """ + pytester.makeini("[pytest]") + config = pytester.parseconfigure() + assert config.cache is not None + p = config.cache.mkdir("name") + assert p.is_dir() + # Instead of messing with umask, make sure .pytest_cache has the same + # permissions as the default that `mkdir` gives `p`. + assert (p.parent.stat().st_mode & 0o777) == (p.stat().st_mode & 0o777) + def test_config_cache_dataerror(self, pytester: Pytester) -> None: pytester.makeini("[pytest]") config = pytester.parseconfigure() @@ -36,9 +58,11 @@ def test_config_cache_dataerror(self, pytester: Pytester) -> None: assert val == -2 @pytest.mark.filterwarnings("ignore:could not create cache path") - def test_cache_writefail_cachfile_silent(self, pytester: Pytester) -> None: + def test_cache_writefail_cachefile_silent(self, pytester: Pytester) -> None: pytester.makeini("[pytest]") - pytester.path.joinpath(".pytest_cache").write_text("gone wrong") + pytester.path.joinpath(".pytest_cache").write_text( + "gone wrong", encoding="utf-8" + ) config = pytester.parseconfigure() cache = config.cache assert cache is not None @@ -87,7 +111,7 @@ def test_cache_failure_warns( "*= warnings summary =*", "*/cacheprovider.py:*", " */cacheprovider.py:*: PytestCacheWarning: could not create cache path " - f"{unwritable_cache_dir}/v/cache/nodeids", + f"{unwritable_cache_dir}/v/cache/nodeids: *", ' config.cache.set("cache/nodeids", sorted(self.cached_nodeids))', "*1 failed, 3 warnings in*", ] @@ -131,12 +155,10 @@ def test_cachefuncarg(cache): def test_custom_rel_cache_dir(self, pytester: Pytester) -> None: rel_cache_dir = os.path.join("custom_cache_dir", "subdir") pytester.makeini( - """ + f""" [pytest] - cache_dir = {cache_dir} - """.format( - cache_dir=rel_cache_dir - ) + cache_dir = {rel_cache_dir} + """ ) pytester.makepyfile(test_errored="def test_error():\n assert False") pytester.runpytest() @@ -148,12 +170,10 @@ def test_custom_abs_cache_dir( tmp = tmp_path_factory.mktemp("tmp") abs_cache_dir = tmp / "custom_cache_dir" pytester.makeini( - """ + f""" [pytest] - cache_dir = {cache_dir} - """.format( - cache_dir=abs_cache_dir - ) + cache_dir = {abs_cache_dir} + """ ) pytester.makepyfile(test_errored="def test_error():\n assert False") pytester.runpytest() @@ -167,17 +187,17 @@ def test_custom_cache_dir_with_env_var( """ [pytest] cache_dir = {cache_dir} - """.format( - cache_dir="$env_var" - ) + """.format(cache_dir="$env_var") ) pytester.makepyfile(test_errored="def test_error():\n assert False") pytester.runpytest() assert pytester.path.joinpath("custom_cache_dir").is_dir() -@pytest.mark.parametrize("env", ((), ("TOX_ENV_DIR", "/tox_env_dir"))) -def test_cache_reportheader(env, pytester: Pytester, monkeypatch: MonkeyPatch) -> None: +@pytest.mark.parametrize("env", ((), ("TOX_ENV_DIR", "mydir/tox-env"))) +def test_cache_reportheader( + env: Sequence[str], pytester: Pytester, monkeypatch: MonkeyPatch +) -> None: pytester.makepyfile("""def test_foo(): pass""") if env: monkeypatch.setenv(*env) @@ -198,12 +218,10 @@ def test_cache_reportheader_external_abspath( pytester.makepyfile("def test_hello(): pass") pytester.makeini( - """ + f""" [pytest] - cache_dir = {abscache} - """.format( - abscache=external_cache - ) + cache_dir = {external_cache} + """ ) result = pytester.runpytest("-v") result.stdout.fnmatch_lines([f"cachedir: {external_cache}"]) @@ -420,7 +438,13 @@ def test_fail(val): result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 failed in*"]) - def test_terminal_report_lastfailed(self, pytester: Pytester) -> None: + @pytest.mark.parametrize("parent", ("directory", "package")) + def test_terminal_report_lastfailed(self, pytester: Pytester, parent: str) -> None: + if parent == "package": + pytester.makepyfile( + __init__="", + ) + test_a = pytester.makepyfile( test_a=""" def test_a1(): pass @@ -494,7 +518,6 @@ def test_a2(): pass def test_lastfailed_collectfailure( self, pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: - pytester.makepyfile( test_maybe=""" import os @@ -506,7 +529,7 @@ def test_hello(): """ ) - def rlf(fail_import, fail_run): + def rlf(fail_import: int, fail_run: int) -> Any: monkeypatch.setenv("FAILIMPORT", str(fail_import)) monkeypatch.setenv("FAILTEST", str(fail_run)) @@ -554,7 +577,9 @@ def test_pass(): """ ) - def rlf(fail_import, fail_run, args=()): + def rlf( + fail_import: int, fail_run: int, args: Sequence[str] = () + ) -> Tuple[Any, Any]: monkeypatch.setenv("FAILIMPORT", str(fail_import)) monkeypatch.setenv("FAILTEST", str(fail_run)) @@ -638,13 +663,11 @@ def test(): assert 0 assert result.ret == 1 pytester.makepyfile( - """ + f""" import pytest @pytest.{mark} def test(): assert 0 - """.format( - mark=mark - ) + """ ) result = pytester.runpytest() assert result.ret == 0 @@ -773,7 +796,7 @@ def pytest_sessionfinish(): result = pytester.runpytest("--lf", "--lfnf", "none") result.stdout.fnmatch_lines( [ - "collected 2 items / 2 deselected", + "collected 2 items / 2 deselected / 0 selected", "run-last-failure: no previously failed tests, deselecting all items.", "deselected=2", "* 2 deselected in *", @@ -849,6 +872,33 @@ def test_3(): pass ] ) + def test_lastfailed_skip_collection_with_nesting(self, pytester: Pytester) -> None: + """Check that file skipping works even when the file with failures is + nested at a different level of the collection tree.""" + pytester.makepyfile( + **{ + "test_1.py": """ + def test_1(): pass + """, + "pkg/__init__.py": "", + "pkg/test_2.py": """ + def test_2(): assert False + """, + } + ) + # first run + result = pytester.runpytest() + result.stdout.fnmatch_lines(["collected 2 items", "*1 failed*1 passed*"]) + # second run - test_1.py is skipped. + result = pytester.runpytest("--lf") + result.stdout.fnmatch_lines( + [ + "collected 1 item", + "run-last-failure: rerun previous 1 failure (skipped 1 file)", + "*= 1 failed in *", + ] + ) + def test_lastfailed_with_known_failures_not_being_selected( self, pytester: Pytester ) -> None: @@ -902,8 +952,10 @@ def test_lastfailed_with_known_failures_not_being_selected( "collected 1 item", "run-last-failure: rerun previous 1 failure (skipped 1 file)", "", - "", - " ", + "", + " ", + " ", + " ", ] ) @@ -932,8 +984,10 @@ def test_fail(): assert 0 "*collected 1 item", "run-last-failure: 1 known failures not in selected tests", "", - "", - " ", + "", + " ", + " ", + " ", ], consecutive=True, ) @@ -947,8 +1001,10 @@ def test_fail(): assert 0 "collected 2 items / 1 deselected / 1 selected", "run-last-failure: rerun previous 1 failure", "", - "", - " ", + "", + " ", + " ", + " ", "*= 1/2 tests collected (1 deselected) in *", ], ) @@ -977,10 +1033,12 @@ def test_other(): assert 0 "collected 3 items / 1 deselected / 2 selected", "run-last-failure: rerun previous 2 failures", "", - "", - " ", - " ", - " ", + "", + " ", + " ", + " ", + " ", + " ", "", "*= 2/3 tests collected (1 deselected) in *", ], @@ -1014,8 +1072,10 @@ def test_pass(): pass "collected 1 item", "run-last-failure: 1 known failures not in selected tests", "", - "", - " ", + "", + " ", + " ", + " ", "", "*= 1 test collected in*", ], @@ -1053,6 +1113,28 @@ def test_packages(self, pytester: Pytester) -> None: result = pytester.runpytest("--lf") result.assert_outcomes(failed=3) + def test_non_python_file_skipped( + self, + pytester: Pytester, + dummy_yaml_custom_test: None, + ) -> None: + pytester.makepyfile( + **{ + "test_bad.py": """def test_bad(): assert False""", + }, + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines(["collected 2 items", "* 1 failed, 1 passed in *"]) + + result = pytester.runpytest("--lf") + result.stdout.fnmatch_lines( + [ + "collected 1 item", + "run-last-failure: rerun previous 1 failure (skipped 1 file)", + "* 1 failed in *", + ] + ) + class TestNewFirst: def test_newfirst_usecase(self, pytester: Pytester) -> None: @@ -1080,7 +1162,9 @@ def test_1(): assert 1 ["*test_2/test_2.py::test_1 PASSED*", "*test_1/test_1.py::test_1 PASSED*"] ) - p1.write_text("def test_1(): assert 1\n" "def test_2(): assert 1\n") + p1.write_text( + "def test_1(): assert 1\n" "def test_2(): assert 1\n", encoding="utf-8" + ) os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9))) result = pytester.runpytest("--nf", "--collect-only", "-q") @@ -1153,7 +1237,8 @@ def test_1(num): assert num p1.write_text( "import pytest\n" "@pytest.mark.parametrize('num', [1, 2, 3])\n" - "def test_1(num): assert num\n" + "def test_1(num): assert num\n", + encoding="utf-8", ) os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9))) @@ -1193,20 +1278,41 @@ def test_readme_failed(self, pytester: Pytester) -> None: assert self.check_readme(pytester) is True -def test_gitignore(pytester: Pytester) -> None: +class Action(Enum): + """Action to perform on the cache directory.""" + + MKDIR = auto() + SET = auto() + + +@pytest.mark.parametrize("action", list(Action)) +def test_gitignore( + pytester: Pytester, + action: Action, +) -> None: """Ensure we automatically create .gitignore file in the pytest_cache directory (#3286).""" from _pytest.cacheprovider import Cache config = pytester.parseconfig() cache = Cache.for_config(config, _ispytest=True) - cache.set("foo", "bar") + if action == Action.MKDIR: + cache.mkdir("foo") + elif action == Action.SET: + cache.set("foo", "bar") + else: + assert_never(action) 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("custom") - cache.set("something", "else") + gitignore_path.write_text("custom", encoding="utf-8") + if action == Action.MKDIR: + cache.mkdir("something") + elif action == Action.SET: + cache.set("something", "else") + else: + assert_never(action) assert gitignore_path.read_text(encoding="UTF-8") == "custom" @@ -1249,3 +1355,8 @@ def test_cachedir_tag(pytester: Pytester) -> None: cache.set("foo", "bar") cachedir_tag_path = cache._cachedir.joinpath("CACHEDIR.TAG") assert cachedir_tag_path.read_bytes() == CACHEDIR_TAG_CONTENT + + +def test_clioption_with_cacheshow_and_help(pytester: Pytester) -> None: + result = pytester.runpytest("--cache-show", "--help") + assert result.ret == 0 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_capture.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_capture.py index 1bc1f2f8db2cd..0521c3b6b0485 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_capture.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_capture.py @@ -1,16 +1,16 @@ +# mypy: allow-untyped-defs import contextlib import io +from io import UnsupportedOperation import os import subprocess import sys import textwrap -from io import UnsupportedOperation 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 CaptureFixture @@ -20,6 +20,8 @@ from _pytest.config import ExitCode from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester +import pytest + # note: py.io capture tests where copied from # pylib 1.4.20.dev2 (rev 13d9af95547e) @@ -502,13 +504,11 @@ def test_capture_is_represented_on_failure_issue128( self, pytester: Pytester, method ) -> None: p = pytester.makepyfile( - """\ - def test_hello(cap{}): + f"""\ + def test_hello(cap{method}): print("xxx42xxx") assert 0 - """.format( - method - ) + """ ) result = pytester.runpytest(p) result.stdout.fnmatch_lines(["xxx42xxx"]) @@ -623,7 +623,7 @@ def test_disabled_capture_fixture( self, pytester: Pytester, fixture: str, no_capture: bool ) -> None: pytester.makepyfile( - """\ + f"""\ def test_disabled({fixture}): print('captured before') with {fixture}.disabled(): @@ -633,9 +633,7 @@ def test_disabled({fixture}): def test_normal(): print('test_normal executed') - """.format( - fixture=fixture - ) + """ ) args = ("-s",) if no_capture else () result = pytester.runpytest_subprocess(*args) @@ -680,7 +678,7 @@ def test_fixture_use_by_other_fixtures(self, pytester: Pytester, fixture) -> Non """Ensure that capsys and capfd can be used by other fixtures during setup and teardown.""" pytester.makepyfile( - """\ + f"""\ import sys import pytest @@ -702,9 +700,7 @@ def test_captured_print(captured_print): out, err = captured_print assert out == 'stdout contents begin\\n' assert err == 'stderr contents begin\\n' - """.format( - fixture=fixture - ) + """ ) result = pytester.runpytest_subprocess() result.stdout.fnmatch_lines(["*1 passed*"]) @@ -717,7 +713,7 @@ def test_fixture_use_by_other_fixtures_teardown( ) -> None: """Ensure we can access setup and teardown buffers from teardown when using capsys/capfd (##3033)""" pytester.makepyfile( - """\ + f"""\ import sys import pytest import os @@ -734,9 +730,7 @@ def fix({cap}): def test_a(fix): print("call out") sys.stderr.write("call err\\n") - """.format( - cap=cap - ) + """ ) reprec = pytester.inline_run() reprec.assertoutcome(passed=1) @@ -750,9 +744,10 @@ def test_setup_failure_does_not_kill_capturing(pytester: Pytester) -> None: def pytest_runtest_setup(item): raise ValueError(42) """ - ) + ), + encoding="utf-8", ) - sub1.joinpath("test_mod.py").write_text("def test_func1(): pass") + sub1.joinpath("test_mod.py").write_text("def test_func1(): pass", encoding="utf-8") result = pytester.runpytest(pytester.path, "--traceconfig") result.stdout.fnmatch_lines(["*ValueError(42)*", "*1 error*"]) @@ -890,14 +885,26 @@ def test_dontreadfrominput() -> None: from _pytest.capture import DontReadFromInput f = DontReadFromInput() - assert f.buffer is f + assert f.buffer is f # type: ignore[comparison-overlap] assert not f.isatty() pytest.raises(OSError, f.read) pytest.raises(OSError, f.readlines) iter_f = iter(f) pytest.raises(OSError, next, iter_f) pytest.raises(UnsupportedOperation, f.fileno) + pytest.raises(UnsupportedOperation, f.flush) + assert not f.readable() + pytest.raises(UnsupportedOperation, f.seek, 0) + assert not f.seekable() + pytest.raises(UnsupportedOperation, f.tell) + pytest.raises(UnsupportedOperation, f.truncate, 0) + pytest.raises(UnsupportedOperation, f.write, b"") + pytest.raises(UnsupportedOperation, f.writelines, []) + assert not f.writable() + assert isinstance(f.encoding, str) f.close() # just for completeness + with f: + pass def test_captureresult() -> None: @@ -1035,15 +1042,12 @@ def test_simple_resume_suspend(self) -> None: pytest.raises(AssertionError, cap.suspend) assert repr(cap) == ( - "".format( - cap.targetfd_save, cap.tmpfile - ) + f"" ) # Should not crash with missing "_old". + assert isinstance(cap.syscapture, capture.SysCapture) assert repr(cap.syscapture) == ( - " _state='done' tmpfile={!r}>".format( - cap.syscapture.tmpfile - ) + f" _state='done' tmpfile={cap.syscapture.tmpfile!r}>" ) def test_capfd_sys_stdout_mode(self, capfd) -> None: @@ -1184,7 +1188,6 @@ class TestTeeStdCapture(TestStdCapture): def test_capturing_error_recursive(self) -> None: 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: @@ -1340,6 +1343,7 @@ def test_capsys_results_accessible_by_attribute(capsys: CaptureFixture[str]) -> def test_fdcapture_tmpfile_remains_the_same() -> None: cap = StdCaptureFD(out=False, err=True) + assert isinstance(cap.err, capture.FDCapture) try: cap.start_capturing() capfile = cap.err.tmpfile @@ -1378,28 +1382,27 @@ def test_capture_again(): def test_capturing_and_logging_fundamentals(pytester: Pytester, method: str) -> None: # here we check a fundamental feature p = pytester.makepyfile( - """ + f""" import sys, os, logging from _pytest import capture cap = capture.MultiCapture( in_=None, out=None, - err=capture.%s, + err=capture.{method}, ) cap.start_capturing() logging.warning("hello1") outerr = cap.readouterr() - print("suspend, captured %%s" %%(outerr,)) + print("suspend, captured %s" %(outerr,)) logging.warning("hello2") cap.pop_outerr_to_orig() logging.warning("hello3") outerr = cap.readouterr() - print("suspend2, captured %%s" %% (outerr,)) + print("suspend2, captured %s" % (outerr,)) """ - % (method,) ) result = pytester.runpython(p) result.stdout.fnmatch_lines( @@ -1433,19 +1436,19 @@ def test_capattr(): not sys.platform.startswith("win"), reason="only on windows", ) -def test_py36_windowsconsoleio_workaround_non_standard_streams() -> None: +def test_windowsconsoleio_workaround_non_standard_streams() -> None: """ - Ensure _py36_windowsconsoleio_workaround function works with objects that + Ensure _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 + from _pytest.capture import _windowsconsoleio_workaround class DummyStream: def write(self, s): pass stream = cast(TextIO, DummyStream()) - _py36_windowsconsoleio_workaround(stream) + _windowsconsoleio_workaround(stream) def test_dontreadfrominput_has_encoding(pytester: Pytester) -> None: @@ -1509,9 +1512,9 @@ def test_global_capture_with_live_logging(pytester: Pytester) -> None: def pytest_runtest_logreport(report): if "test_global" in report.nodeid: if report.when == "teardown": - with open("caplog", "w") as f: + with open("caplog", "w", encoding="utf-8") as f: f.write(report.caplog) - with open("capstdout", "w") as f: + with open("capstdout", "w", encoding="utf-8") as f: f.write(report.capstdout) """ ) @@ -1541,14 +1544,14 @@ def test_global(fix1): result = pytester.runpytest_subprocess("--log-cli-level=INFO") assert result.ret == 0 - with open("caplog") as f: + with open("caplog", encoding="utf-8") as f: caplog = f.read() assert "fix setup" in caplog assert "something in test" in caplog assert "fix teardown" in caplog - with open("capstdout") as f: + with open("capstdout", encoding="utf-8") as f: capstdout = f.read() assert "fix setup" in capstdout @@ -1565,16 +1568,16 @@ def test_capture_with_live_logging( # capture should work with live cli logging pytester.makepyfile( - """ + f""" import logging import sys logger = logging.getLogger(__name__) - def test_capture({0}): + def test_capture({capture_fixture}): print("hello") sys.stderr.write("world\\n") - captured = {0}.readouterr() + captured = {capture_fixture}.readouterr() assert captured.out == "hello\\n" assert captured.err == "world\\n" @@ -1582,11 +1585,9 @@ def test_capture({0}): print("next") logging.info("something") - captured = {0}.readouterr() + captured = {capture_fixture}.readouterr() assert captured.out == "next\\n" - """.format( - capture_fixture - ) + """ ) result = pytester.runpytest_subprocess("--log-cli-level=INFO") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_collection.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_collection.py index 6a8a5c1cef11e..7f0790693a521 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_collection.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_collection.py @@ -1,12 +1,15 @@ +# mypy: allow-untyped-defs import os +from pathlib import Path import pprint import shutil import sys +import tempfile import textwrap -from pathlib import Path from typing import List +from typing import Type -import pytest +from _pytest.assertion.util import running_on_ci from _pytest.config import ExitCode from _pytest.fixtures import FixtureRequest from _pytest.main import _in_venv @@ -16,6 +19,7 @@ from _pytest.pathlib import symlink_or_skip from _pytest.pytester import HookRecorder from _pytest.pytester import Pytester +import pytest def ensure_file(file_path: Path) -> Path: @@ -99,7 +103,8 @@ def test_getcustomfile_roundtrip(self, pytester: Pytester) -> None: conftest=""" import pytest class CustomFile(pytest.File): - pass + def collect(self): + return [] def pytest_collect_file(file_path, parent): if file_path.suffix == ".xxx": return CustomFile.from_parent(path=file_path, parent=parent) @@ -140,7 +145,7 @@ def test_ignored_certain_directories(self, pytester: Pytester) -> None: ensure_file(tmp_path / ".bzr" / "test_notfound.py") ensure_file(tmp_path / "normal" / "test_found.py") for x in tmp_path.rglob("test_*.py"): - x.write_text("def test_hello(): pass", "utf-8") + x.write_text("def test_hello(): pass", encoding="utf-8") result = pytester.runpytest("--collect-only") s = result.stdout.str() @@ -162,7 +167,7 @@ def test_ignored_virtualenvs(self, pytester: Pytester, fname: str) -> None: bindir = "Scripts" if sys.platform.startswith("win") else "bin" ensure_file(pytester.path / "virtual" / bindir / fname) testfile = ensure_file(pytester.path / "virtual" / "test_invenv.py") - testfile.write_text("def test_hello(): pass") + testfile.write_text("def test_hello(): pass", encoding="utf-8") # by default, ignore tests inside a virtualenv result = pytester.runpytest() @@ -192,7 +197,7 @@ def test_ignored_virtualenvs_norecursedirs_precedence( # norecursedirs takes priority ensure_file(pytester.path / ".virtual" / bindir / fname) testfile = ensure_file(pytester.path / ".virtual" / "test_invenv.py") - testfile.write_text("def test_hello(): pass") + testfile.write_text("def test_hello(): pass", encoding="utf-8") result = pytester.runpytest("--collect-in-virtualenv") result.stdout.no_fnmatch_line("*test_invenv*") # ...unless the virtualenv is explicitly given on the CLI @@ -231,10 +236,14 @@ def test_custom_norecursedirs(self, pytester: Pytester) -> None: ) tmp_path = pytester.path ensure_file(tmp_path / "mydir" / "test_hello.py").write_text( - "def test_1(): pass" + "def test_1(): pass", encoding="utf-8" + ) + ensure_file(tmp_path / "xyz123" / "test_2.py").write_text( + "def test_2(): 0/0", encoding="utf-8" + ) + ensure_file(tmp_path / "xy" / "test_ok.py").write_text( + "def test_3(): pass", encoding="utf-8" ) - ensure_file(tmp_path / "xyz123" / "test_2.py").write_text("def test_2(): 0/0") - ensure_file(tmp_path / "xy" / "test_ok.py").write_text("def test_3(): pass") rec = pytester.inline_run() rec.assertoutcome(passed=1) rec = pytester.inline_run("xyz123/test_2.py") @@ -244,32 +253,55 @@ def test_testpaths_ini(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> No pytester.makeini( """ [pytest] - testpaths = gui uts + testpaths = */tests """ ) tmp_path = pytester.path - ensure_file(tmp_path / "env" / "test_1.py").write_text("def test_env(): pass") - ensure_file(tmp_path / "gui" / "test_2.py").write_text("def test_gui(): pass") - ensure_file(tmp_path / "uts" / "test_3.py").write_text("def test_uts(): pass") + ensure_file(tmp_path / "a" / "test_1.py").write_text( + "def test_a(): pass", encoding="utf-8" + ) + ensure_file(tmp_path / "b" / "tests" / "test_2.py").write_text( + "def test_b(): pass", encoding="utf-8" + ) + ensure_file(tmp_path / "c" / "tests" / "test_3.py").write_text( + "def test_c(): pass", encoding="utf-8" + ) # executing from rootdir only tests from `testpaths` directories # are collected items, reprec = pytester.inline_genitems("-v") - assert [x.name for x in items] == ["test_gui", "test_uts"] + assert [x.name for x in items] == ["test_b", "test_c"] # check that explicitly passing directories in the command-line # collects the tests - for dirname in ("env", "gui", "uts"): + for dirname in ("a", "b", "c"): items, reprec = pytester.inline_genitems(tmp_path.joinpath(dirname)) assert [x.name for x in items] == ["test_%s" % dirname] # changing cwd to each subdirectory and running pytest without # arguments collects the tests in that directory normally - for dirname in ("env", "gui", "uts"): + for dirname in ("a", "b", "c"): monkeypatch.chdir(pytester.path.joinpath(dirname)) items, reprec = pytester.inline_genitems() assert [x.name for x in items] == ["test_%s" % dirname] + def test_missing_permissions_on_unselected_directory_doesnt_crash( + self, pytester: Pytester + ) -> None: + """Regression test for #12120.""" + test = pytester.makepyfile(test="def test(): pass") + bad = pytester.mkdir("bad") + try: + bad.chmod(0) + + result = pytester.runpytest(test) + finally: + bad.chmod(750) + bad.rmdir() + + assert result.ret == ExitCode.OK + result.assert_outcomes(passed=1) + class TestCollectPluginHookRelay: def test_pytest_collect_file(self, pytester: Pytester) -> None: @@ -324,17 +356,39 @@ def test_collect_report_postprocessing(self, pytester: Pytester) -> None: pytester.makeconftest( """ import pytest - @pytest.hookimpl(hookwrapper=True) + @pytest.hookimpl(wrapper=True) def pytest_make_collect_report(): - outcome = yield - rep = outcome.get_result() + rep = yield rep.headerlines += ["header1"] - outcome.force_result(rep) + return rep """ ) result = pytester.runpytest(p) result.stdout.fnmatch_lines(["*ERROR collecting*", "*header1*"]) + def test_collection_error_traceback_is_clean(self, pytester: Pytester) -> None: + """When a collection error occurs, the report traceback doesn't contain + internal pytest stack entries. + + Issue #11710. + """ + pytester.makepyfile( + """ + raise Exception("LOUSY") + """ + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines( + [ + "*ERROR collecting*", + "test_*.py:1: in ", + ' raise Exception("LOUSY")', + "E Exception: LOUSY", + "*= short test summary info =*", + ], + consecutive=True, + ) + class TestCustomConftests: def test_ignore_collect_path(self, pytester: Pytester) -> None: @@ -345,8 +399,8 @@ def pytest_ignore_collect(collection_path, config): """ ) sub = pytester.mkdir("xy123") - ensure_file(sub / "test_hello.py").write_text("syntax error") - sub.joinpath("conftest.py").write_text("syntax error") + ensure_file(sub / "test_hello.py").write_text("syntax error", encoding="utf-8") + sub.joinpath("conftest.py").write_text("syntax error", encoding="utf-8") pytester.makepyfile("def test_hello(): pass") pytester.makepyfile(test_one="syntax error") result = pytester.runpytest("--fulltrace") @@ -480,7 +534,7 @@ def test_collect_topdir(self, pytester: Pytester) -> None: # assert root2 == rcol, rootid colitems = rcol.perform_collect([rcol.nodeid], genitems=False) assert len(colitems) == 1 - assert colitems[0].path == p + assert colitems[0].path == topdir def get_reported_items(self, hookrec: HookRecorder) -> List[Item]: """Return pytest.Item instances reported by the pytest_collectreport hook""" @@ -501,7 +555,7 @@ def test_collect_protocol_single_function(self, pytester: Pytester) -> None: newid = item.nodeid assert newid == id pprint.pprint(hookrec.calls) - topdir = pytester.path # noqa + topdir = pytester.path # noqa: F841 hookrec.assert_contains( [ ("pytest_collectstart", "collector.path == topdir"), @@ -637,6 +691,23 @@ def test_method(self): # ensure we are reporting the collection of the single test item (#2464) assert [x.name for x in self.get_reported_items(hookrec)] == ["test_method"] + def test_collect_parametrized_order(self, pytester: Pytester) -> None: + p = pytester.makepyfile( + """ + import pytest + + @pytest.mark.parametrize('i', [0, 1, 2]) + def test_param(i): ... + """ + ) + items, hookrec = pytester.inline_genitems(f"{p}::test_param") + assert len(items) == 3 + assert [item.nodeid for item in items] == [ + "test_collect_parametrized_order.py::test_param[0]", + "test_collect_parametrized_order.py::test_param[1]", + "test_collect_parametrized_order.py::test_param[2]", + ] + class Test_getinitialnodes: def test_global_file(self, pytester: Pytester) -> None: @@ -647,11 +718,12 @@ def test_global_file(self, pytester: Pytester) -> None: assert isinstance(col, pytest.Module) assert col.name == "x.py" assert col.parent is not None - assert col.parent.parent is None + assert col.parent.parent is not None + assert col.parent.parent.parent is None for parent in col.listchain(): assert parent.config is config - def test_pkgfile(self, pytester: Pytester) -> None: + def test_pkgfile(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> None: """Verify nesting when a module is within a package. The parent chain should match: Module -> Package -> Session. Session's parent should always be None. @@ -660,7 +732,8 @@ def test_pkgfile(self, pytester: Pytester) -> None: subdir = tmp_path.joinpath("subdir") x = ensure_file(subdir / "x.py") ensure_file(subdir / "__init__.py") - with subdir.cwd(): + with monkeypatch.context() as mp: + mp.chdir(subdir) config = pytester.parseconfigure(x) col = pytester.getnode(config, x) assert col is not None @@ -730,6 +803,20 @@ def testmethod_two(self, arg0): assert s.endswith("test_example_items1.testone") print(s) + def test_classmethod_is_discovered(self, pytester: Pytester) -> None: + """Test that classmethods are discovered""" + p = pytester.makepyfile( + """ + class TestCase: + @classmethod + def test_classmethod(cls) -> None: + pass + """ + ) + items, reprec = pytester.inline_genitems(p) + ids = [x.getmodpath() for x in items] # type: ignore[attr-defined] + assert ids == ["TestCase.test_classmethod"] + def test_class_and_functions_discovery_using_glob(self, pytester: Pytester) -> None: """Test that Python_classes and Python_functions config options work as prefixes and glob-like patterns (#600).""" @@ -881,6 +968,76 @@ def test_method(self): pass assert item.keywords["kw"] == "method" assert len(item.keywords) == len(set(item.keywords)) + def test_unpacked_marks_added_to_keywords(self, pytester: Pytester) -> None: + item = pytester.getitem( + """ + import pytest + pytestmark = pytest.mark.foo + class TestClass: + pytestmark = pytest.mark.bar + def test_method(self): pass + test_method.pytestmark = pytest.mark.baz + """, + "test_method", + ) + assert isinstance(item, pytest.Function) + cls = item.getparent(pytest.Class) + assert cls is not None + mod = item.getparent(pytest.Module) + assert mod is not None + + assert item.keywords["foo"] == pytest.mark.foo.mark + assert item.keywords["bar"] == pytest.mark.bar.mark + assert item.keywords["baz"] == pytest.mark.baz.mark + + assert cls.keywords["foo"] == pytest.mark.foo.mark + assert cls.keywords["bar"] == pytest.mark.bar.mark + assert "baz" not in cls.keywords + + assert mod.keywords["foo"] == pytest.mark.foo.mark + assert "bar" not in mod.keywords + assert "baz" not in mod.keywords + + +class TestCollectDirectoryHook: + def test_custom_directory_example(self, pytester: Pytester) -> None: + """Verify the example from the customdirectory.rst doc.""" + pytester.copy_example("customdirectory") + + reprec = pytester.inline_run() + + reprec.assertoutcome(passed=2, failed=0) + calls = reprec.getcalls("pytest_collect_directory") + assert len(calls) == 2 + assert calls[0].path == pytester.path + assert isinstance(calls[0].parent, pytest.Session) + assert calls[1].path == pytester.path / "tests" + assert isinstance(calls[1].parent, pytest.Dir) + + def test_directory_ignored_if_none(self, pytester: Pytester) -> None: + """If the (entire) hook returns None, it's OK, the directory is ignored.""" + pytester.makeconftest( + """ + import pytest + + @pytest.hookimpl(wrapper=True) + def pytest_collect_directory(): + yield + return None + """, + ) + pytester.makepyfile( + **{ + "tests/test_it.py": """ + import pytest + + def test_it(): pass + """, + }, + ) + reprec = pytester.inline_run() + reprec.assertoutcome(passed=0, failed=0) + COLLECTION_ERROR_PY_FILES = dict( test_01_failure=""" @@ -1011,13 +1168,18 @@ def test_fixture_scope_sibling_conftests(pytester: Pytester) -> None: def fix(): return 1 """ - ) + ), + encoding="utf-8", + ) + foo_path.joinpath("test_foo.py").write_text( + "def test_foo(fix): assert fix == 1", encoding="utf-8" ) - foo_path.joinpath("test_foo.py").write_text("def test_foo(fix): assert fix == 1") # Tests in `food/` should not see the conftest fixture from `foo/` food_path = pytester.mkpydir("food") - food_path.joinpath("test_food.py").write_text("def test_food(fix): assert fix == 1") + food_path.joinpath("test_food.py").write_text( + "def test_food(fix): assert fix == 1", encoding="utf-8" + ) res = pytester.runpytest() assert res.ret == 1 @@ -1038,22 +1200,24 @@ def test_collect_init_tests(pytester: Pytester) -> None: result.stdout.fnmatch_lines( [ "collected 2 items", - "", - " ", - " ", - " ", - " ", + "", + " ", + " ", + " ", + " ", + " ", ] ) result = pytester.runpytest("./tests", "--collect-only") result.stdout.fnmatch_lines( [ "collected 2 items", - "", - " ", - " ", - " ", - " ", + "", + " ", + " ", + " ", + " ", + " ", ] ) # Ignores duplicates with "." and pkginit (#4310). @@ -1061,11 +1225,12 @@ def test_collect_init_tests(pytester: Pytester) -> None: result.stdout.fnmatch_lines( [ "collected 2 items", - "", - " ", - " ", - " ", - " ", + "", + " ", + " ", + " ", + " ", + " ", ] ) # Same as before, but different order. @@ -1073,21 +1238,32 @@ def test_collect_init_tests(pytester: Pytester) -> None: result.stdout.fnmatch_lines( [ "collected 2 items", - "", - " ", - " ", - " ", - " ", + "", + " ", + " ", + " ", + " ", + " ", ] ) result = pytester.runpytest("./tests/test_foo.py", "--collect-only") result.stdout.fnmatch_lines( - ["", " ", " "] + [ + "", + " ", + " ", + " ", + ] ) result.stdout.no_fnmatch_line("*test_init*") result = pytester.runpytest("./tests/__init__.py", "--collect-only") result.stdout.fnmatch_lines( - ["", " ", " "] + [ + "", + " ", + " ", + " ", + ] ) result.stdout.no_fnmatch_line("*test_foo*") @@ -1143,23 +1319,21 @@ def test_collect_with_chdir_during_import(pytester: Pytester) -> None: subdir = pytester.mkdir("sub") pytester.path.joinpath("conftest.py").write_text( textwrap.dedent( - """ + f""" import os - os.chdir(%r) + os.chdir({str(subdir)!r}) """ - % (str(subdir),) - ) + ), + encoding="utf-8", ) pytester.makepyfile( - """ + f""" def test_1(): import os - assert os.getcwd() == %r + assert os.getcwd() == {str(subdir)!r} """ - % (str(subdir),) ) - with pytester.path.cwd(): - result = pytester.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 passed in*"]) assert result.ret == 0 @@ -1170,8 +1344,7 @@ def test_1(): testpaths = . """ ) - with pytester.path.cwd(): - result = pytester.runpytest("--collect-only") + result = pytester.runpytest("--collect-only") result.stdout.fnmatch_lines(["collected 1 item"]) @@ -1180,8 +1353,12 @@ def test_collect_pyargs_with_testpaths( ) -> None: testmod = pytester.mkdir("testmod") # NOTE: __init__.py is not collected since it does not match python_files. - testmod.joinpath("__init__.py").write_text("def test_func(): pass") - testmod.joinpath("test_file.py").write_text("def test_func(): pass") + testmod.joinpath("__init__.py").write_text( + "def test_func(): pass", encoding="utf-8" + ) + testmod.joinpath("test_file.py").write_text( + "def test_func(): pass", encoding="utf-8" + ) root = pytester.mkdir("root") root.joinpath("pytest.ini").write_text( @@ -1191,14 +1368,66 @@ def test_collect_pyargs_with_testpaths( addopts = --pyargs testpaths = testmod """ - ) + ), + encoding="utf-8", ) monkeypatch.setenv("PYTHONPATH", str(pytester.path), prepend=os.pathsep) - with root.cwd(): + with monkeypatch.context() as mp: + mp.chdir(root) result = pytester.runpytest_subprocess() result.stdout.fnmatch_lines(["*1 passed in*"]) +def test_initial_conftests_with_testpaths(pytester: Pytester) -> None: + """The testpaths ini option should load conftests in those paths as 'initial' (#10987).""" + p = pytester.mkdir("some_path") + p.joinpath("conftest.py").write_text( + textwrap.dedent( + """ + def pytest_sessionstart(session): + raise Exception("pytest_sessionstart hook successfully run") + """ + ), + encoding="utf-8", + ) + pytester.makeini( + """ + [pytest] + testpaths = some_path + """ + ) + + # No command line args - falls back to testpaths. + result = pytester.runpytest() + assert result.ret == ExitCode.INTERNAL_ERROR + result.stdout.fnmatch_lines( + "INTERNALERROR* Exception: pytest_sessionstart hook successfully run" + ) + + # No fallback. + result = pytester.runpytest(".") + assert result.ret == ExitCode.NO_TESTS_COLLECTED + + +def test_large_option_breaks_initial_conftests(pytester: Pytester) -> None: + """Long option values do not break initial conftests handling (#10169).""" + option_value = "x" * 1024 * 1000 + pytester.makeconftest( + """ + def pytest_addoption(parser): + parser.addoption("--xx", default=None) + """ + ) + pytester.makepyfile( + f""" + def test_foo(request): + assert request.config.getoption("xx") == {option_value!r} + """ + ) + result = pytester.runpytest(f"--xx={option_value}") + assert result.ret == 0 + + def test_collect_symlink_file_arg(pytester: Pytester) -> None: """Collect a direct symlink works even if it does not match python_files (#4325).""" real = pytester.makepyfile( @@ -1226,6 +1455,7 @@ def test_nodeid(request): assert request.node.nodeid == "test_real.py::test_nodeid" """ ), + encoding="utf-8", ) out_of_tree = pytester.mkdir("out_of_tree") @@ -1254,12 +1484,16 @@ def test_collect_symlink_dir(pytester: Pytester) -> None: def test_collectignore_via_conftest(pytester: Pytester) -> None: """collect_ignore in parent conftest skips importing child (issue #4592).""" tests = pytester.mkpydir("tests") - tests.joinpath("conftest.py").write_text("collect_ignore = ['ignore_me']") + tests.joinpath("conftest.py").write_text( + "collect_ignore = ['ignore_me']", encoding="utf-8" + ) ignore_me = tests.joinpath("ignore_me") ignore_me.mkdir() ignore_me.joinpath("__init__.py").touch() - ignore_me.joinpath("conftest.py").write_text("assert 0, 'should_not_be_called'") + ignore_me.joinpath("conftest.py").write_text( + "assert 0, 'should_not_be_called'", encoding="utf-8" + ) result = pytester.runpytest() assert result.ret == ExitCode.NO_TESTS_COLLECTED @@ -1268,23 +1502,31 @@ def test_collectignore_via_conftest(pytester: Pytester) -> None: def test_collect_pkg_init_and_file_in_args(pytester: Pytester) -> None: subdir = pytester.mkdir("sub") init = subdir.joinpath("__init__.py") - init.write_text("def test_init(): pass") + init.write_text("def test_init(): pass", encoding="utf-8") p = subdir.joinpath("test_file.py") - p.write_text("def test_file(): pass") + p.write_text("def test_file(): pass", encoding="utf-8") - # NOTE: without "-o python_files=*.py" this collects test_file.py twice. - # This changed/broke with "Add package scoped fixtures #2283" (2b1410895) - # initially (causing a RecursionError). - result = pytester.runpytest("-v", str(init), str(p)) + # Just the package directory, the __init__.py module is filtered out. + result = pytester.runpytest("-v", subdir) result.stdout.fnmatch_lines( [ "sub/test_file.py::test_file PASSED*", + "*1 passed in*", + ] + ) + + # But it's included if specified directly. + result = pytester.runpytest("-v", init, p) + result.stdout.fnmatch_lines( + [ + "sub/__init__.py::test_init PASSED*", "sub/test_file.py::test_file PASSED*", "*2 passed in*", ] ) - result = pytester.runpytest("-v", "-o", "python_files=*.py", str(init), str(p)) + # Or if the pattern allows it. + result = pytester.runpytest("-v", "-o", "python_files=*.py", subdir) result.stdout.fnmatch_lines( [ "sub/__init__.py::test_init PASSED*", @@ -1297,12 +1539,15 @@ def test_collect_pkg_init_and_file_in_args(pytester: Pytester) -> None: def test_collect_pkg_init_only(pytester: Pytester) -> None: subdir = pytester.mkdir("sub") init = subdir.joinpath("__init__.py") - init.write_text("def test_init(): pass") + init.write_text("def test_init(): pass", encoding="utf-8") - result = pytester.runpytest(str(init)) + result = pytester.runpytest(subdir) result.stdout.fnmatch_lines(["*no tests ran in*"]) - result = pytester.runpytest("-v", "-o", "python_files=*.py", str(init)) + result = pytester.runpytest("-v", init) + result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"]) + + result = pytester.runpytest("-v", "-o", "python_files=*.py", subdir) result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"]) @@ -1312,7 +1557,7 @@ def test_collect_sub_with_symlinks(use_pkg: bool, pytester: Pytester) -> None: sub = pytester.mkdir("sub") if use_pkg: sub.joinpath("__init__.py").touch() - sub.joinpath("test_file.py").write_text("def test_file(): pass") + sub.joinpath("test_file.py").write_text("def test_file(): pass", encoding="utf-8") # Create a broken symlink. symlink_or_skip("test_doesnotexist.py", sub.joinpath("test_broken.py")) @@ -1350,7 +1595,7 @@ def test_collector_respects_tbstyle(pytester: Pytester) -> None: def test_does_not_eagerly_collect_packages(pytester: Pytester) -> None: pytester.makepyfile("def test(): pass") pydir = pytester.mkpydir("foopkg") - pydir.joinpath("__init__.py").write_text("assert False") + pydir.joinpath("__init__.py").write_text("assert False", encoding="utf-8") result = pytester.runpytest() assert result.ret == ExitCode.OK @@ -1379,13 +1624,16 @@ def __init__(self, *k, x, **kw): super().__init__(*k, **kw) self.x = x + def collect(self): + raise NotImplementedError() + collector = MyCollector.from_parent( parent=request.session, path=pytester.path / "foo", x=10 ) assert collector.x == 10 -def test_class_from_parent(pytester: Pytester, request: FixtureRequest) -> None: +def test_class_from_parent(request: FixtureRequest) -> None: """Ensure Class.from_parent can forward custom arguments to the constructor.""" class MyCollector(pytest.Class): @@ -1426,13 +1674,11 @@ def test_conftest(self, pytester: Pytester) -> None: pytester.makepyfile( **{ "tests/conftest.py": "", - "tests/test_foo.py": """ + "tests/test_foo.py": f""" import sys def test_check(): assert r"{tests_dir}" not in sys.path - """.format( - tests_dir=tests_dir - ), + """, } ) result = pytester.runpytest("-v", "--import-mode=importlib") @@ -1477,6 +1723,35 @@ def test_modules_not_importable_as_side_effect(self, pytester: Pytester) -> None ] ) + def test_using_python_path(self, pytester: Pytester) -> None: + """ + Dummy modules created by insert_missing_modules should not get in + the way of modules that could be imported via python path (#9645). + """ + pytester.makeini( + """ + [pytest] + pythonpath = . + addopts = --import-mode importlib + """ + ) + pytester.makepyfile( + **{ + "tests/__init__.py": "", + "tests/conftest.py": "", + "tests/subpath/__init__.py": "", + "tests/subpath/helper.py": "", + "tests/subpath/test_something.py": """ + import tests.subpath.helper + + def test_something(): + assert True + """, + } + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines("*1 passed in*") + def test_does_not_crash_on_error_from_decorated_function(pytester: Pytester) -> None: """Regression test for an issue around bad exception formatting due to @@ -1504,3 +1779,129 @@ def test_foo(): assert True assert result.ret == ExitCode.OK assert result.parseoutcomes() == {"passed": 1} + + +@pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows only") +def test_collect_short_file_windows(pytester: Pytester) -> None: + """Reproducer for #11895: short paths not collected on Windows.""" + short_path = tempfile.mkdtemp() + if "~" not in short_path: # pragma: no cover + if running_on_ci(): + # On CI, we are expecting that under the current GitHub actions configuration, + # tempfile.mkdtemp() is producing short paths, so we want to fail to prevent + # this from silently changing without us noticing. + pytest.fail( + f"tempfile.mkdtemp() failed to produce a short path on CI: {short_path}" + ) + else: + # We want to skip failing this test locally in this situation because + # depending on the local configuration tempfile.mkdtemp() might not produce a short path: + # For example, user might have configured %TEMP% exactly to avoid generating short paths. + pytest.skip( + f"tempfile.mkdtemp() failed to produce a short path: {short_path}, skipping" + ) + + test_file = Path(short_path).joinpath("test_collect_short_file_windows.py") + test_file.write_text("def test(): pass", encoding="UTF-8") + result = pytester.runpytest(short_path) + assert result.parseoutcomes() == {"passed": 1} + + +def test_pyargs_collection_tree(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: + """When using `--pyargs`, the collection tree of a pyargs collection + argument should only include parents in the import path, not up to confcutdir. + + Regression test for #11904. + """ + site_packages = pytester.path / "venv/lib/site-packages" + site_packages.mkdir(parents=True) + monkeypatch.syspath_prepend(site_packages) + pytester.makepyfile( + **{ + "venv/lib/site-packages/pkg/__init__.py": "", + "venv/lib/site-packages/pkg/sub/__init__.py": "", + "venv/lib/site-packages/pkg/sub/test_it.py": "def test(): pass", + } + ) + + result = pytester.runpytest("--pyargs", "--collect-only", "pkg.sub.test_it") + assert result.ret == ExitCode.OK + result.stdout.fnmatch_lines( + [ + "", + " ", + " ", + " ", + ], + consecutive=True, + ) + + # Now with an unrelated rootdir with unrelated files. + monkeypatch.chdir(tempfile.gettempdir()) + + result = pytester.runpytest("--pyargs", "--collect-only", "pkg.sub.test_it") + assert result.ret == ExitCode.OK + result.stdout.fnmatch_lines( + [ + "", + " ", + " ", + " ", + ], + consecutive=True, + ) + + +def test_do_not_collect_symlink_siblings( + pytester: Pytester, tmp_path: Path, request: pytest.FixtureRequest +) -> None: + """ + Regression test for #12039: Do not collect from directories that are symlinks to other directories in the same path. + + The check for short paths under Windows via os.path.samefile, introduced in #11936, also finds the symlinked + directory created by tmp_path/tmpdir. + """ + # Use tmp_path because it creates a symlink with the name "current" next to the directory it creates. + symlink_path = tmp_path.parent / (tmp_path.name[:-1] + "current") + assert symlink_path.is_symlink() is True + + # Create test file. + tmp_path.joinpath("test_foo.py").write_text("def test(): pass", encoding="UTF-8") + + # Ensure we collect it only once if we pass the tmp_path. + result = pytester.runpytest(tmp_path, "-sv") + result.assert_outcomes(passed=1) + + # Ensure we collect it only once if we pass the symlinked directory. + result = pytester.runpytest(symlink_path, "-sv") + result.assert_outcomes(passed=1) + + +@pytest.mark.parametrize( + "exception_class, msg", + [ + (KeyboardInterrupt, "*!!! KeyboardInterrupt !!!*"), + (SystemExit, "INTERNALERROR> SystemExit"), + ], +) +def test_respect_system_exceptions( + pytester: Pytester, + exception_class: Type[BaseException], + msg: str, +): + head = "Before exception" + tail = "After exception" + ensure_file(pytester.path / "test_eggs.py").write_text( + f"print('{head}')", encoding="UTF-8" + ) + ensure_file(pytester.path / "test_ham.py").write_text( + f"raise {exception_class.__name__}()", encoding="UTF-8" + ) + ensure_file(pytester.path / "test_spam.py").write_text( + f"print('{tail}')", encoding="UTF-8" + ) + + result = pytester.runpytest_subprocess("-s") + result.stdout.fnmatch_lines([f"*{head}*"]) + result.stdout.fnmatch_lines([msg]) + result.stdout.no_fnmatch_line(f"*{tail}*") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_compat.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_compat.py index 37cf4a077d783..73ac1bad8586d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_compat.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_compat.py @@ -1,20 +1,22 @@ +# mypy: allow-untyped-defs import enum -import sys +from functools import cached_property from functools import partial from functools import wraps +import sys from typing import TYPE_CHECKING 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.outcomes import OutcomeException from _pytest.pytester import Pytester +import pytest + if TYPE_CHECKING: from typing_extensions import Literal @@ -92,7 +94,7 @@ def foo(x): assert get_real_func(partial(foo)) is foo -@pytest.mark.skipif(sys.version_info >= (3, 11), reason="couroutine removed") +@pytest.mark.skipif(sys.version_info >= (3, 11), reason="coroutine removed") def test_is_generator_asyncio(pytester: Pytester) -> None: pytester.makepyfile( """ @@ -135,7 +137,7 @@ def test_is_generator_async_gen_syntax(pytester: Pytester) -> None: pytester.makepyfile( """ from _pytest.compat import is_generator - def test_is_generator_py36(): + def test_is_generator(): async def foo(): yield await foo() @@ -167,17 +169,17 @@ def raise_fail_outcome(self): def test_helper_failures() -> None: helper = ErrorsHelper() - with pytest.raises(Exception): - helper.raise_exception + with pytest.raises(Exception): # noqa: B017 + _ = helper.raise_exception with pytest.raises(OutcomeException): - helper.raise_fail_outcome + _ = helper.raise_fail_outcome def test_safe_getattr() -> None: helper = ErrorsHelper() assert safe_getattr(helper, "raise_exception", "default") == "default" assert safe_getattr(helper, "raise_fail_outcome", "default") == "default" - with pytest.raises(BaseException): + with pytest.raises(BaseException): # noqa: B017 assert safe_getattr(helper, "raise_baseexception", "default") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_config.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_config.py index 8013966f07161..0d097f71622b2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_config.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_config.py @@ -1,8 +1,12 @@ +# mypy: allow-untyped-defs +import dataclasses +import importlib.metadata import os +from pathlib import Path import re import sys import textwrap -from pathlib import Path +from typing import Any from typing import Dict from typing import List from typing import Sequence @@ -10,11 +14,7 @@ from typing import Type from typing import Union -import attr - import _pytest._code -import pytest -from _pytest.compat import importlib_metadata from _pytest.config import _get_plugin_specs_as_list from _pytest.config import _iter_rewritable_modules from _pytest.config import _strtobool @@ -22,6 +22,8 @@ from _pytest.config import ConftestImportFailure from _pytest.config import ExitCode from _pytest.config import parse_warning_filter +from _pytest.config.argparsing import get_ini_default_for_type +from _pytest.config.argparsing import Parser from _pytest.config.exceptions import UsageError from _pytest.config.findpaths import determine_setup from _pytest.config.findpaths import get_common_ancestor @@ -29,6 +31,7 @@ from _pytest.monkeypatch import MonkeyPatch from _pytest.pathlib import absolutepath from _pytest.pytester import Pytester +import pytest class TestParseIni: @@ -48,16 +51,14 @@ def test_getcfg_and_config( monkeypatch.chdir(sub) (tmp_path / filename).write_text( textwrap.dedent( - """\ + f"""\ [{section}] name = value - """.format( - section=section - ) + """ ), encoding="utf-8", ) - _, _, cfg = locate_config([sub]) + _, _, cfg = locate_config(Path.cwd(), [sub]) assert cfg["name"] == "value" config = pytester.parseconfigure(str(sub)) assert config.inicfg["name"] == "value" @@ -75,7 +76,7 @@ def test_setupcfg_uses_toolpytest_with_pytest(self, pytester: Pytester) -> None: % p1.name, ) result = pytester.runpytest() - result.stdout.fnmatch_lines(["*, configfile: setup.cfg, *", "* 1 passed in *"]) + result.stdout.fnmatch_lines(["configfile: setup.cfg", "* 1 passed in *"]) assert result.ret == 0 def test_append_parse_args( @@ -88,7 +89,8 @@ def test_append_parse_args( [pytest] addopts = --verbose """ - ) + ), + encoding="utf-8", ) config = pytester.parseconfig(tmp_path) assert config.option.color == "no" @@ -112,32 +114,66 @@ def test_tox_ini_wrong_version(self, pytester: Pytester) -> None: @pytest.mark.parametrize( "section, name", - [("tool:pytest", "setup.cfg"), ("pytest", "tox.ini"), ("pytest", "pytest.ini")], + [ + ("tool:pytest", "setup.cfg"), + ("pytest", "tox.ini"), + ("pytest", "pytest.ini"), + ("pytest", ".pytest.ini"), + ], ) def test_ini_names(self, pytester: Pytester, name, section) -> None: pytester.path.joinpath(name).write_text( textwrap.dedent( - """ + f""" [{section}] - minversion = 1.0 - """.format( - section=section - ) - ) + minversion = 3.36 + """ + ), + encoding="utf-8", ) config = pytester.parseconfig() - assert config.getini("minversion") == "1.0" + assert config.getini("minversion") == "3.36" def test_pyproject_toml(self, pytester: Pytester) -> None: - pytester.makepyprojecttoml( + pyproject_toml = pytester.makepyprojecttoml( """ [tool.pytest.ini_options] minversion = "1.0" """ ) config = pytester.parseconfig() + assert config.inipath == pyproject_toml assert config.getini("minversion") == "1.0" + def test_empty_pyproject_toml(self, pytester: Pytester) -> None: + """An empty pyproject.toml is considered as config if no other option is found.""" + pyproject_toml = pytester.makepyprojecttoml("") + config = pytester.parseconfig() + assert config.inipath == pyproject_toml + + def test_empty_pyproject_toml_found_many(self, pytester: Pytester) -> None: + """ + In case we find multiple pyproject.toml files in our search, without a [tool.pytest.ini_options] + table and without finding other candidates, the closest to where we started wins. + """ + pytester.makefile( + ".toml", + **{ + "pyproject": "", + "foo/pyproject": "", + "foo/bar/pyproject": "", + }, + ) + config = pytester.parseconfig(pytester.path / "foo/bar") + assert config.inipath == pytester.path / "foo/bar/pyproject.toml" + + def test_pytest_ini_trumps_pyproject_toml(self, pytester: Pytester) -> None: + """A pytest.ini always take precedence over a pyproject.toml file.""" + pytester.makepyprojecttoml("[tool.pytest.ini_options]") + pytest_ini = pytester.makefile(".ini", pytest="") + config = pytester.parseconfig() + assert config.inipath == pytest_ini + def test_toxini_before_lower_pytestini(self, pytester: Pytester) -> None: sub = pytester.mkdir("sub") sub.joinpath("tox.ini").write_text( @@ -146,7 +182,8 @@ def test_toxini_before_lower_pytestini(self, pytester: Pytester) -> None: [pytest] minversion = 2.0 """ - ) + ), + encoding="utf-8", ) pytester.path.joinpath("pytest.ini").write_text( textwrap.dedent( @@ -154,16 +191,46 @@ def test_toxini_before_lower_pytestini(self, pytester: Pytester) -> None: [pytest] minversion = 1.5 """ - ) + ), + encoding="utf-8", ) config = pytester.parseconfigure(sub) assert config.getini("minversion") == "2.0" def test_ini_parse_error(self, pytester: Pytester) -> None: - pytester.path.joinpath("pytest.ini").write_text("addopts = -x") + pytester.path.joinpath("pytest.ini").write_text( + "addopts = -x", encoding="utf-8" + ) result = pytester.runpytest() assert result.ret != 0 - result.stderr.fnmatch_lines(["ERROR: *pytest.ini:1: no section header defined"]) + result.stderr.fnmatch_lines("ERROR: *pytest.ini:1: no section header defined") + + def test_toml_parse_error(self, pytester: Pytester) -> None: + pytester.makepyprojecttoml( + """ + \\" + """ + ) + result = pytester.runpytest() + assert result.ret != 0 + result.stderr.fnmatch_lines("ERROR: *pyproject.toml: Invalid statement*") + + def test_confcutdir_default_without_configfile(self, pytester: Pytester) -> None: + # If --confcutdir is not specified, and there is no configfile, default + # to the rootpath. + sub = pytester.mkdir("sub") + os.chdir(sub) + config = pytester.parseconfigure() + assert config.pluginmanager._confcutdir == sub + + def test_confcutdir_default_with_configfile(self, pytester: Pytester) -> None: + # If --confcutdir is not specified, and there is a configfile, default + # to the configfile's directory. + pytester.makeini("[pytest]") + sub = pytester.mkdir("sub") + os.chdir(sub) + config = pytester.parseconfigure() + assert config.pluginmanager._confcutdir == pytester.path @pytest.mark.xfail(reason="probably not needed") def test_confcutdir(self, pytester: Pytester) -> None: @@ -408,11 +475,11 @@ def test_missing_required_plugins( This test installs a mock "myplugin-1.5" which is used in the parametrized test cases. """ - @attr.s + @dataclasses.dataclass class DummyEntryPoint: - name = attr.ib() - module = attr.ib() - group = "pytest11" + name: str + module: str + group: str = "pytest11" def load(self): __import__(self.module) @@ -422,11 +489,11 @@ def load(self): DummyEntryPoint("myplugin1", "myplugin1_module"), ] - @attr.s + @dataclasses.dataclass class DummyDist: - entry_points = attr.ib() - files = () - version = plugin_version + entry_points: object + files: object = () + version: str = plugin_version @property def metadata(self): @@ -438,7 +505,7 @@ def my_dists(): pytester.makepyfile(myplugin1_module="# my plugin module") pytester.syspathinsert() - monkeypatch.setattr(importlib_metadata, "distributions", my_dists) + monkeypatch.setattr(importlib.metadata, "distributions", my_dists) monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False) pytester.makeini(ini_file_text) @@ -470,6 +537,24 @@ def pytest_load_initial_conftests(early_config, parser, args): result = pytester.runpytest("--foo=1") result.stdout.fnmatch_lines("* no tests ran in *") + def test_args_source_args(self, pytester: Pytester): + config = pytester.parseconfig("--", "test_filename.py") + assert config.args_source == Config.ArgsSource.ARGS + + def test_args_source_invocation_dir(self, pytester: Pytester): + config = pytester.parseconfig() + assert config.args_source == Config.ArgsSource.INVOCATION_DIR + + def test_args_source_testpaths(self, pytester: Pytester): + pytester.makeini( + """ + [pytest] + testpaths=* + """ + ) + config = pytester.parseconfig() + assert config.args_source == Config.ArgsSource.TESTPATHS + class TestConfigCmdlineParsing: def test_parsing_again_fails(self, pytester: Pytester) -> None: @@ -500,6 +585,8 @@ def pytest_addoption(parser): ) config = pytester.parseconfig("-c", "custom.ini") assert config.getini("custom") == "1" + config = pytester.parseconfig("--config-file", "custom.ini") + assert config.getini("custom") == "1" pytester.makefile( ".cfg", @@ -510,6 +597,8 @@ def pytest_addoption(parser): ) config = pytester.parseconfig("-c", "custom_tool_pytest_section.cfg") assert config.getini("custom") == "1" + config = pytester.parseconfig("--config-file", "custom_tool_pytest_section.cfg") + assert config.getini("custom") == "1" pytester.makefile( ".toml", @@ -522,6 +611,8 @@ def pytest_addoption(parser): ) config = pytester.parseconfig("-c", "custom.toml") assert config.getini("custom") == "1" + config = pytester.parseconfig("--config-file", "custom.toml") + assert config.getini("custom") == "1" def test_absolute_win32_path(self, pytester: Pytester) -> None: temp_ini_file = pytester.makefile( @@ -536,6 +627,8 @@ def test_absolute_win32_path(self, pytester: Pytester) -> None: temp_ini_file_norm = normpath(str(temp_ini_file)) ret = pytest.main(["-c", temp_ini_file_norm]) assert ret == ExitCode.OK + ret = pytest.main(["--config-file", temp_ini_file_norm]) + assert ret == ExitCode.OK class TestConfigAPI: @@ -595,20 +688,13 @@ def test_getoption(self, pytester: Pytester) -> None: def test_getconftest_pathlist(self, pytester: Pytester, tmp_path: Path) -> None: somepath = tmp_path.joinpath("x", "y", "z") p = tmp_path.joinpath("conftest.py") - p.write_text(f"mylist = {['.', str(somepath)]}") + p.write_text(f"mylist = {['.', str(somepath)]}", encoding="utf-8") config = pytester.parseconfigure(p) - assert ( - config._getconftest_pathlist("notexist", path=tmp_path, rootpath=tmp_path) - is None - ) - pl = ( - config._getconftest_pathlist("mylist", path=tmp_path, rootpath=tmp_path) - or [] - ) - print(pl) - assert len(pl) == 2 - assert pl[0] == tmp_path - assert pl[1] == somepath + assert config._getconftest_pathlist("notexist", path=tmp_path) is None + assert config._getconftest_pathlist("mylist", path=tmp_path) == [ + tmp_path, + somepath, + ] @pytest.mark.parametrize("maybe_type", ["not passed", "None", '"string"']) def test_addini(self, pytester: Pytester, maybe_type: str) -> None: @@ -801,6 +887,67 @@ def pytest_addoption(parser): assert len(values) == 2 assert values == ["456", "123"] + def test_addini_default_values(self, pytester: Pytester) -> None: + """Tests the default values for configuration based on + config type + """ + pytester.makeconftest( + """ + def pytest_addoption(parser): + parser.addini("linelist1", "", type="linelist") + parser.addini("paths1", "", type="paths") + parser.addini("pathlist1", "", type="pathlist") + parser.addini("args1", "", type="args") + parser.addini("bool1", "", type="bool") + parser.addini("string1", "", type="string") + parser.addini("none_1", "", type="linelist", default=None) + parser.addini("none_2", "", default=None) + parser.addini("no_type", "") + """ + ) + + config = pytester.parseconfig() + # default for linelist, paths, pathlist and args is [] + value = config.getini("linelist1") + assert value == [] + value = config.getini("paths1") + assert value == [] + value = config.getini("pathlist1") + assert value == [] + value = config.getini("args1") + assert value == [] + # default for bool is False + value = config.getini("bool1") + assert value is False + # default for string is "" + value = config.getini("string1") + assert value == "" + # should return None if None is explicity set as default value + # irrespective of the type argument + value = config.getini("none_1") + assert value is None + value = config.getini("none_2") + assert value is None + # in case no type is provided and no default set + # treat it as string and default value will be "" + value = config.getini("no_type") + assert value == "" + + @pytest.mark.parametrize( + "type, expected", + [ + pytest.param(None, "", id="None"), + pytest.param("string", "", id="string"), + pytest.param("paths", [], id="paths"), + pytest.param("pathlist", [], id="pathlist"), + pytest.param("args", [], id="args"), + pytest.param("linelist", [], id="linelist"), + pytest.param("bool", False, id="bool"), + ], + ) + def test_get_ini_default_for_type(self, type: Any, expected: Any) -> None: + assert get_ini_default_for_type(type) == expected + def test_confcutdir_check_isdir(self, pytester: Pytester) -> None: """Give an error if --confcutdir is not a valid directory (#2078)""" exp_match = r"^--confcutdir must be a directory, given: " @@ -827,6 +974,9 @@ def test_confcutdir_check_isdir(self, pytester: Pytester) -> None: (["src/bar/__init__.py"], ["bar"]), (["src/bar/__init__.py", "setup.py"], ["bar"]), (["source/python/bar/__init__.py", "setup.py"], ["bar"]), + # editable installation finder modules + (["__editable___xyz_finder.py"], []), + (["bar/__init__.py", "__editable___xyz_finder.py"], ["bar"]), ], ) def test_iter_rewritable_modules(self, names, expected) -> None: @@ -868,7 +1018,8 @@ def test_inifilename(self, tmp_path: Path) -> None: [pytest] name = value """ - ) + ), + encoding="utf-8", ) inifilename = "../../foo/bar.ini" @@ -885,7 +1036,8 @@ def test_inifilename(self, tmp_path: Path) -> None: name = wrong-value should_not_be_set = true """ - ) + ), + encoding="utf-8", ) with MonkeyPatch.context() as mp: mp.chdir(cwd) @@ -953,7 +1105,7 @@ class Dist: def my_dists(): return (Dist,) - monkeypatch.setattr(importlib_metadata, "distributions", my_dists) + monkeypatch.setattr(importlib.metadata, "distributions", my_dists) pytester.makeconftest( """ pytest_plugins = "mytestplugin", @@ -986,7 +1138,7 @@ class Distribution: def distributions(): return (Distribution(),) - monkeypatch.setattr(importlib_metadata, "distributions", distributions) + monkeypatch.setattr(importlib.metadata, "distributions", distributions) with pytest.raises(ImportError): pytester.parseconfig() @@ -1013,7 +1165,7 @@ class Distribution: def distributions(): return (Distribution(),) - monkeypatch.setattr(importlib_metadata, "distributions", distributions) + monkeypatch.setattr(importlib.metadata, "distributions", distributions) pytester.parseconfig() @@ -1041,7 +1193,7 @@ class Distribution: def distributions(): return (Distribution(),) - monkeypatch.setattr(importlib_metadata, "distributions", distributions) + monkeypatch.setattr(importlib.metadata, "distributions", distributions) args = ("-p", "no:mytestplugin") if block_it else () config = pytester.parseconfig(*args) config.pluginmanager.import_plugin("mytestplugin") @@ -1090,8 +1242,8 @@ def distributions(): return (Distribution(),) monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1") - monkeypatch.setattr(importlib_metadata, "distributions", distributions) - monkeypatch.setitem(sys.modules, "mytestplugin", PseudoPlugin()) # type: ignore[misc] + monkeypatch.setattr(importlib.metadata, "distributions", distributions) + monkeypatch.setitem(sys.modules, "mytestplugin", PseudoPlugin()) config = pytester.parseconfig(*parse_args) has_loaded = config.pluginmanager.get_plugin("mytestplugin") is not None assert has_loaded == should_load @@ -1109,8 +1261,7 @@ def test_terminal_plugin(request): import myplugin assert myplugin.terminal_plugin == [False, True] """, - **{ - "myplugin": """ + myplugin=""" terminal_plugin = [] def pytest_configure(config): @@ -1119,25 +1270,13 @@ def pytest_configure(config): def pytest_sessionstart(session): config = session.config terminal_plugin.append(bool(config.pluginmanager.get_plugin("terminalreporter"))) - """ - }, + """, ) pytester.syspathinsert() result = pytester.runpytest("-p", "myplugin", str(p1)) assert result.ret == 0 -def test_cmdline_processargs_simple(pytester: Pytester) -> None: - pytester.makeconftest( - """ - def pytest_cmdline_preparse(args): - args.append("-h") - """ - ) - result = pytester.runpytest() - result.stdout.fnmatch_lines(["*pytest*", "*-h*"]) - - def test_invalid_options_show_extra_information(pytester: Pytester) -> None: """Display extra information when pytest exits due to unrecognized options in the command-line.""" @@ -1267,7 +1406,7 @@ def pytest_load_initial_conftests(self): hookimpls = [ ( hookimpl.function.__module__, - "wrapper" if hookimpl.hookwrapper else "nonwrapper", + "wrapper" if (hookimpl.wrapper or hookimpl.hookwrapper) else "nonwrapper", ) for hookimpl in hc.get_hookimpls() ] @@ -1321,16 +1460,16 @@ class pytest_something: class TestRootdir: def test_simple_noini(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None: - assert get_common_ancestor([tmp_path]) == tmp_path + assert get_common_ancestor(Path.cwd(), [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 + assert get_common_ancestor(Path.cwd(), [a, tmp_path]) == tmp_path + assert get_common_ancestor(Path.cwd(), [tmp_path, a]) == tmp_path monkeypatch.chdir(tmp_path) - assert get_common_ancestor([]) == tmp_path + assert get_common_ancestor(Path.cwd(), []) == 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 + assert get_common_ancestor(Path.cwd(), [no_path]) == tmp_path + assert get_common_ancestor(Path.cwd(), [no_path / "a"]) == tmp_path @pytest.mark.parametrize( "name, contents", @@ -1345,17 +1484,27 @@ def test_simple_noini(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None: ) def test_with_ini(self, tmp_path: Path, name: str, contents: str) -> None: inipath = tmp_path / name - inipath.write_text(contents, "utf-8") + inipath.write_text(contents, encoding="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) + rootpath, parsed_inipath, _ = determine_setup( + inifile=None, + args=args, + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert parsed_inipath == inipath - rootpath, parsed_inipath, ini_config = determine_setup(None, [str(b), str(a)]) + rootpath, parsed_inipath, ini_config = determine_setup( + inifile=None, + args=[str(b), str(a)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert parsed_inipath == inipath assert ini_config == {"x": "10"} @@ -1367,7 +1516,12 @@ def test_pytestini_overrides_empty_other(self, tmp_path: Path, name: str) -> Non a = tmp_path / "a" a.mkdir() (a / name).touch() - rootpath, parsed_inipath, _ = determine_setup(None, [str(a)]) + rootpath, parsed_inipath, _ = determine_setup( + inifile=None, + args=[str(a)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert parsed_inipath == inipath @@ -1376,14 +1530,24 @@ def test_setuppy_fallback(self, tmp_path: Path) -> None: a.mkdir() (a / "setup.cfg").touch() (tmp_path / "setup.py").touch() - rootpath, inipath, inicfg = determine_setup(None, [str(a)]) + rootpath, inipath, inicfg = determine_setup( + inifile=None, + args=[str(a)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert inipath is None assert inicfg == {} def test_nothing(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None: monkeypatch.chdir(tmp_path) - rootpath, inipath, inicfg = determine_setup(None, [str(tmp_path)]) + rootpath, inipath, inicfg = determine_setup( + inifile=None, + args=[str(tmp_path)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert inipath is None assert inicfg == {} @@ -1404,8 +1568,13 @@ def test_with_specific_inifile( ) -> None: p = tmp_path / name p.touch() - p.write_text(contents, "utf-8") - rootpath, inipath, ini_config = determine_setup(str(p), [str(tmp_path)]) + p.write_text(contents, encoding="utf-8") + rootpath, inipath, ini_config = determine_setup( + inifile=str(p), + args=[str(tmp_path)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert inipath == p assert ini_config == {"x": "10"} @@ -1419,14 +1588,24 @@ def test_explicit_config_file_sets_rootdir( monkeypatch.chdir(tmp_path) # No config file is explicitly given: rootdir is determined to be cwd. - rootpath, found_inipath, *_ = determine_setup(None, [str(tests_dir)]) + rootpath, found_inipath, *_ = determine_setup( + inifile=None, + args=[str(tests_dir)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert found_inipath is None # Config file is explicitly given: rootdir is determined to be inifile's directory. inipath = tmp_path / "pytest.ini" inipath.touch() - rootpath, found_inipath, *_ = determine_setup(str(inipath), [str(tests_dir)]) + rootpath, found_inipath, *_ = determine_setup( + inifile=str(inipath), + args=[str(tests_dir)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert found_inipath == inipath @@ -1438,7 +1617,12 @@ def test_with_arg_outside_cwd_without_inifile( a.mkdir() b = tmp_path / "b" b.mkdir() - rootpath, inifile, _ = determine_setup(None, [str(a), str(b)]) + rootpath, inifile, _ = determine_setup( + inifile=None, + args=[str(a), str(b)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert inifile is None @@ -1449,7 +1633,12 @@ def test_with_arg_outside_cwd_with_inifile(self, tmp_path: Path) -> None: b.mkdir() inipath = a / "pytest.ini" inipath.touch() - rootpath, parsed_inipath, _ = determine_setup(None, [str(a), str(b)]) + rootpath, parsed_inipath, _ = determine_setup( + inifile=None, + args=[str(a), str(b)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == a assert inipath == parsed_inipath @@ -1458,7 +1647,12 @@ 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) + rootpath, inipath, _ = determine_setup( + inifile=None, + args=dirs, + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert inipath is None @@ -1469,7 +1663,12 @@ def test_with_existing_file_in_subdir( a.mkdir() (a / "exists").touch() monkeypatch.chdir(tmp_path) - rootpath, inipath, _ = determine_setup(None, ["a/exist"]) + rootpath, inipath, _ = determine_setup( + inifile=None, + args=["a/exist"], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert inipath is None @@ -1483,7 +1682,12 @@ def test_with_config_also_in_parent_directory( (tmp_path / "myproject" / "tests").mkdir() monkeypatch.chdir(tmp_path / "myproject") - rootpath, inipath, _ = determine_setup(None, ["tests/"]) + rootpath, inipath, _ = determine_setup( + inifile=None, + args=["tests/"], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path / "myproject" assert inipath == tmp_path / "myproject" / "setup.cfg" @@ -1495,12 +1699,11 @@ def test_override_ini_names(self, pytester: Pytester, name: str) -> None: section = "[pytest]" if name != "setup.cfg" else "[tool:pytest]" pytester.path.joinpath(name).write_text( textwrap.dedent( - """ + f""" {section} - custom = 1.0""".format( - section=section - ) - ) + custom = 1.0""" + ), + encoding="utf-8", ) pytester.makeconftest( """ @@ -1537,7 +1740,7 @@ def pytest_addoption(parser): ) pytester.makepyfile( r""" - def test_overriden(pytestconfig): + def test_overridden(pytestconfig): config_paths = pytestconfig.getini("paths") print(config_paths) for cpf in config_paths: @@ -1663,8 +1866,8 @@ def test_addopts_from_ini_not_concatenated(self, pytester: Pytester) -> None: result = pytester.runpytest("cache_dir=ignored") result.stderr.fnmatch_lines( [ - "%s: error: argument -o/--override-ini: expected one argument (via addopts config)" - % (pytester._request.config._parser.optparser.prog,) + f"{pytester._request.config._parser.optparser.prog}: error: " + f"argument -o/--override-ini: expected one argument (via addopts config)" ] ) assert result.ret == _pytest.config.ExitCode.USAGE_ERROR @@ -1701,6 +1904,18 @@ def test(): assert "ERROR:" not in result.stderr.str() result.stdout.fnmatch_lines(["collected 1 item", "*= 1 passed in *="]) + def test_override_ini_without_config_file(self, pytester: Pytester) -> None: + pytester.makepyfile(**{"src/override_ini_without_config_file.py": ""}) + pytester.makepyfile( + **{ + "tests/test_override_ini_without_config_file.py": ( + "import override_ini_without_config_file\ndef test(): pass" + ), + } + ) + result = pytester.runpytest("--override-ini", "pythonpath=src") + assert result.parseoutcomes() == {"passed": 1} + def test_help_via_addopts(pytester: Pytester) -> None: pytester.makeini( @@ -1752,8 +1967,8 @@ def pytest_addoption(parser): result.stderr.fnmatch_lines( [ "ERROR: usage: *", - "%s: error: argument --invalid-option-should-allow-for-help: expected one argument" - % (pytester._request.config._parser.optparser.prog,), + f"{pytester._request.config._parser.optparser.prog}: error: " + f"argument --invalid-option-should-allow-for-help: expected one argument", ] ) # Does not display full/default help. @@ -1791,6 +2006,10 @@ def test_config_does_not_load_blocked_plugin_from_args(pytester: Pytester) -> No result.stderr.fnmatch_lines(["*: error: unrecognized arguments: -s"]) assert result.ret == ExitCode.USAGE_ERROR + result = pytester.runpytest(str(p), "-p no:capture", "-s") + result.stderr.fnmatch_lines(["*: error: unrecognized arguments: -s"]) + assert result.ret == ExitCode.USAGE_ERROR + def test_invocation_args(pytester: Pytester) -> None: """Ensure that Config.invocation_* arguments are correctly defined""" @@ -1828,16 +2047,6 @@ class DummyPlugin: ], ) def test_config_blocked_default_plugins(pytester: Pytester, plugin: str) -> None: - if plugin == "debugging": - # Fixed in xdist (after 1.27.0). - # https://github.com/pytest-dev/pytest-xdist/pull/422 - try: - import xdist # noqa: F401 - except ImportError: - pass - else: - pytest.skip("does not work with xdist currently") - p = pytester.makepyfile("def test(): pass") result = pytester.runpytest(str(p), "-pno:%s" % plugin) @@ -1846,7 +2055,7 @@ def test_config_blocked_default_plugins(pytester: Pytester, plugin: str) -> None result.stderr.fnmatch_lines( [ "ERROR: not found: */test_config_blocked_default_plugins.py", - "(no name '*/test_config_blocked_default_plugins.py' in any of [])", + "(no match in any of **", ] ) return @@ -1887,6 +2096,9 @@ def test_pytest_custom_cfg_unsupported(self, pytester: Pytester) -> None: with pytest.raises(pytest.fail.Exception): pytester.runpytest("-c", "custom.cfg") + with pytest.raises(pytest.fail.Exception): + pytester.runpytest("--config-file", "custom.cfg") + class TestPytestPluginsVariable: def test_pytest_plugins_in_non_top_level_conftest_unsupported( @@ -1915,7 +2127,6 @@ def test_pytest_plugins_in_non_top_level_conftest_unsupported_pyargs( self, pytester: Pytester, use_pyargs: bool ) -> None: """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": "", @@ -1930,9 +2141,7 @@ def test_pytest_plugins_in_non_top_level_conftest_unsupported_pyargs( args = ("--pyargs", "pkg") if use_pyargs else () res = pytester.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" + 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: @@ -1995,9 +2204,7 @@ def test_conftest_import_error_repr(tmp_path: Path) -> None: 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 + raise ConftestImportFailure(path, cause=exc) from exc def test_strtobool() -> None: @@ -2108,8 +2315,81 @@ def test_debug_help(self, pytester: Pytester) -> None: result = pytester.runpytest("-h") result.stdout.fnmatch_lines( [ - "*store internal tracing debug information in this log*", - "*This file is opened with 'w' and truncated as a result*", - "*Defaults to 'pytestdebug.log'.", + "*Store internal tracing debug information in this log*", + "*file. This file is opened with 'w' and truncated as a*", + "*Default: pytestdebug.log.", ] ) + + +class TestVerbosity: + SOME_OUTPUT_TYPE = Config.VERBOSITY_ASSERTIONS + SOME_OUTPUT_VERBOSITY_LEVEL = 5 + + class VerbosityIni: + def pytest_addoption(self, parser: Parser) -> None: + Config._add_verbosity_ini( + parser, TestVerbosity.SOME_OUTPUT_TYPE, help="some help text" + ) + + def test_level_matches_verbose_when_not_specified( + self, pytester: Pytester, tmp_path: Path + ) -> None: + tmp_path.joinpath("pytest.ini").write_text( + textwrap.dedent( + """\ + [pytest] + addopts = --verbose + """ + ), + encoding="utf-8", + ) + pytester.plugins = [TestVerbosity.VerbosityIni()] + + config = pytester.parseconfig(tmp_path) + + assert ( + config.get_verbosity(TestVerbosity.SOME_OUTPUT_TYPE) + == config.option.verbose + ) + + def test_level_matches_verbose_when_not_known_type( + self, pytester: Pytester, tmp_path: Path + ) -> None: + tmp_path.joinpath("pytest.ini").write_text( + textwrap.dedent( + """\ + [pytest] + addopts = --verbose + """ + ), + encoding="utf-8", + ) + pytester.plugins = [TestVerbosity.VerbosityIni()] + + config = pytester.parseconfig(tmp_path) + + assert config.get_verbosity("some fake verbosity type") == config.option.verbose + + def test_level_matches_specified_override( + self, pytester: Pytester, tmp_path: Path + ) -> None: + setting_name = f"verbosity_{TestVerbosity.SOME_OUTPUT_TYPE}" + tmp_path.joinpath("pytest.ini").write_text( + textwrap.dedent( + f"""\ + [pytest] + addopts = --verbose + {setting_name} = {TestVerbosity.SOME_OUTPUT_VERBOSITY_LEVEL} + """ + ), + encoding="utf-8", + ) + pytester.plugins = [TestVerbosity.VerbosityIni()] + + config = pytester.parseconfig(tmp_path) + + assert ( + config.get_verbosity(TestVerbosity.SOME_OUTPUT_TYPE) + == TestVerbosity.SOME_OUTPUT_VERBOSITY_LEVEL + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_conftest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_conftest.py index 64c1014a53347..06438f082a934 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_conftest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_conftest.py @@ -1,20 +1,22 @@ -import argparse +# mypy: allow-untyped-defs import os -import textwrap from pathlib import Path +import textwrap from typing import cast from typing import Dict from typing import Generator from typing import List from typing import Optional +from typing import Sequence +from typing import Union -import pytest from _pytest.config import ExitCode from _pytest.config import PytestPluginManager from _pytest.monkeypatch import MonkeyPatch from _pytest.pathlib import symlink_or_skip from _pytest.pytester import Pytester from _pytest.tmpdir import TempPathFactory +import pytest def ConftestWithSetinitial(path) -> PytestPluginManager: @@ -24,18 +26,20 @@ def ConftestWithSetinitial(path) -> PytestPluginManager: def conftest_setinitial( - conftest: PytestPluginManager, args, confcutdir: Optional["os.PathLike[str]"] = None + conftest: PytestPluginManager, + args: Sequence[Union[str, Path]], + confcutdir: Optional[Path] = None, ) -> None: - class Namespace: - def __init__(self) -> None: - self.file_or_dir = args - self.confcutdir = os.fspath(confcutdir) if confcutdir is not None else None - self.noconftest = False - self.pyargs = False - self.importmode = "prepend" - - namespace = cast(argparse.Namespace, Namespace()) - conftest._set_initial_conftests(namespace, rootpath=Path(args[0])) + conftest._set_initial_conftests( + args=args, + pyargs=False, + noconftest=False, + rootpath=Path(args[0]), + confcutdir=confcutdir, + invocation_dir=Path.cwd(), + importmode="prepend", + consider_namespace_packages=False, + ) @pytest.mark.usefixtures("_sys_snapshot") @@ -46,8 +50,12 @@ def basedir( ) -> Generator[Path, None, None]: tmp_path = tmp_path_factory.mktemp("basedir", numbered=True) tmp_path.joinpath("adir/b").mkdir(parents=True) - tmp_path.joinpath("adir/conftest.py").write_text("a=1 ; Directory = 3") - tmp_path.joinpath("adir/b/conftest.py").write_text("b=2 ; a = 1.5") + tmp_path.joinpath("adir/conftest.py").write_text( + "a=1 ; Directory = 3", encoding="utf-8" + ) + tmp_path.joinpath("adir/b/conftest.py").write_text( + "b=2 ; a = 1.5", encoding="utf-8" + ) if request.param == "inpackage": tmp_path.joinpath("adir/__init__.py").touch() tmp_path.joinpath("adir/b/__init__.py").touch() @@ -57,63 +65,69 @@ def basedir( def test_basic_init(self, basedir: Path) -> None: conftest = PytestPluginManager() p = basedir / "adir" - assert ( - conftest._rget_with_confmod("a", p, importmode="prepend", rootpath=basedir)[ - 1 - ] - == 1 + conftest._loadconftestmodules( + p, importmode="prepend", rootpath=basedir, consider_namespace_packages=False ) + assert conftest._rget_with_confmod("a", p)[1] == 1 - def test_immediate_initialiation_and_incremental_are_the_same( + def test_immediate_initialization_and_incremental_are_the_same( self, basedir: Path ) -> None: conftest = PytestPluginManager() assert not len(conftest._dirpath2confmods) - conftest._getconftestmodules( - basedir, importmode="prepend", rootpath=Path(basedir) + conftest._loadconftestmodules( + basedir, + importmode="prepend", + rootpath=basedir, + consider_namespace_packages=False, ) snap1 = len(conftest._dirpath2confmods) assert snap1 == 1 - conftest._getconftestmodules( - basedir / "adir", importmode="prepend", rootpath=basedir + conftest._loadconftestmodules( + basedir / "adir", + importmode="prepend", + rootpath=basedir, + consider_namespace_packages=False, ) assert len(conftest._dirpath2confmods) == snap1 + 1 - conftest._getconftestmodules( - basedir / "b", importmode="prepend", rootpath=basedir + conftest._loadconftestmodules( + basedir / "b", + importmode="prepend", + rootpath=basedir, + consider_namespace_packages=False, ) assert len(conftest._dirpath2confmods) == snap1 + 2 def test_value_access_not_existing(self, basedir: Path) -> None: conftest = ConftestWithSetinitial(basedir) with pytest.raises(KeyError): - conftest._rget_with_confmod( - "a", basedir, importmode="prepend", rootpath=Path(basedir) - ) + conftest._rget_with_confmod("a", basedir) def test_value_access_by_path(self, basedir: Path) -> None: conftest = ConftestWithSetinitial(basedir) adir = basedir / "adir" - assert ( - conftest._rget_with_confmod( - "a", adir, importmode="prepend", rootpath=basedir - )[1] - == 1 + conftest._loadconftestmodules( + adir, + importmode="prepend", + rootpath=basedir, + consider_namespace_packages=False, ) - assert ( - conftest._rget_with_confmod( - "a", adir / "b", importmode="prepend", rootpath=basedir - )[1] - == 1.5 + assert conftest._rget_with_confmod("a", adir)[1] == 1 + conftest._loadconftestmodules( + adir / "b", + importmode="prepend", + rootpath=basedir, + consider_namespace_packages=False, ) + assert conftest._rget_with_confmod("a", adir / "b")[1] == 1.5 def test_value_access_with_confmod(self, basedir: Path) -> None: startdir = basedir / "adir" / "b" startdir.joinpath("xx").mkdir() conftest = ConftestWithSetinitial(startdir) - mod, value = conftest._rget_with_confmod( - "a", startdir, importmode="prepend", rootpath=Path(basedir) - ) + mod, value = conftest._rget_with_confmod("a", startdir) assert value == 1.5 + assert mod.__file__ is not None path = Path(mod.__file__) assert path.parent == basedir / "adir" / "b" assert path.stem == "conftest" @@ -121,8 +135,12 @@ def test_value_access_with_confmod(self, basedir: Path) -> None: def test_conftest_in_nonpkg_with_init(tmp_path: Path, _sys_snapshot) -> None: tmp_path.joinpath("adir-1.0/b").mkdir(parents=True) - tmp_path.joinpath("adir-1.0/conftest.py").write_text("a=1 ; Directory = 3") - tmp_path.joinpath("adir-1.0/b/conftest.py").write_text("b=2 ; a = 1.5") + tmp_path.joinpath("adir-1.0/conftest.py").write_text( + "a=1 ; Directory = 3", encoding="utf-8" + ) + tmp_path.joinpath("adir-1.0/b/conftest.py").write_text( + "b=2 ; a = 1.5", encoding="utf-8" + ) tmp_path.joinpath("adir-1.0/b/__init__.py").touch() tmp_path.joinpath("adir-1.0/__init__.py").touch() ConftestWithSetinitial(tmp_path.joinpath("adir-1.0", "b")) @@ -133,9 +151,7 @@ def test_doubledash_considered(pytester: Pytester) -> None: conf.joinpath("conftest.py").touch() conftest = PytestPluginManager() conftest_setinitial(conftest, [conf.name, conf.name]) - values = conftest._getconftestmodules( - conf, importmode="prepend", rootpath=pytester.path - ) + values = conftest._getconftestmodules(conf) assert len(values) == 1 @@ -145,10 +161,9 @@ def test_issue151_load_all_conftests(pytester: Pytester) -> None: p = pytester.mkdir(name) p.joinpath("conftest.py").touch() - conftest = PytestPluginManager() - conftest_setinitial(conftest, names) - d = list(conftest._conftestpath2mod.values()) - assert len(d) == len(names) + pm = PytestPluginManager() + conftest_setinitial(pm, names) + assert len(set(pm.get_plugins()) - {pm}) == len(names) def test_conftest_global_import(pytester: Pytester) -> None: @@ -159,15 +174,25 @@ def test_conftest_global_import(pytester: Pytester) -> None: import pytest from _pytest.config import PytestPluginManager conf = PytestPluginManager() - mod = conf._importconftest(Path("conftest.py"), importmode="prepend", rootpath=Path.cwd()) + mod = conf._importconftest( + Path("conftest.py"), + importmode="prepend", + rootpath=Path.cwd(), + consider_namespace_packages=False, + ) assert mod.x == 3 import conftest assert conftest is mod, (conftest, mod) sub = Path("sub") sub.mkdir() subconf = sub / "conftest.py" - subconf.write_text("y=4") - mod2 = conf._importconftest(subconf, importmode="prepend", rootpath=Path.cwd()) + subconf.write_text("y=4", encoding="utf-8") + mod2 = conf._importconftest( + subconf, + importmode="prepend", + rootpath=Path.cwd(), + consider_namespace_packages=False, + ) assert mod != mod2 assert mod2.y == 4 import conftest @@ -183,26 +208,37 @@ def test_conftestcutdir(pytester: Pytester) -> None: p = pytester.mkdir("x") conftest = PytestPluginManager() conftest_setinitial(conftest, [pytester.path], confcutdir=p) - values = conftest._getconftestmodules( - p, importmode="prepend", rootpath=pytester.path + conftest._loadconftestmodules( + p, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, ) + values = conftest._getconftestmodules(p) assert len(values) == 0 - values = conftest._getconftestmodules( - conf.parent, importmode="prepend", rootpath=pytester.path + conftest._loadconftestmodules( + conf.parent, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, ) + values = conftest._getconftestmodules(conf.parent) assert len(values) == 0 - assert Path(conf) not in conftest._conftestpath2mod + assert not conftest.has_plugin(str(conf)) # but we can still import a conftest directly - conftest._importconftest(conf, importmode="prepend", rootpath=pytester.path) - values = conftest._getconftestmodules( - conf.parent, importmode="prepend", rootpath=pytester.path + conftest._importconftest( + conf, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, ) + values = conftest._getconftestmodules(conf.parent) + assert values[0].__file__ is not None assert values[0].__file__.startswith(str(conf)) # and all sub paths get updated properly - values = conftest._getconftestmodules( - p, importmode="prepend", rootpath=pytester.path - ) + values = conftest._getconftestmodules(p) assert len(values) == 1 + assert values[0].__file__ is not None assert values[0].__file__.startswith(str(conf)) @@ -210,10 +246,9 @@ def test_conftestcutdir_inplace_considered(pytester: Pytester) -> None: conf = pytester.makeconftest("") conftest = PytestPluginManager() conftest_setinitial(conftest, [conf.parent], confcutdir=conf.parent) - values = conftest._getconftestmodules( - conf.parent, importmode="prepend", rootpath=pytester.path - ) + values = conftest._getconftestmodules(conf.parent) assert len(values) == 1 + assert values[0].__file__ is not None assert values[0].__file__.startswith(str(conf)) @@ -222,15 +257,15 @@ def test_setinitial_conftest_subdirs(pytester: Pytester, name: str) -> None: sub = pytester.mkdir(name) subconftest = sub.joinpath("conftest.py") subconftest.touch() - conftest = PytestPluginManager() - conftest_setinitial(conftest, [sub.parent], confcutdir=pytester.path) + pm = PytestPluginManager() + conftest_setinitial(pm, [sub.parent], confcutdir=pytester.path) key = subconftest.resolve() if name not in ("whatever", ".dotdir"): - assert key in conftest._conftestpath2mod - assert len(conftest._conftestpath2mod) == 1 + assert pm.has_plugin(str(key)) + assert len(set(pm.get_plugins()) - {pm}) == 1 else: - assert key not in conftest._conftestpath2mod - assert len(conftest._conftestpath2mod) == 0 + assert not pm.has_plugin(str(key)) + assert len(set(pm.get_plugins()) - {pm}) == 0 def test_conftest_confcutdir(pytester: Pytester) -> None: @@ -242,13 +277,45 @@ def test_conftest_confcutdir(pytester: Pytester) -> None: def pytest_addoption(parser): parser.addoption("--xyz", action="store_true") """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest("-h", "--confcutdir=%s" % x, x) result.stdout.fnmatch_lines(["*--xyz*"]) result.stdout.no_fnmatch_line("*warning: could not load initial*") +def test_installed_conftest_is_picked_up(pytester: Pytester, tmp_path: Path) -> None: + """When using `--pyargs` to run tests in an installed packages (located e.g. + in a site-packages in the PYTHONPATH), conftest files in there are picked + up. + + Regression test for #9767. + """ + # pytester dir - the source tree. + # tmp_path - the simulated site-packages dir (not in source tree). + + pytester.syspathinsert(tmp_path) + pytester.makepyprojecttoml("[tool.pytest.ini_options]") + tmp_path.joinpath("foo").mkdir() + tmp_path.joinpath("foo", "__init__.py").touch() + tmp_path.joinpath("foo", "conftest.py").write_text( + textwrap.dedent( + """\ + import pytest + @pytest.fixture + def fix(): return None + """ + ), + encoding="utf-8", + ) + tmp_path.joinpath("foo", "test_it.py").write_text( + "def test_it(fix): pass", encoding="utf-8" + ) + result = pytester.runpytest("--pyargs", "foo") + assert result.ret == 0 + + def test_conftest_symlink(pytester: Pytester) -> None: """`conftest.py` discovery follows normal path resolution and does not resolve symlinks.""" # Structure: @@ -329,7 +396,7 @@ def fixture(): @pytest.mark.skipif( os.path.normcase("x") != os.path.normcase("X"), - reason="only relevant for case insensitive file systems", + reason="only relevant for case-insensitive file systems", ) def test_conftest_badcase(pytester: Pytester) -> None: """Check conftest.py loading when directory casing is wrong (#5792).""" @@ -369,7 +436,8 @@ def test_conftest_existing_junitxml(pytester: Pytester) -> None: def pytest_addoption(parser): parser.addoption("--xyz", action="store_true") """ - ) + ), + encoding="utf-8", ) pytester.makefile(ext=".xml", junit="") # Writes junit.xml result = pytester.runpytest("-h", "--junitxml", "junit.xml") @@ -380,18 +448,21 @@ def test_conftest_import_order(pytester: Pytester, monkeypatch: MonkeyPatch) -> ct1 = pytester.makeconftest("") sub = pytester.mkdir("sub") ct2 = sub / "conftest.py" - ct2.write_text("") + ct2.write_text("", encoding="utf-8") - def impct(p, importmode, root): + def impct(p, importmode, root, consider_namespace_packages): return p conftest = PytestPluginManager() conftest._confcutdir = pytester.path monkeypatch.setattr(conftest, "_importconftest", impct) - mods = cast( - List[Path], - conftest._getconftestmodules(sub, importmode="prepend", rootpath=pytester.path), + conftest._loadconftestmodules( + sub, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, ) + mods = cast(List[Path], conftest._getconftestmodules(sub)) expected = [ct1, ct2] assert mods == expected @@ -418,7 +489,8 @@ def foo(): def bar(foo): return 'bar' """ - ) + ), + encoding="utf-8", ) subsub = sub.joinpath("subsub") subsub.mkdir() @@ -435,7 +507,8 @@ def bar(): def test_event_fixture(bar): assert bar == 'sub bar' """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest("sub") result.stdout.fnmatch_lines(["*1 passed*"]) @@ -449,10 +522,11 @@ def test_conftest_found_with_double_dash(pytester: Pytester) -> None: def pytest_addoption(parser): parser.addoption("--hello-world", action="store_true") """ - ) + ), + encoding="utf-8", ) p = sub.joinpath("test_hello.py") - p.write_text("def test_hello(): pass") + p.write_text("def test_hello(): pass", encoding="utf-8") result = pytester.runpytest(str(p) + "::test_hello", "-h") result.stdout.fnmatch_lines( """ @@ -476,7 +550,8 @@ def _setup_tree(self, pytester: Pytester) -> Dict[str, Path]: # for issue616 def fxtr(): return "from-package" """ - ) + ), + encoding="utf-8", ) package.joinpath("test_pkgroot.py").write_text( textwrap.dedent( @@ -484,7 +559,8 @@ def fxtr(): def test_pkgroot(fxtr): assert fxtr == "from-package" """ - ) + ), + encoding="utf-8", ) swc = package.joinpath("swc") @@ -498,7 +574,8 @@ def test_pkgroot(fxtr): def fxtr(): return "from-swc" """ - ) + ), + encoding="utf-8", ) swc.joinpath("test_with_conftest.py").write_text( textwrap.dedent( @@ -506,7 +583,8 @@ def fxtr(): def test_with_conftest(fxtr): assert fxtr == "from-swc" """ - ) + ), + encoding="utf-8", ) snc = package.joinpath("snc") @@ -519,10 +597,11 @@ def test_no_conftest(fxtr): assert fxtr == "from-package" # No local conftest.py, so should # use value from parent dir's """ - ) + ), + encoding="utf-8", ) print("created directory structure:") - for x in pytester.path.rglob(""): + for x in pytester.path.glob("**/"): print(" " + str(x.relative_to(pytester.path))) return {"runner": runner, "package": package, "swc": swc, "snc": snc} @@ -563,7 +642,13 @@ def test_parsefactories_relative_node_ids( print("pytestarg : %s" % testarg) print("expected pass : %s" % expect_ntests_passed) os.chdir(dirs[chdir]) - reprec = pytester.inline_run(testarg, "-q", "--traceconfig") + reprec = pytester.inline_run( + testarg, + "-q", + "--traceconfig", + "--confcutdir", + pytester.path, + ) reprec.assertoutcome(passed=expect_ntests_passed) @@ -579,7 +664,7 @@ def test_search_conftest_up_to_inifile( root = pytester.path src = root.joinpath("src") src.mkdir() - src.joinpath("pytest.ini").write_text("[pytest]") + src.joinpath("pytest.ini").write_text("[pytest]", encoding="utf-8") src.joinpath("conftest.py").write_text( textwrap.dedent( """\ @@ -587,7 +672,8 @@ def test_search_conftest_up_to_inifile( @pytest.fixture def fix1(): pass """ - ) + ), + encoding="utf-8", ) src.joinpath("test_foo.py").write_text( textwrap.dedent( @@ -597,7 +683,8 @@ def test_1(fix1): def test_2(out_of_reach): pass """ - ) + ), + encoding="utf-8", ) root.joinpath("conftest.py").write_text( textwrap.dedent( @@ -606,7 +693,8 @@ def test_2(out_of_reach): @pytest.fixture def out_of_reach(): pass """ - ) + ), + encoding="utf-8", ) args = [str(src)] @@ -689,7 +777,8 @@ def test_required_option_help(pytester: Pytester) -> None: def pytest_addoption(parser): parser.addoption("--xyz", action="store_true", required=True) """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest("-h", x) result.stdout.no_fnmatch_line("*argument --xyz is required*") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_debugging.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_debugging.py index a822bb57f5867..7582dac67429a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_debugging.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_debugging.py @@ -1,23 +1,12 @@ -import os +# mypy: allow-untyped-defs import sys from typing import List import _pytest._code -import pytest from _pytest.debugging import _validate_usepdb_cls from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester - -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", "") +import pytest @pytest.fixture(autouse=True) @@ -28,10 +17,19 @@ def pdb_env(request): pytester._monkeypatch.setenv("PDBPP_HIJACK_PDB", "0") -def runpdb_and_get_report(pytester: Pytester, source: str): +def runpdb(pytester: Pytester, source: str): p = pytester.makepyfile(source) - result = pytester.runpytest_inprocess("--pdb", p) - reports = result.reprec.getreports("pytest_runtest_logreport") # type: ignore[attr-defined] + return pytester.runpytest_inprocess("--pdb", p) + + +def runpdb_and_get_stdout(pytester: Pytester, source: str): + result = runpdb(pytester, source) + return result.stdout.str() + + +def runpdb_and_get_report(pytester: Pytester, source: str): + result = runpdb(pytester, source) + reports = result.reprec.getreports("pytest_runtest_logreport") assert len(reports) == 3, reports # setup/call/teardown return reports[1] @@ -132,6 +130,16 @@ def test_func(): assert rep.skipped assert len(pdblist) == 0 + def test_pdb_on_top_level_raise_skiptest(self, pytester, pdblist) -> None: + stdout = runpdb_and_get_stdout( + pytester, + """ + import unittest + raise unittest.SkipTest("This is a common way to skip an entire file.") + """, + ) + assert "entering PDB" not in stdout, stdout + def test_pdb_on_BdbQuit(self, pytester, pdblist) -> None: rep = runpdb_and_get_report( pytester, @@ -252,7 +260,7 @@ def test_pdb_print_captured_logs(self, pytester, showcapture: str) -> None: """ def test_1(): import logging - logging.warn("get " + "rekt") + logging.warning("get " + "rekt") assert False """ ) @@ -271,7 +279,7 @@ def test_pdb_print_captured_logs_nologging(self, pytester: Pytester) -> None: """ def test_1(): import logging - logging.warn("get " + "rekt") + logging.warning("get " + "rekt") assert False """ ) @@ -361,6 +369,7 @@ def test_pdb_prevent_ConftestImportFailure_hiding_exception( result = pytester.runpytest_subprocess("--pdb", ".") result.stdout.fnmatch_lines(["-> import unknown"]) + @pytest.mark.xfail(reason="#10042", strict=False) def test_pdb_interaction_capturing_simple(self, pytester: Pytester) -> None: p1 = pytester.makepyfile( """ @@ -529,6 +538,7 @@ def function_1(): assert "BdbQuit" not in rest assert "UNEXPECTED EXCEPTION" not in rest + @pytest.mark.xfail(reason="#10042", strict=False) def test_pdb_interaction_capturing_twice(self, pytester: Pytester) -> None: p1 = pytester.makepyfile( """ @@ -564,6 +574,7 @@ def test_1(): assert "1 failed" in rest self.flush(child) + @pytest.mark.xfail(reason="#10042", strict=False) def test_pdb_with_injected_do_debug(self, pytester: Pytester) -> None: """Simulates pdbpp, which injects Pdb into do_debug, and uses self.__class__ in do_continue. @@ -911,14 +922,6 @@ def test_foo(): class TestDebuggingBreakpoints: - def test_supports_breakpoint_module_global(self) -> None: - """Test that supports breakpoint global marks on Python 3.7+.""" - if sys.version_info >= (3, 7): - assert SUPPORTS_BREAKPOINT_BUILTIN is True - - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) @pytest.mark.parametrize("arg", ["--pdb", ""]) def test_sys_breakpointhook_configure_and_unconfigure( self, pytester: Pytester, arg: str @@ -952,10 +955,10 @@ def test_nothing(): pass result = pytester.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, pytester: Pytester, custom_debugger_hook) -> None: + def test_pdb_custom_cls( + self, pytester: Pytester, custom_debugger_hook, monkeypatch: MonkeyPatch + ) -> None: + monkeypatch.delenv("PYTHONBREAKPOINT", raising=False) p1 = pytester.makepyfile( """ def test_nothing(): @@ -969,9 +972,6 @@ def test_nothing(): 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, pytester: Pytester, custom_debugger_hook, arg: str ) -> None: @@ -1002,14 +1002,10 @@ def test_nothing(): pass result = pytester.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, pytester: Pytester) -> None: + def test_sys_breakpoint_interception( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: + monkeypatch.delenv("PYTHONBREAKPOINT", raising=False) p1 = pytester.makepyfile( """ def test_1(): @@ -1025,9 +1021,7 @@ def test_1(): assert "reading from stdin while output" not in rest TestPDB.flush(child) - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) + @pytest.mark.xfail(reason="#10042", strict=False) def test_pdb_not_altered(self, pytester: Pytester) -> None: p1 = pytester.makepyfile( """ @@ -1128,7 +1122,7 @@ def test_func_kw(myparam, request, func="func_kw"): def test_trace_after_runpytest(pytester: Pytester) -> None: - """Test that debugging's pytest_configure is re-entrant.""" + """Test that debugging's pytest_configure is reentrant.""" p1 = pytester.makepyfile( """ from _pytest.debugging import pytestPDB @@ -1159,7 +1153,7 @@ def test_inner(): def test_quit_with_swallowed_SystemExit(pytester: Pytester) -> None: - """Test that debugging's pytest_configure is re-entrant.""" + """Test that debugging's pytest_configure is reentrant.""" p1 = pytester.makepyfile( """ def call_pdb_set_trace(): @@ -1187,10 +1181,11 @@ def test_2(): @pytest.mark.parametrize("fixture", ("capfd", "capsys")) +@pytest.mark.xfail(reason="#10042", strict=False) def test_pdb_suspends_fixture_capturing(pytester: Pytester, fixture: str) -> None: """Using "-s" with pytest should suspend/resume fixture capturing.""" p1 = pytester.makepyfile( - """ + f""" def test_inner({fixture}): import sys @@ -1205,9 +1200,7 @@ def test_inner({fixture}): 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 = pytester.spawn_pytest(str(p1) + " -s") @@ -1280,7 +1273,6 @@ def runcall(self, *args, **kwds): def test_raises_bdbquit_with_eoferror(pytester: Pytester) -> None: """It is not guaranteed that DontReadFromInput's read is called.""" - p1 = pytester.makepyfile( """ def input_without_read(*args, **kwargs): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_doctest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_doctest.py index 67b8ccdb7ecd2..a95dde9a6bfb7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_doctest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_doctest.py @@ -1,11 +1,11 @@ +# mypy: allow-untyped-defs import inspect +from pathlib import Path import sys import textwrap -from pathlib import Path from typing import Callable from typing import Optional -import pytest from _pytest.doctest import _get_checker from _pytest.doctest import _is_main_py from _pytest.doctest import _is_mocked @@ -15,6 +15,7 @@ from _pytest.doctest import DoctestModule from _pytest.doctest import DoctestTextfile from _pytest.pytester import Pytester +import pytest class TestDoctests: @@ -113,6 +114,32 @@ def test_simple_doctestfile(self, pytester: Pytester): reprec = pytester.inline_run(p) reprec.assertoutcome(failed=1) + def test_importmode(self, pytester: Pytester): + pytester.makepyfile( + **{ + "src/namespacepkg/innerpkg/__init__.py": "", + "src/namespacepkg/innerpkg/a.py": """ + def some_func(): + return 42 + """, + "src/namespacepkg/innerpkg/b.py": """ + from namespacepkg.innerpkg.a import some_func + def my_func(): + ''' + >>> my_func() + 42 + ''' + return some_func() + """, + } + ) + # For 'namespacepkg' to be considered a namespace package, its containing directory + # needs to be reachable from sys.path: + # https://packaging.python.org/en/latest/guides/packaging-namespace-packages + pytester.syspathinsert(pytester.path / "src") + reprec = pytester.inline_run("--doctest-modules", "--import-mode=importlib") + reprec.assertoutcome(passed=1) + def test_new_pattern(self, pytester: Pytester): p = pytester.maketxtfile( xdoc=""" @@ -160,19 +187,15 @@ def test_multiple_patterns(self, pytester: Pytester): def test_encoding(self, pytester, test_string, encoding): """Test support for doctest_encoding ini option.""" pytester.makeini( - """ + f""" [pytest] - doctest_encoding={} - """.format( - encoding - ) - ) - doctest = """ - >>> "{}" - {} - """.format( - test_string, repr(test_string) + doctest_encoding={encoding} + """ ) + doctest = f""" + >>> "{test_string}" + {test_string!r} + """ fn = pytester.path / "test_encoding.txt" fn.write_text(doctest, encoding=encoding) @@ -201,7 +224,7 @@ def test_doctest_unexpected_exception(self, pytester: Pytester): "Traceback (most recent call last):", ' File "*/doctest.py", line *, in __run', " *", - *((" *^^^^*",) if sys.version_info >= (3, 11) else ()), + *((" *^^^^*", " *", " *") if sys.version_info >= (3, 13) else ()), ' File "", line 1, in ', "ZeroDivisionError: division by zero", "*/test_doctest_unexpected_exception.txt:2: UnexpectedException", @@ -331,7 +354,8 @@ def test(self): >>> 1/0 ''' """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest("--doctest-modules") result.stdout.fnmatch_lines( @@ -357,7 +381,7 @@ def some_property(self): "*= FAILURES =*", "*_ [[]doctest[]] test_doctest_linedata_on_property.Sample.some_property _*", "004 ", - "005 >>> Sample().some_property", + "005 *>>> Sample().some_property", "Expected:", " 'another thing'", "Got:", @@ -368,7 +392,7 @@ def some_property(self): ] ) - def test_doctest_no_linedata_on_overriden_property(self, pytester: Pytester): + def test_doctest_no_linedata_on_overridden_property(self, pytester: Pytester): pytester.makepyfile( """ class Sample(object): @@ -386,7 +410,7 @@ def some_property(self): result.stdout.fnmatch_lines( [ "*= FAILURES =*", - "*_ [[]doctest[]] test_doctest_no_linedata_on_overriden_property.Sample.some_property _*", + "*_ [[]doctest[]] test_doctest_no_linedata_on_overridden_property.Sample.some_property _*", "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example", "[?][?][?] >>> Sample().some_property", "Expected:", @@ -394,7 +418,7 @@ def some_property(self): "Got:", " 'something'", "", - "*/test_doctest_no_linedata_on_overriden_property.py:None: DocTestFailure", + "*/test_doctest_no_linedata_on_overridden_property.py:None: DocTestFailure", "*= 1 failed in *", ] ) @@ -422,7 +446,8 @@ def test_doctest_unex_importerror_with_module(self, pytester: Pytester): """\ import asdalsdkjaslkdjasd """ - ) + ), + encoding="utf-8", ) pytester.maketxtfile( """ @@ -454,6 +479,24 @@ def test_doctestmodule(self, pytester: Pytester): reprec = pytester.inline_run(p, "--doctest-modules") reprec.assertoutcome(failed=1) + def test_doctest_cached_property(self, pytester: Pytester): + p = pytester.makepyfile( + """ + import functools + + class Foo: + @functools.cached_property + def foo(self): + ''' + >>> assert False, "Tacos!" + ''' + ... + """ + ) + result = pytester.runpytest(p, "--doctest-modules") + result.assert_outcomes(failed=1) + assert "Tacos!" in result.stdout.str() + def test_doctestmodule_external_and_issue116(self, pytester: Pytester): p = pytester.mkpydir("hello") p.joinpath("__init__.py").write_text( @@ -466,7 +509,8 @@ def somefunc(): 2 ''' """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest(p, "--doctest-modules") result.stdout.fnmatch_lines( @@ -685,7 +729,7 @@ def foo(): >>> name = 'с' # not letter 'c' but instead Cyrillic 's'. 'anything' """ - ''' + ''' # noqa: RUF001 ) result = pytester.runpytest("--doctest-modules") result.stdout.fnmatch_lines(["Got nothing", "* 1 failed in*"]) @@ -803,8 +847,8 @@ def test_valid_setup_py(self, pytester: Pytester): """ p = pytester.makepyfile( setup=""" - from setuptools import setup, find_packages if __name__ == '__main__': + from setuptools import setup, find_packages setup(name='sample', version='0.0', description='description', @@ -834,6 +878,25 @@ def test_foo(): result = pytester.runpytest(p, "--doctest-modules") result.stdout.fnmatch_lines(["*collected 1 item*"]) + def test_setup_module(self, pytester: Pytester) -> None: + """Regression test for #12011 - setup_module not executed when running + with `--doctest-modules`.""" + pytester.makepyfile( + """ + CONSTANT = 0 + + def setup_module(): + global CONSTANT + CONSTANT = 1 + + def test(): + assert CONSTANT == 1 + """ + ) + result = pytester.runpytest("--doctest-modules") + assert result.ret == 0 + result.assert_outcomes(passed=1) + class TestLiterals: @pytest.mark.parametrize("config_mode", ["ini", "comment"]) @@ -854,23 +917,19 @@ def test_allow_unicode(self, pytester, config_mode): comment = "#doctest: +ALLOW_UNICODE" pytester.maketxtfile( - test_doc=""" + test_doc=f""" >>> b'12'.decode('ascii') {comment} '12' - """.format( - comment=comment - ) + """ ) pytester.makepyfile( - foo=""" + foo=f""" def foo(): ''' >>> b'12'.decode('ascii') {comment} '12' ''' - """.format( - comment=comment - ) + """ ) reprec = pytester.inline_run("--doctest-modules") reprec.assertoutcome(passed=2) @@ -893,23 +952,19 @@ def test_allow_bytes(self, pytester, config_mode): comment = "#doctest: +ALLOW_BYTES" pytester.maketxtfile( - test_doc=""" + test_doc=f""" >>> b'foo' {comment} 'foo' - """.format( - comment=comment - ) + """ ) pytester.makepyfile( - foo=""" + foo=f""" def foo(): ''' >>> b'foo' {comment} 'foo' ''' - """.format( - comment=comment - ) + """ ) reprec = pytester.inline_run("--doctest-modules") reprec.assertoutcome(passed=2) @@ -986,7 +1041,7 @@ def test_number_precision(self, pytester, config_mode): comment = "#doctest: +NUMBER" pytester.maketxtfile( - test_doc=""" + test_doc=f""" Scalars: @@ -1038,9 +1093,7 @@ def test_number_precision(self, pytester, config_mode): >>> 'abc' {comment} 'abc' >>> None {comment} - """.format( - comment=comment - ) + """ ) reprec = pytester.inline_run() reprec.assertoutcome(passed=1) @@ -1068,12 +1121,10 @@ def test_number_precision(self, pytester, config_mode): ) def test_number_non_matches(self, pytester, expression, output): pytester.maketxtfile( - test_doc=""" + test_doc=f""" >>> {expression} #doctest: +NUMBER {output} - """.format( - expression=expression, output=output - ) + """ ) reprec = pytester.inline_run() reprec.assertoutcome(passed=0, failed=1) @@ -1210,7 +1261,6 @@ def my_config_context(): class TestDoctestAutoUseFixtures: - SCOPES = ["module", "session", "class", "function"] def test_doctest_module_session_fixture(self, pytester: Pytester): @@ -1255,15 +1305,13 @@ def test_fixture_scopes(self, pytester, scope, enable_doctest): See #1057 and #1100. """ pytester.makeconftest( - """ + f""" import pytest @pytest.fixture(autouse=True, scope="{scope}") def auto(request): return 99 - """.format( - scope=scope - ) + """ ) pytester.makepyfile( test_1=''' @@ -1291,15 +1339,13 @@ def test_fixture_module_doctest_scopes( See #1057 and #1100. """ pytester.makeconftest( - """ + f""" import pytest @pytest.fixture(autouse={autouse}, scope="{scope}") def auto(request): return 99 - """.format( - scope=scope, autouse=autouse - ) + """ ) if use_fixture_in_doctest: pytester.maketxtfile( @@ -1325,7 +1371,7 @@ def test_auto_use_request_attributes(self, pytester, scope): behave as expected when requested for a doctest item. """ pytester.makeconftest( - """ + f""" import pytest @pytest.fixture(autouse=True, scope="{scope}") @@ -1337,9 +1383,7 @@ def auto(request): if "{scope}" == 'function': assert request.function is None return 99 - """.format( - scope=scope - ) + """ ) pytester.maketxtfile( test_doc=""" @@ -1351,9 +1395,40 @@ def auto(request): str(result.stdout.no_fnmatch_line("*FAILURES*")) result.stdout.fnmatch_lines(["*=== 1 passed in *"]) + @pytest.mark.parametrize("scope", [*SCOPES, "package"]) + def test_auto_use_defined_in_same_module( + self, pytester: Pytester, scope: str + ) -> None: + """Autouse fixtures defined in the same module as the doctest get picked + up properly. -class TestDoctestNamespaceFixture: + Regression test for #11929. + """ + pytester.makepyfile( + f""" + import pytest + AUTO = "the fixture did not run" + + @pytest.fixture(autouse=True, scope="{scope}") + def auto(request): + global AUTO + AUTO = "the fixture ran" + + def my_doctest(): + '''My doctest. + + >>> my_doctest() + 'the fixture ran' + ''' + return AUTO + """ + ) + result = pytester.runpytest("--doctest-modules") + result.assert_outcomes(passed=1) + + +class TestDoctestNamespaceFixture: SCOPES = ["module", "session", "class", "function"] @pytest.mark.parametrize("scope", SCOPES) @@ -1363,16 +1438,14 @@ def test_namespace_doctestfile(self, pytester, scope): simple text file doctest """ pytester.makeconftest( - """ + f""" import pytest import contextlib @pytest.fixture(autouse=True, scope="{scope}") def add_contextlib(doctest_namespace): doctest_namespace['cl'] = contextlib - """.format( - scope=scope - ) + """ ) p = pytester.maketxtfile( """ @@ -1390,16 +1463,14 @@ def test_namespace_pyfile(self, pytester, scope): simple Python file docstring doctest """ pytester.makeconftest( - """ + f""" import pytest import contextlib @pytest.fixture(autouse=True, scope="{scope}") def add_contextlib(doctest_namespace): doctest_namespace['cl'] = contextlib - """.format( - scope=scope - ) + """ ) p = pytester.makepyfile( """ @@ -1502,16 +1573,14 @@ def test_doctest_report_invalid(self, pytester: Pytester): def test_doctest_mock_objects_dont_recurse_missbehaved(mock_module, pytester: Pytester): pytest.importorskip(mock_module) pytester.makepyfile( - """ + f""" from {mock_module} import call class Example(object): ''' >>> 1 + 1 2 ''' - """.format( - mock_module=mock_module - ) + """ ) result = pytester.runpytest("--doctest-modules") result.stdout.fnmatch_lines(["* 1 passed *"]) @@ -1526,7 +1595,7 @@ def __getattr__(self, _): "stop", [None, _is_mocked, lambda f: None, lambda f: False, lambda f: True] ) def test_warning_on_unwrap_of_broken_object( - stop: Optional[Callable[[object], object]] + stop: Optional[Callable[[object], object]], ) -> None: bad_instance = Broken() assert inspect.unwrap.__module__ == "inspect" @@ -1542,7 +1611,9 @@ def test_warning_on_unwrap_of_broken_object( def test_is_setup_py_not_named_setup_py(tmp_path: Path) -> None: not_setup_py = tmp_path.joinpath("not_setup.py") - not_setup_py.write_text('from setuptools import setup; setup(name="foo")') + not_setup_py.write_text( + 'from setuptools import setup; setup(name="foo")', encoding="utf-8" + ) assert not _is_setup_py(not_setup_py) @@ -1558,7 +1629,7 @@ def test_is_setup_py_different_encoding(tmp_path: Path, mod: str) -> None: setup_py = tmp_path.joinpath("setup.py") contents = ( "# -*- coding: cp1252 -*-\n" - 'from {} import setup; setup(name="foo", description="€")\n'.format(mod) + f'from {mod} import setup; setup(name="foo", description="€")\n' ) setup_py.write_bytes(contents.encode("cp1252")) assert _is_setup_py(setup_py) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_entry_points.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_entry_points.py index 5d003127363dd..68e3a8a92e49b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_entry_points.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_entry_points.py @@ -1,7 +1,8 @@ -from _pytest.compat import importlib_metadata +# mypy: allow-untyped-defs +import importlib.metadata def test_pytest_entry_points_are_identical(): - dist = importlib_metadata.distribution("pytest") + dist = importlib.metadata.distribution("pytest") entry_map = {ep.name: ep for ep in dist.entry_points} assert entry_map["pytest"].value == entry_map["py.test"].value diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_error_diffs.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_error_diffs.py index 1668e929ab413..f290eb1679f81 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_error_diffs.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_error_diffs.py @@ -4,10 +4,9 @@ See https://github.com/pytest-dev/pytest/issues/3333 for details. """ -import sys -import pytest from _pytest.pytester import Pytester +import pytest TESTCASES = [ @@ -23,10 +22,14 @@ def test_this(): 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, + E - 2, E ? ^ - E + [1, 4, 3] + E + 4, E ? ^ + E 3, + E ] """, id="Compare lists, one item differs", ), @@ -42,9 +45,11 @@ def test_this(): 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 ? +++ + E [ + E 1, + E 2, + E + 3, + E ] """, id="Compare lists, one extra item", ), @@ -61,9 +66,11 @@ def test_this(): 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] + E [ + E 1, + E - 2, + E 3, + E ] """, id="Compare lists, one item missing", ), @@ -79,10 +86,14 @@ def test_this(): 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, + E - 2, E ? ^ - E + (1, 4, 3) + E + 4, E ? ^ + E 3, + E ) """, id="Compare tuples", ), @@ -101,10 +112,12 @@ def test_this(): E Extra items in the right set: E 2 E Full diff: - E - {1, 2, 3} - E ? ^ ^ - E + {1, 3, 4} - E ? ^ ^ + E { + E 1, + E - 2, + E 3, + E + 4, + E } """, id="Compare sets", ), @@ -125,10 +138,13 @@ def test_this(): E Right contains 1 more item: E {2: 'eggs'} E Full diff: - E - {1: 'spam', 2: 'eggs'} - E ? ^ - E + {1: 'spam', 3: 'eggs'} - E ? ^ + E { + E 1: 'spam', + E - 2: 'eggs', + E ? ^ + E + 3: 'eggs', + E ? ^ + E } """, id="Compare dicts with differing keys", ), @@ -147,10 +163,11 @@ def test_this(): E Differing items: E {2: 'eggs'} != {2: 'bacon'} E Full diff: - E - {1: 'spam', 2: 'bacon'} - E ? ^^^^^ - E + {1: 'spam', 2: 'eggs'} - E ? ^^^^ + E { + E 1: 'spam', + E - 2: 'bacon', + E + 2: 'eggs', + E } """, id="Compare dicts with differing values", ), @@ -171,10 +188,11 @@ def test_this(): E Right contains 1 more item: E {3: 'bacon'} E Full diff: - E - {1: 'spam', 3: 'bacon'} - E ? ^ ^^^^^ - E + {1: 'spam', 2: 'eggs'} - E ? ^ ^^^^ + E { + E 1: 'spam', + E - 3: 'bacon', + E + 2: 'eggs', + E } """, id="Compare dicts with differing items", ), @@ -210,68 +228,61 @@ def test_this(): """, id='Test "not in" string', ), -] -if sys.version_info[:2] >= (3, 7): - TESTCASES.extend( - [ - pytest.param( - """ - from dataclasses import dataclass + pytest.param( + """ + from dataclasses import dataclass - @dataclass - class A: - a: int - b: str + @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 + 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 + """, + id="Compare data classes", + ), + pytest.param( + """ + import attr - @attr.s(auto_attribs=True) - class A: - a: int - b: str + @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", - ), - ] - ) + 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) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_faulthandler.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_faulthandler.py index 5b7911f21f833..a3363de981693 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_faulthandler.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_faulthandler.py @@ -1,8 +1,9 @@ +# mypy: allow-untyped-defs import io import sys -import pytest from _pytest.pytester import Pytester +import pytest def test_enabled(pytester: Pytester) -> None: @@ -113,6 +114,7 @@ def test_cancel_timeout_on_hook(monkeypatch, hook_name) -> None: to timeout before entering pdb (pytest-dev/pytest-faulthandler#12) or any other interactive exception (pytest-dev/pytest-faulthandler#14).""" import faulthandler + from _pytest import faulthandler as faulthandler_plugin called = [] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_findpaths.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_findpaths.py index 3a2917261a255..260b9d07c9c44 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_findpaths.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_findpaths.py @@ -1,11 +1,14 @@ +# mypy: allow-untyped-defs +import os from pathlib import Path from textwrap import dedent -import pytest from _pytest.config import UsageError from _pytest.config.findpaths import get_common_ancestor from _pytest.config.findpaths import get_dirs_from_args +from _pytest.config.findpaths import is_fs_root from _pytest.config.findpaths import load_config_dict_from_file +import pytest class TestLoadConfigDictFromFile: @@ -107,18 +110,19 @@ def test_has_ancestor(self, tmp_path: Path) -> None: 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" + cwd = Path.cwd() + assert get_common_ancestor(cwd, [fn1, fn2]) == tmp_path / "foo" + assert get_common_ancestor(cwd, [fn1.parent, fn2]) == tmp_path / "foo" + assert get_common_ancestor(cwd, [fn1.parent, fn2.parent]) == tmp_path / "foo" + assert get_common_ancestor(cwd, [fn1, fn2.parent]) == tmp_path / "foo" def test_single_dir(self, tmp_path: Path) -> None: - assert get_common_ancestor([tmp_path]) == tmp_path + assert get_common_ancestor(Path.cwd(), [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 + assert get_common_ancestor(Path.cwd(), [fn]) == tmp_path def test_get_dirs_from_args(tmp_path): @@ -133,3 +137,18 @@ def test_get_dirs_from_args(tmp_path): assert get_dirs_from_args( [str(fn), str(tmp_path / "does_not_exist"), str(d), option, xdist_rsync_option] ) == [fn.parent, d] + + +@pytest.mark.parametrize( + "path, expected", + [ + pytest.param( + f"e:{os.sep}", True, marks=pytest.mark.skipif("sys.platform != 'win32'") + ), + (f"{os.sep}", True), + (f"e:{os.sep}projects", False), + (f"{os.sep}projects", False), + ], +) +def test_is_fs_root(path: Path, expected: bool) -> None: + assert is_fs_root(Path(path)) is expected diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_helpconfig.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_helpconfig.py index 44c2c9295bf35..4906ef5c8f02f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_helpconfig.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_helpconfig.py @@ -1,6 +1,7 @@ -import pytest +# mypy: allow-untyped-defs from _pytest.config import ExitCode from _pytest.pytester import Pytester +import pytest def test_version_verbose(pytester: Pytester, pytestconfig, monkeypatch) -> None: @@ -30,11 +31,11 @@ def test_help(pytester: Pytester) -> None: assert result.ret == 0 result.stdout.fnmatch_lines( """ - -m MARKEXPR only run tests matching given mark expression. - For example: -m 'mark1 and not mark2'. - reporting: + -m MARKEXPR Only run tests matching given mark expression. For + example: -m 'mark1 and not mark2'. + Reporting: --durations=N * - -V, --version display pytest version and information about plugins. + -V, --version Display pytest version and information about plugins. When given twice, also display information about plugins. *setup.cfg* @@ -71,9 +72,9 @@ def pytest_addoption(parser): assert result.ret == 0 lines = [ " required_plugins (args):", - " plugins that must be present for pytest to run*", + " Plugins that must be present for pytest to run*", " test_ini (bool):*", - "environment variables:", + "Environment variables:", ] result.stdout.fnmatch_lines(lines, consecutive=True) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_junitxml.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_junitxml.py index 02531e81435cf..86edfbbeb612e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_junitxml.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_junitxml.py @@ -1,7 +1,8 @@ -import os -import platform +# mypy: allow-untyped-defs from datetime import datetime +import os from pathlib import Path +import platform from typing import cast from typing import List from typing import Optional @@ -12,7 +13,6 @@ import xmlschema -import pytest from _pytest.config import Config from _pytest.junitxml import bin_xml_escape from _pytest.junitxml import LogXML @@ -22,13 +22,14 @@ from _pytest.reports import BaseReport from _pytest.reports import TestReport from _pytest.stash import Stash +import pytest @pytest.fixture(scope="session") def schema() -> xmlschema.XMLSchema: """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: + with fn.open(encoding="utf-8") as f: return xmlschema.XMLSchema(f) @@ -41,11 +42,11 @@ def __call__( self, *args: Union[str, "os.PathLike[str]"], family: Optional[str] = "xunit1" ) -> Tuple[RunResult, "DomNode"]: if family: - args = ("-o", "junit_family=" + family) + args + args = ("-o", "junit_family=" + family, *args) xml_path = self.pytester.path.joinpath("junit.xml") result = self.pytester.runpytest("--junitxml=%s" % xml_path, *args) if family == "xunit2": - with xml_path.open() as f: + with xml_path.open(encoding="utf-8") as f: self.schema.validate(f) xmldoc = minidom.parse(str(xml_path)) return result, DomNode(xmldoc) @@ -253,7 +254,6 @@ def test_junit_duration_report( duration_report: str, run_and_parse: RunAndParse, ) -> None: - # mock LogXML.node_reporter so it always sets a known duration to each test report object original_node_reporter = LogXML.node_reporter @@ -470,7 +470,7 @@ def test_classname_nested_dir( self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: p = pytester.mkdir("sub").joinpath("test_hello.py") - p.write_text("def test_func(): 0/0") + p.write_text("def test_func(): 0/0", encoding="utf-8") result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") @@ -603,7 +603,6 @@ def test_func(arg1): node.assert_attr(failures=3, tests=3) for index, char in enumerate("<&'"): - tnode = node.find_nth_by_tag("testcase", index) tnode.assert_attr( classname="test_failure_escape", name="test_func[%s]" % char @@ -989,7 +988,7 @@ def repr_failure(self, excinfo): return "custom item runtest failed" """ ) - pytester.path.joinpath("myfile.xyz").write_text("hello") + pytester.path.joinpath("myfile.xyz").write_text("hello", encoding="utf-8") result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") @@ -1003,7 +1002,7 @@ def repr_failure(self, excinfo): @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) def test_nullbyte(pytester: Pytester, junit_logging: str) -> None: - # A null byte can not occur in XML (see section 2.2 of the spec) + # A null byte cannot occur in XML (see section 2.2 of the spec) pytester.makepyfile( """ import sys @@ -1015,7 +1014,7 @@ def test_print_nullbyte(): ) xmlf = pytester.path.joinpath("junit.xml") pytester.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) - text = xmlf.read_text() + text = xmlf.read_text(encoding="utf-8") assert "\x00" not in text if junit_logging == "system-out": assert "#x00" in text @@ -1037,7 +1036,7 @@ def test_print_nullbyte(): ) xmlf = pytester.path.joinpath("junit.xml") pytester.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) - text = xmlf.read_text() + text = xmlf.read_text(encoding="utf-8") if junit_logging == "system-out": assert "#x0" in text if junit_logging == "no": @@ -1203,7 +1202,7 @@ class Report(BaseReport): node_reporter.append_skipped(test_report) test_report.longrepr = "filename", 1, "Skipped: 卡嘣嘣" node_reporter.append_skipped(test_report) - test_report.wasxfail = ustr # type: ignore[attr-defined] + test_report.wasxfail = ustr node_reporter.append_skipped(test_report) log.pytest_sessionfinish() @@ -1230,6 +1229,36 @@ def test_record(record_property, other): result.stdout.fnmatch_lines(["*= 1 passed in *"]) +def test_record_property_on_test_and_teardown_failure( + pytester: Pytester, run_and_parse: RunAndParse +) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.fixture + def other(record_property): + record_property("bar", 1) + yield + assert 0 + + def test_record(record_property, other): + record_property("foo", "<1") + assert 0 + """ + ) + result, dom = run_and_parse() + node = dom.find_first_by_tag("testsuite") + tnodes = node.find_by_tag("testcase") + for tnode in tnodes: + psnode = tnode.find_first_by_tag("properties") + assert psnode, f"testcase didn't had expected properties:\n{tnode}" + 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 failed, 1 error *"]) + + def test_record_property_same_name( pytester: Pytester, run_and_parse: RunAndParse ) -> None: @@ -1254,12 +1283,10 @@ def test_record_fixtures_without_junitxml( pytester: Pytester, fixture_name: str ) -> None: pytester.makepyfile( - """ + f""" def test_record({fixture_name}): {fixture_name}("foo", "bar") - """.format( - fixture_name=fixture_name - ) + """ ) result = pytester.runpytest() assert result.ret == 0 @@ -1307,7 +1334,7 @@ def test_record_fixtures_xunit2( """ ) pytester.makepyfile( - """ + f""" import pytest @pytest.fixture @@ -1315,9 +1342,7 @@ def other({fixture_name}): {fixture_name}("bar", 1) def test_record({fixture_name}, other): {fixture_name}("foo", "<1"); - """.format( - fixture_name=fixture_name - ) + """ ) result, dom = run_and_parse(family=None) @@ -1327,10 +1352,8 @@ def test_record({fixture_name}, other): "*test_record_fixtures_xunit2.py:6:*record_xml_attribute is an experimental feature" ) expected_lines = [ - "*test_record_fixtures_xunit2.py:6:*{fixture_name} is incompatible " - "with junit_family 'xunit2' (use 'legacy' or 'xunit1')".format( - fixture_name=fixture_name - ) + f"*test_record_fixtures_xunit2.py:6:*{fixture_name} is incompatible " + "with junit_family 'xunit2' (use 'legacy' or 'xunit1')" ] result.stdout.fnmatch_lines(expected_lines) @@ -1447,7 +1470,12 @@ def test_pass(): result.stdout.no_fnmatch_line("*INTERNALERROR*") - items = sorted("%(classname)s %(name)s" % x for x in dom.find_by_tag("testcase")) + items = sorted( + "%(classname)s %(name)s" % x # noqa: UP031 + # dom is a DomNode not a mapping, it's not possible to ** it. + for x in dom.find_by_tag("testcase") + ) + import pprint pprint.pprint(items) @@ -1582,13 +1610,11 @@ def test_set_suite_name( ) -> None: if suite_name: pytester.makeini( - """ + f""" [pytest] junit_suite_name={suite_name} - junit_family={family} - """.format( - suite_name=suite_name, family=xunit_family - ) + junit_family={xunit_family} + """ ) expected = suite_name else: @@ -1625,19 +1651,56 @@ def test_skip(): snode.assert_attr(message="1 <> 2") +def test_bin_escaped_skipreason(pytester: Pytester, run_and_parse: RunAndParse) -> None: + """Escape special characters from mark.skip reason (#11842).""" + pytester.makepyfile( + """ + import pytest + @pytest.mark.skip("\33[31;1mred\33[0m") + def test_skip(): + pass + """ + ) + _, dom = run_and_parse() + node = dom.find_first_by_tag("testcase") + snode = node.find_first_by_tag("skipped") + assert "#x1B[31;1mred#x1B[0m" in snode.text + snode.assert_attr(message="#x1B[31;1mred#x1B[0m") + + +def test_escaped_setup_teardown_error( + pytester: Pytester, run_and_parse: RunAndParse +) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.fixture() + def my_setup(): + raise Exception("error: \033[31mred\033[m") + + def test_esc(my_setup): + pass + """ + ) + _, dom = run_and_parse() + node = dom.find_first_by_tag("testcase") + snode = node.find_first_by_tag("error") + assert "#x1B[31mred#x1B[m" in snode["message"] + assert "#x1B[31mred#x1B[m" in snode.text + + @parametrize_families def test_logging_passing_tests_disabled_does_not_log_test_output( pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makeini( - """ + f""" [pytest] junit_log_passing_tests=False junit_logging=system-out - junit_family={family} - """.format( - family=xunit_family - ) + junit_family={xunit_family} + """ ) pytester.makepyfile( """ @@ -1667,13 +1730,11 @@ def test_logging_passing_tests_disabled_logs_output_for_failing_test_issue5430( xunit_family: str, ) -> None: pytester.makeini( - """ + f""" [pytest] junit_log_passing_tests=False - junit_family={family} - """.format( - family=xunit_family - ) + junit_family={xunit_family} + """ ) pytester.makepyfile( """ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_legacypath.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_legacypath.py index 8acafe98e7c4f..ad4e22e46b4d4 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_legacypath.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_legacypath.py @@ -1,9 +1,11 @@ +# mypy: allow-untyped-defs from pathlib import Path -import pytest from _pytest.compat import LEGACY_PATH +from _pytest.fixtures import TopRequest from _pytest.legacypath import TempdirFactory from _pytest.legacypath import Testdir +import pytest def test_item_fspath(pytester: pytest.Pytester) -> None: @@ -14,7 +16,7 @@ def test_item_fspath(pytester: pytest.Pytester) -> None: items2, hookrec = pytester.inline_genitems(item.nodeid) (item2,) = items2 assert item2.name == item.name - assert item2.fspath == item.fspath # type: ignore[attr-defined] + assert item2.fspath == item.fspath assert item2.path == item.path @@ -90,7 +92,8 @@ def test_cache_makedir(cache: pytest.Cache) -> None: def test_fixturerequest_getmodulepath(pytester: pytest.Pytester) -> None: modcol = pytester.getmodulecol("def test_somefunc(): pass") (item,) = pytester.genitems([modcol]) - req = pytest.FixtureRequest(item, _ispytest=True) + assert isinstance(item, pytest.Function) + req = TopRequest(item, _ispytest=True) assert req.path == modcol.path assert req.fspath == modcol.fspath # type: ignore[attr-defined] @@ -105,7 +108,7 @@ def test_session_scoped_unavailable_attributes(self, session_request): AttributeError, match="path not available in session-scoped context", ): - session_request.fspath + _ = session_request.fspath @pytest.mark.parametrize("config_type", ["ini", "pyproject"]) @@ -152,7 +155,7 @@ def pytest_addoption(parser): ) pytester.makepyfile( r""" - def test_overriden(pytestconfig): + def test_overridden(pytestconfig): config_paths = pytestconfig.getini("paths") print(config_paths) for cpf in config_paths: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_link_resolve.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_link_resolve.py index 60a86ada36e56..0461cd75554cf 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_link_resolve.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_link_resolve.py @@ -1,10 +1,11 @@ +# mypy: allow-untyped-defs +from contextlib import contextmanager import os.path +from pathlib import Path +from string import ascii_lowercase import subprocess import sys import textwrap -from contextlib import contextmanager -from pathlib import Path -from string import ascii_lowercase from _pytest.pytester import Pytester @@ -59,7 +60,8 @@ def test_link_resolve(pytester: Pytester) -> None: def test_foo(): raise AssertionError() """ - ) + ), + encoding="utf-8", ) subst = subst_path_linux diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_main.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_main.py index 2df51bb7bb999..6294f66b360cd 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_main.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_main.py @@ -1,16 +1,17 @@ +# mypy: allow-untyped-defs import argparse import os -import re -import sys from pathlib import Path +import re from typing import Optional -import pytest from _pytest.config import ExitCode from _pytest.config import UsageError +from _pytest.main import CollectionArgument from _pytest.main import resolve_collection_argument from _pytest.main import validate_basetemp from _pytest.pytester import Pytester +import pytest @pytest.mark.parametrize( @@ -24,19 +25,17 @@ def test_wrap_session_notify_exception(ret_exc, pytester: Pytester) -> None: returncode, exc = ret_exc c1 = pytester.makeconftest( - """ + f""" import pytest def pytest_sessionstart(): - raise {exc}("boom") + raise {exc.__name__}("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 = pytester.runpytest() if returncode: @@ -45,32 +44,18 @@ def pytest_internalerror(excrepr, excinfo): assert result.ret == ExitCode.INTERNAL_ERROR assert result.stdout.lines[0] == "INTERNALERROR> Traceback (most recent call last):" - end_lines = ( - result.stdout.lines[-4:] - if sys.version_info >= (3, 11) - else result.stdout.lines[-3:] - ) + end_lines = result.stdout.lines[-3:] if exc == SystemExit: assert end_lines == [ f'INTERNALERROR> File "{c1}", line 4, in pytest_sessionstart', 'INTERNALERROR> raise SystemExit("boom")', - *( - ("INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^",) - if sys.version_info >= (3, 11) - else () - ), "INTERNALERROR> SystemExit: boom", ] else: assert end_lines == [ f'INTERNALERROR> File "{c1}", line 4, in pytest_sessionstart', 'INTERNALERROR> raise ValueError("boom")', - *( - ("INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^",) - if sys.version_info >= (3, 11) - else () - ), "INTERNALERROR> ValueError: boom", ] if returncode is False: @@ -84,13 +69,11 @@ def test_wrap_session_exit_sessionfinish( returncode: Optional[int], pytester: Pytester ) -> None: pytester.makeconftest( - """ + f""" import pytest def pytest_sessionfinish(): pytest.exit(reason="exit_pytest_sessionfinish", returncode={returncode}) - """.format( - returncode=returncode - ) + """ ) result = pytester.runpytest() if returncode: @@ -136,26 +119,43 @@ def invocation_path(self, pytester: Pytester) -> Path: def test_file(self, invocation_path: Path) -> None: """File and parts.""" - assert resolve_collection_argument(invocation_path, "src/pkg/test.py") == ( - invocation_path / "src/pkg/test.py", - [], + assert resolve_collection_argument( + invocation_path, "src/pkg/test.py" + ) == CollectionArgument( + path=invocation_path / "src/pkg/test.py", + parts=[], + module_name=None, ) - assert resolve_collection_argument(invocation_path, "src/pkg/test.py::") == ( - invocation_path / "src/pkg/test.py", - [""], + assert resolve_collection_argument( + invocation_path, "src/pkg/test.py::" + ) == CollectionArgument( + path=invocation_path / "src/pkg/test.py", + parts=[""], + module_name=None, ) assert resolve_collection_argument( invocation_path, "src/pkg/test.py::foo::bar" - ) == (invocation_path / "src/pkg/test.py", ["foo", "bar"]) + ) == CollectionArgument( + path=invocation_path / "src/pkg/test.py", + parts=["foo", "bar"], + module_name=None, + ) assert resolve_collection_argument( invocation_path, "src/pkg/test.py::foo::bar::" - ) == (invocation_path / "src/pkg/test.py", ["foo", "bar", ""]) + ) == CollectionArgument( + path=invocation_path / "src/pkg/test.py", + parts=["foo", "bar", ""], + module_name=None, + ) def test_dir(self, invocation_path: Path) -> None: """Directory and parts.""" - assert resolve_collection_argument(invocation_path, "src/pkg") == ( - invocation_path / "src/pkg", - [], + assert resolve_collection_argument( + invocation_path, "src/pkg" + ) == CollectionArgument( + path=invocation_path / "src/pkg", + parts=[], + module_name=None, ) with pytest.raises( @@ -172,13 +172,24 @@ def test_pypath(self, invocation_path: Path) -> None: """Dotted name and parts.""" assert resolve_collection_argument( invocation_path, "pkg.test", as_pypath=True - ) == (invocation_path / "src/pkg/test.py", []) + ) == CollectionArgument( + path=invocation_path / "src/pkg/test.py", + parts=[], + module_name="pkg.test", + ) assert resolve_collection_argument( invocation_path, "pkg.test::foo::bar", as_pypath=True - ) == (invocation_path / "src/pkg/test.py", ["foo", "bar"]) - assert resolve_collection_argument(invocation_path, "pkg", as_pypath=True) == ( - invocation_path / "src/pkg", - [], + ) == CollectionArgument( + path=invocation_path / "src/pkg/test.py", + parts=["foo", "bar"], + module_name="pkg.test", + ) + assert resolve_collection_argument( + invocation_path, "pkg", as_pypath=True + ) == CollectionArgument( + path=invocation_path / "src/pkg", + parts=[], + module_name="pkg", ) with pytest.raises( @@ -189,10 +200,13 @@ def test_pypath(self, invocation_path: Path) -> None: ) def test_parametrized_name_with_colons(self, invocation_path: Path) -> None: - ret = resolve_collection_argument( + assert resolve_collection_argument( invocation_path, "src/pkg/test.py::test[a::b]" + ) == CollectionArgument( + path=invocation_path / "src/pkg/test.py", + parts=["test[a::b]"], + module_name=None, ) - assert ret == (invocation_path / "src/pkg/test.py", ["test[a::b]"]) def test_does_not_exist(self, invocation_path: Path) -> None: """Given a file/module that does not exist raises UsageError.""" @@ -212,9 +226,12 @@ def test_does_not_exist(self, invocation_path: Path) -> None: def test_absolute_paths_are_resolved_correctly(self, invocation_path: Path) -> None: """Absolute paths resolve back to absolute paths.""" full_path = str(invocation_path / "src") - assert resolve_collection_argument(invocation_path, full_path) == ( - Path(os.path.abspath("src")), - [], + assert resolve_collection_argument( + invocation_path, full_path + ) == CollectionArgument( + path=Path(os.path.abspath("src")), + parts=[], + module_name=None, ) # ensure full paths given in the command-line without the drive letter resolve @@ -222,7 +239,11 @@ def test_absolute_paths_are_resolved_correctly(self, invocation_path: Path) -> N drive, full_path_without_drive = os.path.splitdrive(full_path) assert resolve_collection_argument( invocation_path, full_path_without_drive - ) == (Path(os.path.abspath("src")), []) + ) == CollectionArgument( + path=Path(os.path.abspath("src")), + parts=[], + module_name=None, + ) def test_module_full_path_without_drive(pytester: Pytester) -> None: @@ -262,3 +283,34 @@ def test(fix): "* 1 passed in *", ] ) + + +def test_very_long_cmdline_arg(pytester: Pytester) -> None: + """ + Regression test for #11394. + + Note: we could not manage to actually reproduce the error with this code, we suspect + GitHub runners are configured to support very long paths, however decided to leave + the test in place in case this ever regresses in the future. + """ + pytester.makeconftest( + """ + import pytest + + def pytest_addoption(parser): + parser.addoption("--long-list", dest="long_list", action="store", default="all", help="List of things") + + @pytest.fixture(scope="module") + def specified_feeds(request): + list_string = request.config.getoption("--long-list") + return list_string.split(',') + """ + ) + pytester.makepyfile( + """ + def test_foo(specified_feeds): + assert len(specified_feeds) == 100_000 + """ + ) + result = pytester.runpytest("--long-list", ",".join(["helloworld"] * 100_000)) + result.stdout.fnmatch_lines("* 1 passed *") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_mark.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_mark.py index da67d1ea7bcd6..2896afa453211 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_mark.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_mark.py @@ -1,23 +1,24 @@ +# mypy: allow-untyped-defs import os import sys from typing import List from typing import Optional from unittest import mock -import pytest from _pytest.config import ExitCode from _pytest.mark import MarkGenerator from _pytest.mark.structures import EMPTY_PARAMETERSET_OPTION from _pytest.nodes import Collector from _pytest.nodes import Node from _pytest.pytester import Pytester +import pytest class TestMark: @pytest.mark.parametrize("attr", ["mark", "param"]) def test_pytest_exists_in_namespace_all(self, attr: str) -> None: module = sys.modules["pytest"] - assert attr in module.__all__ # type: ignore + assert attr in module.__all__ def test_pytest_mark_notcallable(self) -> None: mark = MarkGenerator(_ispytest=True) @@ -33,7 +34,7 @@ class SomeClass: assert pytest.mark.foo(some_function) is 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 marked_with_args is not some_function assert pytest.mark.foo(SomeClass) is SomeClass assert pytest.mark.foo.with_args(SomeClass) is not SomeClass # type: ignore[comparison-overlap] @@ -41,7 +42,7 @@ class SomeClass: def test_pytest_mark_name_starts_with_underscore(self) -> None: mark = MarkGenerator(_ispytest=True) with pytest.raises(AttributeError): - mark._some_name + _ = mark._some_name def test_marked_class_run_twice(pytester: Pytester) -> None: @@ -806,12 +807,12 @@ def test_2(self): pytester.makepyfile( conftest=""" import pytest - @pytest.hookimpl(hookwrapper=True) + @pytest.hookimpl(wrapper=True) def pytest_pycollect_makeitem(name): - outcome = yield + item = yield if name == "TestClass": - item = outcome.get_result() item.extra_keyword_matches.add("xxx") + return item """ ) reprec = pytester.inline_run(p.parent, "-s", "-k", keyword) @@ -823,25 +824,6 @@ def pytest_pycollect_makeitem(name): assert len(dlist) == 1 assert dlist[0].items[0].name == "test_1" - def test_select_starton(self, pytester: Pytester) -> None: - threepass = pytester.makepyfile( - test_threepass=""" - def test_one(): assert 1 - def test_two(): assert 1 - def test_three(): assert 1 - """ - ) - reprec = pytester.inline_run( - "-Wignore::pytest.PytestRemovedIn7Warning", "-k", "test_two:", threepass - ) - passed, skipped, failed = reprec.listoutcomes() - assert len(passed) == 2 - assert not failed - dlist = reprec.getcalls("pytest_deselected") - assert len(dlist) == 1 - item = dlist[0].items[0] - assert item.name == "test_one" - def test_keyword_extra(self, pytester: Pytester) -> None: p = pytester.makepyfile( """ @@ -890,17 +872,30 @@ def test_one(): assert 1 deselected_tests = dlist[0].items assert len(deselected_tests) == 1 - def test_no_match_directories_outside_the_suite(self, pytester: Pytester) -> None: + def test_no_match_directories_outside_the_suite( + self, + pytester: Pytester, + monkeypatch: pytest.MonkeyPatch, + ) -> None: """`-k` should not match against directories containing the test suite (#7040).""" - test_contents = """ - def test_aaa(): pass - def test_ddd(): pass - """ + pytester.makefile( + **{ + "suite/pytest": """[pytest]""", + }, + ext=".ini", + ) pytester.makepyfile( - **{"ddd/tests/__init__.py": "", "ddd/tests/test_foo.py": test_contents} + **{ + "suite/ddd/tests/__init__.py": "", + "suite/ddd/tests/test_foo.py": """ + def test_aaa(): pass + def test_ddd(): pass + """, + } ) + monkeypatch.chdir(pytester.path / "suite") - def get_collected_names(*args): + def get_collected_names(*args: str) -> List[str]: _, rec = pytester.inline_genitems(*args) calls = rec.getcalls("pytest_collection_finish") assert len(calls) == 1 @@ -912,12 +907,6 @@ def get_collected_names(*args): # do not collect anything based on names outside the collection tree assert get_collected_names("-k", pytester._name) == [] - # "-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( @@ -945,16 +934,15 @@ def test_parameterset_for_parametrize_marks( ) -> None: if mark is not None: pytester.makeini( - """ + f""" [pytest] - {}={} - """.format( - EMPTY_PARAMETERSET_OPTION, mark - ) + {EMPTY_PARAMETERSET_OPTION}={mark} + """ ) config = pytester.parseconfig() - from _pytest.mark import pytest_configure, get_empty_parameterset_mark + from _pytest.mark import get_empty_parameterset_mark + from _pytest.mark import pytest_configure pytest_configure(config) result_mark = get_empty_parameterset_mark(config, ["a"], all) @@ -969,16 +957,15 @@ def test_parameterset_for_parametrize_marks( def test_parameterset_for_fail_at_collect(pytester: Pytester) -> None: pytester.makeini( - """ + f""" [pytest] - {}=fail_at_collect - """.format( - EMPTY_PARAMETERSET_OPTION - ) + {EMPTY_PARAMETERSET_OPTION}=fail_at_collect + """ ) config = pytester.parseconfig() - from _pytest.mark import pytest_configure, get_empty_parameterset_mark + from _pytest.mark import get_empty_parameterset_mark + from _pytest.mark import pytest_configure pytest_configure(config) @@ -1128,3 +1115,62 @@ def test_foo(): result = pytester.runpytest(foo, "-m", expr) result.stderr.fnmatch_lines([expected]) assert result.ret == ExitCode.USAGE_ERROR + + +def test_mark_mro() -> None: + xfail = pytest.mark.xfail + + @xfail("a") + class A: + pass + + @xfail("b") + class B: + pass + + @xfail("c") + class C(A, B): + pass + + from _pytest.mark.structures import get_unpacked_marks + + all_marks = get_unpacked_marks(C) + + assert all_marks == [xfail("b").mark, xfail("a").mark, xfail("c").mark] + + assert get_unpacked_marks(C, consider_mro=False) == [xfail("c").mark] + + +# @pytest.mark.issue("https://github.com/pytest-dev/pytest/issues/10447") +def test_mark_fixture_order_mro(pytester: Pytester): + """This ensures we walk marks of the mro starting with the base classes + the action at a distance fixtures are taken as minimal example from a real project + + """ + foo = pytester.makepyfile( + """ + import pytest + + @pytest.fixture + def add_attr1(request): + request.instance.attr1 = object() + + + @pytest.fixture + def add_attr2(request): + request.instance.attr2 = request.instance.attr1 + + + @pytest.mark.usefixtures('add_attr1') + class Parent: + pass + + + @pytest.mark.usefixtures('add_attr2') + class TestThings(Parent): + def test_attrs(self): + assert self.attr1 == self.attr2 + """ + ) + result = pytester.runpytest(foo) + result.assert_outcomes(passed=1) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_mark_expression.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_mark_expression.py index f3643e7b4098d..07c89f9083825 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_mark_expression.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_mark_expression.py @@ -1,8 +1,8 @@ from typing import Callable -import pytest from _pytest.mark.expression import Expression from _pytest.mark.expression import ParseError +import pytest def evaluate(input: str, matcher: Callable[[str], bool]) -> bool: @@ -61,7 +61,7 @@ def test_basic(expr: str, expected: bool) -> None: ("not not not not not true", False), ), ) -def test_syntax_oddeties(expr: str, expected: bool) -> None: +def test_syntax_oddities(expr: str, expected: bool) -> None: matcher = {"true": True, "false": False}.__getitem__ assert evaluate(expr, matcher) is expected diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_meta.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_meta.py index 9201bd2161120..40ed95d6b47a6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_meta.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_meta.py @@ -3,6 +3,7 @@ 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 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_monkeypatch.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_monkeypatch.py index 9552181802105..12be774beca43 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_monkeypatch.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_monkeypatch.py @@ -1,15 +1,16 @@ +# mypy: allow-untyped-defs import os +from pathlib import Path import re import sys import textwrap -from pathlib import Path from typing import Dict from typing import Generator from typing import Type -import pytest from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester +import pytest @pytest.fixture @@ -50,21 +51,24 @@ class A: class TestSetattrWithImportPath: def test_string_expression(self, monkeypatch: MonkeyPatch) -> None: - monkeypatch.setattr("os.path.abspath", lambda x: "hello2") - assert os.path.abspath("123") == "hello2" + with monkeypatch.context() as mp: + mp.setattr("os.path.abspath", lambda x: "hello2") + assert os.path.abspath("123") == "hello2" def test_string_expression_class(self, monkeypatch: MonkeyPatch) -> None: - monkeypatch.setattr("_pytest.config.Config", 42) - import _pytest + with monkeypatch.context() as mp: + mp.setattr("_pytest.config.Config", 42) + import _pytest - assert _pytest.config.Config == 42 # type: ignore + assert _pytest.config.Config == 42 # type: ignore def test_unicode_string(self, monkeypatch: MonkeyPatch) -> None: - monkeypatch.setattr("_pytest.config.Config", 42) - import _pytest + with monkeypatch.context() as mp: + mp.setattr("_pytest.config.Config", 42) + import _pytest - assert _pytest.config.Config == 42 # type: ignore - monkeypatch.delattr("_pytest.config.Config") + assert _pytest.config.Config == 42 # type: ignore + mp.delattr("_pytest.config.Config") def test_wrong_target(self, monkeypatch: MonkeyPatch) -> None: with pytest.raises(TypeError): @@ -80,14 +84,16 @@ def test_unknown_attr(self, monkeypatch: MonkeyPatch) -> None: 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 # type: ignore + with monkeypatch.context() as mp: + mp.setattr("os.path.qweqwe", 42, raising=False) + assert os.path.qweqwe == 42 # type: ignore def test_delattr(self, monkeypatch: MonkeyPatch) -> None: - monkeypatch.delattr("os.path.abspath") - assert not hasattr(os.path, "abspath") - monkeypatch.undo() - assert os.path.abspath + with monkeypatch.context() as mp: + mp.delattr("os.path.abspath") + assert not hasattr(os.path, "abspath") + mp.undo() + assert os.path.abspath # type:ignore[truthy-function] def test_delattr() -> None: @@ -319,7 +325,8 @@ def test_importerror(pytester: Pytester) -> None: x = 1 """ - ) + ), + encoding="utf-8", ) pytester.path.joinpath("test_importerror.py").write_text( textwrap.dedent( @@ -327,7 +334,8 @@ def test_importerror(pytester: Pytester) -> None: def test_importerror(monkeypatch): monkeypatch.setattr('package.a.x', 2) """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest() result.stdout.fnmatch_lines( @@ -420,6 +428,7 @@ class A: assert A.x == 1 +@pytest.mark.filterwarnings(r"ignore:.*\bpkg_resources\b:DeprecationWarning") def test_syspath_prepend_with_namespace_packages( pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: @@ -428,11 +437,13 @@ def test_syspath_prepend_with_namespace_packages( ns = d.joinpath("ns_pkg") ns.mkdir() ns.joinpath("__init__.py").write_text( - "__import__('pkg_resources').declare_namespace(__name__)" + "__import__('pkg_resources').declare_namespace(__name__)", encoding="utf-8" ) lib = ns.joinpath(dirname) lib.mkdir() - lib.joinpath("__init__.py").write_text("def check(): return %r" % dirname) + lib.joinpath("__init__.py").write_text( + "def check(): return %r" % dirname, encoding="utf-8" + ) monkeypatch.syspath_prepend("hello") import ns_pkg.hello @@ -451,5 +462,5 @@ def test_syspath_prepend_with_namespace_packages( # Should invalidate caches via importlib.invalidate_caches. modules_tmpdir = pytester.mkdir("modules_tmpdir") monkeypatch.syspath_prepend(str(modules_tmpdir)) - modules_tmpdir.joinpath("main_app.py").write_text("app = True") + modules_tmpdir.joinpath("main_app.py").write_text("app = True", encoding="utf-8") from main_app import app # noqa: F401 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_nodes.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_nodes.py index df1439e1c49cc..a3caf471f7075 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_nodes.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_nodes.py @@ -1,39 +1,16 @@ -import re -import warnings +# mypy: allow-untyped-defs from pathlib import Path +import re from typing import cast -from typing import List from typing import Type +import warnings -import pytest from _pytest import nodes from _pytest.compat import legacy_path from _pytest.outcomes import OutcomeException from _pytest.pytester import Pytester from _pytest.warning_types import PytestWarning - - -@pytest.mark.parametrize( - ("nodeid", "expected"), - ( - ("", [""]), - ("a", ["", "a"]), - ("aa/b", ["", "aa", "aa/b"]), - ("a/b/c", ["", "a", "a/b", "a/b/c"]), - ("a/bbb/c::D", ["", "a", "a/bbb", "a/bbb/c", "a/bbb/c::D"]), - ("a/b/c::D::eee", ["", "a", "a/b", "a/b/c", "a/b/c::D", "a/b/c::D::eee"]), - ("::xx", ["", "::xx"]), - # / only considered until first :: - ("a/b/c::D/d::e", ["", "a", "a/b", "a/b/c", "a/b/c::D/d", "a/b/c::D/d::e"]), - # : alone is not a separator. - ("a/b::D:e:f::g", ["", "a", "a/b", "a/b::D:e:f", "a/b::D:e:f::g"]), - # / not considered if a part of a test name - ("a/b::c/d::e[/test]", ["", "a", "a/b", "a/b::c/d", "a/b::c/d::e[/test]"]), - ), -) -def test_iterparentnodeids(nodeid: str, expected: List[str]) -> None: - result = list(nodes.iterparentnodeids(nodeid)) - assert result == expected +import pytest def test_node_from_parent_disallowed_arguments() -> None: @@ -63,7 +40,6 @@ def test_subclassing_both_item_and_collector_deprecated( Verifies we warn on diamond inheritance as well as correctly managing legacy inheritance constructors with missing args as found in plugins. """ - # We do not expect any warnings messages to issued during class definition. with warnings.catch_warnings(): warnings.simplefilter("error") @@ -73,6 +49,12 @@ def __init__(self, fspath, parent): """Legacy ctor with legacy call # don't wana see""" super().__init__(fspath, parent) + def collect(self): + raise NotImplementedError() + + def runtest(self): + raise NotImplementedError() + with pytest.warns(PytestWarning) as rec: SoWrong.from_parent( request.session, fspath=legacy_path(tmp_path / "broken.txt") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_parseopt.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_parseopt.py index 28529d0437832..e959dfd631bc6 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_parseopt.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_parseopt.py @@ -1,15 +1,17 @@ +# mypy: allow-untyped-defs import argparse +import locale import os +from pathlib import Path import shlex import subprocess import sys -from pathlib import Path -import pytest from _pytest.config import argparsing as parseopt from _pytest.config.exceptions import UsageError from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester +import pytest @pytest.fixture @@ -53,9 +55,6 @@ def test_argument_type(self) -> None: assert argument.type is str argument = parseopt.Argument("-t", dest="abc", type=float) assert argument.type is float - with pytest.warns(DeprecationWarning): - with pytest.raises(KeyError): - argument = parseopt.Argument("-t", dest="abc", type="choice") argument = parseopt.Argument( "-t", dest="abc", type=str, choices=["red", "blue"] ) @@ -126,6 +125,17 @@ def test_parse2(self, parser: parseopt.Parser) -> None: args = parser.parse([Path(".")]) assert getattr(args, parseopt.FILE_OR_DIR)[0] == "." + # Warning ignore because of: + # https://github.com/python/cpython/issues/85308 + # Can be removed once Python<3.12 support is dropped. + @pytest.mark.filterwarnings("ignore:'encoding' argument not specified") + def test_parse_from_file(self, parser: parseopt.Parser, tmp_path: Path) -> None: + tests = [".", "some.py::Test::test_method[param0]", "other/test_file.py"] + args_file = tmp_path / "tests.txt" + args_file.write_text("\n".join(tests), encoding="utf-8") + args = parser.parse([f"@{args_file.absolute()}"]) + assert getattr(args, parseopt.FILE_OR_DIR) == tests + def test_parse_known_args(self, parser: parseopt.Parser) -> None: parser.parse_known_args([Path(".")]) parser.addoption("--hello", action="store_true") @@ -289,13 +299,19 @@ def test_multiple_metavar_help(self, parser: parseopt.Parser) -> None: def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: + if sys.version_info >= (3, 11): + # New in Python 3.11, ignores utf-8 mode + encoding = locale.getencoding() + else: + encoding = locale.getpreferredencoding(False) try: bash_version = subprocess.run( ["bash", "--version"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=True, - universal_newlines=True, + text=True, + encoding=encoding, ).stdout except (OSError, subprocess.CalledProcessError): pytest.skip("bash is not available") @@ -305,14 +321,12 @@ def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: script = str(pytester.path.joinpath("test_argcomplete")) - with open(str(script), "w") as fp: + with open(str(script), "w", encoding="utf-8") 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" {} -m pytest 8>&1 9>&2'.format( - shlex.quote(sys.executable) - ) + f'COMP_WORDBREAKS="$COMP_WORDBREAKS" {shlex.quote(sys.executable)} -m pytest 8>&1 9>&2' ) # alternative would be extended Pytester.{run(),_run(),popen()} to be able # to handle a keyword argument env that replaces os.environ in popen or @@ -330,9 +344,7 @@ def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: pytest.skip("argcomplete not available") elif not result.stdout.str(): pytest.skip( - "bash provided no output on stdout, argcomplete not available? (stderr={!r})".format( - result.stderr.str() - ) + f"bash provided no output on stdout, argcomplete not available? (stderr={result.stderr.str()!r})" ) else: result.stdout.fnmatch_lines(["--funcargs", "--fulltrace"]) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pastebin.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pastebin.py index b338519ae17b1..651a04da84aae 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pastebin.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pastebin.py @@ -1,10 +1,12 @@ +# mypy: allow-untyped-defs +import email.message import io from typing import List from typing import Union -import pytest from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester +import pytest class TestPasteCapture: @@ -98,7 +100,9 @@ def mocked_urlopen_fail(self, monkeypatch: MonkeyPatch): def mocked(url, data): calls.append((url, data)) - raise urllib.error.HTTPError(url, 400, "Bad request", {}, io.BytesIO()) + raise urllib.error.HTTPError( + url, 400, "Bad request", email.message.Message(), io.BytesIO() + ) monkeypatch.setattr(urllib.request, "urlopen", mocked) return calls diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pathlib.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pathlib.py index 5eb153e847df4..688d13f2f053f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pathlib.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pathlib.py @@ -1,30 +1,61 @@ +# mypy: allow-untyped-defs +import errno +import importlib.abc +import importlib.machinery import os.path +from pathlib import Path import pickle +import shutil import sys -import unittest.mock -from pathlib import Path from textwrap import dedent from types import ModuleType from typing import Any from typing import Generator +from typing import Iterator +from typing import Optional +from typing import Sequence +from typing import Tuple +import unittest.mock -import pytest from _pytest.monkeypatch import MonkeyPatch from _pytest.pathlib import bestrelpath from _pytest.pathlib import commonpath +from _pytest.pathlib import compute_module_name +from _pytest.pathlib import CouldNotResolvePathError 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 ImportMode from _pytest.pathlib import ImportPathMismatchError from _pytest.pathlib import insert_missing_modules +from _pytest.pathlib import is_importable from _pytest.pathlib import maybe_delete_a_numbered_dir from _pytest.pathlib import module_name_from_path from _pytest.pathlib import resolve_package_path +from _pytest.pathlib import resolve_pkg_root_and_module_name +from _pytest.pathlib import safe_exists from _pytest.pathlib import symlink_or_skip from _pytest.pathlib import visit +from _pytest.pytester import Pytester +from _pytest.pytester import RunResult from _pytest.tmpdir import TempPathFactory +import pytest + + +@pytest.fixture(autouse=True) +def autouse_pytester(pytester: Pytester) -> None: + """ + Fixture to make pytester() being autouse for all tests in this module. + + pytester makes sure to restore sys.path to its previous state, and many tests in this module + import modules and change sys.path because of that, so common module names such as "test" or "test.conftest" + end up leaking to tests in other modules. + + Note: we might consider extracting the sys.path restoration aspect into its own fixture, and apply it + to the entire test suite always. + """ class TestFNMatcherPort: @@ -76,6 +107,15 @@ def test_not_matching(self, pattern: str, path: str) -> None: assert not fnmatch_ex(pattern, path) +@pytest.fixture(params=[True, False]) +def ns_param(request: pytest.FixtureRequest) -> bool: + """ + Simple parametrized fixture for tests which call import_path() with consider_namespace_packages + using True and False. + """ + return bool(request.param) + + class TestImportPath: """ @@ -91,16 +131,22 @@ def path1(self, tmp_path_factory: TempPathFactory) -> Generator[Path, None, None yield path assert path.joinpath("samplefile").exists() + @pytest.fixture(autouse=True) + def preserve_sys(self): + with unittest.mock.patch.dict(sys.modules): + with unittest.mock.patch.object(sys, "path", list(sys.path)): + yield + def setuptestfs(self, path: Path) -> None: # print "setting up test fs for", repr(path) samplefile = path / "samplefile" - samplefile.write_text("samplefile\n") + samplefile.write_text("samplefile\n", encoding="utf-8") execfile = path / "execfile" - execfile.write_text("x=42") + execfile.write_text("x=42", encoding="utf-8") execfilepy = path / "execfile.py" - execfilepy.write_text("x=42") + execfilepy.write_text("x=42", encoding="utf-8") d = {1: 2, "hello": "world", "answer": 42} path.joinpath("samplepickle").write_bytes(pickle.dumps(d, 1)) @@ -114,9 +160,9 @@ def setuptestfs(self, path: Path) -> None: otherdir.joinpath("__init__.py").touch() module_a = otherdir / "a.py" - module_a.write_text("from .b import stuff as result\n") + module_a.write_text("from .b import stuff as result\n", encoding="utf-8") module_b = otherdir / "b.py" - module_b.write_text('stuff="got it"\n') + module_b.write_text('stuff="got it"\n', encoding="utf-8") module_c = otherdir / "c.py" module_c.write_text( dedent( @@ -125,7 +171,8 @@ def setuptestfs(self, path: Path) -> None: import otherdir.a value = otherdir.a.result """ - ) + ), + encoding="utf-8", ) module_d = otherdir / "d.py" module_d.write_text( @@ -135,181 +182,249 @@ def setuptestfs(self, path: Path) -> None: from otherdir import a value2 = a.result """ - ) + ), + encoding="utf-8", ) - def test_smoke_test(self, path1: Path) -> None: - obj = import_path(path1 / "execfile.py", root=path1) - assert obj.x == 42 # type: ignore[attr-defined] + def test_smoke_test(self, path1: Path, ns_param: bool) -> None: + obj = import_path( + path1 / "execfile.py", root=path1, consider_namespace_packages=ns_param + ) + assert obj.x == 42 assert obj.__name__ == "execfile" + def test_import_path_missing_file(self, path1: Path, ns_param: bool) -> None: + with pytest.raises(ImportPathMismatchError): + import_path( + path1 / "sampledir", root=path1, consider_namespace_packages=ns_param + ) + def test_renamed_dir_creates_mismatch( - self, tmp_path: Path, monkeypatch: MonkeyPatch + self, tmp_path: Path, monkeypatch: MonkeyPatch, ns_param: bool ) -> None: tmp_path.joinpath("a").mkdir() p = tmp_path.joinpath("a", "test_x123.py") p.touch() - import_path(p, root=tmp_path) + import_path(p, root=tmp_path, consider_namespace_packages=ns_param) tmp_path.joinpath("a").rename(tmp_path.joinpath("b")) with pytest.raises(ImportPathMismatchError): - import_path(tmp_path.joinpath("b", "test_x123.py"), root=tmp_path) + import_path( + tmp_path.joinpath("b", "test_x123.py"), + root=tmp_path, + consider_namespace_packages=ns_param, + ) # Errors can be ignored. monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "1") - import_path(tmp_path.joinpath("b", "test_x123.py"), root=tmp_path) + import_path( + tmp_path.joinpath("b", "test_x123.py"), + root=tmp_path, + consider_namespace_packages=ns_param, + ) # PY_IGNORE_IMPORTMISMATCH=0 does not ignore error. monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "0") with pytest.raises(ImportPathMismatchError): - import_path(tmp_path.joinpath("b", "test_x123.py"), root=tmp_path) + import_path( + tmp_path.joinpath("b", "test_x123.py"), + root=tmp_path, + consider_namespace_packages=ns_param, + ) - def test_messy_name(self, tmp_path: Path) -> None: + def test_messy_name(self, tmp_path: Path, ns_param: bool) -> None: # https://bitbucket.org/hpk42/py-trunk/issue/129 path = tmp_path / "foo__init__.py" path.touch() - module = import_path(path, root=tmp_path) + module = import_path(path, root=tmp_path, consider_namespace_packages=ns_param) assert module.__name__ == "foo__init__" - def test_dir(self, tmp_path: Path) -> None: + def test_dir(self, tmp_path: Path, ns_param: bool) -> None: p = tmp_path / "hello_123" p.mkdir() p_init = p / "__init__.py" p_init.touch() - m = import_path(p, root=tmp_path) + m = import_path(p, root=tmp_path, consider_namespace_packages=ns_param) assert m.__name__ == "hello_123" - m = import_path(p_init, root=tmp_path) + m = import_path(p_init, root=tmp_path, consider_namespace_packages=ns_param) assert m.__name__ == "hello_123" - def test_a(self, path1: Path) -> None: + def test_a(self, path1: Path, ns_param: bool) -> None: otherdir = path1 / "otherdir" - mod = import_path(otherdir / "a.py", root=path1) - assert mod.result == "got it" # type: ignore[attr-defined] + mod = import_path( + otherdir / "a.py", root=path1, consider_namespace_packages=ns_param + ) + assert mod.result == "got it" assert mod.__name__ == "otherdir.a" - def test_b(self, path1: Path) -> None: + def test_b(self, path1: Path, ns_param: bool) -> None: otherdir = path1 / "otherdir" - mod = import_path(otherdir / "b.py", root=path1) - assert mod.stuff == "got it" # type: ignore[attr-defined] + mod = import_path( + otherdir / "b.py", root=path1, consider_namespace_packages=ns_param + ) + assert mod.stuff == "got it" assert mod.__name__ == "otherdir.b" - def test_c(self, path1: Path) -> None: + def test_c(self, path1: Path, ns_param: bool) -> None: otherdir = path1 / "otherdir" - mod = import_path(otherdir / "c.py", root=path1) - assert mod.value == "got it" # type: ignore[attr-defined] + mod = import_path( + otherdir / "c.py", root=path1, consider_namespace_packages=ns_param + ) + assert mod.value == "got it" - def test_d(self, path1: Path) -> None: + def test_d(self, path1: Path, ns_param: bool) -> None: otherdir = path1 / "otherdir" - mod = import_path(otherdir / "d.py", root=path1) - assert mod.value2 == "got it" # type: ignore[attr-defined] + mod = import_path( + otherdir / "d.py", root=path1, consider_namespace_packages=ns_param + ) + assert mod.value2 == "got it" - def test_import_after(self, tmp_path: Path) -> None: + def test_import_after(self, tmp_path: Path, ns_param: bool) -> None: tmp_path.joinpath("xxxpackage").mkdir() tmp_path.joinpath("xxxpackage", "__init__.py").touch() mod1path = tmp_path.joinpath("xxxpackage", "module1.py") mod1path.touch() - mod1 = import_path(mod1path, root=tmp_path) + mod1 = import_path( + mod1path, root=tmp_path, consider_namespace_packages=ns_param + ) assert mod1.__name__ == "xxxpackage.module1" from xxxpackage import module1 assert module1 is mod1 def test_check_filepath_consistency( - self, monkeypatch: MonkeyPatch, tmp_path: Path + self, monkeypatch: MonkeyPatch, tmp_path: Path, ns_param: bool ) -> None: name = "pointsback123" p = tmp_path.joinpath(name + ".py") p.touch() - for ending in (".pyc", ".pyo"): - mod = ModuleType(name) - pseudopath = tmp_path.joinpath(name + ending) - pseudopath.touch() - mod.__file__ = str(pseudopath) - monkeypatch.setitem(sys.modules, name, mod) - newmod = import_path(p, root=tmp_path) - assert mod == newmod - monkeypatch.undo() + with monkeypatch.context() as mp: + for ending in (".pyc", ".pyo"): + mod = ModuleType(name) + pseudopath = tmp_path.joinpath(name + ending) + pseudopath.touch() + mod.__file__ = str(pseudopath) + mp.setitem(sys.modules, name, mod) + newmod = import_path( + p, root=tmp_path, consider_namespace_packages=ns_param + ) + assert mod == newmod mod = ModuleType(name) pseudopath = tmp_path.joinpath(name + "123.py") pseudopath.touch() mod.__file__ = str(pseudopath) monkeypatch.setitem(sys.modules, name, mod) with pytest.raises(ImportPathMismatchError) as excinfo: - import_path(p, root=tmp_path) + import_path(p, root=tmp_path, consider_namespace_packages=ns_param) modname, modfile, orig = excinfo.value.args assert modname == name assert modfile == str(pseudopath) assert orig == p assert issubclass(ImportPathMismatchError, ImportError) - def test_issue131_on__init__(self, tmp_path: Path) -> None: - # __init__.py files may be namespace packages, and thus the - # __file__ of an imported module may not be ourselves - # see issue - tmp_path.joinpath("proja").mkdir() - p1 = tmp_path.joinpath("proja", "__init__.py") - p1.touch() - tmp_path.joinpath("sub", "proja").mkdir(parents=True) - p2 = tmp_path.joinpath("sub", "proja", "__init__.py") - p2.touch() - m1 = import_path(p1, root=tmp_path) - m2 = import_path(p2, root=tmp_path) - assert m1 == m2 - - def test_ensuresyspath_append(self, tmp_path: Path) -> None: + def test_ensuresyspath_append(self, tmp_path: Path, ns_param: bool) -> None: root1 = tmp_path / "root1" root1.mkdir() file1 = root1 / "x123.py" file1.touch() assert str(root1) not in sys.path - import_path(file1, mode="append", root=tmp_path) + import_path( + file1, mode="append", root=tmp_path, consider_namespace_packages=ns_param + ) assert str(root1) == sys.path[-1] assert str(root1) not in sys.path[:-1] - def test_invalid_path(self, tmp_path: Path) -> None: + def test_invalid_path(self, tmp_path: Path, ns_param: bool) -> None: with pytest.raises(ImportError): - import_path(tmp_path / "invalid.py", root=tmp_path) + import_path( + tmp_path / "invalid.py", + root=tmp_path, + consider_namespace_packages=ns_param, + ) @pytest.fixture - def simple_module(self, tmp_path: Path) -> Path: - fn = tmp_path / "_src/tests/mymod.py" + def simple_module( + self, tmp_path: Path, request: pytest.FixtureRequest + ) -> Iterator[Path]: + name = f"mymod_{request.node.name}" + fn = tmp_path / f"_src/tests/{name}.py" fn.parent.mkdir(parents=True) - fn.write_text("def foo(x): return 40 + x") - return fn - - def test_importmode_importlib(self, simple_module: Path, tmp_path: Path) -> None: + fn.write_text("def foo(x): return 40 + x", encoding="utf-8") + module_name = module_name_from_path(fn, root=tmp_path) + yield fn + sys.modules.pop(module_name, None) + + def test_importmode_importlib( + self, + simple_module: Path, + tmp_path: Path, + request: pytest.FixtureRequest, + ns_param: bool, + ) -> None: """`importlib` mode does not change sys.path.""" - module = import_path(simple_module, mode="importlib", root=tmp_path) - assert module.foo(2) == 42 # type: ignore[attr-defined] + module = import_path( + simple_module, + mode="importlib", + root=tmp_path, + consider_namespace_packages=ns_param, + ) + assert module.foo(2) == 42 assert str(simple_module.parent) not in sys.path assert module.__name__ in sys.modules - assert module.__name__ == "_src.tests.mymod" + assert module.__name__ == f"_src.tests.mymod_{request.node.name}" assert "_src" in sys.modules assert "_src.tests" in sys.modules - def test_importmode_twice_is_different_module( - self, simple_module: Path, tmp_path: Path + def test_remembers_previous_imports( + self, simple_module: Path, tmp_path: Path, ns_param: bool ) -> None: - """`importlib` mode always returns a new module.""" - module1 = import_path(simple_module, mode="importlib", root=tmp_path) - module2 = import_path(simple_module, mode="importlib", root=tmp_path) - assert module1 is not module2 + """`importlib` mode called remembers previous module (#10341, #10811).""" + module1 = import_path( + simple_module, + mode="importlib", + root=tmp_path, + consider_namespace_packages=ns_param, + ) + module2 = import_path( + simple_module, + mode="importlib", + root=tmp_path, + consider_namespace_packages=ns_param, + ) + assert module1 is module2 def test_no_meta_path_found( - self, simple_module: Path, monkeypatch: MonkeyPatch, tmp_path: Path + self, + simple_module: Path, + monkeypatch: MonkeyPatch, + tmp_path: Path, + ns_param: bool, ) -> None: """Even without any meta_path should still import module.""" monkeypatch.setattr(sys, "meta_path", []) - module = import_path(simple_module, mode="importlib", root=tmp_path) - assert module.foo(2) == 42 # type: ignore[attr-defined] + module = import_path( + simple_module, + mode="importlib", + root=tmp_path, + consider_namespace_packages=ns_param, + ) + assert module.foo(2) == 42 # mode='importlib' fails if no spec is found to load the module import importlib.util + # Force module to be re-imported. + del sys.modules[module.__name__] + monkeypatch.setattr( importlib.util, "spec_from_file_location", lambda *args: None ) with pytest.raises(ImportError): - import_path(simple_module, mode="importlib", root=tmp_path) + import_path( + simple_module, + mode="importlib", + root=tmp_path, + consider_namespace_packages=False, + ) def test_resolve_package_path(tmp_path: Path) -> None: @@ -319,18 +434,18 @@ def test_resolve_package_path(tmp_path: Path) -> None: (pkg / "subdir").mkdir() (pkg / "subdir/__init__.py").touch() assert resolve_package_path(pkg) == pkg - assert resolve_package_path(pkg.joinpath("subdir", "__init__.py")) == pkg + assert resolve_package_path(pkg / "subdir/__init__.py") == pkg def test_package_unimportable(tmp_path: Path) -> None: pkg = tmp_path / "pkg1-1" pkg.mkdir() pkg.joinpath("__init__.py").touch() - subdir = pkg.joinpath("subdir") + subdir = pkg / "subdir" subdir.mkdir() - pkg.joinpath("subdir/__init__.py").touch() + (pkg / "subdir/__init__.py").touch() assert resolve_package_path(subdir) == subdir - xyz = subdir.joinpath("xyz.py") + xyz = subdir / "xyz.py" xyz.touch() assert resolve_package_path(xyz) == subdir assert not resolve_package_path(pkg) @@ -437,7 +552,7 @@ def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> N return False, even when they are clearly equal. """ module_path = tmp_path.joinpath("my_module.py") - module_path.write_text("def foo(): return 42") + module_path.write_text("def foo(): return 42", encoding="utf-8") monkeypatch.syspath_prepend(tmp_path) with monkeypatch.context() as mp: @@ -445,13 +560,16 @@ def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> N # the paths too. Using a context to narrow the patch as much as possible given # this is an important system function. mp.setattr(os.path, "samefile", lambda x, y: False) - module = import_path(module_path, root=tmp_path) + module = import_path( + module_path, root=tmp_path, consider_namespace_packages=False + ) assert getattr(module, "foo")() == 42 class TestImportLibMode: - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") - def test_importmode_importlib_with_dataclass(self, tmp_path: Path) -> None: + def test_importmode_importlib_with_dataclass( + self, tmp_path: Path, ns_param: bool + ) -> None: """Ensure that importlib mode works with a module containing dataclasses (#7856).""" fn = tmp_path.joinpath("_src/tests/test_dataclass.py") fn.parent.mkdir(parents=True) @@ -464,16 +582,27 @@ def test_importmode_importlib_with_dataclass(self, tmp_path: Path) -> None: class Data: value: str """ - ) + ), + encoding="utf-8", ) - module = import_path(fn, mode="importlib", root=tmp_path) + module = import_path( + fn, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param + ) Data: Any = getattr(module, "Data") data = Data(value="foo") assert data.value == "foo" assert data.__module__ == "_src.tests.test_dataclass" - def test_importmode_importlib_with_pickle(self, tmp_path: Path) -> None: + # Ensure we do not import the same module again (#11475). + module2 = import_path( + fn, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param + ) + assert module is module2 + + def test_importmode_importlib_with_pickle( + self, tmp_path: Path, ns_param: bool + ) -> None: """Ensure that importlib mode works with pickle (#7859).""" fn = tmp_path.joinpath("_src/tests/test_pickle.py") fn.parent.mkdir(parents=True) @@ -489,16 +618,25 @@ def round_trip(): s = pickle.dumps(_action) return pickle.loads(s) """ - ) + ), + encoding="utf-8", ) - module = import_path(fn, mode="importlib", root=tmp_path) + module = import_path( + fn, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param + ) round_trip = getattr(module, "round_trip") action = round_trip() assert action() == 42 + # Ensure we do not import the same module again (#11475). + module2 = import_path( + fn, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param + ) + assert module is module2 + def test_importmode_importlib_with_pickle_separate_modules( - self, tmp_path: Path + self, tmp_path: Path, ns_param: bool ) -> None: """ Ensure that importlib mode works can load pickles that look similar but are @@ -509,14 +647,15 @@ def test_importmode_importlib_with_pickle_separate_modules( fn1.write_text( dedent( """ - import attr + import dataclasses import pickle - @attr.s(auto_attribs=True) + @dataclasses.dataclass class Data: x: int = 42 """ - ) + ), + encoding="utf-8", ) fn2 = tmp_path.joinpath("_src/m2/tests/test.py") @@ -524,14 +663,15 @@ class Data: fn2.write_text( dedent( """ - import attr + import dataclasses import pickle - @attr.s(auto_attribs=True) + @dataclasses.dataclass class Data: x: str = "" """ - ) + ), + encoding="utf-8", ) import pickle @@ -540,10 +680,14 @@ def round_trip(obj): s = pickle.dumps(obj) return pickle.loads(s) - module = import_path(fn1, mode="importlib", root=tmp_path) + module = import_path( + fn1, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param + ) Data1 = getattr(module, "Data") - module = import_path(fn2, mode="importlib", root=tmp_path) + module = import_path( + fn2, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param + ) Data2 = getattr(module, "Data") assert round_trip(Data1(20)) == Data1(20) @@ -559,16 +703,864 @@ def test_module_name_from_path(self, tmp_path: Path) -> None: result = module_name_from_path(Path("/home/foo/test_foo.py"), Path("/bar")) assert result == "home.foo.test_foo" - def test_insert_missing_modules(self) -> None: - modules = {"src.tests.foo": ModuleType("src.tests.foo")} - insert_missing_modules(modules, "src.tests.foo") - assert sorted(modules) == ["src", "src.tests", "src.tests.foo"] + # Importing __init__.py files should return the package as module name. + result = module_name_from_path(tmp_path / "src/app/__init__.py", tmp_path) + assert result == "src.app" + + # Unless __init__.py file is at the root, in which case we cannot have an empty module name. + result = module_name_from_path(tmp_path / "__init__.py", tmp_path) + assert result == "__init__" + + # Modules which start with "." are considered relative and will not be imported + # unless part of a package, so we replace it with a "_" when generating the fake module name. + result = module_name_from_path(tmp_path / ".env/tests/test_foo.py", tmp_path) + assert result == "_env.tests.test_foo" + + # We want to avoid generating extra intermediate modules if some directory just happens + # to contain a "." in the name. + result = module_name_from_path( + tmp_path / ".env.310/tests/test_foo.py", tmp_path + ) + assert result == "_env_310.tests.test_foo" + + def test_resolve_pkg_root_and_module_name( + self, tmp_path: Path, monkeypatch: MonkeyPatch, pytester: Pytester + ) -> None: + # Create a directory structure first without __init__.py files. + (tmp_path / "src/app/core").mkdir(parents=True) + models_py = tmp_path / "src/app/core/models.py" + models_py.touch() + + with pytest.raises(CouldNotResolvePathError): + _ = resolve_pkg_root_and_module_name(models_py) + + # Create the __init__.py files, it should now resolve to a proper module name. + (tmp_path / "src/app/__init__.py").touch() + (tmp_path / "src/app/core/__init__.py").touch() + assert resolve_pkg_root_and_module_name( + models_py, consider_namespace_packages=True + ) == ( + tmp_path / "src", + "app.core.models", + ) + + # If we add tmp_path to sys.path, src becomes a namespace package. + monkeypatch.syspath_prepend(tmp_path) + validate_namespace_package(pytester, [tmp_path], ["src.app.core.models"]) + + assert resolve_pkg_root_and_module_name( + models_py, consider_namespace_packages=True + ) == ( + tmp_path, + "src.app.core.models", + ) + assert resolve_pkg_root_and_module_name( + models_py, consider_namespace_packages=False + ) == ( + tmp_path / "src", + "app.core.models", + ) + + def test_insert_missing_modules( + self, monkeypatch: MonkeyPatch, tmp_path: Path + ) -> None: + monkeypatch.chdir(tmp_path) + # Use 'xxx' and 'xxy' as parent names as they are unlikely to exist and + # don't end up being imported. + modules = {"xxx.tests.foo": ModuleType("xxx.tests.foo")} + insert_missing_modules(modules, "xxx.tests.foo") + assert sorted(modules) == ["xxx", "xxx.tests", "xxx.tests.foo"] mod = ModuleType("mod", doc="My Module") - modules = {"src": mod} - insert_missing_modules(modules, "src") - assert modules == {"src": mod} + modules = {"xxy": mod} + insert_missing_modules(modules, "xxy") + assert modules == {"xxy": mod} modules = {} insert_missing_modules(modules, "") assert modules == {} + + def test_parent_contains_child_module_attribute( + self, monkeypatch: MonkeyPatch, tmp_path: Path + ): + monkeypatch.chdir(tmp_path) + # Use 'xxx' and 'xxy' as parent names as they are unlikely to exist and + # don't end up being imported. + modules = {"xxx.tests.foo": ModuleType("xxx.tests.foo")} + insert_missing_modules(modules, "xxx.tests.foo") + assert sorted(modules) == ["xxx", "xxx.tests", "xxx.tests.foo"] + assert modules["xxx"].tests is modules["xxx.tests"] + assert modules["xxx.tests"].foo is modules["xxx.tests.foo"] + + def test_importlib_package( + self, monkeypatch: MonkeyPatch, tmp_path: Path, ns_param: bool + ): + """ + Importing a package using --importmode=importlib should not import the + package's __init__.py file more than once (#11306). + """ + monkeypatch.chdir(tmp_path) + monkeypatch.syspath_prepend(tmp_path) + + package_name = "importlib_import_package" + tmp_path.joinpath(package_name).mkdir() + init = tmp_path.joinpath(f"{package_name}/__init__.py") + init.write_text( + dedent( + """ + from .singleton import Singleton + + instance = Singleton() + """ + ), + encoding="ascii", + ) + singleton = tmp_path.joinpath(f"{package_name}/singleton.py") + singleton.write_text( + dedent( + """ + class Singleton: + INSTANCES = [] + + def __init__(self) -> None: + self.INSTANCES.append(self) + if len(self.INSTANCES) > 1: + raise RuntimeError("Already initialized") + """ + ), + encoding="ascii", + ) + + mod = import_path( + init, + root=tmp_path, + mode=ImportMode.importlib, + consider_namespace_packages=ns_param, + ) + assert len(mod.instance.INSTANCES) == 1 + # Ensure we do not import the same module again (#11475). + mod2 = import_path( + init, + root=tmp_path, + mode=ImportMode.importlib, + consider_namespace_packages=ns_param, + ) + assert mod is mod2 + + def test_importlib_root_is_package(self, pytester: Pytester) -> None: + """ + Regression for importing a `__init__`.py file that is at the root + (#11417). + """ + pytester.makepyfile(__init__="") + pytester.makepyfile( + """ + def test_my_test(): + assert True + """ + ) + + result = pytester.runpytest("--import-mode=importlib") + result.stdout.fnmatch_lines("* 1 passed *") + + def create_installed_doctests_and_tests_dir( + self, path: Path, monkeypatch: MonkeyPatch + ) -> Tuple[Path, Path, Path]: + """ + Create a directory structure where the application code is installed in a virtual environment, + and the tests are in an outside ".tests" directory. + + Return the paths to the core module (installed in the virtualenv), and the test modules. + """ + app = path / "src/app" + app.mkdir(parents=True) + (app / "__init__.py").touch() + core_py = app / "core.py" + core_py.write_text( + dedent( + """ + def foo(): + ''' + >>> 1 + 1 + 2 + ''' + """ + ), + encoding="ascii", + ) + + # Install it into a site-packages directory, and add it to sys.path, mimicking what + # happens when installing into a virtualenv. + site_packages = path / ".env/lib/site-packages" + site_packages.mkdir(parents=True) + shutil.copytree(app, site_packages / "app") + assert (site_packages / "app/core.py").is_file() + + monkeypatch.syspath_prepend(site_packages) + + # Create the tests files, outside 'src' and the virtualenv. + # We use the same test name on purpose, but in different directories, to ensure + # this works as advertised. + conftest_path1 = path / ".tests/a/conftest.py" + conftest_path1.parent.mkdir(parents=True) + conftest_path1.write_text( + dedent( + """ + import pytest + @pytest.fixture + def a_fix(): return "a" + """ + ), + encoding="ascii", + ) + test_path1 = path / ".tests/a/test_core.py" + test_path1.write_text( + dedent( + """ + import app.core + def test(a_fix): + assert a_fix == "a" + """, + ), + encoding="ascii", + ) + + conftest_path2 = path / ".tests/b/conftest.py" + conftest_path2.parent.mkdir(parents=True) + conftest_path2.write_text( + dedent( + """ + import pytest + @pytest.fixture + def b_fix(): return "b" + """ + ), + encoding="ascii", + ) + + test_path2 = path / ".tests/b/test_core.py" + test_path2.write_text( + dedent( + """ + import app.core + def test(b_fix): + assert b_fix == "b" + """, + ), + encoding="ascii", + ) + return (site_packages / "app/core.py"), test_path1, test_path2 + + def test_import_using_normal_mechanism_first( + self, monkeypatch: MonkeyPatch, pytester: Pytester, ns_param: bool + ) -> None: + """ + Test import_path imports from the canonical location when possible first, only + falling back to its normal flow when the module being imported is not reachable via sys.path (#11475). + """ + core_py, test_path1, test_path2 = self.create_installed_doctests_and_tests_dir( + pytester.path, monkeypatch + ) + + # core_py is reached from sys.path, so should be imported normally. + mod = import_path( + core_py, + mode="importlib", + root=pytester.path, + consider_namespace_packages=ns_param, + ) + assert mod.__name__ == "app.core" + assert mod.__file__ and Path(mod.__file__) == core_py + + # Ensure we do not import the same module again (#11475). + mod2 = import_path( + core_py, + mode="importlib", + root=pytester.path, + consider_namespace_packages=ns_param, + ) + assert mod is mod2 + + # tests are not reachable from sys.path, so they are imported as a standalone modules. + # Instead of '.tests.a.test_core', we import as "_tests.a.test_core" because + # importlib considers module names starting with '.' to be local imports. + mod = import_path( + test_path1, + mode="importlib", + root=pytester.path, + consider_namespace_packages=ns_param, + ) + assert mod.__name__ == "_tests.a.test_core" + + # Ensure we do not import the same module again (#11475). + mod2 = import_path( + test_path1, + mode="importlib", + root=pytester.path, + consider_namespace_packages=ns_param, + ) + assert mod is mod2 + + mod = import_path( + test_path2, + mode="importlib", + root=pytester.path, + consider_namespace_packages=ns_param, + ) + assert mod.__name__ == "_tests.b.test_core" + + # Ensure we do not import the same module again (#11475). + mod2 = import_path( + test_path2, + mode="importlib", + root=pytester.path, + consider_namespace_packages=ns_param, + ) + assert mod is mod2 + + def test_import_using_normal_mechanism_first_integration( + self, monkeypatch: MonkeyPatch, pytester: Pytester, ns_param: bool + ) -> None: + """ + Same test as above, but verify the behavior calling pytest. + + We should not make this call in the same test as above, as the modules have already + been imported by separate import_path() calls. + """ + core_py, test_path1, test_path2 = self.create_installed_doctests_and_tests_dir( + pytester.path, monkeypatch + ) + result = pytester.runpytest( + "--import-mode=importlib", + "-o", + f"consider_namespace_packages={ns_param}", + "--doctest-modules", + "--pyargs", + "app", + "./.tests", + ) + result.stdout.fnmatch_lines( + [ + f"{core_py.relative_to(pytester.path)} . *", + f"{test_path1.relative_to(pytester.path)} . *", + f"{test_path2.relative_to(pytester.path)} . *", + "* 3 passed*", + ] + ) + + def test_import_path_imports_correct_file( + self, pytester: Pytester, ns_param: bool + ) -> None: + """ + Import the module by the given path, even if other module with the same name + is reachable from sys.path. + """ + pytester.syspathinsert() + # Create a 'x.py' module reachable from sys.path that raises AssertionError + # if imported. + x_at_root = pytester.path / "x.py" + x_at_root.write_text("raise AssertionError('x at root')", encoding="ascii") + + # Create another x.py module, but in some subdirectories to ensure it is not + # accessible from sys.path. + x_in_sub_folder = pytester.path / "a/b/x.py" + x_in_sub_folder.parent.mkdir(parents=True) + x_in_sub_folder.write_text("X = 'a/b/x'", encoding="ascii") + + # Import our x.py module from the subdirectories. + # The 'x.py' module from sys.path was not imported for sure because + # otherwise we would get an AssertionError. + mod = import_path( + x_in_sub_folder, + mode=ImportMode.importlib, + root=pytester.path, + consider_namespace_packages=ns_param, + ) + assert mod.__file__ and Path(mod.__file__) == x_in_sub_folder + assert mod.X == "a/b/x" + + mod2 = import_path( + x_in_sub_folder, + mode=ImportMode.importlib, + root=pytester.path, + consider_namespace_packages=ns_param, + ) + assert mod is mod2 + + # Attempt to import root 'x.py'. + with pytest.raises(AssertionError, match="x at root"): + _ = import_path( + x_at_root, + mode=ImportMode.importlib, + root=pytester.path, + consider_namespace_packages=ns_param, + ) + + +def test_safe_exists(tmp_path: Path) -> None: + d = tmp_path.joinpath("some_dir") + d.mkdir() + assert safe_exists(d) is True + + f = tmp_path.joinpath("some_file") + f.touch() + assert safe_exists(f) is True + + # Use unittest.mock() as a context manager to have a very narrow + # patch lifetime. + p = tmp_path.joinpath("some long filename" * 100) + with unittest.mock.patch.object( + Path, + "exists", + autospec=True, + side_effect=OSError(errno.ENAMETOOLONG, "name too long"), + ): + assert safe_exists(p) is False + + with unittest.mock.patch.object( + Path, + "exists", + autospec=True, + side_effect=ValueError("name too long"), + ): + assert safe_exists(p) is False + + +def test_import_sets_module_as_attribute(pytester: Pytester) -> None: + """Unittest test for #12194.""" + pytester.path.joinpath("foo/bar/baz").mkdir(parents=True) + pytester.path.joinpath("foo/__init__.py").touch() + pytester.path.joinpath("foo/bar/__init__.py").touch() + pytester.path.joinpath("foo/bar/baz/__init__.py").touch() + pytester.syspathinsert() + + # Import foo.bar.baz and ensure parent modules also ended up imported. + baz = import_path( + pytester.path.joinpath("foo/bar/baz/__init__.py"), + mode=ImportMode.importlib, + root=pytester.path, + consider_namespace_packages=False, + ) + assert baz.__name__ == "foo.bar.baz" + foo = sys.modules["foo"] + assert foo.__name__ == "foo" + bar = sys.modules["foo.bar"] + assert bar.__name__ == "foo.bar" + + # Check parent modules have an attribute pointing to their children. + assert bar.baz is baz + assert foo.bar is bar + + # Ensure we returned the "foo.bar" module cached in sys.modules. + bar_2 = import_path( + pytester.path.joinpath("foo/bar/__init__.py"), + mode=ImportMode.importlib, + root=pytester.path, + consider_namespace_packages=False, + ) + assert bar_2 is bar + + +def test_import_sets_module_as_attribute_without_init_files(pytester: Pytester) -> None: + """Similar to test_import_sets_module_as_attribute, but without __init__.py files.""" + pytester.path.joinpath("foo/bar").mkdir(parents=True) + pytester.path.joinpath("foo/bar/baz.py").touch() + pytester.syspathinsert() + + # Import foo.bar.baz and ensure parent modules also ended up imported. + baz = import_path( + pytester.path.joinpath("foo/bar/baz.py"), + mode=ImportMode.importlib, + root=pytester.path, + consider_namespace_packages=False, + ) + assert baz.__name__ == "foo.bar.baz" + foo = sys.modules["foo"] + assert foo.__name__ == "foo" + bar = sys.modules["foo.bar"] + assert bar.__name__ == "foo.bar" + + # Check parent modules have an attribute pointing to their children. + assert bar.baz is baz + assert foo.bar is bar + + # Ensure we returned the "foo.bar.baz" module cached in sys.modules. + baz_2 = import_path( + pytester.path.joinpath("foo/bar/baz.py"), + mode=ImportMode.importlib, + root=pytester.path, + consider_namespace_packages=False, + ) + assert baz_2 is baz + + +def test_import_sets_module_as_attribute_regression(pytester: Pytester) -> None: + """Regression test for #12194.""" + pytester.path.joinpath("foo/bar/baz").mkdir(parents=True) + pytester.path.joinpath("foo/__init__.py").touch() + pytester.path.joinpath("foo/bar/__init__.py").touch() + pytester.path.joinpath("foo/bar/baz/__init__.py").touch() + f = pytester.makepyfile( + """ + import foo + from foo.bar import baz + foo.bar.baz + + def test_foo() -> None: + pass + """ + ) + + pytester.syspathinsert() + result = pytester.runpython(f) + assert result.ret == 0 + + result = pytester.runpytest("--import-mode=importlib", "--doctest-modules") + assert result.ret == 0 + + +def test_import_submodule_not_namespace(pytester: Pytester) -> None: + """ + Regression test for importing a submodule 'foo.bar' while there is a 'bar' directory + reachable from sys.path -- ensuring the top-level module does not end up imported as a namespace + package. + + #12194 + https://github.com/pytest-dev/pytest/pull/12208#issuecomment-2056458432 + """ + pytester.syspathinsert() + # Create package 'foo' with a submodule 'bar'. + pytester.path.joinpath("foo").mkdir() + foo_path = pytester.path.joinpath("foo/__init__.py") + foo_path.touch() + bar_path = pytester.path.joinpath("foo/bar.py") + bar_path.touch() + # Create top-level directory in `sys.path` with the same name as that submodule. + pytester.path.joinpath("bar").mkdir() + + # Import `foo`, then `foo.bar`, and check they were imported from the correct location. + foo = import_path( + foo_path, + mode=ImportMode.importlib, + root=pytester.path, + consider_namespace_packages=False, + ) + bar = import_path( + bar_path, + mode=ImportMode.importlib, + root=pytester.path, + consider_namespace_packages=False, + ) + assert foo.__name__ == "foo" + assert bar.__name__ == "foo.bar" + assert foo.__file__ is not None + assert bar.__file__ is not None + assert Path(foo.__file__) == foo_path + assert Path(bar.__file__) == bar_path + + +class TestNamespacePackages: + """Test import_path support when importing from properly namespace packages.""" + + @pytest.fixture(autouse=True) + def setup_imports_tracking(self, monkeypatch: MonkeyPatch) -> None: + monkeypatch.setattr(sys, "pytest_namespace_packages_test", [], raising=False) + + def setup_directories( + self, tmp_path: Path, monkeypatch: Optional[MonkeyPatch], pytester: Pytester + ) -> Tuple[Path, Path]: + # Use a code to guard against modules being imported more than once. + # This is a safeguard in case future changes break this invariant. + code = dedent( + """ + import sys + imported = getattr(sys, "pytest_namespace_packages_test", []) + assert __name__ not in imported, f"{__name__} already imported" + imported.append(__name__) + sys.pytest_namespace_packages_test = imported + """ + ) + + # Set up a namespace package "com.company", containing + # two subpackages, "app" and "calc". + (tmp_path / "src/dist1/com/company/app/core").mkdir(parents=True) + (tmp_path / "src/dist1/com/company/app/__init__.py").write_text( + code, encoding="UTF-8" + ) + (tmp_path / "src/dist1/com/company/app/core/__init__.py").write_text( + code, encoding="UTF-8" + ) + models_py = tmp_path / "src/dist1/com/company/app/core/models.py" + models_py.touch() + + (tmp_path / "src/dist2/com/company/calc/algo").mkdir(parents=True) + (tmp_path / "src/dist2/com/company/calc/__init__.py").write_text( + code, encoding="UTF-8" + ) + (tmp_path / "src/dist2/com/company/calc/algo/__init__.py").write_text( + code, encoding="UTF-8" + ) + algorithms_py = tmp_path / "src/dist2/com/company/calc/algo/algorithms.py" + algorithms_py.write_text(code, encoding="UTF-8") + + r = validate_namespace_package( + pytester, + [tmp_path / "src/dist1", tmp_path / "src/dist2"], + ["com.company.app.core.models", "com.company.calc.algo.algorithms"], + ) + assert r.ret == 0 + if monkeypatch is not None: + monkeypatch.syspath_prepend(tmp_path / "src/dist1") + monkeypatch.syspath_prepend(tmp_path / "src/dist2") + return models_py, algorithms_py + + @pytest.mark.parametrize("import_mode", ["prepend", "append", "importlib"]) + def test_resolve_pkg_root_and_module_name_ns_multiple_levels( + self, + tmp_path: Path, + monkeypatch: MonkeyPatch, + pytester: Pytester, + import_mode: str, + ) -> None: + models_py, algorithms_py = self.setup_directories( + tmp_path, monkeypatch, pytester + ) + + pkg_root, module_name = resolve_pkg_root_and_module_name( + models_py, consider_namespace_packages=True + ) + assert (pkg_root, module_name) == ( + tmp_path / "src/dist1", + "com.company.app.core.models", + ) + + mod = import_path( + models_py, mode=import_mode, root=tmp_path, consider_namespace_packages=True + ) + assert mod.__name__ == "com.company.app.core.models" + assert mod.__file__ == str(models_py) + + # Ensure we do not import the same module again (#11475). + mod2 = import_path( + models_py, mode=import_mode, root=tmp_path, consider_namespace_packages=True + ) + assert mod is mod2 + + pkg_root, module_name = resolve_pkg_root_and_module_name( + algorithms_py, consider_namespace_packages=True + ) + assert (pkg_root, module_name) == ( + tmp_path / "src/dist2", + "com.company.calc.algo.algorithms", + ) + + mod = import_path( + algorithms_py, + mode=import_mode, + root=tmp_path, + consider_namespace_packages=True, + ) + assert mod.__name__ == "com.company.calc.algo.algorithms" + assert mod.__file__ == str(algorithms_py) + + # Ensure we do not import the same module again (#11475). + mod2 = import_path( + algorithms_py, + mode=import_mode, + root=tmp_path, + consider_namespace_packages=True, + ) + assert mod is mod2 + + @pytest.mark.parametrize("import_mode", ["prepend", "append", "importlib"]) + def test_incorrect_namespace_package( + self, + tmp_path: Path, + monkeypatch: MonkeyPatch, + pytester: Pytester, + import_mode: str, + ) -> None: + models_py, algorithms_py = self.setup_directories( + tmp_path, monkeypatch, pytester + ) + # Namespace packages must not have an __init__.py at its top-level + # directory; if it does, it is no longer a namespace package, and we fall back + # to importing just the part of the package containing the __init__.py files. + (tmp_path / "src/dist1/com/__init__.py").touch() + + # Because of the __init__ file, 'com' is no longer a namespace package: + # 'com.company.app' is importable as a normal module. + # 'com.company.calc' is no longer importable because 'com' is not a namespace package anymore. + r = validate_namespace_package( + pytester, + [tmp_path / "src/dist1", tmp_path / "src/dist2"], + ["com.company.app.core.models", "com.company.calc.algo.algorithms"], + ) + assert r.ret == 1 + r.stderr.fnmatch_lines("*No module named 'com.company.calc*") + + pkg_root, module_name = resolve_pkg_root_and_module_name( + models_py, consider_namespace_packages=True + ) + assert (pkg_root, module_name) == ( + tmp_path / "src/dist1", + "com.company.app.core.models", + ) + + # dist2/com/company will contain a normal Python package. + pkg_root, module_name = resolve_pkg_root_and_module_name( + algorithms_py, consider_namespace_packages=True + ) + assert (pkg_root, module_name) == ( + tmp_path / "src/dist2/com/company", + "calc.algo.algorithms", + ) + + def test_detect_meta_path( + self, + tmp_path: Path, + monkeypatch: MonkeyPatch, + pytester: Pytester, + ) -> None: + """ + resolve_pkg_root_and_module_name() considers sys.meta_path when importing namespace packages. + + Regression test for #12112. + """ + + class CustomImporter(importlib.abc.MetaPathFinder): + """ + Imports the module name "com" as a namespace package. + + This ensures our namespace detection considers sys.meta_path, which is important + to support all possible ways a module can be imported (for example editable installs). + """ + + def find_spec( + self, name: str, path: Any = None, target: Any = None + ) -> Optional[importlib.machinery.ModuleSpec]: + if name == "com": + spec = importlib.machinery.ModuleSpec("com", loader=None) + spec.submodule_search_locations = [str(com_root_2), str(com_root_1)] + return spec + return None + + # Setup directories without configuring sys.path. + models_py, algorithms_py = self.setup_directories( + tmp_path, monkeypatch=None, pytester=pytester + ) + com_root_1 = tmp_path / "src/dist1/com" + com_root_2 = tmp_path / "src/dist2/com" + + # Because the namespace package is not setup correctly, we cannot resolve it as a namespace package. + pkg_root, module_name = resolve_pkg_root_and_module_name( + models_py, consider_namespace_packages=True + ) + assert (pkg_root, module_name) == ( + tmp_path / "src/dist1/com/company", + "app.core.models", + ) + + # Insert our custom importer, which will recognize the "com" directory as a namespace package. + new_meta_path = [CustomImporter(), *sys.meta_path] + monkeypatch.setattr(sys, "meta_path", new_meta_path) + + # Now we should be able to resolve the path as namespace package. + pkg_root, module_name = resolve_pkg_root_and_module_name( + models_py, consider_namespace_packages=True + ) + assert (pkg_root, module_name) == ( + tmp_path / "src/dist1", + "com.company.app.core.models", + ) + + @pytest.mark.parametrize("insert", [True, False]) + def test_full_ns_packages_without_init_files( + self, pytester: Pytester, tmp_path: Path, monkeypatch: MonkeyPatch, insert: bool + ) -> None: + (tmp_path / "src/dist1/ns/b/app/bar/test").mkdir(parents=True) + (tmp_path / "src/dist1/ns/b/app/bar/m.py").touch() + + if insert: + # The presence of this __init__.py is not a problem, ns.b.app is still part of the namespace package. + (tmp_path / "src/dist1/ns/b/app/__init__.py").touch() + + (tmp_path / "src/dist2/ns/a/core/foo/test").mkdir(parents=True) + (tmp_path / "src/dist2/ns/a/core/foo/m.py").touch() + + # Validate the namespace package by importing it in a Python subprocess. + r = validate_namespace_package( + pytester, + [tmp_path / "src/dist1", tmp_path / "src/dist2"], + ["ns.b.app.bar.m", "ns.a.core.foo.m"], + ) + assert r.ret == 0 + monkeypatch.syspath_prepend(tmp_path / "src/dist1") + monkeypatch.syspath_prepend(tmp_path / "src/dist2") + + assert resolve_pkg_root_and_module_name( + tmp_path / "src/dist1/ns/b/app/bar/m.py", consider_namespace_packages=True + ) == (tmp_path / "src/dist1", "ns.b.app.bar.m") + assert resolve_pkg_root_and_module_name( + tmp_path / "src/dist2/ns/a/core/foo/m.py", consider_namespace_packages=True + ) == (tmp_path / "src/dist2", "ns.a.core.foo.m") + + +def test_is_importable(pytester: Pytester) -> None: + pytester.syspathinsert() + + path = pytester.path / "bar/foo.py" + path.parent.mkdir() + path.touch() + assert is_importable("bar.foo", path) is True + + # Ensure that the module that can be imported points to the path we expect. + path = pytester.path / "some/other/path/bar/foo.py" + path.mkdir(parents=True, exist_ok=True) + assert is_importable("bar.foo", path) is False + + # Paths containing "." cannot be imported. + path = pytester.path / "bar.x/__init__.py" + path.parent.mkdir() + path.touch() + assert is_importable("bar.x", path) is False + + # Pass starting with "." denote relative imports and cannot be checked using is_importable. + path = pytester.path / ".bar.x/__init__.py" + path.parent.mkdir() + path.touch() + assert is_importable(".bar.x", path) is False + + +def test_compute_module_name(tmp_path: Path) -> None: + assert compute_module_name(tmp_path, tmp_path) is None + assert compute_module_name(Path(), Path()) is None + + assert compute_module_name(tmp_path, tmp_path / "mod.py") == "mod" + assert compute_module_name(tmp_path, tmp_path / "src/app/bar") == "src.app.bar" + assert compute_module_name(tmp_path, tmp_path / "src/app/bar.py") == "src.app.bar" + assert ( + compute_module_name(tmp_path, tmp_path / "src/app/bar/__init__.py") + == "src.app.bar" + ) + + +def validate_namespace_package( + pytester: Pytester, paths: Sequence[Path], modules: Sequence[str] +) -> RunResult: + """ + Validate that a Python namespace package is set up correctly. + + In a sub interpreter, add 'paths' to sys.path and attempt to import the given modules. + + In this module many tests configure a set of files as a namespace package, this function + is used as sanity check that our files are configured correctly from the point of view of Python. + """ + lines = [ + "import sys", + # Configure sys.path. + *[f"sys.path.append(r{str(x)!r})" for x in paths], + # Imports. + *[f"import {x}" for x in modules], + ] + return pytester.runpython_c("\n".join(lines)) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pluginmanager.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pluginmanager.py index 9fe23d1779230..99b003b66ed4b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pluginmanager.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pluginmanager.py @@ -1,10 +1,10 @@ +# mypy: allow-untyped-defs import os import shutil import sys import types from typing import List -import pytest from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import PytestPluginManager @@ -13,6 +13,7 @@ from _pytest.monkeypatch import MonkeyPatch from _pytest.pathlib import import_path from _pytest.pytester import Pytester +import pytest @pytest.fixture @@ -45,7 +46,10 @@ def pytest_myhook(xyz): kwargs=dict(pluginmanager=config.pluginmanager) ) config.pluginmanager._importconftest( - conf, importmode="prepend", rootpath=pytester.path + conf, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, ) # print(config.pluginmanager.get_plugins()) res = config.hook.pytest_myhook(xyz=10) @@ -74,7 +78,10 @@ def pytest_addoption(parser): """ ) config.pluginmanager._importconftest( - p, importmode="prepend", rootpath=pytester.path + p, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, ) assert config.option.test123 @@ -98,6 +105,40 @@ def pytest_configure(self): config.pluginmanager.register(A()) assert len(values) == 2 + @pytest.mark.skipif( + not sys.platform.startswith("win"), + reason="requires a case-insensitive file system", + ) + def test_conftestpath_case_sensitivity(self, pytester: Pytester) -> None: + """Unit test for issue #9765.""" + config = pytester.parseconfig() + pytester.makepyfile(**{"tests/conftest.py": ""}) + + conftest = pytester.path.joinpath("tests/conftest.py") + conftest_upper_case = pytester.path.joinpath("TESTS/conftest.py") + + mod = config.pluginmanager._importconftest( + conftest, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, + ) + plugin = config.pluginmanager.get_plugin(str(conftest)) + assert plugin is mod + + mod_uppercase = config.pluginmanager._importconftest( + conftest_upper_case, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, + ) + plugin_uppercase = config.pluginmanager.get_plugin(str(conftest_upper_case)) + assert plugin_uppercase is mod_uppercase + + # No str(conftestpath) normalization so conftest should be imported + # twice and modules should be different objects + assert mod is not mod_uppercase + def test_hook_tracing(self, _config_for_test: Config) -> None: pytestpm = _config_for_test.pluginmanager # fully initialized with plugins saveindent = [] @@ -141,12 +182,18 @@ def test_hook_proxy(self, pytester: Pytester) -> None: conftest2 = pytester.path.joinpath("tests/subdir/conftest.py") config.pluginmanager._importconftest( - conftest1, importmode="prepend", rootpath=pytester.path + conftest1, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, ) ihook_a = session.gethookproxy(pytester.path / "tests") assert ihook_a is not None config.pluginmanager._importconftest( - conftest2, importmode="prepend", rootpath=pytester.path + conftest2, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, ) ihook_b = session.gethookproxy(pytester.path / "tests") assert ihook_a is not ihook_b @@ -242,8 +289,12 @@ def test_consider_module( mod = types.ModuleType("temp") 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" + p1 = pytestpm.get_plugin("pytest_p1") + assert p1 is not None + assert p1.__name__ == "pytest_p1" + p2 = pytestpm.get_plugin("pytest_p2") + assert p2 is not None + assert p2.__name__ == "pytest_p2" def test_consider_module_import_module( self, pytester: Pytester, _config_for_test: Config @@ -336,6 +387,7 @@ def test_import_plugin_importname( len2 = len(pytestpm.get_plugins()) assert len1 == len2 plugin1 = pytestpm.get_plugin("pytest_hello") + assert plugin1 is not None assert plugin1.__name__.endswith("pytest_hello") plugin2 = pytestpm.get_plugin("pytest_hello") assert plugin2 is plugin1 @@ -347,10 +399,11 @@ def test_import_plugin_dotted_name( pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwex.y") pytester.syspathinsert() - pytester.mkpydir("pkg").joinpath("plug.py").write_text("x=3") + pytester.mkpydir("pkg").joinpath("plug.py").write_text("x=3", encoding="utf-8") pluginname = "pkg.plug" pytestpm.import_plugin(pluginname) mod = pytestpm.get_plugin("pkg.plug") + assert mod is not None assert mod.x == 3 def test_consider_conftest_deps( @@ -359,13 +412,15 @@ def test_consider_conftest_deps( pytestpm: PytestPluginManager, ) -> None: mod = import_path( - pytester.makepyfile("pytest_plugins='xyz'"), root=pytester.path + pytester.makepyfile("pytest_plugins='xyz'"), + root=pytester.path, + consider_namespace_packages=False, ) with pytest.raises(ImportError): - pytestpm.consider_conftest(mod) + pytestpm.consider_conftest(mod, registration_name="unused") -class TestPytestPluginManagerBootstrapming: +class TestPytestPluginManagerBootstrapping: def test_preparse_args(self, pytestpm: PytestPluginManager) -> None: pytest.raises( ImportError, lambda: pytestpm.consider_preparse(["xyz", "-p", "hello123"]) @@ -391,7 +446,7 @@ def test_plugin_prevent_register(self, pytestpm: PytestPluginManager) -> None: assert len(l2) == len(l1) assert 42 not in l2 - def test_plugin_prevent_register_unregistered_alredy_registered( + def test_plugin_prevent_register_unregistered_already_registered( self, pytestpm: PytestPluginManager ) -> None: pytestpm.register(42, name="abc") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pytester.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pytester.py index 049f8b22d81b3..9c6081a56dbb9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pytester.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pytester.py @@ -1,22 +1,21 @@ +# mypy: allow-untyped-defs import os import subprocess import sys import time -from pathlib import Path from types import ModuleType from typing import List -import _pytest.pytester as pytester_mod -import pytest from _pytest.config import ExitCode from _pytest.config import PytestPluginManager from _pytest.monkeypatch import MonkeyPatch -from _pytest.pytester import CwdSnapshot +import _pytest.pytester as pytester_mod from _pytest.pytester import HookRecorder from _pytest.pytester import LineMatcher from _pytest.pytester import Pytester from _pytest.pytester import SysModulesSnapshot from _pytest.pytester import SysPathsSnapshot +import pytest def test_make_hook_recorder(pytester: Pytester) -> None: @@ -222,13 +221,13 @@ def test_inline_run_test_module_not_cleaned_up(self, pytester: Pytester) -> None result = pytester.inline_run(str(test_mod)) assert result.ret == ExitCode.OK # rewrite module, now test should fail if module was re-imported - test_mod.write_text("def test_foo(): assert False") + test_mod.write_text("def test_foo(): assert False", encoding="utf-8") result2 = pytester.inline_run(str(test_mod)) assert result2.ret == ExitCode.TESTS_FAILED def spy_factory(self): class SysModulesSnapshotSpy: - instances: List["SysModulesSnapshotSpy"] = [] # noqa: F821 + instances: List["SysModulesSnapshotSpy"] = [] def __init__(self, preserve=None) -> None: SysModulesSnapshotSpy.instances.append(self) @@ -301,17 +300,6 @@ def test_assert_outcomes_after_pytest_error(pytester: Pytester) -> None: result.assert_outcomes(passed=0) -def test_cwd_snapshot(pytester: Pytester) -> None: - foo = pytester.mkdir("foo") - bar = pytester.mkdir("bar") - os.chdir(foo) - snapshot = CwdSnapshot() - os.chdir(bar) - assert Path().absolute() == bar - snapshot.restore() - assert Path().absolute() == foo - - class TestSysModulesSnapshot: key = "my-test-module" @@ -618,14 +606,9 @@ def test_linematcher_string_api() -> None: def test_pytest_addopts_before_pytester(request, monkeypatch: MonkeyPatch) -> None: - orig = os.environ.get("PYTEST_ADDOPTS", None) monkeypatch.setenv("PYTEST_ADDOPTS", "--orig-unused") - pytester: Pytester = request.getfixturevalue("pytester") + _: Pytester = request.getfixturevalue("pytester") assert "PYTEST_ADDOPTS" not in os.environ - pytester._finalize() - assert os.environ.get("PYTEST_ADDOPTS") == "--orig-unused" - monkeypatch.undo() - assert os.environ.get("PYTEST_ADDOPTS") == orig def test_run_stdin(pytester: Pytester) -> None: @@ -722,15 +705,13 @@ def test_spawn_uses_tmphome(pytester: Pytester) -> None: pytester._monkeypatch.setenv("CUSTOMENV", "42") p1 = pytester.makepyfile( - """ + f""" import os def test(): assert os.environ["HOME"] == {tmphome!r} assert os.environ["CUSTOMENV"] == "42" - """.format( - tmphome=tmphome - ) + """ ) child = pytester.spawn_pytest(str(p1)) out = child.read() @@ -744,7 +725,7 @@ def test_run_result_repr() -> None: # known exit code r = pytester_mod.RunResult(1, outlines, errlines, duration=0.5) assert repr(r) == ( - f"" ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_python_path.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_python_path.py index 5ee0f55e36a33..73a8725680f68 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_python_path.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_python_path.py @@ -1,11 +1,12 @@ +# mypy: allow-untyped-defs import sys from textwrap import dedent from typing import Generator from typing import List from typing import Optional -import pytest from _pytest.pytester import Pytester +import pytest @pytest.fixture() @@ -82,11 +83,11 @@ def test_no_ini(pytester: Pytester, file_structure) -> None: def test_clean_up(pytester: Pytester) -> None: """Test that the plugin cleans up after itself.""" - # This is tough to test behaviorly because the cleanup really runs last. + # This is tough to test behaviorally because the cleanup really runs last. # So the test make several implementation assumptions: # - Cleanup is done in pytest_unconfigure(). - # - Not a hookwrapper. - # So we can add a hookwrapper ourselves to test what it does. + # - Not a hook wrapper. + # So we can add a hook wrapper ourselves to test what it does. pytester.makefile(".ini", pytest="[pytest]\npythonpath=I_SHALL_BE_REMOVED\n") pytester.makepyfile(test_foo="""def test_foo(): pass""") @@ -94,12 +95,14 @@ def test_clean_up(pytester: Pytester) -> None: after: Optional[List[str]] = None class Plugin: - @pytest.hookimpl(hookwrapper=True, tryfirst=True) + @pytest.hookimpl(wrapper=True, tryfirst=True) def pytest_unconfigure(self) -> Generator[None, None, None]: nonlocal before, after before = sys.path.copy() - yield - after = sys.path.copy() + try: + return (yield) + finally: + after = sys.path.copy() result = pytester.runpytest_inprocess(plugins=[Plugin()]) assert result.ret == 0 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_recwarn.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_recwarn.py index d3f218f1660ad..27ee9aa72f073 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_recwarn.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_recwarn.py @@ -1,10 +1,15 @@ -import re -import warnings +# mypy: allow-untyped-defs +import sys +from typing import List from typing import Optional +from typing import Type +from typing import Union +import warnings import pytest -from _pytest.pytester import Pytester -from _pytest.recwarn import WarningsRecorder +from pytest import ExitCode +from pytest import Pytester +from pytest import WarningsRecorder def test_recwarn_stacklevel(recwarn: WarningsRecorder) -> None: @@ -38,6 +43,47 @@ def test_recwarn_captures_deprecation_warning(recwarn: WarningsRecorder) -> None assert recwarn.pop(DeprecationWarning) +class TestSubclassWarningPop: + class ParentWarning(Warning): + pass + + class ChildWarning(ParentWarning): + pass + + class ChildOfChildWarning(ChildWarning): + pass + + @staticmethod + def raise_warnings_from_list(_warnings: List[Type[Warning]]): + for warn in _warnings: + warnings.warn(f"Warning {warn().__repr__()}", warn) + + def test_pop_finds_exact_match(self): + with pytest.warns((self.ParentWarning, self.ChildWarning)) as record: + self.raise_warnings_from_list( + [self.ChildWarning, self.ParentWarning, self.ChildOfChildWarning] + ) + + assert len(record) == 3 + _warn = record.pop(self.ParentWarning) + assert _warn.category is self.ParentWarning + + def test_pop_raises_if_no_match(self): + with pytest.raises(AssertionError): + with pytest.warns(self.ParentWarning) as record: + self.raise_warnings_from_list([self.ParentWarning]) + record.pop(self.ChildOfChildWarning) + + def test_pop_finds_best_inexact_match(self): + with pytest.warns(self.ParentWarning) as record: + self.raise_warnings_from_list( + [self.ChildOfChildWarning, self.ChildWarning, self.ChildOfChildWarning] + ) + + _warn = record.pop(self.ParentWarning) + assert _warn.category is self.ChildWarning + + class TestWarningsRecorderChecker: def test_recording(self) -> None: rec = WarningsRecorder(_ispytest=True) @@ -114,13 +160,13 @@ 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 + filters = warnings.filters[:] warn = warnings.warn warn_explicit = warnings.warn_explicit self.test_deprecated_call_raises() self.test_deprecated_call() assert onceregistry == warnings.onceregistry # type: ignore - assert filters == warnings.filters # type: ignore + assert filters == warnings.filters assert warn is warnings.warn assert warn_explicit is warnings.warn_explicit @@ -150,7 +196,7 @@ def f(): f() @pytest.mark.parametrize( - "warning_type", [PendingDeprecationWarning, DeprecationWarning] + "warning_type", [PendingDeprecationWarning, DeprecationWarning, FutureWarning] ) @pytest.mark.parametrize("mode", ["context_manager", "call"]) @pytest.mark.parametrize("call_f_first", [True, False]) @@ -173,50 +219,35 @@ def f(): with pytest.deprecated_call(): assert f() == 10 - @pytest.mark.parametrize("mode", ["context_manager", "call"]) - 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. - """ - - def f(): - raise ValueError("some exception") - - with pytest.raises(ValueError, match="some exception"): - if mode == "call": - pytest.deprecated_call(f) - else: - with pytest.deprecated_call(): - f() - def test_deprecated_call_specificity(self) -> None: other_warnings = [ Warning, UserWarning, SyntaxWarning, RuntimeWarning, - FutureWarning, ImportWarning, UnicodeWarning, ] for warning in other_warnings: def f(): - warnings.warn(warning("hi")) + warnings.warn(warning("hi")) # noqa: B023 - with pytest.raises(pytest.fail.Exception): - pytest.deprecated_call(f) - with pytest.raises(pytest.fail.Exception): - with pytest.deprecated_call(): - f() + with pytest.warns(warning): + with pytest.raises(pytest.fail.Exception): + pytest.deprecated_call(f) + with pytest.raises(pytest.fail.Exception): + with pytest.deprecated_call(): + f() def test_deprecated_call_supports_match(self) -> None: with pytest.deprecated_call(match=r"must be \d+$"): warnings.warn("value must be 42", DeprecationWarning) - with pytest.raises(pytest.fail.Exception): - with pytest.deprecated_call(match=r"must be \d+$"): - warnings.warn("this is not here", DeprecationWarning) + with pytest.deprecated_call(): + with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"): + with pytest.deprecated_call(match=r"must be \d+$"): + warnings.warn("this is not here", DeprecationWarning) class TestWarns: @@ -228,8 +259,9 @@ def test_check_callable(self) -> None: def test_several_messages(self) -> None: # different messages, b/c Python suppresses multiple identical warnings pytest.warns(RuntimeWarning, lambda: warnings.warn("w1", RuntimeWarning)) - with pytest.raises(pytest.fail.Exception): - pytest.warns(UserWarning, lambda: warnings.warn("w2", RuntimeWarning)) + with pytest.warns(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: @@ -244,13 +276,14 @@ def test_warning_tuple(self) -> None: pytest.warns( (RuntimeWarning, SyntaxWarning), lambda: warnings.warn("w2", SyntaxWarning) ) - pytest.raises( - pytest.fail.Exception, - lambda: pytest.warns( - (RuntimeWarning, SyntaxWarning), - lambda: warnings.warn("w3", UserWarning), - ), - ) + with pytest.warns(): + pytest.raises( + pytest.fail.Exception, + lambda: pytest.warns( + (RuntimeWarning, SyntaxWarning), + lambda: warnings.warn("w3", UserWarning), + ), + ) def test_as_contextmanager(self) -> None: with pytest.warns(RuntimeWarning): @@ -259,48 +292,47 @@ def test_as_contextmanager(self) -> None: with pytest.warns(UserWarning): warnings.warn("user", UserWarning) - with pytest.raises(pytest.fail.Exception) as excinfo: - with pytest.warns(RuntimeWarning): - warnings.warn("user", UserWarning) + with pytest.warns(): + with pytest.raises(pytest.fail.Exception) as excinfo: + with pytest.warns(RuntimeWarning): + warnings.warn("user", UserWarning) excinfo.match( - r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) were emitted. " - r"The list of emitted warnings is: \[UserWarning\('user',?\)\]." + r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) were emitted.\n" + r" Emitted warnings: \[UserWarning\('user',?\)\]." ) - with pytest.raises(pytest.fail.Exception) as excinfo: - with pytest.warns(UserWarning): - warnings.warn("runtime", RuntimeWarning) + with pytest.warns(): + with pytest.raises(pytest.fail.Exception) as excinfo: + with pytest.warns(UserWarning): + warnings.warn("runtime", RuntimeWarning) excinfo.match( - r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted. " - r"The list of emitted warnings is: \[RuntimeWarning\('runtime',?\)\]." + r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted.\n" + r" Emitted warnings: \[RuntimeWarning\('runtime',?\)]." ) with pytest.raises(pytest.fail.Exception) as excinfo: with pytest.warns(UserWarning): pass excinfo.match( - r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted. " - r"The list of emitted warnings is: \[\]." + r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted.\n" + r" Emitted warnings: \[\]." ) warning_classes = (UserWarning, FutureWarning) - with pytest.raises(pytest.fail.Exception) as excinfo: - with pytest.warns(warning_classes) as warninfo: - warnings.warn("runtime", RuntimeWarning) - warnings.warn("import", ImportWarning) + with pytest.warns(): + with pytest.raises(pytest.fail.Exception) as excinfo: + with pytest.warns(warning_classes) as warninfo: + warnings.warn("runtime", RuntimeWarning) + warnings.warn("import", ImportWarning) - message_template = ( - "DID NOT WARN. No warnings of type {0} were emitted. " - "The list of emitted warnings is: {1}." - ) - excinfo.match( - re.escape( - message_template.format( - warning_classes, [each.message for each in warninfo] - ) - ) + messages = [each.message for each in warninfo] + expected_str = ( + f"DID NOT WARN. No warnings of type {warning_classes} were emitted.\n" + f" Emitted warnings: {messages}." ) + assert str(excinfo.value) == expected_str + def test_record(self) -> None: with pytest.warns(UserWarning) as record: warnings.warn("user", UserWarning) @@ -317,17 +349,9 @@ def test_record_only(self) -> None: assert str(record[0].message) == "user" assert str(record[1].message) == "runtime" - def test_record_only_none_deprecated_warn(self) -> None: - # This should become an error when WARNS_NONE_ARG is removed in Pytest 8.0 - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - with pytest.warns(None) as record: # type: ignore[call-overload] - warnings.warn("user", UserWarning) - warnings.warn("runtime", RuntimeWarning) - - assert len(record) == 2 - assert str(record[0].message) == "user" - assert str(record[1].message) == "runtime" + def test_record_only_none_type_error(self) -> None: + with pytest.raises(TypeError): + pytest.warns(None) # type: ignore[call-overload] def test_record_by_subclass(self) -> None: with pytest.warns(Warning) as record: @@ -372,25 +396,31 @@ def test_match_regex(self) -> None: with pytest.warns(UserWarning, match=r"must be \d+$"): warnings.warn("value must be 42", UserWarning) - with pytest.raises(pytest.fail.Exception): - with pytest.warns(UserWarning, match=r"must be \d+$"): - warnings.warn("this is not here", UserWarning) + with pytest.warns(): + with pytest.raises(pytest.fail.Exception): + with pytest.warns(UserWarning, match=r"must be \d+$"): + warnings.warn("this is not here", UserWarning) - with pytest.raises(pytest.fail.Exception): - with pytest.warns(FutureWarning, match=r"must be \d+$"): - warnings.warn("value must be 42", UserWarning) + with pytest.warns(): + with pytest.raises(pytest.fail.Exception): + with pytest.warns(FutureWarning, match=r"must be \d+$"): + warnings.warn("value must be 42", UserWarning) 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) + with pytest.warns(): + with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"): + with pytest.warns(UserWarning, match=r"aaa"): + 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) -> None: - with pytest.raises(pytest.fail.Exception): - with pytest.warns(UserWarning, match=r"aaa"): - warnings.warn("bbbbbbbbbb", UserWarning) - warnings.warn("cccccccccc", UserWarning) + with pytest.warns(): + with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"): + 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) -> None: @@ -408,3 +438,160 @@ def test_warns_context_manager_with_kwargs(self) -> None: with pytest.warns(UserWarning, foo="bar"): # type: ignore pass assert "Unexpected keyword arguments" in str(excinfo.value) + + def test_re_emit_single(self) -> None: + with pytest.warns(DeprecationWarning): + with pytest.warns(UserWarning): + warnings.warn("user warning", UserWarning) + warnings.warn("some deprecation warning", DeprecationWarning) + + def test_re_emit_multiple(self) -> None: + with pytest.warns(UserWarning): + warnings.warn("first warning", UserWarning) + warnings.warn("second warning", UserWarning) + + def test_re_emit_match_single(self) -> None: + with pytest.warns(DeprecationWarning): + with pytest.warns(UserWarning, match="user warning"): + warnings.warn("user warning", UserWarning) + warnings.warn("some deprecation warning", DeprecationWarning) + + def test_re_emit_match_multiple(self) -> None: + with warnings.catch_warnings(): + warnings.simplefilter("error") # if anything is re-emitted + with pytest.warns(UserWarning, match="user warning"): + warnings.warn("first user warning", UserWarning) + warnings.warn("second user warning", UserWarning) + + def test_re_emit_non_match_single(self) -> None: + with pytest.warns(UserWarning, match="v2 warning"): + with pytest.warns(UserWarning, match="v1 warning"): + warnings.warn("v1 warning", UserWarning) + warnings.warn("non-matching v2 warning", UserWarning) + + def test_catch_warning_within_raise(self) -> None: + # warns-in-raises works since https://github.com/pytest-dev/pytest/pull/11129 + with pytest.raises(ValueError, match="some exception"): + with pytest.warns(FutureWarning, match="some warning"): + warnings.warn("some warning", category=FutureWarning) + raise ValueError("some exception") + # and raises-in-warns has always worked but we'll check for symmetry. + with pytest.warns(FutureWarning, match="some warning"): + with pytest.raises(ValueError, match="some exception"): + warnings.warn("some warning", category=FutureWarning) + raise ValueError("some exception") + + def test_skip_within_warns(self, pytester: Pytester) -> None: + """Regression test for #11907.""" + pytester.makepyfile( + """ + import pytest + + def test_it(): + with pytest.warns(Warning): + pytest.skip("this is OK") + """, + ) + + result = pytester.runpytest() + assert result.ret == ExitCode.OK + result.assert_outcomes(skipped=1) + + def test_fail_within_warns(self, pytester: Pytester) -> None: + """Regression test for #11907.""" + pytester.makepyfile( + """ + import pytest + + def test_it(): + with pytest.warns(Warning): + pytest.fail("BOOM") + """, + ) + + result = pytester.runpytest() + assert result.ret == ExitCode.TESTS_FAILED + result.assert_outcomes(failed=1) + assert "DID NOT WARN" not in str(result.stdout) + + def test_exit_within_warns(self, pytester: Pytester) -> None: + """Regression test for #11907.""" + pytester.makepyfile( + """ + import pytest + + def test_it(): + with pytest.warns(Warning): + pytest.exit() + """, + ) + + result = pytester.runpytest() + assert result.ret == ExitCode.INTERRUPTED + result.assert_outcomes() + + def test_keyboard_interrupt_within_warns(self, pytester: Pytester) -> None: + """Regression test for #11907.""" + pytester.makepyfile( + """ + import pytest + + def test_it(): + with pytest.warns(Warning): + raise KeyboardInterrupt() + """, + ) + + result = pytester.runpytest_subprocess() + assert result.ret == ExitCode.INTERRUPTED + result.assert_outcomes() + + +def test_raise_type_error_on_invalid_warning() -> None: + """Check pytest.warns validates warning messages are strings (#10865) or + Warning instances (#11959).""" + with pytest.raises(TypeError, match="Warning must be str or Warning"): + with pytest.warns(UserWarning): + warnings.warn(1) # type: ignore + + +@pytest.mark.parametrize( + "message", + [ + pytest.param("Warning", id="str"), + pytest.param(UserWarning(), id="UserWarning"), + pytest.param(Warning(), id="Warning"), + ], +) +def test_no_raise_type_error_on_valid_warning(message: Union[str, Warning]) -> None: + """Check pytest.warns validates warning messages are strings (#10865) or + Warning instances (#11959).""" + with pytest.warns(Warning): + warnings.warn(message) + + +@pytest.mark.skipif( + hasattr(sys, "pypy_version_info"), + reason="Not for pypy", +) +def test_raise_type_error_on_invalid_warning_message_cpython() -> None: + # Check that we get the same behavior with the stdlib, at least if filtering + # (see https://github.com/python/cpython/issues/103577 for details) + with pytest.raises(TypeError): + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "test") + warnings.warn(1) # type: ignore + + +def test_multiple_arg_custom_warning() -> None: + """Test for issue #11906.""" + + class CustomWarning(UserWarning): + def __init__(self, a, b): + pass + + with pytest.warns(CustomWarning): + with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"): + with pytest.warns(CustomWarning, match="not gonna match"): + a, b = 1, 2 + warnings.warn(CustomWarning(a, b)) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_reports.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_reports.py index 31b6cf1afc6aa..c6baeebc9dd37 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_reports.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_reports.py @@ -1,13 +1,15 @@ +# mypy: allow-untyped-defs 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.pytester import Pytester +from _pytest.python_api import approx from _pytest.reports import CollectReport from _pytest.reports import TestReport +import pytest class TestReportSerialization: @@ -277,7 +279,7 @@ def test_chained_exceptions( ) -> None: """Check serialization/deserialization of report objects containing chained exceptions (#5786)""" pytester.makepyfile( - """ + f""" def foo(): raise ValueError('value error') def test_a(): @@ -285,27 +287,25 @@ def test_a(): foo() except ValueError as e: raise RuntimeError('runtime error') from e - if {error_during_import}: + if {report_class is CollectReport}: test_a() - """.format( - error_during_import=report_class is CollectReport - ) + """ ) reprec = pytester.inline_run() if report_class is TestReport: - reports: Union[ - Sequence[TestReport], Sequence[CollectReport] - ] = reprec.getreports("pytest_runtest_logreport") + reports: Union[Sequence[TestReport], Sequence[CollectReport]] = ( + reprec.getreports("pytest_runtest_logreport") + ) # 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 + # three collection reports: session, test file, directory reports = reprec.getreports("pytest_collectreport") - assert len(reports) == 2 + assert len(reports) == 3 report = reports[1] def check_longrepr(longrepr: ExceptionChainRepr) -> None: @@ -409,12 +409,32 @@ def test_report_prevent_ConftestImportFailure_hiding_exception( ) -> None: sub_dir = pytester.path.joinpath("ns") sub_dir.mkdir() - sub_dir.joinpath("conftest.py").write_text("import unknown") + sub_dir.joinpath("conftest.py").write_text("import unknown", encoding="utf-8") result = pytester.runpytest_subprocess(".") result.stdout.fnmatch_lines(["E *Error: No module named 'unknown'"]) result.stdout.no_fnmatch_line("ERROR - *ConftestImportFailure*") + def test_report_timestamps_match_duration(self, pytester: Pytester, mock_timing): + reprec = pytester.inline_runsource( + """ + import pytest + from _pytest import timing + @pytest.fixture + def fixture_(): + timing.sleep(5) + yield + timing.sleep(5) + def test_1(fixture_): timing.sleep(10) + """ + ) + reports = reprec.getreports("pytest_runtest_logreport") + assert len(reports) == 3 + for report in reports: + data = report._to_json() + loaded_report = TestReport._from_json(data) + assert loaded_report.stop - loaded_report.start == approx(report.duration) + class TestHooks: """Test that the hooks are working correctly for plugins""" @@ -450,7 +470,7 @@ def test_b(): pass ) reprec = pytester.inline_run() reports = reprec.getreports("pytest_collectreport") - assert len(reports) == 2 + assert len(reports) == 3 for rep in reports: data = pytestconfig.hook.pytest_report_to_serializable( config=pytestconfig, report=rep diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_runner.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_runner.py index 2e2c462d97896..99c11a3d92c74 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_runner.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_runner.py @@ -1,14 +1,16 @@ +# mypy: allow-untyped-defs +from functools import partial import inspect import os +from pathlib import Path import sys import types -from pathlib import Path from typing import Dict from typing import List from typing import Tuple from typing import Type +import warnings -import pytest from _pytest import outcomes from _pytest import reports from _pytest import runner @@ -18,6 +20,11 @@ from _pytest.monkeypatch import MonkeyPatch from _pytest.outcomes import OutcomeException from _pytest.pytester import Pytester +import pytest + + +if sys.version_info < (3, 11): + from exceptiongroup import ExceptionGroup class TestSetupState: @@ -77,8 +84,6 @@ def fin3(): assert r == ["fin3", "fin1"] def test_teardown_multiple_fail(self, pytester: Pytester) -> None: - # Ensure the first exception is the one which is re-raised. - # Ideally both would be reported however. def fin1(): raise Exception("oops1") @@ -90,9 +95,14 @@ def fin2(): ss.setup(item) ss.addfinalizer(fin1, item) ss.addfinalizer(fin2, item) - with pytest.raises(Exception) as err: + with pytest.raises(ExceptionGroup) as err: ss.teardown_exact(None) - assert err.value.args == ("oops2",) + + # Note that finalizers are run LIFO, but because FIFO is more intuitive for + # users we reverse the order of messages, and see the error from fin1 first. + err1, err2 = err.value.exceptions + assert err1.args == ("oops1",) + assert err2.args == ("oops2",) def test_teardown_multiple_scopes_one_fails(self, pytester: Pytester) -> None: module_teardown = [] @@ -113,6 +123,25 @@ def fin_module(): ss.teardown_exact(None) assert module_teardown == ["fin_module"] + def test_teardown_multiple_scopes_several_fail(self, pytester) -> None: + def raiser(exc): + raise exc + + item = pytester.getitem("def test_func(): pass") + mod = item.listchain()[-2] + ss = item.session._setupstate + ss.setup(item) + ss.addfinalizer(partial(raiser, KeyError("from module scope")), mod) + ss.addfinalizer(partial(raiser, TypeError("from function scope 1")), item) + ss.addfinalizer(partial(raiser, ValueError("from function scope 2")), item) + + with pytest.raises(ExceptionGroup, match="errors during test teardown") as e: + ss.teardown_exact(None) + mod, func = e.value.exceptions + assert isinstance(mod, KeyError) + assert isinstance(func.exceptions[0], TypeError) # type: ignore + assert isinstance(func.exceptions[1], ValueError) # type: ignore + class BaseFunctionalTests: def test_passfunction(self, pytester: Pytester) -> None: @@ -380,7 +409,7 @@ def test_func(): # assert rep.outcome.when == "setup" # assert rep.outcome.where.lineno == 3 # assert rep.outcome.where.path.basename == "test_func.py" - # assert instanace(rep.failed.failurerepr, PythonFailureRepr) + # assert isinstance(rep.failed.failurerepr, PythonFailureRepr) def test_systemexit_does_not_bail_out(self, pytester: Pytester) -> None: try: @@ -447,6 +476,7 @@ class TestClass(object): assert not rep.skipped assert rep.passed locinfo = rep.location + assert locinfo is not None assert locinfo[0] == col.path.name assert not locinfo[1] assert locinfo[2] == col.path.name @@ -515,10 +545,10 @@ def pytest_runtest_setup(self, item): @pytest.fixture def mylist(self, request): return request.function.mylist - @pytest.hookimpl(hookwrapper=True) + @pytest.hookimpl(wrapper=True) def pytest_runtest_call(self, item): try: - (yield).get_result() + yield except ValueError: pass def test_hello1(self, mylist): @@ -733,6 +763,73 @@ def test_importorskip_imports_last_module_part() -> None: assert os.path == ospath +class TestImportOrSkipExcType: + """Tests for #11523.""" + + def test_no_warning(self) -> None: + # An attempt on a module which does not exist will raise ModuleNotFoundError, so it will + # be skipped normally and no warning will be issued. + with warnings.catch_warnings(record=True) as captured: + warnings.simplefilter("always") + + with pytest.raises(pytest.skip.Exception): + pytest.importorskip("TestImportOrSkipExcType_test_no_warning") + + assert captured == [] + + def test_import_error_with_warning(self, pytester: Pytester) -> None: + # Create a module which exists and can be imported, however it raises + # ImportError due to some other problem. In this case we will issue a warning + # about the future behavior change. + fn = pytester.makepyfile("raise ImportError('some specific problem')") + pytester.syspathinsert() + + with warnings.catch_warnings(record=True) as captured: + warnings.simplefilter("always") + + with pytest.raises(pytest.skip.Exception): + pytest.importorskip(fn.stem) + + [warning] = captured + assert warning.category is pytest.PytestDeprecationWarning + + def test_import_error_suppress_warning(self, pytester: Pytester) -> None: + # Same as test_import_error_with_warning, but we can suppress the warning + # by passing ImportError as exc_type. + fn = pytester.makepyfile("raise ImportError('some specific problem')") + pytester.syspathinsert() + + with warnings.catch_warnings(record=True) as captured: + warnings.simplefilter("always") + + with pytest.raises(pytest.skip.Exception): + pytest.importorskip(fn.stem, exc_type=ImportError) + + assert captured == [] + + def test_warning_integration(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + def test_foo(): + pytest.importorskip("warning_integration_module") + """ + ) + pytester.makepyfile( + warning_integration_module=""" + raise ImportError("required library foobar not compiled properly") + """ + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines( + [ + "*Module 'warning_integration_module' was found, but when imported by pytest it raised:", + "* ImportError('required library foobar not compiled properly')", + "*1 skipped, 1 warning*", + ] + ) + + def test_importorskip_dev_module(monkeypatch) -> None: try: mod = types.ModuleType("mockmodule") @@ -799,12 +896,12 @@ def test_unicode_in_longrepr(pytester: Pytester) -> None: pytester.makeconftest( """\ import pytest - @pytest.hookimpl(hookwrapper=True) + @pytest.hookimpl(wrapper=True) def pytest_runtest_makereport(): - outcome = yield - rep = outcome.get_result() + rep = yield if rep.when == "call": rep.longrepr = 'ä' + return rep """ ) pytester.makepyfile( @@ -880,6 +977,7 @@ def test_fix(foo): 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: nodeid = "item_that_raises" @@ -896,6 +994,9 @@ def runtest(self): # Check that exception info is stored on sys assert sys.last_type is IndexError assert isinstance(sys.last_value, IndexError) + if sys.version_info >= (3, 12, 0): + assert isinstance(sys.last_exc, IndexError) # type: ignore[attr-defined] + assert sys.last_value.args[0] == "TEST" assert sys.last_traceback @@ -904,6 +1005,8 @@ def runtest(self): runner.pytest_runtest_call(ItemMightRaise()) # type: ignore[arg-type] assert not hasattr(sys, "last_type") assert not hasattr(sys, "last_value") + if sys.version_info >= (3, 12, 0): + assert not hasattr(sys, "last_exc") assert not hasattr(sys, "last_traceback") @@ -978,7 +1081,7 @@ def test_longreprtext_collect_skip(self, pytester: Pytester) -> None: ) rec = pytester.inline_run() calls = rec.getcalls("pytest_collectreport") - _, call = calls + _, call, _ = calls assert isinstance(call.report.longrepr, tuple) assert "Skipped" in call.report.longreprtext @@ -1059,3 +1162,20 @@ def func() -> None: with pytest.raises(TypeError) as excinfo: OutcomeException(func) # type: ignore assert str(excinfo.value) == expected + + +def test_pytest_version_env_var(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: + monkeypatch.setenv("PYTEST_VERSION", "old version") + pytester.makepyfile( + """ + import pytest + import os + + + def test(): + assert os.environ.get("PYTEST_VERSION") == pytest.__version__ + """ + ) + result = pytester.runpytest_inprocess() + assert result.ret == ExitCode.OK + assert os.environ["PYTEST_VERSION"] == "old version" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_runner_xunit.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_runner_xunit.py index e077ac41e2c2e..587c9eb9fefbc 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_runner_xunit.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_runner_xunit.py @@ -1,8 +1,10 @@ +# mypy: allow-untyped-defs """Test correct setup/teardowns at module, class, and instance level.""" + from typing import List -import pytest from _pytest.pytester import Pytester +import pytest def test_module_and_function_setup(pytester: Pytester) -> None: @@ -254,7 +256,7 @@ def test_setup_teardown_function_level_with_optional_argument( sys, "trace_setups_teardowns", trace_setups_teardowns, raising=False ) p = pytester.makepyfile( - """ + f""" import pytest import sys @@ -275,9 +277,7 @@ def teardown_method(self, {arg}): trace('teardown_method') def test_method_1(self): pass def test_method_2(self): pass - """.format( - arg=arg - ) + """ ) result = pytester.inline_run(p) result.assertoutcome(passed=4) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_scope.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_scope.py index 09ee1343a8068..1727c2ee1bbb4 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_scope.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_scope.py @@ -1,7 +1,7 @@ import re -import pytest from _pytest.scope import Scope +import pytest def test_ordering() -> None: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_session.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_session.py index 3ca6d390383c0..8624af478b140 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_session.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_session.py @@ -1,7 +1,8 @@ -import pytest +# mypy: allow-untyped-defs from _pytest.config import ExitCode from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester +import pytest class SessionTests: @@ -172,8 +173,9 @@ def test_one(): pass except pytest.skip.Exception: # pragma: no cover pytest.fail("wrong skipped caught") reports = reprec.getreports("pytest_collectreport") - assert len(reports) == 1 - assert reports[0].skipped + # Session, Dir + assert len(reports) == 2 + assert reports[1].skipped class TestNewSession(SessionTests): @@ -265,9 +267,9 @@ def test_plugin_already_exists(pytester: Pytester) -> None: def test_exclude(pytester: Pytester) -> None: hellodir = pytester.mkdir("hello") - hellodir.joinpath("test_hello.py").write_text("x y syntaxerror") + hellodir.joinpath("test_hello.py").write_text("x y syntaxerror", encoding="utf-8") hello2dir = pytester.mkdir("hello2") - hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror") + hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror", encoding="utf-8") pytester.makepyfile(test_ok="def test_pass(): pass") result = pytester.runpytest("--ignore=hello", "--ignore=hello2") assert result.ret == 0 @@ -276,13 +278,13 @@ def test_exclude(pytester: Pytester) -> None: def test_exclude_glob(pytester: Pytester) -> None: hellodir = pytester.mkdir("hello") - hellodir.joinpath("test_hello.py").write_text("x y syntaxerror") + hellodir.joinpath("test_hello.py").write_text("x y syntaxerror", encoding="utf-8") hello2dir = pytester.mkdir("hello2") - hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror") + hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror", encoding="utf-8") hello3dir = pytester.mkdir("hallo3") - hello3dir.joinpath("test_hello3.py").write_text("x y syntaxerror") + hello3dir.joinpath("test_hello3.py").write_text("x y syntaxerror", encoding="utf-8") subdir = pytester.mkdir("sub") - subdir.joinpath("test_hello4.py").write_text("x y syntaxerror") + subdir.joinpath("test_hello4.py").write_text("x y syntaxerror", encoding="utf-8") pytester.makepyfile(test_ok="def test_pass(): pass") result = pytester.runpytest("--ignore-glob=*h[ea]llo*") assert result.ret == 0 @@ -335,6 +337,56 @@ def pytest_sessionfinish(): assert res.ret == ExitCode.NO_TESTS_COLLECTED +def test_collection_args_do_not_duplicate_modules(pytester: Pytester) -> None: + """Test that when multiple collection args are specified on the command line + for the same module, only a single Module collector is created. + + Regression test for #723, #3358. + """ + pytester.makepyfile( + **{ + "d/test_it": """ + def test_1(): pass + def test_2(): pass + """ + } + ) + + result = pytester.runpytest( + "--collect-only", + "d/test_it.py::test_1", + "d/test_it.py::test_2", + ) + result.stdout.fnmatch_lines( + [ + " ", + " ", + " ", + " ", + ], + consecutive=True, + ) + + # Different, but related case. + result = pytester.runpytest( + "--collect-only", + "--keep-duplicates", + "d", + "d", + ) + result.stdout.fnmatch_lines( + [ + " ", + " ", + " ", + " ", + " ", + " ", + ], + consecutive=True, + ) + + @pytest.mark.parametrize("path", ["root", "{relative}/root", "{environment}/root"]) def test_rootdir_option_arg( pytester: Pytester, monkeypatch: MonkeyPatch, path: str @@ -367,3 +419,63 @@ def test_rootdir_wrong_option_arg(pytester: Pytester) -> None: result.stderr.fnmatch_lines( ["*Directory *wrong_dir* not found. Check your '--rootdir' option.*"] ) + + +def test_shouldfail_is_sticky(pytester: Pytester) -> None: + """Test that session.shouldfail cannot be reset to False after being set. + + Issue #11706. + """ + pytester.makeconftest( + """ + def pytest_sessionfinish(session): + assert session.shouldfail + session.shouldfail = False + assert session.shouldfail + """ + ) + pytester.makepyfile( + """ + import pytest + + def test_foo(): + pytest.fail("This is a failing test") + + def test_bar(): pass + """ + ) + + result = pytester.runpytest("--maxfail=1", "-Wall") + + result.assert_outcomes(failed=1, warnings=1) + result.stdout.fnmatch_lines("*session.shouldfail cannot be unset*") + + +def test_shouldstop_is_sticky(pytester: Pytester) -> None: + """Test that session.shouldstop cannot be reset to False after being set. + + Issue #11706. + """ + pytester.makeconftest( + """ + def pytest_sessionfinish(session): + assert session.shouldstop + session.shouldstop = False + assert session.shouldstop + """ + ) + pytester.makepyfile( + """ + import pytest + + def test_foo(): + pytest.fail("This is a failing test") + + def test_bar(): pass + """ + ) + + result = pytester.runpytest("--stepwise", "-Wall") + + result.assert_outcomes(failed=1, warnings=1) + result.stdout.fnmatch_lines("*session.shouldstop cannot be unset*") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_setuponly.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_setuponly.py index fe4bdc514eb6a..8638f5a61403a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_setuponly.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_setuponly.py @@ -1,8 +1,9 @@ +# mypy: allow-untyped-defs import sys -import pytest from _pytest.config import ExitCode from _pytest.pytester import Pytester +import pytest @pytest.fixture(params=["--setup-only", "--setup-plan", "--setup-show"], scope="module") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_skipping.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_skipping.py index 3010943607c22..a1511b26d1cca 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_skipping.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_skipping.py @@ -1,12 +1,13 @@ +# mypy: allow-untyped-defs import sys import textwrap -import pytest from _pytest.pytester import Pytester from _pytest.runner import runtestprotocol from _pytest.skipping import evaluate_skip_marks from _pytest.skipping import evaluate_xfail_marks from _pytest.skipping import pytest_runtest_setup +import pytest class TestEvaluation: @@ -73,16 +74,15 @@ def test_marked_one_arg_twice(self, pytester: Pytester) -> None: """@pytest.mark.skipif("not hasattr(os, 'murks')")""", """@pytest.mark.skipif(condition="hasattr(os, 'murks')")""", ] - for i in range(0, 2): + for i in range(2): item = pytester.getitem( - """ + f""" import pytest - %s - %s + {lines[i]} + {lines[(i + 1) % 2]} def test_func(): pass """ - % (lines[i], lines[(i + 1) % 2]) ) skipped = evaluate_skip_marks(item) assert skipped @@ -195,7 +195,8 @@ def test_skipif_markeval_namespace_multiple(self, pytester: Pytester) -> None: def pytest_markeval_namespace(): return {"arg": "root"} """ - ) + ), + encoding="utf-8", ) root.joinpath("test_root.py").write_text( textwrap.dedent( @@ -206,7 +207,8 @@ def pytest_markeval_namespace(): def test_root(): assert False """ - ) + ), + encoding="utf-8", ) foo = root.joinpath("foo") foo.mkdir() @@ -219,7 +221,8 @@ def test_root(): def pytest_markeval_namespace(): return {"arg": "foo"} """ - ) + ), + encoding="utf-8", ) foo.joinpath("test_foo.py").write_text( textwrap.dedent( @@ -230,7 +233,8 @@ def pytest_markeval_namespace(): def test_foo(): assert False """ - ) + ), + encoding="utf-8", ) bar = root.joinpath("bar") bar.mkdir() @@ -243,7 +247,8 @@ def test_foo(): def pytest_markeval_namespace(): return {"arg": "bar"} """ - ) + ), + encoding="utf-8", ) bar.joinpath("test_bar.py").write_text( textwrap.dedent( @@ -254,7 +259,8 @@ def pytest_markeval_namespace(): def test_bar(): assert False """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run("-vs", "--capture=no") @@ -441,10 +447,8 @@ def test_this_false(): result = pytester.runpytest(p, "-rx") result.stdout.fnmatch_lines( [ - "*test_one*test_this*", - "*NOTRUN*noway", - "*test_one*test_this_true*", - "*NOTRUN*condition:*True*", + "*test_one*test_this - reason: *NOTRUN* noway", + "*test_one*test_this_true - reason: *NOTRUN* condition: True", "*1 passed*", ] ) @@ -461,9 +465,7 @@ def setup_module(mod): """ ) result = pytester.runpytest(p, "-rx") - result.stdout.fnmatch_lines( - ["*test_one*test_this*", "*NOTRUN*hello", "*1 xfailed*"] - ) + result.stdout.fnmatch_lines(["*test_one*test_this*NOTRUN*hello", "*1 xfailed*"]) def test_xfail_xpass(self, pytester: Pytester) -> None: p = pytester.makepyfile( @@ -489,7 +491,7 @@ def test_this(): result = pytester.runpytest(p) result.stdout.fnmatch_lines(["*1 xfailed*"]) result = pytester.runpytest(p, "-rx") - result.stdout.fnmatch_lines(["*XFAIL*test_this*", "*reason:*hello*"]) + result.stdout.fnmatch_lines(["*XFAIL*test_this*reason:*hello*"]) result = pytester.runpytest(p, "--runxfail") result.stdout.fnmatch_lines(["*1 pass*"]) @@ -507,7 +509,7 @@ def test_this(): result = pytester.runpytest(p) result.stdout.fnmatch_lines(["*1 xfailed*"]) result = pytester.runpytest(p, "-rx") - result.stdout.fnmatch_lines(["*XFAIL*test_this*", "*reason:*hello*"]) + result.stdout.fnmatch_lines(["*XFAIL*test_this*reason:*hello*"]) result = pytester.runpytest(p, "--runxfail") result.stdout.fnmatch_lines( """ @@ -543,7 +545,7 @@ def test_this(arg): """ ) result = pytester.runpytest(p, "-rxX") - result.stdout.fnmatch_lines(["*XFAIL*test_this*", "*NOTRUN*"]) + result.stdout.fnmatch_lines(["*XFAIL*test_this*NOTRUN*"]) def test_dynamic_xfail_set_during_funcarg_setup(self, pytester: Pytester) -> None: p = pytester.makepyfile( @@ -604,7 +606,7 @@ def test_xfail_raises( @pytest.mark.xfail(raises=%s) def test_raises(): raise %s() - """ + """ # noqa: UP031 (python syntax issues) % (expected, actual) ) result = pytester.runpytest(p) @@ -622,7 +624,7 @@ def test_foo(): """ ) result = pytester.runpytest(p, "-rxX") - result.stdout.fnmatch_lines(["*XFAIL*", "*unsupported feature*"]) + result.stdout.fnmatch_lines(["*XFAIL*unsupported feature*"]) assert result.ret == 0 @pytest.mark.parametrize("strict", [True, False]) @@ -633,7 +635,8 @@ def test_strict_xfail(self, pytester: Pytester, strict: bool) -> None: @pytest.mark.xfail(reason='unsupported feature', strict=%s) def test_foo(): - with open('foo_executed', 'w'): pass # make sure test executes + with open('foo_executed', 'w', encoding='utf-8'): + pass # make sure test executes """ % strict ) @@ -646,7 +649,7 @@ def test_foo(): result.stdout.fnmatch_lines( [ "*test_strict_xfail*", - "XPASS test_strict_xfail.py::test_foo unsupported feature", + "XPASS test_strict_xfail.py::test_foo - unsupported feature", ] ) assert result.ret == (1 if strict else 0) @@ -905,7 +908,7 @@ def test_skipif_reporting(self, pytester: Pytester, params) -> None: @pytest.mark.skipif(%(params)s) def test_that(): assert 0 - """ + """ # noqa: UP031 (python syntax issues) % dict(params=params) ) result = pytester.runpytest(p, "-s", "-rs") @@ -931,15 +934,13 @@ def test_skipif_reporting_multiple( self, pytester: Pytester, marker, msg1, msg2 ) -> None: pytester.makepyfile( - test_foo=""" + test_foo=f""" import pytest @pytest.mark.{marker}(False, reason='first_condition') @pytest.mark.{marker}(True, reason='second_condition') def test_foobar(): assert 1 - """.format( - marker=marker - ) + """ ) result = pytester.runpytest("-s", "-rsxX") result.stdout.fnmatch_lines( @@ -986,33 +987,34 @@ def test_skipped_reasons_functional(pytester: Pytester) -> None: pytester.makepyfile( test_one=""" import pytest - from conftest import doskip + from helpers import doskip - def setup_function(func): - doskip() + def setup_function(func): # LINE 4 + doskip("setup function") def test_func(): pass - class TestClass(object): + class TestClass: def test_method(self): - doskip() + doskip("test method") - @pytest.mark.skip("via_decorator") + @pytest.mark.skip("via_decorator") # LINE 14 def test_deco(self): assert 0 """, - conftest=""" + helpers=""" import pytest, sys - def doskip(): + def doskip(reason): assert sys._getframe().f_lineno == 3 - pytest.skip('test') + pytest.skip(reason) # LINE 4 """, ) result = pytester.runpytest("-rs") result.stdout.fnmatch_lines_random( [ - "SKIPPED [[]2[]] conftest.py:4: test", + "SKIPPED [[]1[]] test_one.py:7: setup function", + "SKIPPED [[]1[]] helpers.py:4: test method", "SKIPPED [[]1[]] test_one.py:14: via_decorator", ] ) @@ -1139,14 +1141,12 @@ def test_func(): """ ) result = pytester.runpytest() - markline = " ^" + markline = " ^" 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:] + markline = markline[1:] - if sys.version_info[:2] >= (3, 10): + if sys.version_info >= (3, 10): expected = [ "*ERROR*test_nameerror*", "*asd*", @@ -1185,7 +1185,7 @@ def test_boolean(): """ ) result = pytester.runpytest("-rsx") - result.stdout.fnmatch_lines(["*SKIP*x == 3*", "*XFAIL*test_boolean*", "*x == 3*"]) + result.stdout.fnmatch_lines(["*SKIP*x == 3*", "*XFAIL*test_boolean*x == 3*"]) def test_default_markers(pytester: Pytester) -> None: @@ -1297,8 +1297,7 @@ def test_func(): result = pytester.runpytest("-rxs") result.stdout.fnmatch_lines( """ - *XFAIL* - *True123* + *XFAIL*True123* *1 xfail* """ ) @@ -1444,6 +1443,27 @@ def test_pass(): ) +def test_skip_from_fixture(pytester: Pytester) -> None: + pytester.makepyfile( + **{ + "tests/test_1.py": """ + import pytest + def test_pass(arg): + pass + @pytest.fixture + def arg(): + condition = True + if condition: + pytest.skip("Fixture conditional skip") + """, + } + ) + result = pytester.runpytest("-rs", "tests/test_1.py", "--rootdir=tests") + result.stdout.fnmatch_lines( + ["SKIPPED [[]1[]] tests/test_1.py:2: Fixture conditional skip"] + ) + + def test_skip_using_reason_works_ok(pytester: Pytester) -> None: p = pytester.makepyfile( """ @@ -1472,54 +1492,6 @@ def test_failing_reason(): result.assert_outcomes(failed=1) -def test_fail_fails_with_msg_and_reason(pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - import pytest - - def test_fail_both_arguments(): - pytest.fail(reason="foo", msg="bar") - """ - ) - result = pytester.runpytest(p) - result.stdout.fnmatch_lines( - "*UsageError: Passing both ``reason`` and ``msg`` to pytest.fail(...) is not permitted.*" - ) - result.assert_outcomes(failed=1) - - -def test_skip_fails_with_msg_and_reason(pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - import pytest - - def test_skip_both_arguments(): - pytest.skip(reason="foo", msg="bar") - """ - ) - result = pytester.runpytest(p) - result.stdout.fnmatch_lines( - "*UsageError: Passing both ``reason`` and ``msg`` to pytest.skip(...) is not permitted.*" - ) - result.assert_outcomes(failed=1) - - -def test_exit_with_msg_and_reason_fails(pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - import pytest - - def test_exit_both_arguments(): - pytest.exit(reason="foo", msg="bar") - """ - ) - result = pytester.runpytest(p) - result.stdout.fnmatch_lines( - "*UsageError: cannot pass reason and msg to exit(), `msg` is deprecated, use `reason`.*" - ) - result.assert_outcomes(failed=1) - - def test_exit_with_reason_works_ok(pytester: Pytester) -> None: p = pytester.makepyfile( """ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_stash.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_stash.py index 2c9df4832e443..e523c4e6f2b08 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_stash.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_stash.py @@ -1,6 +1,6 @@ -import pytest from _pytest.stash import Stash from _pytest.stash import StashKey +import pytest def test_stash() -> None: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_stepwise.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_stepwise.py index 63d29d6241ff8..472afea66207a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_stepwise.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_stepwise.py @@ -1,6 +1,11 @@ -import pytest +# mypy: allow-untyped-defs +from pathlib import Path + +from _pytest.cacheprovider import Cache from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester +from _pytest.stepwise import STEPWISE_CACHE_DIR +import pytest @pytest.fixture @@ -277,4 +282,77 @@ def test_three(): def test_sw_skip_help(pytester: Pytester) -> None: result = pytester.runpytest("-h") - result.stdout.fnmatch_lines("*implicitly enables --stepwise.") + result.stdout.fnmatch_lines("*Implicitly enables --stepwise.") + + +def test_stepwise_xdist_dont_store_lastfailed(pytester: Pytester) -> None: + pytester.makefile( + ext=".ini", + pytest=f"[pytest]\ncache_dir = {pytester.path}\n", + ) + + pytester.makepyfile( + conftest=""" +import pytest + +@pytest.hookimpl(tryfirst=True) +def pytest_configure(config) -> None: + config.workerinput = True +""" + ) + pytester.makepyfile( + test_one=""" +def test_one(): + assert False +""" + ) + result = pytester.runpytest("--stepwise") + assert result.ret == pytest.ExitCode.INTERRUPTED + + stepwise_cache_file = ( + pytester.path / Cache._CACHE_PREFIX_VALUES / STEPWISE_CACHE_DIR + ) + assert not Path(stepwise_cache_file).exists() + + +def test_disabled_stepwise_xdist_dont_clear_cache(pytester: Pytester) -> None: + pytester.makefile( + ext=".ini", + pytest=f"[pytest]\ncache_dir = {pytester.path}\n", + ) + + stepwise_cache_file = ( + pytester.path / Cache._CACHE_PREFIX_VALUES / STEPWISE_CACHE_DIR + ) + stepwise_cache_dir = stepwise_cache_file.parent + stepwise_cache_dir.mkdir(exist_ok=True, parents=True) + + stepwise_cache_file_relative = f"{Cache._CACHE_PREFIX_VALUES}/{STEPWISE_CACHE_DIR}" + + expected_value = '"test_one.py::test_one"' + content = {f"{stepwise_cache_file_relative}": expected_value} + + pytester.makefile(ext="", **content) + + pytester.makepyfile( + conftest=""" +import pytest + +@pytest.hookimpl(tryfirst=True) +def pytest_configure(config) -> None: + config.workerinput = True +""" + ) + pytester.makepyfile( + test_one=""" +def test_one(): + assert True +""" + ) + result = pytester.runpytest() + assert result.ret == 0 + + assert Path(stepwise_cache_file).exists() + with stepwise_cache_file.open(encoding="utf-8") as file_handle: + observed_value = file_handle.readlines() + assert [expected_value] == observed_value diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_terminal.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_terminal.py index 23f597e33251f..5ed0fee82e675 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_terminal.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_terminal.py @@ -1,22 +1,22 @@ +# mypy: allow-untyped-defs """Terminal reporting of the full testing process.""" -import collections + +from io import StringIO import os +from pathlib import Path import sys import textwrap -from io import StringIO -from pathlib import Path from types import SimpleNamespace from typing import cast from typing import Dict from typing import List +from typing import NamedTuple from typing import Tuple import pluggy -import _pytest.config -import _pytest.terminal -import pytest from _pytest._io.wcwidth import wcswidth +import _pytest.config from _pytest.config import Config from _pytest.config import ExitCode from _pytest.monkeypatch import MonkeyPatch @@ -24,6 +24,7 @@ from _pytest.reports import BaseReport from _pytest.reports import CollectReport from _pytest.reports import TestReport +import _pytest.terminal from _pytest.terminal import _folded_skips from _pytest.terminal import _format_trimmed from _pytest.terminal import _get_line_with_reprcrash_message @@ -31,8 +32,12 @@ from _pytest.terminal import _plugin_nameversions from _pytest.terminal import getreportopt from _pytest.terminal import TerminalReporter +import pytest + -DistInfo = collections.namedtuple("DistInfo", ["project_name", "version"]) +class DistInfo(NamedTuple): + project_name: str + version: int TRANS_FNMATCH = str.maketrans({"[": "[[]", "]": "[]]"}) @@ -155,7 +160,6 @@ def test_report_collect_after_half_a_second( self, pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: """Test for "collecting" being updated after 0.5s""" - pytester.makepyfile( **{ "test1.py": """ @@ -244,7 +248,8 @@ class TestClass(object): def test_method(self): pass """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest("-vv") assert result.ret == 0 @@ -385,21 +390,53 @@ def test_9(): def test_10(): pytest.xfail("It's 🕙 o'clock") + + @pytest.mark.skip( + reason="1 cannot do foobar because baz is missing due to I don't know what" + ) + def test_long_skip(): + pass + + @pytest.mark.xfail( + reason="2 cannot do foobar because baz is missing due to I don't know what" + ) + def test_long_xfail(): + print(1 / 0) """ ) + + common_output = [ + "test_verbose_skip_reason.py::test_1 SKIPPED (123) *", + "test_verbose_skip_reason.py::test_2 XPASS (456) *", + "test_verbose_skip_reason.py::test_3 XFAIL (789) *", + "test_verbose_skip_reason.py::test_4 XFAIL *", + "test_verbose_skip_reason.py::test_5 SKIPPED (unconditional skip) *", + "test_verbose_skip_reason.py::test_6 XPASS *", + "test_verbose_skip_reason.py::test_7 SKIPPED *", + "test_verbose_skip_reason.py::test_8 SKIPPED (888 is great) *", + "test_verbose_skip_reason.py::test_9 XFAIL *", + "test_verbose_skip_reason.py::test_10 XFAIL (It's 🕙 o'clock) *", + ] + result = pytester.runpytest("-v") result.stdout.fnmatch_lines( [ - "test_verbose_skip_reason.py::test_1 SKIPPED (123) *", - "test_verbose_skip_reason.py::test_2 XPASS (456) *", - "test_verbose_skip_reason.py::test_3 XFAIL (789) *", - "test_verbose_skip_reason.py::test_4 XFAIL *", - "test_verbose_skip_reason.py::test_5 SKIPPED (unconditional skip) *", - "test_verbose_skip_reason.py::test_6 XPASS *", - "test_verbose_skip_reason.py::test_7 SKIPPED *", - "test_verbose_skip_reason.py::test_8 SKIPPED (888 is great) *", - "test_verbose_skip_reason.py::test_9 XFAIL *", - "test_verbose_skip_reason.py::test_10 XFAIL (It's 🕙 o'clock) *", + *common_output, + "test_verbose_skip_reason.py::test_long_skip SKIPPED (1 cannot *...) *", + "test_verbose_skip_reason.py::test_long_xfail XFAIL (2 cannot *...) *", + ] + ) + + result = pytester.runpytest("-vv") + result.stdout.fnmatch_lines( + [ + *common_output, + "test_verbose_skip_reason.py::test_long_skip SKIPPED" + " (1 cannot do foobar", + "because baz is missing due to I don't know what) *", + "test_verbose_skip_reason.py::test_long_xfail XFAIL" + " (2 cannot do foobar", + "because baz is missing due to I don't know what) *", ] ) @@ -414,7 +451,11 @@ def test_func(): ) result = pytester.runpytest("--collect-only") result.stdout.fnmatch_lines( - ["", " "] + [ + "", + " ", + " ", + ] ) def test_collectonly_skipped_module(self, pytester: Pytester) -> None: @@ -443,14 +484,15 @@ def test_with_description(): result = pytester.runpytest("--collect-only", "--verbose") result.stdout.fnmatch_lines( [ - "", - " ", - "", - " ", - " This test has a description.", - " ", - " more1.", - " more2.", + "", + " ", + " ", + " ", + " ", + " This test has a description.", + " ", + " more1.", + " more2.", ], consecutive=True, ) @@ -682,20 +724,18 @@ def test_three(): pass """ ) - result = pytester.runpytest( - "-Wignore::pytest.PytestRemovedIn7Warning", "-k", "test_two:", testpath - ) + result = pytester.runpytest("-k", "test_t", testpath) result.stdout.fnmatch_lines( ["collected 3 items / 1 deselected / 2 selected", "*test_deselected.py ..*"] ) assert result.ret == 0 - def test_deselected_with_hookwrapper(self, pytester: Pytester) -> None: + def test_deselected_with_hook_wrapper(self, pytester: Pytester) -> None: pytester.makeconftest( """ import pytest - @pytest.hookimpl(hookwrapper=True) + @pytest.hookimpl(wrapper=True) def pytest_collection_modifyitems(config, items): yield deselected = items.pop() @@ -751,6 +791,33 @@ def test_pass(): result.stdout.no_fnmatch_line("*= 1 deselected =*") assert result.ret == 0 + def test_selected_count_with_error(self, pytester: Pytester) -> None: + pytester.makepyfile( + test_selected_count_3=""" + def test_one(): + pass + def test_two(): + pass + def test_three(): + pass + """, + test_selected_count_error=""" + 5/0 + def test_foo(): + pass + def test_bar(): + pass + """, + ) + result = pytester.runpytest("-k", "test_t") + result.stdout.fnmatch_lines( + [ + "collected 3 items / 1 error / 1 deselected / 2 selected", + "* ERROR collecting test_selected_count_error.py *", + ] + ) + assert result.ret == ExitCode.INTERRUPTED + def test_no_skip_summary_if_failure(self, pytester: Pytester) -> None: pytester.makepyfile( """ @@ -801,13 +868,7 @@ def test_passes(): result.stdout.fnmatch_lines( [ "*===== test session starts ====*", - "platform %s -- Python %s*pytest-%s**pluggy-%s" - % ( - sys.platform, - verinfo, - pytest.__version__, - pluggy.__version__, - ), + f"platform {sys.platform} -- Python {verinfo}*pytest-{pytest.__version__}**pluggy-{pluggy.__version__}", "*test_header_trailer_info.py .*", "=* 1 passed*in *.[0-9][0-9]s *=", ] @@ -828,13 +889,7 @@ def test_passes(): result = pytester.runpytest("--no-header") verinfo = ".".join(map(str, sys.version_info[:3])) result.stdout.no_fnmatch_line( - "platform %s -- Python %s*pytest-%s**pluggy-%s" - % ( - sys.platform, - verinfo, - pytest.__version__, - pluggy.__version__, - ) + f"platform {sys.platform} -- Python {verinfo}*pytest-{pytest.__version__}**pluggy-{pluggy.__version__}" ) if request.config.pluginmanager.list_plugin_distinfo(): result.stdout.no_fnmatch_line("plugins: *") @@ -850,7 +905,7 @@ def test_header(self, pytester: Pytester) -> None: # with configfile pytester.makeini("""[pytest]""") result = pytester.runpytest() - result.stdout.fnmatch_lines(["rootdir: *test_header0, configfile: tox.ini"]) + result.stdout.fnmatch_lines(["rootdir: *test_header0", "configfile: tox.ini"]) # with testpaths option, and not passing anything in the command-line pytester.makeini( @@ -861,33 +916,31 @@ def test_header(self, pytester: Pytester) -> None: ) result = pytester.runpytest() result.stdout.fnmatch_lines( - ["rootdir: *test_header0, configfile: 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 = pytester.runpytest("tests") - result.stdout.fnmatch_lines(["rootdir: *test_header0, configfile: tox.ini"]) + result.stdout.fnmatch_lines(["rootdir: *test_header0", "configfile: tox.ini"]) def test_header_absolute_testpath( self, pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: - """Regresstion test for #7814.""" + """Regression test for #7814.""" tests = pytester.path.joinpath("tests") tests.mkdir() pytester.makepyprojecttoml( - """ + f""" [tool.pytest.ini_options] - testpaths = ['{}'] - """.format( - tests - ) + testpaths = ['{tests}'] + """ ) result = pytester.runpytest() result.stdout.fnmatch_lines( [ - "rootdir: *absolute_testpath0, configfile: pyproject.toml, testpaths: {}".format( - tests - ) + "rootdir: *absolute_testpath0", + "configfile: pyproject.toml", + f"testpaths: {tests}", ] ) @@ -939,6 +992,22 @@ def test_showlocals(): ] ) + def test_noshowlocals_addopts_override(self, pytester: Pytester) -> None: + pytester.makeini("[pytest]\naddopts=--showlocals") + p1 = pytester.makepyfile( + """ + def test_noshowlocals(): + x = 3 + y = "x" * 5000 + assert 0 + """ + ) + + # Override global --showlocals for py.test via arg + result = pytester.runpytest(p1, "--no-showlocals") + result.stdout.no_fnmatch_line("x* = 3") + result.stdout.no_fnmatch_line("y* = 'xxxxxx*") + def test_showlocals_short(self, pytester: Pytester) -> None: p1 = pytester.makepyfile( """ @@ -1080,7 +1149,21 @@ def test(): assert result.stdout.lines.count(expected) == 1 -def test_fail_extra_reporting(pytester: Pytester, monkeypatch) -> None: +@pytest.mark.parametrize( + ("use_ci", "expected_message"), + ( + (True, f"- AssertionError: {'this_failed'*100}"), + (False, "- AssertionError: this_failedt..."), + ), + ids=("on CI", "not on CI"), +) +def test_fail_extra_reporting( + pytester: Pytester, monkeypatch, use_ci: bool, expected_message: str +) -> None: + if use_ci: + monkeypatch.setenv("CI", "true") + else: + monkeypatch.delenv("CI", raising=False) monkeypatch.setenv("COLUMNS", "80") pytester.makepyfile("def test_this(): assert 0, 'this_failed' * 100") result = pytester.runpytest("-rN") @@ -1089,7 +1172,7 @@ def test_fail_extra_reporting(pytester: Pytester, monkeypatch) -> None: result.stdout.fnmatch_lines( [ "*test summary*", - "FAILED test_fail_extra_reporting.py::test_this - AssertionError: this_failedt...", + f"FAILED test_fail_extra_reporting.py::test_this {expected_message}", ] ) @@ -1176,14 +1259,14 @@ def test_this(): "=*= FAILURES =*=", "{red}{bold}_*_ test_this _*_{reset}", "", - " {kw}def{hl-reset} {function}test_this{hl-reset}():", - "> fail()", + " {reset}{kw}def{hl-reset} {function}test_this{hl-reset}():{endline}", + "> fail(){endline}", "", "{bold}{red}test_color_yes.py{reset}:5: ", "_ _ * _ _*", "", - " {kw}def{hl-reset} {function}fail{hl-reset}():", - "> {kw}assert{hl-reset} {number}0{hl-reset}", + " {reset}{kw}def{hl-reset} {function}fail{hl-reset}():{endline}", + "> {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", "", "{bold}{red}test_color_yes.py{reset}:2: AssertionError", @@ -1203,9 +1286,9 @@ def test_this(): "=*= FAILURES =*=", "{red}{bold}_*_ test_this _*_{reset}", "{bold}{red}test_color_yes.py{reset}:5: in test_this", - " fail()", + " {reset}fail(){endline}", "{bold}{red}test_color_yes.py{reset}:2: in fail", - " {kw}assert{hl-reset} {number}0{hl-reset}", + " {reset}{kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", "{red}=*= {red}{bold}1 failed{reset}{red} in *s{reset}{red} =*={reset}", ] @@ -1450,6 +1533,19 @@ def test_func2(): s = result.stdout.str() assert "def test_func2" not in s + def test_tb_crashline_pytrace_false(self, pytester: Pytester, option) -> None: + p = pytester.makepyfile( + """ + import pytest + def test_func1(): + pytest.fail('test_func1', pytrace=False) + """ + ) + result = pytester.runpytest("--tb=line") + result.stdout.str() + bn = p.name + result.stdout.fnmatch_lines(["*%s:3: Failed: test_func1" % bn]) + def test_pytest_report_header(self, pytester: Pytester, option) -> None: pytester.makeconftest( """ @@ -1463,7 +1559,8 @@ def pytest_report_header(config): """ def pytest_report_header(config, start_path): return ["line1", str(start_path)] -""" +""", + encoding="utf-8", ) result = pytester.runpytest("a") result.stdout.fnmatch_lines(["*hello: 42*", "line1", str(pytester.path)]) @@ -1567,7 +1664,7 @@ def test_fdopen_kept_alive_issue124(pytester: Pytester) -> None: import os, sys k = [] def test_open_file_and_keep_alive(capfd): - stdout = os.fdopen(1, 'w', 1) + stdout = os.fdopen(1, 'w', buffering=1, encoding='utf-8') k.append(stdout) def test_close_kept_alive_file(): @@ -1696,7 +1793,7 @@ def test_failure(): @pytest.fixture(scope="session") def tr() -> TerminalReporter: - config = _pytest.config._prepareconfig() + config = _pytest.config._prepareconfig([]) return TerminalReporter(config) @@ -1895,9 +1992,9 @@ def test_normal_verbosity(self, pytester: Pytester, test_files) -> None: result = pytester.runpytest("-o", "console_output_style=classic") result.stdout.fnmatch_lines( [ + f"sub{os.sep}test_three.py .F.", "test_one.py .", "test_two.py F", - f"sub{os.sep}test_three.py .F.", "*2 failed, 3 passed in*", ] ) @@ -1906,18 +2003,18 @@ def test_verbose(self, pytester: Pytester, test_files) -> None: result = pytester.runpytest("-o", "console_output_style=classic", "-v") result.stdout.fnmatch_lines( [ - "test_one.py::test_one PASSED", - "test_two.py::test_two FAILED", f"sub{os.sep}test_three.py::test_three_1 PASSED", f"sub{os.sep}test_three.py::test_three_2 FAILED", f"sub{os.sep}test_three.py::test_three_3 PASSED", + "test_one.py::test_one PASSED", + "test_two.py::test_two FAILED", "*2 failed, 3 passed in*", ] ) def test_quiet(self, pytester: Pytester, test_files) -> None: result = pytester.runpytest("-o", "console_output_style=classic", "-q") - result.stdout.fnmatch_lines([".F.F.", "*2 failed, 3 passed in*"]) + result.stdout.fnmatch_lines([".F..F", "*2 failed, 3 passed in*"]) class TestProgressOutputStyle: @@ -2124,6 +2221,24 @@ def test_capture_no(self, many_tests_files, pytester: Pytester) -> None: output = pytester.runpytest("--capture=no") output.stdout.no_fnmatch_line("*%]*") + def test_capture_no_progress_enabled( + self, many_tests_files, pytester: Pytester + ) -> None: + pytester.makeini( + """ + [pytest] + console_output_style = progress-even-when-capture-no + """ + ) + output = pytester.runpytest("-s") + output.stdout.re_match_lines( + [ + r"test_bar.py \.{10} \s+ \[ 50%\]", + r"test_foo.py \.{5} \s+ \[ 75%\]", + r"test_foobar.py \.{5} \s+ \[100%\]", + ] + ) + class TestProgressWithTeardown: """Ensure we show the correct percentages for tests that fail during teardown (#3088)""" @@ -2260,10 +2375,15 @@ def test_line_with_reprcrash(monkeypatch: MonkeyPatch) -> None: def mock_get_pos(*args): return mocked_pos - monkeypatch.setattr(_pytest.terminal, "_get_pos", mock_get_pos) + monkeypatch.setattr(_pytest.terminal, "_get_node_id_with_markup", mock_get_pos) + + class Namespace: + def __init__(self, **kwargs): + self.__dict__.update(kwargs) class config: - pass + def __init__(self): + self.option = Namespace(verbose=0) class rep: def _get_verbose_word(self, *args): @@ -2274,10 +2394,21 @@ class reprcrash: pass def check(msg, width, expected): + class DummyTerminalWriter: + fullwidth = width + + def markup(self, word: str, **markup: str): + return word + __tracebackhide__ = True if msg: rep.longrepr.reprcrash.message = msg # type: ignore - actual = _get_line_with_reprcrash_message(config, rep(), width) # type: ignore + actual = _get_line_with_reprcrash_message( + config(), # type: ignore[arg-type] + rep(), # type: ignore[arg-type] + DummyTerminalWriter(), # type: ignore[arg-type] + {}, + ) assert actual == expected if actual != f"{mocked_verbose_word} {mocked_pos}": @@ -2317,6 +2448,43 @@ def check(msg, width, expected): check("🉐🉐🉐🉐🉐\n2nd line", 80, "FAILED nodeid::🉐::withunicode - 🉐🉐🉐🉐🉐") +def test_short_summary_with_verbose( + monkeypatch: MonkeyPatch, pytester: Pytester +) -> None: + """With -vv do not truncate the summary info (#11777).""" + # On CI we also do not truncate the summary info, monkeypatch it to ensure we + # are testing against the -vv flag on CI. + monkeypatch.setattr(_pytest.terminal, "running_on_ci", lambda: False) + + string_length = 200 + pytester.makepyfile( + f""" + def test(): + s1 = "A" * {string_length} + s2 = "B" * {string_length} + assert s1 == s2 + """ + ) + + # No -vv, summary info should be truncated. + result = pytester.runpytest() + result.stdout.fnmatch_lines( + [ + "*short test summary info*", + "* assert 'AAA...", + ], + ) + + # No truncation with -vv. + result = pytester.runpytest("-vv") + result.stdout.fnmatch_lines( + [ + "*short test summary info*", + f"*{'A' * string_length}*{'B' * string_length}'", + ] + ) + + @pytest.mark.parametrize( "seconds, expected", [ @@ -2377,8 +2545,8 @@ def test_foo(): 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}", + " {reset}{kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", + "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}", "{bold}{red}E assert 1 == 10{reset}", ] ) @@ -2399,9 +2567,9 @@ def test_foo(): result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():", + " {reset}{kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", " {print}print{hl-reset}({str}'''{hl-reset}{str}{hl-reset}", - "> {str} {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}", + "> {str} {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", ] ) @@ -2422,8 +2590,8 @@ def test_foo(): 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}", + " {reset}{kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", + "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}", "{bold}{red}E assert 1 == 10{reset}", ] ) @@ -2484,3 +2652,355 @@ def test_format_trimmed() -> None: assert _format_trimmed(" ({}) ", msg, len(msg) + 4) == " (unconditional skip) " assert _format_trimmed(" ({}) ", msg, len(msg) + 3) == " (unconditional ...) " + + +class TestFineGrainedTestCase: + DEFAULT_FILE_CONTENTS = """ + import pytest + + @pytest.mark.parametrize("i", range(4)) + def test_ok(i): + ''' + some docstring + ''' + pass + + def test_fail(): + assert False + """ + LONG_SKIP_FILE_CONTENTS = """ + import pytest + + @pytest.mark.skip( + "some long skip reason that will not fit on a single line with other content that goes" + " on and on and on and on and on" + ) + def test_skip(): + pass + """ + + @pytest.mark.parametrize("verbosity", [1, 2]) + def test_execute_positive(self, verbosity, pytester: Pytester) -> None: + # expected: one test case per line (with file name), word describing result + p = TestFineGrainedTestCase._initialize_files(pytester, verbosity=verbosity) + result = pytester.runpytest(p) + + result.stdout.fnmatch_lines( + [ + "collected 5 items", + "", + f"{p.name}::test_ok[0] PASSED [ 20%]", + f"{p.name}::test_ok[1] PASSED [ 40%]", + f"{p.name}::test_ok[2] PASSED [ 60%]", + f"{p.name}::test_ok[3] PASSED [ 80%]", + f"{p.name}::test_fail FAILED [100%]", + ], + consecutive=True, + ) + + def test_execute_0_global_1(self, pytester: Pytester) -> None: + # expected: one file name per line, single character describing result + p = TestFineGrainedTestCase._initialize_files(pytester, verbosity=0) + result = pytester.runpytest("-v", p) + + result.stdout.fnmatch_lines( + [ + "collecting ... collected 5 items", + "", + f"{p.name} ....F [100%]", + ], + consecutive=True, + ) + + @pytest.mark.parametrize("verbosity", [-1, -2]) + def test_execute_negative(self, verbosity, pytester: Pytester) -> None: + # expected: single character describing result + p = TestFineGrainedTestCase._initialize_files(pytester, verbosity=verbosity) + result = pytester.runpytest(p) + + result.stdout.fnmatch_lines( + [ + "collected 5 items", + "....F [100%]", + ], + consecutive=True, + ) + + def test_execute_skipped_positive_2(self, pytester: Pytester) -> None: + # expected: one test case per line (with file name), word describing result, full reason + p = TestFineGrainedTestCase._initialize_files( + pytester, + verbosity=2, + file_contents=TestFineGrainedTestCase.LONG_SKIP_FILE_CONTENTS, + ) + result = pytester.runpytest(p) + + result.stdout.fnmatch_lines( + [ + "collected 1 item", + "", + f"{p.name}::test_skip SKIPPED (some long skip", + "reason that will not fit on a single line with other content that goes", + "on and on and on and on and on) [100%]", + ], + consecutive=True, + ) + + def test_execute_skipped_positive_1(self, pytester: Pytester) -> None: + # expected: one test case per line (with file name), word describing result, reason truncated + p = TestFineGrainedTestCase._initialize_files( + pytester, + verbosity=1, + file_contents=TestFineGrainedTestCase.LONG_SKIP_FILE_CONTENTS, + ) + result = pytester.runpytest(p) + + result.stdout.fnmatch_lines( + [ + "collected 1 item", + "", + f"{p.name}::test_skip SKIPPED (some long ski...) [100%]", + ], + consecutive=True, + ) + + def test_execute_skipped__0_global_1(self, pytester: Pytester) -> None: + # expected: one file name per line, single character describing result (no reason) + p = TestFineGrainedTestCase._initialize_files( + pytester, + verbosity=0, + file_contents=TestFineGrainedTestCase.LONG_SKIP_FILE_CONTENTS, + ) + result = pytester.runpytest("-v", p) + + result.stdout.fnmatch_lines( + [ + "collecting ... collected 1 item", + "", + f"{p.name} s [100%]", + ], + consecutive=True, + ) + + @pytest.mark.parametrize("verbosity", [-1, -2]) + def test_execute_skipped_negative(self, verbosity, pytester: Pytester) -> None: + # expected: single character describing result (no reason) + p = TestFineGrainedTestCase._initialize_files( + pytester, + verbosity=verbosity, + file_contents=TestFineGrainedTestCase.LONG_SKIP_FILE_CONTENTS, + ) + result = pytester.runpytest(p) + + result.stdout.fnmatch_lines( + [ + "collected 1 item", + "s [100%]", + ], + consecutive=True, + ) + + @pytest.mark.parametrize("verbosity", [1, 2]) + def test__collect_only_positive(self, verbosity, pytester: Pytester) -> None: + p = TestFineGrainedTestCase._initialize_files(pytester, verbosity=verbosity) + result = pytester.runpytest("--collect-only", p) + + result.stdout.fnmatch_lines( + [ + "collected 5 items", + "", + f"", + f" ", + " ", + " some docstring", + " ", + " some docstring", + " ", + " some docstring", + " ", + " some docstring", + " ", + ], + consecutive=True, + ) + + def test_collect_only_0_global_1(self, pytester: Pytester) -> None: + p = TestFineGrainedTestCase._initialize_files(pytester, verbosity=0) + result = pytester.runpytest("-v", "--collect-only", p) + + result.stdout.fnmatch_lines( + [ + "collecting ... collected 5 items", + "", + f"", + f" ", + " ", + " ", + " ", + " ", + " ", + ], + consecutive=True, + ) + + def test_collect_only_negative_1(self, pytester: Pytester) -> None: + p = TestFineGrainedTestCase._initialize_files(pytester, verbosity=-1) + result = pytester.runpytest("--collect-only", p) + + result.stdout.fnmatch_lines( + [ + "collected 5 items", + "", + f"{p.name}::test_ok[0]", + f"{p.name}::test_ok[1]", + f"{p.name}::test_ok[2]", + f"{p.name}::test_ok[3]", + f"{p.name}::test_fail", + ], + consecutive=True, + ) + + def test_collect_only_negative_2(self, pytester: Pytester) -> None: + p = TestFineGrainedTestCase._initialize_files(pytester, verbosity=-2) + result = pytester.runpytest("--collect-only", p) + + result.stdout.fnmatch_lines( + [ + "collected 5 items", + "", + f"{p.name}: 5", + ], + consecutive=True, + ) + + @staticmethod + def _initialize_files( + pytester: Pytester, verbosity: int, file_contents: str = DEFAULT_FILE_CONTENTS + ) -> Path: + p = pytester.makepyfile(file_contents) + pytester.makeini( + f""" + [pytest] + verbosity_test_cases = {verbosity} + """ + ) + return p + + +def test_summary_xfail_reason(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.mark.xfail + def test_xfail(): + assert False + + @pytest.mark.xfail(reason="foo") + def test_xfail_reason(): + assert False + """ + ) + result = pytester.runpytest("-rx") + expect1 = "XFAIL test_summary_xfail_reason.py::test_xfail" + expect2 = "XFAIL test_summary_xfail_reason.py::test_xfail_reason - foo" + result.stdout.fnmatch_lines([expect1, expect2]) + assert result.stdout.lines.count(expect1) == 1 + assert result.stdout.lines.count(expect2) == 1 + + +def test_summary_xfail_tb(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.mark.xfail + def test_xfail(): + a, b = 1, 2 + assert a == b + """ + ) + result = pytester.runpytest("-rx") + result.stdout.fnmatch_lines( + [ + "*= XFAILURES =*", + "*_ test_xfail _*", + "* @pytest.mark.xfail*", + "* def test_xfail():*", + "* a, b = 1, 2*", + "> *assert a == b*", + "E *assert 1 == 2*", + "test_summary_xfail_tb.py:6: AssertionError*", + "*= short test summary info =*", + "XFAIL test_summary_xfail_tb.py::test_xfail", + "*= 1 xfailed in * =*", + ] + ) + + +def test_xfail_tb_line(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.mark.xfail + def test_xfail(): + a, b = 1, 2 + assert a == b + """ + ) + result = pytester.runpytest("-rx", "--tb=line") + result.stdout.fnmatch_lines( + [ + "*= XFAILURES =*", + "*test_xfail_tb_line.py:6: assert 1 == 2", + "*= short test summary info =*", + "XFAIL test_xfail_tb_line.py::test_xfail", + "*= 1 xfailed in * =*", + ] + ) + + +def test_summary_xpass_reason(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.mark.xfail + def test_pass(): + ... + + @pytest.mark.xfail(reason="foo") + def test_reason(): + ... + """ + ) + result = pytester.runpytest("-rX") + expect1 = "XPASS test_summary_xpass_reason.py::test_pass" + expect2 = "XPASS test_summary_xpass_reason.py::test_reason - foo" + result.stdout.fnmatch_lines([expect1, expect2]) + assert result.stdout.lines.count(expect1) == 1 + assert result.stdout.lines.count(expect2) == 1 + + +def test_xpass_output(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.mark.xfail + def test_pass(): + print('hi there') + """ + ) + result = pytester.runpytest("-rX") + result.stdout.fnmatch_lines( + [ + "*= XPASSES =*", + "*_ test_pass _*", + "*- Captured stdout call -*", + "*= short test summary info =*", + "XPASS test_xpass_output.py::test_pass*", + "*= 1 xpassed in * =*", + ] + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_threadexception.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_threadexception.py index 5b7519f27d87a..99837b94e8a5f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_threadexception.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_threadexception.py @@ -1,11 +1,5 @@ -import sys - -import pytest from _pytest.pytester import Pytester - - -if sys.version_info < (3, 8): - pytest.skip("threadexception plugin needs Python>=3.8", allow_module_level=True) +import pytest @pytest.mark.filterwarnings("default::pytest.PytestUnhandledThreadExceptionWarning") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_tmpdir.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_tmpdir.py index 4f7c5384700d0..331ee7da6c7c1 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_tmpdir.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_tmpdir.py @@ -1,15 +1,15 @@ +# mypy: allow-untyped-defs +import dataclasses import os +from pathlib import Path import stat import sys -import warnings -from pathlib import Path from typing import Callable from typing import cast from typing import List +from typing import Union +import warnings -import attr - -import pytest from _pytest import pathlib from _pytest.config import Config from _pytest.monkeypatch import MonkeyPatch @@ -23,6 +23,7 @@ from _pytest.pytester import Pytester from _pytest.tmpdir import get_user from _pytest.tmpdir import TempPathFactory +import pytest def test_tmp_path_fixture(pytester: Pytester) -> None: @@ -31,9 +32,9 @@ def test_tmp_path_fixture(pytester: Pytester) -> None: results.stdout.fnmatch_lines(["*1 passed*"]) -@attr.s +@dataclasses.dataclass class FakeConfig: - basetemp = attr.ib() + basetemp: Union[str, Path] @property def trace(self): @@ -42,13 +43,21 @@ def trace(self): def get(self, key): return lambda *k: None + def getini(self, name): + if name == "tmp_path_retention_count": + return 3 + elif name == "tmp_path_retention_policy": + return "all" + else: + assert False + @property def option(self): return self class TestTmpPathHandler: - def test_mktemp(self, tmp_path): + def test_mktemp(self, tmp_path: Path) -> None: config = cast(Config, FakeConfig(tmp_path)) t = TempPathFactory.from_config(config, _ispytest=True) tmp = t.mktemp("world") @@ -59,7 +68,9 @@ def test_mktemp(self, tmp_path): assert str(tmp2.relative_to(t.getbasetemp())).startswith("this") assert tmp2 != tmp - def test_tmppath_relative_basetemp_absolute(self, tmp_path, monkeypatch): + def test_tmppath_relative_basetemp_absolute( + self, tmp_path: Path, monkeypatch: MonkeyPatch + ) -> None: """#4425""" monkeypatch.chdir(tmp_path) config = cast(Config, FakeConfig("hello")) @@ -84,6 +95,136 @@ def test_1(tmp_path): assert mytemp.exists() assert not mytemp.joinpath("hello").exists() + def test_policy_failed_removes_only_passed_dir(self, pytester: Pytester) -> None: + p = pytester.makepyfile( + """ + def test_1(tmp_path): + assert 0 == 0 + def test_2(tmp_path): + assert 0 == 1 + """ + ) + pytester.makepyprojecttoml( + """ + [tool.pytest.ini_options] + tmp_path_retention_policy = "failed" + """ + ) + + pytester.inline_run(p) + root = pytester._test_tmproot + + for child in root.iterdir(): + base_dir = list( + filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir()) + ) + assert len(base_dir) == 1 + test_dir = list( + filter( + lambda x: x.is_dir() and not x.is_symlink(), base_dir[0].iterdir() + ) + ) + # Check only the failed one remains + assert len(test_dir) == 1 + assert test_dir[0].name == "test_20" + + def test_policy_failed_removes_basedir_when_all_passed( + self, pytester: Pytester + ) -> None: + p = pytester.makepyfile( + """ + def test_1(tmp_path): + assert 0 == 0 + """ + ) + pytester.makepyprojecttoml( + """ + [tool.pytest.ini_options] + tmp_path_retention_policy = "failed" + """ + ) + + pytester.inline_run(p) + root = pytester._test_tmproot + for child in root.iterdir(): + # This symlink will be deleted by cleanup_numbered_dir **after** + # the test finishes because it's triggered by atexit. + # So it has to be ignored here. + base_dir = filter(lambda x: not x.is_symlink(), child.iterdir()) + # Check the base dir itself is gone + assert len(list(base_dir)) == 0 + + # issue #10502 + def test_policy_failed_removes_dir_when_skipped_from_fixture( + self, pytester: Pytester + ) -> None: + p = pytester.makepyfile( + """ + import pytest + + @pytest.fixture + def fixt(tmp_path): + pytest.skip() + + def test_fixt(fixt): + pass + """ + ) + pytester.makepyprojecttoml( + """ + [tool.pytest.ini_options] + tmp_path_retention_policy = "failed" + """ + ) + + pytester.inline_run(p) + + # Check if the whole directory is removed + root = pytester._test_tmproot + for child in root.iterdir(): + base_dir = list( + filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir()) + ) + assert len(base_dir) == 0 + + # issue #10502 + def test_policy_all_keeps_dir_when_skipped_from_fixture( + self, pytester: Pytester + ) -> None: + p = pytester.makepyfile( + """ + import pytest + + @pytest.fixture + def fixt(tmp_path): + pytest.skip() + + def test_fixt(fixt): + pass + """ + ) + pytester.makepyprojecttoml( + """ + [tool.pytest.ini_options] + tmp_path_retention_policy = "all" + """ + ) + pytester.inline_run(p) + + # Check if the whole directory is kept + root = pytester._test_tmproot + for child in root.iterdir(): + base_dir = list( + filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir()) + ) + assert len(base_dir) == 1 + test_dir = list( + filter( + lambda x: x.is_dir() and not x.is_symlink(), base_dir[0].iterdir() + ) + ) + assert len(test_dir) == 1 + testdata = [ ("mypath", True), @@ -101,12 +242,10 @@ def test_1(tmp_path): def test_mktemp(pytester: Pytester, basename: str, is_ok: bool) -> None: mytemp = pytester.mkdir("mytemp") p = pytester.makepyfile( - """ + f""" def test_abs_path(tmp_path_factory): - tmp_path_factory.mktemp('{}', numbered=False) - """.format( - basename - ) + tmp_path_factory.mktemp('{basename}', numbered=False) + """ ) result = pytester.runpytest(p, "--basetemp=%s" % mytemp) @@ -197,7 +336,6 @@ def test_tmp_path_fallback_uid_not_found(pytester: Pytester) -> None: """Test that tmp_path works even if the current process's user id does not correspond to a valid user. """ - pytester.makepyfile( """ def test_some(tmp_path): @@ -275,12 +413,12 @@ def test_lock_register_cleanup_removal(self, tmp_path: Path) -> None: assert not lock.exists() - def _do_cleanup(self, tmp_path: Path) -> None: + def _do_cleanup(self, tmp_path: Path, keep: int = 2) -> None: self.test_make(tmp_path) cleanup_numbered_dir( root=tmp_path, prefix=self.PREFIX, - keep=2, + keep=keep, consider_lock_dead_if_created_before=0, ) @@ -289,6 +427,11 @@ def test_cleanup_keep(self, tmp_path): a, b = (x for x in tmp_path.iterdir() if not x.is_symlink()) print(a, b) + def test_cleanup_keep_0(self, tmp_path: Path): + self._do_cleanup(tmp_path, 0) + dir_num = len(list(tmp_path.iterdir())) + assert dir_num == 0 + def test_cleanup_locked(self, tmp_path): p = make_numbered_dir(root=tmp_path, prefix=self.PREFIX) @@ -367,33 +510,31 @@ def test_on_rm_rf_error(self, tmp_path: Path) -> None: # unknown exception with pytest.warns(pytest.PytestWarning): - exc_info1 = (None, RuntimeError(), None) + exc_info1 = (RuntimeError, RuntimeError(), None) on_rm_rf_error(os.unlink, str(fn), exc_info1, start_path=tmp_path) assert fn.is_file() # we ignore FileNotFoundError - exc_info2 = (None, FileNotFoundError(), None) + exc_info2 = (FileNotFoundError, FileNotFoundError(), None) assert not on_rm_rf_error(None, str(fn), exc_info2, start_path=tmp_path) # unknown function with pytest.warns( pytest.PytestWarning, - match=r"^\(rm_rf\) unknown function None when removing .*foo.txt:\nNone: ", + match=r"^\(rm_rf\) unknown function None when removing .*foo.txt:\n: ", ): - exc_info3 = (None, PermissionError(), None) + exc_info3 = (PermissionError, PermissionError(), None) on_rm_rf_error(None, str(fn), exc_info3, start_path=tmp_path) assert fn.is_file() # ignored function - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - with pytest.warns(None) as warninfo: # type: ignore[call-overload] - 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_info5 = (None, PermissionError(), None) + with warnings.catch_warnings(record=True) as w: + exc_info4 = PermissionError() + 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 w] + + exc_info5 = PermissionError() on_rm_rf_error(os.unlink, str(fn), exc_info5, start_path=tmp_path) assert not fn.is_file() @@ -416,7 +557,7 @@ def test_basetemp_with_read_only_files(pytester: Pytester) -> None: def test(tmp_path): fn = tmp_path / 'foo.txt' - fn.write_text('hello') + fn.write_text('hello', encoding='utf-8') mode = os.stat(str(fn)).st_mode os.chmod(str(fn), mode & ~stat.S_IREAD) """ @@ -446,7 +587,7 @@ def test_tmp_path_factory_create_directory_with_safe_permissions( """Verify that pytest creates directories under /tmp with private permissions.""" # Use the test's tmp_path as the system temproot (/tmp). monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(tmp_path)) - tmp_factory = TempPathFactory(None, lambda *args: None, _ispytest=True) + tmp_factory = TempPathFactory(None, 3, "all", lambda *args: None, _ispytest=True) basetemp = tmp_factory.getbasetemp() # No world-readable permissions. @@ -466,14 +607,14 @@ def test_tmp_path_factory_fixes_up_world_readable_permissions( """ # Use the test's tmp_path as the system temproot (/tmp). monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(tmp_path)) - tmp_factory = TempPathFactory(None, lambda *args: None, _ispytest=True) + tmp_factory = TempPathFactory(None, 3, "all", lambda *args: None, _ispytest=True) basetemp = tmp_factory.getbasetemp() # Before - simulate bad perms. os.chmod(basetemp.parent, 0o777) assert (basetemp.parent.stat().st_mode & 0o077) != 0 - tmp_factory = TempPathFactory(None, lambda *args: None, _ispytest=True) + tmp_factory = TempPathFactory(None, 3, "all", lambda *args: None, _ispytest=True) basetemp = tmp_factory.getbasetemp() # After - fixed. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_unittest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_unittest.py index 1601086d5b20c..96223b22a2ea8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_unittest.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_unittest.py @@ -1,11 +1,12 @@ +# mypy: allow-untyped-defs import gc import sys from typing import List -import pytest from _pytest.config import ExitCode from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester +import pytest def test_simple_unittest(pytester: Pytester) -> None: @@ -207,10 +208,14 @@ def test_demo(self): """ ) + pytester.inline_run("-s", testpath) gc.collect() + + # Either already destroyed, or didn't run setUp. for obj in gc.get_objects(): - assert type(obj).__name__ != "TestCaseObjectsShouldBeCleanedUp" + if type(obj).__name__ == "TestCaseObjectsShouldBeCleanedUp": + assert not hasattr(obj, "an_expensive_obj") def test_unittest_skip_issue148(pytester: Pytester) -> None: @@ -294,7 +299,7 @@ def test_func2(self): @classmethod def tearDownClass(cls): cls.x -= 1 - def test_teareddown(): + def test_torn_down(): assert MyTestCase.x == 0 """ ) @@ -341,7 +346,7 @@ def test_func2(self): assert self.x == 1 def teardown_class(cls): cls.x -= 1 - def test_teareddown(): + def test_torn_down(): assert MyTestCase.x == 0 """ ) @@ -352,22 +357,21 @@ def test_teareddown(): @pytest.mark.parametrize("type", ["Error", "Failure"]) def test_testcase_adderrorandfailure_defers(pytester: Pytester, type: str) -> None: pytester.makepyfile( - """ + f""" from unittest import TestCase import pytest class MyTestCase(TestCase): def run(self, result): excinfo = pytest.raises(ZeroDivisionError, lambda: 0/0) try: - result.add%s(self, excinfo._excinfo) + result.add{type}(self, excinfo._excinfo) except KeyboardInterrupt: raise except: - pytest.fail("add%s should not raise") + pytest.fail("add{type} should not raise") def test_hello(self): pass """ - % (type, type) ) result = pytester.runpytest() result.stdout.no_fnmatch_line("*should not raise*") @@ -399,14 +403,13 @@ def from_exc_info(cls, *args, **kwargs): mp.setattr(_pytest._code, 'ExceptionInfo', FakeExceptionInfo) try: excinfo = excinfo._excinfo - result.add%(type)s(self, excinfo) + result.add{type}(self, excinfo) finally: mp.undo() def test_hello(self): pass - """ - % locals() + """.format(**locals()) ) result = pytester.runpytest() result.stdout.fnmatch_lines( @@ -833,7 +836,7 @@ def test_passing_test_is_fail(self): @pytest.mark.parametrize("stmt", ["return", "yield"]) def test_unittest_setup_interaction(pytester: Pytester, stmt: str) -> None: pytester.makepyfile( - """ + f""" import unittest import pytest class MyTestCase(unittest.TestCase): @@ -855,9 +858,7 @@ def test_method2(self): def test_classattr(self): assert self.__class__.hello == "world" - """.format( - stmt=stmt - ) + """ ) result = pytester.runpytest() result.stdout.fnmatch_lines(["*3 passed*"]) @@ -880,7 +881,7 @@ def test_method1(self): def tearDownClass(cls): cls.x = 1 - def test_not_teareddown(): + def test_not_torn_down(): assert TestFoo.x == 0 """ @@ -952,7 +953,7 @@ def test_issue333_result_clearing(pytester: Pytester) -> None: pytester.makeconftest( """ import pytest - @pytest.hookimpl(hookwrapper=True) + @pytest.hookimpl(wrapper=True) def pytest_runtest_call(item): yield assert 0 @@ -1062,7 +1063,7 @@ def pytest_collection_modifyitems(items): ) pytester.makepyfile( - """ + f""" import pytest import {module} @@ -1081,9 +1082,7 @@ def test_two(self): assert self.fixture2 - """.format( - module=module, base=base - ) + """ ) result = pytester.runpytest("-s") @@ -1241,33 +1240,69 @@ def test_2(self): @pytest.mark.parametrize("mark", ["@unittest.skip", "@pytest.mark.skip"]) -def test_pdb_teardown_skipped( +def test_pdb_teardown_skipped_for_functions( pytester: Pytester, monkeypatch: MonkeyPatch, mark: str ) -> None: - """With --pdb, setUp and tearDown should not be called for skipped tests.""" + """ + With --pdb, setUp and tearDown should not be called for tests skipped + via a decorator (#7215). + """ tracked: List[str] = [] - monkeypatch.setattr(pytest, "test_pdb_teardown_skipped", tracked, raising=False) + monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False) pytester.makepyfile( - """ + f""" import unittest import pytest class MyTestCase(unittest.TestCase): def setUp(self): - pytest.test_pdb_teardown_skipped.append("setUp:" + self.id()) + pytest.track_pdb_teardown_skipped.append("setUp:" + self.id()) def tearDown(self): - pytest.test_pdb_teardown_skipped.append("tearDown:" + self.id()) + pytest.track_pdb_teardown_skipped.append("tearDown:" + self.id()) {mark}("skipped for reasons") def test_1(self): pass - """.format( - mark=mark - ) + """ + ) + result = pytester.runpytest_inprocess("--pdb") + result.stdout.fnmatch_lines("* 1 skipped in *") + assert tracked == [] + + +@pytest.mark.parametrize("mark", ["@unittest.skip", "@pytest.mark.skip"]) +def test_pdb_teardown_skipped_for_classes( + pytester: Pytester, monkeypatch: MonkeyPatch, mark: str +) -> None: + """ + With --pdb, setUp and tearDown should not be called for tests skipped + via a decorator on the class (#10060). + """ + tracked: List[str] = [] + monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False) + + pytester.makepyfile( + f""" + import unittest + import pytest + + {mark}("skipped for reasons") + class MyTestCase(unittest.TestCase): + + def setUp(self): + pytest.track_pdb_teardown_skipped.append("setUp:" + self.id()) + + def tearDown(self): + pytest.track_pdb_teardown_skipped.append("tearDown:" + self.id()) + + def test_1(self): + pass + + """ ) result = pytester.runpytest_inprocess("--pdb") result.stdout.fnmatch_lines("* 1 skipped in *") @@ -1314,9 +1349,6 @@ def test_plain_unittest_does_not_support_async(pytester: Pytester) -> None: result.stdout.fnmatch_lines(expected_lines) -@pytest.mark.skipif( - sys.version_info < (3, 8), reason="Feature introduced in Python 3.8" -) def test_do_class_cleanups_on_success(pytester: Pytester) -> None: testpath = pytester.makepyfile( """ @@ -1342,9 +1374,6 @@ def test_cleanup_called_exactly_once(): assert passed == 3 -@pytest.mark.skipif( - sys.version_info < (3, 8), reason="Feature introduced in Python 3.8" -) def test_do_class_cleanups_on_setupclass_failure(pytester: Pytester) -> None: testpath = pytester.makepyfile( """ @@ -1369,9 +1398,6 @@ def test_cleanup_called_exactly_once(): assert passed == 1 -@pytest.mark.skipif( - sys.version_info < (3, 8), reason="Feature introduced in Python 3.8" -) def test_do_class_cleanups_on_teardownclass_failure(pytester: Pytester) -> None: testpath = pytester.makepyfile( """ @@ -1474,6 +1500,95 @@ def test_cleanup_called_the_right_number_of_times(): assert passed == 1 +class TestClassCleanupErrors: + """ + Make sure to show exceptions raised during class cleanup function (those registered + via addClassCleanup()). + + See #11728. + """ + + def test_class_cleanups_failure_in_setup(self, pytester: Pytester) -> None: + testpath = pytester.makepyfile( + """ + import unittest + class MyTestCase(unittest.TestCase): + @classmethod + def setUpClass(cls): + def cleanup(n): + raise Exception(f"fail {n}") + cls.addClassCleanup(cleanup, 2) + cls.addClassCleanup(cleanup, 1) + raise Exception("fail 0") + def test(self): + pass + """ + ) + result = pytester.runpytest("-s", testpath) + result.assert_outcomes(passed=0, errors=1) + result.stdout.fnmatch_lines( + [ + "*Unittest class cleanup errors *2 sub-exceptions*", + "*Exception: fail 1", + "*Exception: fail 2", + ] + ) + result.stdout.fnmatch_lines( + [ + "* ERROR at setup of MyTestCase.test *", + "E * Exception: fail 0", + ] + ) + + def test_class_cleanups_failure_in_teardown(self, pytester: Pytester) -> None: + testpath = pytester.makepyfile( + """ + import unittest + class MyTestCase(unittest.TestCase): + @classmethod + def setUpClass(cls): + def cleanup(n): + raise Exception(f"fail {n}") + cls.addClassCleanup(cleanup, 2) + cls.addClassCleanup(cleanup, 1) + def test(self): + pass + """ + ) + result = pytester.runpytest("-s", testpath) + result.assert_outcomes(passed=1, errors=1) + result.stdout.fnmatch_lines( + [ + "*Unittest class cleanup errors *2 sub-exceptions*", + "*Exception: fail 1", + "*Exception: fail 2", + ] + ) + + def test_class_cleanup_1_failure_in_teardown(self, pytester: Pytester) -> None: + testpath = pytester.makepyfile( + """ + import unittest + class MyTestCase(unittest.TestCase): + @classmethod + def setUpClass(cls): + def cleanup(n): + raise Exception(f"fail {n}") + cls.addClassCleanup(cleanup, 1) + def test(self): + pass + """ + ) + result = pytester.runpytest("-s", testpath) + result.assert_outcomes(passed=1, errors=1) + result.stdout.fnmatch_lines( + [ + "*ERROR at teardown of MyTestCase.test*", + "*Exception: fail 1", + ] + ) + + def test_traceback_pruning(pytester: Pytester) -> None: """Regression test for #9610 - doesn't crash during traceback pruning.""" pytester.makepyfile( @@ -1498,3 +1613,30 @@ def test_it(self): assert passed == 1 assert failed == 1 assert reprec.ret == 1 + + +def test_raising_unittest_skiptest_during_collection( + pytester: Pytester, +) -> None: + pytester.makepyfile( + """ + import unittest + + class TestIt(unittest.TestCase): + def test_it(self): pass + def test_it2(self): pass + + raise unittest.SkipTest() + + class TestIt2(unittest.TestCase): + def test_it(self): pass + def test_it2(self): pass + """ + ) + reprec = pytester.inline_run() + passed, skipped, failed = reprec.countoutcomes() + assert passed == 0 + # Unittest reports one fake test for a skipped module. + assert skipped == 1 + assert failed == 0 + assert reprec.ret == ExitCode.NO_TESTS_COLLECTED diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_unraisableexception.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_unraisableexception.py index f625833dceaca..1657cfe4a84eb 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_unraisableexception.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_unraisableexception.py @@ -1,13 +1,13 @@ import sys -import pytest from _pytest.pytester import Pytester +import pytest -if sys.version_info < (3, 8): - pytest.skip("unraisableexception plugin needs Python>=3.8", allow_module_level=True) +PYPY = hasattr(sys, "pypy_version_info") +@pytest.mark.skipif(PYPY, reason="garbage-collection differences make this flaky") @pytest.mark.filterwarnings("default::pytest.PytestUnraisableExceptionWarning") def test_unraisable(pytester: Pytester) -> None: pytester.makepyfile( @@ -40,6 +40,7 @@ def test_2(): pass ) +@pytest.mark.skipif(PYPY, reason="garbage-collection differences make this flaky") @pytest.mark.filterwarnings("default::pytest.PytestUnraisableExceptionWarning") def test_unraisable_in_setup(pytester: Pytester) -> None: pytester.makepyfile( @@ -76,6 +77,7 @@ def test_2(): pass ) +@pytest.mark.skipif(PYPY, reason="garbage-collection differences make this flaky") @pytest.mark.filterwarnings("default::pytest.PytestUnraisableExceptionWarning") def test_unraisable_in_teardown(pytester: Pytester) -> None: pytester.makepyfile( @@ -116,7 +118,7 @@ def test_2(): pass @pytest.mark.filterwarnings("error::pytest.PytestUnraisableExceptionWarning") def test_unraisable_warning_error(pytester: Pytester) -> None: pytester.makepyfile( - test_it=""" + test_it=f""" class BrokenDel: def __del__(self) -> None: raise ValueError("del is broken") @@ -124,6 +126,7 @@ def __del__(self) -> None: def test_it() -> None: obj = BrokenDel() del obj + {"import gc; gc.collect()" * PYPY} def test_2(): pass """ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_warning_types.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_warning_types.py index b49cc68f9c699..a50d278bde277 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_warning_types.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_warning_types.py @@ -1,8 +1,9 @@ +# mypy: allow-untyped-defs import inspect -import pytest from _pytest import warning_types from _pytest.pytester import Pytester +import pytest @pytest.mark.parametrize( @@ -36,3 +37,12 @@ def test(): ) result = pytester.runpytest() result.stdout.fnmatch_lines(["E pytest.PytestWarning: some warning"]) + + +@pytest.mark.filterwarnings("error") +def test_warn_explicit_for_annotates_errors_with_location(): + with pytest.raises(Warning, match="(?m)test\n at .*python_api.py:\\d+"): + warning_types.warn_explicit_for( + pytest.raises, # type: ignore[arg-type] + warning_types.PytestWarning("test"), + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_warnings.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_warnings.py index 5663c46cead18..3ef0cd3b54693 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_warnings.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_warnings.py @@ -1,12 +1,15 @@ +# mypy: allow-untyped-defs import os -import warnings +import sys from typing import List from typing import Optional from typing import Tuple +import warnings -import pytest from _pytest.fixtures import FixtureRequest from _pytest.pytester import Pytester +import pytest + WARNINGS_SUMMARY_HEADER = "warnings summary" @@ -15,16 +18,13 @@ def pyfile_with_warnings(pytester: Pytester, request: FixtureRequest) -> str: """Create a test file which calls a function in a module which generates warnings.""" pytester.syspathinsert() - test_name = request.function.__name__ - module_name = test_name.lstrip("test_") + "_module" + module_name = request.function.__name__[len("test_") :] + "_module" test_file = pytester.makepyfile( - """ + f""" import {module_name} def test_func(): assert {module_name}.foo() == 1 - """.format( - module_name=module_name - ), + """, **{ module_name: """ import warnings @@ -239,7 +239,7 @@ def test_func(): @pytest.mark.filterwarnings("always::UserWarning") -def test_warning_captured_hook(pytester: Pytester) -> None: +def test_warning_recorded_hook(pytester: Pytester) -> None: pytester.makeconftest( """ def pytest_configure(config): @@ -276,9 +276,9 @@ def pytest_warning_recorded(self, warning_message, when, nodeid, location): expected = [ ("config warning", "config", ""), ("collect warning", "collect", ""), - ("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"), + ("setup warning", "runtest", "test_warning_recorded_hook.py::test_func"), + ("call warning", "runtest", "test_warning_recorded_hook.py::test_func"), + ("teardown warning", "runtest", "test_warning_recorded_hook.py::test_func"), ] for index in range(len(expected)): collected_result = collected[index] @@ -435,7 +435,7 @@ class TestDeprecationWarningsByDefault: def create_file(self, pytester: Pytester, mark="") -> None: pytester.makepyfile( - """ + f""" import pytest, warnings warnings.warn(DeprecationWarning("collection")) @@ -443,9 +443,7 @@ def create_file(self, pytester: Pytester, mark="") -> None: {mark} def test_foo(): warnings.warn(PendingDeprecationWarning("test run")) - """.format( - mark=mark - ) + """ ) @pytest.mark.parametrize("customize_filters", [True, False]) @@ -517,6 +515,7 @@ def test_hidden_by_system(self, pytester: Pytester, monkeypatch) -> None: assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() +@pytest.mark.skip("not relevant until pytest 9.0") @pytest.mark.parametrize("change_default", [None, "ini", "cmdline"]) def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> None: """This ensures that PytestRemovedInXWarnings raised by pytest are turned into errors. @@ -528,7 +527,7 @@ def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> No """ import warnings, pytest def test(): - warnings.warn(pytest.PytestRemovedIn7Warning("some warning")) + warnings.warn(pytest.PytestRemovedIn9Warning("some warning")) """ ) if change_default == "ini": @@ -536,12 +535,12 @@ def test(): """ [pytest] filterwarnings = - ignore::pytest.PytestRemovedIn7Warning + ignore::pytest.PytestRemovedIn9Warning """ ) args = ( - ("-Wignore::pytest.PytestRemovedIn7Warning",) + ("-Wignore::pytest.PytestRemovedIn9Warning",) if change_default == "cmdline" else () ) @@ -621,11 +620,11 @@ def test_group_warnings_by_message_summary(pytester: Pytester) -> None: "*== %s ==*" % WARNINGS_SUMMARY_HEADER, "test_1.py: 21 warnings", "test_2.py: 1 warning", - " */test_1.py:7: UserWarning: foo", + " */test_1.py:8: UserWarning: foo", " warnings.warn(UserWarning(msg))", "", "test_1.py: 20 warnings", - " */test_1.py:7: UserWarning: bar", + " */test_1.py:8: UserWarning: bar", " warnings.warn(UserWarning(msg))", "", "-- Docs: *", @@ -773,3 +772,71 @@ def test_it(): "*Unknown pytest.mark.unknown*", ] ) + + +def test_warning_on_testpaths_not_found(pytester: Pytester) -> None: + # Check for warning when testpaths set, but not found by glob + pytester.makeini( + """ + [pytest] + testpaths = absent + """ + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines( + ["*ConfigWarning: No files were found in testpaths*", "*1 warning*"] + ) + + +def test_resource_warning(pytester: Pytester, monkeypatch: pytest.MonkeyPatch) -> None: + # Some platforms (notably PyPy) don't have tracemalloc. + # We choose to explicitly not skip this in case tracemalloc is not + # available, using `importorskip("tracemalloc")` for example, + # because we want to ensure the same code path does not break in those platforms. + try: + import tracemalloc # noqa: F401 + + has_tracemalloc = True + except ImportError: + has_tracemalloc = False + + # Explicitly disable PYTHONTRACEMALLOC in case pytest's test suite is running + # with it enabled. + monkeypatch.delenv("PYTHONTRACEMALLOC", raising=False) + + pytester.makepyfile( + """ + def open_file(p): + f = p.open("r", encoding="utf-8") + assert p.read_text() == "hello" + + def test_resource_warning(tmp_path): + p = tmp_path.joinpath("foo.txt") + p.write_text("hello", encoding="utf-8") + open_file(p) + """ + ) + result = pytester.run(sys.executable, "-Xdev", "-m", "pytest") + expected_extra = ( + [ + "*ResourceWarning* unclosed file*", + "*Enable tracemalloc to get traceback where the object was allocated*", + "*See https* for more info.", + ] + if has_tracemalloc + else [] + ) + result.stdout.fnmatch_lines([*expected_extra, "*1 passed*"]) + + monkeypatch.setenv("PYTHONTRACEMALLOC", "20") + + result = pytester.run(sys.executable, "-Xdev", "-m", "pytest") + expected_extra = ( + [ + "*ResourceWarning* unclosed file*", + "*Object allocated at*", + ] + if has_tracemalloc + else [] + ) + result.stdout.fnmatch_lines([*expected_extra, "*1 passed*"]) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/typing_checks.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/typing_checks.py index 0a6b5ad284104..4b146a2511003 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/typing_checks.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/typing_checks.py @@ -1,9 +1,17 @@ +# mypy: allow-untyped-defs """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 contextlib +from typing import Optional + +from typing_extensions import assert_type + import pytest +from pytest import MonkeyPatch # Issue #7488. @@ -22,3 +30,22 @@ def check_fixture_ids_callable() -> None: @pytest.mark.parametrize("func", [str, int], ids=lambda x: str(x.__name__)) def check_parametrize_ids_callable(func) -> None: pass + + +# Issue #10999. +def check_monkeypatch_typeddict(monkeypatch: MonkeyPatch) -> None: + from typing import TypedDict + + class Foo(TypedDict): + x: int + y: float + + a: Foo = {"x": 1, "y": 3.14} + monkeypatch.setitem(a, "x", 2) + monkeypatch.delitem(a, "y") + + +def check_raises_is_a_context_manager(val: bool) -> None: + with pytest.raises(RuntimeError) if val else contextlib.nullcontext() as excinfo: + pass + assert_type(excinfo, Optional[pytest.ExceptionInfo[RuntimeError]]) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/w3c-import.log new file mode 100644 index 0000000000000..e8cd5aaa29748 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/w3c-import.log @@ -0,0 +1,69 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/acceptance_test.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/deprecated_test.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_argcomplete.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_assertion.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_assertrewrite.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_cacheprovider.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_capture.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_collection.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_compat.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_config.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_debugging.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_doctest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_entry_points.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_error_diffs.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_faulthandler.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_findpaths.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_helpconfig.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_junitxml.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_legacypath.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_link_resolve.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_main.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_mark.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_mark_expression.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_meta.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_monkeypatch.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_nodes.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_parseopt.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pastebin.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pathlib.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pluginmanager.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_pytester.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_python_path.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_recwarn.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_reports.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_runner.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_runner_xunit.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_scope.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_session.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_setuponly.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_setupplan.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_skipping.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_stash.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_stepwise.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_terminal.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_threadexception.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_tmpdir.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_unittest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_unraisableexception.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_warning_types.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/test_warnings.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/testing/typing_checks.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/tox.ini b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/tox.ini index b2f90008ce121..30d3e68defc4b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/tox.ini +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/tox.ini @@ -4,20 +4,25 @@ minversion = 3.20.0 distshare = {homedir}/.tox/distshare envlist = linting - py36 - py37 py38 py39 py310 py311 + py312 + py313 pypy3 - py37-{pexpect,xdist,unittestextras,numpy,pluggymain} + py38-{pexpect,xdist,unittestextras,numpy,pluggymain,pylib} doctesting + doctesting-coverage plugins - py37-freeze + py38-freeze docs docs-checklinks + # checks that 3.11 native ExceptionGroup works with exceptiongroup + # not included in CI. + py311-exceptiongroup + [testenv] @@ -26,12 +31,20 @@ commands = doctesting: {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest coverage: coverage combine coverage: coverage report -m -passenv = USER USERNAME COVERAGE_* PYTEST_ADDOPTS TERM SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST +passenv = + COVERAGE_* + PYTEST_ADDOPTS + TERM + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST setenv = _PYTEST_TOX_DEFAULT_POSARGS={env:_PYTEST_TOX_POSARGS_DOCTESTING:} {env:_PYTEST_TOX_POSARGS_LSOF:} {env:_PYTEST_TOX_POSARGS_XDIST:} + # See https://docs.python.org/3/library/io.html#io-encoding-warning + # If we don't enable this, neither can any of our downstream users! + PYTHONWARNDEFAULTENCODING=1 + # Configuration to run with coverage similar to CI, e.g. - # "tox -e py37-coverage". + # "tox -e py38-coverage". coverage: _PYTEST_TOX_COVERAGE_RUN=coverage run -m coverage: _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess coverage: COVERAGE_FILE={toxinidir}/.coverage @@ -44,12 +57,14 @@ setenv = lsof: _PYTEST_TOX_POSARGS_LSOF=--lsof xdist: _PYTEST_TOX_POSARGS_XDIST=-n auto -extras = testing +extras = dev deps = doctesting: PyYAML + exceptiongroup: exceptiongroup>=1.0.0rc8 numpy: numpy>=1.19.4 pexpect: pexpect>=4.8.0 pluggymain: pluggy @ git+https://github.com/pytest-dev/pluggy.git + pylib: py>=1.8.2 unittestextras: twisted unittestextras: asynctest xdist: pytest-xdist>=2.1.0 @@ -61,6 +76,9 @@ skip_install = True basepython = python3 deps = pre-commit>=2.9.3 commands = pre-commit run --all-files --show-diff-on-failure {posargs:} +setenv = + # pre-commit and tools it launches are not clean of this warning. + PYTHONWARNDEFAULTENCODING= [testenv:docs] basepython = python3 @@ -75,6 +93,9 @@ commands = # 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:} +setenv = + # Sphinx is not clean of this warning. + PYTHONWARNDEFAULTENCODING= [testenv:docs-checklinks] basepython = python3 @@ -83,34 +104,42 @@ changedir = doc/en deps = -r{toxinidir}/doc/en/requirements.txt commands = sphinx-build -W -q --keep-going -b linkcheck . _build +setenv = + # Sphinx is not clean of this warning. + PYTHONWARNDEFAULTENCODING= [testenv:regen] changedir = doc/en basepython = python3 -passenv = SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST +passenv = + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST deps = - dataclasses PyYAML regendoc>=0.8.1 sphinx -whitelist_externals = +allowlist_externals = make commands = make regen +setenv = + # We don't want this warning to reach regen output. + PYTHONWARNDEFAULTENCODING= [testenv:plugins] # use latest versions of all plugins, including pre-releases pip_pre=true -# use latest pip and new dependency resolver (#7783) +# use latest pip to get new dependency resolver (#7783) download=true -install_command=python -m pip --use-feature=2020-resolver install {opts} {packages} +install_command=python -m pip install {opts} {packages} changedir = testing/plugins_integration deps = -rtesting/plugins_integration/requirements.txt setenv = PYTHONPATH=. +# Command temporarily removed until pytest-bdd is fixed: +# https://github.com/pytest-dev/pytest/pull/11785 +# pytest bdd_wallet.py commands = 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 @@ -122,7 +151,7 @@ commands = pytest pytest_twisted_integration.py pytest simple_integration.py --force-sugar --flakes -[testenv:py37-freeze] +[testenv:py38-freeze] changedir = testing/freeze deps = pyinstaller @@ -151,34 +180,10 @@ passenv = {[testenv:release]passenv} deps = {[testenv:release]deps} commands = python scripts/prepare-release-pr.py {posargs} -[testenv:publish-gh-release-notes] -description = create GitHub release after deployment +[testenv:generate-gh-release-notes] +description = generate release notes that can be published as GitHub Release basepython = python3 usedevelop = True -passenv = GH_RELEASE_NOTES_TOKEN GITHUB_REF GITHUB_REPOSITORY deps = - github3.py pypandoc -commands = python scripts/publish-gh-release-notes.py {posargs} - -[flake8] -max-line-length = 120 -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. -force_single_line = 1 -known_localfolder = pytest,_pytest -known_third_party = test_source,test_excinfo -force_alphabetical_sort_within_sections = 1 +commands = python scripts/generate-gh-release-notes.py {posargs} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/w3c-import.log new file mode 100644 index 0000000000000..150370ed78cc9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/w3c-import.log @@ -0,0 +1,29 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/AUTHORS +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/CHANGELOG.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/CITATION +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/CODE_OF_CONDUCT.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/CONTRIBUTING.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/OPENCOLLECTIVE.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/RELEASING.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/TIDELIFT.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/codecov.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/pyproject.toml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pytest/tox.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/MANIFEST.in b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/MANIFEST.in index 19256882c56fe..116235d18f461 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/MANIFEST.in +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/MANIFEST.in @@ -2,5 +2,5 @@ include COPYING include MANIFEST.in include README recursive-include example *.py -recursive-include mod_pywebsocket *.py +recursive-include pywebsocket3 *.py recursive-include test *.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/PKG-INFO b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/PKG-INFO new file mode 100644 index 0000000000000..289dfa8649be1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/PKG-INFO @@ -0,0 +1,13 @@ +Metadata-Version: 2.1 +Name: pywebsocket3 +Version: 4.0.2 +Summary: Standalone WebSocket Server for testing purposes. +Home-page: https://github.com/GoogleChromeLabs/pywebsocket3 +Author: Yuzo Fujishima +Author-email: yuzo@chromium.org +License: See LICENSE +Requires-Python: >=2.7 +License-File: LICENSE +Requires-Dist: six + +pywebsocket3 is a standalone server for the WebSocket Protocol (RFC 6455). See pywebsocket3/__init__.py for more detail. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/README.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/README.md index 8684f2cc7ee36..b46b416735606 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/README.md +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/README.md @@ -1,13 +1,14 @@ # pywebsocket3 # -The pywebsocket project aims to provide a [WebSocket](https://tools.ietf.org/html/rfc6455) standalone server. +The pywebsocket3 project aims to provide a [WebSocket](https://tools.ietf.org/html/rfc6455) standalone server. pywebsocket is intended for **testing** or **experimental** purposes. Run this to read the general document: -``` -$ pydoc mod_pywebsocket + +```bash +pydoc pywebsocket3 ``` Please see [Wiki](https://github.com/GoogleChromeLabs/pywebsocket3/wiki) for more details. @@ -15,22 +16,27 @@ Please see [Wiki](https://github.com/GoogleChromeLabs/pywebsocket3/wiki) for mor # INSTALL # To install this package to the system, run this: -``` -$ python setup.py build -$ sudo python setup.py install + +```bash +python setup.py build +sudo python setup.py install ``` To install this package as a normal user, run this instead: +```bash +python setup.py build +python setup.py install --user ``` -$ python setup.py build -$ python setup.py install --user -``` + # LAUNCH # To use pywebsocket as standalone server, run this to read the document: + +```bash +pydoc pywebsocket3.standalone ``` -$ pydoc mod_pywebsocket.standalone -``` + # Disclaimer # + This is not an officially supported Google product diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/abort_handshake_wsh.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/abort_handshake_wsh.py index 1b719ca897371..1bad8c02f21b9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/abort_handshake_wsh.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/abort_handshake_wsh.py @@ -28,7 +28,8 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import -from mod_pywebsocket import handshake + +from pywebsocket3 import handshake def web_socket_do_extra_handshake(request): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/abort_wsh.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/abort_wsh.py index d4c240bf2c1cb..c0495c7107688 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/abort_wsh.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/abort_wsh.py @@ -28,7 +28,8 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import -from mod_pywebsocket import handshake + +from pywebsocket3 import handshake def web_socket_do_extra_handshake(request): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/bench_wsh.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/bench_wsh.py index 2df50e77db276..9ea2f93159c9c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/bench_wsh.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/bench_wsh.py @@ -35,7 +35,9 @@ """ from __future__ import absolute_import + import time + from six.moves import range diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/benchmark_helper_wsh.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/benchmark_helper_wsh.py index fc175333354cc..9ea9f5642277b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/benchmark_helper_wsh.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/benchmark_helper_wsh.py @@ -27,7 +27,9 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Handler for benchmark.html.""" + from __future__ import absolute_import + import six diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/cgi-bin/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/cgi-bin/w3c-import.log new file mode 100644 index 0000000000000..cfd1960096c33 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/cgi-bin/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/cgi-bin/hi.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/close_wsh.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/close_wsh.py index 8f0005ffea990..2463bc7b31f0b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/close_wsh.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/close_wsh.py @@ -28,10 +28,8 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import -import struct -from mod_pywebsocket import common -from mod_pywebsocket import stream +from pywebsocket3 import common def web_socket_do_extra_handshake(request): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/cookie_wsh.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/cookie_wsh.py index 815209694e3ff..1ca2c843863a2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/cookie_wsh.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/cookie_wsh.py @@ -27,6 +27,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import + from six.moves import urllib diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/echo_client.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/echo_client.py index 2ed60b3b59299..5063f00a51f24 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/echo_client.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/echo_client.py @@ -30,13 +30,13 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Simple WebSocket client named echo_client just because of historical reason. -mod_pywebsocket directory must be in PYTHONPATH. +pywebsocket3 directory must be in PYTHONPATH. Example Usage: # server setup - % cd $pywebsocket - % PYTHONPATH=$cwd/src python ./mod_pywebsocket/standalone.py -p 8880 \ + % cd $pywebsocket3 + % PYTHONPATH=$cwd/src python ./pywebsocket3/standalone.py -p 8880 \ -d $cwd/src/example # run client @@ -47,27 +47,27 @@ from __future__ import absolute_import from __future__ import print_function + +import argparse import base64 import codecs -from hashlib import sha1 import logging -import argparse import os -import random import re -import six import socket import ssl -import struct import sys +from hashlib import sha1 + +import six -from mod_pywebsocket import common -from mod_pywebsocket.extensions import PerMessageDeflateExtensionProcessor -from mod_pywebsocket.extensions import _PerMessageDeflateFramer -from mod_pywebsocket.extensions import _parse_window_bits -from mod_pywebsocket.stream import Stream -from mod_pywebsocket.stream import StreamOptions -from mod_pywebsocket import util +from pywebsocket3 import common, util +from pywebsocket3.extensions import ( + PerMessageDeflateExtensionProcessor, + _PerMessageDeflateFramer, + _parse_window_bits, +) +from pywebsocket3.stream import Stream, StreamOptions _TIMEOUT_SEC = 10 _UNDEFINED_PORT = -1 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/internal_error_wsh.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/internal_error_wsh.py index 04aa684283a60..cbc0fd294e1d2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/internal_error_wsh.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/internal_error_wsh.py @@ -28,7 +28,8 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import -from mod_pywebsocket import msgutil + +from pywebsocket3 import msgutil def web_socket_do_extra_handshake(request): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/w3c-import.log new file mode 100644 index 0000000000000..a7a87aaa5555a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/w3c-import.log @@ -0,0 +1,28 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/abort_handshake_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/abort_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/bench_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/benchmark_helper_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/close_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/cookie_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/echo_client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/echo_noext_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/echo_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/hsts_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/internal_error_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/example/origin_check_wsh.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/__init__.py new file mode 100644 index 0000000000000..8f4ade05828c3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/__init__.py @@ -0,0 +1,172 @@ +# Copyright 2011, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" A Standalone WebSocket Server for testing purposes + +pywebsocket3 is an API that provides WebSocket functionalities with +a standalone WebSocket server. It is intended for testing or +experimental purposes. + +Installation +============ +1. Follow standalone server documentation to start running the +standalone server. It can be read by running the following command: + + $ pydoc pywebsocket3.standalone + +2. Once the standalone server is launched verify it by accessing +http://localhost[:port]/console.html. Include the port number when +specified on launch. If everything is working correctly, you +will see a simple echo console. + + +Writing WebSocket handlers +========================== + +When a WebSocket request comes in, the resource name +specified in the handshake is considered as if it is a file path under + and the handler defined in +/_wsh.py is invoked. + +For example, if the resource name is /example/chat, the handler defined in +/example/chat_wsh.py is invoked. + +A WebSocket handler is composed of the following three functions: + + web_socket_do_extra_handshake(request) + web_socket_transfer_data(request) + web_socket_passive_closing_handshake(request) + +where: + request: mod_python request. + +web_socket_do_extra_handshake is called during the handshake after the +headers are successfully parsed and WebSocket properties (ws_origin, +and ws_resource) are added to request. A handler +can reject the request by raising an exception. + +A request object has the following properties that you can use during the +extra handshake (web_socket_do_extra_handshake): +- ws_resource +- ws_origin +- ws_version +- ws_extensions +- ws_deflate +- ws_protocol +- ws_requested_protocols + +The last two are a bit tricky. See the next subsection. + + +Subprotocol Negotiation +----------------------- + +ws_protocol is always set to None when +web_socket_do_extra_handshake is called. If ws_requested_protocols is not +None, you must choose one subprotocol from this list and set it to +ws_protocol. + +Data Transfer +------------- + +web_socket_transfer_data is called after the handshake completed +successfully. A handler can receive/send messages from/to the client +using request. pywebsocket3.msgutil module provides utilities +for data transfer. + +You can receive a message by the following statement. + + message = request.ws_stream.receive_message() + +This call blocks until any complete text frame arrives, and the payload data +of the incoming frame will be stored into message. When you're using IETF +HyBi 00 or later protocol, receive_message() will return None on receiving +client-initiated closing handshake. When any error occurs, receive_message() +will raise some exception. + +You can send a message by the following statement. + + request.ws_stream.send_message(message) + + +Closing Connection +------------------ + +Executing the following statement or just return-ing from +web_socket_transfer_data cause connection close. + + request.ws_stream.close_connection() + +close_connection will wait +for closing handshake acknowledgement coming from the client. When it +couldn't receive a valid acknowledgement, raises an exception. + +web_socket_passive_closing_handshake is called after the server receives +incoming closing frame from the client peer immediately. You can specify +code and reason by return values. They are sent as a outgoing closing frame +from the server. A request object has the following properties that you can +use in web_socket_passive_closing_handshake. +- ws_close_code +- ws_close_reason + + +Threading +--------- + +A WebSocket handler must be thread-safe. The standalone +server uses threads by default. + + +Configuring WebSocket Extension Processors +------------------------------------------ + +See extensions.py for supported WebSocket extensions. Note that they are +unstable and their APIs are subject to change substantially. + +A request object has these extension processing related attributes. + +- ws_requested_extensions: + + A list of common.ExtensionParameter instances representing extension + parameters received from the client in the client's opening handshake. + You shouldn't modify it manually. + +- ws_extensions: + + A list of common.ExtensionParameter instances representing extension + parameters to send back to the client in the server's opening handshake. + You shouldn't touch it directly. Instead, call methods on extension + processors. + +- ws_extension_processors: + + A list of loaded extension processors. Find the processor for the + extension you want to configure from it, and call its methods. +""" + +# vi:sts=4 sw=4 et tw=72 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/_stream_exceptions.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/_stream_exceptions.py new file mode 100644 index 0000000000000..b47878bc4a379 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/_stream_exceptions.py @@ -0,0 +1,82 @@ +# Copyright 2020, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""Stream Exceptions. +""" + +# Note: request.connection.write/read are used in this module, even though +# mod_python document says that they should be used only in connection +# handlers. Unfortunately, we have no other options. For example, +# request.write/read are not suitable because they don't allow direct raw bytes +# writing/reading. + + +# Exceptions +class ConnectionTerminatedException(Exception): + """This exception will be raised when a connection is terminated + unexpectedly. + """ + + pass + + +class InvalidFrameException(ConnectionTerminatedException): + """This exception will be raised when we received an invalid frame we + cannot parse. + """ + + pass + + +class BadOperationException(Exception): + """This exception will be raised when send_message() is called on + server-terminated connection or receive_message() is called on + client-terminated connection. + """ + + pass + + +class UnsupportedFrameException(Exception): + """This exception will be raised when we receive a frame with flag, opcode + we cannot handle. Handlers can just catch and ignore this exception and + call receive_message() again to continue processing the next frame. + """ + + pass + + +class InvalidUTF8Exception(Exception): + """This exception will be raised when we receive a text frame which + contains invalid UTF-8 strings. + """ + + pass + + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/common.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/common.py new file mode 100644 index 0000000000000..a3321e4c68c26 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/common.py @@ -0,0 +1,275 @@ +# Copyright 2012, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""This file must not depend on any module specific to the WebSocket protocol. +""" + +from __future__ import absolute_import + +from pywebsocket3 import http_header_util + + +# Additional log level definitions. +LOGLEVEL_FINE = 9 + +# Constants indicating WebSocket protocol version. +VERSION_HYBI13 = 13 +VERSION_HYBI14 = 13 +VERSION_HYBI15 = 13 +VERSION_HYBI16 = 13 +VERSION_HYBI17 = 13 + +# Constants indicating WebSocket protocol latest version. +VERSION_HYBI_LATEST = VERSION_HYBI13 + +# Port numbers +DEFAULT_WEB_SOCKET_PORT = 80 +DEFAULT_WEB_SOCKET_SECURE_PORT = 443 + +# Schemes +WEB_SOCKET_SCHEME = 'ws' +WEB_SOCKET_SECURE_SCHEME = 'wss' + +# Frame opcodes defined in the spec. +OPCODE_CONTINUATION = 0x0 +OPCODE_TEXT = 0x1 +OPCODE_BINARY = 0x2 +OPCODE_CLOSE = 0x8 +OPCODE_PING = 0x9 +OPCODE_PONG = 0xa + +# UUID for the opening handshake and frame masking. +WEBSOCKET_ACCEPT_UUID = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11' + +# Opening handshake header names and expected values. +UPGRADE_HEADER = 'Upgrade' +WEBSOCKET_UPGRADE_TYPE = 'websocket' +CONNECTION_HEADER = 'Connection' +UPGRADE_CONNECTION_TYPE = 'Upgrade' +HOST_HEADER = 'Host' +ORIGIN_HEADER = 'Origin' +SEC_WEBSOCKET_KEY_HEADER = 'Sec-WebSocket-Key' +SEC_WEBSOCKET_ACCEPT_HEADER = 'Sec-WebSocket-Accept' +SEC_WEBSOCKET_VERSION_HEADER = 'Sec-WebSocket-Version' +SEC_WEBSOCKET_PROTOCOL_HEADER = 'Sec-WebSocket-Protocol' +SEC_WEBSOCKET_EXTENSIONS_HEADER = 'Sec-WebSocket-Extensions' + +# Extensions +PERMESSAGE_DEFLATE_EXTENSION = 'permessage-deflate' + +# Status codes +# Code STATUS_NO_STATUS_RECEIVED, STATUS_ABNORMAL_CLOSURE, and +# STATUS_TLS_HANDSHAKE are pseudo codes to indicate specific error cases. +# Could not be used for codes in actual closing frames. +# Application level errors must use codes in the range +# STATUS_USER_REGISTERED_BASE to STATUS_USER_PRIVATE_MAX. The codes in the +# range STATUS_USER_REGISTERED_BASE to STATUS_USER_REGISTERED_MAX are managed +# by IANA. Usually application must define user protocol level errors in the +# range STATUS_USER_PRIVATE_BASE to STATUS_USER_PRIVATE_MAX. +STATUS_NORMAL_CLOSURE = 1000 +STATUS_GOING_AWAY = 1001 +STATUS_PROTOCOL_ERROR = 1002 +STATUS_UNSUPPORTED_DATA = 1003 +STATUS_NO_STATUS_RECEIVED = 1005 +STATUS_ABNORMAL_CLOSURE = 1006 +STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007 +STATUS_POLICY_VIOLATION = 1008 +STATUS_MESSAGE_TOO_BIG = 1009 +STATUS_MANDATORY_EXTENSION = 1010 +STATUS_INTERNAL_ENDPOINT_ERROR = 1011 +STATUS_TLS_HANDSHAKE = 1015 +STATUS_USER_REGISTERED_BASE = 3000 +STATUS_USER_REGISTERED_MAX = 3999 +STATUS_USER_PRIVATE_BASE = 4000 +STATUS_USER_PRIVATE_MAX = 4999 +# Following definitions are aliases to keep compatibility. Applications must +# not use these obsoleted definitions anymore. +STATUS_NORMAL = STATUS_NORMAL_CLOSURE +STATUS_UNSUPPORTED = STATUS_UNSUPPORTED_DATA +STATUS_CODE_NOT_AVAILABLE = STATUS_NO_STATUS_RECEIVED +STATUS_ABNORMAL_CLOSE = STATUS_ABNORMAL_CLOSURE +STATUS_INVALID_FRAME_PAYLOAD = STATUS_INVALID_FRAME_PAYLOAD_DATA +STATUS_MANDATORY_EXT = STATUS_MANDATORY_EXTENSION + +# HTTP status codes +HTTP_STATUS_BAD_REQUEST = 400 +HTTP_STATUS_FORBIDDEN = 403 +HTTP_STATUS_NOT_FOUND = 404 + + +def is_control_opcode(opcode): + return (opcode >> 3) == 1 + + +class ExtensionParameter(object): + """This is exchanged on extension negotiation in opening handshake.""" + def __init__(self, name): + self._name = name + # TODO(tyoshino): Change the data structure to more efficient one such + # as dict when the spec changes to say like + # - Parameter names must be unique + # - The order of parameters is not significant + self._parameters = [] + + def name(self): + """Return the extension name.""" + return self._name + + def add_parameter(self, name, value): + """Add a parameter.""" + self._parameters.append((name, value)) + + def get_parameters(self): + """Return the parameters.""" + return self._parameters + + def get_parameter_names(self): + """Return the names of the parameters.""" + return [name for name, unused_value in self._parameters] + + def has_parameter(self, name): + """Test if a parameter exists.""" + for param_name, param_value in self._parameters: + if param_name == name: + return True + return False + + def get_parameter_value(self, name): + """Get the value of a specific parameter.""" + for param_name, param_value in self._parameters: + if param_name == name: + return param_value + + +class ExtensionParsingException(Exception): + """Exception to handle errors in extension parsing.""" + def __init__(self, name): + super(ExtensionParsingException, self).__init__(name) + + +def _parse_extension_param(state, definition): + param_name = http_header_util.consume_token(state) + + if param_name is None: + raise ExtensionParsingException('No valid parameter name found') + + http_header_util.consume_lwses(state) + + if not http_header_util.consume_string(state, '='): + definition.add_parameter(param_name, None) + return + + http_header_util.consume_lwses(state) + + # TODO(tyoshino): Add code to validate that parsed param_value is token + param_value = http_header_util.consume_token_or_quoted_string(state) + if param_value is None: + raise ExtensionParsingException( + 'No valid parameter value found on the right-hand side of ' + 'parameter %r' % param_name) + + definition.add_parameter(param_name, param_value) + + +def _parse_extension(state): + extension_token = http_header_util.consume_token(state) + if extension_token is None: + return None + + extension = ExtensionParameter(extension_token) + + while True: + http_header_util.consume_lwses(state) + + if not http_header_util.consume_string(state, ';'): + break + + http_header_util.consume_lwses(state) + + try: + _parse_extension_param(state, extension) + except ExtensionParsingException as e: + raise ExtensionParsingException( + 'Failed to parse parameter for %r (%r)' % (extension_token, e)) + + return extension + + +def parse_extensions(data): + """Parse Sec-WebSocket-Extensions header value. + + Returns a list of ExtensionParameter objects. + Leading LWSes must be trimmed. + """ + state = http_header_util.ParsingState(data) + + extension_list = [] + while True: + extension = _parse_extension(state) + if extension is not None: + extension_list.append(extension) + + http_header_util.consume_lwses(state) + + if http_header_util.peek(state) is None: + break + + if not http_header_util.consume_string(state, ','): + raise ExtensionParsingException( + 'Failed to parse Sec-WebSocket-Extensions header: ' + 'Expected a comma but found %r' % http_header_util.peek(state)) + + http_header_util.consume_lwses(state) + + if len(extension_list) == 0: + raise ExtensionParsingException('No valid extension entry found') + + return extension_list + + +def format_extension(extension): + """Format an ExtensionParameter object.""" + formatted_params = [extension.name()] + for param_name, param_value in extension.get_parameters(): + if param_value is None: + formatted_params.append(param_name) + else: + quoted_value = http_header_util.quote_if_necessary(param_value) + formatted_params.append('%s=%s' % (param_name, quoted_value)) + return '; '.join(formatted_params) + + +def format_extensions(extension_list): + """Format a list of ExtensionParameter objects.""" + formatted_extension_list = [] + for extension in extension_list: + formatted_extension_list.append(format_extension(extension)) + return ', '.join(formatted_extension_list) + + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/dispatch.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/dispatch.py new file mode 100644 index 0000000000000..fd35ceab29782 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/dispatch.py @@ -0,0 +1,391 @@ +# Copyright 2012, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""Dispatch WebSocket request. +""" + +from __future__ import absolute_import + +import io +import os +import re +import traceback + +from pywebsocket3 import ( + common, + handshake, + msgutil, + stream, + util +) + +_SOURCE_PATH_PATTERN = re.compile(r'(?i)_wsh\.py$') +_SOURCE_SUFFIX = '_wsh.py' +_DO_EXTRA_HANDSHAKE_HANDLER_NAME = 'web_socket_do_extra_handshake' +_TRANSFER_DATA_HANDLER_NAME = 'web_socket_transfer_data' +_PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME = ( + 'web_socket_passive_closing_handshake') + + +class DispatchException(Exception): + """Exception in dispatching WebSocket request.""" + def __init__(self, name, status=common.HTTP_STATUS_NOT_FOUND): + super(DispatchException, self).__init__(name) + self.status = status + + +def _default_passive_closing_handshake_handler(request): + """Default web_socket_passive_closing_handshake handler.""" + + return common.STATUS_NORMAL_CLOSURE, '' + + +def _normalize_path(path): + """Normalize path. + + Args: + path: the path to normalize. + + Path is converted to the absolute path. + The input path can use either '\\' or '/' as the separator. + The normalized path always uses '/' regardless of the platform. + """ + + path = path.replace('\\', os.path.sep) + path = os.path.realpath(path) + path = path.replace('\\', '/') + return path + + +def _create_path_to_resource_converter(base_dir): + """Returns a function that converts the path of a WebSocket handler source + file to a resource string by removing the path to the base directory from + its head, removing _SOURCE_SUFFIX from its tail, and replacing path + separators in it with '/'. + + Args: + base_dir: the path to the base directory. + """ + + base_dir = _normalize_path(base_dir) + + base_len = len(base_dir) + suffix_len = len(_SOURCE_SUFFIX) + + def converter(path): + if not path.endswith(_SOURCE_SUFFIX): + return None + # _normalize_path must not be used because resolving symlink breaks + # following path check. + path = path.replace('\\', '/') + if not path.startswith(base_dir): + return None + return path[base_len:-suffix_len] + + return converter + + +def _enumerate_handler_file_paths(directory): + """Returns a generator that enumerates WebSocket Handler source file names + in the given directory. + """ + + for root, unused_dirs, files in os.walk(directory): + for base in files: + path = os.path.join(root, base) + if _SOURCE_PATH_PATTERN.search(path): + yield path + + +class _HandlerSuite(object): + """A handler suite holder class.""" + def __init__(self, do_extra_handshake, transfer_data, + passive_closing_handshake): + self.do_extra_handshake = do_extra_handshake + self.transfer_data = transfer_data + self.passive_closing_handshake = passive_closing_handshake + + +def _source_handler_file(handler_definition): + """Source a handler definition string. + + Args: + handler_definition: a string containing Python statements that define + handler functions. + """ + + global_dic = {} + try: + # This statement is gramatically different in python 2 and 3. + # Hence, yapf will complain about this. To overcome this, we disable + # yapf for this line. + exec(handler_definition, global_dic) # yapf: disable + except Exception: + raise DispatchException('Error in sourcing handler:' + + traceback.format_exc()) + passive_closing_handshake_handler = None + try: + passive_closing_handshake_handler = _extract_handler( + global_dic, _PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME) + except Exception: + passive_closing_handshake_handler = ( + _default_passive_closing_handshake_handler) + return _HandlerSuite( + _extract_handler(global_dic, _DO_EXTRA_HANDSHAKE_HANDLER_NAME), + _extract_handler(global_dic, _TRANSFER_DATA_HANDLER_NAME), + passive_closing_handshake_handler) + + +def _extract_handler(dic, name): + """Extracts a callable with the specified name from the given dictionary + dic. + """ + + if name not in dic: + raise DispatchException('%s is not defined.' % name) + handler = dic[name] + if not callable(handler): + raise DispatchException('%s is not callable.' % name) + return handler + + +class Dispatcher(object): + """Dispatches WebSocket requests. + + This class maintains a map from resource name to handlers. + """ + def __init__(self, + root_dir, + scan_dir=None, + allow_handlers_outside_root_dir=True, + handler_encoding=None): + """Construct an instance. + + Args: + root_dir: The directory where handler definition files are + placed. + scan_dir: The directory where handler definition files are + searched. scan_dir must be a directory under root_dir, + including root_dir itself. If scan_dir is None, + root_dir is used as scan_dir. scan_dir can be useful + in saving scan time when root_dir contains many + subdirectories. + allow_handlers_outside_root_dir: Scans handler files even if their + canonical path is not under root_dir. + """ + + self._logger = util.get_class_logger(self) + + self._handler_suite_map = {} + self._source_warnings = [] + if scan_dir is None: + scan_dir = root_dir + if not os.path.realpath(scan_dir).startswith( + os.path.realpath(root_dir)): + raise DispatchException('scan_dir:%s must be a directory under ' + 'root_dir:%s.' % (scan_dir, root_dir)) + self._source_handler_files_in_dir(root_dir, scan_dir, + allow_handlers_outside_root_dir, + handler_encoding) + + def add_resource_path_alias(self, alias_resource_path, + existing_resource_path): + """Add resource path alias. + + Once added, request to alias_resource_path would be handled by + handler registered for existing_resource_path. + + Args: + alias_resource_path: alias resource path + existing_resource_path: existing resource path + """ + try: + handler_suite = self._handler_suite_map[existing_resource_path] + self._handler_suite_map[alias_resource_path] = handler_suite + except KeyError: + raise DispatchException('No handler for: %r' % + existing_resource_path) + + def source_warnings(self): + """Return warnings in sourcing handlers.""" + + return self._source_warnings + + def do_extra_handshake(self, request): + """Do extra checking in WebSocket handshake. + + Select a handler based on request.uri and call its + web_socket_do_extra_handshake function. + + Args: + request: mod_python request. + + Raises: + DispatchException: when handler was not found + AbortedByUserException: when user handler abort connection + HandshakeException: when opening handshake failed + """ + + handler_suite = self.get_handler_suite(request.ws_resource) + if handler_suite is None: + raise DispatchException('No handler for: %r' % request.ws_resource) + do_extra_handshake_ = handler_suite.do_extra_handshake + try: + do_extra_handshake_(request) + except handshake.AbortedByUserException as e: + # Re-raise to tell the caller of this function to finish this + # connection without sending any error. + self._logger.debug('%s', traceback.format_exc()) + raise + except Exception as e: + util.prepend_message_to_exception( + '%s raised exception for %s: ' % + (_DO_EXTRA_HANDSHAKE_HANDLER_NAME, request.ws_resource), e) + raise handshake.HandshakeException(e, common.HTTP_STATUS_FORBIDDEN) + + def transfer_data(self, request): + """Let a handler transfer_data with a WebSocket client. + + Select a handler based on request.ws_resource and call its + web_socket_transfer_data function. + + Args: + request: mod_python request. + + Raises: + DispatchException: when handler was not found + AbortedByUserException: when user handler abort connection + """ + + # TODO(tyoshino): Terminate underlying TCP connection if possible. + try: + handler_suite = self.get_handler_suite(request.ws_resource) + if handler_suite is None: + raise DispatchException('No handler for: %r' % + request.ws_resource) + transfer_data_ = handler_suite.transfer_data + transfer_data_(request) + + if not request.server_terminated: + request.ws_stream.close_connection() + # Catch non-critical exceptions the handler didn't handle. + except handshake.AbortedByUserException as e: + self._logger.debug('%s', traceback.format_exc()) + raise + except msgutil.BadOperationException as e: + self._logger.debug('%s', e) + request.ws_stream.close_connection( + common.STATUS_INTERNAL_ENDPOINT_ERROR) + except msgutil.InvalidFrameException as e: + # InvalidFrameException must be caught before + # ConnectionTerminatedException that catches InvalidFrameException. + self._logger.debug('%s', e) + request.ws_stream.close_connection(common.STATUS_PROTOCOL_ERROR) + except msgutil.UnsupportedFrameException as e: + self._logger.debug('%s', e) + request.ws_stream.close_connection(common.STATUS_UNSUPPORTED_DATA) + except stream.InvalidUTF8Exception as e: + self._logger.debug('%s', e) + request.ws_stream.close_connection( + common.STATUS_INVALID_FRAME_PAYLOAD_DATA) + except msgutil.ConnectionTerminatedException as e: + self._logger.debug('%s', e) + except Exception as e: + # Any other exceptions are forwarded to the caller of this + # function. + util.prepend_message_to_exception( + '%s raised exception for %s: ' % + (_TRANSFER_DATA_HANDLER_NAME, request.ws_resource), e) + raise + + def passive_closing_handshake(self, request): + """Prepare code and reason for responding client initiated closing + handshake. + """ + + handler_suite = self.get_handler_suite(request.ws_resource) + if handler_suite is None: + return _default_passive_closing_handshake_handler(request) + return handler_suite.passive_closing_handshake(request) + + def get_handler_suite(self, resource): + """Retrieves two handlers (one for extra handshake processing, and one + for data transfer) for the given request as a HandlerSuite object. + """ + + fragment = None + if '#' in resource: + resource, fragment = resource.split('#', 1) + if '?' in resource: + resource = resource.split('?', 1)[0] + handler_suite = self._handler_suite_map.get(resource) + if handler_suite and fragment: + raise DispatchException( + 'Fragment identifiers MUST NOT be used on WebSocket URIs', + common.HTTP_STATUS_BAD_REQUEST) + return handler_suite + + def _source_handler_files_in_dir(self, root_dir, scan_dir, + allow_handlers_outside_root_dir, + handler_encoding): + """Source all the handler source files in the scan_dir directory. + + The resource path is determined relative to root_dir. + """ + + # We build a map from resource to handler code assuming that there's + # only one path from root_dir to scan_dir and it can be obtained by + # comparing realpath of them. + + # Here we cannot use abspath. See + # https://bugs.webkit.org/show_bug.cgi?id=31603 + + convert = _create_path_to_resource_converter(root_dir) + scan_realpath = os.path.realpath(scan_dir) + root_realpath = os.path.realpath(root_dir) + for path in _enumerate_handler_file_paths(scan_realpath): + if (not allow_handlers_outside_root_dir and + (not os.path.realpath(path).startswith(root_realpath))): + self._logger.debug( + 'Canonical path of %s is not under root directory' % path) + continue + try: + with io.open(path, encoding=handler_encoding) as handler_file: + handler_suite = _source_handler_file(handler_file.read()) + except DispatchException as e: + self._source_warnings.append('%s: %s' % (path, e)) + continue + resource = convert(path) + if resource is None: + self._logger.debug('Path to resource conversion on %s failed' % + path) + else: + self._handler_suite_map[convert(path)] = handler_suite + + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/extensions.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/extensions.py new file mode 100644 index 0000000000000..4b5b9e8fb27dd --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/extensions.py @@ -0,0 +1,474 @@ +# Copyright 2012, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import + +from pywebsocket3 import common, util +from pywebsocket3.http_header_util import quote_if_necessary + +# The list of available server side extension processor classes. +_available_processors = {} + + +class ExtensionProcessorInterface(object): + def __init__(self, request): + self._logger = util.get_class_logger(self) + + self._request = request + self._active = True + + def request(self): + return self._request + + def name(self): + return None + + def check_consistency_with_other_processors(self, processors): + pass + + def set_active(self, active): + self._active = active + + def is_active(self): + return self._active + + def _get_extension_response_internal(self): + return None + + def get_extension_response(self): + if not self._active: + self._logger.debug('Extension %s is deactivated', self.name()) + return None + + response = self._get_extension_response_internal() + if response is None: + self._active = False + return response + + def _setup_stream_options_internal(self, stream_options): + pass + + def setup_stream_options(self, stream_options): + if self._active: + self._setup_stream_options_internal(stream_options) + + +def _log_outgoing_compression_ratio(logger, original_bytes, filtered_bytes, + average_ratio): + # Print inf when ratio is not available. + ratio = float('inf') + if original_bytes != 0: + ratio = float(filtered_bytes) / original_bytes + + logger.debug('Outgoing compression ratio: %f (average: %f)' % + (ratio, average_ratio)) + + +def _log_incoming_compression_ratio(logger, received_bytes, filtered_bytes, + average_ratio): + # Print inf when ratio is not available. + ratio = float('inf') + if filtered_bytes != 0: + ratio = float(received_bytes) / filtered_bytes + + logger.debug('Incoming compression ratio: %f (average: %f)' % + (ratio, average_ratio)) + + +def _parse_window_bits(bits): + """Return parsed integer value iff the given string conforms to the + grammar of the window bits extension parameters. + """ + + if bits is None: + raise ValueError('Value is required') + + # For non integer values such as "10.0", ValueError will be raised. + int_bits = int(bits) + + # First condition is to drop leading zero case e.g. "08". + if bits != str(int_bits) or int_bits < 8 or int_bits > 15: + raise ValueError('Invalid value: %r' % bits) + + return int_bits + + +class _AverageRatioCalculator(object): + """Stores total bytes of original and result data, and calculates average + result / original ratio. + """ + def __init__(self): + self._total_original_bytes = 0 + self._total_result_bytes = 0 + + def add_original_bytes(self, value): + self._total_original_bytes += value + + def add_result_bytes(self, value): + self._total_result_bytes += value + + def get_average_ratio(self): + if self._total_original_bytes != 0: + return (float(self._total_result_bytes) / + self._total_original_bytes) + else: + return float('inf') + + +class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface): + """permessage-deflate extension processor. + + Specification: + http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-08 + """ + + _SERVER_MAX_WINDOW_BITS_PARAM = 'server_max_window_bits' + _SERVER_NO_CONTEXT_TAKEOVER_PARAM = 'server_no_context_takeover' + _CLIENT_MAX_WINDOW_BITS_PARAM = 'client_max_window_bits' + _CLIENT_NO_CONTEXT_TAKEOVER_PARAM = 'client_no_context_takeover' + + def __init__(self, request): + """Construct PerMessageDeflateExtensionProcessor.""" + + ExtensionProcessorInterface.__init__(self, request) + self._logger = util.get_class_logger(self) + + self._preferred_client_max_window_bits = None + self._client_no_context_takeover = False + + def name(self): + # This method returns "deflate" (not "permessage-deflate") for + # compatibility. + return 'deflate' + + def _get_extension_response_internal(self): + for name in self._request.get_parameter_names(): + if name not in [ + self._SERVER_MAX_WINDOW_BITS_PARAM, + self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, + self._CLIENT_MAX_WINDOW_BITS_PARAM + ]: + self._logger.debug('Unknown parameter: %r', name) + return None + + server_max_window_bits = None + if self._request.has_parameter(self._SERVER_MAX_WINDOW_BITS_PARAM): + server_max_window_bits = self._request.get_parameter_value( + self._SERVER_MAX_WINDOW_BITS_PARAM) + try: + server_max_window_bits = _parse_window_bits( + server_max_window_bits) + except ValueError as e: + self._logger.debug('Bad %s parameter: %r', + self._SERVER_MAX_WINDOW_BITS_PARAM, e) + return None + + server_no_context_takeover = self._request.has_parameter( + self._SERVER_NO_CONTEXT_TAKEOVER_PARAM) + if (server_no_context_takeover and self._request.get_parameter_value( + self._SERVER_NO_CONTEXT_TAKEOVER_PARAM) is not None): + self._logger.debug('%s parameter must not have a value: %r', + self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, + server_no_context_takeover) + return None + + # client_max_window_bits from a client indicates whether the client can + # accept client_max_window_bits from a server or not. + client_client_max_window_bits = self._request.has_parameter( + self._CLIENT_MAX_WINDOW_BITS_PARAM) + if (client_client_max_window_bits + and self._request.get_parameter_value( + self._CLIENT_MAX_WINDOW_BITS_PARAM) is not None): + self._logger.debug( + '%s parameter must not have a value in a ' + 'client\'s opening handshake: %r', + self._CLIENT_MAX_WINDOW_BITS_PARAM, + client_client_max_window_bits) + return None + + self._rfc1979_deflater = util._RFC1979Deflater( + server_max_window_bits, server_no_context_takeover) + + # Note that we prepare for incoming messages compressed with window + # bits upto 15 regardless of the client_max_window_bits value to be + # sent to the client. + self._rfc1979_inflater = util._RFC1979Inflater() + + self._framer = _PerMessageDeflateFramer(server_max_window_bits, + server_no_context_takeover) + self._framer.set_bfinal(False) + self._framer.set_compress_outgoing_enabled(True) + + response = common.ExtensionParameter(self._request.name()) + + if server_max_window_bits is not None: + response.add_parameter(self._SERVER_MAX_WINDOW_BITS_PARAM, + str(server_max_window_bits)) + + if server_no_context_takeover: + response.add_parameter(self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, + None) + + if self._preferred_client_max_window_bits is not None: + if not client_client_max_window_bits: + self._logger.debug( + 'Processor is configured to use %s but ' + 'the client cannot accept it', + self._CLIENT_MAX_WINDOW_BITS_PARAM) + return None + response.add_parameter(self._CLIENT_MAX_WINDOW_BITS_PARAM, + str(self._preferred_client_max_window_bits)) + + if self._client_no_context_takeover: + response.add_parameter(self._CLIENT_NO_CONTEXT_TAKEOVER_PARAM, + None) + + self._logger.debug('Enable %s extension (' + 'request: server_max_window_bits=%s; ' + 'server_no_context_takeover=%r, ' + 'response: client_max_window_bits=%s; ' + 'client_no_context_takeover=%r)' % + (self._request.name(), server_max_window_bits, + server_no_context_takeover, + self._preferred_client_max_window_bits, + self._client_no_context_takeover)) + + return response + + def _setup_stream_options_internal(self, stream_options): + self._framer.setup_stream_options(stream_options) + + def set_client_max_window_bits(self, value): + """If this option is specified, this class adds the + client_max_window_bits extension parameter to the handshake response, + but doesn't reduce the LZ77 sliding window size of its inflater. + I.e., you can use this for testing client implementation but cannot + reduce memory usage of this class. + + If this method has been called with True and an offer without the + client_max_window_bits extension parameter is received, + + - (When processing the permessage-deflate extension) this processor + declines the request. + - (When processing the permessage-compress extension) this processor + accepts the request. + """ + + self._preferred_client_max_window_bits = value + + def set_client_no_context_takeover(self, value): + """If this option is specified, this class adds the + client_no_context_takeover extension parameter to the handshake + response, but doesn't reset inflater for each message. I.e., you can + use this for testing client implementation but cannot reduce memory + usage of this class. + """ + + self._client_no_context_takeover = value + + def set_bfinal(self, value): + self._framer.set_bfinal(value) + + def enable_outgoing_compression(self): + self._framer.set_compress_outgoing_enabled(True) + + def disable_outgoing_compression(self): + self._framer.set_compress_outgoing_enabled(False) + + +class _PerMessageDeflateFramer(object): + """A framer for extensions with per-message DEFLATE feature.""" + def __init__(self, deflate_max_window_bits, deflate_no_context_takeover): + self._logger = util.get_class_logger(self) + + self._rfc1979_deflater = util._RFC1979Deflater( + deflate_max_window_bits, deflate_no_context_takeover) + + self._rfc1979_inflater = util._RFC1979Inflater() + + self._bfinal = False + + self._compress_outgoing_enabled = False + + # True if a message is fragmented and compression is ongoing. + self._compress_ongoing = False + + # Calculates + # (Total outgoing bytes supplied to this filter) / + # (Total bytes sent to the network after applying this filter) + self._outgoing_average_ratio_calculator = _AverageRatioCalculator() + + # Calculates + # (Total bytes received from the network) / + # (Total incoming bytes obtained after applying this filter) + self._incoming_average_ratio_calculator = _AverageRatioCalculator() + + def set_bfinal(self, value): + self._bfinal = value + + def set_compress_outgoing_enabled(self, value): + self._compress_outgoing_enabled = value + + def _process_incoming_message(self, message, decompress): + if not decompress: + return message + + received_payload_size = len(message) + self._incoming_average_ratio_calculator.add_result_bytes( + received_payload_size) + + message = self._rfc1979_inflater.filter(message) + + filtered_payload_size = len(message) + self._incoming_average_ratio_calculator.add_original_bytes( + filtered_payload_size) + + _log_incoming_compression_ratio( + self._logger, received_payload_size, filtered_payload_size, + self._incoming_average_ratio_calculator.get_average_ratio()) + + return message + + def _process_outgoing_message(self, message, end, binary): + if not binary: + message = message.encode('utf-8') + + if not self._compress_outgoing_enabled: + return message + + original_payload_size = len(message) + self._outgoing_average_ratio_calculator.add_original_bytes( + original_payload_size) + + message = self._rfc1979_deflater.filter(message, + end=end, + bfinal=self._bfinal) + + filtered_payload_size = len(message) + self._outgoing_average_ratio_calculator.add_result_bytes( + filtered_payload_size) + + _log_outgoing_compression_ratio( + self._logger, original_payload_size, filtered_payload_size, + self._outgoing_average_ratio_calculator.get_average_ratio()) + + if not self._compress_ongoing: + self._outgoing_frame_filter.set_compression_bit() + self._compress_ongoing = not end + return message + + def _process_incoming_frame(self, frame): + if frame.rsv1 == 1 and not common.is_control_opcode(frame.opcode): + self._incoming_message_filter.decompress_next_message() + frame.rsv1 = 0 + + def _process_outgoing_frame(self, frame, compression_bit): + if (not compression_bit or common.is_control_opcode(frame.opcode)): + return + + frame.rsv1 = 1 + + def setup_stream_options(self, stream_options): + """Creates filters and sets them to the StreamOptions.""" + class _OutgoingMessageFilter(object): + def __init__(self, parent): + self._parent = parent + + def filter(self, message, end=True, binary=False): + return self._parent._process_outgoing_message( + message, end, binary) + + class _IncomingMessageFilter(object): + def __init__(self, parent): + self._parent = parent + self._decompress_next_message = False + + def decompress_next_message(self): + self._decompress_next_message = True + + def filter(self, message): + message = self._parent._process_incoming_message( + message, self._decompress_next_message) + self._decompress_next_message = False + return message + + self._outgoing_message_filter = _OutgoingMessageFilter(self) + self._incoming_message_filter = _IncomingMessageFilter(self) + stream_options.outgoing_message_filters.append( + self._outgoing_message_filter) + stream_options.incoming_message_filters.append( + self._incoming_message_filter) + + class _OutgoingFrameFilter(object): + def __init__(self, parent): + self._parent = parent + self._set_compression_bit = False + + def set_compression_bit(self): + self._set_compression_bit = True + + def filter(self, frame): + self._parent._process_outgoing_frame(frame, + self._set_compression_bit) + self._set_compression_bit = False + + class _IncomingFrameFilter(object): + def __init__(self, parent): + self._parent = parent + + def filter(self, frame): + self._parent._process_incoming_frame(frame) + + self._outgoing_frame_filter = _OutgoingFrameFilter(self) + self._incoming_frame_filter = _IncomingFrameFilter(self) + stream_options.outgoing_frame_filters.append( + self._outgoing_frame_filter) + stream_options.incoming_frame_filters.append( + self._incoming_frame_filter) + + stream_options.encode_text_message_to_utf8 = False + + +_available_processors[common.PERMESSAGE_DEFLATE_EXTENSION] = ( + PerMessageDeflateExtensionProcessor) + + +def get_extension_processor(extension_request): + """Given an ExtensionParameter representing an extension offer received + from a client, configures and returns an instance of the corresponding + extension processor class. + """ + + processor_class = _available_processors.get(extension_request.name()) + if processor_class is None: + return None + return processor_class(extension_request) + + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/handshake/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/handshake/__init__.py new file mode 100644 index 0000000000000..275e447552e81 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/handshake/__init__.py @@ -0,0 +1,105 @@ +# Copyright 2011, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""WebSocket opening handshake processor. This class try to apply available +opening handshake processors for each protocol version until a connection is +successfully established. +""" + +from __future__ import absolute_import + +import logging + +from pywebsocket3 import common +from pywebsocket3.handshake import hybi +# Export AbortedByUserException, HandshakeException, and VersionException +# symbol from this module. +from pywebsocket3.handshake.base import ( + AbortedByUserException, + HandshakeException, + VersionException +) + + +_LOGGER = logging.getLogger(__name__) + + +def do_handshake(request, dispatcher): + """Performs WebSocket handshake. + + Args: + request: mod_python request. + dispatcher: Dispatcher (dispatch.Dispatcher). + + Handshaker will add attributes such as ws_resource in performing + handshake. + """ + + _LOGGER.debug('Client\'s opening handshake resource: %r', request.uri) + # To print mimetools.Message as escaped one-line string, we converts + # headers_in to dict object. Without conversion, if we use %r, it just + # prints the type and address, and if we use %s, it prints the original + # header string as multiple lines. + # + # Both mimetools.Message and MpTable_Type of mod_python can be + # converted to dict. + # + # mimetools.Message.__str__ returns the original header string. + # dict(mimetools.Message object) returns the map from header names to + # header values. While MpTable_Type doesn't have such __str__ but just + # __repr__ which formats itself as well as dictionary object. + _LOGGER.debug('Client\'s opening handshake headers: %r', + dict(request.headers_in)) + + handshakers = [] + handshakers.append(('RFC 6455', hybi.Handshaker(request, dispatcher))) + + for name, handshaker in handshakers: + _LOGGER.debug('Trying protocol version %s', name) + try: + handshaker.do_handshake() + _LOGGER.info('Established (%s protocol)', name) + return + except HandshakeException as e: + _LOGGER.debug( + 'Failed to complete opening handshake as %s protocol: %r', + name, e) + if e.status: + raise e + except AbortedByUserException as e: + raise + except VersionException as e: + raise + + # TODO(toyoshim): Add a test to cover the case all handshakers fail. + raise HandshakeException( + 'Failed to complete opening handshake for all available protocols', + status=common.HTTP_STATUS_BAD_REQUEST) + + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/handshake/base.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/handshake/base.py new file mode 100644 index 0000000000000..561f7b650a2e5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/handshake/base.py @@ -0,0 +1,393 @@ +# Copyright 2012, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""Common functions and exceptions used by WebSocket opening handshake +processors. +""" + +from __future__ import absolute_import + +from pywebsocket3 import common, http_header_util, util +from pywebsocket3.extensions import get_extension_processor +from pywebsocket3.stream import Stream, StreamOptions + +from six.moves import map, range + + +# Defining aliases for values used frequently. +_VERSION_LATEST = common.VERSION_HYBI_LATEST +_VERSION_LATEST_STRING = str(_VERSION_LATEST) +_SUPPORTED_VERSIONS = [ + _VERSION_LATEST, +] + + +class AbortedByUserException(Exception): + """Exception for aborting a connection intentionally. + + If this exception is raised in do_extra_handshake handler, the connection + will be abandoned. No other WebSocket or HTTP(S) handler will be invoked. + + If this exception is raised in transfer_data_handler, the connection will + be closed without closing handshake. No other WebSocket or HTTP(S) handler + will be invoked. + """ + + pass + + +class HandshakeException(Exception): + """This exception will be raised when an error occurred while processing + WebSocket initial handshake. + """ + def __init__(self, name, status=None): + super(HandshakeException, self).__init__(name) + self.status = status + + +class VersionException(Exception): + """This exception will be raised when a version of client request does not + match with version the server supports. + """ + def __init__(self, name, supported_versions=''): + """Construct an instance. + + Args: + supported_version: a str object to show supported hybi versions. + (e.g. '13') + """ + super(VersionException, self).__init__(name) + self.supported_versions = supported_versions + + +def get_default_port(is_secure): + if is_secure: + return common.DEFAULT_WEB_SOCKET_SECURE_PORT + else: + return common.DEFAULT_WEB_SOCKET_PORT + + +def validate_subprotocol(subprotocol): + """Validate a value in the Sec-WebSocket-Protocol field. + + See the Section 4.1., 4.2.2., and 4.3. of RFC 6455. + """ + + if not subprotocol: + raise HandshakeException('Invalid subprotocol name: empty') + + # Parameter should be encoded HTTP token. + state = http_header_util.ParsingState(subprotocol) + token = http_header_util.consume_token(state) + rest = http_header_util.peek(state) + # If |rest| is not None, |subprotocol| is not one token or invalid. If + # |rest| is None, |token| must not be None because |subprotocol| is + # concatenation of |token| and |rest| and is not None. + if rest is not None: + raise HandshakeException('Invalid non-token string in subprotocol ' + 'name: %r' % rest) + + +def parse_host_header(request): + fields = request.headers_in[common.HOST_HEADER].split(':', 1) + if len(fields) == 1: + return fields[0], get_default_port(request.is_https()) + try: + return fields[0], int(fields[1]) + except ValueError as e: + raise HandshakeException('Invalid port number format: %r' % e) + + +def get_mandatory_header(request, key): + value = request.headers_in.get(key) + if value is None: + raise HandshakeException('Header %s is not defined' % key) + return value + + +def validate_mandatory_header(request, key, expected_value, fail_status=None): + value = get_mandatory_header(request, key) + + if value.lower() != expected_value.lower(): + raise HandshakeException( + 'Expected %r for header %s but found %r (case-insensitive)' % + (expected_value, key, value), + status=fail_status) + + +def parse_token_list(data): + """Parses a header value which follows 1#token and returns parsed elements + as a list of strings. + + Leading LWSes must be trimmed. + """ + + state = http_header_util.ParsingState(data) + + token_list = [] + + while True: + token = http_header_util.consume_token(state) + if token is not None: + token_list.append(token) + + http_header_util.consume_lwses(state) + + if http_header_util.peek(state) is None: + break + + if not http_header_util.consume_string(state, ','): + raise HandshakeException('Expected a comma but found %r' % + http_header_util.peek(state)) + + http_header_util.consume_lwses(state) + + if len(token_list) == 0: + raise HandshakeException('No valid token found') + + return token_list + + +class HandshakerBase(object): + def __init__(self, request, dispatcher): + self._logger = util.get_class_logger(self) + self._request = request + self._dispatcher = dispatcher + + """ subclasses must implement the five following methods """ + + def _protocol_rfc(self): + """ Return the name of the RFC that the handshake class is implementing. + """ + + raise AssertionError("subclasses should implement this method") + + def _transform_header(self, header): + """ + :param header: header name + + transform the header name if needed. For example, HTTP/2 subclass will + return the name of the header in lower case. + """ + + raise AssertionError("subclasses should implement this method") + + def _validate_request(self): + """ validate that all the mandatory fields are set """ + + raise AssertionError("subclasses should implement this method") + + def _set_accept(self): + """ Computes accept value based on Sec-WebSocket-Accept if needed. """ + + raise AssertionError("subclasses should implement this method") + + def _send_handshake(self): + """ Prepare and send the response after it has been parsed and processed. + """ + + raise AssertionError("subclasses should implement this method") + + def do_handshake(self): + self._request.ws_close_code = None + self._request.ws_close_reason = None + + # Parsing. + self._validate_request() + self._request.ws_resource = self._request.uri + self._request.ws_version = self._check_version() + + try: + self._get_origin() + self._set_protocol() + self._parse_extensions() + + self._set_accept() + + self._logger.debug('Protocol version is ' + self._protocol_rfc()) + + # Setup extension processors. + self._request.ws_extension_processors = self._get_extension_processors_requested( + ) + + # List of extra headers. The extra handshake handler may add header + # data as name/value pairs to this list and pywebsocket appends + # them to the WebSocket handshake. + self._request.extra_headers = [] + + # Extra handshake handler may modify/remove processors. + self._dispatcher.do_extra_handshake(self._request) + + stream_options = StreamOptions() + self._process_extensions(stream_options) + + self._request.ws_stream = Stream(self._request, stream_options) + + if self._request.ws_requested_protocols is not None: + if self._request.ws_protocol is None: + raise HandshakeException( + 'do_extra_handshake must choose one subprotocol from ' + 'ws_requested_protocols and set it to ws_protocol') + validate_subprotocol(self._request.ws_protocol) + + self._logger.debug('Subprotocol accepted: %r', + self._request.ws_protocol) + else: + if self._request.ws_protocol is not None: + raise HandshakeException( + 'ws_protocol must be None when the client didn\'t ' + 'request any subprotocol') + + self._send_handshake() + except HandshakeException as e: + if not e.status: + # Fallback to 400 bad request by default. + e.status = common.HTTP_STATUS_BAD_REQUEST + raise e + + def _check_version(self): + sec_websocket_version_header = self._transform_header( + common.SEC_WEBSOCKET_VERSION_HEADER) + version = get_mandatory_header(self._request, + sec_websocket_version_header) + if version == _VERSION_LATEST_STRING: + return _VERSION_LATEST + + if version.find(',') >= 0: + raise HandshakeException( + 'Multiple versions (%r) are not allowed for header %s' % + (version, sec_websocket_version_header), + status=common.HTTP_STATUS_BAD_REQUEST) + raise VersionException('Unsupported version %r for header %s' % + (version, sec_websocket_version_header), + supported_versions=', '.join( + map(str, _SUPPORTED_VERSIONS))) + + def _get_origin(self): + origin_header = self._transform_header(common.ORIGIN_HEADER) + origin = self._request.headers_in.get(origin_header) + if origin is None: + self._logger.debug('Client request does not have origin header') + self._request.ws_origin = origin + + def _set_protocol(self): + self._request.ws_protocol = None + + sec_websocket_protocol_header = self._transform_header( + common.SEC_WEBSOCKET_PROTOCOL_HEADER) + protocol_header = self._request.headers_in.get( + sec_websocket_protocol_header) + + if protocol_header is None: + self._request.ws_requested_protocols = None + return + + self._request.ws_requested_protocols = parse_token_list( + protocol_header) + self._logger.debug('Subprotocols requested: %r', + self._request.ws_requested_protocols) + + def _parse_extensions(self): + sec_websocket_extensions_header = self._transform_header( + common.SEC_WEBSOCKET_EXTENSIONS_HEADER) + extensions_header = self._request.headers_in.get( + sec_websocket_extensions_header) + if not extensions_header: + self._request.ws_requested_extensions = None + return + + try: + self._request.ws_requested_extensions = common.parse_extensions( + extensions_header) + except common.ExtensionParsingException as e: + raise HandshakeException( + 'Failed to parse sec-websocket-extensions header: %r' % e) + + self._logger.debug( + 'Extensions requested: %r', + list( + map(common.ExtensionParameter.name, + self._request.ws_requested_extensions))) + + def _get_extension_processors_requested(self): + processors = [] + if self._request.ws_requested_extensions is not None: + for extension_request in self._request.ws_requested_extensions: + processor = get_extension_processor(extension_request) + # Unknown extension requests are just ignored. + if processor is not None: + processors.append(processor) + return processors + + def _process_extensions(self, stream_options): + processors = [ + processor for processor in self._request.ws_extension_processors + if processor is not None + ] + + # Ask each processor if there are extensions on the request which + # cannot co-exist. When processor decided other processors cannot + # co-exist with it, the processor marks them (or itself) as + # "inactive". The first extension processor has the right to + # make the final call. + for processor in reversed(processors): + if processor.is_active(): + processor.check_consistency_with_other_processors(processors) + processors = [ + processor for processor in processors if processor.is_active() + ] + + accepted_extensions = [] + + for index, processor in enumerate(processors): + if not processor.is_active(): + continue + + extension_response = processor.get_extension_response() + if extension_response is None: + # Rejected. + continue + + accepted_extensions.append(extension_response) + + processor.setup_stream_options(stream_options) + + # Inactivate all of the following compression extensions. + for j in range(index + 1, len(processors)): + processors[j].set_active(False) + + if len(accepted_extensions) > 0: + self._request.ws_extensions = accepted_extensions + self._logger.debug( + 'Extensions accepted: %r', + list(map(common.ExtensionParameter.name, accepted_extensions))) + else: + self._request.ws_extensions = None + + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/handshake/hybi.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/handshake/hybi.py new file mode 100644 index 0000000000000..2e26532c3f7eb --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/handshake/hybi.py @@ -0,0 +1,226 @@ +# Copyright 2012, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""This file provides the opening handshake processor for the WebSocket +protocol (RFC 6455). + +Specification: +http://tools.ietf.org/html/rfc6455 +""" + +from __future__ import absolute_import + +import base64 +import re +from hashlib import sha1 + +from pywebsocket3 import common, util +from pywebsocket3.handshake.base import ( + get_mandatory_header, + HandshakeException, + parse_token_list, + validate_mandatory_header, + HandshakerBase +) + + +# Used to validate the value in the Sec-WebSocket-Key header strictly. RFC 4648 +# disallows non-zero padding, so the character right before == must be any of +# A, Q, g and w. +_SEC_WEBSOCKET_KEY_REGEX = re.compile('^[+/0-9A-Za-z]{21}[AQgw]==$') + + +def check_request_line(request): + # 5.1 1. The three character UTF-8 string "GET". + # 5.1 2. A UTF-8-encoded U+0020 SPACE character (0x20 byte). + if request.method != u'GET': + raise HandshakeException('Method is not GET: %r' % request.method) + + if request.protocol != u'HTTP/1.1': + raise HandshakeException('Version is not HTTP/1.1: %r' % + request.protocol) + + +def compute_accept(key): + """Computes value for the Sec-WebSocket-Accept header from value of the + Sec-WebSocket-Key header. + """ + + accept_binary = sha1(key + common.WEBSOCKET_ACCEPT_UUID).digest() + accept = base64.b64encode(accept_binary) + + return accept + + +def compute_accept_from_unicode(unicode_key): + """A wrapper function for compute_accept which takes a unicode string as an + argument, and encodes it to byte string. It then passes it on to + compute_accept. + """ + + key = unicode_key.encode('UTF-8') + return compute_accept(key) + + +def format_header(name, value): + return u'%s: %s\r\n' % (name, value) + + +class Handshaker(HandshakerBase): + """Opening handshake processor for the WebSocket protocol (RFC 6455).""" + def __init__(self, request, dispatcher): + """Construct an instance. + + Args: + request: mod_python request. + dispatcher: Dispatcher (dispatch.Dispatcher). + + Handshaker will add attributes such as ws_resource during handshake. + """ + super(Handshaker, self).__init__(request, dispatcher) + + def _transform_header(self, header): + return header + + def _protocol_rfc(self): + return 'RFC 6455' + + def _validate_connection_header(self): + connection = get_mandatory_header(self._request, + common.CONNECTION_HEADER) + + try: + connection_tokens = parse_token_list(connection) + except HandshakeException as e: + raise HandshakeException('Failed to parse %s: %s' % + (common.CONNECTION_HEADER, e)) + + connection_is_valid = False + for token in connection_tokens: + if token.lower() == common.UPGRADE_CONNECTION_TYPE.lower(): + connection_is_valid = True + break + if not connection_is_valid: + raise HandshakeException( + '%s header doesn\'t contain "%s"' % + (common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE)) + + def _validate_request(self): + check_request_line(self._request) + validate_mandatory_header(self._request, common.UPGRADE_HEADER, + common.WEBSOCKET_UPGRADE_TYPE) + self._validate_connection_header() + unused_host = get_mandatory_header(self._request, common.HOST_HEADER) + + def _set_accept(self): + # Key validation, response generation. + key = self._get_key() + accept = compute_accept(key) + self._logger.debug('%s: %r (%s)', common.SEC_WEBSOCKET_ACCEPT_HEADER, + accept, util.hexify(base64.b64decode(accept))) + self._request._accept = accept + + def _validate_key(self, key): + if key.find(',') >= 0: + raise HandshakeException('Request has multiple %s header lines or ' + 'contains illegal character \',\': %r' % + (common.SEC_WEBSOCKET_KEY_HEADER, key)) + + # Validate + key_is_valid = False + try: + # Validate key by quick regex match before parsing by base64 + # module. Because base64 module skips invalid characters, we have + # to do this in advance to make this server strictly reject illegal + # keys. + if _SEC_WEBSOCKET_KEY_REGEX.match(key): + decoded_key = base64.b64decode(key) + if len(decoded_key) == 16: + key_is_valid = True + except TypeError as e: + pass + + if not key_is_valid: + raise HandshakeException('Illegal value for header %s: %r' % + (common.SEC_WEBSOCKET_KEY_HEADER, key)) + + return decoded_key + + def _get_key(self): + key = get_mandatory_header(self._request, + common.SEC_WEBSOCKET_KEY_HEADER) + + decoded_key = self._validate_key(key) + + self._logger.debug('%s: %r (%s)', common.SEC_WEBSOCKET_KEY_HEADER, key, + util.hexify(decoded_key)) + + return key.encode('UTF-8') + + def _create_handshake_response(self, accept): + response = [] + + response.append(u'HTTP/1.1 101 Switching Protocols\r\n') + + # WebSocket headers + response.append( + format_header(common.UPGRADE_HEADER, + common.WEBSOCKET_UPGRADE_TYPE)) + response.append( + format_header(common.CONNECTION_HEADER, + common.UPGRADE_CONNECTION_TYPE)) + response.append( + format_header(common.SEC_WEBSOCKET_ACCEPT_HEADER, + accept.decode('UTF-8'))) + if self._request.ws_protocol is not None: + response.append( + format_header(common.SEC_WEBSOCKET_PROTOCOL_HEADER, + self._request.ws_protocol)) + if (self._request.ws_extensions is not None + and len(self._request.ws_extensions) != 0): + response.append( + format_header( + common.SEC_WEBSOCKET_EXTENSIONS_HEADER, + common.format_extensions(self._request.ws_extensions))) + + # Headers not specific for WebSocket + for name, value in self._request.extra_headers: + response.append(format_header(name, value)) + + response.append(u'\r\n') + + return u''.join(response) + + def _send_handshake(self): + raw_response = self._create_handshake_response(self._request._accept) + self._request.connection.write(raw_response.encode('UTF-8')) + self._logger.debug('Sent server\'s opening handshake: %r', + raw_response) + + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/handshake/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/handshake/w3c-import.log new file mode 100644 index 0000000000000..10d704cb0bbbd --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/handshake/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/handshake/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/handshake/base.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/handshake/hybi.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/http_header_util.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/http_header_util.py new file mode 100644 index 0000000000000..63e698bc16e0b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/http_header_util.py @@ -0,0 +1,256 @@ +# Copyright 2011, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""Utilities for parsing and formatting headers that follow the grammar defined +in HTTP RFC http://www.ietf.org/rfc/rfc2616.txt. +""" + +from __future__ import absolute_import + +import six.moves.urllib.parse + + +_SEPARATORS = '()<>@,;:\\"/[]?={} \t' + + +def _is_char(c): + """Returns true iff c is in CHAR as specified in HTTP RFC.""" + + return ord(c) <= 127 + + +def _is_ctl(c): + """Returns true iff c is in CTL as specified in HTTP RFC.""" + + return ord(c) <= 31 or ord(c) == 127 + + +class ParsingState(object): + def __init__(self, data): + self.data = data + self.head = 0 + + +def peek(state, pos=0): + """Peeks the character at pos from the head of data.""" + + if state.head + pos >= len(state.data): + return None + + return state.data[state.head + pos] + + +def consume(state, amount=1): + """Consumes specified amount of bytes from the head and returns the + consumed bytes. If there's not enough bytes to consume, returns None. + """ + + if state.head + amount > len(state.data): + return None + + result = state.data[state.head:state.head + amount] + state.head = state.head + amount + return result + + +def consume_string(state, expected): + """Given a parsing state and a expected string, consumes the string from + the head. Returns True if consumed successfully. Otherwise, returns + False. + """ + + pos = 0 + + for c in expected: + if c != peek(state, pos): + return False + pos += 1 + + consume(state, pos) + return True + + +def consume_lws(state): + """Consumes a LWS from the head. Returns True if any LWS is consumed. + Otherwise, returns False. + + LWS = [CRLF] 1*( SP | HT ) + """ + + original_head = state.head + + consume_string(state, '\r\n') + + pos = 0 + + while True: + c = peek(state, pos) + if c == ' ' or c == '\t': + pos += 1 + else: + if pos == 0: + state.head = original_head + return False + else: + consume(state, pos) + return True + + +def consume_lwses(state): + r"""Consumes \*LWS from the head.""" + + while consume_lws(state): + pass + + +def consume_token(state): + """Consumes a token from the head. Returns the token or None if no token + was found. + """ + + pos = 0 + + while True: + c = peek(state, pos) + if c is None or c in _SEPARATORS or _is_ctl(c) or not _is_char(c): + if pos == 0: + return None + + return consume(state, pos) + else: + pos += 1 + + +def consume_token_or_quoted_string(state): + """Consumes a token or a quoted-string, and returns the token or unquoted + string. If no token or quoted-string was found, returns None. + """ + + original_head = state.head + + if not consume_string(state, '"'): + return consume_token(state) + + result = [] + + expect_quoted_pair = False + + while True: + if not expect_quoted_pair and consume_lws(state): + result.append(' ') + continue + + c = consume(state) + if c is None: + # quoted-string is not enclosed with double quotation + state.head = original_head + return None + elif expect_quoted_pair: + expect_quoted_pair = False + if _is_char(c): + result.append(c) + else: + # Non CHAR character found in quoted-pair + state.head = original_head + return None + elif c == '\\': + expect_quoted_pair = True + elif c == '"': + return ''.join(result) + elif _is_ctl(c): + # Invalid character %r found in qdtext + state.head = original_head + return None + else: + result.append(c) + + +def quote_if_necessary(s): + """Quotes arbitrary string into quoted-string.""" + + quote = False + if s == '': + return '""' + + result = [] + for c in s: + if c == '"' or c in _SEPARATORS or _is_ctl(c) or not _is_char(c): + quote = True + + if c == '"' or _is_ctl(c): + result.append('\\' + c) + else: + result.append(c) + + if quote: + return '"' + ''.join(result) + '"' + else: + return ''.join(result) + + +def parse_uri(uri): + """Parse absolute URI then return host, port and resource.""" + + parsed = six.moves.urllib.parse.urlsplit(uri) + if parsed.scheme != 'wss' and parsed.scheme != 'ws': + # |uri| must be a relative URI. + # TODO(toyoshim): Should validate |uri|. + return None, None, uri + + if parsed.hostname is None: + return None, None, None + + port = None + try: + port = parsed.port + except ValueError: + # The port property cause ValueError on invalid null port descriptions + # like 'ws://host:INVALID_PORT/path', where the assigned port is not + # *DIGIT. For python 3.6 and later, ValueError also raises when + # assigning invalid port numbers such as 'ws://host:-1/path'. Earlier + # versions simply return None and ignore invalid port attributes. + return None, None, None + + if port is None: + if parsed.scheme == 'ws': + port = 80 + else: + port = 443 + + path = parsed.path + if not path: + path += '/' + if parsed.query: + path += '?' + parsed.query + if parsed.fragment: + path += '#' + parsed.fragment + + return parsed.hostname, port, path + + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/memorizingfile.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/memorizingfile.py new file mode 100644 index 0000000000000..4ee132fae610f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/memorizingfile.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +# +# Copyright 2011, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""Memorizing file. + +A memorizing file wraps a file and memorizes lines read by readline. +""" + +from __future__ import absolute_import + +import sys + + +class MemorizingFile(object): + """MemorizingFile wraps a file and memorizes lines read by readline. + + Note that data read by other methods are not memorized. This behavior + is good enough for memorizing lines SimpleHTTPServer reads before + the control reaches WebSocketRequestHandler. + """ + def __init__(self, file_, max_memorized_lines=sys.maxsize): + """Construct an instance. + + Args: + file_: the file object to wrap. + max_memorized_lines: the maximum number of lines to memorize. + Only the first max_memorized_lines are memorized. + Default: sys.maxint. + """ + self._file = file_ + self._memorized_lines = [] + self._max_memorized_lines = max_memorized_lines + self._buffered = False + self._buffered_line = None + + def __getattribute__(self, name): + """Return a file attribute. + + Returns the value overridden by this class for some attributes, + and forwards the call to _file for the other attributes. + """ + if name in ('_file', '_memorized_lines', '_max_memorized_lines', + '_buffered', '_buffered_line', 'readline', + 'get_memorized_lines'): + return object.__getattribute__(self, name) + return self._file.__getattribute__(name) + + def readline(self, size=-1): + """Override file.readline and memorize the line read. + + Note that even if size is specified and smaller than actual size, + the whole line will be read out from underlying file object by + subsequent readline calls. + """ + if self._buffered: + line = self._buffered_line + self._buffered = False + else: + line = self._file.readline() + if line and len(self._memorized_lines) < self._max_memorized_lines: + self._memorized_lines.append(line) + if size >= 0 and size < len(line): + self._buffered = True + self._buffered_line = line[size:] + return line[:size] + return line + + def get_memorized_lines(self): + """Get lines memorized so far.""" + return self._memorized_lines + + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/msgutil.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/msgutil.py new file mode 100644 index 0000000000000..dd6a6fc410014 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/msgutil.py @@ -0,0 +1,218 @@ +# Copyright 2011, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""Message related utilities. + +Note: request.connection.write/read are used in this module, even though +mod_python document says that they should be used only in connection +handlers. Unfortunately, we have no other options. For example, +request.write/read are not suitable because they don't allow direct raw +bytes writing/reading. +""" + +from __future__ import absolute_import + +import threading + +import six.moves.queue + +# Export Exception symbols from msgutil for backward compatibility +from pywebsocket3._stream_exceptions import ( + ConnectionTerminatedException, + InvalidFrameException, + BadOperationException, + UnsupportedFrameException +) + + +# An API for handler to send/receive WebSocket messages. +def close_connection(request): + """Close connection. + + Args: + request: mod_python request. + """ + request.ws_stream.close_connection() + + +def send_message(request, payload_data, end=True, binary=False): + """Send a message (or part of a message). + + Args: + request: mod_python request. + payload_data: unicode text or str binary to send. + end: True to terminate a message. + False to send payload_data as part of a message that is to be + terminated by next or later send_message call with end=True. + binary: send payload_data as binary frame(s). + Raises: + BadOperationException: when server already terminated. + """ + request.ws_stream.send_message(payload_data, end, binary) + + +def receive_message(request): + """Receive a WebSocket frame and return its payload as a text in + unicode or a binary in str. + + Args: + request: mod_python request. + Raises: + InvalidFrameException: when client send invalid frame. + UnsupportedFrameException: when client send unsupported frame e.g. some + of reserved bit is set but no extension can + recognize it. + InvalidUTF8Exception: when client send a text frame containing any + invalid UTF-8 string. + ConnectionTerminatedException: when the connection is closed + unexpectedly. + BadOperationException: when client already terminated. + """ + return request.ws_stream.receive_message() + + +def send_ping(request, body): + request.ws_stream.send_ping(body) + + +class MessageReceiver(threading.Thread): + """This class receives messages from the client. + + This class provides three ways to receive messages: blocking, + non-blocking, and via callback. Callback has the highest precedence. + + Note: This class should not be used with the standalone server for wss + because pyOpenSSL used by the server raises a fatal error if the socket + is accessed from multiple threads. + """ + def __init__(self, request, onmessage=None): + """Construct an instance. + + Args: + request: mod_python request. + onmessage: a function to be called when a message is received. + May be None. If not None, the function is called on + another thread. In that case, MessageReceiver.receive + and MessageReceiver.receive_nowait are useless + because they will never return any messages. + """ + + threading.Thread.__init__(self) + self._request = request + self._queue = six.moves.queue.Queue() + self._onmessage = onmessage + self._stop_requested = False + self.setDaemon(True) + self.start() + + def run(self): + try: + while not self._stop_requested: + message = receive_message(self._request) + if self._onmessage: + self._onmessage(message) + else: + self._queue.put(message) + finally: + close_connection(self._request) + + def receive(self): + """ Receive a message from the channel, blocking. + + Returns: + message as a unicode string. + """ + return self._queue.get() + + def receive_nowait(self): + """ Receive a message from the channel, non-blocking. + + Returns: + message as a unicode string if available. None otherwise. + """ + try: + message = self._queue.get_nowait() + except six.moves.queue.Empty: + message = None + return message + + def stop(self): + """Request to stop this instance. + + The instance will be stopped after receiving the next message. + This method may not be very useful, but there is no clean way + in Python to forcefully stop a running thread. + """ + self._stop_requested = True + + +class MessageSender(threading.Thread): + """This class sends messages to the client. + + This class provides both synchronous and asynchronous ways to send + messages. + + Note: This class should not be used with the standalone server for wss + because pyOpenSSL used by the server raises a fatal error if the socket + is accessed from multiple threads. + """ + def __init__(self, request): + """Construct an instance. + + Args: + request: mod_python request. + """ + threading.Thread.__init__(self) + self._request = request + self._queue = six.moves.queue.Queue() + self.setDaemon(True) + self.start() + + def run(self): + while True: + message, condition = self._queue.get() + condition.acquire() + send_message(self._request, message) + condition.notify() + condition.release() + + def send(self, message): + """Send a message, blocking.""" + + condition = threading.Condition() + condition.acquire() + self._queue.put((message, condition)) + condition.wait() + + def send_nowait(self, message): + """Send a message, non-blocking.""" + + self._queue.put((message, threading.Condition())) + + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/request_handler.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/request_handler.py new file mode 100644 index 0000000000000..e02e2ccf28f6e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/request_handler.py @@ -0,0 +1,322 @@ +# Copyright 2020, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""Request Handler and Request/Connection classes for standalone server. +""" + +import os + +from six.moves import CGIHTTPServer +from six.moves import http_client + +from pywebsocket3 import ( + common, + dispatch, + handshake, + http_header_util, + memorizingfile, + util +) + +# 1024 is practically large enough to contain WebSocket handshake lines. +_MAX_MEMORIZED_LINES = 1024 + + +class _StandaloneConnection(object): + """Mimic mod_python mp_conn.""" + def __init__(self, request_handler): + """Construct an instance. + + Args: + request_handler: A WebSocketRequestHandler instance. + """ + + self._request_handler = request_handler + + def get_local_addr(self): + """Getter to mimic mp_conn.local_addr.""" + + return (self._request_handler.server.server_name, + self._request_handler.server.server_port) + + local_addr = property(get_local_addr) + + def get_remote_addr(self): + """Getter to mimic mp_conn.remote_addr. + + Setting the property in __init__ won't work because the request + handler is not initialized yet there.""" + + return self._request_handler.client_address + + remote_addr = property(get_remote_addr) + + def write(self, data): + """Mimic mp_conn.write().""" + + return self._request_handler.wfile.write(data) + + def read(self, length): + """Mimic mp_conn.read().""" + + return self._request_handler.rfile.read(length) + + def get_memorized_lines(self): + """Get memorized lines.""" + + return self._request_handler.rfile.get_memorized_lines() + + +class _StandaloneRequest(object): + """Mimic mod_python request.""" + def __init__(self, request_handler, use_tls): + """Construct an instance. + + Args: + request_handler: A WebSocketRequestHandler instance. + """ + + self._logger = util.get_class_logger(self) + + self._request_handler = request_handler + self.connection = _StandaloneConnection(request_handler) + self._use_tls = use_tls + self.headers_in = request_handler.headers + + def get_uri(self): + """Getter to mimic request.uri. + + This method returns the raw data at the Request-URI part of the + Request-Line, while the uri method on the request object of mod_python + returns the path portion after parsing the raw data. This behavior is + kept for compatibility. + """ + + return self._request_handler.path + + uri = property(get_uri) + + def get_unparsed_uri(self): + """Getter to mimic request.unparsed_uri.""" + + return self._request_handler.path + + unparsed_uri = property(get_unparsed_uri) + + def get_method(self): + """Getter to mimic request.method.""" + + return self._request_handler.command + + method = property(get_method) + + def get_protocol(self): + """Getter to mimic request.protocol.""" + + return self._request_handler.request_version + + protocol = property(get_protocol) + + def is_https(self): + """Mimic request.is_https().""" + + return self._use_tls + + +class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler): + """CGIHTTPRequestHandler specialized for WebSocket.""" + + # Use httplib.HTTPMessage instead of mimetools.Message. + MessageClass = http_client.HTTPMessage + + def setup(self): + """Override SocketServer.StreamRequestHandler.setup to wrap rfile + with MemorizingFile. + + This method will be called by BaseRequestHandler's constructor + before calling BaseHTTPRequestHandler.handle. + BaseHTTPRequestHandler.handle will call + BaseHTTPRequestHandler.handle_one_request and it will call + WebSocketRequestHandler.parse_request. + """ + + # Call superclass's setup to prepare rfile, wfile, etc. See setup + # definition on the root class SocketServer.StreamRequestHandler to + # understand what this does. + CGIHTTPServer.CGIHTTPRequestHandler.setup(self) + + self.rfile = memorizingfile.MemorizingFile( + self.rfile, max_memorized_lines=_MAX_MEMORIZED_LINES) + + def __init__(self, request, client_address, server): + self._logger = util.get_class_logger(self) + + self._options = server.websocket_server_options + + # Overrides CGIHTTPServerRequestHandler.cgi_directories. + self.cgi_directories = self._options.cgi_directories + # Replace CGIHTTPRequestHandler.is_executable method. + if self._options.is_executable_method is not None: + self.is_executable = self._options.is_executable_method + + # This actually calls BaseRequestHandler.__init__. + CGIHTTPServer.CGIHTTPRequestHandler.__init__(self, request, + client_address, server) + + def parse_request(self): + """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request. + + Return True to continue processing for HTTP(S), False otherwise. + + See BaseHTTPRequestHandler.handle_one_request method which calls + this method to understand how the return value will be handled. + """ + + # We hook parse_request method, but also call the original + # CGIHTTPRequestHandler.parse_request since when we return False, + # CGIHTTPRequestHandler.handle_one_request continues processing and + # it needs variables set by CGIHTTPRequestHandler.parse_request. + # + # Variables set by this method will be also used by WebSocket request + # handling (self.path, self.command, self.requestline, etc. See also + # how _StandaloneRequest's members are implemented using these + # attributes). + if not CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self): + return False + + if self._options.use_basic_auth: + auth = self.headers.get('Authorization') + if auth != self._options.basic_auth_credential: + self.send_response(401) + self.send_header('WWW-Authenticate', + 'Basic realm="Pywebsocket"') + self.end_headers() + self._logger.info('Request basic authentication') + return False + + whole_path = self.path + host, port, resource = http_header_util.parse_uri(self.path) + if resource is None: + self._logger.info('Invalid URI: %r', self.path) + self._logger.info('Fallback to CGIHTTPRequestHandler') + return True + server_options = self.server.websocket_server_options + if host is not None: + validation_host = server_options.validation_host + if validation_host is not None and host != validation_host: + self._logger.info('Invalid host: %r (expected: %r)', host, + validation_host) + self._logger.info('Fallback to CGIHTTPRequestHandler') + return True + if port is not None: + validation_port = server_options.validation_port + if validation_port is not None and port != validation_port: + self._logger.info('Invalid port: %r (expected: %r)', port, + validation_port) + self._logger.info('Fallback to CGIHTTPRequestHandler') + return True + self.path = resource + + request = _StandaloneRequest(self, self._options.use_tls) + + try: + # Fallback to default http handler for request paths for which + # we don't have request handlers. + if not self._options.dispatcher.get_handler_suite(self.path): + self._logger.info('No handler for resource: %r', whole_path) + self._logger.info('Fallback to CGIHTTPRequestHandler') + return True + except dispatch.DispatchException as e: + self._logger.info('Dispatch failed for error: %s', e) + self.send_error(e.status) + return False + + # If any Exceptions without except clause setup (including + # DispatchException) is raised below this point, it will be caught + # and logged by WebSocketServer. + + try: + try: + handshake.do_handshake(request, self._options.dispatcher) + except handshake.VersionException as e: + self._logger.info('Handshake failed for version error: %s', e) + self.send_response(common.HTTP_STATUS_BAD_REQUEST) + self.send_header(common.SEC_WEBSOCKET_VERSION_HEADER, + e.supported_versions) + self.end_headers() + return False + except handshake.HandshakeException as e: + # Handshake for ws(s) failed. + self._logger.info('Handshake failed for error: %s', e) + self.send_error(e.status) + return False + + request._dispatcher = self._options.dispatcher + self._options.dispatcher.transfer_data(request) + except handshake.AbortedByUserException as e: + self._logger.info('Aborted: %s', e) + return False + + def log_request(self, code='-', size='-'): + """Override BaseHTTPServer.log_request.""" + + self._logger.info('"%s" %s %s', self.requestline, str(code), str(size)) + + def log_error(self, *args): + """Override BaseHTTPServer.log_error.""" + + # Despite the name, this method is for warnings than for errors. + # For example, HTTP status code is logged by this method. + self._logger.warning('%s - %s', self.address_string(), + args[0] % args[1:]) + + def is_cgi(self): + """Test whether self.path corresponds to a CGI script. + + Add extra check that self.path doesn't contains .. + Also check if the file is a executable file or not. + If the file is not executable, it is handled as static file or dir + rather than a CGI script. + """ + + if CGIHTTPServer.CGIHTTPRequestHandler.is_cgi(self): + if '..' in self.path: + return False + # strip query parameter from request path + resource_name = self.path.split('?', 2)[0] + # convert resource_name into real path name in filesystem. + scriptfile = self.translate_path(resource_name) + if not os.path.isfile(scriptfile): + return False + if not self.is_executable(scriptfile): + return False + return True + return False + + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/server_util.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/server_util.py new file mode 100644 index 0000000000000..3bf07f885bfb7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/server_util.py @@ -0,0 +1,86 @@ +# Copyright 2020, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""Server related utilities.""" + +import logging +import logging.handlers +import threading +import time + +from pywebsocket3 import common, util + + +def _get_logger_from_class(c): + return logging.getLogger('%s.%s' % (c.__module__, c.__name__)) + + +def configure_logging(options): + logging.addLevelName(common.LOGLEVEL_FINE, 'FINE') + + logger = logging.getLogger() + logger.setLevel(logging.getLevelName(options.log_level.upper())) + if options.log_file: + handler = logging.handlers.RotatingFileHandler(options.log_file, 'a', + options.log_max, + options.log_count) + else: + handler = logging.StreamHandler() + formatter = logging.Formatter( + '[%(asctime)s] [%(levelname)s] %(name)s: %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + + deflate_log_level_name = logging.getLevelName( + options.deflate_log_level.upper()) + _get_logger_from_class(util._Deflater).setLevel(deflate_log_level_name) + _get_logger_from_class(util._Inflater).setLevel(deflate_log_level_name) + + +class ThreadMonitor(threading.Thread): + daemon = True + + def __init__(self, interval_in_sec): + threading.Thread.__init__(self, name='ThreadMonitor') + + self._logger = util.get_class_logger(self) + + self._interval_in_sec = interval_in_sec + + def run(self): + while True: + thread_name_list = [] + for thread in threading.enumerate(): + thread_name_list.append(thread.name) + self._logger.info("%d active threads: %s", + threading.active_count(), + ', '.join(thread_name_list)) + time.sleep(self._interval_in_sec) + + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/standalone.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/standalone.py new file mode 100644 index 0000000000000..0c324c4221e90 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/standalone.py @@ -0,0 +1,492 @@ +#!/usr/bin/env python +# +# Copyright 2012, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""Standalone WebSocket server. + +Use this file to launch pywebsocket as a standalone server. + + +BASIC USAGE +=========== + +Go to the src directory and run + + $ python pywebsocket3/standalone.py [-p ] + [-w ] + [-d ] + + is the port number to use for ws:// connection. + + is the path to the root directory of HTML files. + + is the path to the root directory of WebSocket handlers. +If not specified, will be used. See __init__.py (or +run $ pydoc pywebsocket3) for how to write WebSocket handlers. + +For more detail and other options, run + + $ python pywebsocket3/standalone.py --help + +or see _build_option_parser method below. + +For trouble shooting, adding "--log_level debug" might help you. + + +TRY DEMO +======== + +Go to the src directory and run standalone.py with -d option to set the +document root to the directory containing example HTMLs and handlers like this: + + $ cd src + $ PYTHONPATH=. python pywebsocket3/standalone.py -d example + +to launch pywebsocket with the sample handler and html on port 80. Open +http://localhost/console.html, click the connect button, type something into +the text box next to the send button and click the send button. If everything +is working, you'll see the message you typed echoed by the server. + + +USING TLS +========= + +To run the standalone server with TLS support, run it with -t, -k, and -c +options. When TLS is enabled, the standalone server accepts only TLS connection. + +Note that when ssl module is used and the key/cert location is incorrect, +TLS connection silently fails while pyOpenSSL fails on startup. + +Example: + + $ PYTHONPATH=. python pywebsocket3/standalone.py \ + -d example \ + -p 10443 \ + -t \ + -c ../test/cert/cert.pem \ + -k ../test/cert/key.pem \ + +Note that when passing a relative path to -c and -k option, it will be resolved +using the document root directory as the base. + + +USING CLIENT AUTHENTICATION +=========================== + +To run the standalone server with TLS client authentication support, run it with +--tls-client-auth and --tls-client-ca options in addition to ones required for +TLS support. + +Example: + + $ PYTHONPATH=. python pywebsocket3/standalone.py -d example -p 10443 -t \ + -c ../test/cert/cert.pem -k ../test/cert/key.pem \ + --tls-client-auth \ + --tls-client-ca=../test/cert/cacert.pem + +Note that when passing a relative path to --tls-client-ca option, it will be +resolved using the document root directory as the base. + + +CONFIGURATION FILE +================== + +You can also write a configuration file and use it by specifying the path to +the configuration file by --config option. Please write a configuration file +following the documentation of the Python ConfigParser library. Name of each +entry must be the long version argument name. E.g. to set log level to debug, +add the following line: + +log_level=debug + +For options which doesn't take value, please add some fake value. E.g. for +--tls option, add the following line: + +tls=True + +Note that tls will be enabled even if you write tls=False as the value part is +fake. + +When both a command line argument and a configuration file entry are set for +the same configuration item, the command line value will override one in the +configuration file. + + +THREADING +========= + +This server is derived from SocketServer.ThreadingMixIn. Hence a thread is +used for each request. + + +SECURITY WARNING +================ + +This uses CGIHTTPServer and CGIHTTPServer is not secure. +It may execute arbitrary Python code or external programs. It should not be +used outside a firewall. +""" + +from __future__ import absolute_import + +import argparse +import base64 +import logging +import os +import sys +import traceback + +import six +from six.moves import configparser + +from pywebsocket3 import common, server_util, util +from pywebsocket3.websocket_server import WebSocketServer + + +_DEFAULT_LOG_MAX_BYTES = 1024 * 256 +_DEFAULT_LOG_BACKUP_COUNT = 5 + +_DEFAULT_REQUEST_QUEUE_SIZE = 128 + + +def _build_option_parser(): + parser = argparse.ArgumentParser() + + parser.add_argument( + '--config', + dest='config_file', + type=six.text_type, + default=None, + help=('Path to configuration file. See the file comment ' + 'at the top of this file for the configuration ' + 'file format')) + parser.add_argument('-H', + '--server-host', + '--server_host', + dest='server_host', + default='', + help='server hostname to listen to') + parser.add_argument('-V', + '--validation-host', + '--validation_host', + dest='validation_host', + default=None, + help='server hostname to validate in absolute path.') + parser.add_argument('-p', + '--port', + dest='port', + type=int, + default=common.DEFAULT_WEB_SOCKET_PORT, + help='port to listen to') + parser.add_argument('-P', + '--validation-port', + '--validation_port', + dest='validation_port', + type=int, + default=None, + help='server port to validate in absolute path.') + parser.add_argument( + '-w', + '--websock-handlers', + '--websock_handlers', + dest='websock_handlers', + default='.', + help=('The root directory of WebSocket handler files. ' + 'If the path is relative, --document-root is used ' + 'as the base.')) + parser.add_argument('-m', + '--websock-handlers-map-file', + '--websock_handlers_map_file', + dest='websock_handlers_map_file', + default=None, + help=('WebSocket handlers map file. ' + 'Each line consists of alias_resource_path and ' + 'existing_resource_path, separated by spaces.')) + parser.add_argument('-s', + '--scan-dir', + '--scan_dir', + dest='scan_dir', + default=None, + help=('Must be a directory under --websock-handlers. ' + 'Only handlers under this directory are scanned ' + 'and registered to the server. ' + 'Useful for saving scan time when the handler ' + 'root directory contains lots of files that are ' + 'not handler file or are handler files but you ' + 'don\'t want them to be registered. ')) + parser.add_argument( + '--allow-handlers-outside-root-dir', + '--allow_handlers_outside_root_dir', + dest='allow_handlers_outside_root_dir', + action='store_true', + default=False, + help=('Scans WebSocket handlers even if their canonical ' + 'path is not under --websock-handlers.')) + parser.add_argument('-d', + '--document-root', + '--document_root', + dest='document_root', + default='.', + help='Document root directory.') + parser.add_argument('-x', + '--cgi-paths', + '--cgi_paths', + dest='cgi_paths', + default=None, + help=('CGI paths relative to document_root.' + 'Comma-separated. (e.g -x /cgi,/htbin) ' + 'Files under document_root/cgi_path are handled ' + 'as CGI programs. Must be executable.')) + parser.add_argument('-t', + '--tls', + dest='use_tls', + action='store_true', + default=False, + help='use TLS (wss://)') + parser.add_argument('-k', + '--private-key', + '--private_key', + dest='private_key', + default='', + help='TLS private key file.') + parser.add_argument('-c', + '--certificate', + dest='certificate', + default='', + help='TLS certificate file.') + parser.add_argument('--tls-client-auth', + dest='tls_client_auth', + action='store_true', + default=False, + help='Requests TLS client auth on every connection.') + parser.add_argument('--tls-client-cert-optional', + dest='tls_client_cert_optional', + action='store_true', + default=False, + help=('Makes client certificate optional even though ' + 'TLS client auth is enabled.')) + parser.add_argument('--tls-client-ca', + dest='tls_client_ca', + default='', + help=('Specifies a pem file which contains a set of ' + 'concatenated CA certificates which are used to ' + 'validate certificates passed from clients')) + parser.add_argument('--basic-auth', + dest='use_basic_auth', + action='store_true', + default=False, + help='Requires Basic authentication.') + parser.add_argument( + '--basic-auth-credential', + dest='basic_auth_credential', + default='test:test', + help='Specifies the credential of basic authentication ' + 'by username:password pair (e.g. test:test).') + parser.add_argument('-l', + '--log-file', + '--log_file', + dest='log_file', + default='', + help='Log file.') + # Custom log level: + # - FINE: Prints status of each frame processing step + parser.add_argument('--log-level', + '--log_level', + type=six.text_type, + dest='log_level', + default='warn', + choices=[ + 'fine', 'debug', 'info', 'warning', 'warn', + 'error', 'critical' + ], + help='Log level.') + parser.add_argument( + '--deflate-log-level', + '--deflate_log_level', + type=six.text_type, + dest='deflate_log_level', + default='warn', + choices=['debug', 'info', 'warning', 'warn', 'error', 'critical'], + help='Log level for _Deflater and _Inflater.') + parser.add_argument('--thread-monitor-interval-in-sec', + '--thread_monitor_interval_in_sec', + dest='thread_monitor_interval_in_sec', + type=int, + default=-1, + help=('If positive integer is specified, run a thread ' + 'monitor to show the status of server threads ' + 'periodically in the specified inteval in ' + 'second. If non-positive integer is specified, ' + 'disable the thread monitor.')) + parser.add_argument('--log-max', + '--log_max', + dest='log_max', + type=int, + default=_DEFAULT_LOG_MAX_BYTES, + help='Log maximum bytes') + parser.add_argument('--log-count', + '--log_count', + dest='log_count', + type=int, + default=_DEFAULT_LOG_BACKUP_COUNT, + help='Log backup count') + parser.add_argument('-q', + '--queue', + dest='request_queue_size', + type=int, + default=_DEFAULT_REQUEST_QUEUE_SIZE, + help='request queue size') + parser.add_argument( + '--handler-encoding', + '--handler_encoding', + dest='handler_encoding', + type=six.text_type, + default=None, + help=('Text encoding used for loading handlers. ' + 'By default, the encoding from the locale is used when ' + 'reading handler files, but this option can override it. ' + 'Any encoding supported by the codecs module may be used.')) + + return parser + + +def _parse_args_and_config(args): + parser = _build_option_parser() + + # First, parse options without configuration file. + temporary_options, temporary_args = parser.parse_known_args(args=args) + if temporary_args: + logging.critical('Unrecognized positional arguments: %r', + temporary_args) + sys.exit(1) + + if temporary_options.config_file: + try: + config_fp = open(temporary_options.config_file, 'r') + except IOError as e: + logging.critical('Failed to open configuration file %r: %r', + temporary_options.config_file, e) + sys.exit(1) + + config_parser = configparser.SafeConfigParser() + config_parser.readfp(config_fp) + config_fp.close() + + args_from_config = [] + for name, value in config_parser.items('pywebsocket'): + args_from_config.append('--' + name) + args_from_config.append(value) + if args is None: + args = args_from_config + else: + args = args_from_config + args + return parser.parse_known_args(args=args) + else: + return temporary_options, temporary_args + + +def _main(args=None): + """You can call this function from your own program, but please note that + this function has some side-effects that might affect your program. For + example, it changes the current directory. + """ + + options, args = _parse_args_and_config(args=args) + + os.chdir(options.document_root) + + server_util.configure_logging(options) + + # TODO(tyoshino): Clean up initialization of CGI related values. Move some + # of code here to WebSocketRequestHandler class if it's better. + options.cgi_directories = [] + options.is_executable_method = None + if options.cgi_paths: + options.cgi_directories = options.cgi_paths.split(',') + if sys.platform in ('cygwin', 'win32'): + cygwin_path = None + # For Win32 Python, it is expected that CYGWIN_PATH + # is set to a directory of cygwin binaries. + # For example, websocket_server.py in Chromium sets CYGWIN_PATH to + # full path of third_party/cygwin/bin. + if 'CYGWIN_PATH' in os.environ: + cygwin_path = os.environ['CYGWIN_PATH'] + + def __check_script(scriptpath): + return util.get_script_interp(scriptpath, cygwin_path) + + options.is_executable_method = __check_script + + if options.use_tls: + logging.debug('Using ssl module') + + if not options.private_key or not options.certificate: + logging.critical( + 'To use TLS, specify private_key and certificate.') + sys.exit(1) + + if (options.tls_client_cert_optional and not options.tls_client_auth): + logging.critical('Client authentication must be enabled to ' + 'specify tls_client_cert_optional') + sys.exit(1) + else: + if options.tls_client_auth: + logging.critical('TLS must be enabled for client authentication.') + sys.exit(1) + + if options.tls_client_cert_optional: + logging.critical('TLS must be enabled for client authentication.') + sys.exit(1) + + if not options.scan_dir: + options.scan_dir = options.websock_handlers + + if options.use_basic_auth: + options.basic_auth_credential = 'Basic ' + base64.b64encode( + options.basic_auth_credential.encode('UTF-8')).decode() + + try: + if options.thread_monitor_interval_in_sec > 0: + # Run a thread monitor to show the status of server threads for + # debugging. + server_util.ThreadMonitor( + options.thread_monitor_interval_in_sec).start() + + server = WebSocketServer(options) + server.serve_forever() + except Exception as e: + logging.critical('pywebsocket3: %s' % e) + logging.critical('pywebsocket3: %s' % traceback.format_exc()) + sys.exit(1) + + +if __name__ == '__main__': + _main(sys.argv[1:]) + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/stream.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/stream.py new file mode 100644 index 0000000000000..dd41850dc4653 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/stream.py @@ -0,0 +1,951 @@ +# Copyright 2011, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""This file provides classes and helper functions for parsing/building frames +of the WebSocket protocol (RFC 6455). + +Specification: +http://tools.ietf.org/html/rfc6455 +""" + +import logging +import os +import struct +import time +from collections import deque + +import six + +from pywebsocket3 import common, util +from pywebsocket3._stream_exceptions import ( + BadOperationException, + ConnectionTerminatedException, + InvalidFrameException, + InvalidUTF8Exception, + UnsupportedFrameException +) + +_NOOP_MASKER = util.NoopMasker() + + +class Frame(object): + def __init__(self, + fin=1, + rsv1=0, + rsv2=0, + rsv3=0, + opcode=None, + payload=b''): + self.fin = fin + self.rsv1 = rsv1 + self.rsv2 = rsv2 + self.rsv3 = rsv3 + self.opcode = opcode + self.payload = payload + + +# Helper functions made public to be used for writing unittests for WebSocket +# clients. + + +def create_length_header(length, mask): + """Creates a length header. + + Args: + length: Frame length. Must be less than 2^63. + mask: Mask bit. Must be boolean. + + Raises: + ValueError: when bad data is given. + """ + + if mask: + mask_bit = 1 << 7 + else: + mask_bit = 0 + + if length < 0: + raise ValueError('length must be non negative integer') + elif length <= 125: + return util.pack_byte(mask_bit | length) + elif length < (1 << 16): + return util.pack_byte(mask_bit | 126) + struct.pack('!H', length) + elif length < (1 << 63): + return util.pack_byte(mask_bit | 127) + struct.pack('!Q', length) + else: + raise ValueError('Payload is too big for one frame') + + +def create_header(opcode, payload_length, fin, rsv1, rsv2, rsv3, mask): + """Creates a frame header. + + Raises: + Exception: when bad data is given. + """ + + if opcode < 0 or 0xf < opcode: + raise ValueError('Opcode out of range') + + if payload_length < 0 or (1 << 63) <= payload_length: + raise ValueError('payload_length out of range') + + if (fin | rsv1 | rsv2 | rsv3) & ~1: + raise ValueError('FIN bit and Reserved bit parameter must be 0 or 1') + + header = b'' + + first_byte = ((fin << 7) + | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4) + | opcode) + header += util.pack_byte(first_byte) + header += create_length_header(payload_length, mask) + + return header + + +def _build_frame(header, body, mask): + if not mask: + return header + body + + masking_nonce = os.urandom(4) + masker = util.RepeatedXorMasker(masking_nonce) + + return header + masking_nonce + masker.mask(body) + + +def _filter_and_format_frame_object(frame, mask, frame_filters): + for frame_filter in frame_filters: + frame_filter.filter(frame) + + header = create_header(frame.opcode, len(frame.payload), frame.fin, + frame.rsv1, frame.rsv2, frame.rsv3, mask) + return _build_frame(header, frame.payload, mask) + + +def create_binary_frame(message, + opcode=common.OPCODE_BINARY, + fin=1, + mask=False, + frame_filters=[]): + """Creates a simple binary frame with no extension, reserved bit.""" + + frame = Frame(fin=fin, opcode=opcode, payload=message) + return _filter_and_format_frame_object(frame, mask, frame_filters) + + +def create_text_frame(message, + opcode=common.OPCODE_TEXT, + fin=1, + mask=False, + frame_filters=[]): + """Creates a simple text frame with no extension, reserved bit.""" + + encoded_message = message.encode('utf-8') + return create_binary_frame(encoded_message, opcode, fin, mask, + frame_filters) + + +def parse_frame(receive_bytes, + logger=None, + ws_version=common.VERSION_HYBI_LATEST, + unmask_receive=True): + """Parses a frame. Returns a tuple containing each header field and + payload. + + Args: + receive_bytes: a function that reads frame data from a stream or + something similar. The function takes length of the bytes to be + read. The function must raise ConnectionTerminatedException if + there is not enough data to be read. + logger: a logging object. + ws_version: the version of WebSocket protocol. + unmask_receive: unmask received frames. When received unmasked + frame, raises InvalidFrameException. + + Raises: + ConnectionTerminatedException: when receive_bytes raises it. + InvalidFrameException: when the frame contains invalid data. + """ + + if not logger: + logger = logging.getLogger() + + logger.log(common.LOGLEVEL_FINE, 'Receive the first 2 octets of a frame') + + first_byte = ord(receive_bytes(1)) + fin = (first_byte >> 7) & 1 + rsv1 = (first_byte >> 6) & 1 + rsv2 = (first_byte >> 5) & 1 + rsv3 = (first_byte >> 4) & 1 + opcode = first_byte & 0xf + + second_byte = ord(receive_bytes(1)) + mask = (second_byte >> 7) & 1 + payload_length = second_byte & 0x7f + + logger.log( + common.LOGLEVEL_FINE, 'FIN=%s, RSV1=%s, RSV2=%s, RSV3=%s, opcode=%s, ' + 'Mask=%s, Payload_length=%s', fin, rsv1, rsv2, rsv3, opcode, mask, + payload_length) + + if (mask == 1) != unmask_receive: + raise InvalidFrameException( + 'Mask bit on the received frame did\'nt match masking ' + 'configuration for received frames') + + # The HyBi and later specs disallow putting a value in 0x0-0xFFFF + # into the 8-octet extended payload length field (or 0x0-0xFD in + # 2-octet field). + valid_length_encoding = True + length_encoding_bytes = 1 + if payload_length == 127: + logger.log(common.LOGLEVEL_FINE, + 'Receive 8-octet extended payload length') + + extended_payload_length = receive_bytes(8) + payload_length = struct.unpack('!Q', extended_payload_length)[0] + if payload_length > 0x7FFFFFFFFFFFFFFF: + raise InvalidFrameException('Extended payload length >= 2^63') + if ws_version >= 13 and payload_length < 0x10000: + valid_length_encoding = False + length_encoding_bytes = 8 + + logger.log(common.LOGLEVEL_FINE, 'Decoded_payload_length=%s', + payload_length) + elif payload_length == 126: + logger.log(common.LOGLEVEL_FINE, + 'Receive 2-octet extended payload length') + + extended_payload_length = receive_bytes(2) + payload_length = struct.unpack('!H', extended_payload_length)[0] + if ws_version >= 13 and payload_length < 126: + valid_length_encoding = False + length_encoding_bytes = 2 + + logger.log(common.LOGLEVEL_FINE, 'Decoded_payload_length=%s', + payload_length) + + if not valid_length_encoding: + logger.warning( + 'Payload length is not encoded using the minimal number of ' + 'bytes (%d is encoded using %d bytes)', payload_length, + length_encoding_bytes) + + if mask == 1: + logger.log(common.LOGLEVEL_FINE, 'Receive mask') + + masking_nonce = receive_bytes(4) + masker = util.RepeatedXorMasker(masking_nonce) + + logger.log(common.LOGLEVEL_FINE, 'Mask=%r', masking_nonce) + else: + masker = _NOOP_MASKER + + logger.log(common.LOGLEVEL_FINE, 'Receive payload data') + if logger.isEnabledFor(common.LOGLEVEL_FINE): + receive_start = time.time() + + raw_payload_bytes = receive_bytes(payload_length) + + if logger.isEnabledFor(common.LOGLEVEL_FINE): + logger.log( + common.LOGLEVEL_FINE, 'Done receiving payload data at %s MB/s', + payload_length / (time.time() - receive_start) / 1000 / 1000) + logger.log(common.LOGLEVEL_FINE, 'Unmask payload data') + + if logger.isEnabledFor(common.LOGLEVEL_FINE): + unmask_start = time.time() + + unmasked_bytes = masker.mask(raw_payload_bytes) + + if logger.isEnabledFor(common.LOGLEVEL_FINE): + logger.log(common.LOGLEVEL_FINE, + 'Done unmasking payload data at %s MB/s', + payload_length / (time.time() - unmask_start) / 1000 / 1000) + + return opcode, unmasked_bytes, fin, rsv1, rsv2, rsv3 + + +class FragmentedFrameBuilder(object): + """A stateful class to send a message as fragments.""" + def __init__(self, mask, frame_filters=[], encode_utf8=True): + """Constructs an instance.""" + + self._mask = mask + self._frame_filters = frame_filters + # This is for skipping UTF-8 encoding when building text type frames + # from compressed data. + self._encode_utf8 = encode_utf8 + + self._started = False + + # Hold opcode of the first frame in messages to verify types of other + # frames in the message are all the same. + self._opcode = common.OPCODE_TEXT + + def build(self, payload_data, end, binary): + if binary: + frame_type = common.OPCODE_BINARY + else: + frame_type = common.OPCODE_TEXT + if self._started: + if self._opcode != frame_type: + raise ValueError('Message types are different in frames for ' + 'the same message') + opcode = common.OPCODE_CONTINUATION + else: + opcode = frame_type + self._opcode = frame_type + + if end: + self._started = False + fin = 1 + else: + self._started = True + fin = 0 + + if binary or not self._encode_utf8: + return create_binary_frame(payload_data, opcode, fin, self._mask, + self._frame_filters) + else: + return create_text_frame(payload_data, opcode, fin, self._mask, + self._frame_filters) + + +def _create_control_frame(opcode, body, mask, frame_filters): + frame = Frame(opcode=opcode, payload=body) + + for frame_filter in frame_filters: + frame_filter.filter(frame) + + if len(frame.payload) > 125: + raise BadOperationException( + 'Payload data size of control frames must be 125 bytes or less') + + header = create_header(frame.opcode, len(frame.payload), frame.fin, + frame.rsv1, frame.rsv2, frame.rsv3, mask) + return _build_frame(header, frame.payload, mask) + + +def create_ping_frame(body, mask=False, frame_filters=[]): + return _create_control_frame(common.OPCODE_PING, body, mask, frame_filters) + + +def create_pong_frame(body, mask=False, frame_filters=[]): + return _create_control_frame(common.OPCODE_PONG, body, mask, frame_filters) + + +def create_close_frame(body, mask=False, frame_filters=[]): + return _create_control_frame(common.OPCODE_CLOSE, body, mask, + frame_filters) + + +def create_closing_handshake_body(code, reason): + body = b'' + if code is not None: + if (code > common.STATUS_USER_PRIVATE_MAX + or code < common.STATUS_NORMAL_CLOSURE): + raise BadOperationException('Status code is out of range') + if (code == common.STATUS_NO_STATUS_RECEIVED + or code == common.STATUS_ABNORMAL_CLOSURE + or code == common.STATUS_TLS_HANDSHAKE): + raise BadOperationException('Status code is reserved pseudo ' + 'code') + encoded_reason = reason.encode('utf-8') + body = struct.pack('!H', code) + encoded_reason + return body + + +class StreamOptions(object): + """Holds option values to configure Stream objects.""" + def __init__(self): + """Constructs StreamOptions.""" + + # Filters applied to frames. + self.outgoing_frame_filters = [] + self.incoming_frame_filters = [] + + # Filters applied to messages. Control frames are not affected by them. + self.outgoing_message_filters = [] + self.incoming_message_filters = [] + + self.encode_text_message_to_utf8 = True + self.mask_send = False + self.unmask_receive = True + + +class Stream(object): + """A class for parsing/building frames of the WebSocket protocol + (RFC 6455). + """ + def __init__(self, request, options): + """Constructs an instance. + + Args: + request: mod_python request. + """ + + self._logger = util.get_class_logger(self) + + self._options = options + self._request = request + + self._request.client_terminated = False + self._request.server_terminated = False + + # Holds body of received fragments. + self._received_fragments = [] + # Holds the opcode of the first fragment. + self._original_opcode = None + + self._writer = FragmentedFrameBuilder( + self._options.mask_send, self._options.outgoing_frame_filters, + self._options.encode_text_message_to_utf8) + + self._ping_queue = deque() + + def _read(self, length): + """Reads length bytes from connection. In case we catch any exception, + prepends remote address to the exception message and raise again. + + Raises: + ConnectionTerminatedException: when read returns empty string. + """ + + try: + read_bytes = self._request.connection.read(length) + if not read_bytes: + raise ConnectionTerminatedException( + 'Receiving %d byte failed. Peer (%r) closed connection' % + (length, (self._request.connection.remote_addr, ))) + return read_bytes + except IOError as e: + # Also catch an IOError because mod_python throws it. + raise ConnectionTerminatedException( + 'Receiving %d byte failed. IOError (%s) occurred' % + (length, e)) + + def _write(self, bytes_to_write): + """Writes given bytes to connection. In case we catch any exception, + prepends remote address to the exception message and raise again. + """ + + try: + self._request.connection.write(bytes_to_write) + except Exception as e: + util.prepend_message_to_exception( + 'Failed to send message to %r: ' % + (self._request.connection.remote_addr, ), e) + raise + + def receive_bytes(self, length): + """Receives multiple bytes. Retries read when we couldn't receive the + specified amount. This method returns byte strings. + + Raises: + ConnectionTerminatedException: when read returns empty string. + """ + + read_bytes = [] + while length > 0: + new_read_bytes = self._read(length) + read_bytes.append(new_read_bytes) + length -= len(new_read_bytes) + return b''.join(read_bytes) + + def _read_until(self, delim_char): + """Reads bytes until we encounter delim_char. The result will not + contain delim_char. + + Raises: + ConnectionTerminatedException: when read returns empty string. + """ + + read_bytes = [] + while True: + ch = self._read(1) + if ch == delim_char: + break + read_bytes.append(ch) + return b''.join(read_bytes) + + def _receive_frame(self): + """Receives a frame and return data in the frame as a tuple containing + each header field and payload separately. + + Raises: + ConnectionTerminatedException: when read returns empty + string. + InvalidFrameException: when the frame contains invalid data. + """ + def _receive_bytes(length): + return self.receive_bytes(length) + + return parse_frame(receive_bytes=_receive_bytes, + logger=self._logger, + ws_version=self._request.ws_version, + unmask_receive=self._options.unmask_receive) + + def _receive_frame_as_frame_object(self): + opcode, unmasked_bytes, fin, rsv1, rsv2, rsv3 = self._receive_frame() + + return Frame(fin=fin, + rsv1=rsv1, + rsv2=rsv2, + rsv3=rsv3, + opcode=opcode, + payload=unmasked_bytes) + + def receive_filtered_frame(self): + """Receives a frame and applies frame filters and message filters. + The frame to be received must satisfy following conditions: + - The frame is not fragmented. + - The opcode of the frame is TEXT or BINARY. + + DO NOT USE this method except for testing purpose. + """ + + frame = self._receive_frame_as_frame_object() + if not frame.fin: + raise InvalidFrameException( + 'Segmented frames must not be received via ' + 'receive_filtered_frame()') + if (frame.opcode != common.OPCODE_TEXT + and frame.opcode != common.OPCODE_BINARY): + raise InvalidFrameException( + 'Control frames must not be received via ' + 'receive_filtered_frame()') + + for frame_filter in self._options.incoming_frame_filters: + frame_filter.filter(frame) + for message_filter in self._options.incoming_message_filters: + frame.payload = message_filter.filter(frame.payload) + return frame + + def send_message(self, message, end=True, binary=False): + """Send message. + + Args: + message: text in unicode or binary in str to send. + binary: send message as binary frame. + + Raises: + BadOperationException: when called on a server-terminated + connection or called with inconsistent message type or + binary parameter. + """ + + if self._request.server_terminated: + raise BadOperationException( + 'Requested send_message after sending out a closing handshake') + + if binary and isinstance(message, six.text_type): + raise BadOperationException( + 'Message for binary frame must not be instance of Unicode') + + for message_filter in self._options.outgoing_message_filters: + message = message_filter.filter(message, end, binary) + + try: + # Set this to any positive integer to limit maximum size of data in + # payload data of each frame. + MAX_PAYLOAD_DATA_SIZE = -1 + + if MAX_PAYLOAD_DATA_SIZE <= 0: + self._write(self._writer.build(message, end, binary)) + return + + bytes_written = 0 + while True: + end_for_this_frame = end + bytes_to_write = len(message) - bytes_written + if (MAX_PAYLOAD_DATA_SIZE > 0 + and bytes_to_write > MAX_PAYLOAD_DATA_SIZE): + end_for_this_frame = False + bytes_to_write = MAX_PAYLOAD_DATA_SIZE + + frame = self._writer.build( + message[bytes_written:bytes_written + bytes_to_write], + end_for_this_frame, binary) + self._write(frame) + + bytes_written += bytes_to_write + + # This if must be placed here (the end of while block) so that + # at least one frame is sent. + if len(message) <= bytes_written: + break + except ValueError as e: + raise BadOperationException(e) + + def _get_message_from_frame(self, frame): + """Gets a message from frame. If the message is composed of fragmented + frames and the frame is not the last fragmented frame, this method + returns None. The whole message will be returned when the last + fragmented frame is passed to this method. + + Raises: + InvalidFrameException: when the frame doesn't match defragmentation + context, or the frame contains invalid data. + """ + + if frame.opcode == common.OPCODE_CONTINUATION: + if not self._received_fragments: + if frame.fin: + raise InvalidFrameException( + 'Received a termination frame but fragmentation ' + 'not started') + else: + raise InvalidFrameException( + 'Received an intermediate frame but ' + 'fragmentation not started') + + if frame.fin: + # End of fragmentation frame + self._received_fragments.append(frame.payload) + message = b''.join(self._received_fragments) + self._received_fragments = [] + return message + else: + # Intermediate frame + self._received_fragments.append(frame.payload) + return None + else: + if self._received_fragments: + if frame.fin: + raise InvalidFrameException( + 'Received an unfragmented frame without ' + 'terminating existing fragmentation') + else: + raise InvalidFrameException( + 'New fragmentation started without terminating ' + 'existing fragmentation') + + if frame.fin: + # Unfragmented frame + + self._original_opcode = frame.opcode + return frame.payload + else: + # Start of fragmentation frame + + if common.is_control_opcode(frame.opcode): + raise InvalidFrameException( + 'Control frames must not be fragmented') + + self._original_opcode = frame.opcode + self._received_fragments.append(frame.payload) + return None + + def _process_close_message(self, message): + """Processes close message. + + Args: + message: close message. + + Raises: + InvalidFrameException: when the message is invalid. + """ + + self._request.client_terminated = True + + # Status code is optional. We can have status reason only if we + # have status code. Status reason can be empty string. So, + # allowed cases are + # - no application data: no code no reason + # - 2 octet of application data: has code but no reason + # - 3 or more octet of application data: both code and reason + if len(message) == 0: + self._logger.debug('Received close frame (empty body)') + self._request.ws_close_code = common.STATUS_NO_STATUS_RECEIVED + elif len(message) == 1: + raise InvalidFrameException( + 'If a close frame has status code, the length of ' + 'status code must be 2 octet') + elif len(message) >= 2: + self._request.ws_close_code = struct.unpack('!H', message[0:2])[0] + self._request.ws_close_reason = message[2:].decode( + 'utf-8', 'replace') + self._logger.debug('Received close frame (code=%d, reason=%r)', + self._request.ws_close_code, + self._request.ws_close_reason) + + # As we've received a close frame, no more data is coming over the + # socket. We can now safely close the socket without worrying about + # RST sending. + + if self._request.server_terminated: + self._logger.debug( + 'Received ack for server-initiated closing handshake') + return + + self._logger.debug('Received client-initiated closing handshake') + + code = common.STATUS_NORMAL_CLOSURE + reason = '' + if hasattr(self._request, '_dispatcher'): + dispatcher = self._request._dispatcher + code, reason = dispatcher.passive_closing_handshake(self._request) + if code is None and reason is not None and len(reason) > 0: + self._logger.warning( + 'Handler specified reason despite code being None') + reason = '' + if reason is None: + reason = '' + self._send_closing_handshake(code, reason) + self._logger.debug( + 'Acknowledged closing handshake initiated by the peer ' + '(code=%r, reason=%r)', code, reason) + + def _process_ping_message(self, message): + """Processes ping message. + + Args: + message: ping message. + """ + + try: + handler = self._request.on_ping_handler + if handler: + handler(self._request, message) + return + except AttributeError: + pass + self._send_pong(message) + + def _process_pong_message(self, message): + """Processes pong message. + + Args: + message: pong message. + """ + + # TODO(tyoshino): Add ping timeout handling. + + inflight_pings = deque() + + while True: + try: + expected_body = self._ping_queue.popleft() + if expected_body == message: + # inflight_pings contains pings ignored by the + # other peer. Just forget them. + self._logger.debug( + 'Ping %r is acked (%d pings were ignored)', + expected_body, len(inflight_pings)) + break + else: + inflight_pings.append(expected_body) + except IndexError: + # The received pong was unsolicited pong. Keep the + # ping queue as is. + self._ping_queue = inflight_pings + self._logger.debug('Received a unsolicited pong') + break + + try: + handler = self._request.on_pong_handler + if handler: + handler(self._request, message) + except AttributeError: + pass + + def receive_message(self): + """Receive a WebSocket frame and return its payload as a text in + unicode or a binary in str. + + Returns: + payload data of the frame + - as unicode instance if received text frame + - as str instance if received binary frame + or None iff received closing handshake. + Raises: + BadOperationException: when called on a client-terminated + connection. + ConnectionTerminatedException: when read returns empty + string. + InvalidFrameException: when the frame contains invalid + data. + UnsupportedFrameException: when the received frame has + flags, opcode we cannot handle. You can ignore this + exception and continue receiving the next frame. + """ + + if self._request.client_terminated: + raise BadOperationException( + 'Requested receive_message after receiving a closing ' + 'handshake') + + while True: + # mp_conn.read will block if no bytes are available. + + frame = self._receive_frame_as_frame_object() + + # Check the constraint on the payload size for control frames + # before extension processes the frame. + # See also http://tools.ietf.org/html/rfc6455#section-5.5 + if (common.is_control_opcode(frame.opcode) + and len(frame.payload) > 125): + raise InvalidFrameException( + 'Payload data size of control frames must be 125 bytes or ' + 'less') + + for frame_filter in self._options.incoming_frame_filters: + frame_filter.filter(frame) + + if frame.rsv1 or frame.rsv2 or frame.rsv3: + raise UnsupportedFrameException( + 'Unsupported flag is set (rsv = %d%d%d)' % + (frame.rsv1, frame.rsv2, frame.rsv3)) + + message = self._get_message_from_frame(frame) + if message is None: + continue + + for message_filter in self._options.incoming_message_filters: + message = message_filter.filter(message) + + if self._original_opcode == common.OPCODE_TEXT: + # The WebSocket protocol section 4.4 specifies that invalid + # characters must be replaced with U+fffd REPLACEMENT + # CHARACTER. + try: + return message.decode('utf-8') + except UnicodeDecodeError as e: + raise InvalidUTF8Exception(e) + elif self._original_opcode == common.OPCODE_BINARY: + return message + elif self._original_opcode == common.OPCODE_CLOSE: + self._process_close_message(message) + return None + elif self._original_opcode == common.OPCODE_PING: + self._process_ping_message(message) + elif self._original_opcode == common.OPCODE_PONG: + self._process_pong_message(message) + else: + raise UnsupportedFrameException('Opcode %d is not supported' % + self._original_opcode) + + def _send_closing_handshake(self, code, reason): + body = create_closing_handshake_body(code, reason) + frame = create_close_frame( + body, + mask=self._options.mask_send, + frame_filters=self._options.outgoing_frame_filters) + + self._request.server_terminated = True + + self._write(frame) + + def close_connection(self, + code=common.STATUS_NORMAL_CLOSURE, + reason='', + wait_response=True): + """Closes a WebSocket connection. Note that this method blocks until + it receives acknowledgement to the closing handshake. + + Args: + code: Status code for close frame. If code is None, a close + frame with empty body will be sent. + reason: string representing close reason. + wait_response: True when caller want to wait the response. + Raises: + BadOperationException: when reason is specified with code None + or reason is not an instance of both str and unicode. + """ + + if self._request.server_terminated: + self._logger.debug( + 'Requested close_connection but server is already terminated') + return + + # When we receive a close frame, we call _process_close_message(). + # _process_close_message() immediately acknowledges to the + # server-initiated closing handshake and sets server_terminated to + # True. So, here we can assume that we haven't received any close + # frame. We're initiating a closing handshake. + + if code is None: + if reason is not None and len(reason) > 0: + raise BadOperationException( + 'close reason must not be specified if code is None') + reason = '' + else: + if not isinstance(reason, bytes) and not isinstance( + reason, six.text_type): + raise BadOperationException( + 'close reason must be an instance of bytes or unicode') + + self._send_closing_handshake(code, reason) + self._logger.debug('Initiated closing handshake (code=%r, reason=%r)', + code, reason) + + if (code == common.STATUS_GOING_AWAY + or code == common.STATUS_PROTOCOL_ERROR) or not wait_response: + # It doesn't make sense to wait for a close frame if the reason is + # protocol error or that the server is going away. For some of + # other reasons, it might not make sense to wait for a close frame, + # but it's not clear, yet. + return + + # TODO(ukai): 2. wait until the /client terminated/ flag has been set, + # or until a server-defined timeout expires. + # + # For now, we expect receiving closing handshake right after sending + # out closing handshake. + message = self.receive_message() + if message is not None: + raise ConnectionTerminatedException( + 'Didn\'t receive valid ack for closing handshake') + # TODO: 3. close the WebSocket connection. + # note: mod_python Connection (mp_conn) doesn't have close method. + + def send_ping(self, body, binary=False): + if not binary and isinstance(body, six.text_type): + body = body.encode('UTF-8') + frame = create_ping_frame(body, self._options.mask_send, + self._options.outgoing_frame_filters) + self._write(frame) + + self._ping_queue.append(body) + + def _send_pong(self, body): + frame = create_pong_frame(body, self._options.mask_send, + self._options.outgoing_frame_filters) + self._write(frame) + + def get_last_received_opcode(self): + """Returns the opcode of the WebSocket message which the last received + frame belongs to. The return value is valid iff immediately after + receive_message call. + """ + + return self._original_opcode + + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/util.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/util.py new file mode 100644 index 0000000000000..9c25ab8315228 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/util.py @@ -0,0 +1,384 @@ +# Copyright 2011, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""WebSocket utilities.""" + +from __future__ import absolute_import + +import logging +import os +import re +import struct +import zlib + +import six +from six.moves import map, range + +try: + from pywebsocket3 import fast_masking +except ImportError: + pass + + +def prepend_message_to_exception(message, exc): + """Prepend message to the exception.""" + exc.args = (message + str(exc), ) + return + + +def __translate_interp(interp, cygwin_path): + """Translate interp program path for Win32 python to run cygwin program + (e.g. perl). Note that it doesn't support path that contains space, + which is typically true for Unix, where #!-script is written. + For Win32 python, cygwin_path is a directory of cygwin binaries. + + Args: + interp: interp command line + cygwin_path: directory name of cygwin binary, or None + Returns: + translated interp command line. + """ + if not cygwin_path: + return interp + m = re.match('^[^ ]*/([^ ]+)( .*)?', interp) + if m: + cmd = os.path.join(cygwin_path, m.group(1)) + return cmd + m.group(2) + return interp + + +def get_script_interp(script_path, cygwin_path=None): + r"""Get #!-interpreter command line from the script. + + It also fixes command path. When Cygwin Python is used, e.g. in WebKit, + it could run "/usr/bin/perl -wT hello.pl". + When Win32 Python is used, e.g. in Chromium, it couldn't. So, fix + "/usr/bin/perl" to "\perl.exe". + + Args: + script_path: pathname of the script + cygwin_path: directory name of cygwin binary, or None + Returns: + #!-interpreter command line, or None if it is not #!-script. + """ + fp = open(script_path) + line = fp.readline() + fp.close() + m = re.match('^#!(.*)', line) + if m: + return __translate_interp(m.group(1), cygwin_path) + return None + + +def hexify(s): + return ' '.join(['%02x' % x for x in six.iterbytes(s)]) + + +def get_class_logger(o): + """Return the logging class information.""" + return logging.getLogger('%s.%s' % + (o.__class__.__module__, o.__class__.__name__)) + + +def pack_byte(b): + """Pack an integer to network-ordered byte""" + return struct.pack('!B', b) + + +class NoopMasker(object): + """A NoOp masking object. + + This has the same interface as RepeatedXorMasker but just returns + the string passed in without making any change. + """ + def __init__(self): + """NoOp.""" + pass + + def mask(self, s): + """NoOp.""" + return s + + +class RepeatedXorMasker(object): + """A masking object that applies XOR on the string. + + Applies XOR on the byte string given to mask method with the masking bytes + given to the constructor repeatedly. This object remembers the position + in the masking bytes the last mask method call ended and resumes from + that point on the next mask method call. + """ + def __init__(self, masking_key): + self._masking_key = masking_key + self._masking_key_index = 0 + + def _mask_using_swig(self, s): + """Perform the mask via SWIG.""" + masked_data = fast_masking.mask(s, self._masking_key, + self._masking_key_index) + self._masking_key_index = ((self._masking_key_index + len(s)) % + len(self._masking_key)) + return masked_data + + def _mask_using_array(self, s): + """Perform the mask via python.""" + if isinstance(s, six.text_type): + raise Exception( + 'Masking Operation should not process unicode strings') + + result = bytearray(s) + + # Use temporary local variables to eliminate the cost to access + # attributes + masking_key = [c for c in six.iterbytes(self._masking_key)] + masking_key_size = len(masking_key) + masking_key_index = self._masking_key_index + + for i in range(len(result)): + result[i] ^= masking_key[masking_key_index] + masking_key_index = (masking_key_index + 1) % masking_key_size + + self._masking_key_index = masking_key_index + + return bytes(result) + + if 'fast_masking' in globals(): + mask = _mask_using_swig + else: + mask = _mask_using_array + + +# By making wbits option negative, we can suppress CMF/FLG (2 octet) and +# ADLER32 (4 octet) fields of zlib so that we can use zlib module just as +# deflate library. DICTID won't be added as far as we don't set dictionary. +# LZ77 window of 32K will be used for both compression and decompression. +# For decompression, we can just use 32K to cover any windows size. For +# compression, we use 32K so receivers must use 32K. +# +# Compression level is Z_DEFAULT_COMPRESSION. We don't have to match level +# to decode. +# +# See zconf.h, deflate.cc, inflate.cc of zlib library, and zlibmodule.c of +# Python. See also RFC1950 (ZLIB 3.3). + + +class _Deflater(object): + def __init__(self, window_bits): + self._logger = get_class_logger(self) + + # Using the smallest window bits of 9 for generating input frames. + # On WebSocket spec, the smallest window bit is 8. However, zlib does + # not accept window_bit = 8. + # + # Because of a zlib deflate quirk, back-references will not use the + # entire range of 1 << window_bits, but will instead use a restricted + # range of (1 << window_bits) - 262. With an increased window_bits = 9, + # back-references will be within a range of 250. These can still be + # decompressed with window_bits = 8 and the 256-byte window used there. + # + # Similar disscussions can be found in https://crbug.com/691074 + window_bits = max(window_bits, 9) + + self._compress = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, + zlib.DEFLATED, -window_bits) + + def compress(self, bytes): + compressed_bytes = self._compress.compress(bytes) + self._logger.debug('Compress input %r', bytes) + self._logger.debug('Compress result %r', compressed_bytes) + return compressed_bytes + + def compress_and_flush(self, bytes): + compressed_bytes = self._compress.compress(bytes) + compressed_bytes += self._compress.flush(zlib.Z_SYNC_FLUSH) + self._logger.debug('Compress input %r', bytes) + self._logger.debug('Compress result %r', compressed_bytes) + return compressed_bytes + + def compress_and_finish(self, bytes): + compressed_bytes = self._compress.compress(bytes) + compressed_bytes += self._compress.flush(zlib.Z_FINISH) + self._logger.debug('Compress input %r', bytes) + self._logger.debug('Compress result %r', compressed_bytes) + return compressed_bytes + + +class _Inflater(object): + def __init__(self, window_bits): + self._logger = get_class_logger(self) + self._window_bits = window_bits + + self._unconsumed = b'' + + self.reset() + + def decompress(self, size): + if not (size == -1 or size > 0): + raise Exception('size must be -1 or positive') + + data = b'' + + while True: + data += self._decompress.decompress(self._unconsumed, + max(0, size - len(data))) + self._unconsumed = self._decompress.unconsumed_tail + if self._decompress.unused_data: + # Encountered a last block (i.e. a block with BFINAL = 1) and + # found a new stream (unused_data). We cannot use the same + # zlib.Decompress object for the new stream. Create a new + # Decompress object to decompress the new one. + # + # It's fine to ignore unconsumed_tail if unused_data is not + # empty. + self._unconsumed = self._decompress.unused_data + self.reset() + if size >= 0 and len(data) == size: + # data is filled. Don't call decompress again. + break + else: + # Re-invoke Decompress.decompress to try to decompress all + # available bytes before invoking read which blocks until + # any new byte is available. + continue + else: + # Here, since unused_data is empty, even if unconsumed_tail is + # not empty, bytes of requested length are already in data. We + # don't have to "continue" here. + break + + if data: + self._logger.debug('Decompressed %r', data) + return data + + def append(self, data): + self._logger.debug('Appended %r', data) + self._unconsumed += data + + def reset(self): + self._logger.debug('Reset') + self._decompress = zlib.decompressobj(-self._window_bits) + + +# Compresses/decompresses given octets using the method introduced in RFC1979. + + +class _RFC1979Deflater(object): + """A compressor class that applies DEFLATE to given byte sequence and + flushes using the algorithm described in the RFC1979 section 2.1. + """ + def __init__(self, window_bits, no_context_takeover): + self._deflater = None + if window_bits is None: + window_bits = zlib.MAX_WBITS + self._window_bits = window_bits + self._no_context_takeover = no_context_takeover + + def filter(self, bytes, end=True, bfinal=False): + if self._deflater is None: + self._deflater = _Deflater(self._window_bits) + + if bfinal: + result = self._deflater.compress_and_finish(bytes) + # Add a padding block with BFINAL = 0 and BTYPE = 0. + result = result + pack_byte(0) + self._deflater = None + return result + + result = self._deflater.compress_and_flush(bytes) + if end: + # Strip last 4 octets which is LEN and NLEN field of a + # non-compressed block added for Z_SYNC_FLUSH. + result = result[:-4] + + if self._no_context_takeover and end: + self._deflater = None + + return result + + +class _RFC1979Inflater(object): + """A decompressor class a la RFC1979. + + A decompressor class for byte sequence compressed and flushed following + the algorithm described in the RFC1979 section 2.1. + """ + def __init__(self, window_bits=zlib.MAX_WBITS): + self._inflater = _Inflater(window_bits) + + def filter(self, bytes): + # Restore stripped LEN and NLEN field of a non-compressed block added + # for Z_SYNC_FLUSH. + self._inflater.append(bytes + b'\x00\x00\xff\xff') + return self._inflater.decompress(-1) + + +class DeflateSocket(object): + """A wrapper class for socket object to intercept send and recv to perform + deflate compression and decompression transparently. + """ + + # Size of the buffer passed to recv to receive compressed data. + _RECV_SIZE = 4096 + + def __init__(self, socket): + self._socket = socket + + self._logger = get_class_logger(self) + + self._deflater = _Deflater(zlib.MAX_WBITS) + self._inflater = _Inflater(zlib.MAX_WBITS) + + def recv(self, size): + """Receives data from the socket specified on the construction up + to the specified size. Once any data is available, returns it even + if it's smaller than the specified size. + """ + + # TODO(tyoshino): Allow call with size=0. It should block until any + # decompressed data is available. + if size <= 0: + raise Exception('Non-positive size passed') + while True: + data = self._inflater.decompress(size) + if len(data) != 0: + return data + + read_data = self._socket.recv(DeflateSocket._RECV_SIZE) + if not read_data: + return b'' + self._inflater.append(read_data) + + def sendall(self, bytes): + self.send(bytes) + + def send(self, bytes): + self._socket.sendall(self._deflater.compress_and_flush(bytes)) + return len(bytes) + + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/w3c-import.log new file mode 100644 index 0000000000000..942503ca0ad1c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/w3c-import.log @@ -0,0 +1,30 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/_stream_exceptions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/common.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/dispatch.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/extensions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/http_header_util.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/memorizingfile.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/msgutil.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/request_handler.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/server_util.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/standalone.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/stream.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/util.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/websocket_server.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/websocket_server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/websocket_server.py new file mode 100644 index 0000000000000..dab2f079fff0a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/pywebsocket3/websocket_server.py @@ -0,0 +1,290 @@ +# Copyright 2020, Google Inc. +# 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 Google Inc. 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 +# 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""Standalone WebsocketServer + +This file deals with the main module of standalone server. Although it is fine +to import this file directly to use WebSocketServer, it is strongly recommended +to use standalone.py, since it is intended to act as a skeleton of this module. +""" + +from __future__ import absolute_import + +import logging +import re +import select +import socket +import ssl +import threading +import traceback + +from six.moves import BaseHTTPServer, socketserver + +from pywebsocket3 import dispatch, util +from pywebsocket3.request_handler import WebSocketRequestHandler + + +def _alias_handlers(dispatcher, websock_handlers_map_file): + """Set aliases specified in websock_handler_map_file in dispatcher. + + Args: + dispatcher: dispatch.Dispatcher instance + websock_handler_map_file: alias map file + """ + + with open(websock_handlers_map_file) as f: + for line in f: + if line[0] == '#' or line.isspace(): + continue + m = re.match(r'(\S+)\s+(\S+)$', line) + if not m: + logging.warning('Wrong format in map file:' + line) + continue + try: + dispatcher.add_resource_path_alias(m.group(1), m.group(2)) + except dispatch.DispatchException as e: + logging.error(str(e)) + + +class WebSocketServer(socketserver.ThreadingMixIn, BaseHTTPServer.HTTPServer): + """HTTPServer specialized for WebSocket.""" + + # Overrides SocketServer.ThreadingMixIn.daemon_threads + daemon_threads = True + # Overrides BaseHTTPServer.HTTPServer.allow_reuse_address + allow_reuse_address = True + + def __init__(self, options): + """Override SocketServer.TCPServer.__init__ to set SSL enabled + socket object to self.socket before server_bind and server_activate, + if necessary. + """ + + # Fall back to None for embedders that don't know about the + # handler_encoding option. + handler_encoding = getattr(options, "handler_encoding", None) + + # Share a Dispatcher among request handlers to save time for + # instantiation. Dispatcher can be shared because it is thread-safe. + options.dispatcher = dispatch.Dispatcher( + options.websock_handlers, options.scan_dir, + options.allow_handlers_outside_root_dir, handler_encoding) + if options.websock_handlers_map_file: + _alias_handlers(options.dispatcher, + options.websock_handlers_map_file) + warnings = options.dispatcher.source_warnings() + if warnings: + for warning in warnings: + logging.warning('Warning in source loading: %s' % warning) + + self._logger = util.get_class_logger(self) + + self.request_queue_size = options.request_queue_size + self.__ws_is_shut_down = threading.Event() + self.__ws_serving = False + + socketserver.BaseServer.__init__(self, + (options.server_host, options.port), + WebSocketRequestHandler) + + # Expose the options object to allow handler objects access it. We name + # it with websocket_ prefix to avoid conflict. + self.websocket_server_options = options + + self._create_sockets() + self.server_bind() + self.server_activate() + + def _create_sockets(self): + self.server_name, self.server_port = self.server_address + self._sockets = [] + if not self.server_name: + # On platforms that doesn't support IPv6, the first bind fails. + # On platforms that supports IPv6 + # - If it binds both IPv4 and IPv6 on call with AF_INET6, the + # first bind succeeds and the second fails (we'll see 'Address + # already in use' error). + # - If it binds only IPv6 on call with AF_INET6, both call are + # expected to succeed to listen both protocol. + addrinfo_array = [(socket.AF_INET6, socket.SOCK_STREAM, '', '', + ''), + (socket.AF_INET, socket.SOCK_STREAM, '', '', '')] + else: + addrinfo_array = socket.getaddrinfo(self.server_name, + self.server_port, + socket.AF_UNSPEC, + socket.SOCK_STREAM, + socket.IPPROTO_TCP) + for addrinfo in addrinfo_array: + self._logger.info('Create socket on: %r', addrinfo) + family, socktype, proto, canonname, sockaddr = addrinfo + try: + socket_ = socket.socket(family, socktype) + except Exception as e: + self._logger.info('Skip by failure: %r', e) + continue + server_options = self.websocket_server_options + if server_options.use_tls: + if server_options.tls_client_auth: + if server_options.tls_client_cert_optional: + client_cert_ = ssl.CERT_OPTIONAL + else: + client_cert_ = ssl.CERT_REQUIRED + else: + client_cert_ = ssl.CERT_NONE + ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ssl_context.verify_mode = client_cert_ + ssl_context.load_cert_chain(keyfile=server_options.private_key, + certfile=server_options.certificate) + if client_cert_ != ssl.CERT_NONE: + ssl_context.load_verify_locations(cafile=server_options.tls_client_ca) + socket_ = ssl_context.wrap_socket(socket_, server_side=True) + self._sockets.append((socket_, addrinfo)) + + def server_bind(self): + """Override SocketServer.TCPServer.server_bind to enable multiple + sockets bind. + """ + + failed_sockets = [] + + for socketinfo in self._sockets: + socket_, addrinfo = socketinfo + self._logger.info('Bind on: %r', addrinfo) + if self.allow_reuse_address: + socket_.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + socket_.bind(self.server_address) + except Exception as e: + self._logger.info('Skip by failure: %r', e) + socket_.close() + failed_sockets.append(socketinfo) + if self.server_address[1] == 0: + # The operating system assigns the actual port number for port + # number 0. This case, the second and later sockets should use + # the same port number. Also self.server_port is rewritten + # because it is exported, and will be used by external code. + self.server_address = (self.server_name, + socket_.getsockname()[1]) + self.server_port = self.server_address[1] + self._logger.info('Port %r is assigned', self.server_port) + + for socketinfo in failed_sockets: + self._sockets.remove(socketinfo) + + def server_activate(self): + """Override SocketServer.TCPServer.server_activate to enable multiple + sockets listen. + """ + + failed_sockets = [] + + for socketinfo in self._sockets: + socket_, addrinfo = socketinfo + self._logger.info('Listen on: %r', addrinfo) + try: + socket_.listen(self.request_queue_size) + except Exception as e: + self._logger.info('Skip by failure: %r', e) + socket_.close() + failed_sockets.append(socketinfo) + + for socketinfo in failed_sockets: + self._sockets.remove(socketinfo) + + if len(self._sockets) == 0: + self._logger.critical( + 'No sockets activated. Use info log level to see the reason.') + + def server_close(self): + """Override SocketServer.TCPServer.server_close to enable multiple + sockets close. + """ + + for socketinfo in self._sockets: + socket_, addrinfo = socketinfo + self._logger.info('Close on: %r', addrinfo) + socket_.close() + + def fileno(self): + """Override SocketServer.TCPServer.fileno.""" + + self._logger.critical('Not supported: fileno') + return self._sockets[0][0].fileno() + + def handle_error(self, request, client_address): + """Override SocketServer.handle_error.""" + + self._logger.error('Exception in processing request from: %r\n%s', + client_address, traceback.format_exc()) + # Note: client_address is a tuple. + + def get_request(self): + """Override TCPServer.get_request.""" + + accepted_socket, client_address = self.socket.accept() + + server_options = self.websocket_server_options + if server_options.use_tls: + # Print cipher in use. Handshake is done on accept. + self._logger.debug('Cipher: %s', accepted_socket.cipher()) + self._logger.debug('Client cert: %r', + accepted_socket.getpeercert()) + + return accepted_socket, client_address + + def serve_forever(self, poll_interval=0.5): + """Override SocketServer.BaseServer.serve_forever.""" + + self.__ws_serving = True + self.__ws_is_shut_down.clear() + handle_request = self.handle_request + if hasattr(self, '_handle_request_noblock'): + handle_request = self._handle_request_noblock + else: + self._logger.warning('Fallback to blocking request handler') + try: + while self.__ws_serving: + r, w, e = select.select( + [socket_[0] for socket_ in self._sockets], [], [], + poll_interval) + for socket_ in r: + self.socket = socket_ + handle_request() + self.socket = None + finally: + self.__ws_is_shut_down.set() + + def shutdown(self): + """Override SocketServer.BaseServer.shutdown.""" + + self.__ws_serving = False + self.__ws_is_shut_down.wait() + + +# vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/setup.cfg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/setup.cfg new file mode 100644 index 0000000000000..8bfd5a12f85b8 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/setup.cfg @@ -0,0 +1,4 @@ +[egg_info] +tag_build = +tag_date = 0 + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/setup.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/setup.py index 12c60d861790b..ab9a24a3e7626 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/setup.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/setup.py @@ -28,7 +28,8 @@ # 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. -"""Set up script for mod_pywebsocket. + +"""Set up script for pywebsocket3. """ from __future__ import absolute_import @@ -36,7 +37,7 @@ from setuptools import setup, Extension import sys -_PACKAGE_NAME = 'mod_pywebsocket' +_PACKAGE_NAME = 'pywebsocket3' # Build and use a C++ extension for faster masking. SWIG is required. _USE_FAST_MASKING = False @@ -49,8 +50,8 @@ if _USE_FAST_MASKING: setup(ext_modules=[ - Extension('mod_pywebsocket/_fast_masking', - ['mod_pywebsocket/fast_masking.i'], + Extension('pywebsocket3/_fast_masking', + ['pywebsocket3/fast_masking.i'], swig_opts=['-c++']) ]) @@ -58,16 +59,16 @@ author='Yuzo Fujishima', author_email='yuzo@chromium.org', description='Standalone WebSocket Server for testing purposes.', - long_description=('mod_pywebsocket is a standalone server for ' + long_description=('pywebsocket3 is a standalone server for ' 'the WebSocket Protocol (RFC 6455). ' - 'See mod_pywebsocket/__init__.py for more detail.'), + 'See pywebsocket3/__init__.py for more detail.'), license='See LICENSE', name=_PACKAGE_NAME, packages=[_PACKAGE_NAME, _PACKAGE_NAME + '.handshake'], python_requires='>=2.7', install_requires=['six'], url='https://github.com/GoogleChromeLabs/pywebsocket3', - version='3.0.2', + version='4.0.2', ) # vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/client_for_testing.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/client_for_testing.py index a45e8f5cf2de5..6275676371839 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/client_for_testing.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/client_for_testing.py @@ -33,8 +33,8 @@ This module contains helper methods for performing handshake, frame sending/receiving as a WebSocket client. -This is code for testing mod_pywebsocket. Keep this code independent from -mod_pywebsocket. Don't import e.g. Stream class for generating frame for +This is code for testing pywebsocket3. Keep this code independent from +pywebsocket3. Don't import e.g. Stream class for generating frame for testing. Using util.hexify, etc. that are not related to protocol processing is allowed. @@ -43,22 +43,20 @@ """ from __future__ import absolute_import + import base64 import errno -import logging import os -import random import re import socket import struct import time from hashlib import sha1 -from six import iterbytes -from six import indexbytes -from mod_pywebsocket import common -from mod_pywebsocket import util -from mod_pywebsocket.handshake import HandshakeException +from six import indexbytes, iterbytes + +from pywebsocket3 import common, util +from pywebsocket3.handshake import HandshakeException DEFAULT_PORT = 80 DEFAULT_SECURE_PORT = 443 @@ -702,15 +700,15 @@ def assert_connection_closed(self): try: read_data = receive_bytes(self._socket, 1) except Exception as e: - if str(e).find( - 'Connection closed before receiving requested length ' - ) == 0: + if str(e).find('Connection closed before receiving requested length ') == 0: return + try: - error_number, message = e for error_name in ['ECONNRESET', 'WSAECONNRESET']: - if (error_name in dir(errno) - and error_number == getattr(errno, error_name)): + if ( + error_name in dir(errno) and + e.errno == getattr(errno, error_name) + ): return except: raise e diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/mock.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/mock.py index eeaef52ecf019..c460d9b7f0319 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/mock.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/mock.py @@ -30,17 +30,14 @@ """ from __future__ import absolute_import -import six.moves.queue -import threading -import struct -import six -from mod_pywebsocket import common -from mod_pywebsocket import util -from mod_pywebsocket.stream import Stream -from mod_pywebsocket.stream import StreamOptions +import six +import six.moves.queue from six.moves import range +from pywebsocket3 import common, util +from pywebsocket3.stream import Stream, StreamOptions + class _MockConnBase(object): """Base class of mocks for mod_python.apache.mp_conn. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/run_all.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/run_all.py index ea52223cea6d1..569bdb4c07e81 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/run_all.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/run_all.py @@ -31,7 +31,7 @@ """Run all tests in the same directory. This suite is expected to be run under pywebsocket's src directory, i.e. the -directory containing mod_pywebsocket, test, etc. +directory containing pywebsocket3, test, etc. To change loggin level, please specify --log-level option. python test/run_test.py --log-level debug @@ -42,14 +42,16 @@ """ from __future__ import absolute_import -import logging + import argparse +import logging import os import re -import six import sys import unittest +import six + _TEST_MODULE_PATTERN = re.compile(r'^(test_.+)\.py$') diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/set_sys_path.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/set_sys_path.py index 48d0e116a5843..c35cb6f972149 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/set_sys_path.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/set_sys_path.py @@ -28,14 +28,15 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Configuration for testing. -Test files should import this module before mod_pywebsocket. +Test files should import this module before pywebsocket3. """ from __future__ import absolute_import + import os import sys -# Add the parent directory to sys.path to enable importing mod_pywebsocket. +# Add the parent directory to sys.path to enable importing pywebsocket3. sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) # vi:sts=4 sw=4 et diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_dispatch.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_dispatch.py index 132dd92d76cef..18a39d1af9df3 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_dispatch.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_dispatch.py @@ -31,15 +31,16 @@ """Tests for dispatch module.""" from __future__ import absolute_import + import os import unittest -import set_sys_path # Update sys.path to locate mod_pywebsocket module. +from six.moves import zip -from mod_pywebsocket import dispatch -from mod_pywebsocket import handshake +import set_sys_path # Update sys.path to locate pywebsocket3 module. +from pywebsocket3 import dispatch, handshake from test import mock -from six.moves import zip + _TEST_HANDLERS_DIR = os.path.join(os.path.dirname(__file__), 'testdata', 'handlers') diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_endtoend.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_endtoend.py index 2789e4a57e2e6..9718c6a2b2902 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_endtoend.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_endtoend.py @@ -28,23 +28,23 @@ # 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. -"""End-to-end tests for pywebsocket. Tests standalone.py. +"""End-to-end tests for pywebsocket3. Tests standalone.py. """ from __future__ import absolute_import -from six.moves import urllib + import locale import logging import os -import signal import socket import subprocess import sys import time import unittest -import set_sys_path # Update sys.path to locate mod_pywebsocket module. +from six.moves import urllib +import set_sys_path # Update sys.path to locate pywebsocket3 module. from test import client_for_testing # Special message that tells the echo server to start closing handshake @@ -137,7 +137,7 @@ def setUp(self): self.server_stderr = None self.top_dir = os.path.join(os.path.dirname(__file__), '..') os.putenv('PYTHONPATH', os.path.pathsep.join(sys.path)) - self.standalone_command = os.path.join(self.top_dir, 'mod_pywebsocket', + self.standalone_command = os.path.join(self.top_dir, 'pywebsocket3', 'standalone.py') self.document_root = os.path.join(self.top_dir, 'example') s = socket.socket() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_extensions.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_extensions.py index 39a111888be90..008df24827383 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_extensions.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_extensions.py @@ -31,13 +31,12 @@ """Tests for extensions module.""" from __future__ import absolute_import + import unittest import zlib -import set_sys_path # Update sys.path to locate mod_pywebsocket module. - -from mod_pywebsocket import common -from mod_pywebsocket import extensions +import set_sys_path # Update sys.path to locate pywebsocket3 module. +from pywebsocket3 import common, extensions class ExtensionsTest(unittest.TestCase): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_handshake.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_handshake.py index 7f4acf56ff064..c8e25ab0994ed 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_handshake.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_handshake.py @@ -31,16 +31,17 @@ """Tests for handshake.base module.""" from __future__ import absolute_import -import unittest -import set_sys_path # Update sys.path to locate mod_pywebsocket module. +import unittest -from mod_pywebsocket.common import ExtensionParameter -from mod_pywebsocket.common import ExtensionParsingException -from mod_pywebsocket.common import format_extensions -from mod_pywebsocket.common import parse_extensions -from mod_pywebsocket.handshake.base import HandshakeException -from mod_pywebsocket.handshake.base import validate_subprotocol +import set_sys_path # Update sys.path to locate pywebsocket3 module. +from pywebsocket3.common import ( + ExtensionParameter, + ExtensionParsingException, + format_extensions, + parse_extensions, +) +from pywebsocket3.handshake.base import HandshakeException, validate_subprotocol class ValidateSubprotocolTest(unittest.TestCase): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_handshake_hybi.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_handshake_hybi.py index 8c65822170346..ee63ed45b4950 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_handshake_hybi.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_handshake_hybi.py @@ -31,15 +31,17 @@ """Tests for handshake module.""" from __future__ import absolute_import -import unittest -import set_sys_path # Update sys.path to locate mod_pywebsocket module. -from mod_pywebsocket import common -from mod_pywebsocket.handshake.base import AbortedByUserException -from mod_pywebsocket.handshake.base import HandshakeException -from mod_pywebsocket.handshake.base import VersionException -from mod_pywebsocket.handshake.hybi import Handshaker +import unittest +import set_sys_path # Update sys.path to locate pywebsocket3 module. +from pywebsocket3 import common +from pywebsocket3.handshake.base import ( + AbortedByUserException, + HandshakeException, + VersionException, +) +from pywebsocket3.handshake.hybi import Handshaker from test import mock diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_http_header_util.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_http_header_util.py index f8c8e7a981bb0..bd9b9bfc2e66a 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_http_header_util.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_http_header_util.py @@ -31,10 +31,11 @@ """Tests for http_header_util module.""" from __future__ import absolute_import + import unittest import sys -from mod_pywebsocket import http_header_util +from pywebsocket3 import http_header_util class UnitTest(unittest.TestCase): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_memorizingfile.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_memorizingfile.py index f7288c510b89b..4749085962570 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_memorizingfile.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_memorizingfile.py @@ -31,12 +31,13 @@ """Tests for memorizingfile module.""" from __future__ import absolute_import + import unittest -import six -import set_sys_path # Update sys.path to locate mod_pywebsocket module. +import six -from mod_pywebsocket import memorizingfile +import set_sys_path # Update sys.path to locate pywebsocket3 module. +from pywebsocket3 import memorizingfile class UtilTest(unittest.TestCase): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_mock.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_mock.py index 073873dde97df..df5353bc5939d 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_mock.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_mock.py @@ -31,12 +31,13 @@ """Tests for mock module.""" from __future__ import absolute_import -import six.moves.queue + import threading import unittest -import set_sys_path # Update sys.path to locate mod_pywebsocket module. +import six.moves.queue +import set_sys_path # Update sys.path to locate pywebsocket3 module. from test import mock diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_msgutil.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_msgutil.py index 1122c281b7874..99aa200ba4843 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_msgutil.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_msgutil.py @@ -33,26 +33,26 @@ from __future__ import absolute_import from __future__ import print_function from __future__ import division -import array -import six.moves.queue + import random import struct import unittest import zlib -import set_sys_path # Update sys.path to locate mod_pywebsocket module. - -from mod_pywebsocket import common -from mod_pywebsocket.extensions import PerMessageDeflateExtensionProcessor -from mod_pywebsocket import msgutil -from mod_pywebsocket.stream import InvalidUTF8Exception -from mod_pywebsocket.stream import Stream -from mod_pywebsocket.stream import StreamOptions -from mod_pywebsocket import util -from test import mock +from six import iterbytes from six.moves import map from six.moves import range -from six import iterbytes +import six.moves.queue + +import set_sys_path # Update sys.path to locate pywebsocket3 module. +from pywebsocket3 import common, msgutil, util +from pywebsocket3.extensions import PerMessageDeflateExtensionProcessor +from pywebsocket3.stream import ( + InvalidUTF8Exception, + Stream, + StreamOptions, +) +from test import mock # We use one fixed nonce for testing instead of cryptographically secure PRNG. _MASKING_NONCE = b'ABCD' diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_stream.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_stream.py index 153899d20533e..c165e84688359 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_stream.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_stream.py @@ -31,12 +31,11 @@ """Tests for stream module.""" from __future__ import absolute_import -import unittest -import set_sys_path # Update sys.path to locate mod_pywebsocket module. +import unittest -from mod_pywebsocket import common -from mod_pywebsocket import stream +import set_sys_path # Update sys.path to locate pywebsocket3 module. +from pywebsocket3 import common, stream class StreamTest(unittest.TestCase): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_util.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_util.py index bf4bd32bbac30..24c4b5bfbdd6a 100755 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_util.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_util.py @@ -32,18 +32,16 @@ from __future__ import absolute_import from __future__ import print_function + import os import random -import sys import unittest -import struct - -import set_sys_path # Update sys.path to locate mod_pywebsocket module. -from mod_pywebsocket import util +from six import int2byte, PY3 from six.moves import range -from six import PY3 -from six import int2byte + +import set_sys_path # Update sys.path to locate pywebsocket3 module. +from pywebsocket3 import util _TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), 'testdata') diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/abort_by_user_wsh.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/abort_by_user_wsh.py index 63cb541bb7ec4..a6e0831847376 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/abort_by_user_wsh.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/abort_by_user_wsh.py @@ -27,7 +27,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from mod_pywebsocket import handshake +from pywebsocket3 import handshake def web_socket_do_extra_handshake(request): diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/sub/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/sub/w3c-import.log new file mode 100644 index 0000000000000..e365dbe32aac9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/sub/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/sub/exception_in_transfer_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/sub/no_wsh_at_the_end.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/sub/non_callable_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/sub/plain_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/w3c-import.log new file mode 100644 index 0000000000000..108c0f6572b12 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/abort_by_user_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/blank_wsh.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/testdata/handlers/origin_check_wsh.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/w3c-import.log new file mode 100644 index 0000000000000..e8f9eea1914b9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/w3c-import.log @@ -0,0 +1,32 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/client_for_testing.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/mock.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/run_all.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/set_sys_path.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_dispatch.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_endtoend.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_extensions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_handshake.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_handshake_hybi.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_http_header_util.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_memorizingfile.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_mock.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_msgutil.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_stream.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/test/test_util.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/w3c-import.log new file mode 100644 index 0000000000000..98eb54183669b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/MANIFEST.in +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/PKG-INFO +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/README.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/setup.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/pywebsocket3/setup.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/documentation/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/documentation/w3c-import.log new file mode 100644 index 0000000000000..c10d6f098e9de --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/documentation/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/documentation/Makefile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/documentation/conf.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/documentation/index.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/w3c-import.log new file mode 100644 index 0000000000000..eaf25ffc586ee --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/w3c-import.log @@ -0,0 +1,26 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/CHANGES +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/CONTRIBUTORS +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/MANIFEST.in +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/setup.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/setup.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/six.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/test_six.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/six/tox.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/tooltool/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/tooltool/w3c-import.log new file mode 100644 index 0000000000000..2f5168dcc2f53 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/tooltool/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/tooltool/tooltool.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/typing_extensions/src/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/typing_extensions/src/w3c-import.log new file mode 100644 index 0000000000000..17466d1e83b9b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/typing_extensions/src/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/typing_extensions/src/typing_extensions.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/typing_extensions/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/typing_extensions/w3c-import.log new file mode 100644 index 0000000000000..d4a6ca0b9cbbd --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/typing_extensions/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/typing_extensions/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/typing_extensions/PKG-INFO +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/typing_extensions/pyproject.toml diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/w3c-import.log new file mode 100644 index 0000000000000..32e82d2d5d480 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/PKG-INFO +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/setup.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/setup.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/webencodings.egg-info/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/webencodings.egg-info/w3c-import.log new file mode 100644 index 0000000000000..dfa67ca3cc08a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/webencodings.egg-info/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/webencodings.egg-info/PKG-INFO +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/webencodings.egg-info/SOURCES.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/webencodings.egg-info/dependency_links.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/webencodings.egg-info/top_level.txt diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/webencodings/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/webencodings/w3c-import.log new file mode 100644 index 0000000000000..22abde5b90c8d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/webencodings/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/webencodings/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/webencodings/labels.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/webencodings/mklabels.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/webencodings/tests.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/webencodings/webencodings/x_user_defined.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/FUNDING.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/FUNDING.yml new file mode 100644 index 0000000000000..c6c5426a5a6df --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/FUNDING.yml @@ -0,0 +1,3 @@ +github: python-websockets +open_collective: websockets +tidelift: pypi/websockets diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/ISSUE_TEMPLATE/config.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000000..3ba13e0cec6cb --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/ISSUE_TEMPLATE/issue.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/ISSUE_TEMPLATE/issue.md new file mode 100644 index 0000000000000..3cf4e3b77011e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/ISSUE_TEMPLATE/issue.md @@ -0,0 +1,29 @@ +--- +name: Report an issue +about: Let us know about a problem with websockets +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/ISSUE_TEMPLATE/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/ISSUE_TEMPLATE/w3c-import.log new file mode 100644 index 0000000000000..31776d14f2c09 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/ISSUE_TEMPLATE/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/ISSUE_TEMPLATE/config.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/ISSUE_TEMPLATE/issue.md diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/dependabot.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/dependabot.yml new file mode 100644 index 0000000000000..ad1e824b4a5bc --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "saturday" + time: "07:00" + timezone: "Europe/Paris" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/w3c-import.log new file mode 100644 index 0000000000000..bec0955d126d7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/FUNDING.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/dependabot.yml diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/workflows/tests.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/workflows/tests.yml new file mode 100644 index 0000000000000..470f5bc960631 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/workflows/tests.yml @@ -0,0 +1,83 @@ +name: Run tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + +env: + WEBSOCKETS_TESTS_TIMEOUT_FACTOR: 10 + +jobs: + coverage: + name: Run test coverage checks + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Install Python 3.x + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Install tox + run: pip install tox + - name: Run tests with coverage + run: tox -e coverage + - name: Run tests with per-module coverage + run: tox -e maxi_cov + + quality: + name: Run code quality checks + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Install Python 3.x + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Install tox + run: pip install tox + - name: Check code formatting + run: tox -e black + - name: Check code style + run: tox -e ruff + - name: Check types statically + run: tox -e mypy + + matrix: + name: Run tests on Python ${{ matrix.python }} + needs: + - coverage + - quality + runs-on: ubuntu-latest + strategy: + matrix: + python: + - "3.8" + - "3.9" + - "3.10" + - "3.11" + - "pypy-3.8" + - "pypy-3.9" + is_main: + - ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + exclude: + - python: "pypy-3.8" + is_main: false + - python: "pypy-3.9" + is_main: false + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Install Python ${{ matrix.python }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + - name: Install tox + run: pip install tox + - name: Run tests + run: tox -e py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/workflows/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/workflows/w3c-import.log new file mode 100644 index 0000000000000..905e303bcbd10 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/workflows/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/workflows/tests.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/workflows/wheels.yml diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/workflows/wheels.yml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/workflows/wheels.yml new file mode 100644 index 0000000000000..707ef2c60d1db --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/.github/workflows/wheels.yml @@ -0,0 +1,88 @@ +name: Build wheels + +on: + push: + tags: + - '*' + workflow_dispatch: + +jobs: + sdist: + name: Build source distribution and architecture-independent wheel + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Install Python 3.x + uses: actions/setup-python@v4 + with: + python-version: 3.x + - name: Build sdist + run: python setup.py sdist + - name: Save sdist + uses: actions/upload-artifact@v3 + with: + path: dist/*.tar.gz + - name: Install wheel + run: pip install wheel + - name: Build wheel + env: + BUILD_EXTENSION: no + run: python setup.py bdist_wheel + - name: Save wheel + uses: actions/upload-artifact@v3 + with: + path: dist/*.whl + + wheels: + name: Build architecture-specific wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + - windows-latest + - macOS-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Install Python 3.x + uses: actions/setup-python@v4 + with: + python-version: 3.x + - name: Set up QEMU + if: runner.os == 'Linux' + uses: docker/setup-qemu-action@v3 + with: + platforms: all + - name: Build wheels + uses: pypa/cibuildwheel@v2.16.2 + env: + BUILD_EXTENSION: yes + - name: Save wheels + uses: actions/upload-artifact@v3 + with: + path: wheelhouse/*.whl + + release: + name: Release + needs: + - sdist + - wheels + runs-on: ubuntu-latest + # Don't release when running the workflow manually from GitHub's UI. + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + steps: + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: artifact + path: dist + - name: Upload to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + - name: Create GitHub release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release create ${{ github.ref_name }} --notes "See https://websockets.readthedocs.io/en/stable/project/changelog.html for details." diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/CODE_OF_CONDUCT.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000..80f80d51b1151 --- /dev/null +++ b/LayoutTests/imported/w3c/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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/LICENSE b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/LICENSE index 119b29ef35b9f..5d61ece22a75a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/LICENSE +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/LICENSE @@ -1,5 +1,4 @@ -Copyright (c) 2013-2021 Aymeric Augustin and contributors. -All rights reserved. +Copyright (c) Aymeric Augustin and contributors Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -9,9 +8,9 @@ modification, are permitted provided that the following conditions are met: * 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. + * Neither the name of the copyright holder 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 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/Makefile b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/Makefile new file mode 100644 index 0000000000000..cf3b533939418 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/Makefile @@ -0,0 +1,35 @@ +.PHONY: default style types tests coverage maxi_cov build clean + +export PYTHONASYNCIODEBUG=1 +export PYTHONPATH=src +export PYTHONWARNINGS=default + +default: style types tests + +style: + black src tests + ruff --fix src tests + +types: + mypy --strict src + +tests: + python -m unittest + +coverage: + coverage run --source src/websockets,tests -m unittest + coverage html + coverage report --show-missing --fail-under=100 + +maxi_cov: + python tests/maxi_cov.py + coverage html + 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 .mypy_cache build compliance/reports dist docs/_build htmlcov MANIFEST src/websockets.egg-info diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/README.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/README.rst index 2b9a445ea5c9f..870b208baae0a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/README.rst +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/README.rst @@ -2,7 +2,7 @@ :width: 480px :alt: websockets -|licence| |version| |pyversions| |wheel| |tests| |docs| +|licence| |version| |pyversions| |tests| |docs| |openssf| .. |licence| image:: https://img.shields.io/pypi/l/websockets.svg :target: https://pypi.python.org/pypi/websockets @@ -13,15 +13,15 @@ .. |pyversions| image:: https://img.shields.io/pypi/pyversions/websockets.svg :target: https://pypi.python.org/pypi/websockets -.. |wheel| image:: https://img.shields.io/pypi/wheel/websockets.svg - :target: https://pypi.python.org/pypi/websockets - -.. |tests| image:: https://img.shields.io/github/checks-status/aaugustin/websockets/main - :target: https://github.com/aaugustin/websockets/actions/workflows/tests.yml +.. |tests| image:: https://img.shields.io/github/checks-status/python-websockets/websockets/main?label=tests + :target: https://github.com/python-websockets/websockets/actions/workflows/tests.yml .. |docs| image:: https://img.shields.io/readthedocs/websockets.svg :target: https://websockets.readthedocs.io/ +.. |openssf| image:: https://bestpractices.coreinfrastructure.org/projects/6475/badge + :target: https://bestpractices.coreinfrastructure.org/projects/6475 + What is ``websockets``? ----------------------- @@ -30,37 +30,24 @@ with a focus on correctness, simplicity, robustness, and performance. .. _WebSocket: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API -Built on top of ``asyncio``, Python's standard asynchronous I/O framework, it -provides an elegant coroutine-based API. +Built on top of ``asyncio``, Python's standard asynchronous I/O framework, the +default implementation provides an elegant coroutine-based API. -`Documentation is available on Read the Docs. `_ +An implementation on top of ``threading`` and a Sans-I/O implementation are also +available. -Here's how a client sends and receives messages: +`Documentation is available on Read the Docs. `_ .. copy-pasted because GitHub doesn't support the include directive -.. code:: python - - #!/usr/bin/env python - - import asyncio - from websockets import connect - - async def hello(uri): - async with connect(uri) as websocket: - await websocket.send("Hello world!") - await websocket.recv() - - asyncio.run(hello("ws://localhost:8765")) - -And here's an echo server: +Here's an echo server with the ``asyncio`` API: .. code:: python #!/usr/bin/env python import asyncio - from websockets import serve + from websockets.server import serve async def echo(websocket): async for message in websocket: @@ -72,6 +59,23 @@ And here's an echo server: asyncio.run(main()) +Here's how a client sends and receives messages with the ``threading`` API: + +.. code:: python + + #!/usr/bin/env python + + from websockets.sync.client import connect + + def hello(): + with connect("ws://localhost:8765") as websocket: + websocket.send("Hello world!") + message = websocket.recv() + print(f"Received: {message}") + + hello() + + Does that look good? `Get started with the tutorial! `_ @@ -79,7 +83,7 @@ Does that look good? .. raw:: html
    - +

    websockets for enterprise

    Available as part of the Tidelift Subscription

    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. Learn more.

    @@ -91,9 +95,8 @@ Why should I use ``websockets``? The development of ``websockets`` is shaped by four principles: -1. **Correctness**: ``websockets`` is heavily tested for compliance - with :rfc:`6455`. Continuous integration fails under 100% branch - coverage. +1. **Correctness**: ``websockets`` is heavily tested for compliance with + :rfc:`6455`. Continuous integration fails under 100% branch coverage. 2. **Simplicity**: all you need to understand is ``msg = await ws.recv()`` and ``await ws.send(msg)``. ``websockets`` takes care of managing connections @@ -123,7 +126,7 @@ Why shouldn't I use ``websockets``? * 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. + is minimal — just enough for an HTTP health check. If you want to do both in the same server, look at HTTP frameworks that build on top of ``websockets`` to support WebSocket connections, like @@ -143,13 +146,13 @@ contact`_. Tidelift will coordinate the fix and disclosure. 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/ +.. _issue: https://github.com/python-websockets/websockets/issues/new +.. _pull request: https://github.com/python-websockets/websockets/compare/ Participants must uphold the `Contributor Covenant code of conduct`_. -.. _Contributor Covenant code of conduct: https://github.com/aaugustin/websockets/blob/main/CODE_OF_CONDUCT.md +.. _Contributor Covenant code of conduct: https://github.com/python-websockets/websockets/blob/main/CODE_OF_CONDUCT.md ``websockets`` is released under the `BSD license`_. -.. _BSD license: https://github.com/aaugustin/websockets/blob/main/LICENSE +.. _BSD license: https://github.com/python-websockets/websockets/blob/main/LICENSE diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/SECURITY.md b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/SECURITY.md new file mode 100644 index 0000000000000..175b20c589ded --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/SECURITY.md @@ -0,0 +1,12 @@ +# Security + +## Policy + +Only the latest version receives security updates. + +## Contact information + +Please report security vulnerabilities to the +[Tidelift security team](https://tidelift.com/security). + +Tidelift will coordinate the fix and disclosure. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/README.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/README.rst new file mode 100644 index 0000000000000..8570f9176d56b --- /dev/null +++ b/LayoutTests/imported/w3c/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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/fuzzingclient.json b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/fuzzingclient.json new file mode 100644 index 0000000000000..202ff49a03a59 --- /dev/null +++ b/LayoutTests/imported/w3c/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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/fuzzingserver.json b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/fuzzingserver.json new file mode 100644 index 0000000000000..1bdb42723ef92 --- /dev/null +++ b/LayoutTests/imported/w3c/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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/test_client.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/test_client.py new file mode 100644 index 0000000000000..1ed4d711e925d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/test_client.py @@ -0,0 +1,48 @@ +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) + + +asyncio.run(run_tests(SERVER, urllib.parse.quote(AGENT))) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/test_server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/test_server.py new file mode 100644 index 0000000000000..92f895d92695e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/test_server.py @@ -0,0 +1,29 @@ +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): + async for msg in ws: + await ws.send(msg) + + +async def main(): + with websockets.serve(echo, HOST, PORT, max_size=2 ** 25, max_queue=1): + try: + await asyncio.Future() + except KeyboardInterrupt: + pass + + +asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/w3c-import.log new file mode 100644 index 0000000000000..24453a53271e4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/fuzzingclient.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/fuzzingserver.json +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/test_client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/compliance/test_server.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/Makefile b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/Makefile new file mode 100644 index 0000000000000..0458706458855 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/Makefile @@ -0,0 +1,23 @@ +# Minimal makefile for Sphinx documentation +# + +# 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 + +# 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) + +livehtml: + sphinx-autobuild --watch "$(SOURCEDIR)/../src" "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/_static/favicon.ico b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/_static/favicon.ico new file mode 100644 index 0000000000000..36e855029d705 Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/_static/favicon.ico differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/_static/tidelift.png b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/_static/tidelift.png new file mode 100644 index 0000000000000..317dc4d9852df Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/_static/tidelift.png differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/_static/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/_static/w3c-import.log new file mode 100644 index 0000000000000..16d76f7cd2f94 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/_static/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/_static/favicon.ico +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/_static/tidelift.png +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/_static/websockets.svg diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/_static/websockets.svg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/_static/websockets.svg new file mode 100644 index 0000000000000..b07fb22387385 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/_static/websockets.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/conf.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/conf.py new file mode 100644 index 0000000000000..9d61dc717369b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/conf.py @@ -0,0 +1,171 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +import datetime +import importlib +import inspect +import os +import subprocess +import sys + +# -- Path setup -------------------------------------------------------------- + +# 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")) + + +# -- Project information ----------------------------------------------------- + +project = "websockets" +copyright = f"2013-{datetime.date.today().year}, Aymeric Augustin and contributors" +author = "Aymeric Augustin" + +from websockets.version import tag as version, version as release + + +# -- General configuration --------------------------------------------------- + +nitpicky = True + +nitpick_ignore = [ + # topics/design.rst discusses undocumented APIs + ("py:meth", "client.WebSocketClientProtocol.handshake"), + ("py:meth", "server.WebSocketServerProtocol.handshake"), + ("py:attr", "legacy.protocol.WebSocketCommonProtocol.is_client"), + ("py:attr", "legacy.protocol.WebSocketCommonProtocol.messages"), + ("py:meth", "legacy.protocol.WebSocketCommonProtocol.close_connection"), + ("py:attr", "legacy.protocol.WebSocketCommonProtocol.close_connection_task"), + ("py:meth", "legacy.protocol.WebSocketCommonProtocol.keepalive_ping"), + ("py:attr", "legacy.protocol.WebSocketCommonProtocol.keepalive_ping_task"), + ("py:meth", "legacy.protocol.WebSocketCommonProtocol.transfer_data"), + ("py:attr", "legacy.protocol.WebSocketCommonProtocol.transfer_data_task"), + ("py:meth", "legacy.protocol.WebSocketCommonProtocol.connection_open"), + ("py:meth", "legacy.protocol.WebSocketCommonProtocol.ensure_open"), + ("py:meth", "legacy.protocol.WebSocketCommonProtocol.fail_connection"), + ("py:meth", "legacy.protocol.WebSocketCommonProtocol.connection_lost"), + ("py:meth", "legacy.protocol.WebSocketCommonProtocol.read_message"), + ("py:meth", "legacy.protocol.WebSocketCommonProtocol.write_frame"), +] + +# 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.linkcode", + "sphinx.ext.napoleon", + "sphinx_copybutton", + "sphinx_inline_tabs", + "sphinxcontrib.spelling", + "sphinxcontrib_trio", + "sphinxext.opengraph", +] +# It is currently inconvenient to install PyEnchant on Apple Silicon. +try: + import sphinxcontrib.spelling +except ImportError: + extensions.remove("sphinxcontrib.spelling") + +autodoc_typehints = "description" + +autodoc_typehints_description_target = "documented" + +# Workaround for https://github.com/sphinx-doc/sphinx/issues/9560 +from sphinx.domains.python import PythonDomain + +assert PythonDomain.object_types["data"].roles == ("data", "obj") +PythonDomain.object_types["data"].roles = ("data", "class", "obj") + +intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} + +spelling_show_suggestions = True + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# Configure viewcode extension. +from websockets.version import commit + +code_url = f"https://github.com/python-websockets/websockets/blob/{commit}" + +def linkcode_resolve(domain, info): + # Non-linkable objects from the starter kit in the tutorial. + if domain == "js" or info["module"] == "connect4": + return + + assert domain == "py", "expected only Python objects" + + mod = importlib.import_module(info["module"]) + if "." in info["fullname"]: + objname, attrname = info["fullname"].split(".") + obj = getattr(mod, objname) + try: + # object is a method of a class + obj = getattr(obj, attrname) + except AttributeError: + # object is an attribute of a class + return None + else: + obj = getattr(mod, info["fullname"]) + + try: + file = inspect.getsourcefile(obj) + lines = inspect.getsourcelines(obj) + except TypeError: + # e.g. object is a typing.Union + return None + file = os.path.relpath(file, os.path.abspath("..")) + if not file.startswith("src/websockets"): + # e.g. object is a typing.NewType + return None + start, end = lines[1], lines[1] + len(lines[0]) - 1 + + return f"{code_url}/{file}#L{start}-L{end}" + +# Configure opengraph extension + +# Social cards don't support the SVG logo. Also, the text preview looks bad. +ogp_social_cards = {"enable": False} + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = "furo" + +html_theme_options = { + "light_css_variables": { + "color-brand-primary": "#306998", # blue from logo + "color-brand-content": "#0b487a", # blue more saturated and less dark + }, + "dark_css_variables": { + "color-brand-primary": "#ffd43bcc", # yellow from logo, more muted than content + "color-brand-content": "#ffd43bd9", # yellow from logo, transparent like text + }, + "sidebar_hide_name": True, +} + +html_logo = "_static/websockets.svg" + +html_favicon = "_static/favicon.ico" + +# 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"] + +html_copy_source = False + +html_show_sphinx = False diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/asyncio.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/asyncio.rst new file mode 100644 index 0000000000000..e77f50adddcdb --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/asyncio.rst @@ -0,0 +1,69 @@ +Using asyncio +============= + +.. currentmodule:: websockets + +How do I run two coroutines in parallel? +---------------------------------------- + +You must start two tasks, which the event loop will run concurrently. You can +achieve this with :func:`asyncio.gather` or :func:`asyncio.create_task`. + +Keep track of the tasks and make sure they terminate or you cancel them when +the connection terminates. + +Why does my program never receive any messages? +----------------------------------------------- + +Your program runs a coroutine that never yields control to the event loop. The +coroutine that receives messages never gets a chance to run. + +Putting an ``await`` statement in a ``for`` or a ``while`` loop isn't enough +to yield control. Awaiting a coroutine may yield control, but there's no +guarantee that it will. + +For example, :meth:`~legacy.protocol.WebSocketCommonProtocol.send` only yields +control when send buffers are full, which never happens in most practical +cases. + +If you run a loop that contains only synchronous operations and +a :meth:`~legacy.protocol.WebSocketCommonProtocol.send` call, you must yield +control explicitly with :func:`asyncio.sleep`:: + + async def producer(websocket): + message = generate_next_message() + await websocket.send(message) + await asyncio.sleep(0) # yield control to the event loop + +:func:`asyncio.sleep` always suspends the current task, allowing other tasks +to run. This behavior is documented precisely because it isn't expected from +every coroutine. + +See `issue 867`_. + +.. _issue 867: https://github.com/python-websockets/websockets/issues/867 + +Why am I having problems with threads? +-------------------------------------- + +If you choose websockets' default implementation based on :mod:`asyncio`, then +you shouldn't use threads. Indeed, choosing :mod:`asyncio` to handle concurrency +is mutually exclusive with :mod:`threading`. + +If you believe that you need to run websockets in a thread and some logic in +another thread, you should run that logic in a :class:`~asyncio.Task` instead. +If it blocks the event loop, :meth:`~asyncio.loop.run_in_executor` will help. + +This question is really about :mod:`asyncio`. Please review the advice about +:ref:`asyncio-multithreading` in the Python documentation. + +Why does my simple program misbehave mysteriously? +-------------------------------------------------- + +You are using :func:`time.sleep` instead of :func:`asyncio.sleep`, which +blocks the event loop and prevents asyncio from operating normally. + +This may lead to messages getting send but not received, to connection +timeouts, and to unexpected results of shotgun debugging e.g. adding an +unnecessary call to :meth:`~legacy.protocol.WebSocketCommonProtocol.send` +makes the program functional. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/client.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/client.rst new file mode 100644 index 0000000000000..c590ac107db3a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/client.rst @@ -0,0 +1,101 @@ +Client +====== + +.. currentmodule:: websockets + +Why does the client close the connection prematurely? +----------------------------------------------------- + +You're exiting the context manager prematurely. Wait for the work to be +finished before exiting. + +For example, if your code has a structure similar to:: + + async with connect(...) as websocket: + asyncio.create_task(do_some_work()) + +change it to:: + + async with connect(...) as websocket: + await do_some_work() + +How do I access HTTP headers? +----------------------------- + +Once the connection is established, HTTP headers are available in +:attr:`~client.WebSocketClientProtocol.request_headers` and +:attr:`~client.WebSocketClientProtocol.response_headers`. + +How do I set HTTP headers? +-------------------------- + +To set the ``Origin``, ``Sec-WebSocket-Extensions``, or +``Sec-WebSocket-Protocol`` headers in the WebSocket handshake request, use the +``origin``, ``extensions``, or ``subprotocols`` arguments of +:func:`~client.connect`. + +To override the ``User-Agent`` header, use the ``user_agent_header`` argument. +Set it to :obj:`None` to remove the header. + +To set other HTTP headers, for example the ``Authorization`` header, use the +``extra_headers`` argument:: + + async with connect(..., extra_headers={"Authorization": ...}) as websocket: + ... + +In the :mod:`threading` API, this argument is named ``additional_headers``:: + + with connect(..., additional_headers={"Authorization": ...}) as websocket: + ... + +How do I force the IP address that the client connects to? +---------------------------------------------------------- + +Use the ``host`` argument of :meth:`~asyncio.loop.create_connection`:: + + await websockets.connect("ws://example.com", host="192.168.0.1") + +:func:`~client.connect` accepts the same arguments as +:meth:`~asyncio.loop.create_connection`. + +How do I close a connection? +---------------------------- + +The easiest is to use :func:`~client.connect` as a context manager:: + + async with connect(...) as websocket: + ... + +The connection is closed when exiting the context manager. + +How do I reconnect when the connection drops? +--------------------------------------------- + +Use :func:`~client.connect` as an asynchronous iterator:: + + async for websocket in websockets.connect(...): + try: + ... + except websockets.ConnectionClosed: + continue + +Make sure you handle exceptions in the ``async for`` loop. Uncaught exceptions +will break out of the loop. + +How do I stop a client that is processing messages in a loop? +------------------------------------------------------------- + +You can close the connection. + +Here's an example that terminates cleanly when it receives SIGTERM on Unix: + +.. literalinclude:: ../../example/faq/shutdown_client.py + :emphasize-lines: 10-13 + +How do I disable TLS/SSL certificate verification? +-------------------------------------------------- + +Look at the ``ssl`` argument of :meth:`~asyncio.loop.create_connection`. + +:func:`~client.connect` accepts the same arguments as +:meth:`~asyncio.loop.create_connection`. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/common.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/common.rst new file mode 100644 index 0000000000000..2c63c4f36f115 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/common.rst @@ -0,0 +1,161 @@ +Both sides +========== + +.. currentmodule:: websockets + +What does ``ConnectionClosedError: no close frame received or sent`` mean? +-------------------------------------------------------------------------- + +If you're seeing this traceback in the logs of a server: + +.. code-block:: pytb + + connection handler failed + Traceback (most recent call last): + ... + asyncio.exceptions.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: no close frame received or sent + +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: no close frame received or sent + +it means that the TCP connection was lost. As a consequence, the WebSocket +connection was closed without receiving and sending 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, despite websockets' keepalive mechanism. + +If you're facing a reproducible issue, :ref:`enable debug logs ` to +see when and how connections are closed. + +What does ``ConnectionClosedError: sent 1011 (internal error) keepalive ping timeout; no close frame received`` mean? +--------------------------------------------------------------------------------------------------------------------- + +If you're seeing this traceback in the logs of a server: + +.. code-block:: pytb + + connection handler failed + Traceback (most recent call last): + ... + asyncio.exceptions.CancelledError + + The above exception was the direct cause of the following exception: + + Traceback (most recent call last): + ... + websockets.exceptions.ConnectionClosedError: sent 1011 (internal error) keepalive ping timeout; no close frame received + +or if a client crashes with this traceback: + +.. code-block:: pytb + + Traceback (most recent call last): + ... + asyncio.exceptions.CancelledError + + The above exception was the direct cause of the following exception: + + Traceback (most recent call last): + ... + websockets.exceptions.ConnectionClosedError: sent 1011 (internal error) keepalive ping timeout; no close frame received + +it means that the WebSocket connection suffered from excessive latency and was +closed after reaching the timeout of websockets' keepalive mechanism. + +You can catch and handle :exc:`~exceptions.ConnectionClosed` to prevent it +from being logged. + +There are two main reasons why latency may increase: + +* Poor network connectivity. +* More traffic than the recipient can handle. + +See the discussion of :doc:`timeouts <../topics/timeouts>` for details. + +If websockets' default timeout of 20 seconds is too short for your use case, +you can adjust it with the ``ping_timeout`` argument. + +How do I set a timeout on :meth:`~legacy.protocol.WebSocketCommonProtocol.recv`? +-------------------------------------------------------------------------------- + +On Python ≥ 3.11, use :func:`asyncio.timeout`:: + + async with asyncio.timeout(timeout=10): + message = await websocket.recv() + +On older versions of Python, use :func:`asyncio.wait_for`:: + + message = await asyncio.wait_for(websocket.recv(), timeout=10) + +This technique works for most APIs. When it doesn't, for example with +asynchronous context managers, websockets provides an ``open_timeout`` argument. + +How can I pass arguments to a custom protocol subclass? +------------------------------------------------------- + +You can bind additional arguments to the protocol factory with +:func:`functools.partial`:: + + import asyncio + import functools + import websockets + + class MyServerProtocol(websockets.WebSocketServerProtocol): + def __init__(self, *args, extra_argument=None, **kwargs): + super().__init__(*args, **kwargs) + # do something with extra_argument + + create_protocol = functools.partial(MyServerProtocol, extra_argument=42) + start_server = websockets.serve(..., create_protocol=create_protocol) + +This example was for a server. The same pattern applies on a client. + +How do I keep idle connections open? +------------------------------------ + +websockets sends pings at 20 seconds intervals to keep the connection open. + +It closes the connection if it doesn't get a pong within 20 seconds. + +You can adjust this behavior with ``ping_interval`` and ``ping_timeout``. + +See :doc:`../topics/timeouts` for details. + +How do I respond to pings? +-------------------------- + +If you are referring to Ping_ and Pong_ frames defined in the WebSocket +protocol, don't bother, because websockets handles them for you. + +.. _Ping: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.5.2 +.. _Pong: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.5.3 + +If you are connecting to a server that defines its own heartbeat at the +application level, then you need to build that logic into your application. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/index.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/index.rst new file mode 100644 index 0000000000000..9d5b0d538ac77 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/index.rst @@ -0,0 +1,21 @@ +Frequently asked questions +========================== + +.. currentmodule:: websockets + +.. admonition:: Many questions asked in websockets' issue tracker are really + about :mod:`asyncio`. + :class: seealso + + Python's documentation about `developing with asyncio`_ is a good + complement. + + .. _developing with asyncio: https://docs.python.org/3/library/asyncio-dev.html + +.. toctree:: + + server + client + common + asyncio + misc diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/misc.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/misc.rst new file mode 100644 index 0000000000000..ee5ad23728e14 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/misc.rst @@ -0,0 +1,49 @@ +Miscellaneous +============= + +.. currentmodule:: websockets + +Why do I get the error: ``module 'websockets' has no attribute '...'``? +....................................................................... + +Often, this is because you created a script called ``websockets.py`` in your +current working directory. Then ``import websockets`` imports this module +instead of the websockets library. + +.. _real-import-paths: + +Why is the default implementation located in ``websockets.legacy``? +................................................................... + +This is an artifact of websockets' history. For its first eight years, only the +:mod:`asyncio` implementation existed. Then, the Sans-I/O implementation was +added. Moving the code in a ``legacy`` submodule eased this refactoring and +optimized maintainability. + +All public APIs were kept at their original locations. ``websockets.legacy`` +isn't a public API. It's only visible in the source code and in stack traces. +There is no intent to deprecate this implementation — at least until a superior +alternative exists. + +Why is websockets slower than another library in my benchmark? +.............................................................. + +Not all libraries are as feature-complete as websockets. For a fair benchmark, +you should disable features that the other library doesn't provide. Typically, +you may need to disable: + +* Compression: set ``compression=None`` +* Keepalive: set ``ping_interval=None`` +* UTF-8 decoding: send ``bytes`` rather than ``str`` + +If websockets is still slower than another Python library, please file a bug. + +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. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/server.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/server.rst new file mode 100644 index 0000000000000..08b412d306b95 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/server.rst @@ -0,0 +1,336 @@ +Server +====== + +.. currentmodule:: websockets + +Why does the server close the connection prematurely? +----------------------------------------------------- + +Your connection handler exits prematurely. Wait for the work to be finished +before returning. + +For example, if your handler has a structure similar to:: + + async def handler(websocket): + asyncio.create_task(do_some_work()) + +change it to:: + + async def handler(websocket): + await do_some_work() + +Why does the server close the connection after 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): + print(websocket.recv()) + +change it like this:: + + async def handler(websocket): + 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, this connection handler prevents the event loop from running during +one second:: + + async def handler(websocket): + time.sleep(1) + ... + +Change it to:: + + async def handler(websocket): + await asyncio.sleep(1) + ... + +In addition, calling a coroutine doesn't guarantee that it will yield control to +the event loop. + +For example, this connection handler blocks the event loop by sending messages +continuously:: + + async def handler(websocket): + while True: + await websocket.send("firehose!") + +:meth:`~legacy.protocol.WebSocketCommonProtocol.send` completes synchronously as +long as there's space in send buffers. The event loop never runs. (This pattern +is uncommon in real-world applications. It occurs mostly in toy programs.) + +You can avoid the issue by yielding control to the event loop explicitly:: + + async def handler(websocket): + while True: + await websocket.send("firehose!") + await asyncio.sleep(0) + +All 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 + +.. _send-message-to-all-users: + +How do I send a message to all users? +------------------------------------- + +Record all connections in a global variable:: + + CONNECTIONS = set() + + async def handler(websocket): + CONNECTIONS.add(websocket) + try: + await websocket.wait_closed() + finally: + CONNECTIONS.remove(websocket) + +Then, call :func:`~websockets.broadcast`:: + + import websockets + + def message_all(message): + websockets.broadcast(CONNECTIONS, message) + +If you're running multiple server processes, make sure you call ``message_all`` +in each process. + +.. _send-message-to-single-user: + +How do I send a message to a single user? +----------------------------------------- + +Record connections in a global variable, keyed by user identifier:: + + CONNECTIONS = {} + + async def handler(websocket): + user_id = ... # identify user in your app's context + CONNECTIONS[user_id] = websocket + try: + await websocket.wait_closed() + finally: + del CONNECTIONS[user_id] + +Then, call :meth:`~legacy.protocol.WebSocketCommonProtocol.send`:: + + async def message_user(user_id, message): + websocket = CONNECTIONS[user_id] # raises KeyError if user disconnected + await websocket.send(message) # may raise websockets.ConnectionClosed + +Add error handling according to the behavior you want if the user disconnected +before the message could be sent. + +This example supports only one connection per user. To support concurrent +connections by the same user, you can change ``CONNECTIONS`` to store a set of +connections for each user. + +If you're running multiple server processes, call ``message_user`` in each +process. The process managing the user's connection sends the message; other +processes do nothing. + +When you reach a scale where server processes cannot keep up with the stream of +all messages, you need a better architecture. For example, you could deploy an +external publish / subscribe system such as Redis_. Server processes would +subscribe their clients. Then, they would receive messages only for the +connections that they're managing. + +.. _Redis: https://redis.io/ + +How do I send a message to a channel, a topic, or some users? +------------------------------------------------------------- + +websockets doesn't provide built-in publish / subscribe functionality. + +Record connections in a global variable, keyed by user identifier, as shown in +:ref:`How do I send a message to a single user?` + +Then, build the set of recipients and broadcast the message to them, as shown in +:ref:`How do I send a message to all users?` + +:doc:`../howto/django` contains a complete implementation of this pattern. + +Again, as you scale, you may reach the performance limits of a basic in-process +implementation. You may need an external publish / subscribe system like Redis_. + +.. _Redis: https://redis.io/ + +How do I pass arguments to the connection handler? +-------------------------------------------------- + +You can bind additional arguments to the connection handler with +:func:`functools.partial`:: + + import asyncio + import functools + import websockets + + async def handler(websocket, extra_argument): + ... + + bound_handler = functools.partial(handler, extra_argument=42) + start_server = websockets.serve(bound_handler, ...) + +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. + +How do I access the request path? +--------------------------------- + +It is available in the :attr:`~server.WebSocketServerProtocol.path` attribute. + +You may route a connection to different handlers depending on the request path:: + + async def handler(websocket): + if websocket.path == "/blue": + await blue_handler(websocket) + elif websocket.path == "/green": + await green_handler(websocket) + else: + # No handler for this path; close the connection. + return + +You may also route the connection based on the first message received from the +client, as shown in the :doc:`tutorial <../intro/tutorial2>`. When you want to +authenticate the connection before routing it, this is usually more convenient. + +Generally speaking, there is far less emphasis on the request path in WebSocket +servers than in HTTP servers. When a WebSocket server provides a single endpoint, +it may ignore the request path entirely. + +How do I access HTTP headers? +----------------------------- + +To access HTTP headers during the WebSocket handshake, you can override +:attr:`~server.WebSocketServerProtocol.process_request`:: + + async def process_request(self, path, request_headers): + authorization = request_headers["Authorization"] + +Once the connection is established, HTTP headers are available in +:attr:`~server.WebSocketServerProtocol.request_headers` and +:attr:`~server.WebSocketServerProtocol.response_headers`:: + + async def handler(websocket): + authorization = websocket.request_headers["Authorization"] + +How do I set HTTP headers? +-------------------------- + +To set the ``Sec-WebSocket-Extensions`` or ``Sec-WebSocket-Protocol`` headers in +the WebSocket handshake response, use the ``extensions`` or ``subprotocols`` +arguments of :func:`~server.serve`. + +To override the ``Server`` header, use the ``server_header`` argument. Set it to +:obj:`None` to remove the header. + +To set other HTTP headers, use the ``extra_headers`` argument. + +How do I get the IP address of the client? +------------------------------------------ + +It's available in :attr:`~legacy.protocol.WebSocketCommonProtocol.remote_address`:: + + async def handler(websocket): + remote_ip = websocket.remote_address[0] + +How do I set the IP addresses that my server listens on? +-------------------------------------------------------- + +Use the ``host`` argument of :meth:`~asyncio.loop.create_server`:: + + await websockets.serve(handler, host="192.168.0.1", port=8080) + +:func:`~server.serve` accepts the same arguments as +:meth:`~asyncio.loop.create_server`. + +What does ``OSError: [Errno 99] error while attempting to bind on address ('::1', 80, 0, 0): address not available`` mean? +-------------------------------------------------------------------------------------------------------------------------- + +You are calling :func:`~server.serve` without a ``host`` argument in a context +where IPv6 isn't available. + +To listen only on IPv4, specify ``host="0.0.0.0"`` or ``family=socket.AF_INET``. + +Refer to the documentation of :meth:`~asyncio.loop.create_server` for details. + +How do I close a connection? +---------------------------- + +websockets takes care of closing the connection when the handler exits. + +How do I stop a server? +----------------------- + +Exit the :func:`~server.serve` context manager. + +Here's an example that terminates cleanly when it receives SIGTERM on Unix: + +.. literalinclude:: ../../example/faq/shutdown_server.py + :emphasize-lines: 12-15,18 + +How do I stop a server while keeping existing connections open? +--------------------------------------------------------------- + +Call the server's :meth:`~server.WebSocketServer.close` method with +``close_connections=False``. + +Here's how to adapt the example just above:: + + async def server(): + ... + + server = await websockets.serve(echo, "localhost", 8765) + await stop + await server.close(close_connections=False) + +How do I implement a health check? +---------------------------------- + +Intercept WebSocket handshake requests with the +:meth:`~server.WebSocketServerProtocol.process_request` hook. + +When a request is sent to the health check endpoint, treat is as an HTTP request +and return a ``(status, headers, body)`` tuple, as in this example: + +.. literalinclude:: ../../example/faq/health_check_server.py + :emphasize-lines: 7-9,18 + +How do I run HTTP and WebSocket servers on the same port? +--------------------------------------------------------- + +You don't. + +HTTP and WebSocket have widely different operational characteristics. Running +them with the same server becomes inconvenient when you scale. + +Providing an 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 an HTTP server and run it separately. + +Alternatively, pick an HTTP framework that builds on top of ``websockets`` to +support WebSocket connections, like Sanic_. + +.. _Sanic: https://sanicframework.org/en/ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/w3c-import.log new file mode 100644 index 0000000000000..c4e053a58f672 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/asyncio.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/client.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/common.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/misc.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/faq/server.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/autoreload.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/autoreload.rst new file mode 100644 index 0000000000000..fc736a59186c4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/autoreload.rst @@ -0,0 +1,31 @@ +Reload on code changes +====================== + +When developing a websockets server, you may run it locally to test changes. +Unfortunately, whenever you want to try a new version of the code, you must +stop the server and restart it, which slows down your development process. + +Web frameworks such as Django or Flask provide a development server that +reloads the application automatically when you make code changes. There is no +such functionality in websockets because it's designed for production rather +than development. + +However, you can achieve the same result easily. + +Install watchdog_ with the ``watchmedo`` shell utility: + +.. code-block:: console + + $ pip install 'watchdog[watchmedo]' + +.. _watchdog: https://pypi.org/project/watchdog/ + +Run your server with ``watchmedo auto-restart``: + +.. code-block:: console + + $ watchmedo auto-restart --pattern "*.py" --recursive --signal SIGTERM \ + python app.py + +This example assumes that the server is defined in a script called ``app.py``. +Adapt it as necessary. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/cheatsheet.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/cheatsheet.rst new file mode 100644 index 0000000000000..95b551f6731dd --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/cheatsheet.rst @@ -0,0 +1,87 @@ +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:`~legacy.protocol.WebSocketCommonProtocol.recv` and + :meth:`~legacy.protocol.WebSocketCommonProtocol.send` to receive and send + messages at any time. + + * When :meth:`~legacy.protocol.WebSocketCommonProtocol.recv` or + :meth:`~legacy.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:`~legacy.protocol.WebSocketCommonProtocol.recv`, + consider awaiting :meth:`~legacy.protocol.WebSocketCommonProtocol.wait_closed` + to detect quickly when the connection is closed. + + * You may :meth:`~legacy.protocol.WebSocketCommonProtocol.ping` or + :meth:`~legacy.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.loop.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.loop.create_connection`. You can also use it as an + asynchronous context manager. + + * For advanced customization, you may subclass + :class:`~client.WebSocketClientProtocol` and pass either this subclass or + a factory function as the ``create_protocol`` argument. + +* Call :meth:`~legacy.protocol.WebSocketCommonProtocol.recv` and + :meth:`~legacy.protocol.WebSocketCommonProtocol.send` to receive and send messages + at any time. + +* You may :meth:`~legacy.protocol.WebSocketCommonProtocol.ping` or + :meth:`~legacy.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:`~legacy.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.DEBUG) + 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 + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/django.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/django.rst new file mode 100644 index 0000000000000..e3da0a878b83d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/django.rst @@ -0,0 +1,294 @@ +Integrate with Django +===================== + +If you're looking at adding real-time capabilities to a Django project with +WebSocket, you have two main options. + +1. Using Django Channels_, a project adding WebSocket to Django, among other + features. This approach is fully supported by Django. However, it requires + switching to a new deployment architecture. + +2. Deploying a separate WebSocket server next to your Django project. This + technique is well suited when you need to add a small set of real-time + features — maybe a notification service — to an HTTP application. + +.. _Channels: https://channels.readthedocs.io/ + +This guide shows how to implement the second technique with websockets. It +assumes familiarity with Django. + +Authenticate connections +------------------------ + +Since the websockets server runs outside of Django, we need to integrate it +with ``django.contrib.auth``. + +We will generate authentication tokens in the Django project. Then we will +send them to the websockets server, where they will authenticate the user. + +Generating a token for the current user and making it available in the browser +is up to you. You could render the token in a template or fetch it with an API +call. + +Refer to the topic guide on :doc:`authentication <../topics/authentication>` +for details on this design. + +Generate tokens +............... + +We want secure, short-lived tokens containing the user ID. We'll rely on +`django-sesame`_, a small library designed exactly for this purpose. + +.. _django-sesame: https://github.com/aaugustin/django-sesame + +Add django-sesame to the dependencies of your Django project, install it, and +configure it in the settings of the project: + +.. code-block:: python + + AUTHENTICATION_BACKENDS = [ + "django.contrib.auth.backends.ModelBackend", + "sesame.backends.ModelBackend", + ] + +(If your project already uses another authentication backend than the default +``"django.contrib.auth.backends.ModelBackend"``, adjust accordingly.) + +You don't need ``"sesame.middleware.AuthenticationMiddleware"``. It is for +authenticating users in the Django server, while we're authenticating them in +the websockets server. + +We'd like our tokens to be valid for 30 seconds. We expect web pages to load +and to establish the WebSocket connection within this delay. Configure +django-sesame accordingly in the settings of your Django project: + +.. code-block:: python + + SESAME_MAX_AGE = 30 + +If you expect your web site to load faster for all clients, a shorter lifespan +is possible. However, in the context of this document, it would make manual +testing more difficult. + +You could also enable single-use tokens. However, this would update the last +login date of the user every time a WebSocket connection is established. This +doesn't seem like a good idea, both in terms of behavior and in terms of +performance. + +Now you can generate tokens in a ``django-admin shell`` as follows: + +.. code-block:: pycon + + >>> from django.contrib.auth import get_user_model + >>> User = get_user_model() + >>> user = User.objects.get(username="") + >>> from sesame.utils import get_token + >>> get_token(user) + '' + +Keep this console open: since tokens expire after 30 seconds, you'll have to +generate a new token every time you want to test connecting to the server. + +Validate tokens +............... + +Let's move on to the websockets server. + +Add websockets to the dependencies of your Django project and install it. +Indeed, we're going to reuse the environment of the Django project, so we can +call its APIs in the websockets server. + +Now here's how to implement authentication. + +.. literalinclude:: ../../example/django/authentication.py + +Let's unpack this code. + +We're calling ``django.setup()`` before doing anything with Django because +we're using Django in a `standalone script`_. This assumes that the +``DJANGO_SETTINGS_MODULE`` environment variable is set to the Python path to +your settings module. + +.. _standalone script: https://docs.djangoproject.com/en/stable/topics/settings/#calling-django-setup-is-required-for-standalone-django-usage + +The connection handler reads the first message received from the client, which +is expected to contain a django-sesame token. Then it authenticates the user +with ``get_user()``, the API for `authentication outside a view`_. If +authentication fails, it closes the connection and exits. + +.. _authentication outside a view: https://django-sesame.readthedocs.io/en/stable/howto.html#outside-a-view + +When we call an API that makes a database query such as ``get_user()``, we +wrap the call in :func:`~asyncio.to_thread`. Indeed, the Django ORM doesn't +support asynchronous I/O. It would block the event loop if it didn't run in a +separate thread. :func:`~asyncio.to_thread` is available since Python 3.9. In +earlier versions, use :meth:`~asyncio.loop.run_in_executor` instead. + +Finally, we start a server with :func:`~websockets.server.serve`. + +We're ready to test! + +Save this code to a file called ``authentication.py``, make sure the +``DJANGO_SETTINGS_MODULE`` environment variable is set properly, and start the +websockets server: + +.. code-block:: console + + $ python authentication.py + +Generate a new token — remember, they're only valid for 30 seconds — and use +it to connect to your server. Paste your token and press Enter when you get a +prompt: + +.. code-block:: console + + $ python -m websockets ws://localhost:8888/ + Connected to ws://localhost:8888/ + > + < Hello ! + Connection closed: 1000 (OK). + +It works! + +If you enter an expired or invalid token, authentication fails and the server +closes the connection: + +.. code-block:: console + + $ python -m websockets ws://localhost:8888/ + Connected to ws://localhost:8888. + > not a token + Connection closed: 1011 (internal error) authentication failed. + +You can also test from a browser by generating a new token and running the +following code in the JavaScript console of the browser: + +.. code-block:: javascript + + websocket = new WebSocket("ws://localhost:8888/"); + websocket.onopen = (event) => websocket.send(""); + websocket.onmessage = (event) => console.log(event.data); + +If you don't want to import your entire Django project into the websockets +server, you can build a separate Django project with ``django.contrib.auth``, +``django-sesame``, a suitable ``User`` model, and a subset of the settings of +the main project. + +Stream events +------------- + +We can connect and authenticate but our server doesn't do anything useful yet! + +Let's send a message every time a user makes an action in the admin. This +message will be broadcast to all users who can access the model on which the +action was made. This may be used for showing notifications to other users. + +Many use cases for WebSocket with Django follow a similar pattern. + +Set up event bus +................ + +We need a event bus to enable communications between Django and websockets. +Both sides connect permanently to the bus. Then Django writes events and +websockets reads them. For the sake of simplicity, we'll rely on `Redis +Pub/Sub`_. + +.. _Redis Pub/Sub: https://redis.io/topics/pubsub + +The easiest way to add Redis to a Django project is by configuring a cache +backend with `django-redis`_. This library manages connections to Redis +efficiently, persisting them between requests, and provides an API to access +the Redis connection directly. + +.. _django-redis: https://github.com/jazzband/django-redis + +Install Redis, add django-redis to the dependencies of your Django project, +install it, and configure it in the settings of the project: + +.. code-block:: python + + CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://127.0.0.1:6379/1", + }, + } + +If you already have a default cache, add a new one with a different name and +change ``get_redis_connection("default")`` in the code below to the same name. + +Publish events +.............. + +Now let's write events to the bus. + +Add the following code to a module that is imported when your Django project +starts. Typically, you would put it in a ``signals.py`` module, which you +would import in the ``AppConfig.ready()`` method of one of your apps: + +.. literalinclude:: ../../example/django/signals.py + +This code runs every time the admin saves a ``LogEntry`` object to keep track +of a change. It extracts interesting data, serializes it to JSON, and writes +an event to Redis. + +Let's check that it works: + +.. code-block:: console + + $ redis-cli + 127.0.0.1:6379> SELECT 1 + OK + 127.0.0.1:6379[1]> SUBSCRIBE events + Reading messages... (press Ctrl-C to quit) + 1) "subscribe" + 2) "events" + 3) (integer) 1 + +Leave this command running, start the Django development server and make +changes in the admin: add, modify, or delete objects. You should see +corresponding events published to the ``"events"`` stream. + +Broadcast events +................ + +Now let's turn to reading events and broadcasting them to connected clients. +We need to add several features: + +* Keep track of connected clients so we can broadcast messages. +* Tell which content types the user has permission to view or to change. +* Connect to the message bus and read events. +* Broadcast these events to users who have corresponding permissions. + +Here's a complete implementation. + +.. literalinclude:: ../../example/django/notifications.py + +Since the ``get_content_types()`` function makes a database query, it is +wrapped inside :func:`asyncio.to_thread()`. It runs once when each WebSocket +connection is open; then its result is cached for the lifetime of the +connection. Indeed, running it for each message would trigger database queries +for all connected users at the same time, which would hurt the database. + +The connection handler merely registers the connection in a global variable, +associated to the list of content types for which events should be sent to +that connection, and waits until the client disconnects. + +The ``process_events()`` function reads events from Redis and broadcasts them +to all connections that should receive them. We don't care much if a sending a +notification fails — this happens when a connection drops between the moment +we iterate on connections and the moment the corresponding message is sent — +so we start a task with for each message and forget about it. Also, this means +we're immediately ready to process the next event, even if it takes time to +send a message to a slow client. + +Since Redis can publish a message to multiple subscribers, multiple instances +of this server can safely run in parallel. + +Does it scale? +-------------- + +In theory, given enough servers, this design can scale to a hundred million +clients, since Redis can handle ten thousand servers and each server can +handle ten thousand clients. In practice, you would need a more scalable +message bus before reaching that scale, due to the volume of messages. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/extensions.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/extensions.rst new file mode 100644 index 0000000000000..3c8a7d72a64a8 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/extensions.rst @@ -0,0 +1,30 @@ +Write an extension +================== + +.. currentmodule:: websockets.extensions + +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 being sent or after being received. + +.. _extensions: https://www.rfc-editor.org/rfc/rfc6455.html#section-9 + +As a consequence, writing an extension requires implementing several classes: + +* Extension Factory: it negotiates parameters and instantiates the extension. + + Clients and servers require separate extension factories with distinct APIs. + + Extension factories are the public API of an extension. + +* Extension: it decodes incoming frames and encodes outgoing frames. + + If the extension is symmetrical, clients and servers can use the same + class. + + Extensions are initialized by extension factories, so they don't need to be + part of the public API of an extension. + +websockets provides base classes for extension factories and extensions. +See :class:`ClientExtensionFactory`, :class:`ServerExtensionFactory`, +and :class:`Extension` for details. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/fly.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/fly.rst new file mode 100644 index 0000000000000..ed001a2aeed9f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/fly.rst @@ -0,0 +1,177 @@ +Deploy to Fly +================ + +This guide describes how to deploy a websockets server to Fly_. + +.. _Fly: https://fly.io/ + +.. admonition:: The free tier of Fly is sufficient for trying this guide. + :class: tip + + The `free tier`__ include up to three small VMs. This guide uses only one. + + __ https://fly.io/docs/about/pricing/ + +We're going to deploy a very simple app. The process would be identical for a +more realistic app. + +Create application +------------------ + +Here's the implementation of the app, an echo server. Save it in a file called +``app.py``: + +.. literalinclude:: ../../example/deployment/fly/app.py + :language: python + +This app implements typical requirements for running on a Platform as a Service: + +* it provides a health check at ``/healthz``; +* it closes connections and exits cleanly when it receives a ``SIGTERM`` signal. + +Create a ``requirements.txt`` file containing this line to declare a dependency +on websockets: + +.. literalinclude:: ../../example/deployment/fly/requirements.txt + :language: text + +The app is ready. Let's deploy it! + +Deploy application +------------------ + +Follow the instructions__ to install the Fly CLI, if you haven't done that yet. + +__ https://fly.io/docs/hands-on/install-flyctl/ + +Sign up or log in to Fly. + +Launch the app — you'll have to pick a different name because I'm already using +``websockets-echo``: + +.. code-block:: console + + $ fly launch + Creating app in ... + Scanning source code + Detected a Python app + Using the following build configuration: + Builder: paketobuildpacks/builder:base + ? App Name (leave blank to use an auto-generated name): websockets-echo + ? Select organization: ... + ? Select region: ... + Created app websockets-echo in organization ... + Wrote config file fly.toml + ? Would you like to set up a Postgresql database now? No + We have generated a simple Procfile for you. Modify it to fit your needs and run "fly deploy" to deploy your application. + +.. admonition:: This will build the image with a generic buildpack. + :class: tip + + Fly can `build images`__ with a Dockerfile or a buildpack. Here, ``fly + launch`` configures a generic Paketo buildpack. + + If you'd rather package the app with a Dockerfile, check out the guide to + :ref:`containerize an application `. + + __ https://fly.io/docs/reference/builders/ + +Replace the auto-generated ``fly.toml`` with: + +.. literalinclude:: ../../example/deployment/fly/fly.toml + :language: toml + +This configuration: + +* listens on port 443, terminates TLS, and forwards to the app on port 8080; +* declares a health check at ``/healthz``; +* requests a ``SIGTERM`` for terminating the app. + +Replace the auto-generated ``Procfile`` with: + +.. literalinclude:: ../../example/deployment/fly/Procfile + :language: text + +This tells Fly how to run the app. + +Now you can deploy it: + +.. code-block:: console + + $ fly deploy + + ... lots of output... + + ==> Monitoring deployment + + 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing] + --> v0 deployed successfully + +Validate deployment +------------------- + +Let's confirm that your application is running as expected. + +Since it's a WebSocket server, you need a WebSocket client, such as the +interactive client that comes with websockets. + +If you're currently building a websockets server, perhaps you're already in a +virtualenv where websockets is installed. If not, you can install it in a new +virtualenv as follows: + +.. code-block:: console + + $ python -m venv websockets-client + $ . websockets-client/bin/activate + $ pip install websockets + +Connect the interactive client — you must replace ``websockets-echo`` with the +name of your Fly app in this command: + +.. code-block:: console + + $ python -m websockets wss://websockets-echo.fly.dev/ + Connected to wss://websockets-echo.fly.dev/. + > + +Great! Your app is running! + +Once you're connected, you can send any message and the server will echo it, +or press Ctrl-D to terminate the connection: + +.. code-block:: console + + > Hello! + < Hello! + Connection closed: 1000 (OK). + +You can also confirm that your application shuts down gracefully. + +Connect an interactive client again — remember to replace ``websockets-echo`` +with your app: + +.. code-block:: console + + $ python -m websockets wss://websockets-echo.fly.dev/ + Connected to wss://websockets-echo.fly.dev/. + > + +In another shell, restart the app — again, replace ``websockets-echo`` with your +app: + +.. code-block:: console + + $ fly restart websockets-echo + websockets-echo is being restarted + +Go back to the first shell. The connection is closed with code 1001 (going +away). + +.. code-block:: console + + $ python -m websockets wss://websockets-echo.fly.dev/ + Connected to wss://websockets-echo.fly.dev/. + Connection closed: 1001 (going away). + +If graceful shutdown wasn't working, the server wouldn't perform a closing +handshake and the connection would be closed with code 1006 (abnormal closure). diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/haproxy.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/haproxy.rst new file mode 100644 index 0000000000000..fdaab04011c9d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/haproxy.rst @@ -0,0 +1,61 @@ +Deploy behind HAProxy +===================== + +This guide demonstrates a way to load balance connections across multiple +websockets server processes running on the same machine with HAProxy_. + +We'll run server processes with Supervisor as described in :doc:`this guide +`. + +.. _HAProxy: https://www.haproxy.org/ + +Run server processes +-------------------- + +Save this app to ``app.py``: + +.. literalinclude:: ../../example/deployment/haproxy/app.py + :emphasize-lines: 24 + +Each server process listens on a different port by extracting an incremental +index from an environment variable set by Supervisor. + +Save this configuration to ``supervisord.conf``: + +.. literalinclude:: ../../example/deployment/haproxy/supervisord.conf + +This configuration runs four instances of the app. + +Install Supervisor and run it: + +.. code-block:: console + + $ supervisord -c supervisord.conf -n + +Configure and run HAProxy +------------------------- + +Here's a simple HAProxy configuration to load balance connections across four +processes: + +.. literalinclude:: ../../example/deployment/haproxy/haproxy.cfg + +In the backend configuration, we set the load balancing method to +``leastconn`` in order to balance the number of active connections across +servers. This is best for long running connections. + +Save the configuration to ``haproxy.cfg``, install HAProxy, and run it: + +.. code-block:: console + + $ haproxy -f haproxy.cfg + +You can confirm that HAProxy proxies connections properly: + +.. code-block:: console + + $ PYTHONPATH=src python -m websockets ws://localhost:8080/ + Connected to ws://localhost:8080/. + > Hello! + < Hello! + Connection closed: 1000 (OK). diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/heroku.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/heroku.rst new file mode 100644 index 0000000000000..a97d2e7ce0393 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/heroku.rst @@ -0,0 +1,183 @@ +Deploy to Heroku +================ + +This guide describes how to deploy a websockets server to Heroku_. The same +principles should apply to other Platform as a Service providers. + +.. _Heroku: https://www.heroku.com/ + +.. admonition:: Heroku no longer offers a free tier. + :class: attention + + When this tutorial was written, in September 2021, Heroku offered a free + tier where a websockets app could run at no cost. In November 2022, Heroku + removed the free tier, making it impossible to maintain this document. As a + consequence, it isn't updated anymore and may be removed in the future. + +We're going to deploy a very simple app. The process would be identical for a +more realistic app. + +Create repository +----------------- + +Deploying to Heroku requires a git repository. Let's initialize one: + +.. code-block:: console + + $ mkdir websockets-echo + $ cd websockets-echo + $ git init -b main + Initialized empty Git repository in websockets-echo/.git/ + $ git commit --allow-empty -m "Initial commit." + [main (root-commit) 1e7947d] Initial commit. + +Create application +------------------ + +Here's the implementation of the app, an echo server. Save it in a file called +``app.py``: + +.. literalinclude:: ../../example/deployment/heroku/app.py + :language: python + +Heroku expects the server to `listen on a specific port`_, which is provided +in the ``$PORT`` environment variable. The app reads it and passes it to +:func:`~websockets.server.serve`. + +.. _listen on a specific port: https://devcenter.heroku.com/articles/preparing-a-codebase-for-heroku-deployment#4-listen-on-the-correct-port + +Heroku sends a ``SIGTERM`` signal to all processes when `shutting down a +dyno`_. When the app receives this signal, it closes connections and exits +cleanly. + +.. _shutting down a dyno: https://devcenter.heroku.com/articles/dynos#shutdown + +Create a ``requirements.txt`` file containing this line to declare a dependency +on websockets: + +.. literalinclude:: ../../example/deployment/heroku/requirements.txt + :language: text + +Create a ``Procfile``. + +.. literalinclude:: ../../example/deployment/heroku/Procfile + +This tells Heroku how to run the app. + +Confirm that you created the correct files and commit them to git: + +.. code-block:: console + + $ ls + Procfile app.py requirements.txt + $ git add . + $ git commit -m "Initial implementation." + [main 8418c62] Initial implementation. +  3 files changed, 32 insertions(+) +  create mode 100644 Procfile +  create mode 100644 app.py +  create mode 100644 requirements.txt + +The app is ready. Let's deploy it! + +Deploy application +------------------ + +Follow the instructions_ to install the Heroku CLI, if you haven't done that +yet. + +.. _instructions: https://devcenter.heroku.com/articles/getting-started-with-python#set-up + +Sign up or log in to Heroku. + +Create a Heroku app — you'll have to pick a different name because I'm already +using ``websockets-echo``: + +.. code-block:: console + + $ heroku create websockets-echo + Creating ⬢ websockets-echo... done + https://websockets-echo.herokuapp.com/ | https://git.heroku.com/websockets-echo.git + +.. code-block:: console + + $ git push heroku + + ... lots of output... + + remote: -----> Launching... + remote: Released v1 + remote: https://websockets-echo.herokuapp.com/ deployed to Heroku + remote: + remote: Verifying deploy... done. + To https://git.heroku.com/websockets-echo.git +  * [new branch] main -> main + +Validate deployment +------------------- + +Let's confirm that your application is running as expected. + +Since it's a WebSocket server, you need a WebSocket client, such as the +interactive client that comes with websockets. + +If you're currently building a websockets server, perhaps you're already in a +virtualenv where websockets is installed. If not, you can install it in a new +virtualenv as follows: + +.. code-block:: console + + $ python -m venv websockets-client + $ . websockets-client/bin/activate + $ pip install websockets + +Connect the interactive client — you must replace ``websockets-echo`` with the +name of your Heroku app in this command: + +.. code-block:: console + + $ python -m websockets wss://websockets-echo.herokuapp.com/ + Connected to wss://websockets-echo.herokuapp.com/. + > + +Great! Your app is running! + +Once you're connected, you can send any message and the server will echo it, +or press Ctrl-D to terminate the connection: + +.. code-block:: console + + > Hello! + < Hello! + Connection closed: 1000 (OK). + +You can also confirm that your application shuts down gracefully. + +Connect an interactive client again — remember to replace ``websockets-echo`` +with your app: + +.. code-block:: console + + $ python -m websockets wss://websockets-echo.herokuapp.com/ + Connected to wss://websockets-echo.herokuapp.com/. + > + +In another shell, restart the app — again, replace ``websockets-echo`` with your +app: + +.. code-block:: console + + $ heroku dyno:restart -a websockets-echo + Restarting dynos on ⬢ websockets-echo... done + +Go back to the first shell. The connection is closed with code 1001 (going +away). + +.. code-block:: console + + $ python -m websockets wss://websockets-echo.herokuapp.com/ + Connected to wss://websockets-echo.herokuapp.com/. + Connection closed: 1001 (going away). + +If graceful shutdown wasn't working, the server wouldn't perform a closing +handshake and the connection would be closed with code 1006 (abnormal closure). diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/index.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/index.rst new file mode 100644 index 0000000000000..ddbe67d3ae208 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/index.rst @@ -0,0 +1,56 @@ +How-to guides +============= + +In a hurry? Check out these examples. + +.. toctree:: + :titlesonly: + + quickstart + +If you're stuck, perhaps you'll find the answer here. + +.. toctree:: + :titlesonly: + + cheatsheet + patterns + autoreload + +This guide will help you integrate websockets into a broader system. + +.. toctree:: + :titlesonly: + + django + +The WebSocket protocol makes provisions for extending or specializing its +features, which websockets supports fully. + +.. toctree:: + :titlesonly: + + extensions + +.. _deployment-howto: + +Once your application is ready, learn how to deploy it on various platforms. + +.. toctree:: + :titlesonly: + + render + fly + heroku + kubernetes + supervisor + nginx + haproxy + +If you're integrating the Sans-I/O layer of websockets into a library, rather +than building an application with websockets, follow this guide. + +.. toctree:: + :maxdepth: 2 + + sansio diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/kubernetes.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/kubernetes.rst new file mode 100644 index 0000000000000..064a6ac4d5801 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/kubernetes.rst @@ -0,0 +1,215 @@ +Deploy to Kubernetes +==================== + +This guide describes how to deploy a websockets server to Kubernetes_. It +assumes familiarity with Docker and Kubernetes. + +We're going to deploy a simple app to a local Kubernetes cluster and to ensure +that it scales as expected. + +In a more realistic context, you would follow your organization's practices +for deploying to Kubernetes, but you would apply the same principles as far as +websockets is concerned. + +.. _Kubernetes: https://kubernetes.io/ + +.. _containerize-application: + +Containerize application +------------------------ + +Here's the app we're going to deploy. Save it in a file called +``app.py``: + +.. literalinclude:: ../../example/deployment/kubernetes/app.py + +This is an echo server with one twist: every message blocks the server for +100ms, which creates artificial starvation of CPU time. This makes it easier +to saturate the server for load testing. + +The app exposes a health check on ``/healthz``. It also provides two other +endpoints for testing purposes: ``/inemuri`` will make the app unresponsive +for 10 seconds and ``/seppuku`` will terminate it. + +The quest for the perfect Python container image is out of scope of this +guide, so we'll go for the simplest possible configuration instead: + +.. literalinclude:: ../../example/deployment/kubernetes/Dockerfile + +After saving this ``Dockerfile``, build the image: + +.. code-block:: console + + $ docker build -t websockets-test:1.0 . + +Test your image by running: + +.. code-block:: console + + $ docker run --name run-websockets-test --publish 32080:80 --rm \ + websockets-test:1.0 + +Then, in another shell, in a virtualenv where websockets is installed, connect +to the app and check that it echoes anything you send: + +.. code-block:: console + + $ python -m websockets ws://localhost:32080/ + Connected to ws://localhost:32080/. + > Hey there! + < Hey there! + > + +Now, in yet another shell, stop the app with: + +.. code-block:: console + + $ docker kill -s TERM run-websockets-test + +Going to the shell where you connected to the app, you can confirm that it +shut down gracefully: + +.. code-block:: console + + $ python -m websockets ws://localhost:32080/ + Connected to ws://localhost:32080/. + > Hey there! + < Hey there! + Connection closed: 1001 (going away). + +If it didn't, you'd get code 1006 (abnormal closure). + +Deploy application +------------------ + +Configuring Kubernetes is even further beyond the scope of this guide, so +we'll use a basic configuration for testing, with just one Service_ and one +Deployment_: + +.. literalinclude:: ../../example/deployment/kubernetes/deployment.yaml + +For local testing, a service of type NodePort_ is good enough. For deploying +to production, you would configure an Ingress_. + +.. _Service: https://kubernetes.io/docs/concepts/services-networking/service/ +.. _Deployment: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/ +.. _NodePort: https://kubernetes.io/docs/concepts/services-networking/service/#nodeport +.. _Ingress: https://kubernetes.io/docs/concepts/services-networking/ingress/ + +After saving this to a file called ``deployment.yaml``, you can deploy: + +.. code-block:: console + + $ kubectl apply -f deployment.yaml + service/websockets-test created + deployment.apps/websockets-test created + +Now you have a deployment with one pod running: + +.. code-block:: console + + $ kubectl get deployment websockets-test + NAME READY UP-TO-DATE AVAILABLE AGE + websockets-test 1/1 1 1 10s + $ kubectl get pods -l app=websockets-test + NAME READY STATUS RESTARTS AGE + websockets-test-86b48f4bb7-nltfh 1/1 Running 0 10s + +You can connect to the service — press Ctrl-D to exit: + +.. code-block:: console + + $ python -m websockets ws://localhost:32080/ + Connected to ws://localhost:32080/. + Connection closed: 1000 (OK). + +Validate deployment +------------------- + +First, let's ensure the liveness probe works by making the app unresponsive: + +.. code-block:: console + + $ curl http://localhost:32080/inemuri + Sleeping for 10s + +Since we have only one pod, we know that this pod will go to sleep. + +The liveness probe is configured to run every second. By default, liveness +probes time out after one second and have a threshold of three failures. +Therefore Kubernetes should restart the pod after at most 5 seconds. + +Indeed, after a few seconds, the pod reports a restart: + +.. code-block:: console + + $ kubectl get pods -l app=websockets-test + NAME READY STATUS RESTARTS AGE + websockets-test-86b48f4bb7-nltfh 1/1 Running 1 42s + +Next, let's take it one step further and crash the app: + +.. code-block:: console + + $ curl http://localhost:32080/seppuku + Terminating + +The pod reports a second restart: + +.. code-block:: console + + $ kubectl get pods -l app=websockets-test + NAME READY STATUS RESTARTS AGE + websockets-test-86b48f4bb7-nltfh 1/1 Running 2 72s + +All good — Kubernetes delivers on its promise to keep our app alive! + +Scale deployment +---------------- + +Of course, Kubernetes is for scaling. Let's scale — modestly — to 10 pods: + +.. code-block:: console + + $ kubectl scale deployment.apps/websockets-test --replicas=10 + deployment.apps/websockets-test scaled + +After a few seconds, we have 10 pods running: + +.. code-block:: console + + $ kubectl get deployment websockets-test + NAME READY UP-TO-DATE AVAILABLE AGE + websockets-test 10/10 10 10 10m + +Now let's generate load. We'll use this script: + +.. literalinclude:: ../../example/deployment/kubernetes/benchmark.py + +We'll connect 500 clients in parallel, meaning 50 clients per pod, and have +each client send 6 messages. Since the app blocks for 100ms before responding, +if connections are perfectly distributed, we expect a total run time slightly +over 50 * 6 * 0.1 = 30 seconds. + +Let's try it: + +.. code-block:: console + + $ ulimit -n 512 + $ time python benchmark.py 500 6 + python benchmark.py 500 6 2.40s user 0.51s system 7% cpu 36.471 total + +A total runtime of 36 seconds is in the right ballpark. Repeating this +experiment with other parameters shows roughly consistent results, with the +high variability you'd expect from a quick benchmark without any effort to +stabilize the test setup. + +Finally, we can scale back to one pod. + +.. code-block:: console + + $ kubectl scale deployment.apps/websockets-test --replicas=1 + deployment.apps/websockets-test scaled + $ kubectl get deployment websockets-test + NAME READY UP-TO-DATE AVAILABLE AGE + websockets-test 1/1 1 1 15m diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/nginx.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/nginx.rst new file mode 100644 index 0000000000000..30545fbc7d1ca --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/nginx.rst @@ -0,0 +1,84 @@ +Deploy behind nginx +=================== + +This guide demonstrates a way to load balance connections across multiple +websockets server processes running on the same machine with nginx_. + +We'll run server processes with Supervisor as described in :doc:`this guide +`. + +.. _nginx: https://nginx.org/ + +Run server processes +-------------------- + +Save this app to ``app.py``: + +.. literalinclude:: ../../example/deployment/nginx/app.py + :emphasize-lines: 21,23 + +We'd like to nginx to connect to websockets servers via Unix sockets in order +to avoid the overhead of TCP for communicating between processes running in +the same OS. + +We start the app with :func:`~websockets.server.unix_serve`. Each server +process listens on a different socket thanks to an environment variable set +by Supervisor to a different value. + +Save this configuration to ``supervisord.conf``: + +.. literalinclude:: ../../example/deployment/nginx/supervisord.conf + +This configuration runs four instances of the app. + +Install Supervisor and run it: + +.. code-block:: console + + $ supervisord -c supervisord.conf -n + +Configure and run nginx +----------------------- + +Here's a simple nginx configuration to load balance connections across four +processes: + +.. literalinclude:: ../../example/deployment/nginx/nginx.conf + +We set ``daemon off`` so we can run nginx in the foreground for testing. + +Then we combine the `WebSocket proxying`_ and `load balancing`_ guides: + +* The WebSocket protocol requires HTTP/1.1. We must set the HTTP protocol + version to 1.1, else nginx defaults to HTTP/1.0 for proxying. + +* The WebSocket handshake involves the ``Connection`` and ``Upgrade`` HTTP + headers. We must pass them to the upstream explicitly, else nginx drops + them because they're hop-by-hop headers. + + We deviate from the `WebSocket proxying`_ guide because its example adds a + ``Connection: Upgrade`` header to every upstream request, even if the + original request didn't contain that header. + +* In the upstream configuration, we set the load balancing method to + ``least_conn`` in order to balance the number of active connections across + servers. This is best for long running connections. + +.. _WebSocket proxying: http://nginx.org/en/docs/http/websocket.html +.. _load balancing: http://nginx.org/en/docs/http/load_balancing.html + +Save the configuration to ``nginx.conf``, install nginx, and run it: + +.. code-block:: console + + $ nginx -c nginx.conf -p . + +You can confirm that nginx proxies connections properly: + +.. code-block:: console + + $ PYTHONPATH=src python -m websockets ws://localhost:8080/ + Connected to ws://localhost:8080/. + > Hello! + < Hello! + Connection closed: 1000 (OK). diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/patterns.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/patterns.rst new file mode 100644 index 0000000000000..c6f325d21372f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/patterns.rst @@ -0,0 +1,110 @@ +Patterns +======== + +.. currentmodule:: websockets + +Here are typical patterns for processing messages in a WebSocket server or +client. You will certainly implement some of them in your application. + +This page gives examples of connection handlers for a server. However, they're +also applicable to a client, simply by assuming that ``websocket`` is a +connection created with :func:`~client.connect`. + +WebSocket connections are long-lived. You will usually write a loop to process +several messages during the lifetime of a connection. + +Consumer +-------- + +To receive messages from the WebSocket connection:: + + async def consumer_handler(websocket): + async for message in websocket: + await consumer(message) + +In this example, ``consumer()`` is a coroutine implementing your business +logic for processing a message received on the WebSocket connection. Each +message may be :class:`str` or :class:`bytes`. + +Iteration terminates when the client disconnects. + +Producer +-------- + +To send messages to the WebSocket connection:: + + async def producer_handler(websocket): + while True: + message = await producer() + await websocket.send(message) + +In this example, ``producer()`` is a coroutine implementing your business +logic for generating the next message to send on the WebSocket connection. +Each message must be :class:`str` or :class:`bytes`. + +Iteration terminates when the client disconnects +because :meth:`~server.WebSocketServerProtocol.send` raises a +:exc:`~exceptions.ConnectionClosed` exception, +which breaks out of the ``while True`` loop. + +Consumer and producer +--------------------- + +You can receive and send messages on the same WebSocket connection by +combining the consumer and producer patterns. This requires running two tasks +in parallel:: + + async def handler(websocket): + await asyncio.gather( + consumer_handler(websocket), + producer_handler(websocket), + ) + +If a task terminates, :func:`~asyncio.gather` doesn't cancel the other task. +This can result in a situation where the producer keeps running after the +consumer finished, which may leak resources. + +Here's a way to exit and close the WebSocket connection as soon as a task +terminates, after canceling the other task:: + + async def handler(websocket): + consumer_task = asyncio.create_task(consumer_handler(websocket)) + producer_task = asyncio.create_task(producer_handler(websocket)) + done, pending = await asyncio.wait( + [consumer_task, producer_task], + return_when=asyncio.FIRST_COMPLETED, + ) + for task in pending: + task.cancel() + +Registration +------------ + +To keep track of currently connected clients, you can register them when they +connect and unregister them when they disconnect:: + + connected = set() + + async def handler(websocket): + # Register. + connected.add(websocket) + try: + # Broadcast a message to all connected clients. + websockets.broadcast(connected, "Hello!") + await asyncio.sleep(10) + finally: + # Unregister. + connected.remove(websocket) + +This example maintains the set of connected clients in memory. This works as +long as you run a single process. It doesn't scale to multiple processes. + +Publish–subscribe +----------------- + +If you plan to run multiple processes and you want to communicate updates +between processes, then you must deploy a messaging system. You may find +publish-subscribe functionality useful. + +A complete implementation of this idea with Redis is described in +the :doc:`Django integration guide <../howto/django>`. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/quickstart.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/quickstart.rst new file mode 100644 index 0000000000000..ab870952c1761 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/quickstart.rst @@ -0,0 +1,170 @@ +Quick start +=========== + +.. currentmodule:: websockets + +Here are a few examples to get you started quickly with websockets. + +Say "Hello world!" +------------------ + +Here's a WebSocket server. + +It receives a name from the client, sends a greeting, and closes the connection. + +.. literalinclude:: ../../example/quickstart/server.py + :caption: server.py + :language: python + :linenos: + +:func:`~server.serve` executes the connection handler coroutine ``hello()`` +once for each WebSocket connection. It closes the WebSocket connection when +the handler returns. + +Here's a corresponding WebSocket client. + +It sends a name to the server, receives a greeting, and closes the connection. + +.. literalinclude:: ../../example/quickstart/client.py + :caption: client.py + :language: python + :linenos: + +Using :func:`~client.connect` as an asynchronous context manager ensures the +WebSocket connection is closed. + +.. _secure-server-example: + +Encrypt connections +------------------- + +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 TLS_ (Transport Layer Security). ``wss`` +requires certificates like ``https``. + +.. _TLS: https://developer.mozilla.org/en-US/docs/Web/Security/Transport_Layer_Security + +.. admonition:: TLS vs. SSL + :class: tip + + TLS is sometimes referred to as SSL (Secure Sockets Layer). SSL was an + earlier encryption protocol; the name stuck. + +Here's how to adapt the server to encrypt connections. You must download +:download:`localhost.pem <../../example/quickstart/localhost.pem>` and save it +in the same directory as ``server_secure.py``. + +.. literalinclude:: ../../example/quickstart/server_secure.py + :caption: server_secure.py + :language: python + :linenos: + +Here's how to adapt the client similarly. + +.. literalinclude:: ../../example/quickstart/client_secure.py + :caption: client_secure.py + :language: python + :linenos: + +In this example, the client needs a TLS context because the server uses a +self-signed certificate. + +When connecting to a secure WebSocket server with a valid certificate — any +certificate signed by a CA that your Python installation trusts — you can +simply pass ``ssl=True`` to :func:`~client.connect`. + +.. admonition:: Configure the TLS context securely + :class: attention + + This example demonstrates the ``ssl`` argument with a TLS certificate shared + between the client and the server. This is a simplistic setup. + + Please review the advice and security considerations in the documentation of + the :mod:`ssl` module to configure the TLS context securely. + +Connect from a browser +---------------------- + +The WebSocket protocol was invented for the web — as the name says! + +Here's how to connect to a WebSocket server from a browser. + +Run this script in a console: + +.. literalinclude:: ../../example/quickstart/show_time.py + :caption: show_time.py + :language: python + :linenos: + +Save this file as ``show_time.html``: + +.. literalinclude:: ../../example/quickstart/show_time.html + :caption: show_time.html + :language: html + :linenos: + +Save this file as ``show_time.js``: + +.. literalinclude:: ../../example/quickstart/show_time.js + :caption: show_time.js + :language: js + :linenos: + +Then, open ``show_time.html`` in several browsers. Clocks tick irregularly. + +Broadcast messages +------------------ + +Let's change the previous example to send the same timestamps to all browsers, +instead of generating independent sequences for each client. + +Stop the previous script if it's still running and run this script in a console: + +.. literalinclude:: ../../example/quickstart/show_time_2.py + :caption: show_time_2.py + :language: python + :linenos: + +Refresh ``show_time.html`` in all browsers. Clocks tick in sync. + +Manage application state +------------------------ + +A WebSocket server can receive events from clients, process them to update the +application state, and broadcast the updated state to all connected clients. + +Here's an example where any client can increment or decrement a counter. The +concurrency model of :mod:`asyncio` guarantees that updates are serialized. + +Run this script in a console: + +.. literalinclude:: ../../example/quickstart/counter.py + :caption: counter.py + :language: python + :linenos: + +Save this file as ``counter.html``: + +.. literalinclude:: ../../example/quickstart/counter.html + :caption: counter.html + :language: html + :linenos: + +Save this file as ``counter.css``: + +.. literalinclude:: ../../example/quickstart/counter.css + :caption: counter.css + :language: css + :linenos: + +Save this file as ``counter.js``: + +.. literalinclude:: ../../example/quickstart/counter.js + :caption: counter.js + :language: js + :linenos: + +Then open ``counter.html`` file in several browsers and play with [+] and [-]. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/render.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/render.rst new file mode 100644 index 0000000000000..70bf8c376c427 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/render.rst @@ -0,0 +1,172 @@ +Deploy to Render +================ + +This guide describes how to deploy a websockets server to Render_. + +.. _Render: https://render.com/ + +.. admonition:: The free plan of Render is sufficient for trying this guide. + :class: tip + + However, on a `free plan`__, connections are dropped after five minutes, + which is quite short for WebSocket application. + + __ https://render.com/docs/free + +We're going to deploy a very simple app. The process would be identical for a +more realistic app. + +Create repository +----------------- + +Deploying to Render requires a git repository. Let's initialize one: + +.. code-block:: console + + $ mkdir websockets-echo + $ cd websockets-echo + $ git init -b main + Initialized empty Git repository in websockets-echo/.git/ + $ git commit --allow-empty -m "Initial commit." + [main (root-commit) 816c3b1] Initial commit. + +Render requires the git repository to be hosted at GitHub or GitLab. + +Sign up or log in to GitHub. Create a new repository named ``websockets-echo``. +Don't enable any of the initialization options offered by GitHub. Then, follow +instructions for pushing an existing repository from the command line. + +After pushing, refresh your repository's homepage on GitHub. You should see an +empty repository with an empty initial commit. + +Create application +------------------ + +Here's the implementation of the app, an echo server. Save it in a file called +``app.py``: + +.. literalinclude:: ../../example/deployment/render/app.py + :language: python + +This app implements requirements for `zero downtime deploys`_: + +* it provides a health check at ``/healthz``; +* it closes connections and exits cleanly when it receives a ``SIGTERM`` signal. + +.. _zero downtime deploys: https://render.com/docs/deploys#zero-downtime-deploys + +Create a ``requirements.txt`` file containing this line to declare a dependency +on websockets: + +.. literalinclude:: ../../example/deployment/render/requirements.txt + :language: text + +Confirm that you created the correct files and commit them to git: + +.. code-block:: console + + $ ls + app.py requirements.txt + $ git add . + $ git commit -m "Initial implementation." + [main f26bf7f] Initial implementation. + 2 files changed, 37 insertions(+) + create mode 100644 app.py + create mode 100644 requirements.txt + +Push the changes to GitHub: + +.. code-block:: console + + $ git push + ... + To github.com:/websockets-echo.git + 816c3b1..f26bf7f main -> main + +The app is ready. Let's deploy it! + +Deploy application +------------------ + +Sign up or log in to Render. + +Create a new web service. Connect the git repository that you just created. + +Then, finalize the configuration of your app as follows: + +* **Name**: websockets-echo +* **Start Command**: ``python app.py`` + +If you're just experimenting, select the free plan. Create the web service. + +To configure the health check, go to Settings, scroll down to Health & Alerts, +and set: + +* **Health Check Path**: /healthz + +This triggers a new deployment. + +Validate deployment +------------------- + +Let's confirm that your application is running as expected. + +Since it's a WebSocket server, you need a WebSocket client, such as the +interactive client that comes with websockets. + +If you're currently building a websockets server, perhaps you're already in a +virtualenv where websockets is installed. If not, you can install it in a new +virtualenv as follows: + +.. code-block:: console + + $ python -m venv websockets-client + $ . websockets-client/bin/activate + $ pip install websockets + +Connect the interactive client — you must replace ``websockets-echo`` with the +name of your Render app in this command: + +.. code-block:: console + + $ python -m websockets wss://websockets-echo.onrender.com/ + Connected to wss://websockets-echo.onrender.com/. + > + +Great! Your app is running! + +Once you're connected, you can send any message and the server will echo it, +or press Ctrl-D to terminate the connection: + +.. code-block:: console + + > Hello! + < Hello! + Connection closed: 1000 (OK). + +You can also confirm that your application shuts down gracefully when you deploy +a new version. Due to limitations of Render's free plan, you must upgrade to a +paid plan before you perform this test. + +Connect an interactive client again — remember to replace ``websockets-echo`` +with your app: + +.. code-block:: console + + $ python -m websockets wss://websockets-echo.onrender.com/ + Connected to wss://websockets-echo.onrender.com/. + > + +Trigger a new deployment with Manual Deploy > Deploy latest commit. When the +deployment completes, the connection is closed with code 1001 (going away). + +.. code-block:: console + + $ python -m websockets wss://websockets-echo.onrender.com/ + Connected to wss://websockets-echo.onrender.com/. + Connection closed: 1001 (going away). + +If graceful shutdown wasn't working, the server wouldn't perform a closing +handshake and the connection would be closed with code 1006 (abnormal closure). + +Remember to downgrade to a free plan if you upgraded just for testing this feature. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/sansio.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/sansio.rst new file mode 100644 index 0000000000000..d41519ff09157 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/sansio.rst @@ -0,0 +1,322 @@ +Integrate the Sans-I/O layer +============================ + +.. currentmodule:: websockets + +This guide explains how to integrate the `Sans-I/O`_ layer of websockets to +add support for WebSocket in another library. + +.. _Sans-I/O: https://sans-io.readthedocs.io/ + +As a prerequisite, you should decide how you will handle network I/O and +asynchronous control flow. + +Your integration layer will provide an API for the application on one side, +will talk to the network on the other side, and will rely on websockets to +implement the protocol in the middle. + +.. image:: ../topics/data-flow.svg + :align: center + +Opening a connection +-------------------- + +Client-side +........... + +If you're building a client, parse the URI you'd like to connect to:: + + from websockets.uri import parse_uri + + wsuri = parse_uri("ws://example.com/") + +Open a TCP connection to ``(wsuri.host, wsuri.port)`` and perform a TLS +handshake if ``wsuri.secure`` is :obj:`True`. + +Initialize a :class:`~client.ClientProtocol`:: + + from websockets.client import ClientProtocol + + protocol = ClientProtocol(wsuri) + +Create a WebSocket handshake request +with :meth:`~client.ClientProtocol.connect` and send it +with :meth:`~client.ClientProtocol.send_request`:: + + request = protocol.connect() + protocol.send_request(request) + +Then, call :meth:`~protocol.Protocol.data_to_send` and send its output to +the network, as described in `Send data`_ below. + +Once you receive enough data, as explained in `Receive data`_ below, the first +event returned by :meth:`~protocol.Protocol.events_received` is the WebSocket +handshake response. + +When the handshake fails, the reason is available in +:attr:`~client.ClientProtocol.handshake_exc`:: + + if protocol.handshake_exc is not None: + raise protocol.handshake_exc + +Else, the WebSocket connection is open. + +A WebSocket client API usually performs the handshake then returns a wrapper +around the network socket and the :class:`~client.ClientProtocol`. + +Server-side +........... + +If you're building a server, accept network connections from clients and +perform a TLS handshake if desired. + +For each connection, initialize a :class:`~server.ServerProtocol`:: + + from websockets.server import ServerProtocol + + protocol = ServerProtocol() + +Once you receive enough data, as explained in `Receive data`_ below, the first +event returned by :meth:`~protocol.Protocol.events_received` is the WebSocket +handshake request. + +Create a WebSocket handshake response +with :meth:`~server.ServerProtocol.accept` and send it +with :meth:`~server.ServerProtocol.send_response`:: + + response = protocol.accept(request) + protocol.send_response(response) + +Alternatively, you may reject the WebSocket handshake and return an HTTP +response with :meth:`~server.ServerProtocol.reject`:: + + response = protocol.reject(status, explanation) + protocol.send_response(response) + +Then, call :meth:`~protocol.Protocol.data_to_send` and send its output to +the network, as described in `Send data`_ below. + +Even when you call :meth:`~server.ServerProtocol.accept`, the WebSocket +handshake may fail if the request is incorrect or unsupported. + +When the handshake fails, the reason is available in +:attr:`~server.ServerProtocol.handshake_exc`:: + + if protocol.handshake_exc is not None: + raise protocol.handshake_exc + +Else, the WebSocket connection is open. + +A WebSocket server API usually builds a wrapper around the network socket and +the :class:`~server.ServerProtocol`. Then it invokes a connection handler that +accepts the wrapper in argument. + +It may also provide a way to close all connections and to shut down the server +gracefully. + +Going forwards, this guide focuses on handling an individual connection. + +From the network to the application +----------------------------------- + +Go through the five steps below until you reach the end of the data stream. + +Receive data +............ + +When receiving data from the network, feed it to the protocol's +:meth:`~protocol.Protocol.receive_data` method. + +When reaching the end of the data stream, call the protocol's +:meth:`~protocol.Protocol.receive_eof` method. + +For example, if ``sock`` is a :obj:`~socket.socket`:: + + try: + data = sock.recv(65536) + except OSError: # socket closed + data = b"" + if data: + protocol.receive_data(data) + else: + protocol.receive_eof() + +These methods aren't expected to raise exceptions — unless you call them again +after calling :meth:`~protocol.Protocol.receive_eof`, which is an error. +(If you get an exception, please file a bug!) + +Send data +......... + +Then, call :meth:`~protocol.Protocol.data_to_send` and send its output to +the network:: + + for data in protocol.data_to_send(): + if data: + sock.sendall(data) + else: + sock.shutdown(socket.SHUT_WR) + +The empty bytestring signals the end of the data stream. When you see it, you +must half-close the TCP connection. + +Sending data right after receiving data is necessary because websockets +responds to ping frames, close frames, and incorrect inputs automatically. + +Expect TCP connection to close +.............................. + +Closing a WebSocket connection normally involves a two-way WebSocket closing +handshake. Then, regardless of whether the closure is normal or abnormal, the +server starts the four-way TCP closing handshake. If the network fails at the +wrong point, you can end up waiting until the TCP timeout, which is very long. + +To prevent dangling TCP connections when you expect the end of the data stream +but you never reach it, call :meth:`~protocol.Protocol.close_expected` +and, if it returns :obj:`True`, schedule closing the TCP connection after a +short timeout:: + + # start a new execution thread to run this code + sleep(10) + sock.close() # does nothing if the socket is already closed + +If the connection is still open when the timeout elapses, closing the socket +makes the execution thread that reads from the socket reach the end of the +data stream, possibly with an exception. + +Close TCP connection +.................... + +If you called :meth:`~protocol.Protocol.receive_eof`, close the TCP +connection now. This is a clean closure because the receive buffer is empty. + +After :meth:`~protocol.Protocol.receive_eof` signals the end of the read +stream, :meth:`~protocol.Protocol.data_to_send` always signals the end of +the write stream, unless it already ended. So, at this point, the TCP +connection is already half-closed. The only reason for closing it now is to +release resources related to the socket. + +Now you can exit the loop relaying data from the network to the application. + +Receive events +.............. + +Finally, call :meth:`~protocol.Protocol.events_received` to obtain events +parsed from the data provided to :meth:`~protocol.Protocol.receive_data`:: + + events = connection.events_received() + +The first event will be the WebSocket opening handshake request or response. +See `Opening a connection`_ above for details. + +All later events are WebSocket frames. There are two types of frames: + +* Data frames contain messages transferred over the WebSocket connections. You + should provide them to the application. See `Fragmentation`_ below for + how to reassemble messages from frames. +* Control frames provide information about the connection's state. The main + use case is to expose an abstraction over ping and pong to the application. + Keep in mind that websockets responds to ping frames and close frames + automatically. Don't duplicate this functionality! + +From the application to the network +----------------------------------- + +The connection object provides one method for each type of WebSocket frame. + +For sending a data frame: + +* :meth:`~protocol.Protocol.send_continuation` +* :meth:`~protocol.Protocol.send_text` +* :meth:`~protocol.Protocol.send_binary` + +These methods raise :exc:`~exceptions.ProtocolError` if you don't set +the :attr:`FIN ` bit correctly in fragmented +messages. + +For sending a control frame: + +* :meth:`~protocol.Protocol.send_close` +* :meth:`~protocol.Protocol.send_ping` +* :meth:`~protocol.Protocol.send_pong` + +:meth:`~protocol.Protocol.send_close` initiates the closing handshake. +See `Closing a connection`_ below for details. + +If you encounter an unrecoverable error and you must fail the WebSocket +connection, call :meth:`~protocol.Protocol.fail`. + +After any of the above, call :meth:`~protocol.Protocol.data_to_send` and +send its output to the network, as shown in `Send data`_ above. + +If you called :meth:`~protocol.Protocol.send_close` +or :meth:`~protocol.Protocol.fail`, you expect the end of the data +stream. You should follow the process described in `Close TCP connection`_ +above in order to prevent dangling TCP connections. + +Closing a connection +-------------------- + +Under normal circumstances, when a server wants to close the TCP connection: + +* it closes the write side; +* it reads until the end of the stream, because it expects the client to close + the read side; +* it closes the socket. + +When a client wants to close the TCP connection: + +* it reads until the end of the stream, because it expects the server to close + the read side; +* it closes the write side; +* it closes the socket. + +Applying the rules described earlier in this document gives the intended +result. As a reminder, the rules are: + +* When :meth:`~protocol.Protocol.data_to_send` returns the empty + bytestring, close the write side of the TCP connection. +* When you reach the end of the read stream, close the TCP connection. +* When :meth:`~protocol.Protocol.close_expected` returns :obj:`True`, if + you don't reach the end of the read stream quickly, close the TCP connection. + +Fragmentation +------------- + +WebSocket messages may be fragmented. Since this is a protocol-level concern, +you may choose to reassemble fragmented messages before handing them over to +the application. + +To reassemble a message, read data frames until you get a frame where +the :attr:`FIN ` bit is set, then concatenate +the payloads of all frames. + +You will never receive an inconsistent sequence of frames because websockets +raises a :exc:`~exceptions.ProtocolError` and fails the connection when this +happens. However, you may receive an incomplete sequence if the connection +drops in the middle of a fragmented message. + +Tips +---- + +Serialize operations +.................... + +The Sans-I/O layer expects to run sequentially. If your interact with it from +multiple threads or coroutines, you must ensure correct serialization. This +should happen automatically in a cooperative multitasking environment. + +However, you still have to make sure you don't break this property by +accident. For example, serialize writes to the network +when :meth:`~protocol.Protocol.data_to_send` returns multiple values to +prevent concurrent writes from interleaving incorrectly. + +Avoid buffers +............. + +The Sans-I/O layer doesn't do any buffering. It makes events available in +:meth:`~protocol.Protocol.events_received` as soon as they're received. + +You should make incoming messages available to the application immediately and +stop further processing until the application fetches them. This will usually +result in the best performance. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/supervisor.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/supervisor.rst new file mode 100644 index 0000000000000..5eefc7711b80a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/supervisor.rst @@ -0,0 +1,131 @@ +Deploy with Supervisor +====================== + +This guide proposes a simple way to deploy a websockets server directly on a +Linux or BSD operating system. + +We'll configure Supervisor_ to run several server processes and to restart +them if needed. + +.. _Supervisor: http://supervisord.org/ + +We'll bind all servers to the same port. The OS will take care of balancing +connections. + +Create and activate a virtualenv: + +.. code-block:: console + + $ python -m venv supervisor-websockets + $ . supervisor-websockets/bin/activate + +Install websockets and Supervisor: + +.. code-block:: console + + $ pip install websockets + $ pip install supervisor + +Save this app to a file called ``app.py``: + +.. literalinclude:: ../../example/deployment/supervisor/app.py + +This is an echo server with two features added for the purpose of this guide: + +* It shuts down gracefully when receiving a ``SIGTERM`` signal; +* It enables the ``reuse_port`` option of :meth:`~asyncio.loop.create_server`, + which in turns sets ``SO_REUSEPORT`` on the accept socket. + +Save this Supervisor configuration to ``supervisord.conf``: + +.. literalinclude:: ../../example/deployment/supervisor/supervisord.conf + +This is the minimal configuration required to keep four instances of the app +running, restarting them if they exit. + +Now start Supervisor in the foreground: + +.. code-block:: console + + $ supervisord -c supervisord.conf -n + INFO Increased RLIMIT_NOFILE limit to 1024 + INFO supervisord started with pid 43596 + INFO spawned: 'websockets-test_00' with pid 43597 + INFO spawned: 'websockets-test_01' with pid 43598 + INFO spawned: 'websockets-test_02' with pid 43599 + INFO spawned: 'websockets-test_03' with pid 43600 + INFO success: websockets-test_00 entered RUNNING state, process has stayed up for > than 1 seconds (startsecs) + INFO success: websockets-test_01 entered RUNNING state, process has stayed up for > than 1 seconds (startsecs) + INFO success: websockets-test_02 entered RUNNING state, process has stayed up for > than 1 seconds (startsecs) + INFO success: websockets-test_03 entered RUNNING state, process has stayed up for > than 1 seconds (startsecs) + +In another shell, after activating the virtualenv, we can connect to the app — +press Ctrl-D to exit: + +.. code-block:: console + + $ python -m websockets ws://localhost:8080/ + Connected to ws://localhost:8080/. + > Hello! + < Hello! + Connection closed: 1000 (OK). + +Look at the pid of an instance of the app in the logs and terminate it: + +.. code-block:: console + + $ kill -TERM 43597 + +The logs show that Supervisor restarted this instance: + +.. code-block:: console + + INFO exited: websockets-test_00 (exit status 0; expected) + INFO spawned: 'websockets-test_00' with pid 43629 + INFO success: websockets-test_00 entered RUNNING state, process has stayed up for > than 1 seconds (startsecs) + +Now let's check what happens when we shut down Supervisor, but first let's +establish a connection and leave it open: + +.. code-block:: console + + $ python -m websockets ws://localhost:8080/ + Connected to ws://localhost:8080/. + > + +Look at the pid of supervisord itself in the logs and terminate it: + +.. code-block:: console + + $ kill -TERM 43596 + +The logs show that Supervisor terminated all instances of the app before +exiting: + +.. code-block:: console + + WARN received SIGTERM indicating exit request + INFO waiting for websockets-test_00, websockets-test_01, websockets-test_02, websockets-test_03 to die + INFO stopped: websockets-test_02 (exit status 0) + INFO stopped: websockets-test_03 (exit status 0) + INFO stopped: websockets-test_01 (exit status 0) + INFO stopped: websockets-test_00 (exit status 0) + +And you can see that the connection to the app was closed gracefully: + +.. code-block:: console + + $ python -m websockets ws://localhost:8080/ + Connected to ws://localhost:8080/. + Connection closed: 1001 (going away). + +In this example, we've been sharing the same virtualenv for supervisor and +websockets. + +In a real deployment, you would likely: + +* Install Supervisor with the package manager of the OS. +* Create a virtualenv dedicated to your application. +* Add ``environment=PATH="path/to/your/virtualenv/bin"`` in the Supervisor + configuration. Then ``python app.py`` runs in that virtualenv. + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/w3c-import.log new file mode 100644 index 0000000000000..64a6cf2ade5af --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/w3c-import.log @@ -0,0 +1,31 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/autoreload.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/cheatsheet.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/django.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/extensions.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/fly.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/haproxy.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/heroku.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/kubernetes.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/nginx.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/patterns.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/quickstart.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/render.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/sansio.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/howto/supervisor.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/index.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/index.rst new file mode 100644 index 0000000000000..d9737db12a69b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/index.rst @@ -0,0 +1,75 @@ +websockets +========== + +|licence| |version| |pyversions| |tests| |docs| |openssf| + +.. |licence| image:: https://img.shields.io/pypi/l/websockets.svg + :target: https://pypi.python.org/pypi/websockets + +.. |version| image:: https://img.shields.io/pypi/v/websockets.svg + :target: https://pypi.python.org/pypi/websockets + +.. |pyversions| image:: https://img.shields.io/pypi/pyversions/websockets.svg + :target: https://pypi.python.org/pypi/websockets + +.. |tests| image:: https://img.shields.io/github/checks-status/python-websockets/websockets/main?label=tests + :target: https://github.com/python-websockets/websockets/actions/workflows/tests.yml + +.. |docs| image:: https://img.shields.io/readthedocs/websockets.svg + :target: https://websockets.readthedocs.io/ + +.. |openssf| image:: https://bestpractices.coreinfrastructure.org/projects/6475/badge + :target: https://bestpractices.coreinfrastructure.org/projects/6475 + +websockets is a library for building WebSocket_ servers and clients in Python +with a focus on correctness, simplicity, robustness, and performance. + +.. _WebSocket: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API + +It supports several network I/O and control flow paradigms: + +1. The default implementation builds upon :mod:`asyncio`, Python's standard + asynchronous I/O framework. It provides an elegant coroutine-based API. It's + ideal for servers that handle many clients concurrently. +2. The :mod:`threading` implementation is a good alternative for clients, + especially if you aren't familiar with :mod:`asyncio`. It may also be used + for servers that don't need to serve many clients. +3. The `Sans-I/O`_ implementation is designed for integrating in third-party + libraries, typically application servers, in addition being used internally + by websockets. + +.. _Sans-I/O: https://sans-io.readthedocs.io/ + +Here's an echo server with the :mod:`asyncio` API: + +.. literalinclude:: ../example/echo.py + +Here's how a client sends and receives messages with the :mod:`threading` API: + +.. literalinclude:: ../example/hello.py + +Don't worry about the opening and closing handshakes, pings and pongs, or any +other behavior described in the WebSocket specification. websockets takes care +of this under the hood so you can focus on your application! + +Also, websockets provides an interactive client: + +.. code-block:: console + + $ python -m websockets ws://localhost:8765/ + Connected to ws://localhost:8765/. + > Hello world! + < Hello world! + Connection closed: 1000 (OK). + +Do you like it? :doc:`Let's dive in! ` + +.. toctree:: + :hidden: + + intro/index + howto/index + faq/index + reference/index + topics/index + project/index diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/index.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/index.rst new file mode 100644 index 0000000000000..095262a2073ff --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/index.rst @@ -0,0 +1,46 @@ +Getting started +=============== + +.. currentmodule:: websockets + +Requirements +------------ + +websockets requires Python ≥ 3.8. + +.. admonition:: Use the most recent Python release + :class: tip + + For each minor version (3.x), only the latest bugfix or security release + (3.x.y) is officially supported. + +It doesn't have any dependencies. + +.. _install: + +Installation +------------ + +Install websockets with: + +.. code-block:: console + + $ pip install websockets + +Wheels are available for all platforms. + +Tutorial +-------- + +Learn how to build an real-time web application with websockets. + +.. toctree:: + + tutorial1 + tutorial2 + tutorial3 + +In a hurry? +----------- + +Look at the :doc:`quick start guide <../howto/quickstart>`. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/tutorial1.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/tutorial1.rst new file mode 100644 index 0000000000000..ff85003b58a0b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/tutorial1.rst @@ -0,0 +1,591 @@ +Part 1 - Send & receive +======================= + +.. currentmodule:: websockets + +In this tutorial, you're going to build a web-based `Connect Four`_ game. + +.. _Connect Four: https://en.wikipedia.org/wiki/Connect_Four + +The web removes the constraint of being in the same room for playing a game. +Two players can connect over of the Internet, regardless of where they are, +and play in their browsers. + +When a player makes a move, it should be reflected immediately on both sides. +This is difficult to implement over HTTP due to the request-response style of +the protocol. + +Indeed, there is no good way to be notified when the other player makes a +move. Workarounds such as polling or long-polling introduce significant +overhead. + +Enter `WebSocket `_. + +The WebSocket protocol provides two-way communication between a browser and a +server over a persistent connection. That's exactly what you need to exchange +moves between players, via a server. + +.. admonition:: This is the first part of the tutorial. + + * In this :doc:`first part `, you will create a server and + connect one browser; you can play if you share the same browser. + * In the :doc:`second part `, you will connect a second + browser; you can play from different browsers on a local network. + * In the :doc:`third part `, you will deploy the game to the + web; you can play from any browser connected to the Internet. + +Prerequisites +------------- + +This tutorial assumes basic knowledge of Python and JavaScript. + +If you're comfortable with :doc:`virtual environments `, +you can use one for this tutorial. Else, don't worry: websockets doesn't have +any dependencies; it shouldn't create trouble in the default environment. + +If you haven't installed websockets yet, do it now: + +.. code-block:: console + + $ pip install websockets + +Confirm that websockets is installed: + +.. code-block:: console + + $ python -m websockets --version + +.. admonition:: This tutorial is written for websockets |release|. + :class: tip + + If you installed another version, you should switch to the corresponding + version of the documentation. + +Download the starter kit +------------------------ + +Create a directory and download these three files: +:download:`connect4.js <../../example/tutorial/start/connect4.js>`, +:download:`connect4.css <../../example/tutorial/start/connect4.css>`, +and :download:`connect4.py <../../example/tutorial/start/connect4.py>`. + +The JavaScript module, along with the CSS file, provides a web-based user +interface. Here's its API. + +.. js:module:: connect4 + +.. js:data:: PLAYER1 + + Color of the first player. + +.. js:data:: PLAYER2 + + Color of the second player. + +.. js:function:: createBoard(board) + + Draw a board. + + :param board: DOM element containing the board; must be initially empty. + +.. js:function:: playMove(board, player, column, row) + + Play a move. + + :param board: DOM element containing the board. + :param player: :js:data:`PLAYER1` or :js:data:`PLAYER2`. + :param column: between ``0`` and ``6``. + :param row: between ``0`` and ``5``. + +The Python module provides a class to record moves and tell when a player +wins. Here's its API. + +.. module:: connect4 + +.. data:: PLAYER1 + :value: "red" + + Color of the first player. + +.. data:: PLAYER2 + :value: "yellow" + + Color of the second player. + +.. class:: Connect4 + + A Connect Four game. + + .. method:: play(player, column) + + Play a move. + + :param player: :data:`~connect4.PLAYER1` or :data:`~connect4.PLAYER2`. + :param column: between ``0`` and ``6``. + :returns: Row where the checker lands, between ``0`` and ``5``. + :raises RuntimeError: if the move is illegal. + + .. attribute:: moves + + List of moves played during this game, as ``(player, column, row)`` + tuples. + + .. attribute:: winner + + :data:`~connect4.PLAYER1` or :data:`~connect4.PLAYER2` if they + won; :obj:`None` if the game is still ongoing. + +.. currentmodule:: websockets + +Bootstrap the web UI +-------------------- + +Create an ``index.html`` file next to ``connect4.js`` and ``connect4.css`` +with this content: + +.. literalinclude:: ../../example/tutorial/step1/index.html + :language: html + +This HTML page contains an empty ``
    `` element where you will draw the +Connect Four board. It loads a ``main.js`` script where you will write all +your JavaScript code. + +Create a ``main.js`` file next to ``index.html``. In this script, when the +page loads, draw the board: + +.. code-block:: javascript + + import { createBoard, playMove } from "./connect4.js"; + + window.addEventListener("DOMContentLoaded", () => { + // Initialize the UI. + const board = document.querySelector(".board"); + createBoard(board); + }); + +Open a shell, navigate to the directory containing these files, and start an +HTTP server: + +.. code-block:: console + + $ python -m http.server + +Open http://localhost:8000/ in a web browser. The page displays an empty board +with seven columns and six rows. You will play moves in this board later. + +Bootstrap the server +-------------------- + +Create an ``app.py`` file next to ``connect4.py`` with this content: + +.. code-block:: python + + #!/usr/bin/env python + + import asyncio + + import websockets + + + async def handler(websocket): + while True: + message = await websocket.recv() + print(message) + + + async def main(): + async with websockets.serve(handler, "", 8001): + await asyncio.Future() # run forever + + + if __name__ == "__main__": + asyncio.run(main()) + +The entry point of this program is ``asyncio.run(main())``. It creates an +asyncio event loop, runs the ``main()`` coroutine, and shuts down the loop. + +The ``main()`` coroutine calls :func:`~server.serve` to start a websockets +server. :func:`~server.serve` takes three positional arguments: + +* ``handler`` is a coroutine that manages a connection. When a client + connects, websockets calls ``handler`` with the connection in argument. + When ``handler`` terminates, websockets closes the connection. +* The second argument defines the network interfaces where the server can be + reached. Here, the server listens on all interfaces, so that other devices + on the same local network can connect. +* The third argument is the port on which the server listens. + +Invoking :func:`~server.serve` as an asynchronous context manager, in an +``async with`` block, ensures that the server shuts down properly when +terminating the program. + +For each connection, the ``handler()`` coroutine runs an infinite loop that +receives messages from the browser and prints them. + +Open a shell, navigate to the directory containing ``app.py``, and start the +server: + +.. code-block:: console + + $ python app.py + +This doesn't display anything. Hopefully the WebSocket server is running. +Let's make sure that it works. You cannot test the WebSocket server with a +web browser like you tested the HTTP server. However, you can test it with +websockets' interactive client. + +Open another shell and run this command: + +.. code-block:: console + + $ python -m websockets ws://localhost:8001/ + +You get a prompt. Type a message and press "Enter". Switch to the shell where +the server is running and check that the server received the message. Good! + +Exit the interactive client with Ctrl-C or Ctrl-D. + +Now, if you look at the console where you started the server, you can see the +stack trace of an exception: + +.. code-block:: pytb + + connection handler failed + Traceback (most recent call last): + ... + File "app.py", line 22, in handler + message = await websocket.recv() + ... + websockets.exceptions.ConnectionClosedOK: received 1000 (OK); then sent 1000 (OK) + +Indeed, the server was waiting for the next message +with :meth:`~legacy.protocol.WebSocketCommonProtocol.recv` when the client +disconnected. When this happens, websockets raises +a :exc:`~exceptions.ConnectionClosedOK` exception to let you know that you +won't receive another message on this connection. + +This exception creates noise in the server logs, making it more difficult to +spot real errors when you add functionality to the server. Catch it in the +``handler()`` coroutine: + +.. code-block:: python + + async def handler(websocket): + while True: + try: + message = await websocket.recv() + except websockets.ConnectionClosedOK: + break + print(message) + +Stop the server with Ctrl-C and start it again: + +.. code-block:: console + + $ python app.py + +.. admonition:: You must restart the WebSocket server when you make changes. + :class: tip + + The WebSocket server loads the Python code in ``app.py`` then serves every + WebSocket request with this version of the code. As a consequence, + changes to ``app.py`` aren't visible until you restart the server. + + This is unlike the HTTP server that you started earlier with ``python -m + http.server``. For every request, this HTTP server reads the target file + and sends it. That's why changes are immediately visible. + + It is possible to :doc:`restart the WebSocket server automatically + <../howto/autoreload>` but this isn't necessary for this tutorial. + +Try connecting and disconnecting the interactive client again. +The :exc:`~exceptions.ConnectionClosedOK` exception doesn't appear anymore. + +This pattern is so common that websockets provides a shortcut for iterating +over messages received on the connection until the client disconnects: + +.. code-block:: python + + async def handler(websocket): + async for message in websocket: + print(message) + +Restart the server and check with the interactive client that its behavior +didn't change. + +At this point, you bootstrapped a web application and a WebSocket server. +Let's connect them. + +Transmit from browser to server +------------------------------- + +In JavaScript, you open a WebSocket connection as follows: + +.. code-block:: javascript + + const websocket = new WebSocket("ws://localhost:8001/"); + +Before you exchange messages with the server, you need to decide their format. +There is no universal convention for this. + +Let's use JSON objects with a ``type`` key identifying the type of the event +and the rest of the object containing properties of the event. + +Here's an event describing a move in the middle slot of the board: + +.. code-block:: javascript + + const event = {type: "play", column: 3}; + +Here's how to serialize this event to JSON and send it to the server: + +.. code-block:: javascript + + websocket.send(JSON.stringify(event)); + +Now you have all the building blocks to send moves to the server. + +Add this function to ``main.js``: + +.. literalinclude:: ../../example/tutorial/step1/main.js + :language: js + :start-at: function sendMoves + :end-before: window.addEventListener + +``sendMoves()`` registers a listener for ``click`` events on the board. The +listener figures out which column was clicked, builds a event of type +``"play"``, serializes it, and sends it to the server. + +Modify the initialization to open the WebSocket connection and call the +``sendMoves()`` function: + +.. code-block:: javascript + + window.addEventListener("DOMContentLoaded", () => { + // Initialize the UI. + const board = document.querySelector(".board"); + createBoard(board); + // Open the WebSocket connection and register event handlers. + const websocket = new WebSocket("ws://localhost:8001/"); + sendMoves(board, websocket); + }); + +Check that the HTTP server and the WebSocket server are still running. If you +stopped them, here are the commands to start them again: + +.. code-block:: console + + $ python -m http.server + +.. code-block:: console + + $ python app.py + +Refresh http://localhost:8000/ in your web browser. Click various columns in +the board. The server receives messages with the expected column number. + +There isn't any feedback in the board because you haven't implemented that +yet. Let's do it. + +Transmit from server to browser +------------------------------- + +In JavaScript, you receive WebSocket messages by listening to ``message`` +events. Here's how to receive a message from the server and deserialize it +from JSON: + +.. code-block:: javascript + + websocket.addEventListener("message", ({ data }) => { + const event = JSON.parse(data); + // do something with event + }); + +You're going to need three types of messages from the server to the browser: + +.. code-block:: javascript + + {type: "play", player: "red", column: 3, row: 0} + {type: "win", player: "red"} + {type: "error", message: "This slot is full."} + +The JavaScript code receiving these messages will dispatch events depending on +their type and take appropriate action. For example, it will react to an +event of type ``"play"`` by displaying the move on the board with +the :js:func:`~connect4.playMove` function. + +Add this function to ``main.js``: + +.. literalinclude:: ../../example/tutorial/step1/main.js + :language: js + :start-at: function showMessage + :end-before: function sendMoves + +.. admonition:: Why does ``showMessage`` use ``window.setTimeout``? + :class: hint + + When :js:func:`playMove` modifies the state of the board, the browser + renders changes asynchronously. Conversely, ``window.alert()`` runs + synchronously and blocks rendering while the alert is visible. + + If you called ``window.alert()`` immediately after :js:func:`playMove`, + the browser could display the alert before rendering the move. You could + get a "Player red wins!" alert without seeing red's last move. + + We're using ``window.alert()`` for simplicity in this tutorial. A real + application would display these messages in the user interface instead. + It wouldn't be vulnerable to this problem. + +Modify the initialization to call the ``receiveMoves()`` function: + +.. literalinclude:: ../../example/tutorial/step1/main.js + :language: js + :start-at: window.addEventListener + +At this point, the user interface should receive events properly. Let's test +it by modifying the server to send some events. + +Sending an event from Python is quite similar to JavaScript: + +.. code-block:: python + + event = {"type": "play", "player": "red", "column": 3, "row": 0} + await websocket.send(json.dumps(event)) + +.. admonition:: Don't forget to serialize the event with :func:`json.dumps`. + :class: tip + + Else, websockets raises ``TypeError: data is a dict-like object``. + +Modify the ``handler()`` coroutine in ``app.py`` as follows: + +.. code-block:: python + + import json + + from connect4 import PLAYER1, PLAYER2 + + async def handler(websocket): + for player, column, row in [ + (PLAYER1, 3, 0), + (PLAYER2, 3, 1), + (PLAYER1, 4, 0), + (PLAYER2, 4, 1), + (PLAYER1, 2, 0), + (PLAYER2, 1, 0), + (PLAYER1, 5, 0), + ]: + event = { + "type": "play", + "player": player, + "column": column, + "row": row, + } + await websocket.send(json.dumps(event)) + await asyncio.sleep(0.5) + event = { + "type": "win", + "player": PLAYER1, + } + await websocket.send(json.dumps(event)) + +Restart the WebSocket server and refresh http://localhost:8000/ in your web +browser. Seven moves appear at 0.5 second intervals. Then an alert announces +the winner. + +Good! Now you know how to communicate both ways. + +Once you plug the game engine to process moves, you will have a fully +functional game. + +Add the game logic +------------------ + +In the ``handler()`` coroutine, you're going to initialize a game: + +.. code-block:: python + + from connect4 import Connect4 + + async def handler(websocket): + # Initialize a Connect Four game. + game = Connect4() + + ... + +Then, you're going to iterate over incoming messages and take these steps: + +* parse an event of type ``"play"``, the only type of event that the user + interface sends; +* play the move in the board with the :meth:`~connect4.Connect4.play` method, + alternating between the two players; +* if :meth:`~connect4.Connect4.play` raises :exc:`RuntimeError` because the + move is illegal, send an event of type ``"error"``; +* else, send an event of type ``"play"`` to tell the user interface where the + checker lands; +* if the move won the game, send an event of type ``"win"``. + +Try to implement this by yourself! + +Keep in mind that you must restart the WebSocket server and reload the page in +the browser when you make changes. + +When it works, you can play the game from a single browser, with players +taking alternate turns. + +.. admonition:: Enable debug logs to see all messages sent and received. + :class: tip + + Here's how to enable debug logs: + + .. code-block:: python + + import logging + + logging.basicConfig(format="%(message)s", level=logging.DEBUG) + +If you're stuck, a solution is available at the bottom of this document. + +Summary +------- + +In this first part of the tutorial, you learned how to: + +* build and run a WebSocket server in Python with :func:`~server.serve`; +* receive a message in a connection handler + with :meth:`~server.WebSocketServerProtocol.recv`; +* send a message in a connection handler + with :meth:`~server.WebSocketServerProtocol.send`; +* iterate over incoming messages with ``async for + message in websocket: ...``; +* open a WebSocket connection in JavaScript with the ``WebSocket`` API; +* send messages in a browser with ``WebSocket.send()``; +* receive messages in a browser by listening to ``message`` events; +* design a set of events to be exchanged between the browser and the server. + +You can now play a Connect Four game in a browser, communicating over a +WebSocket connection with a server where the game logic resides! + +However, the two players share a browser, so the constraint of being in the +same room still applies. + +Move on to the :doc:`second part ` of the tutorial to break this +constraint and play from separate browsers. + +Solution +-------- + +.. literalinclude:: ../../example/tutorial/step1/app.py + :caption: app.py + :language: python + :linenos: + +.. literalinclude:: ../../example/tutorial/step1/index.html + :caption: index.html + :language: html + :linenos: + +.. literalinclude:: ../../example/tutorial/step1/main.js + :caption: main.js + :language: js + :linenos: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/tutorial2.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/tutorial2.rst new file mode 100644 index 0000000000000..5ac4ae9dd5501 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/tutorial2.rst @@ -0,0 +1,565 @@ +Part 2 - Route & broadcast +========================== + +.. currentmodule:: websockets + +.. admonition:: This is the second part of the tutorial. + + * In the :doc:`first part `, you created a server and + connected one browser; you could play if you shared the same browser. + * In this :doc:`second part `, you will connect a second + browser; you can play from different browsers on a local network. + * In the :doc:`third part `, you will deploy the game to the + web; you can play from any browser connected to the Internet. + +In the first part of the tutorial, you opened a WebSocket connection from a +browser to a server and exchanged events to play moves. The state of the game +was stored in an instance of the :class:`~connect4.Connect4` class, +referenced as a local variable in the connection handler coroutine. + +Now you want to open two WebSocket connections from two separate browsers, one +for each player, to the same server in order to play the same game. This +requires moving the state of the game to a place where both connections can +access it. + +Share game state +---------------- + +As long as you're running a single server process, you can share state by +storing it in a global variable. + +.. admonition:: What if you need to scale to multiple server processes? + :class: hint + + In that case, you must design a way for the process that handles a given + connection to be aware of relevant events for that client. This is often + achieved with a publish / subscribe mechanism. + +How can you make two connection handlers agree on which game they're playing? +When the first player starts a game, you give it an identifier. Then, you +communicate the identifier to the second player. When the second player joins +the game, you look it up with the identifier. + +In addition to the game itself, you need to keep track of the WebSocket +connections of the two players. Since both players receive the same events, +you don't need to treat the two connections differently; you can store both +in the same set. + +Let's sketch this in code. + +A module-level :class:`dict` enables lookups by identifier: + +.. code-block:: python + + JOIN = {} + +When the first player starts the game, initialize and store it: + +.. code-block:: python + + import secrets + + async def handler(websocket): + ... + + # Initialize a Connect Four game, the set of WebSocket connections + # receiving moves from this game, and secret access token. + game = Connect4() + connected = {websocket} + + join_key = secrets.token_urlsafe(12) + JOIN[join_key] = game, connected + + try: + + ... + + finally: + del JOIN[join_key] + +When the second player joins the game, look it up: + +.. code-block:: python + + async def handler(websocket): + ... + + join_key = ... # TODO + + # Find the Connect Four game. + game, connected = JOIN[join_key] + + # Register to receive moves from this game. + connected.add(websocket) + try: + + ... + + finally: + connected.remove(websocket) + +Notice how we're carefully cleaning up global state with ``try: ... +finally: ...`` blocks. Else, we could leave references to games or +connections in global state, which would cause a memory leak. + +In both connection handlers, you have a ``game`` pointing to the same +:class:`~connect4.Connect4` instance, so you can interact with the game, +and a ``connected`` set of connections, so you can send game events to +both players as follows: + +.. code-block:: python + + async def handler(websocket): + + ... + + for connection in connected: + await connection.send(json.dumps(event)) + + ... + +Perhaps you spotted a major piece missing from the puzzle. How does the second +player obtain ``join_key``? Let's design new events to carry this information. + +To start a game, the first player sends an ``"init"`` event: + +.. code-block:: javascript + + {type: "init"} + +The connection handler for the first player creates a game as shown above and +responds with: + +.. code-block:: javascript + + {type: "init", join: ""} + +With this information, the user interface of the first player can create a +link to ``http://localhost:8000/?join=``. For the sake of simplicity, +we will assume that the first player shares this link with the second player +outside of the application, for example via an instant messaging service. + +To join the game, the second player sends a different ``"init"`` event: + +.. code-block:: javascript + + {type: "init", join: ""} + +The connection handler for the second player can look up the game with the +join key as shown above. There is no need to respond. + +Let's dive into the details of implementing this design. + +Start a game +------------ + +We'll start with the initialization sequence for the first player. + +In ``main.js``, define a function to send an initialization event when the +WebSocket connection is established, which triggers an ``open`` event: + +.. code-block:: javascript + + function initGame(websocket) { + websocket.addEventListener("open", () => { + // Send an "init" event for the first player. + const event = { type: "init" }; + websocket.send(JSON.stringify(event)); + }); + } + +Update the initialization sequence to call ``initGame()``: + +.. literalinclude:: ../../example/tutorial/step2/main.js + :language: js + :start-at: window.addEventListener + +In ``app.py``, define a new ``handler`` coroutine — keep a copy of the +previous one to reuse it later: + +.. code-block:: python + + import secrets + + + JOIN = {} + + + async def start(websocket): + # Initialize a Connect Four game, the set of WebSocket connections + # receiving moves from this game, and secret access token. + game = Connect4() + connected = {websocket} + + join_key = secrets.token_urlsafe(12) + JOIN[join_key] = game, connected + + try: + # Send the secret access token to the browser of the first player, + # where it'll be used for building a "join" link. + event = { + "type": "init", + "join": join_key, + } + await websocket.send(json.dumps(event)) + + # Temporary - for testing. + print("first player started game", id(game)) + async for message in websocket: + print("first player sent", message) + + finally: + del JOIN[join_key] + + + async def handler(websocket): + # Receive and parse the "init" event from the UI. + message = await websocket.recv() + event = json.loads(message) + assert event["type"] == "init" + + # First player starts a new game. + await start(websocket) + +In ``index.html``, add an ```` element to display the link to share with +the other player. + +.. code-block:: html + + + + + + +In ``main.js``, modify ``receiveMoves()`` to handle the ``"init"`` message and +set the target of that link: + +.. code-block:: javascript + + switch (event.type) { + case "init": + // Create link for inviting the second player. + document.querySelector(".join").href = "?join=" + event.join; + break; + // ... + } + +Restart the WebSocket server and reload http://localhost:8000/ in the browser. +There's a link labeled JOIN below the board with a target that looks like +http://localhost:8000/?join=95ftAaU5DJVP1zvb. + +The server logs say ``first player started game ...``. If you click the board, +you see ``"play"`` events. There is no feedback in the UI, though, because +you haven't restored the game logic yet. + +Before we get there, let's handle links with a ``join`` query parameter. + +Join a game +----------- + +We'll now update the initialization sequence to account for the second +player. + +In ``main.js``, update ``initGame()`` to send the join key in the ``"init"`` +message when it's in the URL: + +.. code-block:: javascript + + function initGame(websocket) { + websocket.addEventListener("open", () => { + // Send an "init" event according to who is connecting. + const params = new URLSearchParams(window.location.search); + let event = { type: "init" }; + if (params.has("join")) { + // Second player joins an existing game. + event.join = params.get("join"); + } else { + // First player starts a new game. + } + websocket.send(JSON.stringify(event)); + }); + } + +In ``app.py``, update the ``handler`` coroutine to look for the join key in +the ``"init"`` message, then load that game: + +.. code-block:: python + + async def error(websocket, message): + event = { + "type": "error", + "message": message, + } + await websocket.send(json.dumps(event)) + + + async def join(websocket, join_key): + # Find the Connect Four game. + try: + game, connected = JOIN[join_key] + except KeyError: + await error(websocket, "Game not found.") + return + + # Register to receive moves from this game. + connected.add(websocket) + try: + + # Temporary - for testing. + print("second player joined game", id(game)) + async for message in websocket: + print("second player sent", message) + + finally: + connected.remove(websocket) + + + async def handler(websocket): + # Receive and parse the "init" event from the UI. + message = await websocket.recv() + event = json.loads(message) + assert event["type"] == "init" + + if "join" in event: + # Second player joins an existing game. + await join(websocket, event["join"]) + else: + # First player starts a new game. + await start(websocket) + +Restart the WebSocket server and reload http://localhost:8000/ in the browser. + +Copy the link labeled JOIN and open it in another browser. You may also open +it in another tab or another window of the same browser; however, that makes +it a bit tricky to remember which one is the first or second player. + +.. admonition:: You must start a new game when you restart the server. + :class: tip + + Since games are stored in the memory of the Python process, they're lost + when you stop the server. + + Whenever you make changes to ``app.py``, you must restart the server, + create a new game in a browser, and join it in another browser. + +The server logs say ``first player started game ...`` and ``second player +joined game ...``. The numbers match, proving that the ``game`` local +variable in both connection handlers points to same object in the memory of +the Python process. + +Click the board in either browser. The server receives ``"play"`` events from +the corresponding player. + +In the initialization sequence, you're routing connections to ``start()`` or +``join()`` depending on the first message received by the server. This is a +common pattern in servers that handle different clients. + +.. admonition:: Why not use different URIs for ``start()`` and ``join()``? + :class: hint + + Instead of sending an initialization event, you could encode the join key + in the WebSocket URI e.g. ``ws://localhost:8001/join/``. The + WebSocket server would parse ``websocket.path`` and route the connection, + similar to how HTTP servers route requests. + + When you need to send sensitive data like authentication credentials to + the server, sending it an event is considered more secure than encoding + it in the URI because URIs end up in logs. + + For the purposes of this tutorial, both approaches are equivalent because + the join key comes from an HTTP URL. There isn't much at risk anyway! + +Now you can restore the logic for playing moves and you'll have a fully +functional two-player game. + +Add the game logic +------------------ + +Once the initialization is done, the game is symmetrical, so you can write a +single coroutine to process the moves of both players: + +.. code-block:: python + + async def play(websocket, game, player, connected): + ... + +With such a coroutine, you can replace the temporary code for testing in +``start()`` by: + +.. code-block:: python + + await play(websocket, game, PLAYER1, connected) + +and in ``join()`` by: + +.. code-block:: python + + await play(websocket, game, PLAYER2, connected) + +The ``play()`` coroutine will reuse much of the code you wrote in the first +part of the tutorial. + +Try to implement this by yourself! + +Keep in mind that you must restart the WebSocket server, reload the page to +start a new game with the first player, copy the JOIN link, and join the game +with the second player when you make changes. + +When ``play()`` works, you can play the game from two separate browsers, +possibly running on separate computers on the same local network. + +A complete solution is available at the bottom of this document. + +Watch a game +------------ + +Let's add one more feature: allow spectators to watch the game. + +The process for inviting a spectator can be the same as for inviting the +second player. You will have to duplicate all the initialization logic: + +- declare a ``WATCH`` global variable similar to ``JOIN``; +- generate a watch key when creating a game; it must be different from the + join key, or else a spectator could hijack a game by tweaking the URL; +- include the watch key in the ``"init"`` event sent to the first player; +- generate a WATCH link in the UI with a ``watch`` query parameter; +- update the ``initGame()`` function to handle such links; +- update the ``handler()`` coroutine to invoke a ``watch()`` coroutine for + spectators; +- prevent ``sendMoves()`` from sending ``"play"`` events for spectators. + +Once the initialization sequence is done, watching a game is as simple as +registering the WebSocket connection in the ``connected`` set in order to +receive game events and doing nothing until the spectator disconnects. You +can wait for a connection to terminate with +:meth:`~legacy.protocol.WebSocketCommonProtocol.wait_closed`: + +.. code-block:: python + + async def watch(websocket, watch_key): + + ... + + connected.add(websocket) + try: + await websocket.wait_closed() + finally: + connected.remove(websocket) + +The connection can terminate because the ``receiveMoves()`` function closed it +explicitly after receiving a ``"win"`` event, because the spectator closed +their browser, or because the network failed. + +Again, try to implement this by yourself. + +When ``watch()`` works, you can invite spectators to watch the game from other +browsers, as long as they're on the same local network. + +As a further improvement, you may support adding spectators while a game is +already in progress. This requires replaying moves that were played before +the spectator was added to the ``connected`` set. Past moves are available in +the :attr:`~connect4.Connect4.moves` attribute of the game. + +This feature is included in the solution proposed below. + +Broadcast +--------- + +When you need to send a message to the two players and to all spectators, +you're using this pattern: + +.. code-block:: python + + async def handler(websocket): + + ... + + for connection in connected: + await connection.send(json.dumps(event)) + + ... + +Since this is a very common pattern in WebSocket servers, websockets provides +the :func:`broadcast` helper for this purpose: + +.. code-block:: python + + async def handler(websocket): + + ... + + websockets.broadcast(connected, json.dumps(event)) + + ... + +Calling :func:`broadcast` once is more efficient than +calling :meth:`~legacy.protocol.WebSocketCommonProtocol.send` in a loop. + +However, there's a subtle difference in behavior. Did you notice that there's +no ``await`` in the second version? Indeed, :func:`broadcast` is a function, +not a coroutine like :meth:`~legacy.protocol.WebSocketCommonProtocol.send` +or :meth:`~legacy.protocol.WebSocketCommonProtocol.recv`. + +It's quite obvious why :meth:`~legacy.protocol.WebSocketCommonProtocol.recv` +is a coroutine. When you want to receive the next message, you have to wait +until the client sends it and the network transmits it. + +It's less obvious why :meth:`~legacy.protocol.WebSocketCommonProtocol.send` is +a coroutine. If you send many messages or large messages, you could write +data faster than the network can transmit it or the client can read it. Then, +outgoing data will pile up in buffers, which will consume memory and may +crash your application. + +To avoid this problem, :meth:`~legacy.protocol.WebSocketCommonProtocol.send` +waits until the write buffer drains. By slowing down the application as +necessary, this ensures that the server doesn't send data too quickly. This +is called backpressure and it's useful for building robust systems. + +That said, when you're sending the same messages to many clients in a loop, +applying backpressure in this way can become counterproductive. When you're +broadcasting, you don't want to slow down everyone to the pace of the slowest +clients; you want to drop clients that cannot keep up with the data stream. +That's why :func:`broadcast` doesn't wait until write buffers drain. + +For our Connect Four game, there's no difference in practice: the total amount +of data sent on a connection for a game of Connect Four is less than 64 KB, +so the write buffer never fills up and backpressure never kicks in anyway. + +Summary +------- + +In this second part of the tutorial, you learned how to: + +* configure a connection by exchanging initialization messages; +* keep track of connections within a single server process; +* wait until a client disconnects in a connection handler; +* broadcast a message to many connections efficiently. + +You can now play a Connect Four game from separate browser, communicating over +WebSocket connections with a server that synchronizes the game logic! + +However, the two players have to be on the same local network as the server, +so the constraint of being in the same place still mostly applies. + +Head over to the :doc:`third part ` of the tutorial to deploy the +game to the web and remove this constraint. + +Solution +-------- + +.. literalinclude:: ../../example/tutorial/step2/app.py + :caption: app.py + :language: python + :linenos: + +.. literalinclude:: ../../example/tutorial/step2/index.html + :caption: index.html + :language: html + :linenos: + +.. literalinclude:: ../../example/tutorial/step2/main.js + :caption: main.js + :language: js + :linenos: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/tutorial3.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/tutorial3.rst new file mode 100644 index 0000000000000..6fdec113b2ad9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/tutorial3.rst @@ -0,0 +1,290 @@ +Part 3 - Deploy to the web +========================== + +.. currentmodule:: websockets + +.. admonition:: This is the third part of the tutorial. + + * In the :doc:`first part `, you created a server and + connected one browser; you could play if you shared the same browser. + * In this :doc:`second part `, you connected a second browser; + you could play from different browsers on a local network. + * In this :doc:`third part `, you will deploy the game to the + web; you can play from any browser connected to the Internet. + +In the first and second parts of the tutorial, for local development, you ran +an HTTP server on ``http://localhost:8000/`` with: + +.. code-block:: console + + $ python -m http.server + +and a WebSocket server on ``ws://localhost:8001/`` with: + +.. code-block:: console + + $ python app.py + +Now you want to deploy these servers on the Internet. There's a vast range of +hosting providers to choose from. For the sake of simplicity, we'll rely on: + +* GitHub Pages for the HTTP server; +* Heroku for the WebSocket server. + +Commit project to git +--------------------- + +Perhaps you committed your work to git while you were progressing through the +tutorial. If you didn't, now is a good time, because GitHub and Heroku offer +git-based deployment workflows. + +Initialize a git repository: + +.. code-block:: console + + $ git init -b main + Initialized empty Git repository in websockets-tutorial/.git/ + $ git commit --allow-empty -m "Initial commit." + [main (root-commit) ...] Initial commit. + +Add all files and commit: + +.. code-block:: console + + $ git add . + $ git commit -m "Initial implementation of Connect Four game." + [main ...] Initial implementation of Connect Four game. + 6 files changed, 500 insertions(+) + create mode 100644 app.py + create mode 100644 connect4.css + create mode 100644 connect4.js + create mode 100644 connect4.py + create mode 100644 index.html + create mode 100644 main.js + +Prepare the WebSocket server +---------------------------- + +Before you deploy the server, you must adapt it to meet requirements of +Heroku's runtime. This involves two small changes: + +1. Heroku expects the server to `listen on a specific port`_, provided in the + ``$PORT`` environment variable. + +2. Heroku sends a ``SIGTERM`` signal when `shutting down a dyno`_, which + should trigger a clean exit. + +.. _listen on a specific port: https://devcenter.heroku.com/articles/preparing-a-codebase-for-heroku-deployment#4-listen-on-the-correct-port + +.. _shutting down a dyno: https://devcenter.heroku.com/articles/dynos#shutdown + +Adapt the ``main()`` coroutine accordingly: + +.. code-block:: python + + import os + import signal + +.. literalinclude:: ../../example/tutorial/step3/app.py + :pyobject: main + +To catch the ``SIGTERM`` signal, ``main()`` creates a :class:`~asyncio.Future` +called ``stop`` and registers a signal handler that sets the result of this +future. The value of the future doesn't matter; it's only for waiting for +``SIGTERM``. + +Then, by using :func:`~server.serve` as a context manager and exiting the +context when ``stop`` has a result, ``main()`` ensures that the server closes +connections cleanly and exits on ``SIGTERM``. + +The app is now fully compatible with Heroku. + +Deploy the WebSocket server +--------------------------- + +Create a ``requirements.txt`` file with this content to install ``websockets`` +when building the image: + +.. literalinclude:: ../../example/tutorial/step3/requirements.txt + :language: text + +.. admonition:: Heroku treats ``requirements.txt`` as a signal to `detect a Python app`_. + :class: tip + + That's why you don't need to declare that you need a Python runtime. + +.. _detect a Python app: https://devcenter.heroku.com/articles/python-support#recognizing-a-python-app + +Create a ``Procfile`` file with this content to configure the command for +running the server: + +.. literalinclude:: ../../example/tutorial/step3/Procfile + :language: text + +Commit your changes: + +.. code-block:: console + + $ git add . + $ git commit -m "Deploy to Heroku." + [main ...] Deploy to Heroku. + 3 files changed, 12 insertions(+), 2 deletions(-) + create mode 100644 Procfile + create mode 100644 requirements.txt + +Follow the `set-up instructions`_ to install the Heroku CLI and to log in, if +you haven't done that yet. + +.. _set-up instructions: https://devcenter.heroku.com/articles/getting-started-with-python#set-up + +Create a Heroku app. You must choose a unique name and replace +``websockets-tutorial`` by this name in the following command: + +.. code-block:: console + + $ heroku create websockets-tutorial + Creating ⬢ websockets-tutorial... done + https://websockets-tutorial.herokuapp.com/ | https://git.heroku.com/websockets-tutorial.git + +If you reuse a name that someone else already uses, you will receive this +error; if this happens, try another name: + +.. code-block:: console + + $ heroku create websockets-tutorial + Creating ⬢ websockets-tutorial... ! + ▸ Name websockets-tutorial is already taken + +Deploy by pushing the code to Heroku: + +.. code-block:: console + + $ git push heroku + + ... lots of output... + + remote: Released v1 + remote: https://websockets-tutorial.herokuapp.com/ deployed to Heroku + remote: + remote: Verifying deploy... done. + To https://git.heroku.com/websockets-tutorial.git + * [new branch] main -> main + +You can test the WebSocket server with the interactive client exactly like you +did in the first part of the tutorial. Replace ``websockets-tutorial`` by the +name of your app in the following command: + +.. code-block:: console + + $ python -m websockets wss://websockets-tutorial.herokuapp.com/ + Connected to wss://websockets-tutorial.herokuapp.com/. + > {"type": "init"} + < {"type": "init", "join": "54ICxFae_Ip7TJE2", "watch": "634w44TblL5Dbd9a"} + Connection closed: 1000 (OK). + +It works! + +Prepare the web application +--------------------------- + +Before you deploy the web application, perhaps you're wondering how it will +locate the WebSocket server? Indeed, at this point, its address is hard-coded +in ``main.js``: + +.. code-block:: javascript + + const websocket = new WebSocket("ws://localhost:8001/"); + +You can take this strategy one step further by checking the address of the +HTTP server and determining the address of the WebSocket server accordingly. + +Add this function to ``main.js``; replace ``python-websockets`` by your GitHub +username and ``websockets-tutorial`` by the name of your app on Heroku: + +.. literalinclude:: ../../example/tutorial/step3/main.js + :language: js + :start-at: function getWebSocketServer + :end-before: function initGame + +Then, update the initialization to connect to this address instead: + +.. code-block:: javascript + + const websocket = new WebSocket(getWebSocketServer()); + +Commit your changes: + +.. code-block:: console + + $ git add . + $ git commit -m "Configure WebSocket server address." + [main ...] Configure WebSocket server address. + 1 file changed, 11 insertions(+), 1 deletion(-) + +Deploy the web application +-------------------------- + +Go to GitHub and create a new repository called ``websockets-tutorial``. + +Push your code to this repository. You must replace ``python-websockets`` by +your GitHub username in the following command: + +.. code-block:: console + + $ git remote add origin git@github.com:python-websockets/websockets-tutorial.git + $ git push -u origin main + Enumerating objects: 11, done. + Counting objects: 100% (11/11), done. + Delta compression using up to 8 threads + Compressing objects: 100% (10/10), done. + Writing objects: 100% (11/11), 5.90 KiB | 2.95 MiB/s, done. + Total 11 (delta 0), reused 0 (delta 0), pack-reused 0 + To github.com:/websockets-tutorial.git + * [new branch] main -> main + Branch 'main' set up to track remote branch 'main' from 'origin'. + +Go back to GitHub, open the Settings tab of the repository and select Pages in +the menu. Select the main branch as source and click Save. GitHub tells you +that your site is published. + +Follow the link and start a game! + +Summary +------- + +In this third part of the tutorial, you learned how to deploy a WebSocket +application with Heroku. + +You can start a Connect Four game, send the JOIN link to a friend, and play +over the Internet! + +Congratulations for completing the tutorial. Enjoy building real-time web +applications with websockets! + +Solution +-------- + +.. literalinclude:: ../../example/tutorial/step3/app.py + :caption: app.py + :language: python + :linenos: + +.. literalinclude:: ../../example/tutorial/step3/index.html + :caption: index.html + :language: html + :linenos: + +.. literalinclude:: ../../example/tutorial/step3/main.js + :caption: main.js + :language: js + :linenos: + +.. literalinclude:: ../../example/tutorial/step3/Procfile + :caption: Procfile + :language: text + :linenos: + +.. literalinclude:: ../../example/tutorial/step3/requirements.txt + :caption: requirements.txt + :language: text + :linenos: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/w3c-import.log new file mode 100644 index 0000000000000..91432256b6066 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/tutorial1.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/tutorial2.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/intro/tutorial3.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/make.bat b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/make.bat new file mode 100644 index 0000000000000..2119f51099bf3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/changelog.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/changelog.rst new file mode 100644 index 0000000000000..264e6e42d1c1c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/changelog.rst @@ -0,0 +1,1230 @@ +Changelog +========= + +.. currentmodule:: websockets + +.. _backwards-compatibility policy: + +Backwards-compatibility policy +------------------------------ + +websockets is intended for production use. Therefore, stability is a goal. + +websockets also aims at providing the best API for WebSocket in Python. + +While we value stability, we value progress more. When an improvement requires +changing a public API, we make the change and document it in this changelog. + +When possible with reasonable effort, we preserve backwards-compatibility for +five years after the release that introduced the change. + +When a release contains backwards-incompatible API changes, the major version +is increased, else the minor version is increased. Patch versions are only for +fixing regressions shortly after a release. + +Only documented APIs are public. Undocumented, private APIs may change without +notice. + +12.0 +---- + +*October 21, 2023* + +Backwards-incompatible changes +.............................. + +.. admonition:: websockets 12.0 requires Python ≥ 3.8. + :class: tip + + websockets 11.0 is the last version supporting Python 3.7. + +Improvements +............ + +* Made convenience imports from ``websockets`` compatible with static code + analysis tools such as auto-completion in an IDE or type checking with mypy_. + + .. _mypy: https://github.com/python/mypy + +* Accepted a plain :class:`int` where an :class:`~http.HTTPStatus` is expected. + +* Added :class:`~frames.CloseCode`. + +11.0.3 +------ + +*May 7, 2023* + +Bug fixes +......... + +* Fixed the :mod:`threading` implementation of servers on Windows. + +11.0.2 +------ + +*April 18, 2023* + +Bug fixes +......... + +* Fixed a deadlock in the :mod:`threading` implementation when closing a + connection without reading all messages. + +11.0.1 +------ + +*April 6, 2023* + +Bug fixes +......... + +* Restored the C extension in the source distribution. + +11.0 +---- + +*April 2, 2023* + +Backwards-incompatible changes +.............................. + +.. admonition:: The Sans-I/O implementation was moved. + :class: caution + + Aliases provide compatibility for all previously public APIs according to + the `backwards-compatibility policy`_. + + * The ``connection`` module was renamed to ``protocol``. + + * The ``connection.Connection``, ``server.ServerConnection``, and + ``client.ClientConnection`` classes were renamed to ``protocol.Protocol``, + ``server.ServerProtocol``, and ``client.ClientProtocol``. + +.. admonition:: Sans-I/O protocol constructors now use keyword-only arguments. + :class: caution + + If you instantiate :class:`~server.ServerProtocol` or + :class:`~client.ClientProtocol` directly, make sure you are using keyword + arguments. + +.. admonition:: Closing a connection without an empty close frame is OK. + :class: note + + Receiving an empty close frame now results in + :exc:`~exceptions.ConnectionClosedOK` instead of + :exc:`~exceptions.ConnectionClosedError`. + + As a consequence, calling ``WebSocket.close()`` without arguments in a + browser isn't reported as an error anymore. + +.. admonition:: :func:`~server.serve` times out on the opening handshake after 10 seconds by default. + :class: note + + You can adjust the timeout with the ``open_timeout`` parameter. Set it to + :obj:`None` to disable the timeout entirely. + +New features +............ + +.. admonition:: websockets 11.0 introduces a implementation on top of :mod:`threading`. + :class: important + + It may be more convenient if you don't need to manage many connections and + you're more comfortable with :mod:`threading` than :mod:`asyncio`. + + It is particularly suited to client applications that establish only one + connection. It may be used for servers handling few connections. + + See :func:`~sync.client.connect` and :func:`~sync.server.serve` for details. + +* Added ``open_timeout`` to :func:`~server.serve`. + +* Made it possible to close a server without closing existing connections. + +* Added :attr:`~server.ServerProtocol.select_subprotocol` to customize + negotiation of subprotocols in the Sans-I/O layer. + +Improvements +............ + +* Added platform-independent wheels. + +* Improved error handling in :func:`~websockets.broadcast`. + +* Set ``server_hostname`` automatically on TLS connections when providing a + ``sock`` argument to :func:`~sync.client.connect`. + +10.4 +---- + +*October 25, 2022* + +New features +............ + +* Validated compatibility with Python 3.11. + +* Added the :attr:`~legacy.protocol.WebSocketCommonProtocol.latency` property to + protocols. + +* Changed :attr:`~legacy.protocol.WebSocketCommonProtocol.ping` to return the + latency of the connection. + +* Supported overriding or removing the ``User-Agent`` header in clients and the + ``Server`` header in servers. + +* Added deployment guides for more Platform as a Service providers. + +Improvements +............ + +* Improved FAQ. + +10.3 +---- + +*April 17, 2022* + +Backwards-incompatible changes +.............................. + +.. admonition:: The ``exception`` attribute of :class:`~http11.Request` and :class:`~http11.Response` is deprecated. + :class: note + + Use the ``handshake_exc`` attribute of :class:`~server.ServerProtocol` and + :class:`~client.ClientProtocol` instead. + + See :doc:`../howto/sansio` for details. + +Improvements +............ + +* Reduced noise in logs when :mod:`ssl` or :mod:`zlib` raise exceptions. + +10.2 +---- + +*February 21, 2022* + +Improvements +............ + +* Made compression negotiation more lax for compatibility with Firefox. + +* Improved FAQ and quick start guide. + +Bug fixes +......... + +* Fixed backwards-incompatibility in 10.1 for connection handlers created with + :func:`functools.partial`. + +* Avoided leaking open sockets when :func:`~client.connect` is canceled. + +10.1 +---- + +*November 14, 2021* + +New features +............ + +* Added a tutorial. + +* Made the second parameter of connection handlers optional. It will be + deprecated in the next major release. The request path is available in + the :attr:`~legacy.protocol.WebSocketCommonProtocol.path` attribute of + the first argument. + + If you implemented the connection handler of a server as:: + + async def handler(request, path): + ... + + You should replace it by:: + + async def handler(request): + path = request.path # if handler() uses the path argument + ... + +* Added ``python -m websockets --version``. + +Improvements +............ + +* Added wheels for Python 3.10, PyPy 3.7, and for more platforms. + +* Reverted optimization of default compression settings for clients, mainly to + avoid triggering bugs in poorly implemented servers like `AWS API Gateway`_. + + .. _AWS API Gateway: https://github.com/python-websockets/websockets/issues/1065 + +* Mirrored the entire :class:`~asyncio.Server` API + in :class:`~server.WebSocketServer`. + +* Improved performance for large messages on ARM processors. + +* Documented how to auto-reload on code changes in development. + +Bug fixes +......... + +* Avoided half-closing TCP connections that are already closed. + +10.0 +---- + +*September 9, 2021* + +Backwards-incompatible changes +.............................. + +.. admonition:: websockets 10.0 requires Python ≥ 3.7. + :class: tip + + websockets 9.1 is the last version supporting Python 3.6. + +.. admonition:: The ``loop`` parameter is deprecated from all APIs. + :class: caution + + This reflects a decision made in Python 3.8. See the release notes of + Python 3.10 for details. + + The ``loop`` parameter is also removed + from :class:`~server.WebSocketServer`. This should be transparent. + +.. admonition:: :func:`~client.connect` times out after 10 seconds by default. + :class: note + + You can adjust the timeout with the ``open_timeout`` parameter. Set it to + :obj:`None` to disable the timeout entirely. + +.. admonition:: The ``legacy_recv`` option is deprecated. + :class: note + + See the release notes of websockets 3.0 for details. + +.. admonition:: The signature of :exc:`~exceptions.ConnectionClosed` changed. + :class: note + + If you raise :exc:`~exceptions.ConnectionClosed` or a subclass, rather + than catch them when websockets raises them, you must change your code. + +.. admonition:: A ``msg`` parameter was added to :exc:`~exceptions.InvalidURI`. + :class: note + + If you raise :exc:`~exceptions.InvalidURI`, rather than catch it when + websockets raises it, you must change your code. + +New features +............ + +.. admonition:: websockets 10.0 introduces a `Sans-I/O API + `_ for easier integration + in third-party libraries. + :class: important + + If you're integrating websockets in a library, rather than just using it, + look at the :doc:`Sans-I/O integration guide <../howto/sansio>`. + +* Added compatibility with Python 3.10. + +* Added :func:`~websockets.broadcast` to send a message to many clients. + +* Added support for reconnecting automatically by using + :func:`~client.connect` as an asynchronous iterator. + +* Added ``open_timeout`` to :func:`~client.connect`. + +* Documented how to integrate with `Django `_. + +* Documented how to deploy websockets in production, with several options. + +* Documented how to authenticate connections. + +* Documented how to broadcast messages to many connections. + +Improvements +............ + +* Improved logging. See the :doc:`logging guide <../topics/logging>`. + +* Optimized default compression settings to reduce memory usage. + +* Optimized processing of client-to-server messages when the C extension isn't + available. + +* Supported relative redirects in :func:`~client.connect`. + +* Handled TCP connection drops during the opening handshake. + +* Made it easier to customize authentication with + :meth:`~auth.BasicAuthWebSocketServerProtocol.check_credentials`. + +* Provided additional information in :exc:`~exceptions.ConnectionClosed` + exceptions. + +* Clarified several exceptions or log messages. + +* Restructured documentation. + +* Improved API documentation. + +* Extended FAQ. + +Bug fixes +......... + +* Avoided a crash when receiving a ping while the connection is closing. + +9.1 +--- + +*May 27, 2021* + +Security fix +............ + +.. admonition:: websockets 9.1 fixes a security issue introduced in 8.0. + :class: important + + Version 8.0 was vulnerable to timing attacks on HTTP Basic Auth passwords + (`CVE-2021-33880`_). + + .. _CVE-2021-33880: https://nvd.nist.gov/vuln/detail/CVE-2021-33880 + +9.0.2 +----- + +*May 15, 2021* + +Bug fixes +......... + +* Restored compatibility of ``python -m websockets`` with Python < 3.9. + +* Restored compatibility with mypy. + +9.0.1 +----- + +*May 2, 2021* + +Bug fixes +......... + +* Fixed issues with the packaging of the 9.0 release. + +9.0 +--- + +*May 1, 2021* + +Backwards-incompatible changes +.............................. + +.. admonition:: Several modules are moved or deprecated. + :class: caution + + Aliases provide compatibility for all previously public APIs according to + the `backwards-compatibility policy`_ + + * :class:`~datastructures.Headers` and + :exc:`~datastructures.MultipleValuesError` are moved from + ``websockets.http`` to :mod:`websockets.datastructures`. If you're using + them, you should adjust the import path. + + * The ``client``, ``server``, ``protocol``, and ``auth`` modules were + moved from the ``websockets`` package to a ``websockets.legacy`` + sub-package. Despite the name, they're still fully supported. + + * The ``framing``, ``handshake``, ``headers``, ``http``, and ``uri`` + modules in the ``websockets`` package are deprecated. These modules + provided low-level APIs for reuse by other projects, but they didn't + reach that goal. Keeping these APIs public makes it more difficult to + improve websockets. + + These changes pave the path for a refactoring that should be a transparent + upgrade for most uses and facilitate integration by other projects. + +.. admonition:: Convenience imports from ``websockets`` are performed lazily. + :class: note + + While Python supports this, tools relying on static code analysis don't. + This breaks auto-completion in an IDE or type checking with mypy_. + + .. _mypy: https://github.com/python/mypy + + If you depend on such tools, use the real import paths, which can be found + in the API documentation, for example:: + + from websockets.client import connect + from websockets.server import serve + +New features +............ + +* Added compatibility with Python 3.9. + +Improvements +............ + +* Added support for IRIs in addition to URIs. + +* Added close codes 1012, 1013, and 1014. + +* Raised an error when passing a :class:`dict` to + :meth:`~legacy.protocol.WebSocketCommonProtocol.send`. + +* Improved error reporting. + +Bug fixes +......... + +* Fixed sending fragmented, compressed messages. + +* Fixed ``Host`` header sent when connecting to an IPv6 address. + +* Fixed creating a client or a server with an existing Unix socket. + +* Aligned maximum cookie size with popular web browsers. + +* Ensured cancellation always propagates, even on Python versions where + :exc:`~asyncio.CancelledError` inherits :exc:`Exception`. + +8.1 +--- + +*November 1, 2019* + +New features +............ + +* Added compatibility with Python 3.8. + +8.0.2 +----- + +*July 31, 2019* + +Bug fixes +......... + +* 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 +----- + +*July 21, 2019* + +Bug fixes +......... + +* Restored the ability to import ``WebSocketProtocolError`` from + ``websockets``. + +8.0 +--- + +*July 7, 2019* + +Backwards-incompatible changes +.............................. + +.. admonition:: websockets 8.0 requires Python ≥ 3.6. + :class: tip + + websockets 7.0 is the last version supporting Python 3.4 and 3.5. + +.. admonition:: ``process_request`` is now expected to be a coroutine. + :class: note + + If you're passing a ``process_request`` argument to + :func:`~server.serve` or :class:`~server.WebSocketServerProtocol`, or if + you're overriding + :meth:`~server.WebSocketServerProtocol.process_request` in a subclass, + define it with ``async def`` instead of ``def``. Previously, both were supported. + + For backwards compatibility, functions are still accepted, but mixing + functions and coroutines won't work in some inheritance scenarios. + +.. admonition:: ``max_queue`` must be :obj:`None` to disable the limit. + :class: note + + If you were setting ``max_queue=0`` to make the queue of incoming messages + unbounded, change it to ``max_queue=None``. + +.. admonition:: The ``host``, ``port``, and ``secure`` attributes + of :class:`~legacy.protocol.WebSocketCommonProtocol` are deprecated. + :class: note + + Use :attr:`~legacy.protocol.WebSocketCommonProtocol.local_address` in + servers and + :attr:`~legacy.protocol.WebSocketCommonProtocol.remote_address` in clients + instead of ``host`` and ``port``. + +.. admonition:: ``WebSocketProtocolError`` is renamed + to :exc:`~exceptions.ProtocolError`. + :class: note + + An alias provides backwards compatibility. + +.. admonition:: ``read_response()`` now returns the reason phrase. + :class: note + + If you're using this low-level API, you must change your code. + +New features +............ + +* 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. + +* Added support for asynchronous generators + in :meth:`~legacy.protocol.WebSocketCommonProtocol.send` + to generate fragmented messages incrementally. + +* 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. + +Improvements +............ + +* :meth:`~legacy.protocol.WebSocketCommonProtocol.send`, + :meth:`~legacy.protocol.WebSocketCommonProtocol.ping`, and + :meth:`~legacy.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. + +* Changed :meth:`WebSocketServer.close() + ` to perform a proper closing handshake + instead of failing the connection. + +* Improved error messages when HTTP parsing fails. + +* Improved API documentation. + +Bug fixes +......... + +* 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. + +* Avoided a crash when a ``extra_headers`` callable returns :obj:`None`. + +7.0 +--- + +*November 1, 2018* + +Backwards-incompatible changes +.............................. + +.. admonition:: Keepalive is enabled by default. + :class: important + + websockets now sends Ping frames at regular intervals and closes the + connection if it doesn't receive a matching Pong frame. + See :class:`~legacy.protocol.WebSocketCommonProtocol` for details. + +.. admonition:: Termination of connections by :meth:`WebSocketServer.close() + ` changes. + :class: caution + + 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()) + +.. admonition:: Calling :meth:`~legacy.protocol.WebSocketCommonProtocol.recv` + concurrently raises a :exc:`RuntimeError`. + :class: note + + Concurrent calls lead to non-deterministic behavior because there are no + guarantees about which coroutine will receive which message. + +.. admonition:: The ``timeout`` argument of :func:`~server.serve` + and :func:`~client.connect` is renamed to ``close_timeout`` . + :class: note + + This prevents confusion with ``ping_timeout``. + + For backwards compatibility, ``timeout`` is still supported. + +.. admonition:: The ``origins`` argument of :func:`~server.serve` changes. + :class: note + + Include :obj:`None` in the list rather than ``''`` to allow requests that + don't contain an Origin header. + +.. admonition:: Pending pings aren't canceled when the connection is closed. + :class: note + + A ping — as in ``ping = await websocket.ping()`` — for which no pong was + received yet 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. + +New features +............ + +* Added ``process_request`` and ``select_subprotocol`` arguments to + :func:`~server.serve` and + :class:`~server.WebSocketServerProtocol` to facilitate customization of + :meth:`~server.WebSocketServerProtocol.process_request` and + :meth:`~server.WebSocketServerProtocol.select_subprotocol`. + +* Added support for sending fragmented messages. + +* Added the :meth:`~legacy.protocol.WebSocketCommonProtocol.wait_closed` + method to protocols. + +* Added an interactive client: ``python -m websockets ``. + +Improvements +............ + +* Improved handling of multiple HTTP headers with the same name. + +* Improved error messages when a required HTTP header is missing. + +Bug fixes +......... + +* Fixed a data loss bug in + :meth:`~legacy.protocol.WebSocketCommonProtocol.recv`: + canceling it at the wrong time could result in messages being dropped. + +6.0 +--- + +*July 16, 2018* + +Backwards-incompatible changes +.............................. + +.. admonition:: The :class:`~datastructures.Headers` class is introduced and + several APIs are updated to use it. + :class: caution + + * The ``request_headers`` argument + of :meth:`~server.WebSocketServerProtocol.process_request` is now + a :class:`~datastructures.Headers` instead of + an ``http.client.HTTPMessage``. + + * The ``request_headers`` and ``response_headers`` attributes of + :class:`~legacy.protocol.WebSocketCommonProtocol` are now + :class:`~datastructures.Headers` instead of ``http.client.HTTPMessage``. + + * The ``raw_request_headers`` and ``raw_response_headers`` attributes of + :class:`~legacy.protocol.WebSocketCommonProtocol` are removed. Use + :meth:`~datastructures.Headers.raw_items` instead. + + * Functions defined in the ``handshake`` module now receive + :class:`~datastructures.Headers` in argument instead of ``get_header`` + or ``set_header`` functions. This affects libraries that rely on + low-level APIs. + + * Functions defined in the ``http`` module now return HTTP headers as + :class:`~datastructures.Headers` instead of lists of ``(name, value)`` + pairs. + + Since :class:`~datastructures.Headers` and ``http.client.HTTPMessage`` + provide similar APIs, much of the code dealing with HTTP headers won't + require changes. + +New features +............ + +* Added compatibility with Python 3.7. + +5.0.1 +----- + +*May 24, 2018* + +Bug fixes +......... + +* Fixed a regression in 5.0 that broke some invocations of + :func:`~server.serve` and :func:`~client.connect`. + +5.0 +--- + +*May 22, 2018* + +Security fix +............ + +.. admonition:: websockets 5.0 fixes a security issue introduced in 4.0. + :class: important + + 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 + +Backwards-incompatible changes +.............................. + +.. admonition:: A ``user_info`` field is added to the return value of + ``parse_uri`` and ``WebSocketURI``. + :class: note + + If you're unpacking ``WebSocketURI`` into four variables, adjust your code + to account for that fifth field. + +New features +............ + +* :func:`~client.connect` performs HTTP Basic Auth when the URI contains + credentials. + +* :func:`~server.unix_serve` can be used as an asynchronous context + manager on Python ≥ 3.5.1. + +* Added the :attr:`~legacy.protocol.WebSocketCommonProtocol.closed` property + to protocols. + +* Added new examples in the documentation. + +Improvements +............ + +* 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. + +* If a :meth:`~legacy.protocol.WebSocketCommonProtocol.ping` doesn't receive a + pong, it's canceled when the connection is closed. + +* Reported the cause of :exc:`~exceptions.ConnectionClosed` exceptions. + +* 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. + +* Updated documentation with new features from Python 3.6. + +* Improved several sections of the documentation. + +Bug fixes +......... + +* Prevented :exc:`TypeError` due to missing close code on connection close. + +* Fixed a race condition in the closing handshake that raised + :exc:`~exceptions.InvalidState`. + +4.0.1 +----- + +*November 2, 2017* + +Bug fixes +......... + +* Fixed issues with the packaging of the 4.0 release. + +4.0 +--- + +*November 2, 2017* + +Backwards-incompatible changes +.............................. + +.. admonition:: websockets 4.0 requires Python ≥ 3.4. + :class: tip + + websockets 3.4 is the last version supporting Python 3.3. + +.. admonition:: Compression is enabled by default. + :class: important + + In August 2017, Firefox and Chrome support the permessage-deflate + extension, 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`. + +.. admonition:: The ``state_name`` attribute of protocols is deprecated. + :class: note + + Use ``protocol.state.name`` instead of ``protocol.state_name``. + +New features +............ + +* :class:`~legacy.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`. + +* Allowed ``extra_headers`` to override ``Server`` and ``User-Agent`` headers. + +Improvements +............ + +* Reorganized and extended documentation. + +* Rewrote connection termination to increase robustness in edge cases. + +* Reduced verbosity of "Failing the WebSocket connection" logs. + +Bug fixes +......... + +* Aborted connections if they don't close within the configured ``timeout``. + +* Stopped leaking pending tasks when :meth:`~asyncio.Task.cancel` is called on + a connection while it's being closed. + +3.4 +--- + +*August 20, 2017* + +Backwards-incompatible changes +.............................. + +.. admonition:: ``InvalidStatus`` is replaced + by :class:`~exceptions.InvalidStatusCode`. + :class: note + + This exception is raised when :func:`~client.connect` receives an invalid + response status code from the server. + +New features +............ + +* :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. + +Improvements +............ + +* 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. + +* Rewrote HTTP handling for simplicity and performance. + +* Added an optional C extension to speed up low-level operations. + +Bug fixes +......... + +* Providing a ``sock`` argument to :func:`~client.connect` no longer + crashes. + +3.3 +--- + +*March 29, 2017* + +New features +............ + +* Ensured compatibility with Python 3.6. + +Improvements +............ + +* Reduced noise in logs caused by connection resets. + +Bug fixes +......... + +* Avoided crashing on concurrent writes on slow connections. + +3.2 +--- + +*August 17, 2016* + +New features +............ + +* Added ``timeout``, ``max_size``, and ``max_queue`` arguments to + :func:`~client.connect` and :func:`~server.serve`. + +Improvements +............ + +* Made server shutdown more robust. + +3.1 +--- + +*April 21, 2016* + +New features +............ + +* Added flow control for incoming data. + +Bug fixes +......... + +* Avoided a warning when closing a connection before the opening handshake. + +3.0 +--- + +*December 25, 2015* + +Backwards-incompatible changes +.............................. + +.. admonition:: :meth:`~legacy.protocol.WebSocketCommonProtocol.recv` now + raises an exception when the connection is closed. + :class: caution + + :meth:`~legacy.protocol.WebSocketCommonProtocol.recv` used to return + :obj:`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, 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`. + +New features +............ + +* :func:`~client.connect` can be used as an asynchronous context + manager on Python ≥ 3.5.1. + +* :meth:`~legacy.protocol.WebSocketCommonProtocol.ping` and + :meth:`~legacy.protocol.WebSocketCommonProtocol.pong` support data passed as + :class:`str` in addition to :class:`bytes`. + +* Made ``state_name`` attribute on protocols a public API. + +Improvements +............ + +* Updated documentation with ``await`` and ``async`` syntax from Python 3.5. + +* Worked around an :mod:`asyncio` bug affecting connection termination under + load. + +* Improved documentation. + +2.7 +--- + +*November 18, 2015* + +New features +............ + +* Added compatibility with Python 3.5. + +Improvements +............ + +* Refreshed documentation. + +2.6 +--- + +*August 18, 2015* + +New features +............ + +* Added ``local_address`` and ``remote_address`` attributes on protocols. + +* Closed open connections with code 1001 when a server shuts down. + +Bug fixes +......... + +* Avoided TCP fragmentation of small frames. + +2.5 +--- + +*July 28, 2015* + +New features +............ + +* Provided access to handshake request and response HTTP headers. + +* Allowed customizing handshake request and response HTTP headers. + +* Added support for running on a non-default event loop. + +Improvements +............ + +* Improved documentation. + +* Sent a 403 status code instead of 400 when request Origin isn't allowed. + +* Clarified that the closing handshake can be initiated by the client. + +* Set the close code and reason more consistently. + +* Strengthened connection termination. + +Bug fixes +......... + +* Canceling :meth:`~legacy.protocol.WebSocketCommonProtocol.recv` no longer + drops the next message. + +2.4 +--- + +*January 31, 2015* + +New features +............ + +* Added support for subprotocols. + +* Added ``loop`` argument to :func:`~client.connect` and + :func:`~server.serve`. + +2.3 +--- + +*November 3, 2014* + +Improvements +............ + +* Improved compliance of close codes. + +2.2 +--- + +*July 28, 2014* + +New features +............ + +* Added support for limiting message size. + +2.1 +--- + +*April 26, 2014* + +New features +............ + +* Added ``host``, ``port`` and ``secure`` attributes on protocols. + +* Added support for providing and checking Origin_. + +.. _Origin: https://www.rfc-editor.org/rfc/rfc6455.html#section-10.2 + +2.0 +--- + +*February 16, 2014* + +Backwards-incompatible changes +.............................. + +.. admonition:: :meth:`~legacy.protocol.WebSocketCommonProtocol.send`, + :meth:`~legacy.protocol.WebSocketCommonProtocol.ping`, and + :meth:`~legacy.protocol.WebSocketCommonProtocol.pong` are now coroutines. + :class: caution + + They used to be functions. + + Instead of:: + + websocket.send(message) + + you must write:: + + await websocket.send(message) + +New features +............ + +* Added flow control for outgoing data. + +1.0 +--- + +*November 14, 2013* + +New features +............ + +* Initial public release. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/contributing.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/contributing.rst new file mode 100644 index 0000000000000..020ed7ad85cd1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/contributing.rst @@ -0,0 +1,66 @@ +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/python-websockets/websockets/blob/main/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, as the primary author +feels more confident about writing code than writing docs :-) + +If you're wondering why things are done in a certain way, the :doc:`design +document <../topics/design>` provides lots of details about the internals of +websockets. + +.. _issue: https://github.com/python-websockets/websockets/issues/new +.. _pull request: https://github.com/python-websockets/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 in the documentation; +- it wasn't asked already. + +A good question can be written as a suggestion to improve the documentation. + +Cryptocurrency users +-------------------- + +websockets appears to be quite popular for interfacing with Bitcoin or other +cryptocurrency trackers. I'm strongly opposed to Bitcoin's carbon footprint. + +I'm aware of efforts to build proof-of-stake models. I'll care once the total +energy consumption of all cryptocurrencies drops to a non-bullshit level. + +You already negated all of humanity's efforts to develop renewable energy. +Please stop heating the planet where my children will have to live. + +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 you. + +I will summarily close issues related to Bitcoin or cryptocurrency in any way. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/index.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/index.rst new file mode 100644 index 0000000000000..459146345b0d8 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/index.rst @@ -0,0 +1,12 @@ +About websockets +================ + +This is about websockets-the-project rather than websockets-the-software. + +.. toctree:: + :titlesonly: + + changelog + contributing + license + For enterprise diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/license.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/license.rst new file mode 100644 index 0000000000000..0a3b8703d568a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/license.rst @@ -0,0 +1,4 @@ +License +======= + +.. include:: ../../LICENSE diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/tidelift.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/tidelift.rst new file mode 100644 index 0000000000000..42100fade9b2a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/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 + + + + + +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 + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/w3c-import.log new file mode 100644 index 0000000000000..796222d908eef --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/changelog.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/contributing.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/license.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/project/tidelift.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/asyncio/client.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/asyncio/client.rst new file mode 100644 index 0000000000000..5086015b7b982 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/asyncio/client.rst @@ -0,0 +1,64 @@ +Client (:mod:`asyncio`) +======================= + +.. automodule:: websockets.client + +Opening a connection +-------------------- + +.. autofunction:: connect(uri, *, create_protocol=None, logger=None, compression="deflate", origin=None, extensions=None, subprotocols=None, extra_headers=None, user_agent_header="Python/x.y.z websockets/X.Y", open_timeout=10, 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, **kwds) + :async: + +.. autofunction:: unix_connect(path, uri="ws://localhost/", *, create_protocol=None, logger=None, compression="deflate", origin=None, extensions=None, subprotocols=None, extra_headers=None, user_agent_header="Python/x.y.z websockets/X.Y", open_timeout=10, 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, **kwds) + :async: + +Using a connection +------------------ + +.. autoclass:: WebSocketClientProtocol(*, logger=None, origin=None, extensions=None, subprotocols=None, extra_headers=None, user_agent_header="Python/x.y.z websockets/X.Y", 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) + + .. automethod:: recv + + .. automethod:: send + + .. automethod:: close + + .. automethod:: wait_closed + + .. automethod:: ping + + .. automethod:: pong + + WebSocket connection objects also provide these attributes: + + .. autoattribute:: id + + .. autoattribute:: logger + + .. autoproperty:: local_address + + .. autoproperty:: remote_address + + .. autoproperty:: open + + .. autoproperty:: closed + + .. autoattribute:: latency + + The following attributes are available after the opening handshake, + once the WebSocket connection is open: + + .. autoattribute:: path + + .. autoattribute:: request_headers + + .. autoattribute:: response_headers + + .. autoattribute:: subprotocol + + The following attributes are available after the closing handshake, + once the WebSocket connection is closed: + + .. autoproperty:: close_code + + .. autoproperty:: close_reason diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/asyncio/common.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/asyncio/common.rst new file mode 100644 index 0000000000000..dc7a54ee1abc9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/asyncio/common.rst @@ -0,0 +1,54 @@ +:orphan: + +Both sides (:mod:`asyncio`) +=========================== + +.. automodule:: websockets.legacy.protocol + +.. autoclass:: WebSocketCommonProtocol(*, logger=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) + + .. automethod:: recv + + .. automethod:: send + + .. automethod:: close + + .. automethod:: wait_closed + + .. automethod:: ping + + .. automethod:: pong + + WebSocket connection objects also provide these attributes: + + .. autoattribute:: id + + .. autoattribute:: logger + + .. autoproperty:: local_address + + .. autoproperty:: remote_address + + .. autoproperty:: open + + .. autoproperty:: closed + + .. autoattribute:: latency + + The following attributes are available after the opening handshake, + once the WebSocket connection is open: + + .. autoattribute:: path + + .. autoattribute:: request_headers + + .. autoattribute:: response_headers + + .. autoattribute:: subprotocol + + The following attributes are available after the closing handshake, + once the WebSocket connection is closed: + + .. autoproperty:: close_code + + .. autoproperty:: close_reason diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/asyncio/server.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/asyncio/server.rst new file mode 100644 index 0000000000000..10631791626c9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/asyncio/server.rst @@ -0,0 +1,113 @@ +Server (:mod:`asyncio`) +======================= + +.. automodule:: websockets.server + +Starting a server +----------------- + +.. autofunction:: serve(ws_handler, host=None, port=None, *, create_protocol=None, logger=None, compression="deflate", origins=None, extensions=None, subprotocols=None, extra_headers=None, server_header="Python/x.y.z websockets/X.Y", process_request=None, select_subprotocol=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, **kwds) + :async: + +.. autofunction:: unix_serve(ws_handler, path=None, *, create_protocol=None, logger=None, compression="deflate", origins=None, extensions=None, subprotocols=None, extra_headers=None, server_header="Python/x.y.z websockets/X.Y", process_request=None, select_subprotocol=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, **kwds) + :async: + +Stopping a server +----------------- + +.. autoclass:: WebSocketServer + + .. automethod:: close + + .. automethod:: wait_closed + + .. automethod:: get_loop + + .. automethod:: is_serving + + .. automethod:: start_serving + + .. automethod:: serve_forever + + .. autoattribute:: sockets + +Using a connection +------------------ + +.. autoclass:: WebSocketServerProtocol(ws_handler, ws_server, *, logger=None, origins=None, extensions=None, subprotocols=None, extra_headers=None, server_header="Python/x.y.z websockets/X.Y", process_request=None, select_subprotocol=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) + + .. automethod:: recv + + .. automethod:: send + + .. automethod:: close + + .. automethod:: wait_closed + + .. automethod:: ping + + .. automethod:: pong + + You can customize the opening handshake in a subclass by overriding these methods: + + .. automethod:: process_request + + .. automethod:: select_subprotocol + + WebSocket connection objects also provide these attributes: + + .. autoattribute:: id + + .. autoattribute:: logger + + .. autoproperty:: local_address + + .. autoproperty:: remote_address + + .. autoproperty:: open + + .. autoproperty:: closed + + .. autoattribute:: latency + + The following attributes are available after the opening handshake, + once the WebSocket connection is open: + + .. autoattribute:: path + + .. autoattribute:: request_headers + + .. autoattribute:: response_headers + + .. autoattribute:: subprotocol + + The following attributes are available after the closing handshake, + once the WebSocket connection is closed: + + .. autoproperty:: close_code + + .. autoproperty:: close_reason + + +Basic authentication +-------------------- + +.. automodule:: websockets.auth + +websockets supports HTTP Basic Authentication according to +:rfc:`7235` and :rfc:`7617`. + +.. autofunction:: basic_auth_protocol_factory + +.. autoclass:: BasicAuthWebSocketServerProtocol + + .. autoattribute:: realm + + .. autoattribute:: username + + .. automethod:: check_credentials + +Broadcast +--------- + +.. autofunction:: websockets.broadcast diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/asyncio/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/asyncio/w3c-import.log new file mode 100644 index 0000000000000..9d9fe5dbbf118 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/asyncio/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/asyncio/client.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/asyncio/common.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/asyncio/server.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/datastructures.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/datastructures.rst new file mode 100644 index 0000000000000..ec02d421015c8 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/datastructures.rst @@ -0,0 +1,66 @@ +Data structures +=============== + +WebSocket events +---------------- + +.. automodule:: websockets.frames + + .. autoclass:: Frame + + .. autoclass:: Opcode + + .. autoattribute:: CONT + .. autoattribute:: TEXT + .. autoattribute:: BINARY + .. autoattribute:: CLOSE + .. autoattribute:: PING + .. autoattribute:: PONG + + .. autoclass:: Close + + .. autoclass:: CloseCode + + .. autoattribute:: NORMAL_CLOSURE + .. autoattribute:: GOING_AWAY + .. autoattribute:: PROTOCOL_ERROR + .. autoattribute:: UNSUPPORTED_DATA + .. autoattribute:: NO_STATUS_RCVD + .. autoattribute:: ABNORMAL_CLOSURE + .. autoattribute:: INVALID_DATA + .. autoattribute:: POLICY_VIOLATION + .. autoattribute:: MESSAGE_TOO_BIG + .. autoattribute:: MANDATORY_EXTENSION + .. autoattribute:: INTERNAL_ERROR + .. autoattribute:: SERVICE_RESTART + .. autoattribute:: TRY_AGAIN_LATER + .. autoattribute:: BAD_GATEWAY + .. autoattribute:: TLS_HANDSHAKE + +HTTP events +----------- + +.. automodule:: websockets.http11 + + .. autoclass:: Request + + .. autoclass:: Response + +.. automodule:: websockets.datastructures + + .. autoclass:: Headers + + .. automethod:: get_all + + .. automethod:: raw_items + + .. autoexception:: MultipleValuesError + +URIs +---- + +.. automodule:: websockets.uri + + .. autofunction:: parse_uri + + .. autoclass:: WebSocketURI diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/exceptions.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/exceptions.rst new file mode 100644 index 0000000000000..907a650d204ba --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/exceptions.rst @@ -0,0 +1,6 @@ +Exceptions +========== + +.. automodule:: websockets.exceptions + :members: + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/extensions.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/extensions.rst new file mode 100644 index 0000000000000..a70f1b1e58a76 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/extensions.rst @@ -0,0 +1,60 @@ +Extensions +========== + +.. currentmodule:: websockets.extensions + +The WebSocket protocol supports extensions_. + +At the time of writing, there's only one `registered extension`_ with a public +specification, WebSocket Per-Message Deflate. + +.. _extensions: https://www.rfc-editor.org/rfc/rfc6455.html#section-9 +.. _registered extension: https://www.iana.org/assignments/websocket/websocket.xhtml#extension-name + +Per-Message Deflate +------------------- + +.. automodule:: websockets.extensions.permessage_deflate + + :mod:`websockets.extensions.permessage_deflate` implements WebSocket + Per-Message Deflate. + + This extension is specified in :rfc:`7692`. + + Refer to the :doc:`topic guide on compression <../topics/compression>` to + learn more about tuning compression settings. + + .. autoclass:: ClientPerMessageDeflateFactory + + .. autoclass:: ServerPerMessageDeflateFactory + +Base classes +------------ + +.. automodule:: websockets.extensions + + :mod:`websockets.extensions` defines base classes for implementing + extensions. + + Refer to the :doc:`how-to guide on extensions <../howto/extensions>` to + learn more about writing an extension. + + .. autoclass:: Extension + + .. autoattribute:: name + + .. automethod:: decode + + .. automethod:: encode + + .. autoclass:: ClientExtensionFactory + + .. autoattribute:: name + + .. automethod:: get_request_params + + .. automethod:: process_response_params + + .. autoclass:: ServerExtensionFactory + + .. automethod:: process_request_params diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/features.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/features.rst new file mode 100644 index 0000000000000..98b3c0ddafcab --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/features.rst @@ -0,0 +1,187 @@ +Features +======== + +.. currentmodule:: websockets + +Feature support matrices summarize which implementations support which features. + +.. raw:: html + + + +.. |aio| replace:: :mod:`asyncio` +.. |sync| replace:: :mod:`threading` +.. |sans| replace:: `Sans-I/O`_ +.. _Sans-I/O: https://sans-io.readthedocs.io/ + +Both sides +---------- + +.. table:: + :class: support-matrix-table + + +------------------------------------+--------+--------+--------+ + | | |aio| | |sync| | |sans| | + +====================================+========+========+========+ + | Perform the opening handshake | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Send a message | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Receive a message | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Iterate over received messages | ✅ | ✅ | ❌ | + +------------------------------------+--------+--------+--------+ + | Send a fragmented message | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Receive a fragmented message after | ✅ | ✅ | ❌ | + | reassembly | | | | + +------------------------------------+--------+--------+--------+ + | Receive a fragmented message frame | ❌ | ✅ | ✅ | + | by frame (`#479`_) | | | | + +------------------------------------+--------+--------+--------+ + | Send a ping | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Respond to pings automatically | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Send a pong | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Perform the closing handshake | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Report close codes and reasons | ❌ | ✅ | ✅ | + | from both sides | | | | + +------------------------------------+--------+--------+--------+ + | Compress messages (:rfc:`7692`) | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Tune memory usage for compression | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Negotiate extensions | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Implement custom extensions | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Negotiate a subprotocol | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Enforce security limits | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Log events | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Enforce opening timeout | ✅ | ✅ | — | + +------------------------------------+--------+--------+--------+ + | Enforce closing timeout | ✅ | ✅ | — | + +------------------------------------+--------+--------+--------+ + | Keepalive | ✅ | ❌ | — | + +------------------------------------+--------+--------+--------+ + | Heartbeat | ✅ | ❌ | — | + +------------------------------------+--------+--------+--------+ + +.. _#479: https://github.com/python-websockets/websockets/issues/479 + +Server +------ + +.. table:: + :class: support-matrix-table + + +------------------------------------+--------+--------+--------+ + | | |aio| | |sync| | |sans| | + +====================================+========+========+========+ + | Listen on a TCP socket | ✅ | ✅ | — | + +------------------------------------+--------+--------+--------+ + | Listen on a Unix socket | ✅ | ✅ | — | + +------------------------------------+--------+--------+--------+ + | Listen using a preexisting socket | ✅ | ✅ | — | + +------------------------------------+--------+--------+--------+ + | Encrypt connection with TLS | ✅ | ✅ | — | + +------------------------------------+--------+--------+--------+ + | Close server on context exit | ✅ | ✅ | — | + +------------------------------------+--------+--------+--------+ + | Close connection on handler exit | ✅ | ✅ | — | + +------------------------------------+--------+--------+--------+ + | Shut down server gracefully | ✅ | ✅ | — | + +------------------------------------+--------+--------+--------+ + | Check ``Origin`` header | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Customize subprotocol selection | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Configure ``Server`` header | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Alter opening handshake request | ❌ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Alter opening handshake response | ❌ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Perform HTTP Basic Authentication | ✅ | ❌ | ❌ | + +------------------------------------+--------+--------+--------+ + | Perform HTTP Digest Authentication | ❌ | ❌ | ❌ | + +------------------------------------+--------+--------+--------+ + | Force HTTP response | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + +Client +------ + +.. table:: + :class: support-matrix-table + + +------------------------------------+--------+--------+--------+ + | | |aio| | |sync| | |sans| | + +====================================+========+========+========+ + | Connect to a TCP socket | ✅ | ✅ | — | + +------------------------------------+--------+--------+--------+ + | Connect to a Unix socket | ✅ | ✅ | — | + +------------------------------------+--------+--------+--------+ + | Connect using a preexisting socket | ✅ | ✅ | — | + +------------------------------------+--------+--------+--------+ + | Encrypt connection with TLS | ✅ | ✅ | — | + +------------------------------------+--------+--------+--------+ + | Close connection on context exit | ✅ | ✅ | — | + +------------------------------------+--------+--------+--------+ + | Reconnect automatically | ✅ | ❌ | — | + +------------------------------------+--------+--------+--------+ + | Configure ``Origin`` header | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Configure ``User-Agent`` header | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Alter opening handshake request | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Connect to non-ASCII IRIs | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Perform HTTP Basic Authentication | ✅ | ✅ | ✅ | + +------------------------------------+--------+--------+--------+ + | Perform HTTP Digest Authentication | ❌ | ❌ | ❌ | + | (`#784`_) | | | | + +------------------------------------+--------+--------+--------+ + | Follow HTTP redirects | ✅ | ❌ | — | + +------------------------------------+--------+--------+--------+ + | Connect via a HTTP proxy (`#364`_) | ❌ | ❌ | — | + +------------------------------------+--------+--------+--------+ + | Connect via a SOCKS5 proxy | ❌ | ❌ | — | + | (`#475`_) | | | | + +------------------------------------+--------+--------+--------+ + +.. _#364: https://github.com/python-websockets/websockets/issues/364 +.. _#475: https://github.com/python-websockets/websockets/issues/475 +.. _#784: https://github.com/python-websockets/websockets/issues/784 + +Known limitations +----------------- + +There is no way to control compression of outgoing frames on a per-frame basis +(`#538`_). If compression is enabled, all frames are compressed. + +.. _#538: https://github.com/python-websockets/websockets/issues/538 + +The server doesn't check the Host header and respond with a HTTP 400 Bad Request +if it is missing or invalid (`#1246`). + +.. _#1246: https://github.com/python-websockets/websockets/issues/1246 + +The client API doesn't attempt to guarantee that there is no more than one +connection to a given IP address in a CONNECTING state. This behavior is +`mandated by RFC 6455`_. However, :func:`~client.connect()` isn't the right +layer for enforcing this constraint. It's the caller's responsibility. + +.. _mandated by RFC 6455: https://www.rfc-editor.org/rfc/rfc6455.html#section-4.1 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/index.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/index.rst new file mode 100644 index 0000000000000..0b80f087a1f4f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/index.rst @@ -0,0 +1,90 @@ +API reference +============= + +.. currentmodule:: websockets + +Features +-------- + +Check which implementations support which features and known limitations. + +.. toctree:: + :titlesonly: + + features + + +:mod:`asyncio` +-------------- + +This is the default implementation. It's ideal for servers that handle many +clients concurrently. + +.. toctree:: + :titlesonly: + + asyncio/server + asyncio/client + +:mod:`threading` +---------------- + +This alternative implementation can be a good choice for clients. + +.. toctree:: + :titlesonly: + + sync/server + sync/client + +`Sans-I/O`_ +----------- + +This layer is designed for integrating in third-party libraries, typically +application servers. + +.. _Sans-I/O: https://sans-io.readthedocs.io/ + +.. toctree:: + :titlesonly: + + sansio/server + sansio/client + +Extensions +---------- + +The Per-Message Deflate extension is built in. You may also define custom +extensions. + +.. toctree:: + :titlesonly: + + extensions + +Shared +------ + +These low-level APIs are shared by all implementations. + +.. toctree:: + :titlesonly: + + datastructures + exceptions + types + +API stability +------------- + +Public APIs documented in this API reference are subject to the +:ref:`backwards-compatibility policy `. + +Anything that isn't listed in the API reference is a private API. There's no +guarantees of behavior or backwards-compatibility for private APIs. + +Convenience imports +------------------- + +For convenience, many public APIs can be imported directly from the +``websockets`` package. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sansio/client.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sansio/client.rst new file mode 100644 index 0000000000000..09bafc74558dd --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sansio/client.rst @@ -0,0 +1,58 @@ +Client (`Sans-I/O`_) +==================== + +.. _Sans-I/O: https://sans-io.readthedocs.io/ + +.. currentmodule:: websockets.client + +.. autoclass:: ClientProtocol(wsuri, origin=None, extensions=None, subprotocols=None, state=State.CONNECTING, max_size=2 ** 20, logger=None) + + .. automethod:: receive_data + + .. automethod:: receive_eof + + .. automethod:: connect + + .. automethod:: send_request + + .. automethod:: send_continuation + + .. automethod:: send_text + + .. automethod:: send_binary + + .. automethod:: send_close + + .. automethod:: send_ping + + .. automethod:: send_pong + + .. automethod:: fail + + .. automethod:: events_received + + .. automethod:: data_to_send + + .. automethod:: close_expected + + WebSocket protocol objects also provide these attributes: + + .. autoattribute:: id + + .. autoattribute:: logger + + .. autoproperty:: state + + The following attributes are available after the opening handshake, + once the WebSocket connection is open: + + .. autoattribute:: handshake_exc + + The following attributes are available after the closing handshake, + once the WebSocket connection is closed: + + .. autoproperty:: close_code + + .. autoproperty:: close_reason + + .. autoproperty:: close_exc diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sansio/common.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sansio/common.rst new file mode 100644 index 0000000000000..cd1ef3c63a585 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sansio/common.rst @@ -0,0 +1,64 @@ +:orphan: + +Both sides (`Sans-I/O`_) +========================= + +.. _Sans-I/O: https://sans-io.readthedocs.io/ + +.. automodule:: websockets.protocol + +.. autoclass:: Protocol(side, state=State.OPEN, max_size=2 ** 20, logger=None) + + .. automethod:: receive_data + + .. automethod:: receive_eof + + .. automethod:: send_continuation + + .. automethod:: send_text + + .. automethod:: send_binary + + .. automethod:: send_close + + .. automethod:: send_ping + + .. automethod:: send_pong + + .. automethod:: fail + + .. automethod:: events_received + + .. automethod:: data_to_send + + .. automethod:: close_expected + + .. autoattribute:: id + + .. autoattribute:: logger + + .. autoproperty:: state + + .. autoproperty:: close_code + + .. autoproperty:: close_reason + + .. autoproperty:: close_exc + +.. autoclass:: Side + + .. autoattribute:: SERVER + + .. autoattribute:: CLIENT + +.. autoclass:: State + + .. autoattribute:: CONNECTING + + .. autoattribute:: OPEN + + .. autoattribute:: CLOSING + + .. autoattribute:: CLOSED + +.. autodata:: SEND_EOF diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sansio/server.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sansio/server.rst new file mode 100644 index 0000000000000..d70df6277a4eb --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sansio/server.rst @@ -0,0 +1,62 @@ +Server (`Sans-I/O`_) +==================== + +.. _Sans-I/O: https://sans-io.readthedocs.io/ + +.. currentmodule:: websockets.server + +.. autoclass:: ServerProtocol(origins=None, extensions=None, subprotocols=None, state=State.CONNECTING, max_size=2 ** 20, logger=None) + + .. automethod:: receive_data + + .. automethod:: receive_eof + + .. automethod:: accept + + .. automethod:: select_subprotocol + + .. automethod:: reject + + .. automethod:: send_response + + .. automethod:: send_continuation + + .. automethod:: send_text + + .. automethod:: send_binary + + .. automethod:: send_close + + .. automethod:: send_ping + + .. automethod:: send_pong + + .. automethod:: fail + + .. automethod:: events_received + + .. automethod:: data_to_send + + .. automethod:: close_expected + + WebSocket protocol objects also provide these attributes: + + .. autoattribute:: id + + .. autoattribute:: logger + + .. autoproperty:: state + + The following attributes are available after the opening handshake, + once the WebSocket connection is open: + + .. autoattribute:: handshake_exc + + The following attributes are available after the closing handshake, + once the WebSocket connection is closed: + + .. autoproperty:: close_code + + .. autoproperty:: close_reason + + .. autoproperty:: close_exc diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sansio/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sansio/w3c-import.log new file mode 100644 index 0000000000000..c5911147794a9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sansio/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sansio/client.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sansio/common.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sansio/server.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sync/client.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sync/client.rst new file mode 100644 index 0000000000000..6cccd6ec486c4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sync/client.rst @@ -0,0 +1,49 @@ +Client (:mod:`threading`) +========================= + +.. automodule:: websockets.sync.client + +Opening a connection +-------------------- + +.. autofunction:: connect(uri, *, sock=None, ssl_context=None, server_hostname=None, origin=None, extensions=None, subprotocols=None, additional_headers=None, user_agent_header="Python/x.y.z websockets/X.Y", compression="deflate", open_timeout=10, close_timeout=10, max_size=2 ** 20, logger=None, create_connection=None) + +.. autofunction:: unix_connect(path, uri=None, *, sock=None, ssl_context=None, server_hostname=None, origin=None, extensions=None, subprotocols=None, additional_headers=None, user_agent_header="Python/x.y.z websockets/X.Y", compression="deflate", open_timeout=10, close_timeout=10, max_size=2 ** 20, logger=None, create_connection=None) + +Using a connection +------------------ + +.. autoclass:: ClientConnection + + .. automethod:: __iter__ + + .. automethod:: recv + + .. automethod:: recv_streaming + + .. automethod:: send + + .. automethod:: close + + .. automethod:: ping + + .. automethod:: pong + + WebSocket connection objects also provide these attributes: + + .. autoattribute:: id + + .. autoattribute:: logger + + .. autoproperty:: local_address + + .. autoproperty:: remote_address + + The following attributes are available after the opening handshake, + once the WebSocket connection is open: + + .. autoattribute:: request + + .. autoattribute:: response + + .. autoproperty:: subprotocol diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sync/common.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sync/common.rst new file mode 100644 index 0000000000000..3dc6d4a509672 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sync/common.rst @@ -0,0 +1,41 @@ +:orphan: + +Both sides (:mod:`threading`) +============================= + +.. automodule:: websockets.sync.connection + +.. autoclass:: Connection + + .. automethod:: __iter__ + + .. automethod:: recv + + .. automethod:: recv_streaming + + .. automethod:: send + + .. automethod:: close + + .. automethod:: ping + + .. automethod:: pong + + WebSocket connection objects also provide these attributes: + + .. autoattribute:: id + + .. autoattribute:: logger + + .. autoproperty:: local_address + + .. autoproperty:: remote_address + + The following attributes are available after the opening handshake, + once the WebSocket connection is open: + + .. autoattribute:: request + + .. autoattribute:: response + + .. autoproperty:: subprotocol diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sync/server.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sync/server.rst new file mode 100644 index 0000000000000..35c112046acbb --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sync/server.rst @@ -0,0 +1,60 @@ +Server (:mod:`threading`) +========================= + +.. automodule:: websockets.sync.server + +Creating a server +----------------- + +.. autofunction:: serve(handler, host=None, port=None, *, sock=None, ssl_context=None, origins=None, extensions=None, subprotocols=None, select_subprotocol=None, process_request=None, process_response=None, server_header="Python/x.y.z websockets/X.Y", compression="deflate", open_timeout=10, close_timeout=10, max_size=2 ** 20, logger=None, create_connection=None) + +.. autofunction:: unix_serve(handler, path=None, *, sock=None, ssl_context=None, origins=None, extensions=None, subprotocols=None, select_subprotocol=None, process_request=None, process_response=None, server_header="Python/x.y.z websockets/X.Y", compression="deflate", open_timeout=10, close_timeout=10, max_size=2 ** 20, logger=None, create_connection=None) + +Running a server +---------------- + +.. autoclass:: WebSocketServer + + .. automethod:: serve_forever + + .. automethod:: shutdown + + .. automethod:: fileno + +Using a connection +------------------ + +.. autoclass:: ServerConnection + + .. automethod:: __iter__ + + .. automethod:: recv + + .. automethod:: recv_streaming + + .. automethod:: send + + .. automethod:: close + + .. automethod:: ping + + .. automethod:: pong + + WebSocket connection objects also provide these attributes: + + .. autoattribute:: id + + .. autoattribute:: logger + + .. autoproperty:: local_address + + .. autoproperty:: remote_address + + The following attributes are available after the opening handshake, + once the WebSocket connection is open: + + .. autoattribute:: request + + .. autoattribute:: response + + .. autoproperty:: subprotocol diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sync/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sync/w3c-import.log new file mode 100644 index 0000000000000..0ccf0dd4fcc56 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sync/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sync/client.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sync/common.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/sync/server.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/types.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/types.rst new file mode 100644 index 0000000000000..9d3aa8310bc6c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/types.rst @@ -0,0 +1,24 @@ +Types +===== + +.. automodule:: websockets.typing + + .. autodata:: Data + + .. autodata:: LoggerLike + + .. autodata:: StatusLike + + .. autodata:: Origin + + .. autodata:: Subprotocol + + .. autodata:: ExtensionName + + .. autodata:: ExtensionParameter + +.. autodata:: websockets.protocol.Event + +.. autodata:: websockets.datastructures.HeadersLike + +.. autodata:: websockets.datastructures.SupportsKeysAndGetItem diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/w3c-import.log new file mode 100644 index 0000000000000..3f0f5bd41e21f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/datastructures.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/exceptions.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/extensions.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/features.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/reference/types.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/requirements.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/requirements.txt new file mode 100644 index 0000000000000..bcd1d711435f9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/requirements.txt @@ -0,0 +1,8 @@ +furo +sphinx +sphinx-autobuild +sphinx-copybutton +sphinx-inline-tabs +sphinxcontrib-spelling +sphinxcontrib-trio +sphinxext-opengraph diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/spelling_wordlist.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/spelling_wordlist.txt new file mode 100644 index 0000000000000..dfa7065e79e18 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/spelling_wordlist.txt @@ -0,0 +1,85 @@ +augustin +auth +autoscaler +aymeric +backend +backoff +backpressure +balancer +balancers +bottlenecked +bufferbloat +bugfix +buildpack +bytestring +bytestrings +changelog +coroutine +coroutines +cryptocurrencies +cryptocurrency +css +ctrl +deserialize +django +dev +Dockerfile +dyno +formatter +fractalideas +gunicorn +healthz +html +hypercorn +iframe +IPv +istio +iterable +js +keepalive +KiB +kubernetes +lifecycle +linkerd +liveness +lookups +MiB +mutex +mypy +nginx +Paketo +permessage +pid +procfile +proxying +py +pythonic +reconnection +redis +redistributions +retransmit +runtime +scalable +stateful +subclasses +subclassing +submodule +subpackages +subprotocol +subprotocols +supervisord +tidelift +tls +tox +txt +unregister +uple +uvicorn +uvloop +virtualenv +WebSocket +websocket +websockets +ws +wsgi +www diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/authentication.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/authentication.rst new file mode 100644 index 0000000000000..31bc8e6da8a14 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/authentication.rst @@ -0,0 +1,348 @@ +Authentication +============== + +The WebSocket protocol was designed for creating web applications that need +bidirectional communication between clients running in browsers and servers. + +In most practical use cases, WebSocket servers need to authenticate clients in +order to route communications appropriately and securely. + +:rfc:`6455` stays elusive when it comes to authentication: + + This protocol doesn't prescribe any particular way that servers can + authenticate clients during the WebSocket handshake. The WebSocket + server can use any client authentication mechanism available to a + generic HTTP server, such as cookies, HTTP authentication, or TLS + authentication. + +None of these three mechanisms works well in practice. Using cookies is +cumbersome, HTTP authentication isn't supported by all mainstream browsers, +and TLS authentication in a browser is an esoteric user experience. + +Fortunately, there are better alternatives! Let's discuss them. + +System design +------------- + +Consider a setup where the WebSocket server is separate from the HTTP server. + +Most servers built with websockets to complement a web application adopt this +design because websockets doesn't aim at supporting HTTP. + +The following diagram illustrates the authentication flow. + +.. image:: authentication.svg + +Assuming the current user is authenticated with the HTTP server (1), the +application needs to obtain credentials from the HTTP server (2) in order to +send them to the WebSocket server (3), who can check them against the database +of user accounts (4). + +Usernames and passwords aren't a good choice of credentials here, if only +because passwords aren't available in clear text in the database. + +Tokens linked to user accounts are a better choice. These tokens must be +impossible to forge by an attacker. For additional security, they can be +short-lived or even single-use. + +Sending credentials +------------------- + +Assume the web application obtained authentication credentials, likely a +token, from the HTTP server. There's four options for passing them to the +WebSocket server. + +1. **Sending credentials as the first message in the WebSocket connection.** + + This is fully reliable and the most secure mechanism in this discussion. It + has two minor downsides: + + * Authentication is performed at the application layer. Ideally, it would + be managed at the protocol layer. + + * Authentication is performed after the WebSocket handshake, making it + impossible to monitor authentication failures with HTTP response codes. + +2. **Adding credentials to the WebSocket URI in a query parameter.** + + This is also fully reliable but less secure. Indeed, it has a major + downside: + + * URIs end up in logs, which leaks credentials. Even if that risk could be + lowered with single-use tokens, it is usually considered unacceptable. + + Authentication is still performed at the application layer but it can + happen before the WebSocket handshake, which improves separation of + concerns and enables responding to authentication failures with HTTP 401. + +3. **Setting a cookie on the domain of the WebSocket URI.** + + Cookies are undoubtedly the most common and hardened mechanism for sending + credentials from a web application to a server. In an HTTP application, + credentials would be a session identifier or a serialized, signed session. + + Unfortunately, when the WebSocket server runs on a different domain from + the web application, this idea bumps into the `Same-Origin Policy`_. For + security reasons, setting a cookie on a different origin is impossible. + + The proper workaround consists in: + + * creating a hidden iframe_ served from the domain of the WebSocket server + * sending the token to the iframe with postMessage_ + * setting the cookie in the iframe + + before opening the WebSocket connection. + + Sharing a parent domain (e.g. example.com) between the HTTP server (e.g. + www.example.com) and the WebSocket server (e.g. ws.example.com) and setting + the cookie on that parent domain would work too. + + However, the cookie would be shared with all subdomains of the parent + domain. For a cookie containing credentials, this is unacceptable. + +.. _Same-Origin Policy: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy +.. _iframe: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe +.. _postMessage: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/postMessage + +4. **Adding credentials to the WebSocket URI in user information.** + + Letting the browser perform HTTP Basic Auth is a nice idea in theory. + + In practice it doesn't work due to poor support in browsers. + + As of May 2021: + + * Chrome 90 behaves as expected. + + * Firefox 88 caches credentials too aggressively. + + When connecting again to the same server with new credentials, it reuses + the old credentials, which may be expired, resulting in an HTTP 401. Then + the next connection succeeds. Perhaps errors clear the cache. + + When tokens are short-lived or single-use, this bug produces an + interesting effect: every other WebSocket connection fails. + + * Safari 14 ignores credentials entirely. + +Two other options are off the table: + +1. **Setting a custom HTTP header** + + This would be the most elegant mechanism, solving all issues with the options + discussed above. + + Unfortunately, it doesn't work because the `WebSocket API`_ doesn't support + `setting custom headers`_. + +.. _WebSocket API: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API +.. _setting custom headers: https://github.com/whatwg/html/issues/3062 + +2. **Authenticating with a TLS certificate** + + While this is suggested by the RFC, installing a TLS certificate is too far + from the mainstream experience of browser users. This could make sense in + high security contexts. I hope developers working on such projects don't + take security advice from the documentation of random open source projects. + +Let's experiment! +----------------- + +The `experiments/authentication`_ directory demonstrates these techniques. + +Run the experiment in an environment where websockets is installed: + +.. _experiments/authentication: https://github.com/python-websockets/websockets/tree/main/experiments/authentication + +.. code-block:: console + + $ python experiments/authentication/app.py + Running on http://localhost:8000/ + +When you browse to the HTTP server at http://localhost:8000/ and you submit a +username, the server creates a token and returns a testing web page. + +This page opens WebSocket connections to four WebSocket servers running on +four different origins. It attempts to authenticate with the token in four +different ways. + +First message +............. + +As soon as the connection is open, the client sends a message containing the +token: + +.. code-block:: javascript + + const websocket = new WebSocket("ws://.../"); + websocket.onopen = () => websocket.send(token); + + // ... + +At the beginning of the connection handler, the server receives this message +and authenticates the user. If authentication fails, the server closes the +connection: + +.. code-block:: python + + async def first_message_handler(websocket): + token = await websocket.recv() + user = get_user(token) + if user is None: + await websocket.close(CloseCode.INTERNAL_ERROR, "authentication failed") + return + + ... + +Query parameter +............... + +The client adds the token to the WebSocket URI in a query parameter before +opening the connection: + +.. code-block:: javascript + + const uri = `ws://.../?token=${token}`; + const websocket = new WebSocket(uri); + + // ... + +The server intercepts the HTTP request, extracts the token and authenticates +the user. If authentication fails, it returns an HTTP 401: + +.. code-block:: python + + class QueryParamProtocol(websockets.WebSocketServerProtocol): + async def process_request(self, path, headers): + token = get_query_parameter(path, "token") + if token is None: + return http.HTTPStatus.UNAUTHORIZED, [], b"Missing token\n" + + user = get_user(token) + if user is None: + return http.HTTPStatus.UNAUTHORIZED, [], b"Invalid token\n" + + self.user = user + + async def query_param_handler(websocket): + user = websocket.user + + ... + +Cookie +...... + +The client sets a cookie containing the token before opening the connection. + +The cookie must be set by an iframe loaded from the same origin as the +WebSocket server. This requires passing the token to this iframe. + +.. code-block:: javascript + + // in main window + iframe.contentWindow.postMessage(token, "http://..."); + + // in iframe + document.cookie = `token=${data}; SameSite=Strict`; + + // in main window + const websocket = new WebSocket("ws://.../"); + + // ... + +This sequence must be synchronized between the main window and the iframe. +This involves several events. Look at the full implementation for details. + +The server intercepts the HTTP request, extracts the token and authenticates +the user. If authentication fails, it returns an HTTP 401: + +.. code-block:: python + + class CookieProtocol(websockets.WebSocketServerProtocol): + async def process_request(self, path, headers): + # Serve iframe on non-WebSocket requests + ... + + token = get_cookie(headers.get("Cookie", ""), "token") + if token is None: + return http.HTTPStatus.UNAUTHORIZED, [], b"Missing token\n" + + user = get_user(token) + if user is None: + return http.HTTPStatus.UNAUTHORIZED, [], b"Invalid token\n" + + self.user = user + + async def cookie_handler(websocket): + user = websocket.user + + ... + +User information +................ + +The client adds the token to the WebSocket URI in user information before +opening the connection: + +.. code-block:: javascript + + const uri = `ws://token:${token}@.../`; + const websocket = new WebSocket(uri); + + // ... + +Since HTTP Basic Auth is designed to accept a username and a password rather +than a token, we send ``token`` as username and the token as password. + +The server intercepts the HTTP request, extracts the token and authenticates +the user. If authentication fails, it returns an HTTP 401: + +.. code-block:: python + + class UserInfoProtocol(websockets.BasicAuthWebSocketServerProtocol): + async def check_credentials(self, username, password): + if username != "token": + return False + + user = get_user(password) + if user is None: + return False + + self.user = user + return True + + async def user_info_handler(websocket): + user = websocket.user + + ... + +Machine-to-machine authentication +--------------------------------- + +When the WebSocket client is a standalone program rather than a script running +in a browser, there are far fewer constraints. HTTP Authentication is the best +solution in this scenario. + +To authenticate a websockets client with HTTP Basic Authentication +(:rfc:`7617`), include the credentials in the URI: + +.. code-block:: python + + async with websockets.connect( + f"wss://{username}:{password}@example.com", + ) as websocket: + ... + +(You must :func:`~urllib.parse.quote` ``username`` and ``password`` if they +contain unsafe characters.) + +To authenticate a websockets client with HTTP Bearer Authentication +(:rfc:`6750`), add a suitable ``Authorization`` header: + +.. code-block:: python + + async with websockets.connect( + "wss://example.com", + extra_headers={"Authorization": f"Bearer {token}"} + ) as websocket: + ... diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/authentication.svg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/authentication.svg new file mode 100644 index 0000000000000..ad2ad0e442881 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/authentication.svg @@ -0,0 +1,63 @@ +HTTPserverWebSocketserverweb appin browseruser accounts(1) authenticate user(2) obtain credentials(3) send credentials(4) authenticate user \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/broadcast.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/broadcast.rst new file mode 100644 index 0000000000000..1acb372d4f3ae --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/broadcast.rst @@ -0,0 +1,348 @@ +Broadcasting messages +===================== + +.. currentmodule:: websockets + + +.. admonition:: If you just want to send a message to all connected clients, + use :func:`broadcast`. + :class: tip + + If you want to learn about its design in depth, continue reading this + document. + +WebSocket servers often send the same message to all connected clients or to a +subset of clients for which the message is relevant. + +Let's explore options for broadcasting a message, explain the design +of :func:`broadcast`, and discuss alternatives. + +For each option, we'll provide a connection handler called ``handler()`` and a +function or coroutine called ``broadcast()`` that sends a message to all +connected clients. + +Integrating them is left as an exercise for the reader. You could start with:: + + import asyncio + import websockets + + async def handler(websocket): + ... + + async def broadcast(message): + ... + + async def broadcast_messages(): + while True: + await asyncio.sleep(1) + message = ... # your application logic goes here + await broadcast(message) + + async def main(): + async with websockets.serve(handler, "localhost", 8765): + await broadcast_messages() # runs forever + + if __name__ == "__main__": + asyncio.run(main()) + +``broadcast_messages()`` must yield control to the event loop between each +message, or else it will never let the server run. That's why it includes +``await asyncio.sleep(1)``. + +A complete example is available in the `experiments/broadcast`_ directory. + +.. _experiments/broadcast: https://github.com/python-websockets/websockets/tree/main/experiments/broadcast + +The naive way +------------- + +The most obvious way to send a message to all connected clients consists in +keeping track of them and sending the message to each of them. + +Here's a connection handler that registers clients in a global variable:: + + CLIENTS = set() + + async def handler(websocket): + CLIENTS.add(websocket) + try: + await websocket.wait_closed() + finally: + CLIENTS.remove(websocket) + +This implementation assumes that the client will never send any messages. If +you'd rather not make this assumption, you can change:: + + await websocket.wait_closed() + +to:: + + async for _ in websocket: + pass + +Here's a coroutine that broadcasts a message to all clients:: + + async def broadcast(message): + for websocket in CLIENTS.copy(): + try: + await websocket.send(message) + except websockets.ConnectionClosed: + pass + +There are two tricks in this version of ``broadcast()``. + +First, it makes a copy of ``CLIENTS`` before iterating it. Else, if a client +connects or disconnects while ``broadcast()`` is running, the loop would fail +with:: + + RuntimeError: Set changed size during iteration + +Second, it ignores :exc:`~exceptions.ConnectionClosed` exceptions because a +client could disconnect between the moment ``broadcast()`` makes a copy of +``CLIENTS`` and the moment it sends a message to this client. This is fine: a +client that disconnected doesn't belongs to "all connected clients" anymore. + +The naive way can be very fast. Indeed, if all connections have enough free +space in their write buffers, ``await websocket.send(message)`` writes the +message and returns immediately, as it doesn't need to wait for the buffer to +drain. In this case, ``broadcast()`` doesn't yield control to the event loop, +which minimizes overhead. + +The naive way can also fail badly. If the write buffer of a connection reaches +``write_limit``, ``broadcast()`` waits for the buffer to drain before sending +the message to other clients. This can cause a massive drop in performance. + +As a consequence, this pattern works only when write buffers never fill up, +which is usually outside of the control of the server. + +If you know for sure that you will never write more than ``write_limit`` bytes +within ``ping_interval + ping_timeout``, then websockets will terminate slow +connections before the write buffer has time to fill up. + +Don't set extreme ``write_limit``, ``ping_interval``, and ``ping_timeout`` +values to ensure that this condition holds. Set reasonable values and use the +built-in :func:`broadcast` function instead. + +The concurrent way +------------------ + +The naive way didn't work well because it serialized writes, while the whole +point of asynchronous I/O is to perform I/O concurrently. + +Let's modify ``broadcast()`` to send messages concurrently:: + + async def send(websocket, message): + try: + await websocket.send(message) + except websockets.ConnectionClosed: + pass + + def broadcast(message): + for websocket in CLIENTS: + asyncio.create_task(send(websocket, message)) + +We move the error handling logic in a new coroutine and we schedule +a :class:`~asyncio.Task` to run it instead of executing it immediately. + +Since ``broadcast()`` no longer awaits coroutines, we can make it a function +rather than a coroutine and do away with the copy of ``CLIENTS``. + +This version of ``broadcast()`` makes clients independent from one another: a +slow client won't block others. As a side effect, it makes messages +independent from one another. + +If you broadcast several messages, there is no strong guarantee that they will +be sent in the expected order. Fortunately, the event loop runs tasks in the +order in which they are created, so the order is correct in practice. + +Technically, this is an implementation detail of the event loop. However, it +seems unlikely for an event loop to run tasks in an order other than FIFO. + +If you wanted to enforce the order without relying this implementation detail, +you could be tempted to wait until all clients have received the message:: + + async def broadcast(message): + if CLIENTS: # asyncio.wait doesn't accept an empty list + await asyncio.wait([ + asyncio.create_task(send(websocket, message)) + for websocket in CLIENTS + ]) + +However, this doesn't really work in practice. Quite often, it will block +until the slowest client times out. + +Backpressure meets broadcast +---------------------------- + +At this point, it becomes apparent that backpressure, usually a good practice, +doesn't work well when broadcasting a message to thousands of clients. + +When you're sending messages to a single client, you don't want to send them +faster than the network can transfer them and the client accept them. This is +why :meth:`~server.WebSocketServerProtocol.send` checks if the write buffer +is full and, if it is, waits until it drain, giving the network and the +client time to catch up. This provides backpressure. + +Without backpressure, you could pile up data in the write buffer until the +server process runs out of memory and the operating system kills it. + +The :meth:`~server.WebSocketServerProtocol.send` API is designed to enforce +backpressure by default. This helps users of websockets write robust programs +even if they never heard about backpressure. + +For comparison, :class:`asyncio.StreamWriter` requires users to understand +backpressure and to await :meth:`~asyncio.StreamWriter.drain` explicitly +after each :meth:`~asyncio.StreamWriter.write`. + +When broadcasting messages, backpressure consists in slowing down all clients +in an attempt to let the slowest client catch up. With thousands of clients, +the slowest one is probably timing out and isn't going to receive the message +anyway. So it doesn't make sense to synchronize with the slowest client. + +How do we avoid running out of memory when slow clients can't keep up with the +broadcast rate, then? The most straightforward option is to disconnect them. + +If a client gets too far behind, eventually it reaches the limit defined by +``ping_timeout`` and websockets terminates the connection. You can read the +discussion of :doc:`keepalive and timeouts <./timeouts>` for details. + +How :func:`broadcast` works +--------------------------- + +The built-in :func:`broadcast` function is similar to the naive way. The main +difference is that it doesn't apply backpressure. + +This provides the best performance by avoiding the overhead of scheduling and +running one task per client. + +Also, when sending text messages, encoding to UTF-8 happens only once rather +than once per client, providing a small performance gain. + +Per-client queues +----------------- + +At this point, we deal with slow clients rather brutally: we disconnect then. + +Can we do better? For example, we could decide to skip or to batch messages, +depending on how far behind a client is. + +To implement this logic, we can create a queue of messages for each client and +run a task that gets messages from the queue and sends them to the client:: + + import asyncio + + CLIENTS = set() + + async def relay(queue, websocket): + while True: + # Implement custom logic based on queue.qsize() and + # websocket.transport.get_write_buffer_size() here. + message = await queue.get() + await websocket.send(message) + + async def handler(websocket): + queue = asyncio.Queue() + relay_task = asyncio.create_task(relay(queue, websocket)) + CLIENTS.add(queue) + try: + await websocket.wait_closed() + finally: + CLIENTS.remove(queue) + relay_task.cancel() + +Then we can broadcast a message by pushing it to all queues:: + + def broadcast(message): + for queue in CLIENTS: + queue.put_nowait(message) + +The queues provide an additional buffer between the ``broadcast()`` function +and clients. This makes it easier to support slow clients without excessive +memory usage because queued messages aren't duplicated to write buffers +until ``relay()`` processes them. + +Publish–subscribe +----------------- + +Can we avoid centralizing the list of connected clients in a global variable? + +If each client subscribes to a stream a messages, then broadcasting becomes as +simple as publishing a message to the stream. + +Here's a message stream that supports multiple consumers:: + + class PubSub: + def __init__(self): + self.waiter = asyncio.Future() + + def publish(self, value): + waiter, self.waiter = self.waiter, asyncio.Future() + waiter.set_result((value, self.waiter)) + + async def subscribe(self): + waiter = self.waiter + while True: + value, waiter = await waiter + yield value + + __aiter__ = subscribe + + PUBSUB = PubSub() + +The stream is implemented as a linked list of futures. It isn't necessary to +synchronize consumers. They can read the stream at their own pace, +independently from one another. Once all consumers read a message, there are +no references left, therefore the garbage collector deletes it. + +The connection handler subscribes to the stream and sends messages:: + + async def handler(websocket): + async for message in PUBSUB: + await websocket.send(message) + +The broadcast function publishes to the stream:: + + def broadcast(message): + PUBSUB.publish(message) + +Like per-client queues, this version supports slow clients with limited memory +usage. Unlike per-client queues, it makes it difficult to tell how far behind +a client is. The ``PubSub`` class could be extended or refactored to provide +this information. + +The ``for`` loop is gone from this version of the ``broadcast()`` function. +However, there's still a ``for`` loop iterating on all clients hidden deep +inside :mod:`asyncio`. When ``publish()`` sets the result of the ``waiter`` +future, :mod:`asyncio` loops on callbacks registered with this future and +schedules them. This is how connection handlers receive the next value from +the asynchronous iterator returned by ``subscribe()``. + +Performance considerations +-------------------------- + +The built-in :func:`broadcast` function sends all messages without yielding +control to the event loop. So does the naive way when the network and clients +are fast and reliable. + +For each client, a WebSocket frame is prepared and sent to the network. This +is the minimum amount of work required to broadcast a message. + +It would be tempting to prepare a frame and reuse it for all connections. +However, this isn't possible in general for two reasons: + +* Clients can negotiate different extensions. You would have to enforce the + same extensions with the same parameters. For example, you would have to + select some compression settings and reject clients that cannot support + these settings. + +* Extensions can be stateful, producing different encodings of the same + message depending on previous messages. For example, you would have to + disable context takeover to make compression stateless, resulting in poor + compression rates. + +All other patterns discussed above yield control to the event loop once per +client because messages are sent by different tasks. This makes them slower +than the built-in :func:`broadcast` function. + +There is no major difference between the performance of per-client queues and +publish–subscribe. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/compression.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/compression.rst new file mode 100644 index 0000000000000..eaf99070db015 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/compression.rst @@ -0,0 +1,222 @@ +Compression +=========== + +.. currentmodule:: websockets.extensions.permessage_deflate + +Most WebSocket servers exchange JSON messages because they're convenient to +parse and serialize in a browser. These messages contain text data and tend to +be repetitive. + +This makes the stream of messages highly compressible. Enabling compression +can reduce network traffic by more than 80%. + +There's a standard for compressing messages. :rfc:`7692` defines WebSocket +Per-Message Deflate, a compression extension based on the Deflate_ algorithm. + +.. _Deflate: https://en.wikipedia.org/wiki/Deflate + +Configuring compression +----------------------- + +:func:`~websockets.client.connect` and :func:`~websockets.server.serve` enable +compression by default because the reduction in network bandwidth is usually +worth the additional memory and CPU cost. + +If you want to disable compression, set ``compression=None``:: + + import websockets + + websockets.connect(..., compression=None) + + websockets.serve(..., compression=None) + +If you want to customize compression settings, you can enable the Per-Message +Deflate extension explicitly with :class:`ClientPerMessageDeflateFactory` or +:class:`ServerPerMessageDeflateFactory`:: + + 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}, + ), + ], + ) + + websockets.serve( + ..., + extensions=[ + permessage_deflate.ServerPerMessageDeflateFactory( + server_max_window_bits=11, + client_max_window_bits=11, + compress_settings={"memLevel": 4}, + ), + ], + ) + +The Window Bits and Memory Level values in these examples reduce memory usage +at the expense of compression rate. + +Compression settings +-------------------- + +When a client and a server enable the Per-Message Deflate extension, they +negotiate two parameters to guarantee compatibility between compression and +decompression. These parameters affect the trade-off between compression rate +and memory usage for both sides. + +* **Context Takeover** means that the compression context is retained between + messages. In other words, compression is applied to the stream of messages + rather than to each message individually. + + Context takeover should remain enabled to get good performance on + applications that send a stream of messages with similar structure, + that is, most applications. + + This requires retaining the compression context and state between messages, + which increases the memory footprint of a connection. + +* **Window Bits** controls the size of the compression context. It must be + an integer between 9 (lowest memory usage) and 15 (best compression). + Setting it to 8 is possible but rejected by some versions of zlib. + + On the server side, websockets defaults to 12. Specifically, the compression + window size (server to client) is always 12 while the decompression window + (client to server) size may be 12 or 15 depending on whether the client + supports configuring it. + + On the client side, websockets lets the server pick a suitable value, which + has the same effect as defaulting to 15. + +:mod:`zlib` offers additional parameters for tuning compression. They control +the trade-off between compression rate, memory usage, and CPU usage only for +compressing. They're transparent for decompressing. Unless mentioned +otherwise, websockets inherits defaults of :func:`~zlib.compressobj`. + +* **Memory Level** controls the size of the compression state. It must be an + integer between 1 (lowest memory usage) and 9 (best compression). + + websockets defaults to 5. This is lower than zlib's default of 8. Not only + does a lower memory level reduce memory usage, but it can also increase + speed thanks to memory locality. + +* **Compression Level** controls the effort to optimize compression. It must + be an integer between 1 (lowest CPU usage) and 9 (best compression). + +* **Strategy** selects the compression strategy. The best choice depends on + the type of data being compressed. + + +Tuning compression +------------------ + +For servers +........... + +By default, websockets enables compression with conservative settings that +optimize memory usage at the cost of a slightly worse compression rate: +Window Bits = 12 and Memory Level = 5. This strikes a good balance for small +messages that are typical of WebSocket servers. + +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. + +=========== ============ ============ ================ ================ +Window Bits Memory Level Memory usage Size vs. default Time vs. default +=========== ============ ============ ================ ================ +15 8 322 KiB -4.0% +15% +14 7 178 KiB -2.6% +10% +13 6 106 KiB -1.4% +5% +**12** **5** **70 KiB** **=** **=** +11 4 52 KiB +3.7% -5% +10 3 43 KiB +90% +50% +9 2 39 KiB +160% +100% +— — 19 KiB +452% — +=========== ============ ============ ================ ================ + +Window Bits and Memory Level don't have to move in lockstep. However, other +combinations don't yield significantly better results than those shown above. + +Compressed size and compression time depend heavily on the kind of messages +exchanged by the application so this example may not apply to your use case. + +You can adapt `compression/benchmark.py`_ by creating a list of typical +messages and passing it to the ``_run`` function. + +Window Bits = 11 and Memory Level = 4 looks like the sweet spot in this table. + +websockets defaults to Window Bits = 12 and Memory Level = 5 to stay away from +Window Bits = 10 or Memory Level = 3 where performance craters, raising doubts +on what could happen at Window Bits = 11 and Memory Level = 4 on a different +corpus. + +Defaults must be safe for all applications, hence a more conservative choice. + +.. _compression/benchmark.py: https://github.com/python-websockets/websockets/blob/main/experiments/compression/benchmark.py + +The benchmark focuses on compression because it's more expensive than +decompression. Indeed, leaving aside small allocations, theoretical memory +usage is: + +* ``(1 << (windowBits + 2)) + (1 << (memLevel + 9))`` for compression; +* ``1 << windowBits`` for decompression. + +CPU usage is also higher for compression than decompression. + +While it's always possible for a server to use a smaller window size for +compressing outgoing messages, using a smaller window size for decompressing +incoming messages requires collaboration from clients. + +When a client doesn't support configuring the size of its compression window, +websockets enables compression with the largest possible decompression window. +In most use cases, this is more efficient than disabling compression both ways. + +If you are very sensitive to memory usage, you can reverse this behavior by +setting the ``require_client_max_window_bits`` parameter of +:class:`ServerPerMessageDeflateFactory` to ``True``. + +For clients +........... + +By default, websockets enables compression with Memory Level = 5 but leaves +the Window Bits setting up to the server. + +There's two good reasons and one bad reason for not optimizing the client side +like the server side: + +1. If the maintainers of a server configured some optimized settings, we don't + want to override them with more restrictive settings. + +2. Optimizing memory usage doesn't matter very much for clients because it's + uncommon to open thousands of client connections in a program. + +3. On a more pragmatic note, some servers misbehave badly when a client + configures compression settings. `AWS API Gateway`_ is the worst offender. + + .. _AWS API Gateway: https://github.com/python-websockets/websockets/issues/1065 + + Unfortunately, even though websockets is right and AWS is wrong, many users + jump to the conclusion that websockets doesn't work. + + Until the ecosystem levels up, interoperability with buggy servers seems + more valuable than optimizing memory usage. + + +Further reading +--------------- + +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`_ recommends Window Bits = 11 and Memory +Level = 4 for optimizing memory usage. + +.. _experiment by Peter Thorson: https://mailarchive.ietf.org/arch/msg/hybi/F9t4uPufVEy8KBLuL36cZjCmM_Y/ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/data-flow.svg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/data-flow.svg new file mode 100644 index 0000000000000..749d9d482d2f0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/data-flow.svg @@ -0,0 +1,63 @@ +Integration layerSans-I/O layerApplicationreceivemessagessendmessagesNetworksenddatareceivedatareceivebytessendbytessendeventsreceiveevents \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/deployment.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/deployment.rst new file mode 100644 index 0000000000000..2a1fe9a785036 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/deployment.rst @@ -0,0 +1,181 @@ +Deployment +========== + +.. currentmodule:: websockets + +When you deploy your websockets server to production, at a high level, your +architecture will almost certainly look like the following diagram: + +.. image:: deployment.svg + +The basic unit for scaling a websockets server is "one server process". Each +blue box in the diagram represents one server process. + +There's more variation in routing. While the routing layer is shown as one big +box, it is likely to involve several subsystems. + +When you design a deployment, your should consider two questions: + +1. How will I run the appropriate number of server processes? +2. How will I route incoming connections to these processes? + +These questions are strongly related. There's a wide range of acceptable +answers, depending on your goals and your constraints. + +You can find a few concrete examples in the :ref:`deployment how-to guides +`. + +Running server processes +------------------------ + +How many processes do I need? +............................. + +Typically, one server process will manage a few hundreds or thousands +connections, depending on the frequency of messages and the amount of work +they require. + +CPU and memory usage increase with the number of connections to the server. + +Often CPU is the limiting factor. If a server process goes to 100% CPU, then +you reached the limit. How much headroom you want to keep is up to you. + +Once you know how many connections a server process can manage and how many +connections you need to handle, you can calculate how many processes to run. + +You can also automate this calculation by configuring an autoscaler to keep +CPU usage or connection count within acceptable limits. + +Don't scale with threads. Threads doesn't make sense for a server built with +:mod:`asyncio`. + +How do I run processes? +....................... + +Most solutions for running multiple instances of a server process fall into +one of these three buckets: + +1. Running N processes on a platform: + + * a Kubernetes Deployment + + * its equivalent on a Platform as a Service provider + +2. Running N servers: + + * an AWS Auto Scaling group, a GCP Managed instance group, etc. + + * a fixed set of long-lived servers + +3. Running N processes on a server: + + * preferably via a process manager or supervisor + +Option 1 is easiest of you have access to such a platform. + +Option 2 almost always combines with option 3. + +How do I start a process? +......................... + +Run a Python program that invokes :func:`~server.serve`. That's it. + +Don't run an ASGI server such as Uvicorn, Hypercorn, or Daphne. They're +alternatives to websockets, not complements. + +Don't run a WSGI server such as Gunicorn, Waitress, or mod_wsgi. They aren't +designed to run WebSocket applications. + +Applications servers handle network connections and expose a Python API. You +don't need one because websockets handles network connections directly. + +How do I stop a process? +........................ + +Process managers send the SIGTERM signal to terminate processes. Catch this +signal and exit the server to ensure a graceful shutdown. + +Here's an example: + +.. literalinclude:: ../../example/faq/shutdown_server.py + :emphasize-lines: 12-15,18 + +When exiting the context manager, :func:`~server.serve` closes all connections +with code 1001 (going away). As a consequence: + +* If the connection handler is awaiting + :meth:`~server.WebSocketServerProtocol.recv`, it receives a + :exc:`~exceptions.ConnectionClosedOK` exception. It can catch the exception + and clean up before exiting. + +* Otherwise, it should be waiting on + :meth:`~server.WebSocketServerProtocol.wait_closed`, so it can receive the + :exc:`~exceptions.ConnectionClosedOK` exception and exit. + +This example is easily adapted to handle other signals. + +If you override the default signal 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. + +Routing connections +------------------- + +What does routing involve? +.......................... + +Since the routing layer is directly exposed to the Internet, it should provide +appropriate protection against threats ranging from Internet background noise +to targeted attacks. + +You should always secure WebSocket connections with TLS. Since the routing +layer carries the public domain name, it should terminate TLS connections. + +Finally, it must route connections to the server processes, balancing new +connections across them. + +How do I route connections? +........................... + +Here are typical solutions for load balancing, matched to ways of running +processes: + +1. If you're running on a platform, it comes with a routing layer: + + * a Kubernetes Ingress and Service + + * a service mesh: Istio, Consul, Linkerd, etc. + + * the routing mesh of a Platform as a Service + +2. If you're running N servers, you may load balance with: + + * a cloud load balancer: AWS Elastic Load Balancing, GCP Cloud Load + Balancing, etc. + + * A software load balancer: HAProxy, NGINX, etc. + +3. If you're running N processes on a server, you may load balance with: + + * A software load balancer: HAProxy, NGINX, etc. + + * The operating system — all processes listen on the same port + +You may trust the load balancer to handle encryption and to provide security. +You may add another layer in front of the load balancer for these purposes. + +There are many possibilities. Don't add layers that you don't need, though. + +How do I implement a health check? +.................................. + +Load balancers need a way to check whether server processes are up and running +to avoid routing connections to a non-functional backend. + +websockets provide minimal support for responding to HTTP requests with the +:meth:`~server.WebSocketServerProtocol.process_request` hook. + +Here's an example: + +.. literalinclude:: ../../example/faq/health_check_server.py + :emphasize-lines: 7-9,18 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/deployment.svg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/deployment.svg new file mode 100644 index 0000000000000..fbacb18c4b7e3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/deployment.svg @@ -0,0 +1,63 @@ +Internetwebsocketswebsocketswebsocketsrouting \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/design.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/design.rst new file mode 100644 index 0000000000000..f164d29905ee7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/design.rst @@ -0,0 +1,572 @@ +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 :doc:`public APIs + <../reference/index>`. + +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:`~legacy.protocol.WebSocketCommonProtocol.connection_open` which runs when + the :ref:`opening handshake ` completes and the WebSocket + connection is established — not to be confused with + :meth:`~asyncio.BaseProtocol.connection_made` which runs when the TCP connection + is established; +- ``OPEN -> CLOSING``: in + :meth:`~legacy.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 `; also in + :meth:`~legacy.protocol.WebSocketCommonProtocol.fail_connection` which duplicates + a few lines of code from ``write_close_frame()`` and ``write_frame()``; +- ``* -> CLOSED``: in + :meth:`~legacy.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:`~legacy.protocol.WebSocketCommonProtocol.connection_open` starts +two tasks: + +- :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` runs + :meth:`~legacy.protocol.WebSocketCommonProtocol.transfer_data` which handles + incoming data and lets :meth:`~legacy.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 ` below. + +- :attr:`~legacy.protocol.WebSocketCommonProtocol.keepalive_ping_task` runs + :meth:`~legacy.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:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` runs + :meth:`~legacy.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 ` below. + +Besides, :meth:`~legacy.protocol.WebSocketCommonProtocol.fail_connection` starts +the same :attr:`~legacy.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:`~legacy.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:`~legacy.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 an HTTP request based on the ``uri`` and parameters passed to + :meth:`~client.connect`; +- writes the HTTP request to the network; +- reads an 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 an HTTP request from the network; +- calls :meth:`~server.WebSocketServerProtocol.process_request` which may + abort the WebSocket handshake and return an 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 an 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 `. + + +.. _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://www.rfc-editor.org/rfc/rfc6455.html#section-5.3 +.. _closing the TCP connection: https://www.rfc-editor.org/rfc/rfc6455.html#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:`~legacy.protocol.WebSocketCommonProtocol`. + +.. _data framing: https://www.rfc-editor.org/rfc/rfc6455.html#section-5 +.. _sending and receiving data: https://www.rfc-editor.org/rfc/rfc6455.html#section-6 +.. _closing the connection: https://www.rfc-editor.org/rfc/rfc6455.html#section-7 + +The :attr:`~legacy.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:`~legacy.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:`~legacy.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:`~legacy.protocol.WebSocketCommonProtocol.recv` at any time. While this +happens naturally in many use cases, it cannot be relied upon. + +Then :meth:`~legacy.protocol.WebSocketCommonProtocol.recv` fetches the next message +from the :attr:`~legacy.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:`~legacy.protocol.WebSocketCommonProtocol.send` writes one or several data +frames containing the message. While sending a fragmented message, concurrent +calls to :meth:`~legacy.protocol.WebSocketCommonProtocol.send` are put on hold until +all fragments are sent. This makes concurrent calls safe. + +:meth:`~legacy.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:`~legacy.protocol.WebSocketCommonProtocol.pong` writes a pong frame. + +:meth:`~legacy.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:`~legacy.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 :obj:`None`, causing +:attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` to terminate. + +When this side of the connection initiates the closing handshake with +:meth:`~legacy.protocol.WebSocketCommonProtocol.close`, it moves to the ``CLOSING`` +state and sends a close frame. When the other side sends a close frame, +:meth:`~legacy.protocol.WebSocketCommonProtocol.read_message` receives it in the +``CLOSING`` state and returns :obj:`None`, also causing +:attr:`~legacy.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 `. + +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:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task`, which is +started when the WebSocket connection is established, is responsible for +eventually closing the TCP connection. + +First :attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` waits +for :attr:`~legacy.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:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task`; +- a timeout while waiting for the closing handshake to complete: this cancels + :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task`; +- a protocol error, including connection errors: depending on the exception, + :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` :ref:`fails the + connection ` with a suitable code and exits. + +:attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` is separate +from :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` to make it +easier to implement the timeout on the closing handshake. Canceling +:attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` creates no risk +of canceling :attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` +and failing to close the TCP connection, thus leaking resources. + +Then :attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` cancels +:meth:`~legacy.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:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` +and sending a close frame if appropriate. + +:attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` exits, unblocking +:attr:`~legacy.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 `_). 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 <../reference/index>` 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:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` and +:attr:`~legacy.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:`~legacy.protocol.WebSocketCommonProtocol.recv` waits for the next message in +the queue or for :attr:`~legacy.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:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task`, it doesn't +propagate cancellation to them. + +:meth:`~legacy.protocol.WebSocketCommonProtocol.ensure_open` is called by +:meth:`~legacy.protocol.WebSocketCommonProtocol.send`, +:meth:`~legacy.protocol.WebSocketCommonProtocol.ping`, and +:meth:`~legacy.protocol.WebSocketCommonProtocol.pong`. When the connection state is +``CLOSING``, it waits for +:attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` but shields it to +prevent cancellation. + +:meth:`~legacy.protocol.WebSocketCommonProtocol.close` waits for the data transfer +task to terminate with :func:`~asyncio.timeout`. If it's canceled or if the +timeout elapses, :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` +is canceled, which is correct at this point. +:meth:`~legacy.protocol.WebSocketCommonProtocol.close` then waits for +:attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` but shields it +to prevent cancellation. + +:meth:`~legacy.protocol.WebSocketCommonProtocol.close` and +:meth:`~legacy.protocol.WebSocketCommonProtocol.fail_connection` are the only +places where :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` may +be canceled. + +:attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` starts by +waiting for :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task`. It +catches :exc:`~asyncio.CancelledError` to prevent a cancellation of +:attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` from propagating +to :attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_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:`~legacy.protocol.WebSocketCommonProtocol.recv`, +:meth:`~legacy.protocol.WebSocketCommonProtocol.send`, +:meth:`~legacy.protocol.WebSocketCommonProtocol.close` +:meth:`~legacy.protocol.WebSocketCommonProtocol.ping`, or +:meth:`~legacy.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:`~legacy.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:`~legacy.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:`~legacy.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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/index.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/index.rst new file mode 100644 index 0000000000000..120a3dd3277c9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/index.rst @@ -0,0 +1,18 @@ +Topic guides +============ + +Get a deeper understanding of how websockets is built and why. + +.. toctree:: + :titlesonly: + + deployment + logging + authentication + broadcast + compression + timeouts + design + memory + security + performance diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/lifecycle.graffle b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/lifecycle.graffle new file mode 100644 index 0000000000000..a8ab7ff09f52a Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/lifecycle.graffle differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/lifecycle.svg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/lifecycle.svg new file mode 100644 index 0000000000000..0a9818d2930c5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/lifecycle.svg @@ -0,0 +1,3 @@ + + + Produced by OmniGraffle 6.6.2 2018-07-29 15:25:34 +0000Canvas 1Layer 1CONNECTINGOPENCLOSINGCLOSEDtransfer_dataclose_connectionconnectrecv / send / ping / pong / close opening handshakeconnectionterminationdata transfer& closing handshakekeepalive_ping diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/logging.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/logging.rst new file mode 100644 index 0000000000000..e7abd96ce502b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/logging.rst @@ -0,0 +1,245 @@ +Logging +======= + +.. currentmodule:: websockets + +Logs contents +------------- + +When you run a WebSocket client, your code calls coroutines provided by +websockets. + +If an error occurs, websockets tells you by raising an exception. For example, +it raises a :exc:`~exceptions.ConnectionClosed` exception if the other side +closes the connection. + +When you run a WebSocket server, websockets accepts connections, performs the +opening handshake, runs the connection handler coroutine that you provided, +and performs the closing handshake. + +Given this `inversion of control`_, if an error happens in the opening +handshake or if the connection handler crashes, there is no way to raise an +exception that you can handle. + +.. _inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control + +Logs tell you about these errors. + +Besides errors, you may want to record the activity of the server. + +In a request/response protocol such as HTTP, there's an obvious way to record +activity: log one event per request/response. Unfortunately, this solution +doesn't work well for a bidirectional protocol such as WebSocket. + +Instead, when running as a server, websockets logs one event when a +`connection is established`_ and another event when a `connection is +closed`_. + +.. _connection is established: https://www.rfc-editor.org/rfc/rfc6455.html#section-4 +.. _connection is closed: https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.4 + +By default, websockets doesn't log an event for every message. That would be +excessive for many applications exchanging small messages at a fast rate. If +you need this level of detail, you could add logging in your own code. + +Finally, you can enable debug logs to get details about everything websockets +is doing. This can be useful when developing clients as well as servers. + +See :ref:`log levels ` below for a list of events logged by +websockets logs at each log level. + +Configure logging +----------------- + +websockets relies on the :mod:`logging` module from the standard library in +order to maximize compatibility and integrate nicely with other libraries:: + + import logging + +websockets logs to the ``"websockets.client"`` and ``"websockets.server"`` +loggers. + +websockets doesn't provide a default logging configuration because +requirements vary a lot depending on the environment. + +Here's a basic configuration for a server in production:: + + logging.basicConfig( + format="%(asctime)s %(message)s", + level=logging.INFO, + ) + +Here's how to enable debug logs for development:: + + logging.basicConfig( + format="%(message)s", + level=logging.DEBUG, + ) + +Furthermore, websockets adds a ``websocket`` attribute to log records, so you +can include additional information about the current connection in logs. + +You could attempt to add information with a formatter:: + + # this doesn't work! + logging.basicConfig( + format="{asctime} {websocket.id} {websocket.remote_address[0]} {message}", + level=logging.INFO, + style="{", + ) + +However, this technique runs into two problems: + +* The formatter applies to all records. It will crash if it receives a record + without a ``websocket`` attribute. For example, this happens when logging + that the server starts because there is no current connection. + +* Even with :meth:`str.format` style, you're restricted to attribute and index + lookups, which isn't enough to implement some fairly simple requirements. + +There's a better way. :func:`~client.connect` and :func:`~server.serve` accept +a ``logger`` argument to override the default :class:`~logging.Logger`. You +can set ``logger`` to a :class:`~logging.LoggerAdapter` that enriches logs. + +For example, if the server is behind a reverse +proxy, :attr:`~legacy.protocol.WebSocketCommonProtocol.remote_address` gives +the IP address of the proxy, which isn't useful. IP addresses of clients are +provided in an HTTP header set by the proxy. + +Here's how to include them in logs, assuming they're in the +``X-Forwarded-For`` header:: + + logging.basicConfig( + format="%(asctime)s %(message)s", + level=logging.INFO, + ) + + class LoggerAdapter(logging.LoggerAdapter): + """Add connection ID and client IP address to websockets logs.""" + def process(self, msg, kwargs): + try: + websocket = kwargs["extra"]["websocket"] + except KeyError: + return msg, kwargs + xff = websocket.request_headers.get("X-Forwarded-For") + return f"{websocket.id} {xff} {msg}", kwargs + + async with websockets.serve( + ..., + # Python < 3.10 requires passing None as the second argument. + logger=LoggerAdapter(logging.getLogger("websockets.server"), None), + ): + ... + +Logging to JSON +--------------- + +Even though :mod:`logging` predates structured logging, it's still possible to +output logs as JSON with a bit of effort. + +First, we need a :class:`~logging.Formatter` that renders JSON: + +.. literalinclude:: ../../example/logging/json_log_formatter.py + +Then, we configure logging to apply this formatter:: + + handler = logging.StreamHandler() + handler.setFormatter(formatter) + + logger = logging.getLogger() + logger.addHandler(handler) + logger.setLevel(logging.INFO) + +Finally, we populate the ``event_data`` custom attribute in log records with +a :class:`~logging.LoggerAdapter`:: + + class LoggerAdapter(logging.LoggerAdapter): + """Add connection ID and client IP address to websockets logs.""" + def process(self, msg, kwargs): + try: + websocket = kwargs["extra"]["websocket"] + except KeyError: + return msg, kwargs + kwargs["extra"]["event_data"] = { + "connection_id": str(websocket.id), + "remote_addr": websocket.request_headers.get("X-Forwarded-For"), + } + return msg, kwargs + + async with websockets.serve( + ..., + # Python < 3.10 requires passing None as the second argument. + logger=LoggerAdapter(logging.getLogger("websockets.server"), None), + ): + ... + +Disable logging +--------------- + +If your application doesn't configure :mod:`logging`, Python outputs messages +of severity ``WARNING`` and higher to :data:`~sys.stderr`. As a consequence, +you will see a message and a stack trace if a connection handler coroutine +crashes or if you hit a bug in websockets. + +If you want to disable this behavior for websockets, you can add +a :class:`~logging.NullHandler`:: + + logging.getLogger("websockets").addHandler(logging.NullHandler()) + +Additionally, if your application configures :mod:`logging`, you must disable +propagation to the root logger, or else its handlers could output logs:: + + logging.getLogger("websockets").propagate = False + +Alternatively, you could set the log level to ``CRITICAL`` for the +``"websockets"`` logger, as the highest level currently used is ``ERROR``:: + + logging.getLogger("websockets").setLevel(logging.CRITICAL) + +Or you could configure a filter to drop all messages:: + + logging.getLogger("websockets").addFilter(lambda record: None) + +.. _log-levels: + +Log levels +---------- + +Here's what websockets logs at each level. + +``ERROR`` +......... + +* Exceptions raised by connection handler coroutines in servers +* Exceptions resulting from bugs in websockets + +``WARNING`` +........... + +* Failures in :func:`~websockets.broadcast` + +``INFO`` +........ + +* Server starting and stopping +* Server establishing and closing connections +* Client reconnecting automatically + +``DEBUG`` +......... + +* Changes to the state of connections +* Handshake requests and responses +* All frames sent and received +* Steps to close a connection +* Keepalive pings and pongs +* Errors handled transparently + +Debug messages have cute prefixes that make logs easier to scan: + +* ``>`` - send something +* ``<`` - receive something +* ``=`` - set connection state +* ``x`` - shut down connection +* ``%`` - manage pings and pongs +* ``!`` - handle errors and timeouts diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/memory.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/memory.rst new file mode 100644 index 0000000000000..e44247a77c5fc --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/memory.rst @@ -0,0 +1,48 @@ +Memory usage +============ + +.. currentmodule:: websockets + +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. + +With websockets' defaults, on the server side, a single connections uses +70 KiB of memory. + +Refer to the :doc:`topic guide on compression <../topics/compression>` to +learn more about tuning compression settings. + +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 usage. + +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 `. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/performance.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/performance.rst new file mode 100644 index 0000000000000..45e23b2390a3c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/performance.rst @@ -0,0 +1,20 @@ +Performance +=========== + +Here are tips to optimize performance. + +uvloop +------ + +You can make a websockets application faster by running it with uvloop_. + +(This advice isn't specific to websockets. It applies to any :mod:`asyncio` +application.) + +.. _uvloop: https://github.com/MagicStack/uvloop + +broadcast +--------- + +:func:`~websockets.broadcast` is the most efficient way to send a message to +many clients. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/protocol.graffle b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/protocol.graffle new file mode 100644 index 0000000000000..df76f49607e57 Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/protocol.graffle differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/protocol.svg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/protocol.svg new file mode 100644 index 0000000000000..51bfd982be7ba --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/protocol.svg @@ -0,0 +1,3 @@ + + + Produced by OmniGraffle 6.6.2 2019-07-07 08:38:24 +0000Canvas 1Layer 1remote endpointwebsocketsWebSocketCommonProtocolapplication logicreaderStreamReaderwriterStreamWriterpingsdicttransfer_data_taskTasknetworkread_frameread_data_frameread_messagebytesframesdataframeswrite_framemessagesdequerecvsendpingpongclosecontrolframesbytesframes diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/security.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/security.rst new file mode 100644 index 0000000000000..d3dec21bd1039 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/security.rst @@ -0,0 +1,41 @@ +Security +======== + +Encryption +---------- + +For production use, a server should require encrypted connections. + +See this example of :ref:`encrypting connections with TLS +`. + +Memory usage +------------ + +.. 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 70 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 usage. + +Configuring a server to :doc:`optimize 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 these limits, you can monkey-patch the constants in +``websockets.http11``. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/timeouts.rst b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/timeouts.rst new file mode 100644 index 0000000000000..633fc1ab43145 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/timeouts.rst @@ -0,0 +1,116 @@ +Timeouts +======== + +.. currentmodule:: websockets + +Long-lived connections +---------------------- + +Since the WebSocket protocol is intended for real-time communications over +long-lived connections, it is desirable to ensure that connections don't +break, and if they do, to report the problem quickly. + +Connections can drop as a consequence of temporary network connectivity issues, +which are very common, even within data centers. + +Furthermore, WebSocket builds on top of HTTP/1.1 where connections are +short-lived, even with ``Connection: keep-alive``. Typically, HTTP/1.1 +infrastructure closes idle connections after 30 to 120 seconds. + +As a consequence, proxies may terminate WebSocket connections prematurely when +no message was exchanged in 30 seconds. + +.. _keepalive: + +Keepalive in websockets +----------------------- + +To avoid these problems, websockets runs a keepalive and heartbeat mechanism +based on WebSocket Ping_ and Pong_ frames, which are designed for this purpose. + +.. _Ping: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.5.2 +.. _Pong: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.5.3 + +It loops through these steps: + +1. Wait 20 seconds. +2. Send a Ping frame. +3. Receive a corresponding Pong frame within 20 seconds. + +If the Pong frame isn't received, websockets considers the connection broken and +closes it. + +This mechanism serves two purposes: + +1. It creates a trickle of traffic so that the TCP connection isn't idle and + network infrastructure along the path keeps it open ("keepalive"). +2. It detects if the connection drops or becomes so slow that it's unusable in + practice ("heartbeat"). In that case, it terminates the connection and your + application gets a :exc:`~exceptions.ConnectionClosed` exception. + +Timings are configurable with the ``ping_interval`` and ``ping_timeout`` +arguments of :func:`~client.connect` and :func:`~server.serve`. Shorter values +will detect connection drops faster but they will increase network traffic and +they will be more sensitive to latency. + +Setting ``ping_interval`` to :obj:`None` disables the whole keepalive and +heartbeat mechanism. + +Setting ``ping_timeout`` to :obj:`None` disables only timeouts. This enables +keepalive, to keep idle connections open, and disables heartbeat, to support large +latency spikes. + +.. admonition:: Why doesn't websockets rely on TCP keepalive? + :class: hint + + TCP keepalive is disabled by default on most operating systems. When + enabled, the default interval is two hours or more, which is far too much. + +Keepalive in browsers +--------------------- + +Browsers don't enable a keepalive mechanism like websockets by default. As a +consequence, they can fail to notice that a WebSocket connection is broken for +an extended period of time, until the TCP connection times out. + +In this scenario, the ``WebSocket`` object in the browser doesn't fire a +``close`` event. If you have a reconnection mechanism, it doesn't kick in +because it believes that the connection is still working. + +If your browser-based app mysteriously and randomly fails to receive events, +this is a likely cause. You need a keepalive mechanism in the browser to avoid +this scenario. + +Unfortunately, the WebSocket API in browsers doesn't expose the native Ping and +Pong functionality in the WebSocket protocol. You have to roll your own in the +application layer. + +Latency issues +-------------- + +Latency between a client and a server may increase for two reasons: + +* Network connectivity is poor. When network packets are lost, TCP attempts to + retransmit them, which manifests as latency. Excessive packet loss makes + the connection unusable in practice. At some point, timing out is a + reasonable choice. + +* Traffic is high. For example, if a client sends messages on the connection + faster than a server can process them, this manifests as latency as well, + because data is waiting in flight, mostly in OS buffers. + + If the server is more than 20 seconds behind, it doesn't see the Pong before + the default timeout elapses. As a consequence, it closes the connection. + This is a reasonable choice to prevent overload. + + If traffic spikes cause unwanted timeouts and you're confident that the server + will catch up eventually, you can increase ``ping_timeout`` or you can set it + to :obj:`None` to disable heartbeat entirely. + + The same reasoning applies to situations where the server sends more traffic + than the client can accept. + +The latency measured during the last exchange of Ping and Pong frames is +available in the :attr:`~legacy.protocol.WebSocketCommonProtocol.latency` +attribute. Alternatively, you can measure the latency at any time with the +:attr:`~legacy.protocol.WebSocketCommonProtocol.ping` method. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/w3c-import.log new file mode 100644 index 0000000000000..fdc6a235188c3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/w3c-import.log @@ -0,0 +1,34 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/authentication.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/authentication.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/broadcast.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/compression.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/data-flow.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/deployment.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/deployment.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/design.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/lifecycle.graffle +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/lifecycle.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/logging.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/memory.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/performance.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/protocol.graffle +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/protocol.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/security.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/topics/timeouts.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/w3c-import.log new file mode 100644 index 0000000000000..25b208d2bf943 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/Makefile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/conf.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/index.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/make.bat +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/requirements.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/docs/spelling_wordlist.txt diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/Procfile b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/Procfile new file mode 100644 index 0000000000000..2e35818f675d5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/Procfile @@ -0,0 +1 @@ +web: python app.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/app.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/app.py new file mode 100644 index 0000000000000..4ca34d23bbede --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/app.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +import asyncio +import http +import signal + +import websockets + + +async def echo(websocket): + async for message in websocket: + await websocket.send(message) + + +async def health_check(path, request_headers): + if path == "/healthz": + return http.HTTPStatus.OK, [], b"OK\n" + + +async def main(): + # Set the stop condition when receiving SIGTERM. + loop = asyncio.get_running_loop() + stop = loop.create_future() + loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) + + async with websockets.serve( + echo, + host="", + port=8080, + process_request=health_check, + ): + await stop + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/fly.toml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/fly.toml new file mode 100644 index 0000000000000..5290072ed28c6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/fly.toml @@ -0,0 +1,16 @@ +app = "websockets-echo" +kill_signal = "SIGTERM" + +[build] + builder = "paketobuildpacks/builder:base" + +[[services]] + internal_port = 8080 + protocol = "tcp" + + [[services.http_checks]] + path = "/healthz" + + [[services.ports]] + handlers = ["tls", "http"] + port = 443 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/requirements.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/requirements.txt new file mode 100644 index 0000000000000..14774b465e97f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/requirements.txt @@ -0,0 +1 @@ +websockets diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/w3c-import.log new file mode 100644 index 0000000000000..9a5dccb8cb50a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/Procfile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/app.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/fly.toml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/fly/requirements.txt diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/haproxy/app.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/haproxy/app.py new file mode 100644 index 0000000000000..360479b8eb655 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/haproxy/app.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +import asyncio +import os +import signal + +import websockets + + +async def echo(websocket): + async for message in websocket: + await websocket.send(message) + + +async def main(): + # Set the stop condition when receiving SIGTERM. + loop = asyncio.get_running_loop() + stop = loop.create_future() + loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) + + async with websockets.serve( + echo, + host="localhost", + port=8000 + int(os.environ["SUPERVISOR_PROCESS_NAME"][-2:]), + ): + await stop + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/haproxy/haproxy.cfg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/haproxy/haproxy.cfg new file mode 100644 index 0000000000000..e63727d1c0e5b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/haproxy/haproxy.cfg @@ -0,0 +1,17 @@ +defaults + mode http + timeout connect 10s + timeout client 30s + timeout server 30s + +frontend websocket + bind localhost:8080 + default_backend websocket + +backend websocket + balance leastconn + server websockets-test_00 localhost:8000 + server websockets-test_01 localhost:8001 + server websockets-test_02 localhost:8002 + server websockets-test_03 localhost:8003 + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/haproxy/supervisord.conf b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/haproxy/supervisord.conf new file mode 100644 index 0000000000000..76a664d91b12d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/haproxy/supervisord.conf @@ -0,0 +1,7 @@ +[supervisord] + +[program:websockets-test] +command = python app.py +process_name = %(program_name)s_%(process_num)02d +numprocs = 4 +autorestart = true diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/haproxy/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/haproxy/w3c-import.log new file mode 100644 index 0000000000000..012df886093e0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/haproxy/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/haproxy/app.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/haproxy/haproxy.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/haproxy/supervisord.conf diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/heroku/Procfile b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/heroku/Procfile new file mode 100644 index 0000000000000..2e35818f675d5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/heroku/Procfile @@ -0,0 +1 @@ +web: python app.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/heroku/app.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/heroku/app.py new file mode 100644 index 0000000000000..d4ba3edb51110 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/heroku/app.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +import asyncio +import signal +import os + +import websockets + + +async def echo(websocket): + async for message in websocket: + await websocket.send(message) + + +async def main(): + # Set the stop condition when receiving SIGTERM. + loop = asyncio.get_running_loop() + stop = loop.create_future() + loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) + + async with websockets.serve( + echo, + host="", + port=int(os.environ["PORT"]), + ): + await stop + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/heroku/requirements.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/heroku/requirements.txt new file mode 100644 index 0000000000000..14774b465e97f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/heroku/requirements.txt @@ -0,0 +1 @@ +websockets diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/heroku/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/heroku/w3c-import.log new file mode 100644 index 0000000000000..09a868e109e5b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/heroku/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/heroku/Procfile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/heroku/app.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/heroku/requirements.txt diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/Dockerfile b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/Dockerfile new file mode 100644 index 0000000000000..83ed8722c0713 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.9-alpine + +RUN pip install websockets + +COPY app.py . + +CMD ["python", "app.py"] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/app.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/app.py new file mode 100644 index 0000000000000..a8bcef68813c9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/app.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +import asyncio +import http +import signal +import sys +import time + +import websockets + + +async def slow_echo(websocket): + async for message in websocket: + # Block the event loop! This allows saturating a single asyncio + # process without opening an impractical number of connections. + time.sleep(0.1) # 100ms + await websocket.send(message) + + +async def health_check(path, request_headers): + if path == "/healthz": + return http.HTTPStatus.OK, [], b"OK\n" + if path == "/inemuri": + loop = asyncio.get_running_loop() + loop.call_later(1, time.sleep, 10) + return http.HTTPStatus.OK, [], b"Sleeping for 10s\n" + if path == "/seppuku": + loop = asyncio.get_running_loop() + loop.call_later(1, sys.exit, 69) + return http.HTTPStatus.OK, [], b"Terminating\n" + + +async def main(): + # Set the stop condition when receiving SIGTERM. + loop = asyncio.get_running_loop() + stop = loop.create_future() + loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) + + async with websockets.serve( + slow_echo, + host="", + port=80, + process_request=health_check, + ): + await stop + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/benchmark.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/benchmark.py new file mode 100644 index 0000000000000..22ee4c5bd7b9d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/benchmark.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +import asyncio +import sys +import websockets + + +URI = "ws://localhost:32080" + + +async def run(client_id, messages): + async with websockets.connect(URI) as websocket: + for message_id in range(messages): + await websocket.send(f"{client_id}:{message_id}") + await websocket.recv() + + +async def benchmark(clients, messages): + await asyncio.wait([ + asyncio.create_task(run(client_id, messages)) + for client_id in range(clients) + ]) + + +if __name__ == "__main__": + clients, messages = int(sys.argv[1]), int(sys.argv[2]) + asyncio.run(benchmark(clients, messages)) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/deployment.yaml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/deployment.yaml new file mode 100644 index 0000000000000..ba58dd62bf4e7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/deployment.yaml @@ -0,0 +1,35 @@ +apiVersion: v1 +kind: Service +metadata: + name: websockets-test +spec: + type: NodePort + ports: + - port: 80 + nodePort: 32080 + selector: + app: websockets-test +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: websockets-test +spec: + selector: + matchLabels: + app: websockets-test + template: + metadata: + labels: + app: websockets-test + spec: + containers: + - name: websockets-test + image: websockets-test:1.0 + livenessProbe: + httpGet: + path: /healthz + port: 80 + periodSeconds: 1 + ports: + - containerPort: 80 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/w3c-import.log new file mode 100644 index 0000000000000..c91ecbbd69561 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/Dockerfile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/app.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/benchmark.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/kubernetes/deployment.yaml diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/nginx/app.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/nginx/app.py new file mode 100644 index 0000000000000..24e60897562fa --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/nginx/app.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +import asyncio +import os +import signal + +import websockets + + +async def echo(websocket): + async for message in websocket: + await websocket.send(message) + + +async def main(): + # Set the stop condition when receiving SIGTERM. + loop = asyncio.get_running_loop() + stop = loop.create_future() + loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) + + async with websockets.unix_serve( + echo, + path=f"{os.environ['SUPERVISOR_PROCESS_NAME']}.sock", + ): + await stop + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/nginx/nginx.conf b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/nginx/nginx.conf new file mode 100644 index 0000000000000..67aa0086d54f3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/nginx/nginx.conf @@ -0,0 +1,25 @@ +daemon off; + +events { +} + +http { + server { + listen localhost:8080; + + location / { + proxy_http_version 1.1; + proxy_pass http://websocket; + proxy_set_header Connection $http_connection; + proxy_set_header Upgrade $http_upgrade; + } + } + + upstream websocket { + least_conn; + server unix:websockets-test_00.sock; + server unix:websockets-test_01.sock; + server unix:websockets-test_02.sock; + server unix:websockets-test_03.sock; + } +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/nginx/supervisord.conf b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/nginx/supervisord.conf new file mode 100644 index 0000000000000..76a664d91b12d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/nginx/supervisord.conf @@ -0,0 +1,7 @@ +[supervisord] + +[program:websockets-test] +command = python app.py +process_name = %(program_name)s_%(process_num)02d +numprocs = 4 +autorestart = true diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/nginx/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/nginx/w3c-import.log new file mode 100644 index 0000000000000..599eebb9521f3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/nginx/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/nginx/app.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/nginx/nginx.conf +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/nginx/supervisord.conf diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/render/app.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/render/app.py new file mode 100644 index 0000000000000..4ca34d23bbede --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/render/app.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +import asyncio +import http +import signal + +import websockets + + +async def echo(websocket): + async for message in websocket: + await websocket.send(message) + + +async def health_check(path, request_headers): + if path == "/healthz": + return http.HTTPStatus.OK, [], b"OK\n" + + +async def main(): + # Set the stop condition when receiving SIGTERM. + loop = asyncio.get_running_loop() + stop = loop.create_future() + loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) + + async with websockets.serve( + echo, + host="", + port=8080, + process_request=health_check, + ): + await stop + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/render/requirements.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/render/requirements.txt new file mode 100644 index 0000000000000..14774b465e97f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/render/requirements.txt @@ -0,0 +1 @@ +websockets diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/render/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/render/w3c-import.log new file mode 100644 index 0000000000000..fc5cad7f75937 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/render/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/render/app.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/render/requirements.txt diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/supervisor/app.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/supervisor/app.py new file mode 100644 index 0000000000000..bf61983ef7b7a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/supervisor/app.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +import asyncio +import signal + +import websockets + + +async def echo(websocket): + async for message in websocket: + await websocket.send(message) + + +async def main(): + # Set the stop condition when receiving SIGTERM. + loop = asyncio.get_running_loop() + stop = loop.create_future() + loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) + + async with websockets.serve( + echo, + host="", + port=8080, + reuse_port=True, + ): + await stop + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/supervisor/supervisord.conf b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/supervisor/supervisord.conf new file mode 100644 index 0000000000000..76a664d91b12d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/supervisor/supervisord.conf @@ -0,0 +1,7 @@ +[supervisord] + +[program:websockets-test] +command = python app.py +process_name = %(program_name)s_%(process_num)02d +numprocs = 4 +autorestart = true diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/supervisor/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/supervisor/w3c-import.log new file mode 100644 index 0000000000000..5b91d52a094b4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/supervisor/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/supervisor/app.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/deployment/supervisor/supervisord.conf diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/django/authentication.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/django/authentication.py new file mode 100644 index 0000000000000..f6dad0f55ef01 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/django/authentication.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +import asyncio + +import django +import websockets + +django.setup() + +from sesame.utils import get_user +from websockets.frames import CloseCode + + +async def handler(websocket): + sesame = await websocket.recv() + user = await asyncio.to_thread(get_user, sesame) + if user is None: + await websocket.close(CloseCode.INTERNAL_ERROR, "authentication failed") + return + + await websocket.send(f"Hello {user}!") + + +async def main(): + async with websockets.serve(handler, "localhost", 8888): + await asyncio.Future() # run forever + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/django/notifications.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/django/notifications.py new file mode 100644 index 0000000000000..3a9ed10cf0966 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/django/notifications.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python + +import asyncio +import json + +import aioredis +import django +import websockets + +django.setup() + +from django.contrib.contenttypes.models import ContentType +from sesame.utils import get_user +from websockets.frames import CloseCode + + +CONNECTIONS = {} + + +def get_content_types(user): + """Return the set of IDs of content types visible by user.""" + # This does only three database queries because Django caches + # all permissions on the first call to user.has_perm(...). + return { + ct.id + for ct in ContentType.objects.all() + if user.has_perm(f"{ct.app_label}.view_{ct.model}") + or user.has_perm(f"{ct.app_label}.change_{ct.model}") + } + + +async def handler(websocket): + """Authenticate user and register connection in CONNECTIONS.""" + sesame = await websocket.recv() + user = await asyncio.to_thread(get_user, sesame) + if user is None: + await websocket.close(CloseCode.INTERNAL_ERROR, "authentication failed") + return + + ct_ids = await asyncio.to_thread(get_content_types, user) + CONNECTIONS[websocket] = {"content_type_ids": ct_ids} + try: + await websocket.wait_closed() + finally: + del CONNECTIONS[websocket] + + +async def process_events(): + """Listen to events in Redis and process them.""" + redis = aioredis.from_url("redis://127.0.0.1:6379/1") + pubsub = redis.pubsub() + await pubsub.subscribe("events") + async for message in pubsub.listen(): + if message["type"] != "message": + continue + payload = message["data"].decode() + # Broadcast event to all users who have permissions to see it. + event = json.loads(payload) + recipients = ( + websocket + for websocket, connection in CONNECTIONS.items() + if event["content_type_id"] in connection["content_type_ids"] + ) + websockets.broadcast(recipients, payload) + + +async def main(): + async with websockets.serve(handler, "localhost", 8888): + await process_events() # runs forever + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/django/signals.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/django/signals.py new file mode 100644 index 0000000000000..6dc827f72d70f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/django/signals.py @@ -0,0 +1,23 @@ +import json + +from django.contrib.admin.models import LogEntry +from django.db.models.signals import post_save +from django.dispatch import receiver + +from django_redis import get_redis_connection + + +@receiver(post_save, sender=LogEntry) +def publish_event(instance, **kwargs): + event = { + "model": instance.content_type.name, + "object": instance.object_repr, + "message": instance.get_change_message(), + "timestamp": instance.action_time.isoformat(), + "user": str(instance.user), + "content_type_id": instance.content_type_id, + "object_id": instance.object_id, + } + connection = get_redis_connection("default") + payload = json.dumps(event) + connection.publish("events", payload) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/django/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/django/w3c-import.log new file mode 100644 index 0000000000000..24cb2b72a30f1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/django/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/django/authentication.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/django/notifications.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/django/signals.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/echo.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/echo.py new file mode 100644 index 0000000000000..2e47e52d949be --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/echo.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +import asyncio +from websockets.server import serve + +async def echo(websocket): + async for message in websocket: + await websocket.send(message) + +async def main(): + async with serve(echo, "localhost", 8765): + await asyncio.Future() # run forever + +asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/faq/health_check_server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/faq/health_check_server.py new file mode 100644 index 0000000000000..7b8bded772068 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/faq/health_check_server.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +import asyncio +import http +import websockets + +async def health_check(path, request_headers): + if path == "/healthz": + return http.HTTPStatus.OK, [], b"OK\n" + +async def echo(websocket): + async for message in websocket: + await websocket.send(message) + +async def main(): + async with websockets.serve( + echo, "localhost", 8765, + process_request=health_check, + ): + await asyncio.Future() # run forever + +asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/faq/shutdown_client.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/faq/shutdown_client.py new file mode 100644 index 0000000000000..539dd0304a9c2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/faq/shutdown_client.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +import asyncio +import signal +import websockets + +async def client(): + uri = "ws://localhost:8765" + async with websockets.connect(uri) as websocket: + # Close the connection when receiving SIGTERM. + loop = asyncio.get_running_loop() + loop.add_signal_handler( + signal.SIGTERM, loop.create_task, websocket.close()) + + # Process messages received on the connection. + async for message in websocket: + ... + +asyncio.run(client()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/faq/shutdown_server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/faq/shutdown_server.py new file mode 100644 index 0000000000000..1bcc9c90baa04 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/faq/shutdown_server.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +import asyncio +import signal +import websockets + +async def echo(websocket): + async for message in websocket: + await websocket.send(message) + +async def server(): + # Set the stop condition when receiving SIGTERM. + loop = asyncio.get_running_loop() + stop = loop.create_future() + loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) + + async with websockets.serve(echo, "localhost", 8765): + await stop + +asyncio.run(server()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/faq/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/faq/w3c-import.log new file mode 100644 index 0000000000000..bf428c111dd84 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/faq/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/faq/health_check_server.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/faq/shutdown_client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/faq/shutdown_server.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/hello.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/hello.py new file mode 100644 index 0000000000000..a3ce0699ee4c6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/hello.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +import asyncio +from websockets.sync.client import connect + +def hello(): + with connect("ws://localhost:8765") as websocket: + websocket.send("Hello world!") + message = websocket.recv() + print(f"Received: {message}") + +hello() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/basic_auth_client.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/basic_auth_client.py new file mode 100644 index 0000000000000..164732152f6d4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/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.run(hello()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/basic_auth_server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/basic_auth_server.py new file mode 100644 index 0000000000000..d2efeb7e5303b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/basic_auth_server.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +# Server example with HTTP Basic Authentication over TLS + +import asyncio +import websockets + +async def hello(websocket): + greeting = f"Hello {websocket.username}!" + await websocket.send(greeting) + +async def main(): + async with websockets.serve( + hello, "localhost", 8765, + create_protocol=websockets.basic_auth_protocol_factory( + realm="example", credentials=("mary", "p@ssw0rd") + ), + ): + await asyncio.Future() # run forever + +asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/unix_client.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/unix_client.py new file mode 100644 index 0000000000000..92615673032dc --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/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.run(hello()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/unix_server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/unix_server.py new file mode 100644 index 0000000000000..335039c351ca6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/unix_server.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +# WS server example listening on a Unix socket + +import asyncio +import os.path +import websockets + +async def hello(websocket): + name = await websocket.recv() + print(f"<<< {name}") + + greeting = f"Hello {name}!" + + await websocket.send(greeting) + print(f">>> {greeting}") + +async def main(): + socket_path = os.path.join(os.path.dirname(__file__), "socket") + async with websockets.unix_serve(hello, socket_path): + await asyncio.Future() # run forever + +asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/w3c-import.log new file mode 100644 index 0000000000000..f38fd263d83d4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/basic_auth_client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/basic_auth_server.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/unix_client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/legacy/unix_server.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/logging/json_log_formatter.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/logging/json_log_formatter.py new file mode 100644 index 0000000000000..b8fc8d6dc930f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/logging/json_log_formatter.py @@ -0,0 +1,33 @@ +import json +import logging +import datetime + +class JSONFormatter(logging.Formatter): + """ + Render logs as JSON. + + To add details to a log record, store them in a ``event_data`` + custom attribute. This dict is merged into the event. + + """ + def __init__(self): + pass # override logging.Formatter constructor + + def format(self, record): + event = { + "timestamp": self.getTimestamp(record.created), + "message": record.getMessage(), + "level": record.levelname, + "logger": record.name, + } + event_data = getattr(record, "event_data", None) + if event_data: + event.update(event_data) + if record.exc_info: + event["exc_info"] = self.formatException(record.exc_info) + if record.stack_info: + event["stack_info"] = self.formatStack(record.stack_info) + return json.dumps(event) + + def getTimestamp(self, created): + return datetime.datetime.utcfromtimestamp(created).isoformat() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/logging/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/logging/w3c-import.log new file mode 100644 index 0000000000000..c3747ff82c540 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/logging/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/logging/json_log_formatter.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/client.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/client.py new file mode 100644 index 0000000000000..8d588c2b0e448 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/client.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +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}") + +if __name__ == "__main__": + asyncio.run(hello()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/client_secure.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/client_secure.py new file mode 100644 index 0000000000000..f4b39f2b838b1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/client_secure.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +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}") + +if __name__ == "__main__": + asyncio.run(hello()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.css b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.css new file mode 100644 index 0000000000000..558fe03484f8d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.css @@ -0,0 +1,33 @@ +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; + -webkit-user-select: none; +} +.minus { + color: red; +} +.plus { + color: green; +} +.value { + min-width: 2em; +} +.state { + font-size: 2em; +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.html new file mode 100644 index 0000000000000..2e3433bd215ba --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.html @@ -0,0 +1,18 @@ + + + + WebSocket demo + + + +
    +
    -
    +
    ?
    +
    +
    +
    +
    + ? online +
    + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.js new file mode 100644 index 0000000000000..37d892a28b447 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.js @@ -0,0 +1,26 @@ +window.addEventListener("DOMContentLoaded", () => { + const websocket = new WebSocket("ws://localhost:6789/"); + + document.querySelector(".minus").addEventListener("click", () => { + websocket.send(JSON.stringify({ action: "minus" })); + }); + + document.querySelector(".plus").addEventListener("click", () => { + websocket.send(JSON.stringify({ action: "plus" })); + }); + + websocket.onmessage = ({ data }) => { + const event = JSON.parse(data); + switch (event.type) { + case "value": + document.querySelector(".value").textContent = event.value; + break; + case "users": + const users = `${event.count} user${event.count == 1 ? "" : "s"}`; + document.querySelector(".users").textContent = users; + break; + default: + console.error("unsupported event", event); + } + }; +}); diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.py new file mode 100644 index 0000000000000..566e12965edde --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +import asyncio +import json +import logging +import websockets + +logging.basicConfig() + +USERS = set() + +VALUE = 0 + +def users_event(): + return json.dumps({"type": "users", "count": len(USERS)}) + +def value_event(): + return json.dumps({"type": "value", "value": VALUE}) + +async def counter(websocket): + global USERS, VALUE + try: + # Register user + USERS.add(websocket) + websockets.broadcast(USERS, users_event()) + # Send current state to user + await websocket.send(value_event()) + # Manage state changes + async for message in websocket: + event = json.loads(message) + if event["action"] == "minus": + VALUE -= 1 + websockets.broadcast(USERS, value_event()) + elif event["action"] == "plus": + VALUE += 1 + websockets.broadcast(USERS, value_event()) + else: + logging.error("unsupported event: %s", event) + finally: + # Unregister user + USERS.remove(websocket) + websockets.broadcast(USERS, users_event()) + +async def main(): + async with websockets.serve(counter, "localhost", 6789): + await asyncio.Future() # run forever + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/localhost.pem b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/localhost.pem new file mode 100644 index 0000000000000..f9a30ba8f6393 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/server.py new file mode 100644 index 0000000000000..31b18297298bd --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/server.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +import asyncio +import websockets + +async def hello(websocket): + name = await websocket.recv() + print(f"<<< {name}") + + greeting = f"Hello {name}!" + + await websocket.send(greeting) + print(f">>> {greeting}") + +async def main(): + async with websockets.serve(hello, "localhost", 8765): + await asyncio.Future() # run forever + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/server_secure.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/server_secure.py new file mode 100644 index 0000000000000..de41d30dc053c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/server_secure.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +import asyncio +import pathlib +import ssl +import websockets + +async def hello(websocket): + 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) + +async def main(): + async with websockets.serve(hello, "localhost", 8765, ssl=ssl_context): + await asyncio.Future() # run forever + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time.html new file mode 100644 index 0000000000000..b1c93b141d2e4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time.html @@ -0,0 +1,9 @@ + + + + WebSocket demo + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time.js new file mode 100644 index 0000000000000..26bed7ec9e79a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time.js @@ -0,0 +1,12 @@ +window.addEventListener("DOMContentLoaded", () => { + const messages = document.createElement("ul"); + document.body.appendChild(messages); + + const websocket = new WebSocket("ws://localhost:5678/"); + websocket.onmessage = ({ data }) => { + const message = document.createElement("li"); + const content = document.createTextNode(data); + message.appendChild(content); + messages.appendChild(message); + }; +}); diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time.py new file mode 100644 index 0000000000000..a83078e8a9111 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +import asyncio +import datetime +import random +import websockets + +async def show_time(websocket): + while True: + message = datetime.datetime.utcnow().isoformat() + "Z" + await websocket.send(message) + await asyncio.sleep(random.random() * 2 + 1) + +async def main(): + async with websockets.serve(show_time, "localhost", 5678): + await asyncio.Future() # run forever + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time_2.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time_2.py new file mode 100644 index 0000000000000..08e87f5931a44 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time_2.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +import asyncio +import datetime +import random +import websockets + +CONNECTIONS = set() + +async def register(websocket): + CONNECTIONS.add(websocket) + try: + await websocket.wait_closed() + finally: + CONNECTIONS.remove(websocket) + +async def show_time(): + while True: + message = datetime.datetime.utcnow().isoformat() + "Z" + websockets.broadcast(CONNECTIONS, message) + await asyncio.sleep(random.random() * 2 + 1) + +async def main(): + async with websockets.serve(register, "localhost", 5678): + await show_time() + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/w3c-import.log new file mode 100644 index 0000000000000..a7391774c1057 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/w3c-import.log @@ -0,0 +1,29 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +user-select +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/client_secure.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.css +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.js +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/counter.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/localhost.pem +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/server.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/server_secure.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time.js +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/quickstart/show_time_2.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/connect4.css b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/connect4.css new file mode 100644 index 0000000000000..27f0baf6e457f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/connect4.css @@ -0,0 +1,105 @@ +/* General layout */ + +body { + background-color: white; + display: flex; + flex-direction: column-reverse; + justify-content: center; + align-items: center; + margin: 0; + min-height: 100vh; +} + +/* Action buttons */ + +.actions { + display: flex; + flex-direction: row; + justify-content: space-evenly; + align-items: flex-end; + width: 720px; + height: 100px; +} + +.action { + color: darkgray; + font-family: "Helvetica Neue", sans-serif; + font-size: 20px; + line-height: 20px; + font-weight: 300; + text-align: center; + text-decoration: none; + text-transform: uppercase; + padding: 20px; + width: 120px; +} + +.action:hover { + background-color: darkgray; + color: white; + font-weight: 700; +} + +.action[href=""] { + display: none; +} + +/* Connect Four board */ + +.board { + background-color: blue; + display: flex; + flex-direction: row; + padding: 0 10px; + position: relative; +} + +.board::before, +.board::after { + background-color: blue; + content: ""; + height: 720px; + width: 20px; + position: absolute; +} + +.board::before { + left: -20px; +} + +.board::after { + right: -20px; +} + +.column { + display: flex; + flex-direction: column-reverse; + padding: 10px; +} + +.cell { + border-radius: 50%; + width: 80px; + height: 80px; + margin: 10px 0; +} + +.empty { + background-color: white; +} + +.column:hover .empty { + background-color: lightgray; +} + +.column:hover .empty ~ .empty { + background-color: white; +} + +.red { + background-color: red; +} + +.yellow { + background-color: yellow; +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/connect4.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/connect4.js new file mode 100644 index 0000000000000..cb5eb9fa27b55 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/connect4.js @@ -0,0 +1,45 @@ +const PLAYER1 = "red"; + +const PLAYER2 = "yellow"; + +function createBoard(board) { + // Inject stylesheet. + const linkElement = document.createElement("link"); + linkElement.href = import.meta.url.replace(".js", ".css"); + linkElement.rel = "stylesheet"; + document.head.append(linkElement); + // Generate board. + for (let column = 0; column < 7; column++) { + const columnElement = document.createElement("div"); + columnElement.className = "column"; + columnElement.dataset.column = column; + for (let row = 0; row < 6; row++) { + const cellElement = document.createElement("div"); + cellElement.className = "cell empty"; + cellElement.dataset.column = column; + columnElement.append(cellElement); + } + board.append(columnElement); + } +} + +function playMove(board, player, column, row) { + // Check values of arguments. + if (player !== PLAYER1 && player !== PLAYER2) { + throw new Error(`player must be ${PLAYER1} or ${PLAYER2}.`); + } + const columnElement = board.querySelectorAll(".column")[column]; + if (columnElement === undefined) { + throw new RangeError("column must be between 0 and 6."); + } + const cellElement = columnElement.querySelectorAll(".cell")[row]; + if (cellElement === undefined) { + throw new RangeError("row must be between 0 and 5."); + } + // Place checker in cell. + if (!cellElement.classList.replace("empty", player)) { + throw new Error("cell must be empty."); + } +} + +export { PLAYER1, PLAYER2, createBoard, playMove }; diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/connect4.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/connect4.py new file mode 100644 index 0000000000000..0a61e7c7ee1d6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/connect4.py @@ -0,0 +1,62 @@ +__all__ = ["PLAYER1", "PLAYER2", "Connect4"] + +PLAYER1, PLAYER2 = "red", "yellow" + + +class Connect4: + """ + A Connect Four game. + + Play moves with :meth:`play`. + + Get past moves with :attr:`moves`. + + Check for a victory with :attr:`winner`. + + """ + + def __init__(self): + self.moves = [] + self.top = [0 for _ in range(7)] + self.winner = None + + @property + def last_player(self): + """ + Player who played the last move. + + """ + return PLAYER1 if len(self.moves) % 2 else PLAYER2 + + @property + def last_player_won(self): + """ + Whether the last move is winning. + + """ + b = sum(1 << (8 * column + row) for _, column, row in self.moves[::-2]) + return any(b & b >> v & b >> 2 * v & b >> 3 * v for v in [1, 7, 8, 9]) + + def play(self, player, column): + """ + Play a move in a column. + + Returns the row where the checker lands. + + Raises :exc:`RuntimeError` if the move is illegal. + + """ + if player == self.last_player: + raise RuntimeError("It isn't your turn.") + + row = self.top[column] + if row == 6: + raise RuntimeError("This slot is full.") + + self.moves.append((player, column, row)) + self.top[column] += 1 + + if self.winner is None and self.last_player_won: + self.winner = self.last_player + + return row diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/favicon.ico b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/favicon.ico new file mode 100644 index 0000000000000..36e855029d705 Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/favicon.ico differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/w3c-import.log new file mode 100644 index 0000000000000..c7c1c40a15ef9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/connect4.css +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/connect4.js +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/connect4.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/start/favicon.ico diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/app.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/app.py new file mode 100644 index 0000000000000..3b0fbd7868597 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/app.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +import asyncio +import itertools +import json + +import websockets + +from connect4 import PLAYER1, PLAYER2, Connect4 + + +async def handler(websocket): + # Initialize a Connect Four game. + game = Connect4() + + # Players take alternate turns, using the same browser. + turns = itertools.cycle([PLAYER1, PLAYER2]) + player = next(turns) + + async for message in websocket: + # Parse a "play" event from the UI. + event = json.loads(message) + assert event["type"] == "play" + column = event["column"] + + try: + # Play the move. + row = game.play(player, column) + except RuntimeError as exc: + # Send an "error" event if the move was illegal. + event = { + "type": "error", + "message": str(exc), + } + await websocket.send(json.dumps(event)) + continue + + # Send a "play" event to update the UI. + event = { + "type": "play", + "player": player, + "column": column, + "row": row, + } + await websocket.send(json.dumps(event)) + + # If move is winning, send a "win" event. + if game.winner is not None: + event = { + "type": "win", + "player": game.winner, + } + await websocket.send(json.dumps(event)) + + # Alternate turns. + player = next(turns) + + +async def main(): + async with websockets.serve(handler, "", 8001): + await asyncio.Future() # run forever + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/connect4.css b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/connect4.css new file mode 100644 index 0000000000000..27f0baf6e457f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/connect4.css @@ -0,0 +1,105 @@ +/* General layout */ + +body { + background-color: white; + display: flex; + flex-direction: column-reverse; + justify-content: center; + align-items: center; + margin: 0; + min-height: 100vh; +} + +/* Action buttons */ + +.actions { + display: flex; + flex-direction: row; + justify-content: space-evenly; + align-items: flex-end; + width: 720px; + height: 100px; +} + +.action { + color: darkgray; + font-family: "Helvetica Neue", sans-serif; + font-size: 20px; + line-height: 20px; + font-weight: 300; + text-align: center; + text-decoration: none; + text-transform: uppercase; + padding: 20px; + width: 120px; +} + +.action:hover { + background-color: darkgray; + color: white; + font-weight: 700; +} + +.action[href=""] { + display: none; +} + +/* Connect Four board */ + +.board { + background-color: blue; + display: flex; + flex-direction: row; + padding: 0 10px; + position: relative; +} + +.board::before, +.board::after { + background-color: blue; + content: ""; + height: 720px; + width: 20px; + position: absolute; +} + +.board::before { + left: -20px; +} + +.board::after { + right: -20px; +} + +.column { + display: flex; + flex-direction: column-reverse; + padding: 10px; +} + +.cell { + border-radius: 50%; + width: 80px; + height: 80px; + margin: 10px 0; +} + +.empty { + background-color: white; +} + +.column:hover .empty { + background-color: lightgray; +} + +.column:hover .empty ~ .empty { + background-color: white; +} + +.red { + background-color: red; +} + +.yellow { + background-color: yellow; +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/connect4.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/connect4.js new file mode 100644 index 0000000000000..cb5eb9fa27b55 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/connect4.js @@ -0,0 +1,45 @@ +const PLAYER1 = "red"; + +const PLAYER2 = "yellow"; + +function createBoard(board) { + // Inject stylesheet. + const linkElement = document.createElement("link"); + linkElement.href = import.meta.url.replace(".js", ".css"); + linkElement.rel = "stylesheet"; + document.head.append(linkElement); + // Generate board. + for (let column = 0; column < 7; column++) { + const columnElement = document.createElement("div"); + columnElement.className = "column"; + columnElement.dataset.column = column; + for (let row = 0; row < 6; row++) { + const cellElement = document.createElement("div"); + cellElement.className = "cell empty"; + cellElement.dataset.column = column; + columnElement.append(cellElement); + } + board.append(columnElement); + } +} + +function playMove(board, player, column, row) { + // Check values of arguments. + if (player !== PLAYER1 && player !== PLAYER2) { + throw new Error(`player must be ${PLAYER1} or ${PLAYER2}.`); + } + const columnElement = board.querySelectorAll(".column")[column]; + if (columnElement === undefined) { + throw new RangeError("column must be between 0 and 6."); + } + const cellElement = columnElement.querySelectorAll(".cell")[row]; + if (cellElement === undefined) { + throw new RangeError("row must be between 0 and 5."); + } + // Place checker in cell. + if (!cellElement.classList.replace("empty", player)) { + throw new Error("cell must be empty."); + } +} + +export { PLAYER1, PLAYER2, createBoard, playMove }; diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/connect4.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/connect4.py new file mode 100644 index 0000000000000..0a61e7c7ee1d6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/connect4.py @@ -0,0 +1,62 @@ +__all__ = ["PLAYER1", "PLAYER2", "Connect4"] + +PLAYER1, PLAYER2 = "red", "yellow" + + +class Connect4: + """ + A Connect Four game. + + Play moves with :meth:`play`. + + Get past moves with :attr:`moves`. + + Check for a victory with :attr:`winner`. + + """ + + def __init__(self): + self.moves = [] + self.top = [0 for _ in range(7)] + self.winner = None + + @property + def last_player(self): + """ + Player who played the last move. + + """ + return PLAYER1 if len(self.moves) % 2 else PLAYER2 + + @property + def last_player_won(self): + """ + Whether the last move is winning. + + """ + b = sum(1 << (8 * column + row) for _, column, row in self.moves[::-2]) + return any(b & b >> v & b >> 2 * v & b >> 3 * v for v in [1, 7, 8, 9]) + + def play(self, player, column): + """ + Play a move in a column. + + Returns the row where the checker lands. + + Raises :exc:`RuntimeError` if the move is illegal. + + """ + if player == self.last_player: + raise RuntimeError("It isn't your turn.") + + row = self.top[column] + if row == 6: + raise RuntimeError("This slot is full.") + + self.moves.append((player, column, row)) + self.top[column] += 1 + + if self.winner is None and self.last_player_won: + self.winner = self.last_player + + return row diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/favicon.ico b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/favicon.ico new file mode 100644 index 0000000000000..36e855029d705 Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/favicon.ico differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/index.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/index.html new file mode 100644 index 0000000000000..8e38e899222dc --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/index.html @@ -0,0 +1,10 @@ + + + + Connect Four + + +
    + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/main.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/main.js new file mode 100644 index 0000000000000..dd28f9a6a8bb6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/main.js @@ -0,0 +1,53 @@ +import { createBoard, playMove } from "./connect4.js"; + +function showMessage(message) { + window.setTimeout(() => window.alert(message), 50); +} + +function receiveMoves(board, websocket) { + websocket.addEventListener("message", ({ data }) => { + const event = JSON.parse(data); + switch (event.type) { + case "play": + // Update the UI with the move. + playMove(board, event.player, event.column, event.row); + break; + case "win": + showMessage(`Player ${event.player} wins!`); + // No further messages are expected; close the WebSocket connection. + websocket.close(1000); + break; + case "error": + showMessage(event.message); + break; + default: + throw new Error(`Unsupported event type: ${event.type}.`); + } + }); +} + +function sendMoves(board, websocket) { + // When clicking a column, send a "play" event for a move in that column. + board.addEventListener("click", ({ target }) => { + const column = target.dataset.column; + // Ignore clicks outside a column. + if (column === undefined) { + return; + } + const event = { + type: "play", + column: parseInt(column, 10), + }; + websocket.send(JSON.stringify(event)); + }); +} + +window.addEventListener("DOMContentLoaded", () => { + // Initialize the UI. + const board = document.querySelector(".board"); + createBoard(board); + // Open the WebSocket connection and register event handlers. + const websocket = new WebSocket("ws://localhost:8001/"); + receiveMoves(board, websocket); + sendMoves(board, websocket); +}); diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/w3c-import.log new file mode 100644 index 0000000000000..b38f4e57324b6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/app.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/connect4.css +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/connect4.js +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/connect4.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/favicon.ico +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/index.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step1/main.js diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/app.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/app.py new file mode 100644 index 0000000000000..2693d4304df50 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/app.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python + +import asyncio +import json +import secrets + +import websockets + +from connect4 import PLAYER1, PLAYER2, Connect4 + + +JOIN = {} + +WATCH = {} + + +async def error(websocket, message): + """ + Send an error message. + + """ + event = { + "type": "error", + "message": message, + } + await websocket.send(json.dumps(event)) + + +async def replay(websocket, game): + """ + Send previous moves. + + """ + # Make a copy to avoid an exception if game.moves changes while iteration + # is in progress. If a move is played while replay is running, moves will + # be sent out of order but each move will be sent once and eventually the + # UI will be consistent. + for player, column, row in game.moves.copy(): + event = { + "type": "play", + "player": player, + "column": column, + "row": row, + } + await websocket.send(json.dumps(event)) + + +async def play(websocket, game, player, connected): + """ + Receive and process moves from a player. + + """ + async for message in websocket: + # Parse a "play" event from the UI. + event = json.loads(message) + assert event["type"] == "play" + column = event["column"] + + try: + # Play the move. + row = game.play(player, column) + except RuntimeError as exc: + # Send an "error" event if the move was illegal. + await error(websocket, str(exc)) + continue + + # Send a "play" event to update the UI. + event = { + "type": "play", + "player": player, + "column": column, + "row": row, + } + websockets.broadcast(connected, json.dumps(event)) + + # If move is winning, send a "win" event. + if game.winner is not None: + event = { + "type": "win", + "player": game.winner, + } + websockets.broadcast(connected, json.dumps(event)) + + +async def start(websocket): + """ + Handle a connection from the first player: start a new game. + + """ + # Initialize a Connect Four game, the set of WebSocket connections + # receiving moves from this game, and secret access tokens. + game = Connect4() + connected = {websocket} + + join_key = secrets.token_urlsafe(12) + JOIN[join_key] = game, connected + + watch_key = secrets.token_urlsafe(12) + WATCH[watch_key] = game, connected + + try: + # Send the secret access tokens to the browser of the first player, + # where they'll be used for building "join" and "watch" links. + event = { + "type": "init", + "join": join_key, + "watch": watch_key, + } + await websocket.send(json.dumps(event)) + # Receive and process moves from the first player. + await play(websocket, game, PLAYER1, connected) + finally: + del JOIN[join_key] + del WATCH[watch_key] + + +async def join(websocket, join_key): + """ + Handle a connection from the second player: join an existing game. + + """ + # Find the Connect Four game. + try: + game, connected = JOIN[join_key] + except KeyError: + await error(websocket, "Game not found.") + return + + # Register to receive moves from this game. + connected.add(websocket) + try: + # Send the first move, in case the first player already played it. + await replay(websocket, game) + # Receive and process moves from the second player. + await play(websocket, game, PLAYER2, connected) + finally: + connected.remove(websocket) + + +async def watch(websocket, watch_key): + """ + Handle a connection from a spectator: watch an existing game. + + """ + # Find the Connect Four game. + try: + game, connected = WATCH[watch_key] + except KeyError: + await error(websocket, "Game not found.") + return + + # Register to receive moves from this game. + connected.add(websocket) + try: + # Send previous moves, in case the game already started. + await replay(websocket, game) + # Keep the connection open, but don't receive any messages. + await websocket.wait_closed() + finally: + connected.remove(websocket) + + +async def handler(websocket): + """ + Handle a connection and dispatch it according to who is connecting. + + """ + # Receive and parse the "init" event from the UI. + message = await websocket.recv() + event = json.loads(message) + assert event["type"] == "init" + + if "join" in event: + # Second player joins an existing game. + await join(websocket, event["join"]) + elif "watch" in event: + # Spectator watches an existing game. + await watch(websocket, event["watch"]) + else: + # First player starts a new game. + await start(websocket) + + +async def main(): + async with websockets.serve(handler, "", 8001): + await asyncio.Future() # run forever + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/connect4.css b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/connect4.css new file mode 100644 index 0000000000000..27f0baf6e457f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/connect4.css @@ -0,0 +1,105 @@ +/* General layout */ + +body { + background-color: white; + display: flex; + flex-direction: column-reverse; + justify-content: center; + align-items: center; + margin: 0; + min-height: 100vh; +} + +/* Action buttons */ + +.actions { + display: flex; + flex-direction: row; + justify-content: space-evenly; + align-items: flex-end; + width: 720px; + height: 100px; +} + +.action { + color: darkgray; + font-family: "Helvetica Neue", sans-serif; + font-size: 20px; + line-height: 20px; + font-weight: 300; + text-align: center; + text-decoration: none; + text-transform: uppercase; + padding: 20px; + width: 120px; +} + +.action:hover { + background-color: darkgray; + color: white; + font-weight: 700; +} + +.action[href=""] { + display: none; +} + +/* Connect Four board */ + +.board { + background-color: blue; + display: flex; + flex-direction: row; + padding: 0 10px; + position: relative; +} + +.board::before, +.board::after { + background-color: blue; + content: ""; + height: 720px; + width: 20px; + position: absolute; +} + +.board::before { + left: -20px; +} + +.board::after { + right: -20px; +} + +.column { + display: flex; + flex-direction: column-reverse; + padding: 10px; +} + +.cell { + border-radius: 50%; + width: 80px; + height: 80px; + margin: 10px 0; +} + +.empty { + background-color: white; +} + +.column:hover .empty { + background-color: lightgray; +} + +.column:hover .empty ~ .empty { + background-color: white; +} + +.red { + background-color: red; +} + +.yellow { + background-color: yellow; +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/connect4.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/connect4.js new file mode 100644 index 0000000000000..cb5eb9fa27b55 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/connect4.js @@ -0,0 +1,45 @@ +const PLAYER1 = "red"; + +const PLAYER2 = "yellow"; + +function createBoard(board) { + // Inject stylesheet. + const linkElement = document.createElement("link"); + linkElement.href = import.meta.url.replace(".js", ".css"); + linkElement.rel = "stylesheet"; + document.head.append(linkElement); + // Generate board. + for (let column = 0; column < 7; column++) { + const columnElement = document.createElement("div"); + columnElement.className = "column"; + columnElement.dataset.column = column; + for (let row = 0; row < 6; row++) { + const cellElement = document.createElement("div"); + cellElement.className = "cell empty"; + cellElement.dataset.column = column; + columnElement.append(cellElement); + } + board.append(columnElement); + } +} + +function playMove(board, player, column, row) { + // Check values of arguments. + if (player !== PLAYER1 && player !== PLAYER2) { + throw new Error(`player must be ${PLAYER1} or ${PLAYER2}.`); + } + const columnElement = board.querySelectorAll(".column")[column]; + if (columnElement === undefined) { + throw new RangeError("column must be between 0 and 6."); + } + const cellElement = columnElement.querySelectorAll(".cell")[row]; + if (cellElement === undefined) { + throw new RangeError("row must be between 0 and 5."); + } + // Place checker in cell. + if (!cellElement.classList.replace("empty", player)) { + throw new Error("cell must be empty."); + } +} + +export { PLAYER1, PLAYER2, createBoard, playMove }; diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/connect4.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/connect4.py new file mode 100644 index 0000000000000..0a61e7c7ee1d6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/connect4.py @@ -0,0 +1,62 @@ +__all__ = ["PLAYER1", "PLAYER2", "Connect4"] + +PLAYER1, PLAYER2 = "red", "yellow" + + +class Connect4: + """ + A Connect Four game. + + Play moves with :meth:`play`. + + Get past moves with :attr:`moves`. + + Check for a victory with :attr:`winner`. + + """ + + def __init__(self): + self.moves = [] + self.top = [0 for _ in range(7)] + self.winner = None + + @property + def last_player(self): + """ + Player who played the last move. + + """ + return PLAYER1 if len(self.moves) % 2 else PLAYER2 + + @property + def last_player_won(self): + """ + Whether the last move is winning. + + """ + b = sum(1 << (8 * column + row) for _, column, row in self.moves[::-2]) + return any(b & b >> v & b >> 2 * v & b >> 3 * v for v in [1, 7, 8, 9]) + + def play(self, player, column): + """ + Play a move in a column. + + Returns the row where the checker lands. + + Raises :exc:`RuntimeError` if the move is illegal. + + """ + if player == self.last_player: + raise RuntimeError("It isn't your turn.") + + row = self.top[column] + if row == 6: + raise RuntimeError("This slot is full.") + + self.moves.append((player, column, row)) + self.top[column] += 1 + + if self.winner is None and self.last_player_won: + self.winner = self.last_player + + return row diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/favicon.ico b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/favicon.ico new file mode 100644 index 0000000000000..36e855029d705 Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/favicon.ico differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/index.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/index.html new file mode 100644 index 0000000000000..1a16f72a257bb --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/index.html @@ -0,0 +1,15 @@ + + + + Connect Four + + +
    + New + Join + Watch +
    +
    + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/main.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/main.js new file mode 100644 index 0000000000000..d38a0140acc72 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/main.js @@ -0,0 +1,83 @@ +import { createBoard, playMove } from "./connect4.js"; + +function initGame(websocket) { + websocket.addEventListener("open", () => { + // Send an "init" event according to who is connecting. + const params = new URLSearchParams(window.location.search); + let event = { type: "init" }; + if (params.has("join")) { + // Second player joins an existing game. + event.join = params.get("join"); + } else if (params.has("watch")) { + // Spectator watches an existing game. + event.watch = params.get("watch"); + } else { + // First player starts a new game. + } + websocket.send(JSON.stringify(event)); + }); +} + +function showMessage(message) { + window.setTimeout(() => window.alert(message), 50); +} + +function receiveMoves(board, websocket) { + websocket.addEventListener("message", ({ data }) => { + const event = JSON.parse(data); + switch (event.type) { + case "init": + // Create links for inviting the second player and spectators. + document.querySelector(".join").href = "?join=" + event.join; + document.querySelector(".watch").href = "?watch=" + event.watch; + break; + case "play": + // Update the UI with the move. + playMove(board, event.player, event.column, event.row); + break; + case "win": + showMessage(`Player ${event.player} wins!`); + // No further messages are expected; close the WebSocket connection. + websocket.close(1000); + break; + case "error": + showMessage(event.message); + break; + default: + throw new Error(`Unsupported event type: ${event.type}.`); + } + }); +} + +function sendMoves(board, websocket) { + // Don't send moves for a spectator watching a game. + const params = new URLSearchParams(window.location.search); + if (params.has("watch")) { + return; + } + + // When clicking a column, send a "play" event for a move in that column. + board.addEventListener("click", ({ target }) => { + const column = target.dataset.column; + // Ignore clicks outside a column. + if (column === undefined) { + return; + } + const event = { + type: "play", + column: parseInt(column, 10), + }; + websocket.send(JSON.stringify(event)); + }); +} + +window.addEventListener("DOMContentLoaded", () => { + // Initialize the UI. + const board = document.querySelector(".board"); + createBoard(board); + // Open the WebSocket connection and register event handlers. + const websocket = new WebSocket("ws://localhost:8001/"); + initGame(websocket); + receiveMoves(board, websocket); + sendMoves(board, websocket); +}); diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/w3c-import.log new file mode 100644 index 0000000000000..43732b887da87 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/app.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/connect4.css +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/connect4.js +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/connect4.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/favicon.ico +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/index.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step2/main.js diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/Procfile b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/Procfile new file mode 100644 index 0000000000000..2e35818f675d5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/Procfile @@ -0,0 +1 @@ +web: python app.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/app.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/app.py new file mode 100644 index 0000000000000..c2ee020d201ca --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/app.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python + +import asyncio +import json +import os +import secrets +import signal + +import websockets + +from connect4 import PLAYER1, PLAYER2, Connect4 + + +JOIN = {} + +WATCH = {} + + +async def error(websocket, message): + """ + Send an error message. + + """ + event = { + "type": "error", + "message": message, + } + await websocket.send(json.dumps(event)) + + +async def replay(websocket, game): + """ + Send previous moves. + + """ + # Make a copy to avoid an exception if game.moves changes while iteration + # is in progress. If a move is played while replay is running, moves will + # be sent out of order but each move will be sent once and eventually the + # UI will be consistent. + for player, column, row in game.moves.copy(): + event = { + "type": "play", + "player": player, + "column": column, + "row": row, + } + await websocket.send(json.dumps(event)) + + +async def play(websocket, game, player, connected): + """ + Receive and process moves from a player. + + """ + async for message in websocket: + # Parse a "play" event from the UI. + event = json.loads(message) + assert event["type"] == "play" + column = event["column"] + + try: + # Play the move. + row = game.play(player, column) + except RuntimeError as exc: + # Send an "error" event if the move was illegal. + await error(websocket, str(exc)) + continue + + # Send a "play" event to update the UI. + event = { + "type": "play", + "player": player, + "column": column, + "row": row, + } + websockets.broadcast(connected, json.dumps(event)) + + # If move is winning, send a "win" event. + if game.winner is not None: + event = { + "type": "win", + "player": game.winner, + } + websockets.broadcast(connected, json.dumps(event)) + + +async def start(websocket): + """ + Handle a connection from the first player: start a new game. + + """ + # Initialize a Connect Four game, the set of WebSocket connections + # receiving moves from this game, and secret access tokens. + game = Connect4() + connected = {websocket} + + join_key = secrets.token_urlsafe(12) + JOIN[join_key] = game, connected + + watch_key = secrets.token_urlsafe(12) + WATCH[watch_key] = game, connected + + try: + # Send the secret access tokens to the browser of the first player, + # where they'll be used for building "join" and "watch" links. + event = { + "type": "init", + "join": join_key, + "watch": watch_key, + } + await websocket.send(json.dumps(event)) + # Receive and process moves from the first player. + await play(websocket, game, PLAYER1, connected) + finally: + del JOIN[join_key] + del WATCH[watch_key] + + +async def join(websocket, join_key): + """ + Handle a connection from the second player: join an existing game. + + """ + # Find the Connect Four game. + try: + game, connected = JOIN[join_key] + except KeyError: + await error(websocket, "Game not found.") + return + + # Register to receive moves from this game. + connected.add(websocket) + try: + # Send the first move, in case the first player already played it. + await replay(websocket, game) + # Receive and process moves from the second player. + await play(websocket, game, PLAYER2, connected) + finally: + connected.remove(websocket) + + +async def watch(websocket, watch_key): + """ + Handle a connection from a spectator: watch an existing game. + + """ + # Find the Connect Four game. + try: + game, connected = WATCH[watch_key] + except KeyError: + await error(websocket, "Game not found.") + return + + # Register to receive moves from this game. + connected.add(websocket) + try: + # Send previous moves, in case the game already started. + await replay(websocket, game) + # Keep the connection open, but don't receive any messages. + await websocket.wait_closed() + finally: + connected.remove(websocket) + + +async def handler(websocket): + """ + Handle a connection and dispatch it according to who is connecting. + + """ + # Receive and parse the "init" event from the UI. + message = await websocket.recv() + event = json.loads(message) + assert event["type"] == "init" + + if "join" in event: + # Second player joins an existing game. + await join(websocket, event["join"]) + elif "watch" in event: + # Spectator watches an existing game. + await watch(websocket, event["watch"]) + else: + # First player starts a new game. + await start(websocket) + + +async def main(): + # Set the stop condition when receiving SIGTERM. + loop = asyncio.get_running_loop() + stop = loop.create_future() + loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) + + port = int(os.environ.get("PORT", "8001")) + async with websockets.serve(handler, "", port): + await stop + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/connect4.css b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/connect4.css new file mode 100644 index 0000000000000..27f0baf6e457f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/connect4.css @@ -0,0 +1,105 @@ +/* General layout */ + +body { + background-color: white; + display: flex; + flex-direction: column-reverse; + justify-content: center; + align-items: center; + margin: 0; + min-height: 100vh; +} + +/* Action buttons */ + +.actions { + display: flex; + flex-direction: row; + justify-content: space-evenly; + align-items: flex-end; + width: 720px; + height: 100px; +} + +.action { + color: darkgray; + font-family: "Helvetica Neue", sans-serif; + font-size: 20px; + line-height: 20px; + font-weight: 300; + text-align: center; + text-decoration: none; + text-transform: uppercase; + padding: 20px; + width: 120px; +} + +.action:hover { + background-color: darkgray; + color: white; + font-weight: 700; +} + +.action[href=""] { + display: none; +} + +/* Connect Four board */ + +.board { + background-color: blue; + display: flex; + flex-direction: row; + padding: 0 10px; + position: relative; +} + +.board::before, +.board::after { + background-color: blue; + content: ""; + height: 720px; + width: 20px; + position: absolute; +} + +.board::before { + left: -20px; +} + +.board::after { + right: -20px; +} + +.column { + display: flex; + flex-direction: column-reverse; + padding: 10px; +} + +.cell { + border-radius: 50%; + width: 80px; + height: 80px; + margin: 10px 0; +} + +.empty { + background-color: white; +} + +.column:hover .empty { + background-color: lightgray; +} + +.column:hover .empty ~ .empty { + background-color: white; +} + +.red { + background-color: red; +} + +.yellow { + background-color: yellow; +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/connect4.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/connect4.js new file mode 100644 index 0000000000000..cb5eb9fa27b55 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/connect4.js @@ -0,0 +1,45 @@ +const PLAYER1 = "red"; + +const PLAYER2 = "yellow"; + +function createBoard(board) { + // Inject stylesheet. + const linkElement = document.createElement("link"); + linkElement.href = import.meta.url.replace(".js", ".css"); + linkElement.rel = "stylesheet"; + document.head.append(linkElement); + // Generate board. + for (let column = 0; column < 7; column++) { + const columnElement = document.createElement("div"); + columnElement.className = "column"; + columnElement.dataset.column = column; + for (let row = 0; row < 6; row++) { + const cellElement = document.createElement("div"); + cellElement.className = "cell empty"; + cellElement.dataset.column = column; + columnElement.append(cellElement); + } + board.append(columnElement); + } +} + +function playMove(board, player, column, row) { + // Check values of arguments. + if (player !== PLAYER1 && player !== PLAYER2) { + throw new Error(`player must be ${PLAYER1} or ${PLAYER2}.`); + } + const columnElement = board.querySelectorAll(".column")[column]; + if (columnElement === undefined) { + throw new RangeError("column must be between 0 and 6."); + } + const cellElement = columnElement.querySelectorAll(".cell")[row]; + if (cellElement === undefined) { + throw new RangeError("row must be between 0 and 5."); + } + // Place checker in cell. + if (!cellElement.classList.replace("empty", player)) { + throw new Error("cell must be empty."); + } +} + +export { PLAYER1, PLAYER2, createBoard, playMove }; diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/connect4.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/connect4.py new file mode 100644 index 0000000000000..0a61e7c7ee1d6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/connect4.py @@ -0,0 +1,62 @@ +__all__ = ["PLAYER1", "PLAYER2", "Connect4"] + +PLAYER1, PLAYER2 = "red", "yellow" + + +class Connect4: + """ + A Connect Four game. + + Play moves with :meth:`play`. + + Get past moves with :attr:`moves`. + + Check for a victory with :attr:`winner`. + + """ + + def __init__(self): + self.moves = [] + self.top = [0 for _ in range(7)] + self.winner = None + + @property + def last_player(self): + """ + Player who played the last move. + + """ + return PLAYER1 if len(self.moves) % 2 else PLAYER2 + + @property + def last_player_won(self): + """ + Whether the last move is winning. + + """ + b = sum(1 << (8 * column + row) for _, column, row in self.moves[::-2]) + return any(b & b >> v & b >> 2 * v & b >> 3 * v for v in [1, 7, 8, 9]) + + def play(self, player, column): + """ + Play a move in a column. + + Returns the row where the checker lands. + + Raises :exc:`RuntimeError` if the move is illegal. + + """ + if player == self.last_player: + raise RuntimeError("It isn't your turn.") + + row = self.top[column] + if row == 6: + raise RuntimeError("This slot is full.") + + self.moves.append((player, column, row)) + self.top[column] += 1 + + if self.winner is None and self.last_player_won: + self.winner = self.last_player + + return row diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/favicon.ico b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/favicon.ico new file mode 100644 index 0000000000000..36e855029d705 Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/favicon.ico differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/index.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/index.html new file mode 100644 index 0000000000000..1a16f72a257bb --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/index.html @@ -0,0 +1,15 @@ + + + + Connect Four + + +
    + New + Join + Watch +
    +
    + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/main.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/main.js new file mode 100644 index 0000000000000..3000fa2f78c4d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/main.js @@ -0,0 +1,93 @@ +import { createBoard, playMove } from "./connect4.js"; + +function getWebSocketServer() { + if (window.location.host === "python-websockets.github.io") { + return "wss://websockets-tutorial.herokuapp.com/"; + } else if (window.location.host === "localhost:8000") { + return "ws://localhost:8001/"; + } else { + throw new Error(`Unsupported host: ${window.location.host}`); + } +} + +function initGame(websocket) { + websocket.addEventListener("open", () => { + // Send an "init" event according to who is connecting. + const params = new URLSearchParams(window.location.search); + let event = { type: "init" }; + if (params.has("join")) { + // Second player joins an existing game. + event.join = params.get("join"); + } else if (params.has("watch")) { + // Spectator watches an existing game. + event.watch = params.get("watch"); + } else { + // First player starts a new game. + } + websocket.send(JSON.stringify(event)); + }); +} + +function showMessage(message) { + window.setTimeout(() => window.alert(message), 50); +} + +function receiveMoves(board, websocket) { + websocket.addEventListener("message", ({ data }) => { + const event = JSON.parse(data); + switch (event.type) { + case "init": + // Create links for inviting the second player and spectators. + document.querySelector(".join").href = "?join=" + event.join; + document.querySelector(".watch").href = "?watch=" + event.watch; + break; + case "play": + // Update the UI with the move. + playMove(board, event.player, event.column, event.row); + break; + case "win": + showMessage(`Player ${event.player} wins!`); + // No further messages are expected; close the WebSocket connection. + websocket.close(1000); + break; + case "error": + showMessage(event.message); + break; + default: + throw new Error(`Unsupported event type: ${event.type}.`); + } + }); +} + +function sendMoves(board, websocket) { + // Don't send moves for a spectator watching a game. + const params = new URLSearchParams(window.location.search); + if (params.has("watch")) { + return; + } + + // When clicking a column, send a "play" event for a move in that column. + board.addEventListener("click", ({ target }) => { + const column = target.dataset.column; + // Ignore clicks outside a column. + if (column === undefined) { + return; + } + const event = { + type: "play", + column: parseInt(column, 10), + }; + websocket.send(JSON.stringify(event)); + }); +} + +window.addEventListener("DOMContentLoaded", () => { + // Initialize the UI. + const board = document.querySelector(".board"); + createBoard(board); + // Open the WebSocket connection and register event handlers. + const websocket = new WebSocket(getWebSocketServer()); + initGame(websocket); + receiveMoves(board, websocket); + sendMoves(board, websocket); +}); diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/requirements.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/requirements.txt new file mode 100644 index 0000000000000..14774b465e97f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/requirements.txt @@ -0,0 +1 @@ +websockets diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/w3c-import.log new file mode 100644 index 0000000000000..f37d49c9ae924 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/w3c-import.log @@ -0,0 +1,25 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/Procfile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/app.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/connect4.css +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/connect4.js +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/connect4.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/favicon.ico +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/index.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/main.js +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/tutorial/step3/requirements.txt diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/w3c-import.log new file mode 100644 index 0000000000000..ec6aae4d68b9c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/echo.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/example/hello.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/app.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/app.py new file mode 100644 index 0000000000000..039e21174b169 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/app.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python + +import asyncio +import http +import http.cookies +import pathlib +import signal +import urllib.parse +import uuid + +import websockets +from websockets.frames import CloseCode + + +# User accounts database + +USERS = {} + + +def create_token(user, lifetime=1): + """Create token for user and delete it once its lifetime is over.""" + token = uuid.uuid4().hex + USERS[token] = user + asyncio.get_running_loop().call_later(lifetime, USERS.pop, token) + return token + + +def get_user(token): + """Find user authenticated by token or return None.""" + return USERS.get(token) + + +# Utilities + + +def get_cookie(raw, key): + cookie = http.cookies.SimpleCookie(raw) + morsel = cookie.get(key) + if morsel is not None: + return morsel.value + + +def get_query_param(path, key): + query = urllib.parse.urlparse(path).query + params = urllib.parse.parse_qs(query) + values = params.get(key, []) + if len(values) == 1: + return values[0] + + +# Main HTTP server + +CONTENT_TYPES = { + ".css": "text/css", + ".html": "text/html; charset=utf-8", + ".ico": "image/x-icon", + ".js": "text/javascript", +} + + +async def serve_html(path, request_headers): + user = get_query_param(path, "user") + path = urllib.parse.urlparse(path).path + if path == "/": + if user is None: + page = "index.html" + else: + page = "test.html" + else: + page = path[1:] + + try: + template = pathlib.Path(__file__).with_name(page) + except ValueError: + pass + else: + if template.is_file(): + headers = {"Content-Type": CONTENT_TYPES[template.suffix]} + body = template.read_bytes() + if user is not None: + token = create_token(user) + body = body.replace(b"TOKEN", token.encode()) + return http.HTTPStatus.OK, headers, body + + return http.HTTPStatus.NOT_FOUND, {}, b"Not found\n" + + +async def noop_handler(websocket): + pass + + +# Send credentials as the first message in the WebSocket connection + + +async def first_message_handler(websocket): + token = await websocket.recv() + user = get_user(token) + if user is None: + await websocket.close(CloseCode.INTERNAL_ERROR, "authentication failed") + return + + await websocket.send(f"Hello {user}!") + message = await websocket.recv() + assert message == f"Goodbye {user}." + + +# Add credentials to the WebSocket URI in a query parameter + + +class QueryParamProtocol(websockets.WebSocketServerProtocol): + async def process_request(self, path, headers): + token = get_query_param(path, "token") + if token is None: + return http.HTTPStatus.UNAUTHORIZED, [], b"Missing token\n" + + user = get_user(token) + if user is None: + return http.HTTPStatus.UNAUTHORIZED, [], b"Invalid token\n" + + self.user = user + + +async def query_param_handler(websocket): + user = websocket.user + + await websocket.send(f"Hello {user}!") + message = await websocket.recv() + assert message == f"Goodbye {user}." + + +# Set a cookie on the domain of the WebSocket URI + + +class CookieProtocol(websockets.WebSocketServerProtocol): + async def process_request(self, path, headers): + if "Upgrade" not in headers: + template = pathlib.Path(__file__).with_name(path[1:]) + headers = {"Content-Type": CONTENT_TYPES[template.suffix]} + body = template.read_bytes() + return http.HTTPStatus.OK, headers, body + + token = get_cookie(headers.get("Cookie", ""), "token") + if token is None: + return http.HTTPStatus.UNAUTHORIZED, [], b"Missing token\n" + + user = get_user(token) + if user is None: + return http.HTTPStatus.UNAUTHORIZED, [], b"Invalid token\n" + + self.user = user + + +async def cookie_handler(websocket): + user = websocket.user + + await websocket.send(f"Hello {user}!") + message = await websocket.recv() + assert message == f"Goodbye {user}." + + +# Adding credentials to the WebSocket URI in user information + + +class UserInfoProtocol(websockets.BasicAuthWebSocketServerProtocol): + async def check_credentials(self, username, password): + if username != "token": + return False + + user = get_user(password) + if user is None: + return False + + self.user = user + return True + + +async def user_info_handler(websocket): + user = websocket.user + + await websocket.send(f"Hello {user}!") + message = await websocket.recv() + assert message == f"Goodbye {user}." + + +# Start all five servers + + +async def main(): + # Set the stop condition when receiving SIGINT or SIGTERM. + loop = asyncio.get_running_loop() + stop = loop.create_future() + loop.add_signal_handler(signal.SIGINT, stop.set_result, None) + loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) + + async with websockets.serve( + noop_handler, + host="", + port=8000, + process_request=serve_html, + ), websockets.serve( + first_message_handler, + host="", + port=8001, + ), websockets.serve( + query_param_handler, + host="", + port=8002, + create_protocol=QueryParamProtocol, + ), websockets.serve( + cookie_handler, + host="", + port=8003, + create_protocol=CookieProtocol, + ), websockets.serve( + user_info_handler, + host="", + port=8004, + create_protocol=UserInfoProtocol, + ): + print("Running on http://localhost:8000/") + await stop + print("\rExiting") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie.html new file mode 100644 index 0000000000000..ca17358fd0d05 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie.html @@ -0,0 +1,15 @@ + + + + Cookie | WebSocket Authentication + + + +

    [??] Cookie

    +

    [OK] Cookie

    +

    [KO] Cookie

    + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie.js new file mode 100644 index 0000000000000..2cca34fcbb49c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie.js @@ -0,0 +1,23 @@ +// send token to iframe +window.addEventListener("DOMContentLoaded", () => { + const iframe = document.querySelector("iframe"); + iframe.addEventListener("load", () => { + iframe.contentWindow.postMessage(token, "http://localhost:8003"); + }); +}); + +// once iframe has set cookie, open WebSocket connection +window.addEventListener("message", ({ origin }) => { + if (origin !== "http://localhost:8003") { + return; + } + + const websocket = new WebSocket("ws://localhost:8003/"); + + websocket.onmessage = ({ data }) => { + // event.data is expected to be "Hello !" + websocket.send(`Goodbye ${data.slice(6, -1)}.`); + }; + + runTest(websocket); +}); diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie_iframe.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie_iframe.html new file mode 100644 index 0000000000000..9f49ebb9a080e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie_iframe.html @@ -0,0 +1,9 @@ + + + + Cookie iframe | WebSocket Authentication + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie_iframe.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie_iframe.js new file mode 100644 index 0000000000000..2d2e692e8d252 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie_iframe.js @@ -0,0 +1,9 @@ +// receive token from the parent window, set cookie and notify parent +window.addEventListener("message", ({ origin, data }) => { + if (origin !== "http://localhost:8000") { + return; + } + + document.cookie = `token=${data}; SameSite=Strict`; + window.parent.postMessage("", "http://localhost:8000"); +}); diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/favicon.ico b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/favicon.ico new file mode 100644 index 0000000000000..36e855029d705 Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/favicon.ico differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/first_message.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/first_message.html new file mode 100644 index 0000000000000..4dc511a1764f2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/first_message.html @@ -0,0 +1,14 @@ + + + + First message | WebSocket Authentication + + + +

    [??] First message

    +

    [OK] First message

    +

    [KO] First message

    + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/first_message.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/first_message.js new file mode 100644 index 0000000000000..1acf048bafb3f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/first_message.js @@ -0,0 +1,11 @@ +window.addEventListener("DOMContentLoaded", () => { + const websocket = new WebSocket("ws://localhost:8001/"); + websocket.onopen = () => websocket.send(token); + + websocket.onmessage = ({ data }) => { + // event.data is expected to be "Hello !" + websocket.send(`Goodbye ${data.slice(6, -1)}.`); + }; + + runTest(websocket); +}); diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/index.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/index.html new file mode 100644 index 0000000000000..c37deef2707b9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/index.html @@ -0,0 +1,12 @@ + + + + WebSocket Authentication + + + +
    + +
    + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/query_param.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/query_param.html new file mode 100644 index 0000000000000..27aa454a40170 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/query_param.html @@ -0,0 +1,14 @@ + + + + Query parameter | WebSocket Authentication + + + +

    [??] Query parameter

    +

    [OK] Query parameter

    +

    [KO] Query parameter

    + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/query_param.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/query_param.js new file mode 100644 index 0000000000000..6a54d0b6caeb5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/query_param.js @@ -0,0 +1,11 @@ +window.addEventListener("DOMContentLoaded", () => { + const uri = `ws://localhost:8002/?token=${token}`; + const websocket = new WebSocket(uri); + + websocket.onmessage = ({ data }) => { + // event.data is expected to be "Hello !" + websocket.send(`Goodbye ${data.slice(6, -1)}.`); + }; + + runTest(websocket); +}); diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/script.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/script.js new file mode 100644 index 0000000000000..ec4e5e6709bfc --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/script.js @@ -0,0 +1,51 @@ +var token = window.parent.token; + +function getExpectedEvents() { + return [ + { + type: "open", + }, + { + type: "message", + data: `Hello ${window.parent.user}!`, + }, + { + type: "close", + code: 1000, + reason: "", + wasClean: true, + }, + ]; +} + +function isEqual(expected, actual) { + // good enough for our purposes here! + return JSON.stringify(expected) === JSON.stringify(actual); +} + +function testStep(expected, actual) { + if (isEqual(expected, actual)) { + document.body.className = "ok"; + } else if (isEqual(expected.slice(0, actual.length), actual)) { + document.body.className = "test"; + } else { + document.body.className = "ko"; + } +} + +function runTest(websocket) { + const expected = getExpectedEvents(); + var actual = []; + websocket.addEventListener("open", ({ type }) => { + actual.push({ type }); + testStep(expected, actual); + }); + websocket.addEventListener("message", ({ type, data }) => { + actual.push({ type, data }); + testStep(expected, actual); + }); + websocket.addEventListener("close", ({ type, code, reason, wasClean }) => { + actual.push({ type, code, reason, wasClean }); + testStep(expected, actual); + }); +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/style.css b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/style.css new file mode 100644 index 0000000000000..6e3918ccae60e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/style.css @@ -0,0 +1,69 @@ +/* page layout */ + +body { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin: 0; + height: 100vh; +} +div.title, iframe { + width: 100vw; + height: 20vh; + border: none; +} +div.title { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} +h1, p { + margin: 0; + width: 24em; +} + +/* text style */ + +h1, input, p { + font-family: monospace; + font-size: 3em; +} +input { + color: #333; + border: 3px solid #999; + padding: 1em; +} +input:focus { + border-color: #333; + outline: none; +} +input::placeholder { + color: #999; + opacity: 1; +} + +/* test results */ + +body.test { + background-color: #666; + color: #fff; +} +body.ok { + background-color: #090; + color: #fff; +} +body.ko { + background-color: #900; + color: #fff; +} +body > p { + display: none; +} +body > p.title, +body.test > p.test, +body.ok > p.ok, +body.ko > p.ko { + display: block; +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/test.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/test.html new file mode 100644 index 0000000000000..3883d6a39e67c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/test.html @@ -0,0 +1,15 @@ + + + + WebSocket Authentication + + + +

    WebSocket Authentication

    + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/test.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/test.js new file mode 100644 index 0000000000000..428830ff31de4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/test.js @@ -0,0 +1,6 @@ +// for connecting to WebSocket servers +var token = document.body.dataset.token; + +// for test assertions only +const params = new URLSearchParams(window.location.search); +var user = params.get("user"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/user_info.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/user_info.html new file mode 100644 index 0000000000000..7b9c99c730116 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/user_info.html @@ -0,0 +1,14 @@ + + + + User information | WebSocket Authentication + + + +

    [??] User information

    +

    [OK] User information

    +

    [KO] User information

    + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/user_info.js b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/user_info.js new file mode 100644 index 0000000000000..1dab2ce4c1709 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/user_info.js @@ -0,0 +1,11 @@ +window.addEventListener("DOMContentLoaded", () => { + const uri = `ws://token:${token}@localhost:8004/`; + const websocket = new WebSocket(uri); + + websocket.onmessage = ({ data }) => { + // event.data is expected to be "Hello !" + websocket.send(`Goodbye ${data.slice(6, -1)}.`); + }; + + runTest(websocket); +}); diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/w3c-import.log new file mode 100644 index 0000000000000..cd9b44fba53c8 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/w3c-import.log @@ -0,0 +1,33 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/app.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie.js +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie_iframe.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/cookie_iframe.js +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/favicon.ico +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/first_message.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/first_message.js +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/index.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/query_param.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/query_param.js +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/script.js +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/style.css +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/test.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/test.js +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/user_info.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/authentication/user_info.js diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/broadcast/clients.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/broadcast/clients.py new file mode 100644 index 0000000000000..fe39dfe051ee2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/broadcast/clients.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python + +import asyncio +import statistics +import sys +import time + +import websockets + + +LATENCIES = {} + + +async def log_latency(interval): + while True: + await asyncio.sleep(interval) + p = statistics.quantiles(LATENCIES.values(), n=100) + print(f"clients = {len(LATENCIES)}") + print( + f"p50 = {p[49] / 1e6:.1f}ms, " + f"p95 = {p[94] / 1e6:.1f}ms, " + f"p99 = {p[98] / 1e6:.1f}ms" + ) + print() + + +async def client(): + try: + async with websockets.connect( + "ws://localhost:8765", + ping_timeout=None, + ) as websocket: + async for msg in websocket: + client_time = time.time_ns() + server_time = int(msg[:19].decode()) + LATENCIES[websocket] = client_time - server_time + except Exception as exc: + print(exc) + + +async def main(count, interval): + asyncio.create_task(log_latency(interval)) + clients = [] + for _ in range(count): + clients.append(asyncio.create_task(client())) + await asyncio.sleep(0.001) # 1ms between each connection + await asyncio.wait(clients) + + +if __name__ == "__main__": + try: + count = int(sys.argv[1]) + interval = float(sys.argv[2]) + except Exception as exc: + print(f"Usage: {sys.argv[0]} count interval") + print(" Connect clients e.g. 1000") + print(" Report latency every seconds e.g. 1") + print() + print(exc) + else: + asyncio.run(main(count, interval)) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/broadcast/server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/broadcast/server.py new file mode 100644 index 0000000000000..9c9907b7f9e42 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/broadcast/server.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python + +import asyncio +import functools +import os +import sys +import time + +import websockets + + +CLIENTS = set() + + +async def send(websocket, message): + try: + await websocket.send(message) + except websockets.ConnectionClosed: + pass + + +async def relay(queue, websocket): + while True: + message = await queue.get() + await websocket.send(message) + + +class PubSub: + def __init__(self): + self.waiter = asyncio.Future() + + def publish(self, value): + waiter, self.waiter = self.waiter, asyncio.Future() + waiter.set_result((value, self.waiter)) + + async def subscribe(self): + waiter = self.waiter + while True: + value, waiter = await waiter + yield value + + __aiter__ = subscribe + + +PUBSUB = PubSub() + + +async def handler(websocket, method=None): + if method in ["default", "naive", "task", "wait"]: + CLIENTS.add(websocket) + try: + await websocket.wait_closed() + finally: + CLIENTS.remove(websocket) + elif method == "queue": + queue = asyncio.Queue() + relay_task = asyncio.create_task(relay(queue, websocket)) + CLIENTS.add(queue) + try: + await websocket.wait_closed() + finally: + CLIENTS.remove(queue) + relay_task.cancel() + elif method == "pubsub": + async for message in PUBSUB: + await websocket.send(message) + else: + raise NotImplementedError(f"unsupported method: {method}") + + +async def broadcast(method, size, delay): + """Broadcast messages at regular intervals.""" + load_average = 0 + time_average = 0 + pc1, pt1 = time.perf_counter_ns(), time.process_time_ns() + await asyncio.sleep(delay) + while True: + print(f"clients = {len(CLIENTS)}") + pc0, pt0 = time.perf_counter_ns(), time.process_time_ns() + load_average = 0.9 * load_average + 0.1 * (pt0 - pt1) / (pc0 - pc1) + print( + f"load = {(pt0 - pt1) / (pc0 - pc1) * 100:.1f}% / " + f"average = {load_average * 100:.1f}%, " + f"late = {(pc0 - pc1 - delay * 1e9) / 1e6:.1f} ms" + ) + pc1, pt1 = pc0, pt0 + + assert size > 20 + message = str(time.time_ns()).encode() + b" " + os.urandom(size - 20) + + if method == "default": + websockets.broadcast(CLIENTS, message) + elif method == "naive": + # Since the loop can yield control, make a copy of CLIENTS + # to avoid: RuntimeError: Set changed size during iteration + for websocket in CLIENTS.copy(): + await send(websocket, message) + elif method == "task": + for websocket in CLIENTS: + asyncio.create_task(send(websocket, message)) + elif method == "wait": + if CLIENTS: # asyncio.wait doesn't accept an empty list + await asyncio.wait( + [ + asyncio.create_task(send(websocket, message)) + for websocket in CLIENTS + ] + ) + elif method == "queue": + for queue in CLIENTS: + queue.put_nowait(message) + elif method == "pubsub": + PUBSUB.publish(message) + else: + raise NotImplementedError(f"unsupported method: {method}") + + pc2 = time.perf_counter_ns() + wait = delay + (pc1 - pc2) / 1e9 + time_average = 0.9 * time_average + 0.1 * (pc2 - pc1) + print( + f"broadcast = {(pc2 - pc1) / 1e6:.1f}ms / " + f"average = {time_average / 1e6:.1f}ms, " + f"wait = {wait * 1e3:.1f}ms" + ) + await asyncio.sleep(wait) + print() + + +async def main(method, size, delay): + async with websockets.serve( + functools.partial(handler, method=method), + "localhost", + 8765, + compression=None, + ping_timeout=None, + ): + await broadcast(method, size, delay) + + +if __name__ == "__main__": + try: + method = sys.argv[1] + assert method in ["default", "naive", "task", "wait", "queue", "pubsub"] + size = int(sys.argv[2]) + delay = float(sys.argv[3]) + except Exception as exc: + print(f"Usage: {sys.argv[0]} method size delay") + print(" Start a server broadcasting messages with e.g. naive") + print(" Send a payload of bytes every seconds") + print() + print(exc) + else: + asyncio.run(main(method, size, delay)) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/broadcast/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/broadcast/w3c-import.log new file mode 100644 index 0000000000000..3ee419c8ce0e3 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/broadcast/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/broadcast/clients.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/broadcast/server.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/compression/benchmark.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/compression/benchmark.py new file mode 100644 index 0000000000000..c5b13c8fa343d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/compression/benchmark.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python + +import getpass +import json +import pickle +import subprocess +import sys +import time +import zlib + + +CORPUS_FILE = "corpus.pkl" + +REPEAT = 10 + +WB, ML = 12, 5 # defaults used as a reference + + +def _corpus(): + OAUTH_TOKEN = getpass.getpass("OAuth Token? ") + COMMIT_API = ( + f'curl -H "Authorization: token {OAUTH_TOKEN}" ' + f"https://api.github.com/repos/python-websockets/websockets/git/commits/:sha" + ) + + commits = [] + + head = subprocess.check_output("git rev-parse HEAD", shell=True).decode().strip() + todo = [head] + seen = set() + + while todo: + sha = todo.pop(0) + commit = subprocess.check_output(COMMIT_API.replace(":sha", sha), shell=True) + commits.append(commit) + seen.add(sha) + for parent in json.loads(commit)["parents"]: + sha = parent["sha"] + if sha not in seen and sha not in todo: + todo.append(sha) + time.sleep(1) # rate throttling + + return commits + + +def corpus(): + data = _corpus() + with open(CORPUS_FILE, "wb") as handle: + pickle.dump(data, handle) + + +def _run(data): + size = {} + duration = {} + + for wbits in range(9, 16): + size[wbits] = {} + duration[wbits] = {} + + for memLevel in range(1, 10): + encoder = zlib.compressobj(wbits=-wbits, memLevel=memLevel) + encoded = [] + + t0 = time.perf_counter() + + for _ in range(REPEAT): + for item in data: + if isinstance(item, str): + item = item.encode("utf-8") + # Taken from PerMessageDeflate.encode + item = encoder.compress(item) + encoder.flush(zlib.Z_SYNC_FLUSH) + if item.endswith(b"\x00\x00\xff\xff"): + item = item[:-4] + encoded.append(item) + + t1 = time.perf_counter() + + size[wbits][memLevel] = sum(len(item) for item in encoded) + duration[wbits][memLevel] = (t1 - t0) / REPEAT + + raw_size = sum(len(item) for item in data) + + print("=" * 79) + print("Compression ratio") + print("=" * 79) + print("\t".join(["wb \\ ml"] + [str(memLevel) for memLevel in range(1, 10)])) + for wbits in range(9, 16): + print( + "\t".join( + [str(wbits)] + + [ + f"{100 * (1 - size[wbits][memLevel] / raw_size):.1f}%" + for memLevel in range(1, 10) + ] + ) + ) + print("=" * 79) + print() + + print("=" * 79) + print("CPU time") + print("=" * 79) + print("\t".join(["wb \\ ml"] + [str(memLevel) for memLevel in range(1, 10)])) + for wbits in range(9, 16): + print( + "\t".join( + [str(wbits)] + + [ + f"{1000 * duration[wbits][memLevel]:.1f}ms" + for memLevel in range(1, 10) + ] + ) + ) + print("=" * 79) + print() + + print("=" * 79) + print(f"Size vs. {WB} \\ {ML}") + print("=" * 79) + print("\t".join(["wb \\ ml"] + [str(memLevel) for memLevel in range(1, 10)])) + for wbits in range(9, 16): + print( + "\t".join( + [str(wbits)] + + [ + f"{100 * (size[wbits][memLevel] / size[WB][ML] - 1):.1f}%" + for memLevel in range(1, 10) + ] + ) + ) + print("=" * 79) + print() + + print("=" * 79) + print(f"Time vs. {WB} \\ {ML}") + print("=" * 79) + print("\t".join(["wb \\ ml"] + [str(memLevel) for memLevel in range(1, 10)])) + for wbits in range(9, 16): + print( + "\t".join( + [str(wbits)] + + [ + f"{100 * (duration[wbits][memLevel] / duration[WB][ML] - 1):.1f}%" + for memLevel in range(1, 10) + ] + ) + ) + print("=" * 79) + print() + + +def run(): + with open(CORPUS_FILE, "rb") as handle: + data = pickle.load(handle) + _run(data) + + +try: + run = globals()[sys.argv[1]] +except (KeyError, IndexError): + print(f"Usage: {sys.argv[0]} [corpus|run]") +else: + run() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/compression/client.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/compression/client.py new file mode 100644 index 0000000000000..3ee19ddc59ab4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/compression/client.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python + +import asyncio +import statistics +import tracemalloc + +import websockets +from websockets.extensions import permessage_deflate + + +CLIENTS = 20 +INTERVAL = 1 / 10 # seconds + +WB, ML = 12, 5 + +MEM_SIZE = [] + + +async def 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=WB, + client_max_window_bits=WB, + compress_settings={"memLevel": ML}, + ) + ], + ) 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) + + +async def clients(): + await asyncio.gather(*[client(client) for client in range(CLIENTS + 1)]) + + +asyncio.run(clients()) + + +# 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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/compression/server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/compression/server.py new file mode 100644 index 0000000000000..8d1ee3cd7c0d2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/compression/server.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +import asyncio +import os +import signal +import statistics +import tracemalloc + +import websockets +from websockets.extensions import permessage_deflate + + +CLIENTS = 20 +INTERVAL = 1 / 10 # seconds + +WB, ML = 12, 5 + +MEM_SIZE = [] + + +async def handler(ws): + 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 server(): + loop = asyncio.get_running_loop() + stop = loop.create_future() + + # Set the stop condition when receiving SIGTERM. + print("Stop the server with:") + print(f"kill -TERM {os.getpid()}") + print() + loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) + + async with websockets.serve( + handler, + "localhost", + 8765, + extensions=[ + permessage_deflate.ServerPerMessageDeflateFactory( + server_max_window_bits=WB, + client_max_window_bits=WB, + compress_settings={"memLevel": ML}, + ) + ], + ): + tracemalloc.start() + await stop + + +asyncio.run(server()) + + +# First connection may incur 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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/compression/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/compression/w3c-import.log new file mode 100644 index 0000000000000..47a9bb937d096 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/compression/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/compression/benchmark.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/compression/client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/compression/server.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/optimization/parse_frames.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/optimization/parse_frames.py new file mode 100644 index 0000000000000..e3acbe3c20129 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/optimization/parse_frames.py @@ -0,0 +1,101 @@ +"""Benchark parsing WebSocket frames.""" + +import subprocess +import sys +import timeit + +from websockets.extensions.permessage_deflate import PerMessageDeflate +from websockets.frames import Frame, Opcode +from websockets.streams import StreamReader + + +# 256kB of text, compressible by about 70%. +text = subprocess.check_output(["git", "log", "8dd8e410"], text=True) + + +def get_frame(size): + repeat, remainder = divmod(size, 256 * 1024) + payload = repeat * text + text[:remainder] + return Frame(Opcode.TEXT, payload.encode(), True) + + +def parse_frame(data, count, mask, extensions): + reader = StreamReader() + for _ in range(count): + reader.feed_data(data) + parser = Frame.parse( + reader.read_exact, + mask=mask, + extensions=extensions, + ) + try: + next(parser) + except StopIteration: + pass + else: + assert False, "parser should return frame" + reader.feed_eof() + assert reader.at_eof(), "parser should consume all data" + + +def run_benchmark(size, count, compression=False, number=100): + if compression: + extensions = [PerMessageDeflate(True, True, 12, 12, {"memLevel": 5})] + else: + extensions = [] + globals = { + "get_frame": get_frame, + "parse_frame": parse_frame, + "extensions": extensions, + } + sppf = ( + min( + timeit.repeat( + f"parse_frame(data, {count}, mask=True, extensions=extensions)", + f"data = get_frame({size})" + f".serialize(mask=True, extensions=extensions)", + number=number, + globals=globals, + ) + ) + / number + / count + * 1_000_000 + ) + cppf = ( + min( + timeit.repeat( + f"parse_frame(data, {count}, mask=False, extensions=extensions)", + f"data = get_frame({size})" + f".serialize(mask=False, extensions=extensions)", + number=number, + globals=globals, + ) + ) + / number + / count + * 1_000_000 + ) + print(f"{size}\t{compression}\t{sppf:.2f}\t{cppf:.2f}") + + +if __name__ == "__main__": + print("Sizes are in bytes. Times are in µs per frame.", file=sys.stderr) + print("Run `tabs -16` for clean output. Pipe stdout to TSV for saving.") + print(file=sys.stderr) + + print("size\tcompression\tserver\tclient") + run_benchmark(size=8, count=1000, compression=False) + run_benchmark(size=60, count=1000, compression=False) + run_benchmark(size=500, count=1000, compression=False) + run_benchmark(size=4_000, count=1000, compression=False) + run_benchmark(size=30_000, count=200, compression=False) + run_benchmark(size=250_000, count=100, compression=False) + run_benchmark(size=2_000_000, count=20, compression=False) + + run_benchmark(size=8, count=1000, compression=True) + run_benchmark(size=60, count=1000, compression=True) + run_benchmark(size=500, count=200, compression=True) + run_benchmark(size=4_000, count=100, compression=True) + run_benchmark(size=30_000, count=20, compression=True) + run_benchmark(size=250_000, count=10, compression=True) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/optimization/parse_handshake.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/optimization/parse_handshake.py new file mode 100644 index 0000000000000..af5a4ecae2c03 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/optimization/parse_handshake.py @@ -0,0 +1,102 @@ +"""Benchark parsing WebSocket handshake requests.""" + +# The parser for responses is designed similarly and should perform similarly. + +import sys +import timeit + +from websockets.http11 import Request +from websockets.streams import StreamReader + + +CHROME_HANDSHAKE = ( + b"GET / HTTP/1.1\r\n" + b"Host: localhost:5678\r\n" + b"Connection: Upgrade\r\n" + b"Pragma: no-cache\r\n" + b"Cache-Control: no-cache\r\n" + b"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " + b"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36\r\n" + b"Upgrade: websocket\r\n" + b"Origin: null\r\n" + b"Sec-WebSocket-Version: 13\r\n" + b"Accept-Encoding: gzip, deflate, br\r\n" + b"Accept-Language: en-GB,en;q=0.9,en-US;q=0.8,fr;q=0.7\r\n" + b"Sec-WebSocket-Key: ebkySAl+8+e6l5pRKTMkyQ==\r\n" + b"Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n" + b"\r\n" +) + +FIREFOX_HANDSHAKE = ( + b"GET / HTTP/1.1\r\n" + b"Host: localhost:5678\r\n" + b"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) " + b"Gecko/20100101 Firefox/111.0\r\n" + b"Accept: */*\r\n" + b"Accept-Language: en-US,en;q=0.7,fr-FR;q=0.3\r\n" + b"Accept-Encoding: gzip, deflate, br\r\n" + b"Sec-WebSocket-Version: 13\r\n" + b"Origin: null\r\n" + b"Sec-WebSocket-Extensions: permessage-deflate\r\n" + b"Sec-WebSocket-Key: 1PuS+hnb+0AXsL7z2hNAhw==\r\n" + b"Connection: keep-alive, Upgrade\r\n" + b"Sec-Fetch-Dest: websocket\r\n" + b"Sec-Fetch-Mode: websocket\r\n" + b"Sec-Fetch-Site: cross-site\r\n" + b"Pragma: no-cache\r\n" + b"Cache-Control: no-cache\r\n" + b"Upgrade: websocket\r\n" + b"\r\n" +) + +WEBSOCKETS_HANDSHAKE = ( + b"GET / HTTP/1.1\r\n" + b"Host: localhost:8765\r\n" + b"Upgrade: websocket\r\n" + b"Connection: Upgrade\r\n" + b"Sec-WebSocket-Key: 9c55e0/siQ6tJPCs/QR8ZA==\r\n" + b"Sec-WebSocket-Version: 13\r\n" + b"Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n" + b"User-Agent: Python/3.11 websockets/11.0\r\n" + b"\r\n" +) + + +def parse_handshake(handshake): + reader = StreamReader() + reader.feed_data(handshake) + parser = Request.parse(reader.read_line) + try: + next(parser) + except StopIteration: + pass + else: + assert False, "parser should return request" + reader.feed_eof() + assert reader.at_eof(), "parser should consume all data" + + +def run_benchmark(name, handshake, number=10000): + ph = ( + min( + timeit.repeat( + "parse_handshake(handshake)", + number=number, + globals={"parse_handshake": parse_handshake, "handshake": handshake}, + ) + ) + / number + * 1_000_000 + ) + print(f"{name}\t{len(handshake)}\t{ph:.1f}") + + +if __name__ == "__main__": + print("Sizes are in bytes. Times are in µs per frame.", file=sys.stderr) + print("Run `tabs -16` for clean output. Pipe stdout to TSV for saving.") + print(file=sys.stderr) + + print("client\tsize\ttime") + run_benchmark("Chrome", CHROME_HANDSHAKE) + run_benchmark("Firefox", FIREFOX_HANDSHAKE) + run_benchmark("websockets", WEBSOCKETS_HANDSHAKE) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/optimization/streams.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/optimization/streams.py new file mode 100644 index 0000000000000..ca24a598345e6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/optimization/streams.py @@ -0,0 +1,301 @@ +""" +Benchmark two possible implementations of a stream reader. + +The difference lies in the data structure that buffers incoming data: + +* ``ByteArrayStreamReader`` uses a ``bytearray``; +* ``BytesDequeStreamReader`` uses a ``deque[bytes]``. + +``ByteArrayStreamReader`` is faster for streaming small frames, which is the +standard use case of websockets, likely due to its simple implementation and +to ``bytearray`` being fast at appending data and removing data at the front +(https://hg.python.org/cpython/rev/499a96611baa). + +``BytesDequeStreamReader`` is faster for large frames and for bursts, likely +because it copies payloads only once, while ``ByteArrayStreamReader`` copies +them twice. + +""" + + +import collections +import os +import timeit + + +# Implementations + + +class ByteArrayStreamReader: + def __init__(self): + self.buffer = bytearray() + self.eof = False + + def readline(self): + n = 0 # number of bytes to read + p = 0 # number of bytes without a newline + while True: + n = self.buffer.find(b"\n", p) + 1 + if n > 0: + break + p = len(self.buffer) + yield + r = self.buffer[:n] + del self.buffer[:n] + return r + + def readexactly(self, n): + assert n >= 0 + while len(self.buffer) < n: + yield + r = self.buffer[:n] + del self.buffer[:n] + return r + + def feed_data(self, data): + self.buffer += data + + def feed_eof(self): + self.eof = True + + def at_eof(self): + return self.eof and not self.buffer + + +class BytesDequeStreamReader: + def __init__(self): + self.buffer = collections.deque() + self.eof = False + + def readline(self): + b = [] + while True: + # Read next chunk + while True: + try: + c = self.buffer.popleft() + except IndexError: + yield + else: + break + # Handle chunk + n = c.find(b"\n") + 1 + if n == len(c): + # Read exactly enough data + b.append(c) + break + elif n > 0: + # Read too much data + b.append(c[:n]) + self.buffer.appendleft(c[n:]) + break + else: # n == 0 + # Need to read more data + b.append(c) + return b"".join(b) + + def readexactly(self, n): + if n == 0: + return b"" + b = [] + while True: + # Read next chunk + while True: + try: + c = self.buffer.popleft() + except IndexError: + yield + else: + break + # Handle chunk + n -= len(c) + if n == 0: + # Read exactly enough data + b.append(c) + break + elif n < 0: + # Read too much data + b.append(c[:n]) + self.buffer.appendleft(c[n:]) + break + else: # n >= 0 + # Need to read more data + b.append(c) + return b"".join(b) + + def feed_data(self, data): + self.buffer.append(data) + + def feed_eof(self): + self.eof = True + + def at_eof(self): + return self.eof and not self.buffer + + +# Tests + + +class Protocol: + def __init__(self, StreamReader): + self.reader = StreamReader() + self.events = [] + # Start parser coroutine + self.parser = self.run_parser() + next(self.parser) + + def run_parser(self): + while True: + frame = yield from self.reader.readexactly(2) + self.events.append(frame) + frame = yield from self.reader.readline() + self.events.append(frame) + + def data_received(self, data): + self.reader.feed_data(data) + next(self.parser) # run parser until more data is needed + events, self.events = self.events, [] + return events + + +def run_test(StreamReader): + proto = Protocol(StreamReader) + + actual = proto.data_received(b"a") + expected = [] + assert actual == expected, f"{actual} != {expected}" + + actual = proto.data_received(b"b") + expected = [b"ab"] + assert actual == expected, f"{actual} != {expected}" + + actual = proto.data_received(b"c") + expected = [] + assert actual == expected, f"{actual} != {expected}" + + actual = proto.data_received(b"\n") + expected = [b"c\n"] + assert actual == expected, f"{actual} != {expected}" + + actual = proto.data_received(b"efghi\njklmn") + expected = [b"ef", b"ghi\n", b"jk"] + assert actual == expected, f"{actual} != {expected}" + + +# Benchmarks + + +def get_frame_packets(size, packet_size=None): + if size < 126: + frame = bytes([138, size]) + elif size < 65536: + frame = bytes([138, 126]) + bytes(divmod(size, 256)) + else: + size1, size2 = divmod(size, 65536) + frame = ( + bytes([138, 127]) + bytes(divmod(size1, 256)) + bytes(divmod(size2, 256)) + ) + frame += os.urandom(size) + if packet_size is None: + return [frame] + else: + packets = [] + while frame: + packets.append(frame[:packet_size]) + frame = frame[packet_size:] + return packets + + +def benchmark_stream(StreamReader, packets, size, count): + reader = StreamReader() + for _ in range(count): + for packet in packets: + reader.feed_data(packet) + yield from reader.readexactly(2) + if size >= 65536: + yield from reader.readexactly(4) + elif size >= 126: + yield from reader.readexactly(2) + yield from reader.readexactly(size) + reader.feed_eof() + assert reader.at_eof() + + +def benchmark_burst(StreamReader, packets, size, count): + reader = StreamReader() + for _ in range(count): + for packet in packets: + reader.feed_data(packet) + reader.feed_eof() + for _ in range(count): + yield from reader.readexactly(2) + if size >= 65536: + yield from reader.readexactly(4) + elif size >= 126: + yield from reader.readexactly(2) + yield from reader.readexactly(size) + assert reader.at_eof() + + +def run_benchmark(size, count, packet_size=None, number=1000): + stmt = f"list(benchmark(StreamReader, packets, {size}, {count}))" + setup = f"packets = get_frame_packets({size}, {packet_size})" + context = globals() + + context["StreamReader"] = context["ByteArrayStreamReader"] + context["benchmark"] = context["benchmark_stream"] + bas = min(timeit.repeat(stmt, setup, number=number, globals=context)) + context["benchmark"] = context["benchmark_burst"] + bab = min(timeit.repeat(stmt, setup, number=number, globals=context)) + + context["StreamReader"] = context["BytesDequeStreamReader"] + context["benchmark"] = context["benchmark_stream"] + bds = min(timeit.repeat(stmt, setup, number=number, globals=context)) + context["benchmark"] = context["benchmark_burst"] + bdb = min(timeit.repeat(stmt, setup, number=number, globals=context)) + + print( + f"Frame size = {size} bytes, " + f"frame count = {count}, " + f"packet size = {packet_size}" + ) + print(f"* ByteArrayStreamReader (stream): {bas / number * 1_000_000:.1f}µs") + print( + f"* BytesDequeStreamReader (stream): " + f"{bds / number * 1_000_000:.1f}µs ({(bds / bas - 1) * 100:+.1f}%)" + ) + print(f"* ByteArrayStreamReader (burst): {bab / number * 1_000_000:.1f}µs") + print( + f"* BytesDequeStreamReader (burst): " + f"{bdb / number * 1_000_000:.1f}µs ({(bdb / bab - 1) * 100:+.1f}%)" + ) + print() + + +if __name__ == "__main__": + run_test(ByteArrayStreamReader) + run_test(BytesDequeStreamReader) + + run_benchmark(size=8, count=1000) + run_benchmark(size=60, count=1000) + run_benchmark(size=500, count=500) + run_benchmark(size=4_000, count=200) + run_benchmark(size=30_000, count=100) + run_benchmark(size=250_000, count=50) + run_benchmark(size=2_000_000, count=20) + + run_benchmark(size=4_000, count=200, packet_size=1024) + run_benchmark(size=30_000, count=100, packet_size=1024) + run_benchmark(size=250_000, count=50, packet_size=1024) + run_benchmark(size=2_000_000, count=20, packet_size=1024) + + run_benchmark(size=30_000, count=100, packet_size=4096) + run_benchmark(size=250_000, count=50, packet_size=4096) + run_benchmark(size=2_000_000, count=20, packet_size=4096) + + run_benchmark(size=30_000, count=100, packet_size=16384) + run_benchmark(size=250_000, count=50, packet_size=16384) + run_benchmark(size=2_000_000, count=20, packet_size=16384) + + run_benchmark(size=250_000, count=50, packet_size=65536) + run_benchmark(size=2_000_000, count=20, packet_size=65536) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/optimization/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/optimization/w3c-import.log new file mode 100644 index 0000000000000..1c043d2eed92c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/optimization/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/optimization/parse_frames.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/optimization/parse_handshake.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/experiments/optimization/streams.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/fuzzing/fuzz_http11_request_parser.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/fuzzing/fuzz_http11_request_parser.py new file mode 100644 index 0000000000000..59e0cea0f4393 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/fuzzing/fuzz_http11_request_parser.py @@ -0,0 +1,42 @@ +import sys + +import atheris + + +with atheris.instrument_imports(): + from websockets.exceptions import SecurityError + from websockets.http11 import Request + from websockets.streams import StreamReader + + +def test_one_input(data): + reader = StreamReader() + reader.feed_data(data) + reader.feed_eof() + + parser = Request.parse( + reader.read_line, + ) + + try: + next(parser) + except StopIteration as exc: + assert isinstance(exc.value, Request) + return # input accepted + except ( + EOFError, # connection is closed without a full HTTP request + SecurityError, # request exceeds a security limit + ValueError, # request isn't well formatted + ): + return # input rejected with a documented exception + + raise RuntimeError("parsing didn't complete") + + +def main(): + atheris.Setup(sys.argv, test_one_input) + atheris.Fuzz() + + +if __name__ == "__main__": + main() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/fuzzing/fuzz_http11_response_parser.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/fuzzing/fuzz_http11_response_parser.py new file mode 100644 index 0000000000000..6906720a49d3a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/fuzzing/fuzz_http11_response_parser.py @@ -0,0 +1,44 @@ +import sys + +import atheris + + +with atheris.instrument_imports(): + from websockets.exceptions import SecurityError + from websockets.http11 import Response + from websockets.streams import StreamReader + + +def test_one_input(data): + reader = StreamReader() + reader.feed_data(data) + reader.feed_eof() + + parser = Response.parse( + reader.read_line, + reader.read_exact, + reader.read_to_eof, + ) + try: + next(parser) + except StopIteration as exc: + assert isinstance(exc.value, Response) + return # input accepted + except ( + EOFError, # connection is closed without a full HTTP response + SecurityError, # response exceeds a security limit + LookupError, # response isn't well formatted + ValueError, # response isn't well formatted + ): + return # input rejected with a documented exception + + raise RuntimeError("parsing didn't complete") + + +def main(): + atheris.Setup(sys.argv, test_one_input) + atheris.Fuzz() + + +if __name__ == "__main__": + main() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/fuzzing/fuzz_websocket_parser.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/fuzzing/fuzz_websocket_parser.py new file mode 100644 index 0000000000000..1509a3549d7b8 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/fuzzing/fuzz_websocket_parser.py @@ -0,0 +1,51 @@ +import sys + +import atheris + + +with atheris.instrument_imports(): + from websockets.exceptions import PayloadTooBig, ProtocolError + from websockets.frames import Frame + from websockets.streams import StreamReader + + +def test_one_input(data): + fdp = atheris.FuzzedDataProvider(data) + mask = fdp.ConsumeBool() + max_size_enabled = fdp.ConsumeBool() + max_size = fdp.ConsumeInt(4) + payload = fdp.ConsumeBytes(atheris.ALL_REMAINING) + + reader = StreamReader() + reader.feed_data(payload) + reader.feed_eof() + + parser = Frame.parse( + reader.read_exact, + mask=mask, + max_size=max_size if max_size_enabled else None, + ) + + try: + next(parser) + except StopIteration as exc: + assert isinstance(exc.value, Frame) + return # input accepted + except ( + EOFError, # connection is closed without a full WebSocket frame + UnicodeDecodeError, # frame contains invalid UTF-8 + PayloadTooBig, # frame's payload size exceeds ``max_size`` + ProtocolError, # frame contains incorrect values + ): + return # input rejected with a documented exception + + raise RuntimeError("parsing didn't complete") + + +def main(): + atheris.Setup(sys.argv, test_one_input) + atheris.Fuzz() + + +if __name__ == "__main__": + main() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/fuzzing/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/fuzzing/w3c-import.log new file mode 100644 index 0000000000000..42598fad24fe6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/fuzzing/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/fuzzing/fuzz_http11_request_parser.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/fuzzing/fuzz_http11_response_parser.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/fuzzing/fuzz_websocket_parser.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/favicon.ico b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/favicon.ico new file mode 100644 index 0000000000000..36e855029d705 Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/favicon.ico differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/github-social-preview.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/github-social-preview.html new file mode 100644 index 0000000000000..7f2b45badb481 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/github-social-preview.html @@ -0,0 +1,39 @@ + + + + GitHub social preview + + + +

    Take a screenshot of this DOM node to make a PNG.

    +

    For 2x DPI screens.

    +

    preview @ 2x

    +

    For regular screens.

    +

    preview

    + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/github-social-preview.png b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/github-social-preview.png new file mode 100644 index 0000000000000..59a51b6e33810 Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/github-social-preview.png differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/horizontal.svg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/horizontal.svg new file mode 100644 index 0000000000000..ee872dc47869f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/horizontal.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/icon.html b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/icon.html new file mode 100644 index 0000000000000..6a71ec23bcfd9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/icon.html @@ -0,0 +1,25 @@ + + + + Icon + + + +

    Take a screenshot of these DOM nodes to2x make a PNG.

    +

    8x8 / 16x16 @ 2x

    +

    16x16 / 32x32 @ 2x

    +

    32x32 / 32x32 @ 2x

    +

    32x32 / 64x64 @ 2x

    +

    64x64 / 128x128 @ 2x

    +

    128x128 / 256x256 @ 2x

    +

    256x256 / 512x512 @ 2x

    +

    512x512 / 1024x1024 @ 2x

    + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/icon.svg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/icon.svg new file mode 100644 index 0000000000000..cb760940aa192 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/icon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/old.svg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/old.svg new file mode 100644 index 0000000000000..a073139e3311d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/old.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/tidelift.png b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/tidelift.png new file mode 100644 index 0000000000000..317dc4d9852df Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/tidelift.png differ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/vertical.svg b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/vertical.svg new file mode 100644 index 0000000000000..b07fb22387385 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/vertical.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/w3c-import.log new file mode 100644 index 0000000000000..c72dd218389d4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/w3c-import.log @@ -0,0 +1,25 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/favicon.ico +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/github-social-preview.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/github-social-preview.png +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/horizontal.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/icon.html +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/icon.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/old.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/tidelift.png +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/logo/vertical.svg diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/pyproject.toml b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/pyproject.toml new file mode 100644 index 0000000000000..f24616dd7ea14 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/pyproject.toml @@ -0,0 +1,87 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "websockets" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +requires-python = ">=3.8" +license = { text = "BSD-3-Clause" } +authors = [ + { name = "Aymeric Augustin", email = "aymeric.augustin@m4x.org" }, +] +keywords = ["WebSocket"] +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.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] +dynamic = ["version", "readme"] + +[project.urls] +Homepage = "https://github.com/python-websockets/websockets" +Changelog = "https://websockets.readthedocs.io/en/stable/project/changelog.html" +Documentation = "https://websockets.readthedocs.io/" +Funding = "https://tidelift.com/subscription/pkg/pypi-websockets?utm_source=pypi-websockets&utm_medium=referral&utm_campaign=readme" +Tracker = "https://github.com/python-websockets/websockets/issues" + +# On a macOS runner, build Intel, Universal, and Apple Silicon wheels. +[tool.cibuildwheel.macos] +archs = ["x86_64", "universal2", "arm64"] + +# On an Linux Intel runner with QEMU installed, build Intel and ARM wheels. +[tool.cibuildwheel.linux] +archs = ["auto", "aarch64"] + +[tool.coverage.run] +branch = true +omit = [ + # */websockets matches src/websockets and .tox/**/site-packages/websockets + "*/websockets/__main__.py", + "*/websockets/legacy/async_timeout.py", + "*/websockets/legacy/compatibility.py", + "tests/maxi_cov.py", +] + +[tool.coverage.paths] +source = [ + "src/websockets", + ".tox/*/lib/python*/site-packages/websockets", +] + +[tool.coverage.report] +exclude_lines = [ + "except ImportError:", + "if self.debug:", + "if sys.platform != \"win32\":", + "if typing.TYPE_CHECKING:", + "pragma: no cover", + "raise AssertionError", + "raise NotImplementedError", + "self.fail\\(\".*\"\\)", + "@unittest.skip", +] + +[tool.ruff] +select = [ + "E", # pycodestyle + "F", # Pyflakes + "W", # pycodestyle + "I", # isort +] +ignore = [ + "F403", + "F405", +] + +[tool.ruff.isort] +combine-as-imports = true +lines-after-imports = 2 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/setup.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/setup.py index b2d07737d227a..ae0aaa65de7a4 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/setup.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/setup.py @@ -1,3 +1,4 @@ +import os import pathlib import re @@ -6,58 +7,32 @@ root_dir = pathlib.Path(__file__).parent -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +exec((root_dir / "src" / "websockets" / "version.py").read_text(encoding="utf-8")) -long_description = (root_dir / 'README.rst').read_text(encoding='utf-8') - -# PyPI disables the "raw" directive. +# PyPI disables the "raw" directive. Remove this section of the README. long_description = re.sub( r"^\.\. raw:: html.*?^(?=\w)", "", - long_description, + (root_dir / "README.rst").read_text(encoding="utf-8"), flags=re.DOTALL | re.MULTILINE, ) -exec((root_dir / 'src' / 'websockets' / 'version.py').read_text(encoding='utf-8')) - -packages = ['websockets', 'websockets/legacy', 'websockets/extensions'] - -ext_modules = [ - setuptools.Extension( - 'websockets.speedups', - sources=['src/websockets/speedups.c'], - optional=not (root_dir / '.cibuildwheel').exists(), - ) -] - +# Set BUILD_EXTENSION to yes or no to force building or not building the +# speedups extension. If unset, the extension is built only if possible. +if os.environ.get("BUILD_EXTENSION") == "no": + ext_modules = [] +else: + ext_modules = [ + setuptools.Extension( + "websockets.speedups", + sources=["src/websockets/speedups.c"], + optional=os.environ.get("BUILD_EXTENSION") != "yes", + ) + ] + +# Static values are declared in pyproject.toml. 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.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - ], - package_dir = {'': 'src'}, - package_data = {'websockets': ['py.typed']}, - packages=packages, ext_modules=ext_modules, - include_package_data=True, - zip_safe=False, - python_requires='>=3.7', - test_loader='unittest:TestLoader', ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/__init__.py index ec34841247b1b..fdb028f4c4f0b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/__init__.py @@ -1,23 +1,24 @@ from __future__ import annotations +import typing + from .imports import lazy_import -from .version import version as __version__ # noqa +from .version import version as __version__ # noqa: F401 -__all__ = [ # noqa +__all__ = [ + # .client + "ClientProtocol", + # .datastructures + "Headers", + "HeadersLike", + "MultipleValuesError", + # .exceptions "AbortHandshake", - "basic_auth_protocol_factory", - "BasicAuthWebSocketServerProtocol", - "broadcast", - "ClientConnection", - "connect", "ConnectionClosed", "ConnectionClosedError", "ConnectionClosedOK", - "Data", "DuplicateParameter", - "ExtensionName", - "ExtensionParameter", "InvalidHandshake", "InvalidHeader", "InvalidHeaderFormat", @@ -31,84 +32,159 @@ "InvalidStatusCode", "InvalidUpgrade", "InvalidURI", - "LoggerLike", "NegotiationError", - "Origin", - "parse_uri", "PayloadTooBig", "ProtocolError", "RedirectHandshake", "SecurityError", - "serve", - "ServerConnection", - "Subprotocol", - "unix_connect", - "unix_serve", - "WebSocketClientProtocol", - "WebSocketCommonProtocol", "WebSocketException", "WebSocketProtocolError", + # .legacy.auth + "BasicAuthWebSocketServerProtocol", + "basic_auth_protocol_factory", + # .legacy.client + "WebSocketClientProtocol", + "connect", + "unix_connect", + # .legacy.protocol + "WebSocketCommonProtocol", + "broadcast", + # .legacy.server "WebSocketServer", "WebSocketServerProtocol", - "WebSocketURI", + "serve", + "unix_serve", + # .server + "ServerProtocol", + # .typing + "Data", + "ExtensionName", + "ExtensionParameter", + "LoggerLike", + "StatusLike", + "Origin", + "Subprotocol", ] -lazy_import( - globals(), - aliases={ - "auth": ".legacy", - "basic_auth_protocol_factory": ".legacy.auth", - "BasicAuthWebSocketServerProtocol": ".legacy.auth", - "broadcast": ".legacy.protocol", - "ClientConnection": ".client", - "connect": ".legacy.client", - "unix_connect": ".legacy.client", - "WebSocketClientProtocol": ".legacy.client", - "Headers": ".datastructures", - "MultipleValuesError": ".datastructures", - "WebSocketException": ".exceptions", - "ConnectionClosed": ".exceptions", - "ConnectionClosedError": ".exceptions", - "ConnectionClosedOK": ".exceptions", - "InvalidHandshake": ".exceptions", - "SecurityError": ".exceptions", - "InvalidMessage": ".exceptions", - "InvalidHeader": ".exceptions", - "InvalidHeaderFormat": ".exceptions", - "InvalidHeaderValue": ".exceptions", - "InvalidOrigin": ".exceptions", - "InvalidUpgrade": ".exceptions", - "InvalidStatus": ".exceptions", - "InvalidStatusCode": ".exceptions", - "NegotiationError": ".exceptions", - "DuplicateParameter": ".exceptions", - "InvalidParameterName": ".exceptions", - "InvalidParameterValue": ".exceptions", - "AbortHandshake": ".exceptions", - "RedirectHandshake": ".exceptions", - "InvalidState": ".exceptions", - "InvalidURI": ".exceptions", - "PayloadTooBig": ".exceptions", - "ProtocolError": ".exceptions", - "WebSocketProtocolError": ".exceptions", - "protocol": ".legacy", - "WebSocketCommonProtocol": ".legacy.protocol", - "ServerConnection": ".server", - "serve": ".legacy.server", - "unix_serve": ".legacy.server", - "WebSocketServerProtocol": ".legacy.server", - "WebSocketServer": ".legacy.server", - "Data": ".typing", - "LoggerLike": ".typing", - "Origin": ".typing", - "ExtensionHeader": ".typing", - "ExtensionParameter": ".typing", - "Subprotocol": ".typing", - }, - deprecated_aliases={ - "framing": ".legacy", - "handshake": ".legacy", - "parse_uri": ".uri", - "WebSocketURI": ".uri", - }, -) +# When type checking, import non-deprecated aliases eagerly. Else, import on demand. +if typing.TYPE_CHECKING: + from .client import ClientProtocol + from .datastructures import Headers, HeadersLike, MultipleValuesError + from .exceptions import ( + AbortHandshake, + ConnectionClosed, + ConnectionClosedError, + ConnectionClosedOK, + DuplicateParameter, + InvalidHandshake, + InvalidHeader, + InvalidHeaderFormat, + InvalidHeaderValue, + InvalidMessage, + InvalidOrigin, + InvalidParameterName, + InvalidParameterValue, + InvalidState, + InvalidStatus, + InvalidStatusCode, + InvalidUpgrade, + InvalidURI, + NegotiationError, + PayloadTooBig, + ProtocolError, + RedirectHandshake, + SecurityError, + WebSocketException, + WebSocketProtocolError, + ) + from .legacy.auth import ( + BasicAuthWebSocketServerProtocol, + basic_auth_protocol_factory, + ) + from .legacy.client import WebSocketClientProtocol, connect, unix_connect + from .legacy.protocol import WebSocketCommonProtocol, broadcast + from .legacy.server import ( + WebSocketServer, + WebSocketServerProtocol, + serve, + unix_serve, + ) + from .server import ServerProtocol + from .typing import ( + Data, + ExtensionName, + ExtensionParameter, + LoggerLike, + Origin, + StatusLike, + Subprotocol, + ) +else: + lazy_import( + globals(), + aliases={ + # .client + "ClientProtocol": ".client", + # .datastructures + "Headers": ".datastructures", + "HeadersLike": ".datastructures", + "MultipleValuesError": ".datastructures", + # .exceptions + "AbortHandshake": ".exceptions", + "ConnectionClosed": ".exceptions", + "ConnectionClosedError": ".exceptions", + "ConnectionClosedOK": ".exceptions", + "DuplicateParameter": ".exceptions", + "InvalidHandshake": ".exceptions", + "InvalidHeader": ".exceptions", + "InvalidHeaderFormat": ".exceptions", + "InvalidHeaderValue": ".exceptions", + "InvalidMessage": ".exceptions", + "InvalidOrigin": ".exceptions", + "InvalidParameterName": ".exceptions", + "InvalidParameterValue": ".exceptions", + "InvalidState": ".exceptions", + "InvalidStatus": ".exceptions", + "InvalidStatusCode": ".exceptions", + "InvalidUpgrade": ".exceptions", + "InvalidURI": ".exceptions", + "NegotiationError": ".exceptions", + "PayloadTooBig": ".exceptions", + "ProtocolError": ".exceptions", + "RedirectHandshake": ".exceptions", + "SecurityError": ".exceptions", + "WebSocketException": ".exceptions", + "WebSocketProtocolError": ".exceptions", + # .legacy.auth + "BasicAuthWebSocketServerProtocol": ".legacy.auth", + "basic_auth_protocol_factory": ".legacy.auth", + # .legacy.client + "WebSocketClientProtocol": ".legacy.client", + "connect": ".legacy.client", + "unix_connect": ".legacy.client", + # .legacy.protocol + "WebSocketCommonProtocol": ".legacy.protocol", + "broadcast": ".legacy.protocol", + # .legacy.server + "WebSocketServer": ".legacy.server", + "WebSocketServerProtocol": ".legacy.server", + "serve": ".legacy.server", + "unix_serve": ".legacy.server", + # .server + "ServerProtocol": ".server", + # .typing + "Data": ".typing", + "ExtensionName": ".typing", + "ExtensionParameter": ".typing", + "LoggerLike": ".typing", + "Origin": ".typing", + "StatusLike": "typing", + "Subprotocol": ".typing", + }, + deprecated_aliases={ + "framing": ".legacy", + "handshake": ".legacy", + "parse_uri": ".uri", + "WebSocketURI": ".uri", + }, + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/__main__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/__main__.py index c562d21b54473..f2ea5cf4e8fe9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/__main__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/__main__.py @@ -1,16 +1,18 @@ from __future__ import annotations import argparse -import asyncio import os import signal import sys import threading -from typing import Any, Set -from .exceptions import ConnectionClosed -from .frames import Close -from .legacy.client import connect + +try: + import readline # noqa: F401 +except ImportError: # Windows has no `readline` normally + pass + +from .sync.client import ClientConnection, connect from .version import version as websockets_version @@ -46,21 +48,6 @@ def win_enable_vt100() -> None: 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. - if sys.platform == "win32": - ctrl_c = signal.CTRL_C_EVENT - else: - ctrl_c = signal.SIGINT - os.kill(os.getpid(), ctrl_c) - - def print_during_input(string: str) -> None: sys.stdout.write( # Save cursor position @@ -93,63 +80,20 @@ def print_over_input(string: str) -> None: 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.create_task(websocket.recv()) - outgoing: asyncio.Future[Any] = asyncio.create_task(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() - assert websocket.close_code is not None and websocket.close_reason is not None - close_status = Close(websocket.close_code, websocket.close_reason) - - print_over_input(f"Connection closed: {close_status}.") - - exit_from_event_loop_thread(loop, stop) +def print_incoming_messages(websocket: ClientConnection, stop: threading.Event) -> None: + for message in websocket: + if isinstance(message, str): + print_during_input("< " + message) + else: + print_during_input("< (binary) " + message.hex()) + if not stop.is_set(): + # When the server closes the connection, raise KeyboardInterrupt + # in the main thread to exit the program. + if sys.platform == "win32": + ctrl_c = signal.CTRL_C_EVENT + else: + ctrl_c = signal.SIGINT + os.kill(os.getpid(), ctrl_c) def main() -> None: @@ -184,29 +128,17 @@ def main() -> None: sys.stderr.flush() try: - import readline # noqa - except ImportError: # Windows has no `readline` normally - pass - - # Create an event loop that will run in a background thread. - loop = asyncio.new_event_loop() - - # Due to zealous removal of the loop parameter in the Queue constructor, - # we need a factory coroutine to run in the freshly created event loop. - async def queue_factory() -> asyncio.Queue[str]: - return asyncio.Queue() - - # Create a queue of user inputs. There's no need to limit its size. - inputs: asyncio.Queue[str] = loop.run_until_complete(queue_factory()) - - # Create a stop condition when receiving SIGINT or SIGTERM. - stop: asyncio.Future[None] = loop.create_future() + websocket = connect(args.uri) + except Exception as exc: + print(f"Failed to connect to {args.uri}: {exc}.") + sys.exit(1) + else: + print(f"Connected to {args.uri}.") - # Schedule the task that will manage the connection. - loop.create_task(run_client(args.uri, loop, inputs, stop)) + stop = threading.Event() - # Start the event loop in a background thread. - thread = threading.Thread(target=loop.run_forever) + # Start the thread that reads messages from the connection. + thread = threading.Thread(target=print_incoming_messages, args=(websocket, stop)) thread.start() # Read from stdin in the main thread in order to receive signals. @@ -214,17 +146,14 @@ async def queue_factory() -> asyncio.Queue[str]: while True: # Since there's no size limit, put_nowait is identical to put. message = input("> ") - loop.call_soon_threadsafe(inputs.put_nowait, message) + websocket.send(message) except (KeyboardInterrupt, EOFError): # ^C, ^D - loop.call_soon_threadsafe(stop.set_result, None) + stop.set() + websocket.close() + print_over_input("Connection closed.") - # Wait for the event loop to terminate. thread.join() - # For reasons unclear, even though the loop is closed in the thread, - # it still thinks it's running here. - loop.close() - if __name__ == "__main__": main() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/auth.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/auth.py index afcb38cffe2f4..b792e02f5ccc9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/auth.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/auth.py @@ -1,4 +1,6 @@ from __future__ import annotations # See #940 for why lazy_import isn't used here for backwards compatibility. -from .legacy.auth import * # noqa +# See #1400 for why listing compatibility imports in __all__ helps PyCharm. +from .legacy.auth import * +from .legacy.auth import __all__ # noqa: F401 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/client.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/client.py index df8e53429abaf..b2f622042df8d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/client.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/client.py @@ -1,8 +1,8 @@ from __future__ import annotations -from typing import Generator, List, Optional, Sequence +import warnings +from typing import Any, Generator, List, Optional, Sequence -from .connection import CLIENT, CONNECTING, OPEN, Connection, State from .datastructures import Headers, MultipleValuesError from .exceptions import ( InvalidHandshake, @@ -23,8 +23,8 @@ parse_subprotocol, parse_upgrade, ) -from .http import USER_AGENT from .http11 import Request, Response +from .protocol import CLIENT, CONNECTING, OPEN, Protocol, State from .typing import ( ConnectionOption, ExtensionHeader, @@ -38,13 +38,15 @@ # See #940 for why lazy_import isn't used here for backwards compatibility. -from .legacy.client import * # isort:skip # noqa +# See #1400 for why listing compatibility imports in __all__ helps PyCharm. +from .legacy.client import * # isort:skip # noqa: I001 +from .legacy.client import __all__ as legacy__all__ -__all__ = ["ClientConnection"] +__all__ = ["ClientProtocol"] + legacy__all__ -class ClientConnection(Connection): +class ClientProtocol(Protocol): """ Sans-I/O implementation of a WebSocket client connection. @@ -60,16 +62,17 @@ class ClientConnection(Connection): preference. state: initial state of the WebSocket connection. max_size: maximum size of incoming messages in bytes; - :obj:`None` to disable the limit. + :obj:`None` disables the limit. logger: logger for this connection; defaults to ``logging.getLogger("websockets.client")``; - see the :doc:`logging guide <../topics/logging>` for details. + see the :doc:`logging guide <../../topics/logging>` for details. """ def __init__( self, wsuri: WebSocketURI, + *, origin: Optional[Origin] = None, extensions: Optional[Sequence[ClientExtensionFactory]] = None, subprotocols: Optional[Sequence[Subprotocol]] = None, @@ -89,7 +92,7 @@ def __init__( self.available_subprotocols = subprotocols self.key = generate_key() - def connect(self) -> Request: # noqa: F811 + def connect(self) -> Request: """ Create a handshake request to open a connection. @@ -131,8 +134,6 @@ def connect(self) -> Request: # noqa: F811 protocol_header = build_subprotocol(self.available_subprotocols) headers["Sec-WebSocket-Protocol"] = protocol_header - headers["User-Agent"] = USER_AGENT - return Request(self.wsuri.resource_name, headers) def process_response(self, response: Response) -> None: @@ -223,7 +224,6 @@ def process_extensions(self, headers: Headers) -> List[Extension]: extensions = headers.get_all("Sec-WebSocket-Extensions") if extensions: - if self.available_extensions is None: raise InvalidHandshake("no extensions supported") @@ -232,9 +232,7 @@ def process_extensions(self, headers: Headers) -> List[Extension]: ) for name, response_params in parsed_extensions: - for extension_factory in self.available_extensions: - # Skip non-matching extensions based on their name. if extension_factory.name != name: continue @@ -281,7 +279,6 @@ def process_subprotocol(self, headers: Headers) -> Optional[Subprotocol]: subprotocols = headers.get_all("Sec-WebSocket-Protocol") if subprotocols: - if self.available_subprotocols is None: raise InvalidHandshake("no subprotocols supported") @@ -317,11 +314,17 @@ def send_request(self, request: Request) -> None: def parse(self) -> Generator[None, None, None]: if self.state is CONNECTING: - response = yield from Response.parse( - self.reader.read_line, - self.reader.read_exact, - self.reader.read_to_eof, - ) + try: + response = yield from Response.parse( + self.reader.read_line, + self.reader.read_exact, + self.reader.read_to_eof, + ) + except Exception as exc: + self.handshake_exc = exc + self.parser = self.discard() + next(self.parser) # start coroutine + yield if self.debug: code, phrase = response.status_code, response.reason_phrase @@ -335,13 +338,23 @@ def parse(self) -> Generator[None, None, None]: self.process_response(response) except InvalidHandshake as exc: response._exception = exc + self.events.append(response) self.handshake_exc = exc self.parser = self.discard() next(self.parser) # start coroutine - else: - assert self.state is CONNECTING - self.state = OPEN - finally: - self.events.append(response) + yield + + assert self.state is CONNECTING + self.state = OPEN + self.events.append(response) yield from super().parse() + + +class ClientConnection(ClientProtocol): + def __init__(self, *args: Any, **kwargs: Any) -> None: + warnings.warn( + "ClientConnection was renamed to ClientProtocol", + DeprecationWarning, + ) + super().__init__(*args, **kwargs) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/connection.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/connection.py index db8b5369935e1..88bcda1aafe7e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/connection.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/connection.py @@ -1,702 +1,13 @@ from __future__ import annotations -import enum -import logging -import uuid -from typing import Generator, List, Optional, Type, Union +import warnings -from .exceptions import ( - ConnectionClosed, - ConnectionClosedError, - ConnectionClosedOK, - InvalidState, - PayloadTooBig, - ProtocolError, -) -from .extensions import Extension -from .frames import ( - OK_CLOSE_CODES, - OP_BINARY, - OP_CLOSE, - OP_CONT, - OP_PING, - OP_PONG, - OP_TEXT, - Close, - Frame, -) -from .http11 import Request, Response -from .streams import StreamReader -from .typing import LoggerLike, Origin, Subprotocol - - -__all__ = [ - "Connection", - "Side", - "State", - "SEND_EOF", -] - -Event = Union[Request, Response, Frame] -"""Events that :meth:`~Connection.events_received` may return.""" - - -class Side(enum.IntEnum): - """A WebSocket connection is either a server or a client.""" - - SERVER, CLIENT = range(2) - - -SERVER = Side.SERVER -CLIENT = Side.CLIENT - - -class State(enum.IntEnum): - """A WebSocket connection is in one of these four states.""" - - CONNECTING, OPEN, CLOSING, CLOSED = range(4) - - -CONNECTING = State.CONNECTING -OPEN = State.OPEN -CLOSING = State.CLOSING -CLOSED = State.CLOSED - - -SEND_EOF = b"" -"""Sentinel signaling that the TCP connection must be half-closed.""" - - -class Connection: - """ - Sans-I/O implementation of a WebSocket connection. - - Args: - side: :attr:`~Side.CLIENT` or :attr:`~Side.SERVER`. - state: initial state of the WebSocket connection. - max_size: maximum size of incoming messages in bytes; - :obj:`None` to disable the limit. - logger: logger for this connection; depending on ``side``, - defaults to ``logging.getLogger("websockets.client")`` - or ``logging.getLogger("websockets.server")``; - see the :doc:`logging guide <../topics/logging>` for details. - - """ - - def __init__( - self, - side: Side, - state: State = OPEN, - max_size: Optional[int] = 2**20, - logger: Optional[LoggerLike] = None, - ) -> None: - # Unique identifier. For logs. - self.id: uuid.UUID = uuid.uuid4() - """Unique identifier of the connection. Useful in logs.""" - - # Logger or LoggerAdapter for this connection. - if logger is None: - logger = logging.getLogger(f"websockets.{side.name.lower()}") - self.logger: LoggerLike = logger - """Logger for this connection.""" - - # Track if DEBUG is enabled. Shortcut logging calls if it isn't. - self.debug = logger.isEnabledFor(logging.DEBUG) - - # Connection side. CLIENT or SERVER. - self.side = side - - # Connection state. Initially OPEN because subclasses handle CONNECTING. - self.state = state - - # Maximum size of incoming messages in bytes. - self.max_size = max_size - - # Current size of incoming message in bytes. Only set while reading a - # fragmented message i.e. a data frames with the FIN bit not set. - self.cur_size: Optional[int] = None - - # True while sending a fragmented message i.e. a data frames with the - # FIN bit not set. - self.expect_continuation_frame = False - - # WebSocket protocol parameters. - self.origin: Optional[Origin] = None - self.extensions: List[Extension] = [] - self.subprotocol: Optional[Subprotocol] = None - - # Close code and reason, set when a close frame is sent or received. - self.close_rcvd: Optional[Close] = None - self.close_sent: Optional[Close] = None - self.close_rcvd_then_sent: Optional[bool] = None - - # Track if an exception happened during the handshake. - self.handshake_exc: Optional[Exception] = None - """ - Exception to raise if the opening handshake failed. - - :obj:`None` if the opening handshake succeeded. - - """ - - # Track if send_eof() was called. - self.eof_sent = False - - # Parser state. - self.reader = StreamReader() - self.events: List[Event] = [] - self.writes: List[bytes] = [] - self.parser = self.parse() - next(self.parser) # start coroutine - self.parser_exc: Optional[Exception] = None - - @property - def state(self) -> State: - """ - WebSocket connection state. - - Defined in 4.1, 4.2, 7.1.3, and 7.1.4 of :rfc:`6455`. - - """ - return self._state - - @state.setter - def state(self, state: State) -> None: - if self.debug: - self.logger.debug("= connection is %s", state.name) - self._state = state - - @property - def close_code(self) -> Optional[int]: - """ - `WebSocket close code`_. - - .. _WebSocket close code: - https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5 - - :obj:`None` if the connection isn't closed yet. - - """ - if self.state is not CLOSED: - return None - elif self.close_rcvd is None: - return 1006 - else: - return self.close_rcvd.code - - @property - def close_reason(self) -> Optional[str]: - """ - `WebSocket close reason`_. - - .. _WebSocket close reason: - https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.6 - - :obj:`None` if the connection isn't closed yet. - - """ - if self.state is not CLOSED: - return None - elif self.close_rcvd is None: - return "" - else: - return self.close_rcvd.reason - - @property - def close_exc(self) -> ConnectionClosed: - """ - Exception to raise when trying to interact with a closed connection. - - Don't raise this exception while the connection :attr:`state` - is :attr:`~websockets.connection.State.CLOSING`; wait until - it's :attr:`~websockets.connection.State.CLOSED`. - - Indeed, the exception includes the close code and reason, which are - known only once the connection is closed. - - Raises: - AssertionError: if the connection isn't closed yet. - - """ - assert self.state is CLOSED, "connection isn't closed yet" - exc_type: Type[ConnectionClosed] - if ( - self.close_rcvd is not None - and self.close_sent is not None - and self.close_rcvd.code in OK_CLOSE_CODES - and self.close_sent.code in OK_CLOSE_CODES - ): - exc_type = ConnectionClosedOK - else: - exc_type = ConnectionClosedError - exc: ConnectionClosed = exc_type( - self.close_rcvd, - self.close_sent, - self.close_rcvd_then_sent, - ) - # Chain to the exception raised in the parser, if any. - exc.__cause__ = self.parser_exc - return exc - - # Public methods for receiving data. - - def receive_data(self, data: bytes) -> None: - """ - Receive data from the network. - - After calling this method: - - - You must call :meth:`data_to_send` and send this data to the network. - - You should call :meth:`events_received` and process resulting events. - - Raises: - EOFError: if :meth:`receive_eof` was called earlier. - - """ - self.reader.feed_data(data) - next(self.parser) - - def receive_eof(self) -> None: - """ - Receive the end of the data stream from the network. - - After calling this method: - - - You must call :meth:`data_to_send` and send this data to the network. - - You aren't expected to call :meth:`events_received`; it won't return - any new events. - - Raises: - EOFError: if :meth:`receive_eof` was called earlier. - - """ - self.reader.feed_eof() - next(self.parser) - - # Public methods for sending events. - - def send_continuation(self, data: bytes, fin: bool) -> None: - """ - Send a `Continuation frame`_. - - .. _Continuation frame: - https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 - - Parameters: - data: payload containing the same kind of data - as the initial frame. - fin: FIN bit; set it to :obj:`True` if this is the last frame - of a fragmented message and to :obj:`False` otherwise. - - Raises: - ProtocolError: if a fragmented message isn't in progress. - - """ - if not self.expect_continuation_frame: - raise ProtocolError("unexpected continuation frame") - self.expect_continuation_frame = not fin - self.send_frame(Frame(OP_CONT, data, fin)) - - def send_text(self, data: bytes, fin: bool = True) -> None: - """ - Send a `Text frame`_. - - .. _Text frame: - https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 - - Parameters: - data: payload containing text encoded with UTF-8. - fin: FIN bit; set it to :obj:`False` if this is the first frame of - a fragmented message. - - Raises: - ProtocolError: if a fragmented message is in progress. - - """ - if self.expect_continuation_frame: - raise ProtocolError("expected a continuation frame") - self.expect_continuation_frame = not fin - self.send_frame(Frame(OP_TEXT, data, fin)) - - def send_binary(self, data: bytes, fin: bool = True) -> None: - """ - Send a `Binary frame`_. +# lazy_import doesn't support this use case. +from .protocol import SEND_EOF, Protocol as Connection, Side, State # noqa: F401 - .. _Binary frame: - https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 - Parameters: - data: payload containing arbitrary binary data. - fin: FIN bit; set it to :obj:`False` if this is the first frame of - a fragmented message. - - Raises: - ProtocolError: if a fragmented message is in progress. - - """ - if self.expect_continuation_frame: - raise ProtocolError("expected a continuation frame") - self.expect_continuation_frame = not fin - self.send_frame(Frame(OP_BINARY, data, fin)) - - def send_close(self, code: Optional[int] = None, reason: str = "") -> None: - """ - Send a `Close frame`_. - - .. _Close frame: - https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.1 - - Parameters: - code: close code. - reason: close reason. - - Raises: - ProtocolError: if a fragmented message is being sent, if the code - isn't valid, or if a reason is provided without a code - - """ - if self.expect_continuation_frame: - raise ProtocolError("expected a continuation frame") - if code is None: - if reason != "": - raise ProtocolError("cannot send a reason without a code") - close = Close(1005, "") - data = b"" - else: - close = Close(code, reason) - data = close.serialize() - # send_frame() guarantees that self.state is OPEN at this point. - # 7.1.3. The WebSocket Closing Handshake is Started - self.send_frame(Frame(OP_CLOSE, data)) - self.close_sent = close - self.state = CLOSING - - def send_ping(self, data: bytes) -> None: - """ - Send a `Ping frame`_. - - .. _Ping frame: - https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2 - - Parameters: - data: payload containing arbitrary binary data. - - """ - self.send_frame(Frame(OP_PING, data)) - - def send_pong(self, data: bytes) -> None: - """ - Send a `Pong frame`_. - - .. _Pong frame: - https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.3 - - Parameters: - data: payload containing arbitrary binary data. - - """ - self.send_frame(Frame(OP_PONG, data)) - - def fail(self, code: int, reason: str = "") -> None: - """ - `Fail the WebSocket connection`_. - - .. _Fail the WebSocket connection: - https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.7 - - Parameters: - code: close code - reason: close reason - - Raises: - ProtocolError: if the code isn't valid. - """ - # 7.1.7. Fail the WebSocket Connection - - # 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. - if self.state is OPEN: - if code != 1006: - close = Close(code, reason) - data = close.serialize() - self.send_frame(Frame(OP_CLOSE, data)) - self.close_sent = close - self.state = CLOSING - - # When failing the connection, a server closes the TCP connection - # without waiting for the client to complete the handshake, while a - # client waits for the server to close the TCP connection, possibly - # after sending a close frame that the client will ignore. - if self.side is SERVER and not self.eof_sent: - self.send_eof() - - # 7.1.7. Fail the WebSocket Connection "An endpoint MUST NOT continue - # to attempt to process data(including a responding Close frame) from - # the remote endpoint after being instructed to _Fail the WebSocket - # Connection_." - self.parser = self.discard() - next(self.parser) # start coroutine - - # Public method for getting incoming events after receiving data. - - def events_received(self) -> List[Event]: - """ - Fetch events generated from data received from the network. - - Call this method immediately after any of the ``receive_*()`` methods. - - Process resulting events, likely by passing them to the application. - - Returns: - List[Event]: Events read from the connection. - """ - events, self.events = self.events, [] - return events - - # Public method for getting outgoing data after receiving data or sending events. - - def data_to_send(self) -> List[bytes]: - """ - Obtain data to send to the network. - - Call this method immediately after any of the ``receive_*()``, - ``send_*()``, or :meth:`fail` methods. - - Write resulting data to the connection. - - The empty bytestring :data:`~websockets.connection.SEND_EOF` signals - the end of the data stream. When you receive it, half-close the TCP - connection. - - Returns: - List[bytes]: Data to write to the connection. - - """ - writes, self.writes = self.writes, [] - return writes - - def close_expected(self) -> bool: - """ - Tell if the TCP connection is expected to close soon. - - Call this method immediately after any of the ``receive_*()`` or - :meth:`fail` methods. - - If it returns :obj:`True`, schedule closing the TCP connection after a - short timeout if the other side hasn't already closed it. - - Returns: - bool: Whether the TCP connection is expected to close soon. - - """ - # We expect a TCP close if and only if we sent a close frame: - # * Normal closure: once we send a close frame, we expect a TCP close: - # server waits for client to complete the TCP closing handshake; - # client waits for server to initiate the TCP closing handshake. - # * Abnormal closure: we always send a close frame and the same logic - # applies, except on EOFError where we don't send a close frame - # because we already received the TCP close, so we don't expect it. - # We already got a TCP Close if and only if the state is CLOSED. - return self.state is CLOSING or self.handshake_exc is not None - - # Private methods for receiving data. - - def parse(self) -> Generator[None, None, None]: - """ - Parse incoming data into frames. - - :meth:`receive_data` and :meth:`receive_eof` run this generator - coroutine until it needs more data or reaches EOF. - - """ - try: - while True: - if (yield from self.reader.at_eof()): - if self.debug: - self.logger.debug("< EOF") - # If the WebSocket connection is closed cleanly, with a - # closing handhshake, recv_frame() substitutes parse() - # with discard(). This branch is reached only when the - # connection isn't closed cleanly. - raise EOFError("unexpected end of stream") - - if self.max_size is None: - max_size = None - elif self.cur_size is None: - max_size = self.max_size - else: - max_size = self.max_size - self.cur_size - - # During a normal closure, execution ends here on the next - # iteration of the loop after receiving a close frame. At - # this point, recv_frame() replaced parse() by discard(). - frame = yield from Frame.parse( - self.reader.read_exact, - mask=self.side is SERVER, - max_size=max_size, - extensions=self.extensions, - ) - - if self.debug: - self.logger.debug("< %s", frame) - - self.recv_frame(frame) - - except ProtocolError as exc: - self.fail(1002, str(exc)) - self.parser_exc = exc - - except EOFError as exc: - self.fail(1006, str(exc)) - self.parser_exc = exc - - except UnicodeDecodeError as exc: - self.fail(1007, f"{exc.reason} at position {exc.start}") - self.parser_exc = exc - - except PayloadTooBig as exc: - self.fail(1009, str(exc)) - self.parser_exc = exc - - except Exception as exc: - self.logger.error("parser failed", exc_info=True) - # Don't include exception details, which may be security-sensitive. - self.fail(1011) - self.parser_exc = exc - - # During an abnormal closure, execution ends here after catching an - # exception. At this point, fail() replaced parse() by discard(). - yield - raise AssertionError("parse() shouldn't step after error") # pragma: no cover - - def discard(self) -> Generator[None, None, None]: - """ - Discard incoming data. - - This coroutine replaces :meth:`parse`: - - - after receiving a close frame, during a normal closure (1.4); - - after sending a close frame, during an abnormal closure (7.1.7). - - """ - # The server close the TCP connection in the same circumstances where - # discard() replaces parse(). The client closes the connection later, - # after the server closes the connection or a timeout elapses. - # (The latter case cannot be handled in this Sans-I/O layer.) - assert (self.side is SERVER) == (self.eof_sent) - while not (yield from self.reader.at_eof()): - self.reader.discard() - if self.debug: - self.logger.debug("< EOF") - # A server closes the TCP connection immediately, while a client - # waits for the server to close the TCP connection. - if self.side is CLIENT: - self.send_eof() - self.state = CLOSED - # If discard() completes normally, execution ends here. - yield - # Once the reader reaches EOF, its feed_data/eof() methods raise an - # error, so our receive_data/eof() methods don't step the generator. - raise AssertionError("discard() shouldn't step after EOF") # pragma: no cover - - def recv_frame(self, frame: Frame) -> None: - """ - Process an incoming frame. - - """ - if frame.opcode is OP_TEXT or frame.opcode is OP_BINARY: - if self.cur_size is not None: - raise ProtocolError("expected a continuation frame") - if frame.fin: - self.cur_size = None - else: - self.cur_size = len(frame.data) - - elif frame.opcode is OP_CONT: - if self.cur_size is None: - raise ProtocolError("unexpected continuation frame") - if frame.fin: - self.cur_size = None - else: - self.cur_size += len(frame.data) - - elif frame.opcode is OP_PING: - # 5.5.2. Ping: "Upon receipt of a Ping frame, an endpoint MUST - # send a Pong frame in response" - pong_frame = Frame(OP_PONG, frame.data) - self.send_frame(pong_frame) - - elif frame.opcode is OP_PONG: - # 5.5.3 Pong: "A response to an unsolicited Pong frame is not - # expected." - pass - - elif frame.opcode is OP_CLOSE: - # 7.1.5. The WebSocket Connection Close Code - # 7.1.6. The WebSocket Connection Close Reason - self.close_rcvd = Close.parse(frame.data) - if self.state is CLOSING: - assert self.close_sent is not None - self.close_rcvd_then_sent = False - - if self.cur_size is not None: - raise ProtocolError("incomplete fragmented message") - - # 5.5.1 Close: "If an endpoint receives a Close frame and did - # not previously send a Close frame, the endpoint MUST send a - # Close frame in response. (When sending a Close frame in - # response, the endpoint typically echos the status code it - # received.)" - - if self.state is OPEN: - # Echo the original data instead of re-serializing it with - # Close.serialize() because that fails when the close frame - # is empty and Close.parse() synthetizes a 1005 close code. - # The rest is identical to send_close(). - self.send_frame(Frame(OP_CLOSE, frame.data)) - self.close_sent = self.close_rcvd - self.close_rcvd_then_sent = True - self.state = CLOSING - - # 7.1.2. Start the WebSocket Closing Handshake: "Once an - # endpoint has both sent and received a Close control frame, - # that endpoint SHOULD _Close the WebSocket Connection_" - - # A server closes the TCP connection immediately, while a client - # waits for the server to close the TCP connection. - if self.side is SERVER: - self.send_eof() - - # 1.4. Closing Handshake: "after receiving a control frame - # indicating the connection should be closed, a peer discards - # any further data received." - self.parser = self.discard() - next(self.parser) # start coroutine - - else: # pragma: no cover - # This can't happen because Frame.parse() validates opcodes. - raise AssertionError(f"unexpected opcode: {frame.opcode:02x}") - - self.events.append(frame) - - # Private methods for sending events. - - def send_frame(self, frame: Frame) -> None: - if self.state is not OPEN: - raise InvalidState( - f"cannot write to a WebSocket in the {self.state.name} state" - ) - - if self.debug: - self.logger.debug("> %s", frame) - self.writes.append( - frame.serialize(mask=self.side is CLIENT, extensions=self.extensions) - ) - - def send_eof(self) -> None: - assert not self.eof_sent - self.eof_sent = True - if self.debug: - self.logger.debug("> EOF") - self.writes.append(SEND_EOF) +warnings.warn( + "websockets.connection was renamed to websockets.protocol " + "and Connection was renamed to Protocol", + DeprecationWarning, +) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/datastructures.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/datastructures.py index 36a2cbaf99cee..a0a648463adb0 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/datastructures.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/datastructures.py @@ -1,6 +1,5 @@ from __future__ import annotations -import sys from typing import ( Any, Dict, @@ -9,17 +8,12 @@ List, Mapping, MutableMapping, + Protocol, Tuple, Union, ) -if sys.version_info[:2] >= (3, 8): - from typing import Protocol -else: # pragma: no cover - Protocol = object # mypy will report errors on Python 3.7. - - __all__ = ["Headers", "HeadersLike", "MultipleValuesError"] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/exceptions.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/exceptions.py index 0c4fc51851a59..f7169e3b178ae 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/exceptions.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/exceptions.py @@ -34,6 +34,7 @@ from typing import Optional from . import datastructures, frames, http11 +from .typing import StatusLike __all__ = [ @@ -120,19 +121,23 @@ def __str__(self) -> str: @property def code(self) -> int: - return 1006 if self.rcvd is None else self.rcvd.code + if self.rcvd is None: + return frames.CloseCode.ABNORMAL_CLOSURE + return self.rcvd.code @property def reason(self) -> str: - return "" if self.rcvd is None else self.rcvd.reason + if self.rcvd is None: + return "" + return self.rcvd.reason class ConnectionClosedError(ConnectionClosed): """ Like :exc:`ConnectionClosed`, when the connection terminated with an error. - A close code other than 1000 (OK) or 1001 (going away) was received or - sent, or the closing handshake didn't complete properly. + A close frame with a code other than 1000 (OK) or 1001 (going away) was + received or sent, or the closing handshake didn't complete properly. """ @@ -141,7 +146,8 @@ class ConnectionClosedOK(ConnectionClosed): """ Like :exc:`ConnectionClosed`, when the connection terminated properly. - A close code 1000 (OK) or 1001 (going away) was received and sent. + A close code with code 1000 (OK) or 1001 (going away) or without a code was + received and sent. """ @@ -171,7 +177,7 @@ class InvalidMessage(InvalidHandshake): class InvalidHeader(InvalidHandshake): """ - Raised when a HTTP header doesn't have a valid format or value. + Raised when an HTTP header doesn't have a valid format or value. """ @@ -190,7 +196,7 @@ def __str__(self) -> str: class InvalidHeaderFormat(InvalidHeader): """ - Raised when a HTTP header cannot be parsed. + Raised when an HTTP header cannot be parsed. The format of the header doesn't match the grammar for that header. @@ -202,7 +208,7 @@ def __init__(self, name: str, error: str, header: str, pos: int) -> None: class InvalidHeaderValue(InvalidHeader): """ - Raised when a HTTP header has a wrong value. + Raised when an HTTP header has a wrong value. The format of the header is correct but a value isn't acceptable. @@ -310,7 +316,7 @@ def __str__(self) -> str: class AbortHandshake(InvalidHandshake): """ - Raised to abort the handshake on purpose and return a HTTP response. + Raised to abort the handshake on purpose and return an HTTP response. This exception is an implementation detail. @@ -325,11 +331,12 @@ class AbortHandshake(InvalidHandshake): def __init__( self, - status: http.HTTPStatus, + status: StatusLike, headers: datastructures.HeadersLike, body: bytes = b"", ) -> None: - self.status = status + # If a user passes an int instead of a HTTPStatus, fix it automatically. + self.status = http.HTTPStatus(status) self.headers = datastructures.Headers(headers) self.body = body @@ -369,7 +376,7 @@ class InvalidState(WebSocketException, AssertionError): class InvalidURI(WebSocketException): """ - Raised when connecting to an URI that isn't a valid WebSocket URI. + Raised when connecting to a URI that isn't a valid WebSocket URI. """ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/base.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/base.py index 0609676185785..6c481a46cc74c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/base.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/base.py @@ -38,6 +38,7 @@ def decode( PayloadTooBig: if decoding the payload exceeds ``max_size``. """ + raise NotImplementedError def encode(self, frame: frames.Frame) -> frames.Frame: """ @@ -50,6 +51,7 @@ def encode(self, frame: frames.Frame) -> frames.Frame: Frame: Encoded frame. """ + raise NotImplementedError class ClientExtensionFactory: @@ -69,6 +71,7 @@ def get_request_params(self) -> List[ExtensionParameter]: List[ExtensionParameter]: Parameters to send to the server. """ + raise NotImplementedError def process_response_params( self, @@ -91,6 +94,7 @@ def process_response_params( NegotiationError: if parameters aren't acceptable. """ + raise NotImplementedError class ServerExtensionFactory: @@ -126,3 +130,4 @@ def process_request_params( the client aren't acceptable. """ + raise NotImplementedError diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/permessage_deflate.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/permessage_deflate.py index e0de5e8f85505..b391837c66686 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/permessage_deflate.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/permessage_deflate.py @@ -211,7 +211,6 @@ def _extract_parameters( 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 exceptions.DuplicateParameter(name) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/w3c-import.log new file mode 100644 index 0000000000000..d9d6f80188e87 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/base.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/permessage_deflate.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/frames.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/frames.py index 043b688b5227b..6b1befb2e035c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/frames.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/frames.py @@ -13,7 +13,7 @@ try: from .speedups import apply_mask -except ImportError: # pragma: no cover +except ImportError: from .utils import apply_mask @@ -52,45 +52,70 @@ class Opcode(enum.IntEnum): CTRL_OPCODES = OP_CLOSE, OP_PING, OP_PONG -# See https://www.iana.org/assignments/websocket/websocket.xhtml -CLOSE_CODES = { - 1000: "OK", - 1001: "going away", - 1002: "protocol error", - 1003: "unsupported type", +class CloseCode(enum.IntEnum): + """Close code values for WebSocket close frames.""" + + NORMAL_CLOSURE = 1000 + GOING_AWAY = 1001 + PROTOCOL_ERROR = 1002 + UNSUPPORTED_DATA = 1003 # 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", - 1012: "service restart", - 1013: "try again later", - 1014: "bad gateway", - 1015: "TLS failure [internal]", + NO_STATUS_RCVD = 1005 + ABNORMAL_CLOSURE = 1006 + INVALID_DATA = 1007 + POLICY_VIOLATION = 1008 + MESSAGE_TOO_BIG = 1009 + MANDATORY_EXTENSION = 1010 + INTERNAL_ERROR = 1011 + SERVICE_RESTART = 1012 + TRY_AGAIN_LATER = 1013 + BAD_GATEWAY = 1014 + TLS_HANDSHAKE = 1015 + + +# See https://www.iana.org/assignments/websocket/websocket.xhtml +CLOSE_CODE_EXPLANATIONS: dict[int, str] = { + CloseCode.NORMAL_CLOSURE: "OK", + CloseCode.GOING_AWAY: "going away", + CloseCode.PROTOCOL_ERROR: "protocol error", + CloseCode.UNSUPPORTED_DATA: "unsupported data", + CloseCode.NO_STATUS_RCVD: "no status received [internal]", + CloseCode.ABNORMAL_CLOSURE: "abnormal closure [internal]", + CloseCode.INVALID_DATA: "invalid frame payload data", + CloseCode.POLICY_VIOLATION: "policy violation", + CloseCode.MESSAGE_TOO_BIG: "message too big", + CloseCode.MANDATORY_EXTENSION: "mandatory extension", + CloseCode.INTERNAL_ERROR: "internal error", + CloseCode.SERVICE_RESTART: "service restart", + CloseCode.TRY_AGAIN_LATER: "try again later", + CloseCode.BAD_GATEWAY: "bad gateway", + CloseCode.TLS_HANDSHAKE: "TLS handshake failure [internal]", } # Close code that are allowed in a close frame. # Using a set optimizes `code in EXTERNAL_CLOSE_CODES`. EXTERNAL_CLOSE_CODES = { - 1000, - 1001, - 1002, - 1003, - 1007, - 1008, - 1009, - 1010, - 1011, - 1012, - 1013, - 1014, + CloseCode.NORMAL_CLOSURE, + CloseCode.GOING_AWAY, + CloseCode.PROTOCOL_ERROR, + CloseCode.UNSUPPORTED_DATA, + CloseCode.INVALID_DATA, + CloseCode.POLICY_VIOLATION, + CloseCode.MESSAGE_TOO_BIG, + CloseCode.MANDATORY_EXTENSION, + CloseCode.INTERNAL_ERROR, + CloseCode.SERVICE_RESTART, + CloseCode.TRY_AGAIN_LATER, + CloseCode.BAD_GATEWAY, } -OK_CLOSE_CODES = {1000, 1001} + +OK_CLOSE_CODES = { + CloseCode.NORMAL_CLOSURE, + CloseCode.GOING_AWAY, + CloseCode.NO_STATUS_RCVD, +} BytesLike = bytes, bytearray, memoryview @@ -123,7 +148,7 @@ class Frame: def __str__(self) -> str: """ - Return a human-readable represention of a frame. + Return a human-readable representation of a frame. """ coding = None @@ -191,6 +216,8 @@ def parse( extensions: list of extensions, applied in reverse order. Raises: + EOFError: if the connection is closed without a full WebSocket frame. + UnicodeDecodeError: if the frame contains invalid UTF-8. PayloadTooBig: if the frame's payload size exceeds ``max_size``. ProtocolError: if the frame contains incorrect values. @@ -383,7 +410,7 @@ class Close: def __str__(self) -> str: """ - Return a human-readable represention of a close code and reason. + Return a human-readable representation of a close code and reason. """ if 3000 <= self.code < 4000: @@ -391,7 +418,7 @@ def __str__(self) -> str: elif 4000 <= self.code < 5000: explanation = "private use" else: - explanation = CLOSE_CODES.get(self.code, "unknown") + explanation = CLOSE_CODE_EXPLANATIONS.get(self.code, "unknown") result = f"{self.code} ({explanation})" if self.reason: @@ -419,7 +446,7 @@ def parse(cls, data: bytes) -> Close: close.check() return close elif len(data) == 0: - return cls(1005, "") + return cls(CloseCode.NO_STATUS_RCVD, "") else: raise exceptions.ProtocolError("close frame too short") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/http.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/http.py index b14fa94bdce76..9f86f6a1ffa6b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/http.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/http.py @@ -1,6 +1,7 @@ from __future__ import annotations import sys +import typing from .imports import lazy_import from .version import version as websockets_version @@ -9,18 +10,22 @@ # For backwards compatibility: -lazy_import( - globals(), - # Headers and MultipleValuesError used to be defined in this module. - aliases={ - "Headers": ".datastructures", - "MultipleValuesError": ".datastructures", - }, - deprecated_aliases={ - "read_request": ".legacy.http", - "read_response": ".legacy.http", - }, -) +# When type checking, import non-deprecated aliases eagerly. Else, import on demand. +if typing.TYPE_CHECKING: + from .datastructures import Headers, MultipleValuesError # noqa: F401 +else: + lazy_import( + globals(), + # Headers and MultipleValuesError used to be defined in this module. + aliases={ + "Headers": ".datastructures", + "MultipleValuesError": ".datastructures", + }, + deprecated_aliases={ + "read_request": ".legacy.http", + "read_response": ".legacy.http", + }, + ) __all__ = ["USER_AGENT"] diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/http11.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/http11.py index 84048fa47b3dc..ec4e3b8b7d5b8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/http11.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/http11.py @@ -8,14 +8,12 @@ from . import datastructures, exceptions -# Maximum total size of headers is around 256 * 4 KiB = 1 MiB -MAX_HEADERS = 256 +# Maximum total size of headers is around 128 * 8 KiB = 1 MiB. +MAX_HEADERS = 128 -# We can use the same limit for the request line and header lines: -# "GET <4096 bytes> HTTP/1.1\r\n" = 4111 bytes -# "Set-Cookie: <4097 bytes>\r\n" = 4111 bytes -# (RFC requires 4096 bytes; for some reason Firefox supports 4097 bytes.) -MAX_LINE = 4111 +# Limit request line and header lines. 8KiB is the most common default +# configuration of popular HTTP servers. +MAX_LINE = 8192 # Support for HTTP response bodies is intended to read an error message # returned by a server. It isn't designed to perform large file transfers. @@ -70,7 +68,7 @@ class Request: def exception(self) -> Optional[Exception]: # pragma: no cover warnings.warn( "Request.exception is deprecated; " - "use ServerConnection.handshake_exc instead", + "use ServerProtocol.handshake_exc instead", DeprecationWarning, ) return self._exception @@ -174,7 +172,7 @@ class Response: def exception(self) -> Optional[Exception]: # pragma: no cover warnings.warn( "Response.exception is deprecated; " - "use ClientConnection.handshake_exc instead", + "use ClientProtocol.handshake_exc instead", DeprecationWarning, ) return self._exception diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/async_timeout.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/async_timeout.py new file mode 100644 index 0000000000000..8264094f5b224 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/async_timeout.py @@ -0,0 +1,265 @@ +# From https://github.com/aio-libs/async-timeout/blob/master/async_timeout/__init__.py +# Licensed under the Apache License (Apache-2.0) + +import asyncio +import enum +import sys +import warnings +from types import TracebackType +from typing import Optional, Type + + +# From https://github.com/python/typing_extensions/blob/main/src/typing_extensions.py +# Licensed under the Python Software Foundation License (PSF-2.0) + +if sys.version_info >= (3, 11): + from typing import final +else: + # @final exists in 3.8+, but we backport it for all versions + # before 3.11 to keep support for the __final__ attribute. + # See https://bugs.python.org/issue46342 + def final(f): + """This decorator can be used to indicate to type checkers that + the decorated method cannot be overridden, and decorated class + cannot be subclassed. For example: + + class Base: + @final + def done(self) -> None: + ... + class Sub(Base): + def done(self) -> None: # Error reported by type checker + ... + @final + class Leaf: + ... + class Other(Leaf): # Error reported by type checker + ... + + There is no runtime checking of these properties. The decorator + sets the ``__final__`` attribute to ``True`` on the decorated object + to allow runtime introspection. + """ + try: + f.__final__ = True + except (AttributeError, TypeError): + # Skip the attribute silently if it is not writable. + # AttributeError happens if the object has __slots__ or a + # read-only property, TypeError if it's a builtin class. + pass + return f + + +# End https://github.com/aio-libs/async-timeout/blob/master/async_timeout/__init__.py + +__version__ = "4.0.2" + + +__all__ = ("timeout", "timeout_at", "Timeout") + + +def timeout(delay: Optional[float]) -> "Timeout": + """timeout context manager. + + Useful in cases when you want to apply timeout logic around block + of code or in cases when asyncio.wait_for is not suitable. For example: + + >>> async with timeout(0.001): + ... async with aiohttp.get('https://github.com') as r: + ... await r.text() + + + delay - value in seconds or None to disable timeout logic + """ + loop = asyncio.get_running_loop() + if delay is not None: + deadline = loop.time() + delay # type: Optional[float] + else: + deadline = None + return Timeout(deadline, loop) + + +def timeout_at(deadline: Optional[float]) -> "Timeout": + """Schedule the timeout at absolute time. + + deadline argument points on the time in the same clock system + as loop.time(). + + Please note: it is not POSIX time but a time with + undefined starting base, e.g. the time of the system power on. + + >>> async with timeout_at(loop.time() + 10): + ... async with aiohttp.get('https://github.com') as r: + ... await r.text() + + + """ + loop = asyncio.get_running_loop() + return Timeout(deadline, loop) + + +class _State(enum.Enum): + INIT = "INIT" + ENTER = "ENTER" + TIMEOUT = "TIMEOUT" + EXIT = "EXIT" + + +@final +class Timeout: + # Internal class, please don't instantiate it directly + # Use timeout() and timeout_at() public factories instead. + # + # Implementation note: `async with timeout()` is preferred + # over `with timeout()`. + # While technically the Timeout class implementation + # doesn't need to be async at all, + # the `async with` statement explicitly points that + # the context manager should be used from async function context. + # + # This design allows to avoid many silly misusages. + # + # TimeoutError is raised immediately when scheduled + # if the deadline is passed. + # The purpose is to time out as soon as possible + # without waiting for the next await expression. + + __slots__ = ("_deadline", "_loop", "_state", "_timeout_handler") + + def __init__( + self, deadline: Optional[float], loop: asyncio.AbstractEventLoop + ) -> None: + self._loop = loop + self._state = _State.INIT + + self._timeout_handler = None # type: Optional[asyncio.Handle] + if deadline is None: + self._deadline = None # type: Optional[float] + else: + self.update(deadline) + + def __enter__(self) -> "Timeout": + warnings.warn( + "with timeout() is deprecated, use async with timeout() instead", + DeprecationWarning, + stacklevel=2, + ) + self._do_enter() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Optional[bool]: + self._do_exit(exc_type) + return None + + async def __aenter__(self) -> "Timeout": + self._do_enter() + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Optional[bool]: + self._do_exit(exc_type) + return None + + @property + def expired(self) -> bool: + """Is timeout expired during execution?""" + return self._state == _State.TIMEOUT + + @property + def deadline(self) -> Optional[float]: + return self._deadline + + def reject(self) -> None: + """Reject scheduled timeout if any.""" + # cancel is maybe better name but + # task.cancel() raises CancelledError in asyncio world. + if self._state not in (_State.INIT, _State.ENTER): + raise RuntimeError(f"invalid state {self._state.value}") + self._reject() + + def _reject(self) -> None: + if self._timeout_handler is not None: + self._timeout_handler.cancel() + self._timeout_handler = None + + def shift(self, delay: float) -> None: + """Advance timeout on delay seconds. + + The delay can be negative. + + Raise RuntimeError if shift is called when deadline is not scheduled + """ + deadline = self._deadline + if deadline is None: + raise RuntimeError("cannot shift timeout if deadline is not scheduled") + self.update(deadline + delay) + + def update(self, deadline: float) -> None: + """Set deadline to absolute value. + + deadline argument points on the time in the same clock system + as loop.time(). + + If new deadline is in the past the timeout is raised immediately. + + Please note: it is not POSIX time but a time with + undefined starting base, e.g. the time of the system power on. + """ + if self._state == _State.EXIT: + raise RuntimeError("cannot reschedule after exit from context manager") + if self._state == _State.TIMEOUT: + raise RuntimeError("cannot reschedule expired timeout") + if self._timeout_handler is not None: + self._timeout_handler.cancel() + self._deadline = deadline + if self._state != _State.INIT: + self._reschedule() + + def _reschedule(self) -> None: + assert self._state == _State.ENTER + deadline = self._deadline + if deadline is None: + return + + now = self._loop.time() + if self._timeout_handler is not None: + self._timeout_handler.cancel() + + task = asyncio.current_task() + if deadline <= now: + self._timeout_handler = self._loop.call_soon(self._on_timeout, task) + else: + self._timeout_handler = self._loop.call_at(deadline, self._on_timeout, task) + + def _do_enter(self) -> None: + if self._state != _State.INIT: + raise RuntimeError(f"invalid state {self._state.value}") + self._state = _State.ENTER + self._reschedule() + + def _do_exit(self, exc_type: Optional[Type[BaseException]]) -> None: + if exc_type is asyncio.CancelledError and self._state == _State.TIMEOUT: + self._timeout_handler = None + raise asyncio.TimeoutError + # timeout has not expired + self._state = _State.EXIT + self._reject() + return None + + def _on_timeout(self, task: "asyncio.Task[None]") -> None: + task.cancel() + self._state = _State.TIMEOUT + # drop the reference early + self._timeout_handler = None + + +# End https://github.com/aio-libs/async-timeout/blob/master/async_timeout/__init__.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/auth.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/auth.py index 8825c14ecf7bf..d3425836e18b2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/auth.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/auth.py @@ -67,7 +67,7 @@ async def check_credentials(self, username: str, password: str) -> bool: Returns: bool: :obj:`True` if the handshake should continue; - :obj:`False` if it should fail with a HTTP 401 error. + :obj:`False` if it should fail with an HTTP 401 error. """ if self._check_credentials is not None: @@ -81,7 +81,7 @@ async def process_request( request_headers: Headers, ) -> Optional[HTTPResponse]: """ - Check HTTP Basic Auth and return a HTTP 401 response if needed. + Check HTTP Basic Auth and return an HTTP 401 response if needed. """ try: @@ -118,8 +118,8 @@ def basic_auth_protocol_factory( realm: Optional[str] = None, credentials: Optional[Union[Credentials, Iterable[Credentials]]] = None, check_credentials: Optional[Callable[[str, str], Awaitable[bool]]] = None, - create_protocol: Optional[Callable[[Any], BasicAuthWebSocketServerProtocol]] = None, -) -> Callable[[Any], BasicAuthWebSocketServerProtocol]: + create_protocol: Optional[Callable[..., BasicAuthWebSocketServerProtocol]] = None, +) -> Callable[..., BasicAuthWebSocketServerProtocol]: """ Protocol factory that enforces HTTP Basic Auth. @@ -135,20 +135,20 @@ def basic_auth_protocol_factory( ) Args: - 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 + realm: 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: Hard coded authorized credentials. It can be a ``(username, password)`` pair or a list of such pairs. - check_credentials: defines a coroutine that verifies credentials. - This coroutine receives ``username`` and ``password`` arguments + check_credentials: Coroutine that verifies credentials. + It receives ``username`` and ``password`` arguments and returns a :class:`bool`. One of ``credentials`` or ``check_credentials`` must be provided but not both. - create_protocol: factory that creates the protocol. By default, this + create_protocol: Factory that creates the protocol. By default, this is :class:`BasicAuthWebSocketServerProtocol`. It can be replaced by a subclass. Raises: - TypeError: if the ``credentials`` or ``check_credentials`` argument is + TypeError: If the ``credentials`` or ``check_credentials`` argument is wrong. """ @@ -175,11 +175,7 @@ async def check_credentials(username: str, password: str) -> bool: return hmac.compare_digest(expected_password, password) if create_protocol is None: - # Not sure why mypy cannot figure this out. - create_protocol = cast( - Callable[[Any], BasicAuthWebSocketServerProtocol], - BasicAuthWebSocketServerProtocol, - ) + create_protocol = BasicAuthWebSocketServerProtocol return functools.partial( create_protocol, diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/client.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/client.py index fadc3efe87667..48622523ee1f8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/client.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/client.py @@ -44,6 +44,7 @@ from ..http import USER_AGENT from ..typing import ExtensionHeader, LoggerLike, Origin, Subprotocol from ..uri import WebSocketURI, parse_uri +from .compatibility import asyncio_timeout from .handshake import build_request, check_response from .http import read_response from .protocol import WebSocketCommonProtocol @@ -65,12 +66,13 @@ class WebSocketClientProtocol(WebSocketCommonProtocol): await process(message) The iterator exits normally when the connection is closed with close code - 1000 (OK) or 1001 (going away). It raises + 1000 (OK) or 1001 (going away) or without a close code. It raises a :exc:`~websockets.exceptions.ConnectionClosedError` when the connection is closed with any other code. See :func:`connect` for the documentation of ``logger``, ``origin``, - ``extensions``, ``subprotocols``, and ``extra_headers``. + ``extensions``, ``subprotocols``, ``extra_headers``, and + ``user_agent_header``. See :class:`~websockets.legacy.protocol.WebSocketCommonProtocol` for the documentation of ``ping_interval``, ``ping_timeout``, ``close_timeout``, @@ -89,6 +91,7 @@ def __init__( extensions: Optional[Sequence[ClientExtensionFactory]] = None, subprotocols: Optional[Sequence[Subprotocol]] = None, extra_headers: Optional[HeadersLike] = None, + user_agent_header: Optional[str] = USER_AGENT, **kwargs: Any, ) -> None: if logger is None: @@ -98,6 +101,7 @@ def __init__( self.available_extensions = extensions self.available_subprotocols = subprotocols self.extra_headers = extra_headers + self.user_agent_header = user_agent_header def write_http_request(self, path: str, headers: Headers) -> None: """ @@ -127,16 +131,12 @@ async def read_http_response(self) -> Tuple[int, Headers]: after this coroutine returns. Raises: - InvalidMessage: if the HTTP message is malformed or isn't an + 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) - # Remove this branch when dropping support for Python < 3.8 - # because CancelledError no longer inherits Exception. - except asyncio.CancelledError: # pragma: no cover - raise except Exception as exc: raise InvalidMessage("did not receive a valid HTTP response") from exc @@ -185,7 +185,6 @@ def process_extensions( header_values = headers.get_all("Sec-WebSocket-Extensions") if header_values: - if available_extensions is None: raise InvalidHandshake("no extensions supported") @@ -194,9 +193,7 @@ def process_extensions( ) 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 @@ -242,7 +239,6 @@ def process_subprotocol( header_values = headers.get_all("Sec-WebSocket-Protocol") if header_values: - if available_subprotocols is None: raise InvalidHandshake("no subprotocols supported") @@ -274,15 +270,15 @@ async def handshake( Args: wsuri: URI of the WebSocket server. - origin: value of the ``Origin`` header. - available_extensions: list of supported extensions, in order in - which they should be tried. - available_subprotocols: list of supported subprotocols, in order - of decreasing preference. - extra_headers: arbitrary HTTP headers to add to the request. + origin: Value of the ``Origin`` header. + extensions: List of supported extensions, in order in which they + should be negotiated and run. + subprotocols: List of supported subprotocols, in order of decreasing + preference. + extra_headers: Arbitrary HTTP headers to add to the handshake request. Raises: - InvalidHandshake: if the handshake fails. + InvalidHandshake: If the handshake fails. """ request_headers = Headers() @@ -315,7 +311,8 @@ async def handshake( if self.extra_headers is not None: request_headers.update(self.extra_headers) - request_headers.setdefault("User-Agent", USER_AGENT) + if self.user_agent_header is not None: + request_headers.setdefault("User-Agent", self.user_agent_header) self.write_http_request(wsuri.resource_name, request_headers) @@ -376,25 +373,26 @@ class Connect: Args: uri: URI of the WebSocket server. - create_protocol: factory for the :class:`asyncio.Protocol` managing - the connection; defaults to :class:`WebSocketClientProtocol`; may - be set to a wrapper or a subclass to customize connection handling. - logger: logger for this connection; - defaults to ``logging.getLogger("websockets.client")``; - see the :doc:`logging guide <../topics/logging>` for details. - compression: shortcut that enables the "permessage-deflate" extension - by default; may be set to :obj:`None` to disable compression; - see the :doc:`compression guide <../topics/compression>` for details. - origin: value of the ``Origin`` header. This is useful when connecting - to a server that validates the ``Origin`` header to defend against - Cross-Site WebSocket Hijacking attacks. - extensions: list of supported extensions, in order in which they - should be tried. - subprotocols: list of supported subprotocols, in order of decreasing + create_protocol: Factory for the :class:`asyncio.Protocol` managing + the connection. It defaults to :class:`WebSocketClientProtocol`. + Set it to a wrapper or a subclass to customize connection handling. + logger: Logger for this client. + It defaults to ``logging.getLogger("websockets.client")``. + See the :doc:`logging guide <../../topics/logging>` for details. + compression: The "permessage-deflate" extension is enabled by default. + Set ``compression`` to :obj:`None` to disable it. See the + :doc:`compression guide <../../topics/compression>` for details. + origin: Value of the ``Origin`` header, for servers that require it. + extensions: List of supported extensions, in order in which they + should be negotiated and run. + subprotocols: List of supported subprotocols, in order of decreasing preference. - extra_headers: arbitrary HTTP headers to add to the request. - open_timeout: timeout for opening the connection in seconds; - :obj:`None` to disable the timeout + extra_headers: Arbitrary HTTP headers to add to the handshake request. + user_agent_header: Value of the ``User-Agent`` request header. + It defaults to ``"Python/x.y.z websockets/X.Y"``. + Setting it to :obj:`None` removes the header. + open_timeout: Timeout for opening the connection in seconds. + :obj:`None` disables the timeout. See :class:`~websockets.legacy.protocol.WebSocketCommonProtocol` for the documentation of ``ping_interval``, ``ping_timeout``, ``close_timeout``, @@ -415,13 +413,11 @@ class Connect: the TCP connection. The host name from ``uri`` is still used in the TLS handshake for secure connections and in the ``Host`` header. - Returns: - WebSocketClientProtocol: WebSocket connection. - Raises: - InvalidURI: if ``uri`` isn't a valid WebSocket URI. - InvalidHandshake: if the opening handshake fails. - ~asyncio.TimeoutError: if the opening handshake times out. + InvalidURI: If ``uri`` isn't a valid WebSocket URI. + OSError: If the TCP connection fails. + InvalidHandshake: If the opening handshake fails. + ~asyncio.TimeoutError: If the opening handshake times out. """ @@ -431,13 +427,14 @@ def __init__( self, uri: str, *, - create_protocol: Optional[Callable[[Any], WebSocketClientProtocol]] = None, + create_protocol: Optional[Callable[..., WebSocketClientProtocol]] = None, logger: Optional[LoggerLike] = None, compression: Optional[str] = "deflate", origin: Optional[Origin] = None, extensions: Optional[Sequence[ClientExtensionFactory]] = None, subprotocols: Optional[Sequence[Subprotocol]] = None, extra_headers: Optional[HeadersLike] = None, + user_agent_header: Optional[str] = USER_AGENT, open_timeout: Optional[float] = 10, ping_interval: Optional[float] = 20, ping_timeout: Optional[float] = 20, @@ -503,6 +500,7 @@ def __init__( extensions=extensions, subprotocols=subprotocols, extra_headers=extra_headers, + user_agent_header=user_agent_header, ping_interval=ping_interval, ping_timeout=ping_timeout, close_timeout=close_timeout, @@ -530,6 +528,8 @@ def __init__( else: # If sock is given, host and port shouldn't be specified. host, port = None, None + if kwargs.get("ssl"): + kwargs.setdefault("server_hostname", wsuri.host) # If host and port are given, override values from the URI. host = kwargs.pop("host", host) port = kwargs.pop("port", port) @@ -597,10 +597,6 @@ async def __aiter__(self) -> AsyncIterator[WebSocketClientProtocol]: try: async with self as protocol: yield protocol - # Remove this branch when dropping support for Python < 3.8 - # because CancelledError no longer inherits Exception. - except asyncio.CancelledError: # pragma: no cover - raise except Exception: # Add a random initial delay between 0 and 5 seconds. # See 7.2.3. Recovering from Abnormal Closure in RFC 6544. @@ -647,13 +643,13 @@ def __await__(self) -> Generator[Any, None, WebSocketClientProtocol]: return self.__await_impl_timeout__().__await__() async def __await_impl_timeout__(self) -> WebSocketClientProtocol: - return await asyncio.wait_for(self.__await_impl__(), self.open_timeout) + async with asyncio_timeout(self.open_timeout): + return await self.__await_impl__() async def __await_impl__(self) -> WebSocketClientProtocol: for redirects in range(self.MAX_REDIRECTS_ALLOWED): - transport, protocol = await self._create_connection() - protocol = cast(WebSocketClientProtocol, protocol) - + _transport, _protocol = await self._create_connection() + protocol = cast(WebSocketClientProtocol, _protocol) try: await protocol.handshake( self._wsuri, @@ -701,7 +697,7 @@ def unix_connect( It's mainly useful for debugging servers listening on Unix sockets. Args: - path: file system path to the Unix socket. + path: File system path to the Unix socket. uri: URI of the WebSocket server; the host is used in the TLS handshake for secure connections and in the ``Host`` header. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/compatibility.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/compatibility.py index df81de9dbcead..6bd01e70dee10 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/compatibility.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/compatibility.py @@ -1,13 +1,12 @@ from __future__ import annotations -import asyncio import sys -from typing import Any, Dict -def loop_if_py_lt_38(loop: asyncio.AbstractEventLoop) -> Dict[str, Any]: - """ - Helper for the removal of the loop argument in Python 3.10. +__all__ = ["asyncio_timeout"] - """ - return {"loop": loop} if sys.version_info[:2] < (3, 8) else {} + +if sys.version_info[:2] >= (3, 11): + from asyncio import timeout as asyncio_timeout # noqa: F401 +else: + from .async_timeout import timeout as asyncio_timeout # noqa: F401 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/framing.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/framing.py index c4de7eb28bc19..b77b869e3faad 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/framing.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/framing.py @@ -1,6 +1,5 @@ from __future__ import annotations -import dataclasses import struct from typing import Any, Awaitable, Callable, NamedTuple, Optional, Sequence, Tuple @@ -10,12 +9,11 @@ try: from ..speedups import apply_mask -except ImportError: # pragma: no cover +except ImportError: from ..utils import apply_mask class Frame(NamedTuple): - fin: bool opcode: frames.Opcode data: bytes @@ -53,16 +51,16 @@ async def read( Read a WebSocket frame. Args: - reader: coroutine that reads exactly the requested number of + reader: Coroutine that reads exactly the requested number of bytes, unless the end of file is reached. - mask: whether the frame should be masked i.e. whether the read + mask: Whether the frame should be masked i.e. whether the read happens on the server side. - max_size: maximum payload size in bytes. - extensions: list of extensions, applied in reverse order. + max_size: Maximum payload size in bytes. + extensions: List of extensions, applied in reverse order. Raises: - PayloadTooBig: if the frame exceeds ``max_size``. - ProtocolError: if the frame contains incorrect values. + PayloadTooBig: If the frame exceeds ``max_size``. + ProtocolError: If the frame contains incorrect values. """ @@ -130,14 +128,14 @@ def write( Write a WebSocket frame. Args: - frame: frame to write. - write: function that writes bytes. - mask: whether the frame should be masked i.e. whether the write + frame: Frame to write. + write: Function that writes bytes. + mask: Whether the frame should be masked i.e. whether the write happens on the client side. - extensions: list of extensions, applied in order. + extensions: List of extensions, applied in order. Raises: - ProtocolError: if the frame contains incorrect values. + ProtocolError: If the frame contains incorrect values. """ # The frame is written in a single call to write in order to prevent @@ -147,8 +145,11 @@ def write( # Backwards compatibility with previously documented public APIs - -from ..frames import Close, prepare_ctrl as encode_data, prepare_data # noqa +from ..frames import ( # noqa: E402, F401, I001 + Close, + prepare_ctrl as encode_data, + prepare_data, +) def parse_close(data: bytes) -> Tuple[int, str]: @@ -156,14 +157,15 @@ def parse_close(data: bytes) -> Tuple[int, str]: Parse the payload from a close frame. Returns: - Tuple[int, str]: close code and reason. + Close code and reason. Raises: - ProtocolError: if data is ill-formed. - UnicodeDecodeError: if the reason isn't valid UTF-8. + ProtocolError: If data is ill-formed. + UnicodeDecodeError: If the reason isn't valid UTF-8. """ - return dataclasses.astuple(Close.parse(data)) # type: ignore + close = Close.parse(data) + return close.code, close.reason def serialize_close(code: int, reason: str) -> bytes: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/handshake.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/handshake.py index 569937bb9a94d..ad8faf04045e7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/handshake.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/handshake.py @@ -21,7 +21,7 @@ def build_request(headers: Headers) -> str: Update request headers passed in argument. Args: - headers: handshake request headers. + headers: Handshake request headers. Returns: str: ``key`` that must be passed to :func:`check_response`. @@ -45,14 +45,14 @@ def check_request(headers: Headers) -> str: the responsibility of the caller. Args: - headers: handshake request headers. + headers: Handshake request headers. Returns: str: ``key`` that must be passed to :func:`build_response`. Raises: - InvalidHandshake: if the handshake request is invalid; - then the server must return 400 Bad Request error. + InvalidHandshake: If the handshake request is invalid. + Then, the server must return a 400 Bad Request error. """ connection: List[ConnectionOption] = sum( @@ -110,8 +110,8 @@ def build_response(headers: Headers, key: str) -> None: Update response headers passed in argument. Args: - headers: handshake response headers. - key: returned by :func:`check_request`. + headers: Handshake response headers. + key: Returned by :func:`check_request`. """ headers["Upgrade"] = "websocket" @@ -128,11 +128,11 @@ def check_response(headers: Headers, key: str) -> None: the caller. Args: - headers: handshake response headers. - key: returned by :func:`build_request`. + headers: Handshake response headers. + key: Returned by :func:`build_request`. Raises: - InvalidHandshake: if the handshake response is invalid. + InvalidHandshake: If the handshake response is invalid. """ connection: List[ConnectionOption] = sum( diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/http.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/http.py index cc2ef1f067d39..2ac7f7092d58c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/http.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/http.py @@ -10,8 +10,8 @@ __all__ = ["read_request", "read_response"] -MAX_HEADERS = 256 -MAX_LINE = 4110 +MAX_HEADERS = 128 +MAX_LINE = 8192 def d(value: bytes) -> str: @@ -56,12 +56,12 @@ async def read_request(stream: asyncio.StreamReader) -> Tuple[str, Headers]: body, it may be read from ``stream`` after this coroutine returns. Args: - stream: input to read the request from + stream: Input to read the request from. Raises: - EOFError: if the connection is closed without a full HTTP request - SecurityError: if the request exceeds a security limit - ValueError: if the request isn't well formatted + EOFError: If the connection is closed without a full HTTP request. + SecurityError: If the request exceeds a security limit. + ValueError: If the request isn't well formatted. """ # https://www.rfc-editor.org/rfc/rfc7230.html#section-3.1.1 @@ -103,12 +103,12 @@ async def read_response(stream: asyncio.StreamReader) -> Tuple[int, str, Headers body, it may be read from ``stream`` after this coroutine returns. Args: - stream: input to read the response from + stream: Input to read the response from. Raises: - EOFError: if the connection is closed without a full HTTP response - SecurityError: if the response exceeds a security limit - ValueError: if the response isn't well formatted + EOFError: If the connection is closed without a full HTTP response. + SecurityError: If the response exceeds a security limit. + ValueError: If the response isn't well formatted. """ # https://www.rfc-editor.org/rfc/rfc7230.html#section-3.1.2 @@ -192,7 +192,7 @@ async def read_line(stream: asyncio.StreamReader) -> bytes: """ # 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) + # Security: this guarantees header values are small (hard-coded = 8 KiB) if len(line) > MAX_LINE: raise SecurityError("line too long") # Not mandatory but safe - https://www.rfc-editor.org/rfc/rfc7230.html#section-3.5 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/protocol.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/protocol.py index 3f734fe7602a1..19cee0e652b0c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/protocol.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/protocol.py @@ -7,6 +7,8 @@ import random import ssl import struct +import sys +import time import uuid import warnings from typing import ( @@ -14,17 +16,18 @@ AsyncIterable, AsyncIterator, Awaitable, + Callable, Deque, Dict, Iterable, List, Mapping, Optional, + Tuple, Union, cast, ) -from ..connection import State from ..datastructures import Headers from ..exceptions import ( ConnectionClosed, @@ -44,12 +47,14 @@ OP_PONG, OP_TEXT, Close, + CloseCode, Opcode, prepare_ctrl, prepare_data, ) +from ..protocol import State from ..typing import Data, LoggerLike, Subprotocol -from .compatibility import loop_if_py_lt_38 +from .compatibility import asyncio_timeout from .framing import Frame @@ -76,38 +81,38 @@ class WebSocketCommonProtocol(asyncio.Protocol): simplicity. 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 :obj:`None` to disable - this behavior. + 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 :obj:`None` to disable this behavior. .. _Ping: https://www.rfc-editor.org/rfc/rfc6455.html#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 + seconds, the connection is considered unusable and is closed with code 1011. + This ensures that the remote endpoint remains responsive. Set ``ping_timeout`` to :obj:`None` to disable this behavior. .. _Pong: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.5.3 + See the discussion of :doc:`timeouts <../../topics/timeouts>` for details. + The ``close_timeout`` parameter defines a maximum wait time for completing the closing handshake and terminating the TCP connection. For legacy reasons, :meth:`close` completes in at most ``5 * close_timeout`` seconds for clients and ``4 * close_timeout`` for servers. - See the discussion of :doc:`timeouts <../topics/timeouts>` for details. - - ``close_timeout`` needs to be a parameter of the protocol because - websockets usually calls :meth:`close` implicitly upon exit: + ``close_timeout`` is a parameter of the protocol because websockets usually + calls :meth:`close` implicitly upon exit: - * on the client side, when :func:`~websockets.client.connect` is used as a + * on the client side, when using :func:`~websockets.client.connect` as a context manager; - * on the server side, when the connection handler terminates; + * on the server side, when the connection handler terminates. - To apply a timeout to any other API, wrap it in :func:`~asyncio.wait_for`. + To apply a timeout to any other API, wrap it in :func:`~asyncio.timeout` or + :func:`~asyncio.wait_for`. The ``max_size`` parameter enforces the maximum size for incoming messages - in bytes. The default value is 1 MiB. If a larger message is received, + in bytes. The default value is 1 MiB. If a larger message is received, :meth:`recv` will raise :exc:`~websockets.exceptions.ConnectionClosedError` and the connection will be closed with code 1009. @@ -124,38 +129,38 @@ class WebSocketCommonProtocol(asyncio.Protocol): 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. + 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 + 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 + The default value is 64 KiB, equal to asyncio's default (based on the current implementation of ``FlowControlMixin``). - See the discussion of :doc:`memory usage <../topics/memory>` for details. + See the discussion of :doc:`memory usage <../../topics/memory>` for details. Args: - logger: logger for this connection; - defaults to ``logging.getLogger("websockets.protocol")``; - see the :doc:`logging guide <../topics/logging>` for details. - ping_interval: delay between keepalive pings in seconds; - :obj:`None` to disable keepalive pings. - ping_timeout: timeout for keepalive pings in seconds; - :obj:`None` to disable timeouts. - close_timeout: timeout for closing the connection in seconds; - for legacy reasons, the actual timeout is 4 or 5 times larger. - max_size: maximum size of incoming messages in bytes; - :obj:`None` to disable the limit. - max_queue: maximum number of incoming messages in receive buffer; - :obj:`None` to disable the limit. - read_limit: high-water mark of read buffer in bytes. - write_limit: high-water mark of write buffer in bytes. + logger: Logger for this server. + It defaults to ``logging.getLogger("websockets.protocol")``. + See the :doc:`logging guide <../../topics/logging>` for details. + ping_interval: Delay between keepalive pings in seconds. + :obj:`None` disables keepalive pings. + ping_timeout: Timeout for keepalive pings in seconds. + :obj:`None` disables timeouts. + close_timeout: Timeout for closing the connection in seconds. + For legacy reasons, the actual timeout is 4 or 5 times larger. + max_size: Maximum size of incoming messages in bytes. + :obj:`None` disables the limit. + max_queue: Maximum number of incoming messages in receive buffer. + :obj:`None` disables the limit. + read_limit: High-water mark of read buffer in bytes. + write_limit: High-water mark of write buffer in bytes. """ @@ -217,8 +222,6 @@ def __init__( # Logger or LoggerAdapter for this connection. if logger is None: logger = logging.getLogger("websockets.protocol") - # https://github.com/python/typeshed/issues/5561 - logger = cast(logging.Logger, logger) self.logger: LoggerLike = logging.LoggerAdapter(logger, {"websocket": self}) """Logger for this connection.""" @@ -242,7 +245,7 @@ def __init__( self._paused = False self._drain_waiter: Optional[asyncio.Future[None]] = None - self._drain_lock = asyncio.Lock(**loop_if_py_lt_38(loop)) + self._drain_lock = asyncio.Lock() # This class implements the data transfer and closing handshake, which # are shared between the client-side and the server-side. @@ -285,7 +288,19 @@ def __init__( self._fragmented_message_waiter: Optional[asyncio.Future[None]] = None # Mapping of ping IDs to pong waiters, in chronological order. - self.pings: Dict[bytes, asyncio.Future[None]] = {} + self.pings: Dict[bytes, Tuple[asyncio.Future[float], float]] = {} + + self.latency: float = 0 + """ + Latency of the connection, in seconds. + + This value is updated after sending a ping frame and receiving a + matching pong frame. Before the first ping, :attr:`latency` is ``0``. + + By default, websockets enables a :ref:`keepalive ` mechanism + that sends ping frames automatically at regular intervals. You can also + send ping frames and measure latency with :meth:`ping`. + """ # Task running the data transfer. self.transfer_data_task: asyncio.Task[None] @@ -325,7 +340,7 @@ async def _drain(self) -> None: # pragma: no cover # 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_if_py_lt_38(self.loop)) + await asyncio.sleep(0) await self._drain_helper() def connection_open(self) -> None: @@ -445,7 +460,7 @@ def close_code(self) -> Optional[int]: if self.state is not State.CLOSED: return None elif self.close_rcvd is None: - return 1006 + return CloseCode.ABNORMAL_CLOSURE else: return self.close_rcvd.code @@ -471,10 +486,11 @@ async def __aiter__(self) -> AsyncIterator[Data]: """ Iterate on incoming messages. - The iterator 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. + The iterator exits normally when the connection is closed with the close + code 1000 (OK) or 1001 (going away) or without a close code. + + It raises a :exc:`~websockets.exceptions.ConnectionClosedError` + exception when the connection is closed with any other code. """ try: @@ -488,8 +504,8 @@ async def recv(self) -> Data: Receive the next message. When the connection is closed, :meth:`recv` raises - :exc:`~websockets.exceptions.ConnectionClosed`. Specifically, it - raises :exc:`~websockets.exceptions.ConnectionClosedOK` after a normal + :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. This is how you detect the end of the @@ -498,8 +514,8 @@ async def recv(self) -> Data: 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`. + This makes it possible to enforce a timeout by wrapping :meth:`recv` in + :func:`~asyncio.timeout` or :func:`~asyncio.wait_for`. Returns: Data: A string (:class:`str`) for a Text_ frame. A bytestring @@ -509,8 +525,8 @@ async def recv(self) -> Data: .. _Binary: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.6 Raises: - ConnectionClosed: when the connection is closed. - RuntimeError: if two coroutines call :meth:`recv` concurrently. + ConnectionClosed: When the connection is closed. + RuntimeError: If two coroutines call :meth:`recv` concurrently. """ if self._pop_message_waiter is not None: @@ -536,7 +552,6 @@ async def recv(self) -> Data: await asyncio.wait( [pop_message_waiter, self.transfer_data_task], return_when=asyncio.FIRST_COMPLETED, - **loop_if_py_lt_38(self.loop), ) finally: self._pop_message_waiter = None @@ -613,8 +628,8 @@ async def send( to send. Raises: - ConnectionClosed: when the connection is closed. - TypeError: if ``message`` doesn't have a supported type. + ConnectionClosed: When the connection is closed. + TypeError: If ``message`` doesn't have a supported type. """ await self.ensure_open() @@ -639,16 +654,15 @@ async def send( # 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) + fragment = next(iter_message) except StopIteration: return - opcode, data = prepare_data(message_chunk) + opcode, data = prepare_data(fragment) self._fragmented_message_waiter = asyncio.Future() try: @@ -656,8 +670,8 @@ async def send( await self.write_frame(False, opcode, data) # Other fragments. - for message_chunk in iter_message: - confirm_opcode, data = prepare_data(message_chunk) + for fragment in iter_message: + confirm_opcode, data = prepare_data(fragment) if confirm_opcode != opcode: raise TypeError("data contains inconsistent types") await self.write_frame(False, OP_CONT, data) @@ -668,7 +682,7 @@ async def send( except (Exception, asyncio.CancelledError): # We're half-way through a fragmented message and we can't # complete it. This makes the connection unusable. - self.fail_connection(1011) + self.fail_connection(CloseCode.INTERNAL_ERROR) raise finally: @@ -678,18 +692,22 @@ async def send( # 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 + # Implement aiter_message = aiter(message) without aiter + # Work around https://github.com/python/mypy/issues/5738 + aiter_message = cast( + Callable[[AsyncIterable[Data]], AsyncIterator[Data]], + type(message).__aiter__, + )(message) 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 - ) + # Implement fragment = anext(aiter_message) without anext + # Work around https://github.com/python/mypy/issues/5738 + fragment = await cast( + Callable[[AsyncIterator[Data]], Awaitable[Data]], + type(aiter_message).__anext__, + )(aiter_message) except StopAsyncIteration: return - opcode, data = prepare_data(message_chunk) + opcode, data = prepare_data(fragment) self._fragmented_message_waiter = asyncio.Future() try: @@ -697,11 +715,8 @@ async def send( await self.write_frame(False, opcode, data) # Other fragments. - # https://github.com/python/mypy/issues/5738 - # coverage reports this code as not covered, but it is - # exercised by tests - changing it breaks the tests! - async for message_chunk in aiter_message: # type: ignore # pragma: no cover # noqa - confirm_opcode, data = prepare_data(message_chunk) + async for fragment in aiter_message: + confirm_opcode, data = prepare_data(fragment) if confirm_opcode != opcode: raise TypeError("data contains inconsistent types") await self.write_frame(False, OP_CONT, data) @@ -712,7 +727,7 @@ async def send( except (Exception, asyncio.CancelledError): # We're half-way through a fragmented message and we can't # complete it. This makes the connection unusable. - self.fail_connection(1011) + self.fail_connection(CloseCode.INTERNAL_ERROR) raise finally: @@ -722,7 +737,11 @@ async def send( else: raise TypeError("data must be str, bytes-like, or iterable") - async def close(self, code: int = 1000, reason: str = "") -> None: + async def close( + self, + code: int = CloseCode.NORMAL_CLOSURE, + reason: str = "", + ) -> None: """ Perform the closing handshake. @@ -747,19 +766,16 @@ async def close(self, code: int = 1000, reason: str = "") -> None: """ try: - await asyncio.wait_for( - self.write_close_frame(Close(code, reason)), - self.close_timeout, - **loop_if_py_lt_38(self.loop), - ) + async with asyncio_timeout(self.close_timeout): + await self.write_close_frame(Close(code, reason)) 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 no close frame is received within the timeout, asyncio_timeout() + # 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 canceled. @@ -768,11 +784,8 @@ async def close(self, code: int = 1000, reason: str = "") -> None: 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_if_py_lt_38(self.loop), - ) + async with asyncio_timeout(self.close_timeout): + await self.transfer_data_task except (asyncio.TimeoutError, asyncio.CancelledError): pass @@ -798,8 +811,8 @@ async def ping(self, data: Optional[Data] = None) -> Awaitable[None]: .. _Ping: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.5.2 - A ping may serve as a keepalive or as a check that the remote endpoint - received all messages up to this point + A ping may serve as a keepalive, as a check that the remote endpoint + received all messages up to this point, or to measure :attr:`latency`. 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 @@ -814,18 +827,20 @@ async def ping(self, data: Optional[Data] = None) -> Awaitable[None]: containing four random bytes. Returns: - ~asyncio.Future: A future that will be completed when the - corresponding pong is received. You can ignore it if you - don't intend to wait. + ~asyncio.Future[float]: A future that will be completed when the + corresponding pong is received. You can ignore it if you don't + intend to wait. The result of the future is the latency of the + connection in seconds. :: pong_waiter = await ws.ping() - await pong_waiter # only if you want to wait for the pong + # only if you want to wait for the corresponding pong + latency = await pong_waiter Raises: - ConnectionClosed: when the connection is closed. - RuntimeError: if another ping was sent with the same data and + ConnectionClosed: When the connection is closed. + RuntimeError: If another ping was sent with the same data and the corresponding pong wasn't received yet. """ @@ -842,11 +857,14 @@ async def ping(self, data: Optional[Data] = None) -> Awaitable[None]: while data is None or data in self.pings: data = struct.pack("!I", random.getrandbits(32)) - self.pings[data] = self.loop.create_future() + pong_waiter = self.loop.create_future() + # Resolution of time.monotonic() may be too low on Windows. + ping_timestamp = time.perf_counter() + self.pings[data] = (pong_waiter, ping_timestamp) await self.write_frame(True, OP_PING, data) - return asyncio.shield(self.pings[data]) + return asyncio.shield(pong_waiter) async def pong(self, data: Data = b"") -> None: """ @@ -861,11 +879,11 @@ async def pong(self, data: Data = b"") -> None: wait, you should close the connection. Args: - data (Data): payload of the pong; a string will be encoded to + data (Data): Payload of the pong. A string will be encoded to UTF-8. Raises: - ConnectionClosed: when the connection is closed. + ConnectionClosed: When the connection is closed. """ await self.ensure_open() @@ -973,7 +991,7 @@ async def transfer_data(self) -> None: except ProtocolError as exc: self.transfer_data_exc = exc - self.fail_connection(1002) + self.fail_connection(CloseCode.PROTOCOL_ERROR) except (ConnectionError, TimeoutError, EOFError, ssl.SSLError) as exc: # Reading data with self.reader.readexactly may raise: @@ -984,15 +1002,15 @@ async def transfer_data(self) -> None: # bytes are available than requested; # - ssl.SSLError if the other side infringes the TLS protocol. self.transfer_data_exc = exc - self.fail_connection(1006) + self.fail_connection(CloseCode.ABNORMAL_CLOSURE) except UnicodeDecodeError as exc: self.transfer_data_exc = exc - self.fail_connection(1007) + self.fail_connection(CloseCode.INVALID_DATA) except PayloadTooBig as exc: self.transfer_data_exc = exc - self.fail_connection(1009) + self.fail_connection(CloseCode.MESSAGE_TOO_BIG) except Exception as exc: # This shouldn't happen often because exceptions expected under @@ -1001,7 +1019,7 @@ async def transfer_data(self) -> None: self.logger.error("data transfer failed", exc_info=True) self.transfer_data_exc = exc - self.fail_connection(1011) + self.fail_connection(CloseCode.INTERNAL_ERROR) async def read_message(self) -> Optional[Data]: """ @@ -1030,7 +1048,7 @@ async def read_message(self) -> Optional[Data]: return frame.data.decode("utf-8") if text else frame.data # 5.4. Fragmentation - chunks: List[Data] = [] + fragments: List[Data] = [] max_size = self.max_size if text: decoder_factory = codecs.getincrementaldecoder("utf-8") @@ -1038,14 +1056,14 @@ async def read_message(self) -> Optional[Data]: if max_size is None: def append(frame: Frame) -> None: - nonlocal chunks - chunks.append(decoder.decode(frame.data, frame.fin)) + nonlocal fragments + fragments.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)) + nonlocal fragments, max_size + fragments.append(decoder.decode(frame.data, frame.fin)) assert isinstance(max_size, int) max_size -= len(frame.data) @@ -1053,14 +1071,14 @@ def append(frame: Frame) -> None: if max_size is None: def append(frame: Frame) -> None: - nonlocal chunks - chunks.append(frame.data) + nonlocal fragments + fragments.append(frame.data) else: def append(frame: Frame) -> None: - nonlocal chunks, max_size - chunks.append(frame.data) + nonlocal fragments, max_size + fragments.append(frame.data) assert isinstance(max_size, int) max_size -= len(frame.data) @@ -1074,7 +1092,7 @@ def append(frame: Frame) -> None: raise ProtocolError("unexpected opcode") append(frame) - return ("" if text else b"").join(chunks) + return ("" if text else b"").join(fragments) async def read_data_frame(self, max_size: Optional[int]) -> Optional[Frame]: """ @@ -1099,7 +1117,7 @@ async def read_data_frame(self, max_size: Optional[int]) -> Optional[Frame]: try: # Echo the original data instead of re-serializing it with # Close.serialize() because that fails when the close frame - # is empty and Close.parse() synthetizes a 1005 close code. + # is empty and Close.parse() synthesizes a 1005 close code. await self.write_close_frame(self.close_rcvd, frame.data) except ConnectionClosed: # Connection closed before we could echo the close frame. @@ -1117,18 +1135,20 @@ async def read_data_frame(self, max_size: Optional[int]) -> Optional[Frame]: elif frame.opcode == OP_PONG: if frame.data in self.pings: + pong_timestamp = time.perf_counter() # Sending a pong for only the most recent ping is legal. # Acknowledge all previous pings too in that case. ping_id = None ping_ids = [] - for ping_id, ping in self.pings.items(): + for ping_id, (pong_waiter, ping_timestamp) in self.pings.items(): ping_ids.append(ping_id) - if not ping.done(): - ping.set_result(None) + if not pong_waiter.done(): + pong_waiter.set_result(pong_timestamp - ping_timestamp) if ping_id == frame.data: + self.latency = pong_timestamp - ping_timestamp break - else: # pragma: no cover - assert False, "ping_id is in self.pings" + else: + raise AssertionError("solicited pong not found in pings") # Remove acknowledged pings from self.pings. for ping_id in ping_ids: del self.pings[ping_id] @@ -1231,10 +1251,7 @@ async def keepalive_ping(self) -> None: try: while True: - await asyncio.sleep( - self.ping_interval, - **loop_if_py_lt_38(self.loop), - ) + await asyncio.sleep(self.ping_interval) # ping() raises CancelledError if the connection is closed, # when close_connection() cancels self.keepalive_ping_task. @@ -1247,23 +1264,18 @@ async def keepalive_ping(self) -> None: if self.ping_timeout is not None: try: - await asyncio.wait_for( - pong_waiter, - self.ping_timeout, - **loop_if_py_lt_38(self.loop), - ) + async with asyncio_timeout(self.ping_timeout): + await pong_waiter self.logger.debug("% received keepalive pong") except asyncio.TimeoutError: if self.debug: self.logger.debug("! timed out waiting for keepalive pong") - self.fail_connection(1011, "keepalive ping timeout") + self.fail_connection( + CloseCode.INTERNAL_ERROR, + "keepalive ping timeout", + ) break - # Remove this branch when dropping support for Python < 3.8 - # because CancelledError no longer inherits Exception. - except asyncio.CancelledError: - raise - except ConnectionClosed: pass @@ -1297,9 +1309,7 @@ async def close_connection(self) -> None: # 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(): - # Coverage marks this line as a partially executed branch. - # I supect a bug in coverage. Ignore it for now. - return # pragma: no cover + return if self.debug: self.logger.debug("! timed out waiting for TCP close") @@ -1317,9 +1327,7 @@ async def close_connection(self) -> None: pass if await self.wait_for_connection_lost(): - # Coverage marks this line as a partially executed branch. - # I supect a bug in coverage. Ignore it for now. - return # pragma: no cover + return if self.debug: self.logger.debug("! timed out waiting for TCP close") @@ -1352,12 +1360,11 @@ async def close_transport(self) -> None: # Abort the TCP connection. Buffers are discarded. if self.debug: self.logger.debug("x aborting TCP connection") - self.transport.abort() + # Due to a bug in coverage, this is erroneously reported as not covered. + self.transport.abort() # pragma: no cover # connection_lost() is called quickly after aborting. - # Coverage marks this line as a partially executed branch. - # I supect a bug in coverage. Ignore it for now. - await self.wait_for_connection_lost() # pragma: no cover + await self.wait_for_connection_lost() async def wait_for_connection_lost(self) -> bool: """ @@ -1369,11 +1376,8 @@ async def wait_for_connection_lost(self) -> bool: """ if not self.connection_lost_waiter.done(): try: - await asyncio.wait_for( - asyncio.shield(self.connection_lost_waiter), - self.close_timeout, - **loop_if_py_lt_38(self.loop), - ) + async with asyncio_timeout(self.close_timeout): + await asyncio.shield(self.connection_lost_waiter) except asyncio.TimeoutError: pass # Re-check self.connection_lost_waiter.done() synchronously because @@ -1381,7 +1385,11 @@ async def wait_for_connection_lost(self) -> bool: # and the moment this coroutine resumes running. return self.connection_lost_waiter.done() - def fail_connection(self, code: int = 1006, reason: str = "") -> None: + def fail_connection( + self, + code: int = CloseCode.ABNORMAL_CLOSURE, + reason: str = "", + ) -> None: """ 7.1.7. Fail the WebSocket Connection @@ -1412,7 +1420,7 @@ def fail_connection(self, code: int = 1006, reason: str = "") -> None: # 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: + if code != CloseCode.ABNORMAL_CLOSURE and self.state is State.OPEN: close = Close(code, reason) # Write the close frame without draining the write buffer. @@ -1449,13 +1457,13 @@ def abort_pings(self) -> None: assert self.state is State.CLOSED exc = self.connection_closed_exc() - for ping in self.pings.values(): - ping.set_exception(exc) + for pong_waiter, _ping_timestamp in self.pings.values(): + pong_waiter.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() + pong_waiter.cancel() # asyncio.Protocol methods @@ -1496,7 +1504,6 @@ def connection_lost(self, exc: Optional[Exception]) -> None: 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: @@ -1552,13 +1559,17 @@ def eof_received(self) -> None: self.reader.feed_eof() -def broadcast(websockets: Iterable[WebSocketCommonProtocol], message: Data) -> None: +def broadcast( + websockets: Iterable[WebSocketCommonProtocol], + message: Data, + raise_exceptions: bool = False, +) -> None: """ Broadcast a message to several WebSocket connections. - 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. + 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: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.6 .. _Binary: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.6 @@ -1566,33 +1577,42 @@ def broadcast(websockets: Iterable[WebSocketCommonProtocol], message: Data) -> N :func:`broadcast` pushes the message synchronously to all connections even if their write buffers are overflowing. There's no backpressure. - :func:`broadcast` skips silently connections that aren't open in order to - avoid errors on connections where the closing handshake is in progress. - - If you broadcast messages faster than a connection can handle them, - messages will pile up in its write buffer until the connection times out. - Keep low values for ``ping_interval`` and ``ping_timeout`` to prevent - excessive memory usage by slow connections when you use :func:`broadcast`. + If you broadcast messages faster than a connection can handle them, messages + will pile up in its write buffer until the connection times out. Keep + ``ping_interval`` and ``ping_timeout`` low to prevent excessive memory usage + from slow connections. Unlike :meth:`~websockets.server.WebSocketServerProtocol.send`, :func:`broadcast` doesn't support sending fragmented messages. Indeed, - fragmentation is useful for sending large messages without buffering - them in memory, while :func:`broadcast` buffers one copy per connection - as fast as possible. + fragmentation is useful for sending large messages without buffering them in + memory, while :func:`broadcast` buffers one copy per connection as fast as + possible. + + :func:`broadcast` skips connections that aren't open in order to avoid + errors on connections where the closing handshake is in progress. + + :func:`broadcast` ignores failures to write the message on some connections. + It continues writing to other connections. On Python 3.11 and above, you + may set ``raise_exceptions`` to :obj:`True` to record failures and raise all + exceptions in a :pep:`654` :exc:`ExceptionGroup`. Args: - websockets (Iterable[WebSocketCommonProtocol]): WebSocket connections - to which the message will be sent. - message (Data): message to send. + websockets: WebSocket connections to which the message will be sent. + message: Message to send. + raise_exceptions: Whether to raise an exception in case of failures. Raises: - RuntimeError: if a connection is busy sending a fragmented message. - TypeError: if ``message`` doesn't have a supported type. + TypeError: If ``message`` doesn't have a supported type. """ if not isinstance(message, (str, bytes, bytearray, memoryview)): raise TypeError("data must be str or bytes-like") + if raise_exceptions: + if sys.version_info[:2] < (3, 11): # pragma: no cover + raise ValueError("raise_exceptions requires at least Python 3.11") + exceptions = [] + opcode, data = prepare_data(message) for websocket in websockets: @@ -1600,6 +1620,26 @@ def broadcast(websockets: Iterable[WebSocketCommonProtocol], message: Data) -> N continue if websocket._fragmented_message_waiter is not None: - raise RuntimeError("busy sending a fragmented message") + if raise_exceptions: + exception = RuntimeError("sending a fragmented message") + exceptions.append(exception) + else: + websocket.logger.warning( + "skipped broadcast: sending a fragmented message", + ) + + try: + websocket.write_frame_sync(True, opcode, data) + except Exception as write_exception: + if raise_exceptions: + exception = RuntimeError("failed to write message") + exception.__cause__ = write_exception + exceptions.append(exception) + else: + websocket.logger.warning( + "skipped broadcast: failed to write message", + exc_info=True, + ) - websocket.write_frame_sync(True, opcode, data) + if raise_exceptions: + raise ExceptionGroup("skipped broadcast", exceptions) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/server.py index 3e51db1b71e14..7c24dd74af3ea 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/server.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/server.py @@ -25,7 +25,6 @@ cast, ) -from ..connection import State from ..datastructures import Headers, HeadersLike, MultipleValuesError from ..exceptions import ( AbortHandshake, @@ -45,8 +44,9 @@ validate_subprotocols, ) from ..http import USER_AGENT -from ..typing import ExtensionHeader, LoggerLike, Origin, Subprotocol -from .compatibility import loop_if_py_lt_38 +from ..protocol import State +from ..typing import ExtensionHeader, LoggerLike, Origin, StatusLike, Subprotocol +from .compatibility import asyncio_timeout from .handshake import build_response, check_request from .http import read_request from .protocol import WebSocketCommonProtocol @@ -57,7 +57,7 @@ HeadersLikeOrCallable = Union[HeadersLike, Callable[[str, Headers], HeadersLike]] -HTTPResponse = Tuple[http.HTTPStatus, HeadersLike, bytes] +HTTPResponse = Tuple[StatusLike, HeadersLike, bytes] class WebSocketServerProtocol(WebSocketCommonProtocol): @@ -73,7 +73,7 @@ class WebSocketServerProtocol(WebSocketCommonProtocol): await process(message) The iterator exits normally when the connection is closed with close code - 1000 (OK) or 1001 (going away). It raises + 1000 (OK) or 1001 (going away) or without a close code. It raises a :exc:`~websockets.exceptions.ConnectionClosedError` when the connection is closed with any other code. @@ -84,7 +84,7 @@ class WebSocketServerProtocol(WebSocketCommonProtocol): ws_server: WebSocket server that created this connection. See :func:`serve` for the documentation of ``ws_handler``, ``logger``, ``origins``, - ``extensions``, ``subprotocols``, and ``extra_headers``. + ``extensions``, ``subprotocols``, ``extra_headers``, and ``server_header``. See :class:`~websockets.legacy.protocol.WebSocketCommonProtocol` for the documentation of ``ping_interval``, ``ping_timeout``, ``close_timeout``, @@ -108,12 +108,14 @@ def __init__( extensions: Optional[Sequence[ServerExtensionFactory]] = None, subprotocols: Optional[Sequence[Subprotocol]] = None, extra_headers: Optional[HeadersLikeOrCallable] = None, + server_header: Optional[str] = USER_AGENT, process_request: Optional[ Callable[[str, Headers], Awaitable[Optional[HTTPResponse]]] ] = None, select_subprotocol: Optional[ Callable[[Sequence[Subprotocol], Sequence[Subprotocol]], Subprotocol] ] = None, + open_timeout: Optional[float] = 10, **kwargs: Any, ) -> None: if logger is None: @@ -132,8 +134,10 @@ def __init__( self.available_extensions = extensions self.available_subprotocols = subprotocols self.extra_headers = extra_headers + self.server_header = server_header self._process_request = process_request self._select_subprotocol = select_subprotocol + self.open_timeout = open_timeout def connection_made(self, transport: asyncio.BaseTransport) -> None: """ @@ -153,22 +157,20 @@ 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 + attempts to log relevant ones and guarantees that the TCP connection is closed before exiting. """ try: - try: - await self.handshake( - origins=self.origins, - available_extensions=self.available_extensions, - available_subprotocols=self.available_subprotocols, - extra_headers=self.extra_headers, - ) - # Remove this branch when dropping support for Python < 3.8 - # because CancelledError no longer inherits Exception. - except asyncio.CancelledError: # pragma: no cover + async with asyncio_timeout(self.open_timeout): + await self.handshake( + origins=self.origins, + available_extensions=self.available_extensions, + available_subprotocols=self.available_subprotocols, + extra_headers=self.extra_headers, + ) + except asyncio.TimeoutError: # pragma: no cover raise except ConnectionError: raise @@ -216,14 +218,16 @@ async def handler(self) -> None: ) headers.setdefault("Date", email.utils.formatdate(usegmt=True)) - headers.setdefault("Server", USER_AGENT) + if self.server_header is not None: + headers.setdefault("Server", self.server_header) + 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.logger.info( - "connection failed (%d %s)", status.value, status.phrase + "connection rejected (%d %s)", status.value, status.phrase ) await self.close_transport() return @@ -325,9 +329,9 @@ async def process_request( You may override this method in a :class:`WebSocketServerProtocol` subclass, for example: - * to return a HTTP 200 OK response on a given path; then a load + * to return an 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 + * to authenticate the request and return an HTTP 401 Unauthorized or an HTTP 403 Forbidden when authentication fails. You may also override this method with the ``process_request`` @@ -345,7 +349,7 @@ async def process_request( request_headers: request headers. Returns: - Optional[Tuple[http.HTTPStatus, HeadersLike, bytes]]: :obj:`None` + Optional[Tuple[StatusLike, HeadersLike, bytes]]: :obj:`None` to continue the WebSocket handshake normally. An HTTP response, represented by a 3-uple of the response status, @@ -439,15 +443,12 @@ def process_extensions( 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 @@ -499,7 +500,6 @@ def process_subprotocol( 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], [] ) @@ -516,31 +516,29 @@ def select_subprotocol( server_subprotocols: Sequence[Subprotocol], ) -> Optional[Subprotocol]: """ - Pick a subprotocol among those offered by the client. + Pick a subprotocol among those supported by the client and the server. - If several subprotocols are supported by the client and the server, - the default implementation selects the preferred subprotocol 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. + If several subprotocols are available, select the preferred subprotocol + by giving equal weight to the preferences of the client and the server. - This is unlikely to be the most useful implementation in practice. - Many servers providing a subprotocol will require that the client - uses that subprotocol. Such rules can be implemented in a subclass. + If no subprotocol is available, proceed without a subprotocol. - You may also override this method with the ``select_subprotocol`` - argument of :func:`serve` and :class:`WebSocketServerProtocol`. + You may provide a ``select_subprotocol`` argument to :func:`serve` or + :class:`WebSocketServerProtocol` to override this logic. For example, + you could reject the handshake if the client doesn't support a + particular subprotocol, rather than accept the handshake without that + subprotocol. Args: client_subprotocols: list of subprotocols offered by the client. server_subprotocols: list of subprotocols available on the server. Returns: - Optional[Subprotocol]: Selected subprotocol. + Optional[Subprotocol]: Selected subprotocol, if a common subprotocol + was found. :obj:`None` to continue without a subprotocol. - """ if self._select_subprotocol is not None: return self._select_subprotocol(client_subprotocols, server_subprotocols) @@ -548,10 +546,10 @@ def select_subprotocol( 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] + return sorted( + subprotocols, + key=lambda p: client_subprotocols.index(p) + server_subprotocols.index(p), + )[0] async def handshake( self, @@ -594,7 +592,8 @@ async def handshake( # The connection may drop while process_request is running. if self.state is State.CLOSED: - raise self.connection_closed_exc() # pragma: no cover + # This subclass of ConnectionError is silently ignored in handler(). + raise BrokenPipeError("connection closed during opening handshake") # Change the response to a 503 error if the server is shutting down. if not self.ws_server.is_serving(): @@ -635,7 +634,8 @@ async def handshake( response_headers.update(extra_headers) response_headers.setdefault("Date", email.utils.formatdate(usegmt=True)) - response_headers.setdefault("Server", USER_AGENT) + if self.server_header is not None: + response_headers.setdefault("Server", self.server_header) self.write_http_response(http.HTTPStatus.SWITCHING_PROTOCOLS, response_headers) @@ -658,9 +658,9 @@ class WebSocketServer: when shutting down. Args: - logger: logger for this server; - defaults to ``logging.getLogger("websockets.server")``; - see the :doc:`logging guide <../topics/logging>` for details. + logger: Logger for this server. + It defaults to ``logging.getLogger("websockets.server")``. + See the :doc:`logging guide <../../topics/logging>` for details. """ @@ -707,7 +707,7 @@ def wrap(self, server: asyncio.base_events.Server) -> None: self.logger.info("server listening on %s", name) # Initialized here because we need a reference to the event loop. - # This should be moved back to __init__ in Python 3.10. + # This should be moved back to __init__ when dropping Python < 3.10. self.closed_waiter = server.get_loop().create_future() def register(self, protocol: WebSocketServerProtocol) -> None: @@ -724,26 +724,30 @@ def unregister(self, protocol: WebSocketServerProtocol) -> None: """ self.websockets.remove(protocol) - def close(self) -> None: + def close(self, close_connections: bool = True) -> None: """ Close the server. - This method: + * Close the underlying :class:`~asyncio.Server`. + * When ``close_connections`` is :obj:`True`, which is the default, + close existing connections. Specifically: - * 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). + * Reject opening WebSocket connections with an HTTP 503 (service + unavailable) error. This happens when the server accepted the TCP + connection but didn't complete the opening handshake before closing. + * Close open WebSocket connections with close code 1001 (going away). + + * Wait until all connection handlers terminate. :meth:`close` is idempotent. """ if self.close_task is None: - self.close_task = self.get_loop().create_task(self._close()) + self.close_task = self.get_loop().create_task( + self._close(close_connections) + ) - async def _close(self) -> None: + async def _close(self, close_connections: bool) -> None: """ Implementation of :meth:`close`. @@ -757,36 +761,30 @@ async def _close(self) -> None: # 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_if_py_lt_38(self.get_loop())) - - # Close OPEN connections with status code 1001. Since the server was - # closed, handshake() closes OPENING connections with a HTTP 503 - # error. Wait until all connections are closed. - - close_tasks = [ - asyncio.create_task(websocket.close(1001)) - for websocket in self.websockets - if websocket.state is not State.CONNECTING - ] - # asyncio.wait doesn't accept an empty first argument. - if close_tasks: - await asyncio.wait( - close_tasks, - **loop_if_py_lt_38(self.get_loop()), - ) - - # Wait until all connection handlers are complete. + await asyncio.sleep(0) + + if close_connections: + # Close OPEN connections with close code 1001. After server.close(), + # handshake() closes OPENING connections with an HTTP 503 error. + close_tasks = [ + asyncio.create_task(websocket.close(1001)) + for websocket in self.websockets + if websocket.state is not State.CONNECTING + ] + # asyncio.wait doesn't accept an empty first argument. + if close_tasks: + await asyncio.wait(close_tasks) + + # Wait until all TCP connections are closed. + await self.server.wait_closed() + # Wait until all connection handlers terminate. # asyncio.wait doesn't accept an empty first argument. if self.websockets: await asyncio.wait( - [websocket.handler_task for websocket in self.websockets], - **loop_if_py_lt_38(self.get_loop()), + [websocket.handler_task for websocket in self.websockets] ) # Tell wait_closed() to return. @@ -829,19 +827,37 @@ def is_serving(self) -> bool: """ return self.server.is_serving() - async def start_serving(self) -> None: + async def start_serving(self) -> None: # pragma: no cover """ See :meth:`asyncio.Server.start_serving`. + Typical use:: + + server = await serve(..., start_serving=False) + # perform additional setup here... + # ... then start the server + await server.start_serving() + """ - await self.server.start_serving() # pragma: no cover + await self.server.start_serving() - async def serve_forever(self) -> None: + async def serve_forever(self) -> None: # pragma: no cover """ See :meth:`asyncio.Server.serve_forever`. + Typical use:: + + server = await serve(...) + # this coroutine doesn't return + # canceling it stops the server + await server.serve_forever() + + This is an alternative to using :func:`serve` as an asynchronous context + manager. Shutdown is triggered by canceling :meth:`serve_forever` + instead of exiting a :func:`serve` context. + """ - await self.server.serve_forever() # pragma: no cover + await self.server.serve_forever() @property def sockets(self) -> Iterable[socket.socket]: @@ -851,17 +867,17 @@ def sockets(self) -> Iterable[socket.socket]: """ return self.server.sockets - async def __aenter__(self) -> WebSocketServer: - return self # pragma: no cover + async def __aenter__(self) -> WebSocketServer: # pragma: no cover + return self async def __aexit__( self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], traceback: Optional[TracebackType], - ) -> None: - self.close() # pragma: no cover - await self.wait_closed() # pragma: no cover + ) -> None: # pragma: no cover + self.close() + await self.wait_closed() class Serve: @@ -879,53 +895,61 @@ class Serve: server performs the closing handshake and closes the connection. Awaiting :func:`serve` yields a :class:`WebSocketServer`. This object - provides :meth:`~WebSocketServer.close` and - :meth:`~WebSocketServer.wait_closed` methods for shutting down the server. + provides a :meth:`~WebSocketServer.close` method to shut down the server:: - :func:`serve` can be used as an asynchronous context manager:: + stop = asyncio.Future() # set this future to exit the server + + server = await serve(...) + await stop + await server.close() + + :func:`serve` can be used as an asynchronous context manager. Then, the + server is shut down automatically when exiting the context:: stop = asyncio.Future() # set this future to exit the server async with serve(...): await stop - The server is shut down automatically when exiting the context. - Args: - ws_handler: connection handler. It receives the WebSocket connection, + ws_handler: Connection handler. It receives the WebSocket connection, which is a :class:`WebSocketServerProtocol`, in argument. - host: network interfaces the server is bound to; - see :meth:`~asyncio.loop.create_server` for details. - port: TCP port the server listens on; - see :meth:`~asyncio.loop.create_server` for details. - create_protocol: factory for the :class:`asyncio.Protocol` managing - the connection; defaults to :class:`WebSocketServerProtocol`; may - be set to a wrapper or a subclass to customize connection handling. - logger: logger for this server; - defaults to ``logging.getLogger("websockets.server")``; - see the :doc:`logging guide <../topics/logging>` for details. - compression: shortcut that enables the "permessage-deflate" extension - by default; may be set to :obj:`None` to disable compression; - see the :doc:`compression guide <../topics/compression>` for details. - origins: acceptable values of the ``Origin`` header; include - :obj:`None` in the list if the lack of an origin is acceptable. - This is useful for defending against Cross-Site WebSocket - Hijacking attacks. - extensions: list of supported extensions, in order in which they - should be tried. - subprotocols: list of supported subprotocols, in order of decreasing + host: Network interfaces the server binds to. + See :meth:`~asyncio.loop.create_server` for details. + port: TCP port the server listens on. + See :meth:`~asyncio.loop.create_server` for details. + create_protocol: Factory for the :class:`asyncio.Protocol` managing + the connection. It defaults to :class:`WebSocketServerProtocol`. + Set it to a wrapper or a subclass to customize connection handling. + logger: Logger for this server. + It defaults to ``logging.getLogger("websockets.server")``. + See the :doc:`logging guide <../../topics/logging>` for details. + compression: The "permessage-deflate" extension is enabled by default. + Set ``compression`` to :obj:`None` to disable it. See the + :doc:`compression guide <../../topics/compression>` for details. + origins: Acceptable values of the ``Origin`` header, for defending + against Cross-Site WebSocket Hijacking attacks. Include :obj:`None` + in the list if the lack of an origin is acceptable. + extensions: List of supported extensions, in order in which they + should be negotiated and run. + subprotocols: List of supported subprotocols, in order of decreasing preference. extra_headers (Union[HeadersLike, Callable[[str, Headers], HeadersLike]]): - arbitrary HTTP headers to add to the request; this can be + Arbitrary HTTP headers to add to the response. This can be a :data:`~websockets.datastructures.HeadersLike` or a callable taking the request path and headers in arguments and returning a :data:`~websockets.datastructures.HeadersLike`. + server_header: Value of the ``Server`` response header. + It defaults to ``"Python/x.y.z websockets/X.Y"``. + Setting it to :obj:`None` removes the header. process_request (Optional[Callable[[str, Headers], \ - Awaitable[Optional[Tuple[http.HTTPStatus, HeadersLike, bytes]]]]]): - intercept HTTP request before the opening handshake; - see :meth:`~WebSocketServerProtocol.process_request` for details. - select_subprotocol: select a subprotocol supported by the client; - see :meth:`~WebSocketServerProtocol.select_subprotocol` for details. + Awaitable[Optional[Tuple[StatusLike, HeadersLike, bytes]]]]]): + Intercept HTTP request before the opening handshake. + See :meth:`~WebSocketServerProtocol.process_request` for details. + select_subprotocol: Select a subprotocol supported by the client. + See :meth:`~WebSocketServerProtocol.select_subprotocol` for details. + open_timeout: Timeout for opening connections in seconds. + :obj:`None` disables the timeout. See :class:`~websockets.legacy.protocol.WebSocketCommonProtocol` for the documentation of ``ping_interval``, ``ping_timeout``, ``close_timeout``, @@ -955,19 +979,21 @@ def __init__( host: Optional[Union[str, Sequence[str]]] = None, port: Optional[int] = None, *, - create_protocol: Optional[Callable[[Any], WebSocketServerProtocol]] = None, + create_protocol: Optional[Callable[..., WebSocketServerProtocol]] = None, logger: Optional[LoggerLike] = 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, + server_header: Optional[str] = USER_AGENT, process_request: Optional[ Callable[[str, Headers], Awaitable[Optional[HTTPResponse]]] ] = None, select_subprotocol: Optional[ Callable[[Sequence[Subprotocol], Sequence[Subprotocol]], Subprotocol] ] = None, + open_timeout: Optional[float] = 10, ping_interval: Optional[float] = 20, ping_timeout: Optional[float] = 20, close_timeout: Optional[float] = None, @@ -1030,6 +1056,7 @@ def __init__( host=host, port=port, secure=secure, + open_timeout=open_timeout, ping_interval=ping_interval, ping_timeout=ping_timeout, close_timeout=close_timeout, @@ -1043,6 +1070,7 @@ def __init__( extensions=extensions, subprotocols=subprotocols, extra_headers=extra_headers, + server_header=server_header, process_request=process_request, select_subprotocol=select_subprotocol, logger=logger, @@ -1106,17 +1134,18 @@ def unix_serve( **kwargs: Any, ) -> Serve: """ - Similar to :func:`serve`, but for listening on Unix sockets. + Start a WebSocket server listening on a Unix socket. - This function builds upon the event - loop's :meth:`~asyncio.loop.create_unix_server` method. + This function is identical to :func:`serve`, except the ``host`` and + ``port`` arguments are replaced by ``path``. It is only available on Unix. - It is only available on Unix. + Unrecognized keyword arguments are passed the event loop's + :meth:`~asyncio.loop.create_unix_server` method. It's useful for deploying a server behind a reverse proxy such as nginx. Args: - path: file system path to the Unix socket. + path: File system path to the Unix socket. """ return serve(ws_handler, path=path, unix=True, **kwargs) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/w3c-import.log new file mode 100644 index 0000000000000..85d3e85767636 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/w3c-import.log @@ -0,0 +1,26 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/async_timeout.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/auth.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/compatibility.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/framing.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/handshake.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/http.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/protocol.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/legacy/server.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/protocol.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/protocol.py new file mode 100644 index 0000000000000..765e6b9bb4b72 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/protocol.py @@ -0,0 +1,708 @@ +from __future__ import annotations + +import enum +import logging +import uuid +from typing import Generator, List, Optional, Type, Union + +from .exceptions import ( + ConnectionClosed, + ConnectionClosedError, + ConnectionClosedOK, + InvalidState, + PayloadTooBig, + ProtocolError, +) +from .extensions import Extension +from .frames import ( + OK_CLOSE_CODES, + OP_BINARY, + OP_CLOSE, + OP_CONT, + OP_PING, + OP_PONG, + OP_TEXT, + Close, + CloseCode, + Frame, +) +from .http11 import Request, Response +from .streams import StreamReader +from .typing import LoggerLike, Origin, Subprotocol + + +__all__ = [ + "Protocol", + "Side", + "State", + "SEND_EOF", +] + +Event = Union[Request, Response, Frame] +"""Events that :meth:`~Protocol.events_received` may return.""" + + +class Side(enum.IntEnum): + """A WebSocket connection is either a server or a client.""" + + SERVER, CLIENT = range(2) + + +SERVER = Side.SERVER +CLIENT = Side.CLIENT + + +class State(enum.IntEnum): + """A WebSocket connection is in one of these four states.""" + + CONNECTING, OPEN, CLOSING, CLOSED = range(4) + + +CONNECTING = State.CONNECTING +OPEN = State.OPEN +CLOSING = State.CLOSING +CLOSED = State.CLOSED + + +SEND_EOF = b"" +"""Sentinel signaling that the TCP connection must be half-closed.""" + + +class Protocol: + """ + Sans-I/O implementation of a WebSocket connection. + + Args: + side: :attr:`~Side.CLIENT` or :attr:`~Side.SERVER`. + state: initial state of the WebSocket connection. + max_size: maximum size of incoming messages in bytes; + :obj:`None` disables the limit. + logger: logger for this connection; depending on ``side``, + defaults to ``logging.getLogger("websockets.client")`` + or ``logging.getLogger("websockets.server")``; + see the :doc:`logging guide <../../topics/logging>` for details. + + """ + + def __init__( + self, + side: Side, + *, + state: State = OPEN, + max_size: Optional[int] = 2**20, + logger: Optional[LoggerLike] = None, + ) -> None: + # Unique identifier. For logs. + self.id: uuid.UUID = uuid.uuid4() + """Unique identifier of the connection. Useful in logs.""" + + # Logger or LoggerAdapter for this connection. + if logger is None: + logger = logging.getLogger(f"websockets.{side.name.lower()}") + self.logger: LoggerLike = logger + """Logger for this connection.""" + + # Track if DEBUG is enabled. Shortcut logging calls if it isn't. + self.debug = logger.isEnabledFor(logging.DEBUG) + + # Connection side. CLIENT or SERVER. + self.side = side + + # Connection state. Initially OPEN because subclasses handle CONNECTING. + self.state = state + + # Maximum size of incoming messages in bytes. + self.max_size = max_size + + # Current size of incoming message in bytes. Only set while reading a + # fragmented message i.e. a data frames with the FIN bit not set. + self.cur_size: Optional[int] = None + + # True while sending a fragmented message i.e. a data frames with the + # FIN bit not set. + self.expect_continuation_frame = False + + # WebSocket protocol parameters. + self.origin: Optional[Origin] = None + self.extensions: List[Extension] = [] + self.subprotocol: Optional[Subprotocol] = None + + # Close code and reason, set when a close frame is sent or received. + self.close_rcvd: Optional[Close] = None + self.close_sent: Optional[Close] = None + self.close_rcvd_then_sent: Optional[bool] = None + + # Track if an exception happened during the handshake. + self.handshake_exc: Optional[Exception] = None + """ + Exception to raise if the opening handshake failed. + + :obj:`None` if the opening handshake succeeded. + + """ + + # Track if send_eof() was called. + self.eof_sent = False + + # Parser state. + self.reader = StreamReader() + self.events: List[Event] = [] + self.writes: List[bytes] = [] + self.parser = self.parse() + next(self.parser) # start coroutine + self.parser_exc: Optional[Exception] = None + + @property + def state(self) -> State: + """ + WebSocket connection state. + + Defined in 4.1, 4.2, 7.1.3, and 7.1.4 of :rfc:`6455`. + + """ + return self._state + + @state.setter + def state(self, state: State) -> None: + if self.debug: + self.logger.debug("= connection is %s", state.name) + self._state = state + + @property + def close_code(self) -> Optional[int]: + """ + `WebSocket close code`_. + + .. _WebSocket close code: + https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5 + + :obj:`None` if the connection isn't closed yet. + + """ + if self.state is not CLOSED: + return None + elif self.close_rcvd is None: + return CloseCode.ABNORMAL_CLOSURE + else: + return self.close_rcvd.code + + @property + def close_reason(self) -> Optional[str]: + """ + `WebSocket close reason`_. + + .. _WebSocket close reason: + https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.6 + + :obj:`None` if the connection isn't closed yet. + + """ + if self.state is not CLOSED: + return None + elif self.close_rcvd is None: + return "" + else: + return self.close_rcvd.reason + + @property + def close_exc(self) -> ConnectionClosed: + """ + Exception to raise when trying to interact with a closed connection. + + Don't raise this exception while the connection :attr:`state` + is :attr:`~websockets.protocol.State.CLOSING`; wait until + it's :attr:`~websockets.protocol.State.CLOSED`. + + Indeed, the exception includes the close code and reason, which are + known only once the connection is closed. + + Raises: + AssertionError: if the connection isn't closed yet. + + """ + assert self.state is CLOSED, "connection isn't closed yet" + exc_type: Type[ConnectionClosed] + if ( + self.close_rcvd is not None + and self.close_sent is not None + and self.close_rcvd.code in OK_CLOSE_CODES + and self.close_sent.code in OK_CLOSE_CODES + ): + exc_type = ConnectionClosedOK + else: + exc_type = ConnectionClosedError + exc: ConnectionClosed = exc_type( + self.close_rcvd, + self.close_sent, + self.close_rcvd_then_sent, + ) + # Chain to the exception raised in the parser, if any. + exc.__cause__ = self.parser_exc + return exc + + # Public methods for receiving data. + + def receive_data(self, data: bytes) -> None: + """ + Receive data from the network. + + After calling this method: + + - You must call :meth:`data_to_send` and send this data to the network. + - You should call :meth:`events_received` and process resulting events. + + Raises: + EOFError: if :meth:`receive_eof` was called earlier. + + """ + self.reader.feed_data(data) + next(self.parser) + + def receive_eof(self) -> None: + """ + Receive the end of the data stream from the network. + + After calling this method: + + - You must call :meth:`data_to_send` and send this data to the network; + it will return ``[b""]``, signaling the end of the stream, or ``[]``. + - You aren't expected to call :meth:`events_received`; it won't return + any new events. + + Raises: + EOFError: if :meth:`receive_eof` was called earlier. + + """ + self.reader.feed_eof() + next(self.parser) + + # Public methods for sending events. + + def send_continuation(self, data: bytes, fin: bool) -> None: + """ + Send a `Continuation frame`_. + + .. _Continuation frame: + https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + Parameters: + data: payload containing the same kind of data + as the initial frame. + fin: FIN bit; set it to :obj:`True` if this is the last frame + of a fragmented message and to :obj:`False` otherwise. + + Raises: + ProtocolError: if a fragmented message isn't in progress. + + """ + if not self.expect_continuation_frame: + raise ProtocolError("unexpected continuation frame") + self.expect_continuation_frame = not fin + self.send_frame(Frame(OP_CONT, data, fin)) + + def send_text(self, data: bytes, fin: bool = True) -> None: + """ + Send a `Text frame`_. + + .. _Text frame: + https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + Parameters: + data: payload containing text encoded with UTF-8. + fin: FIN bit; set it to :obj:`False` if this is the first frame of + a fragmented message. + + Raises: + ProtocolError: if a fragmented message is in progress. + + """ + if self.expect_continuation_frame: + raise ProtocolError("expected a continuation frame") + self.expect_continuation_frame = not fin + self.send_frame(Frame(OP_TEXT, data, fin)) + + def send_binary(self, data: bytes, fin: bool = True) -> None: + """ + Send a `Binary frame`_. + + .. _Binary frame: + https://datatracker.ietf.org/doc/html/rfc6455#section-5.6 + + Parameters: + data: payload containing arbitrary binary data. + fin: FIN bit; set it to :obj:`False` if this is the first frame of + a fragmented message. + + Raises: + ProtocolError: if a fragmented message is in progress. + + """ + if self.expect_continuation_frame: + raise ProtocolError("expected a continuation frame") + self.expect_continuation_frame = not fin + self.send_frame(Frame(OP_BINARY, data, fin)) + + def send_close(self, code: Optional[int] = None, reason: str = "") -> None: + """ + Send a `Close frame`_. + + .. _Close frame: + https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.1 + + Parameters: + code: close code. + reason: close reason. + + Raises: + ProtocolError: if a fragmented message is being sent, if the code + isn't valid, or if a reason is provided without a code + + """ + if self.expect_continuation_frame: + raise ProtocolError("expected a continuation frame") + if code is None: + if reason != "": + raise ProtocolError("cannot send a reason without a code") + close = Close(CloseCode.NO_STATUS_RCVD, "") + data = b"" + else: + close = Close(code, reason) + data = close.serialize() + # send_frame() guarantees that self.state is OPEN at this point. + # 7.1.3. The WebSocket Closing Handshake is Started + self.send_frame(Frame(OP_CLOSE, data)) + self.close_sent = close + self.state = CLOSING + + def send_ping(self, data: bytes) -> None: + """ + Send a `Ping frame`_. + + .. _Ping frame: + https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2 + + Parameters: + data: payload containing arbitrary binary data. + + """ + self.send_frame(Frame(OP_PING, data)) + + def send_pong(self, data: bytes) -> None: + """ + Send a `Pong frame`_. + + .. _Pong frame: + https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.3 + + Parameters: + data: payload containing arbitrary binary data. + + """ + self.send_frame(Frame(OP_PONG, data)) + + def fail(self, code: int, reason: str = "") -> None: + """ + `Fail the WebSocket connection`_. + + .. _Fail the WebSocket connection: + https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.7 + + Parameters: + code: close code + reason: close reason + + Raises: + ProtocolError: if the code isn't valid. + """ + # 7.1.7. Fail the WebSocket Connection + + # 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. + if self.state is OPEN: + if code != CloseCode.ABNORMAL_CLOSURE: + close = Close(code, reason) + data = close.serialize() + self.send_frame(Frame(OP_CLOSE, data)) + self.close_sent = close + self.state = CLOSING + + # When failing the connection, a server closes the TCP connection + # without waiting for the client to complete the handshake, while a + # client waits for the server to close the TCP connection, possibly + # after sending a close frame that the client will ignore. + if self.side is SERVER and not self.eof_sent: + self.send_eof() + + # 7.1.7. Fail the WebSocket Connection "An endpoint MUST NOT continue + # to attempt to process data(including a responding Close frame) from + # the remote endpoint after being instructed to _Fail the WebSocket + # Connection_." + self.parser = self.discard() + next(self.parser) # start coroutine + + # Public method for getting incoming events after receiving data. + + def events_received(self) -> List[Event]: + """ + Fetch events generated from data received from the network. + + Call this method immediately after any of the ``receive_*()`` methods. + + Process resulting events, likely by passing them to the application. + + Returns: + List[Event]: Events read from the connection. + """ + events, self.events = self.events, [] + return events + + # Public method for getting outgoing data after receiving data or sending events. + + def data_to_send(self) -> List[bytes]: + """ + Obtain data to send to the network. + + Call this method immediately after any of the ``receive_*()``, + ``send_*()``, or :meth:`fail` methods. + + Write resulting data to the connection. + + The empty bytestring :data:`~websockets.protocol.SEND_EOF` signals + the end of the data stream. When you receive it, half-close the TCP + connection. + + Returns: + List[bytes]: Data to write to the connection. + + """ + writes, self.writes = self.writes, [] + return writes + + def close_expected(self) -> bool: + """ + Tell if the TCP connection is expected to close soon. + + Call this method immediately after any of the ``receive_*()``, + ``send_close()``, or :meth:`fail` methods. + + If it returns :obj:`True`, schedule closing the TCP connection after a + short timeout if the other side hasn't already closed it. + + Returns: + bool: Whether the TCP connection is expected to close soon. + + """ + # We expect a TCP close if and only if we sent a close frame: + # * Normal closure: once we send a close frame, we expect a TCP close: + # server waits for client to complete the TCP closing handshake; + # client waits for server to initiate the TCP closing handshake. + # * Abnormal closure: we always send a close frame and the same logic + # applies, except on EOFError where we don't send a close frame + # because we already received the TCP close, so we don't expect it. + # We already got a TCP Close if and only if the state is CLOSED. + return self.state is CLOSING or self.handshake_exc is not None + + # Private methods for receiving data. + + def parse(self) -> Generator[None, None, None]: + """ + Parse incoming data into frames. + + :meth:`receive_data` and :meth:`receive_eof` run this generator + coroutine until it needs more data or reaches EOF. + + :meth:`parse` never raises an exception. Instead, it sets the + :attr:`parser_exc` and yields control. + + """ + try: + while True: + if (yield from self.reader.at_eof()): + if self.debug: + self.logger.debug("< EOF") + # If the WebSocket connection is closed cleanly, with a + # closing handhshake, recv_frame() substitutes parse() + # with discard(). This branch is reached only when the + # connection isn't closed cleanly. + raise EOFError("unexpected end of stream") + + if self.max_size is None: + max_size = None + elif self.cur_size is None: + max_size = self.max_size + else: + max_size = self.max_size - self.cur_size + + # During a normal closure, execution ends here on the next + # iteration of the loop after receiving a close frame. At + # this point, recv_frame() replaced parse() by discard(). + frame = yield from Frame.parse( + self.reader.read_exact, + mask=self.side is SERVER, + max_size=max_size, + extensions=self.extensions, + ) + + if self.debug: + self.logger.debug("< %s", frame) + + self.recv_frame(frame) + + except ProtocolError as exc: + self.fail(CloseCode.PROTOCOL_ERROR, str(exc)) + self.parser_exc = exc + + except EOFError as exc: + self.fail(CloseCode.ABNORMAL_CLOSURE, str(exc)) + self.parser_exc = exc + + except UnicodeDecodeError as exc: + self.fail(CloseCode.INVALID_DATA, f"{exc.reason} at position {exc.start}") + self.parser_exc = exc + + except PayloadTooBig as exc: + self.fail(CloseCode.MESSAGE_TOO_BIG, str(exc)) + self.parser_exc = exc + + except Exception as exc: + self.logger.error("parser failed", exc_info=True) + # Don't include exception details, which may be security-sensitive. + self.fail(CloseCode.INTERNAL_ERROR) + self.parser_exc = exc + + # During an abnormal closure, execution ends here after catching an + # exception. At this point, fail() replaced parse() by discard(). + yield + raise AssertionError("parse() shouldn't step after error") + + def discard(self) -> Generator[None, None, None]: + """ + Discard incoming data. + + This coroutine replaces :meth:`parse`: + + - after receiving a close frame, during a normal closure (1.4); + - after sending a close frame, during an abnormal closure (7.1.7). + + """ + # The server close the TCP connection in the same circumstances where + # discard() replaces parse(). The client closes the connection later, + # after the server closes the connection or a timeout elapses. + # (The latter case cannot be handled in this Sans-I/O layer.) + assert (self.side is SERVER) == (self.eof_sent) + while not (yield from self.reader.at_eof()): + self.reader.discard() + if self.debug: + self.logger.debug("< EOF") + # A server closes the TCP connection immediately, while a client + # waits for the server to close the TCP connection. + if self.side is CLIENT: + self.send_eof() + self.state = CLOSED + # If discard() completes normally, execution ends here. + yield + # Once the reader reaches EOF, its feed_data/eof() methods raise an + # error, so our receive_data/eof() methods don't step the generator. + raise AssertionError("discard() shouldn't step after EOF") + + def recv_frame(self, frame: Frame) -> None: + """ + Process an incoming frame. + + """ + if frame.opcode is OP_TEXT or frame.opcode is OP_BINARY: + if self.cur_size is not None: + raise ProtocolError("expected a continuation frame") + if frame.fin: + self.cur_size = None + else: + self.cur_size = len(frame.data) + + elif frame.opcode is OP_CONT: + if self.cur_size is None: + raise ProtocolError("unexpected continuation frame") + if frame.fin: + self.cur_size = None + else: + self.cur_size += len(frame.data) + + elif frame.opcode is OP_PING: + # 5.5.2. Ping: "Upon receipt of a Ping frame, an endpoint MUST + # send a Pong frame in response" + pong_frame = Frame(OP_PONG, frame.data) + self.send_frame(pong_frame) + + elif frame.opcode is OP_PONG: + # 5.5.3 Pong: "A response to an unsolicited Pong frame is not + # expected." + pass + + elif frame.opcode is OP_CLOSE: + # 7.1.5. The WebSocket Connection Close Code + # 7.1.6. The WebSocket Connection Close Reason + self.close_rcvd = Close.parse(frame.data) + if self.state is CLOSING: + assert self.close_sent is not None + self.close_rcvd_then_sent = False + + if self.cur_size is not None: + raise ProtocolError("incomplete fragmented message") + + # 5.5.1 Close: "If an endpoint receives a Close frame and did + # not previously send a Close frame, the endpoint MUST send a + # Close frame in response. (When sending a Close frame in + # response, the endpoint typically echos the status code it + # received.)" + + if self.state is OPEN: + # Echo the original data instead of re-serializing it with + # Close.serialize() because that fails when the close frame + # is empty and Close.parse() synthesizes a 1005 close code. + # The rest is identical to send_close(). + self.send_frame(Frame(OP_CLOSE, frame.data)) + self.close_sent = self.close_rcvd + self.close_rcvd_then_sent = True + self.state = CLOSING + + # 7.1.2. Start the WebSocket Closing Handshake: "Once an + # endpoint has both sent and received a Close control frame, + # that endpoint SHOULD _Close the WebSocket Connection_" + + # A server closes the TCP connection immediately, while a client + # waits for the server to close the TCP connection. + if self.side is SERVER: + self.send_eof() + + # 1.4. Closing Handshake: "after receiving a control frame + # indicating the connection should be closed, a peer discards + # any further data received." + self.parser = self.discard() + next(self.parser) # start coroutine + + else: + # This can't happen because Frame.parse() validates opcodes. + raise AssertionError(f"unexpected opcode: {frame.opcode:02x}") + + self.events.append(frame) + + # Private methods for sending events. + + def send_frame(self, frame: Frame) -> None: + if self.state is not OPEN: + raise InvalidState( + f"cannot write to a WebSocket in the {self.state.name} state" + ) + + if self.debug: + self.logger.debug("> %s", frame) + self.writes.append( + frame.serialize(mask=self.side is CLIENT, extensions=self.extensions) + ) + + def send_eof(self) -> None: + assert not self.eof_sent + self.eof_sent = True + if self.debug: + self.logger.debug("> EOF") + self.writes.append(SEND_EOF) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/server.py index 5dad50b6a12a6..191660553ffd2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/server.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/server.py @@ -4,9 +4,9 @@ import binascii import email.utils import http -from typing import Generator, List, Optional, Sequence, Tuple, cast +import warnings +from typing import Any, Callable, Generator, List, Optional, Sequence, Tuple, cast -from .connection import CONNECTING, OPEN, SERVER, Connection, State from .datastructures import Headers, MultipleValuesError from .exceptions import ( InvalidHandshake, @@ -25,13 +25,14 @@ parse_subprotocol, parse_upgrade, ) -from .http import USER_AGENT from .http11 import Request, Response +from .protocol import CONNECTING, OPEN, SERVER, Protocol, State from .typing import ( ConnectionOption, ExtensionHeader, LoggerLike, Origin, + StatusLike, Subprotocol, UpgradeProtocol, ) @@ -39,13 +40,15 @@ # See #940 for why lazy_import isn't used here for backwards compatibility. -from .legacy.server import * # isort:skip # noqa +# See #1400 for why listing compatibility imports in __all__ helps PyCharm. +from .legacy.server import * # isort:skip # noqa: I001 +from .legacy.server import __all__ as legacy__all__ -__all__ = ["ServerConnection"] +__all__ = ["ServerProtocol"] + legacy__all__ -class ServerConnection(Connection): +class ServerProtocol(Protocol): """ Sans-I/O implementation of a WebSocket server connection. @@ -58,20 +61,31 @@ class ServerConnection(Connection): should be tried. subprotocols: list of supported subprotocols, in order of decreasing preference. + select_subprotocol: Callback for selecting a subprotocol among + those supported by the client and the server. It has the same + signature as the :meth:`select_subprotocol` method, including a + :class:`ServerProtocol` instance as first argument. state: initial state of the WebSocket connection. max_size: maximum size of incoming messages in bytes; - :obj:`None` to disable the limit. + :obj:`None` disables the limit. logger: logger for this connection; defaults to ``logging.getLogger("websockets.client")``; - see the :doc:`logging guide <../topics/logging>` for details. + see the :doc:`logging guide <../../topics/logging>` for details. """ def __init__( self, + *, origins: Optional[Sequence[Optional[Origin]]] = None, extensions: Optional[Sequence[ServerExtensionFactory]] = None, subprotocols: Optional[Sequence[Subprotocol]] = None, + select_subprotocol: Optional[ + Callable[ + [ServerProtocol, Sequence[Subprotocol]], + Optional[Subprotocol], + ] + ] = None, state: State = CONNECTING, max_size: Optional[int] = 2**20, logger: Optional[LoggerLike] = None, @@ -85,6 +99,14 @@ def __init__( self.origins = origins self.available_extensions = extensions self.available_subprotocols = subprotocols + if select_subprotocol is not None: + # Bind select_subprotocol then shadow self.select_subprotocol. + # Use setattr to work around https://github.com/python/mypy/issues/2427. + setattr( + self, + "select_subprotocol", + select_subprotocol.__get__(self, self.__class__), + ) def accept(self, request: Request) -> Response: """ @@ -95,13 +117,13 @@ def accept(self, request: Request) -> Response: You must send the handshake response with :meth:`send_response`. - You can modify it before sending it, for example to add HTTP headers. + You may modify it before sending it, for example to add HTTP headers. Args: request: WebSocket handshake request event received from the client. Returns: - Response: WebSocket handshake response event to send to the client. + WebSocket handshake response event to send to the client. """ try: @@ -145,6 +167,8 @@ def accept(self, request: Request) -> Response: f"Failed to open a WebSocket connection: {exc}.\n", ) except Exception as exc: + # Handle exceptions raised by user-provided select_subprotocol and + # unexpected errors. request._exception = exc self.handshake_exc = exc self.logger.error("opening handshake failed", exc_info=True) @@ -170,13 +194,12 @@ def accept(self, request: Request) -> Response: if protocol_header is not None: headers["Sec-WebSocket-Protocol"] = protocol_header - headers["Server"] = USER_AGENT - self.logger.info("connection open") return Response(101, "Switching Protocols", headers) def process_request( - self, request: Request + self, + request: Request, ) -> Tuple[str, Optional[str], Optional[str]]: """ Check a handshake request and negotiate extensions and subprotocol. @@ -274,6 +297,7 @@ def process_origin(self, headers: Headers) -> Optional[Origin]: Optional[Origin]: origin, if it is acceptable. Raises: + InvalidHandshake: if the Origin header is invalid. InvalidOrigin: if the origin isn't acceptable. """ @@ -298,8 +322,8 @@ def process_extensions( Accept or reject each extension proposed in the client request. Negotiate parameters for accepted extensions. - :rfc:`6455` leaves the rules up to the specification of each - :extension. + Per :rfc:`6455`, negotiation rules are defined by 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 @@ -324,7 +348,7 @@ def process_extensions( HTTP response header and list of accepted extensions. Raises: - InvalidHandshake: to abort the handshake with an HTTP 400 error. + InvalidHandshake: if the Sec-WebSocket-Extensions header is invalid. """ response_header_value: Optional[str] = None @@ -335,15 +359,12 @@ def process_extensions( header_values = headers.get_all("Sec-WebSocket-Extensions") if header_values and self.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 self.available_extensions: - # Skip non-matching extensions based on their name. if ext_factory.name != name: continue @@ -384,64 +405,83 @@ def process_subprotocol(self, headers: Headers) -> Optional[Subprotocol]: also the value of the ``Sec-WebSocket-Protocol`` response header. Raises: - InvalidHandshake: to abort the handshake with an HTTP 400 error. + InvalidHandshake: if the Sec-WebSocket-Subprotocol header is invalid. """ - subprotocol: Optional[Subprotocol] = None - - header_values = headers.get_all("Sec-WebSocket-Protocol") - - if header_values and self.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, self.available_subprotocols - ) + subprotocols: Sequence[Subprotocol] = sum( + [ + parse_subprotocol(header_value) + for header_value in headers.get_all("Sec-WebSocket-Protocol") + ], + [], + ) - return subprotocol + return self.select_subprotocol(subprotocols) def select_subprotocol( self, - client_subprotocols: Sequence[Subprotocol], - server_subprotocols: Sequence[Subprotocol], + 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 several subprotocols are supported by both the client and the server, + pick the first one in the list declared the server. + + If the server doesn't support any subprotocols, continue without a + subprotocol, regardless of what the client offers. + + If the server supports at least one subprotocol and the client doesn't + offer any, abort the handshake with an HTTP 400 error. - If no common subprotocol is supported by the client and the server, it - proceeds without a subprotocol. + You provide a ``select_subprotocol`` argument to :class:`ServerProtocol` + to override this logic. For example, you could accept the connection + even if client doesn't offer a subprotocol, rather than reject it. - 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. + Here's how to negotiate the ``chat`` subprotocol if the client supports + it and continue without a subprotocol otherwise:: + + def select_subprotocol(protocol, subprotocols): + if "chat" in subprotocols: + return "chat" Args: - client_subprotocols: list of subprotocols offered by the client. - server_subprotocols: list of subprotocols available on the server. + subprotocols: list of subprotocols offered by the client. Returns: - Optional[Subprotocol]: Subprotocol, if a common subprotocol was - found. + Optional[Subprotocol]: Selected subprotocol, if a common subprotocol + was found. + + :obj:`None` to continue without a subprotocol. + + Raises: + NegotiationError: custom implementations may raise this exception + to abort the handshake with an HTTP 400 error. """ - subprotocols = set(client_subprotocols) & set(server_subprotocols) - if not subprotocols: + # Server doesn't offer any subprotocols. + if not self.available_subprotocols: # None or empty list return None - priority = lambda p: ( - client_subprotocols.index(p) + server_subprotocols.index(p) + + # Server offers at least one subprotocol but client doesn't offer any. + if not subprotocols: + raise NegotiationError("missing subprotocol") + + # Server and client both offer subprotocols. Look for a shared one. + proposed_subprotocols = set(subprotocols) + for subprotocol in self.available_subprotocols: + if subprotocol in proposed_subprotocols: + return subprotocol + + # No common subprotocol was found. + raise NegotiationError( + "invalid subprotocol; expected one of " + + ", ".join(self.available_subprotocols) ) - return sorted(subprotocols, key=priority)[0] def reject( self, - status: http.HTTPStatus, + status: StatusLike, text: str, ) -> Response: """ @@ -462,6 +502,8 @@ def reject( Response: WebSocket handshake response event to send to the client. """ + # If a user passes an int instead of a HTTPStatus, fix it automatically. + status = http.HTTPStatus(status) body = text.encode() headers = Headers( [ @@ -469,16 +511,15 @@ def reject( ("Connection", "close"), ("Content-Length", str(len(body))), ("Content-Type", "text/plain; charset=utf-8"), - ("Server", USER_AGENT), ] ) response = Response(status.value, status.phrase, headers, body) # When reject() is called from accept(), handshake_exc is already set. # If a user calls reject(), set handshake_exc to guarantee invariant: - # "handshake_exc is None if and only if opening handshake succeded." + # "handshake_exc is None if and only if opening handshake succeeded." if self.handshake_exc is None: self.handshake_exc = InvalidStatus(response) - self.logger.info("connection failed (%d %s)", status.value, status.phrase) + self.logger.info("connection rejected (%d %s)", status.value, status.phrase) return response def send_response(self, response: Response) -> None: @@ -509,7 +550,16 @@ def send_response(self, response: Response) -> None: def parse(self) -> Generator[None, None, None]: if self.state is CONNECTING: - request = yield from Request.parse(self.reader.read_line) + try: + request = yield from Request.parse( + self.reader.read_line, + ) + except Exception as exc: + self.handshake_exc = exc + self.send_eof() + self.parser = self.discard() + next(self.parser) # start coroutine + yield if self.debug: self.logger.debug("< GET %s HTTP/1.1", request.path) @@ -519,3 +569,12 @@ def parse(self) -> Generator[None, None, None]: self.events.append(request) yield from super().parse() + + +class ServerConnection(ServerProtocol): + def __init__(self, *args: Any, **kwargs: Any) -> None: + warnings.warn( + "ServerConnection was renamed to ServerProtocol", + DeprecationWarning, + ) + super().__init__(*args, **kwargs) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/speedups.pyi b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/speedups.pyi new file mode 100644 index 0000000000000..821438a064e6a --- /dev/null +++ b/LayoutTests/imported/w3c/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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/__init__.py new file mode 100644 index 0000000000000..a6834b8285a56 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/client.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/client.py new file mode 100644 index 0000000000000..087ff5f569a37 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/client.py @@ -0,0 +1,328 @@ +from __future__ import annotations + +import socket +import ssl +import threading +from typing import Any, Optional, Sequence, Type + +from ..client import ClientProtocol +from ..datastructures import HeadersLike +from ..extensions.base import ClientExtensionFactory +from ..extensions.permessage_deflate import enable_client_permessage_deflate +from ..headers import validate_subprotocols +from ..http import USER_AGENT +from ..http11 import Response +from ..protocol import CONNECTING, OPEN, Event +from ..typing import LoggerLike, Origin, Subprotocol +from ..uri import parse_uri +from .connection import Connection +from .utils import Deadline + + +__all__ = ["connect", "unix_connect", "ClientConnection"] + + +class ClientConnection(Connection): + """ + Threaded implementation of a WebSocket client connection. + + :class:`ClientConnection` provides :meth:`recv` and :meth:`send` methods for + receiving and sending messages. + + It supports iteration to receive messages:: + + for message in websocket: + process(message) + + The iterator exits normally when the connection is closed with close code + 1000 (OK) or 1001 (going away) or without a close code. It raises a + :exc:`~websockets.exceptions.ConnectionClosedError` when the connection is + closed with any other code. + + Args: + socket: Socket connected to a WebSocket server. + protocol: Sans-I/O connection. + close_timeout: Timeout for closing the connection in seconds. + + """ + + def __init__( + self, + socket: socket.socket, + protocol: ClientProtocol, + *, + close_timeout: Optional[float] = 10, + ) -> None: + self.protocol: ClientProtocol + self.response_rcvd = threading.Event() + super().__init__( + socket, + protocol, + close_timeout=close_timeout, + ) + + def handshake( + self, + additional_headers: Optional[HeadersLike] = None, + user_agent_header: Optional[str] = USER_AGENT, + timeout: Optional[float] = None, + ) -> None: + """ + Perform the opening handshake. + + """ + with self.send_context(expected_state=CONNECTING): + self.request = self.protocol.connect() + if additional_headers is not None: + self.request.headers.update(additional_headers) + if user_agent_header is not None: + self.request.headers["User-Agent"] = user_agent_header + self.protocol.send_request(self.request) + + if not self.response_rcvd.wait(timeout): + self.close_socket() + self.recv_events_thread.join() + raise TimeoutError("timed out during handshake") + + if self.response is None: + self.close_socket() + self.recv_events_thread.join() + raise ConnectionError("connection closed during handshake") + + if self.protocol.state is not OPEN: + self.recv_events_thread.join(self.close_timeout) + self.close_socket() + self.recv_events_thread.join() + + if self.protocol.handshake_exc is not None: + raise self.protocol.handshake_exc + + def process_event(self, event: Event) -> None: + """ + Process one incoming event. + + """ + # First event - handshake response. + if self.response is None: + assert isinstance(event, Response) + self.response = event + self.response_rcvd.set() + # Later events - frames. + else: + super().process_event(event) + + def recv_events(self) -> None: + """ + Read incoming data from the socket and process events. + + """ + try: + super().recv_events() + finally: + # If the connection is closed during the handshake, unblock it. + self.response_rcvd.set() + + +def connect( + uri: str, + *, + # TCP/TLS — unix and path are only for unix_connect() + sock: Optional[socket.socket] = None, + ssl_context: Optional[ssl.SSLContext] = None, + server_hostname: Optional[str] = None, + unix: bool = False, + path: Optional[str] = None, + # WebSocket + origin: Optional[Origin] = None, + extensions: Optional[Sequence[ClientExtensionFactory]] = None, + subprotocols: Optional[Sequence[Subprotocol]] = None, + additional_headers: Optional[HeadersLike] = None, + user_agent_header: Optional[str] = USER_AGENT, + compression: Optional[str] = "deflate", + # Timeouts + open_timeout: Optional[float] = 10, + close_timeout: Optional[float] = 10, + # Limits + max_size: Optional[int] = 2**20, + # Logging + logger: Optional[LoggerLike] = None, + # Escape hatch for advanced customization + create_connection: Optional[Type[ClientConnection]] = None, +) -> ClientConnection: + """ + Connect to the WebSocket server at ``uri``. + + This function returns a :class:`ClientConnection` instance, which you can + use to send and receive messages. + + :func:`connect` may be used as a context manager:: + + async with websockets.sync.client.connect(...) as websocket: + ... + + The connection is closed automatically when exiting the context. + + Args: + uri: URI of the WebSocket server. + sock: Preexisting TCP socket. ``sock`` overrides the host and port + from ``uri``. You may call :func:`socket.create_connection` to + create a suitable TCP socket. + ssl_context: Configuration for enabling TLS on the connection. + server_hostname: Host name for the TLS handshake. ``server_hostname`` + overrides the host name from ``uri``. + origin: Value of the ``Origin`` header, for servers that require it. + extensions: List of supported extensions, in order in which they + should be negotiated and run. + subprotocols: List of supported subprotocols, in order of decreasing + preference. + additional_headers (HeadersLike | None): Arbitrary HTTP headers to add + to the handshake request. + user_agent_header: Value of the ``User-Agent`` request header. + It defaults to ``"Python/x.y.z websockets/X.Y"``. + Setting it to :obj:`None` removes the header. + compression: The "permessage-deflate" extension is enabled by default. + Set ``compression`` to :obj:`None` to disable it. See the + :doc:`compression guide <../../topics/compression>` for details. + open_timeout: Timeout for opening the connection in seconds. + :obj:`None` disables the timeout. + close_timeout: Timeout for closing the connection in seconds. + :obj:`None` disables the timeout. + max_size: Maximum size of incoming messages in bytes. + :obj:`None` disables the limit. + logger: Logger for this client. + It defaults to ``logging.getLogger("websockets.client")``. + See the :doc:`logging guide <../../topics/logging>` for details. + create_connection: Factory for the :class:`ClientConnection` managing + the connection. Set it to a wrapper or a subclass to customize + connection handling. + + Raises: + InvalidURI: If ``uri`` isn't a valid WebSocket URI. + OSError: If the TCP connection fails. + InvalidHandshake: If the opening handshake fails. + TimeoutError: If the opening handshake times out. + + """ + + # Process parameters + + wsuri = parse_uri(uri) + if not wsuri.secure and ssl_context is not None: + raise TypeError("ssl_context argument is incompatible with a ws:// URI") + + if unix: + if path is None and sock is None: + raise TypeError("missing path argument") + elif path is not None and sock is not None: + raise TypeError("path and sock arguments are incompatible") + else: + assert path is None # private argument, only set by unix_connect() + + if subprotocols is not None: + validate_subprotocols(subprotocols) + + if compression == "deflate": + extensions = enable_client_permessage_deflate(extensions) + elif compression is not None: + raise ValueError(f"unsupported compression: {compression}") + + # Calculate timeouts on the TCP, TLS, and WebSocket handshakes. + # The TCP and TLS timeouts must be set on the socket, then removed + # to avoid conflicting with the WebSocket timeout in handshake(). + deadline = Deadline(open_timeout) + + if create_connection is None: + create_connection = ClientConnection + + try: + # Connect socket + + if sock is None: + if unix: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.settimeout(deadline.timeout()) + assert path is not None # validated above -- this is for mpypy + sock.connect(path) + else: + sock = socket.create_connection( + (wsuri.host, wsuri.port), + deadline.timeout(), + ) + sock.settimeout(None) + + # Disable Nagle algorithm + + if not unix: + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True) + + # Initialize TLS wrapper and perform TLS handshake + + if wsuri.secure: + if ssl_context is None: + ssl_context = ssl.create_default_context() + if server_hostname is None: + server_hostname = wsuri.host + sock.settimeout(deadline.timeout()) + sock = ssl_context.wrap_socket(sock, server_hostname=server_hostname) + sock.settimeout(None) + + # Initialize WebSocket connection + + protocol = ClientProtocol( + wsuri, + origin=origin, + extensions=extensions, + subprotocols=subprotocols, + state=CONNECTING, + max_size=max_size, + logger=logger, + ) + + # Initialize WebSocket protocol + + connection = create_connection( + sock, + protocol, + close_timeout=close_timeout, + ) + # On failure, handshake() closes the socket and raises an exception. + connection.handshake( + additional_headers, + user_agent_header, + deadline.timeout(), + ) + + except Exception: + if sock is not None: + sock.close() + raise + + return connection + + +def unix_connect( + path: Optional[str] = None, + uri: Optional[str] = None, + **kwargs: Any, +) -> ClientConnection: + """ + Connect to a WebSocket server listening on a Unix socket. + + This function is identical to :func:`connect`, except for the additional + ``path`` argument. It's only available on Unix. + + It's mainly useful for debugging servers listening on Unix sockets. + + Args: + path: File system path to the Unix socket. + uri: URI of the WebSocket server. ``uri`` defaults to + ``ws://localhost/`` or, when a ``ssl_context`` is provided, to + ``wss://localhost/``. + + """ + if uri is None: + if kwargs.get("ssl_context") is None: + uri = "ws://localhost/" + else: + uri = "wss://localhost/" + return connect(uri=uri, unix=True, path=path, **kwargs) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/connection.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/connection.py new file mode 100644 index 0000000000000..4a8879e3705a9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/connection.py @@ -0,0 +1,773 @@ +from __future__ import annotations + +import contextlib +import logging +import random +import socket +import struct +import threading +import uuid +from types import TracebackType +from typing import Any, Dict, Iterable, Iterator, Mapping, Optional, Type, Union + +from ..exceptions import ConnectionClosed, ConnectionClosedOK, ProtocolError +from ..frames import DATA_OPCODES, BytesLike, CloseCode, Frame, Opcode, prepare_ctrl +from ..http11 import Request, Response +from ..protocol import CLOSED, OPEN, Event, Protocol, State +from ..typing import Data, LoggerLike, Subprotocol +from .messages import Assembler +from .utils import Deadline + + +__all__ = ["Connection"] + +logger = logging.getLogger(__name__) + + +class Connection: + """ + Threaded implementation of a WebSocket connection. + + :class:`Connection` provides APIs shared between WebSocket servers and + clients. + + You shouldn't use it directly. Instead, use + :class:`~websockets.sync.client.ClientConnection` or + :class:`~websockets.sync.server.ServerConnection`. + + """ + + recv_bufsize = 65536 + + def __init__( + self, + socket: socket.socket, + protocol: Protocol, + *, + close_timeout: Optional[float] = 10, + ) -> None: + self.socket = socket + self.protocol = protocol + self.close_timeout = close_timeout + + # Inject reference to this instance in the protocol's logger. + self.protocol.logger = logging.LoggerAdapter( + self.protocol.logger, + {"websocket": self}, + ) + + # Copy attributes from the protocol for convenience. + self.id: uuid.UUID = self.protocol.id + """Unique identifier of the connection. Useful in logs.""" + self.logger: LoggerLike = self.protocol.logger + """Logger for this connection.""" + self.debug = self.protocol.debug + + # HTTP handshake request and response. + self.request: Optional[Request] = None + """Opening handshake request.""" + self.response: Optional[Response] = None + """Opening handshake response.""" + + # Mutex serializing interactions with the protocol. + self.protocol_mutex = threading.Lock() + + # Assembler turning frames into messages and serializing reads. + self.recv_messages = Assembler() + + # Whether we are busy sending a fragmented message. + self.send_in_progress = False + + # Deadline for the closing handshake. + self.close_deadline: Optional[Deadline] = None + + # Mapping of ping IDs to pong waiters, in chronological order. + self.pings: Dict[bytes, threading.Event] = {} + + # Receiving events from the socket. + self.recv_events_thread = threading.Thread(target=self.recv_events) + self.recv_events_thread.start() + + # Exception raised in recv_events, to be chained to ConnectionClosed + # in the user thread in order to show why the TCP connection dropped. + self.recv_events_exc: Optional[BaseException] = None + + # Public attributes + + @property + def local_address(self) -> Any: + """ + Local address of the connection. + + For IPv4 connections, this is a ``(host, port)`` tuple. + + The format of the address depends on the address family. + See :meth:`~socket.socket.getsockname`. + + """ + return self.socket.getsockname() + + @property + def remote_address(self) -> Any: + """ + Remote address of the connection. + + For IPv4 connections, this is a ``(host, port)`` tuple. + + The format of the address depends on the address family. + See :meth:`~socket.socket.getpeername`. + + """ + return self.socket.getpeername() + + @property + def subprotocol(self) -> Optional[Subprotocol]: + """ + Subprotocol negotiated during the opening handshake. + + :obj:`None` if no subprotocol was negotiated. + + """ + return self.protocol.subprotocol + + # Public methods + + def __enter__(self) -> Connection: + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + if exc_type is None: + self.close() + else: + self.close(CloseCode.INTERNAL_ERROR) + + def __iter__(self) -> Iterator[Data]: + """ + Iterate on incoming messages. + + The iterator calls :meth:`recv` and yields messages in an infinite loop. + + It exits when the connection is closed normally. It raises a + :exc:`~websockets.exceptions.ConnectionClosedError` exception after a + protocol error or a network failure. + + """ + try: + while True: + yield self.recv() + except ConnectionClosedOK: + return + + def recv(self, timeout: Optional[float] = None) -> Data: + """ + Receive the next message. + + When the connection is closed, :meth:`recv` raises + :exc:`~websockets.exceptions.ConnectionClosed`. Specifically, it raises + :exc:`~websockets.exceptions.ConnectionClosedOK` after a normal closure + and :exc:`~websockets.exceptions.ConnectionClosedError` after a protocol + error or a network failure. This is how you detect the end of the + message stream. + + If ``timeout`` is :obj:`None`, block until a message is received. If + ``timeout`` is set and no message is received within ``timeout`` + seconds, raise :exc:`TimeoutError`. Set ``timeout`` to ``0`` to check if + a message was already received. + + If the message is fragmented, wait until all fragments are received, + reassemble them, and return the whole message. + + Returns: + A string (:class:`str`) for a Text_ frame or a bytestring + (:class:`bytes`) for a Binary_ frame. + + .. _Text: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.6 + .. _Binary: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.6 + + Raises: + ConnectionClosed: When the connection is closed. + RuntimeError: If two threads call :meth:`recv` or + :meth:`recv_streaming` concurrently. + + """ + try: + return self.recv_messages.get(timeout) + except EOFError: + raise self.protocol.close_exc from self.recv_events_exc + except RuntimeError: + raise RuntimeError( + "cannot call recv while another thread " + "is already running recv or recv_streaming" + ) from None + + def recv_streaming(self) -> Iterator[Data]: + """ + Receive the next message frame by frame. + + If the message is fragmented, yield each fragment as it is received. + The iterator must be fully consumed, or else the connection will become + unusable. + + :meth:`recv_streaming` raises the same exceptions as :meth:`recv`. + + Returns: + An iterator of strings (:class:`str`) for a Text_ frame or + bytestrings (:class:`bytes`) for a Binary_ frame. + + .. _Text: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.6 + .. _Binary: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.6 + + Raises: + ConnectionClosed: When the connection is closed. + RuntimeError: If two threads call :meth:`recv` or + :meth:`recv_streaming` concurrently. + + """ + try: + yield from self.recv_messages.get_iter() + except EOFError: + raise self.protocol.close_exc from self.recv_events_exc + except RuntimeError: + raise RuntimeError( + "cannot call recv_streaming while another thread " + "is already running recv or recv_streaming" + ) from None + + def send(self, message: Union[Data, Iterable[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: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.6 + .. _Binary: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.6 + + :meth:`send` also accepts an iterable of strings, bytestrings, or + bytes-like objects to enable fragmentation_. 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. + + .. _fragmentation: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.4 + + :meth:`send` rejects dict-like objects because this is often an error. + (If you really want to send the keys of a dict-like object as fragments, + call its :meth:`~dict.keys` method and pass the result to :meth:`send`.) + + When the connection is closed, :meth:`send` 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. + + Args: + message: Message to send. + + Raises: + ConnectionClosed: When the connection is closed. + RuntimeError: If a connection is busy sending a fragmented message. + TypeError: If ``message`` doesn't have a supported type. + + """ + # Unfragmented message -- this case must be handled first because + # strings and bytes-like objects are iterable. + + if isinstance(message, str): + with self.send_context(): + if self.send_in_progress: + raise RuntimeError( + "cannot call send while another thread " + "is already running send" + ) + self.protocol.send_text(message.encode("utf-8")) + + elif isinstance(message, BytesLike): + with self.send_context(): + if self.send_in_progress: + raise RuntimeError( + "cannot call send while another thread " + "is already running send" + ) + self.protocol.send_binary(message) + + # Catch a common mistake -- passing a dict to send(). + + elif isinstance(message, Mapping): + raise TypeError("data is a dict-like object") + + # Fragmented message -- regular iterator. + + elif isinstance(message, Iterable): + chunks = iter(message) + try: + chunk = next(chunks) + except StopIteration: + return + + try: + # First fragment. + if isinstance(chunk, str): + text = True + with self.send_context(): + if self.send_in_progress: + raise RuntimeError( + "cannot call send while another thread " + "is already running send" + ) + self.send_in_progress = True + self.protocol.send_text( + chunk.encode("utf-8"), + fin=False, + ) + elif isinstance(chunk, BytesLike): + text = False + with self.send_context(): + if self.send_in_progress: + raise RuntimeError( + "cannot call send while another thread " + "is already running send" + ) + self.send_in_progress = True + self.protocol.send_binary( + chunk, + fin=False, + ) + else: + raise TypeError("data iterable must contain bytes or str") + + # Other fragments + for chunk in chunks: + if isinstance(chunk, str) and text: + with self.send_context(): + assert self.send_in_progress + self.protocol.send_continuation( + chunk.encode("utf-8"), + fin=False, + ) + elif isinstance(chunk, BytesLike) and not text: + with self.send_context(): + assert self.send_in_progress + self.protocol.send_continuation( + chunk, + fin=False, + ) + else: + raise TypeError("data iterable must contain uniform types") + + # Final fragment. + with self.send_context(): + self.protocol.send_continuation(b"", fin=True) + self.send_in_progress = False + + except RuntimeError: + # We didn't start sending a fragmented message. + raise + + except Exception: + # We're half-way through a fragmented message and we can't + # complete it. This makes the connection unusable. + with self.send_context(): + self.protocol.fail( + CloseCode.INTERNAL_ERROR, + "error in fragmented message", + ) + raise + + else: + raise TypeError("data must be bytes, str, or iterable") + + def close(self, code: int = CloseCode.NORMAL_CLOSURE, reason: str = "") -> None: + """ + Perform the closing handshake. + + :meth:`close` waits for the other end to complete the handshake, for the + TCP connection to terminate, and for all incoming messages to be read + with :meth:`recv`. + + :meth:`close` is idempotent: it doesn't do anything once the + connection is closed. + + Args: + code: WebSocket close code. + reason: WebSocket close reason. + + """ + try: + # The context manager takes care of waiting for the TCP connection + # to terminate after calling a method that sends a close frame. + with self.send_context(): + if self.send_in_progress: + self.protocol.fail( + CloseCode.INTERNAL_ERROR, + "close during fragmented message", + ) + else: + self.protocol.send_close(code, reason) + except ConnectionClosed: + # Ignore ConnectionClosed exceptions raised from send_context(). + # They mean that the connection is closed, which was the goal. + pass + + def ping(self, data: Optional[Data] = None) -> threading.Event: + """ + Send a Ping_. + + .. _Ping: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.5.2 + + A ping may serve as a keepalive or as a check that the remote endpoint + received all messages up to this point + + Args: + data: Payload of the ping. A :class:`str` will be encoded to UTF-8. + If ``data`` is :obj:`None`, the payload is four random bytes. + + Returns: + An event that will be set when the corresponding pong is received. + You can ignore it if you don't intend to wait. + + :: + + pong_event = ws.ping() + pong_event.wait() # only if you want to wait for the pong + + Raises: + ConnectionClosed: When the connection is closed. + RuntimeError: If another ping was sent with the same data and + the corresponding pong wasn't received yet. + + """ + if data is not None: + data = prepare_ctrl(data) + + with self.send_context(): + # Protect against duplicates if a payload is explicitly set. + if data in self.pings: + raise RuntimeError("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)) + + pong_waiter = threading.Event() + self.pings[data] = pong_waiter + self.protocol.send_ping(data) + return pong_waiter + + def pong(self, data: Data = b"") -> None: + """ + Send a Pong_. + + .. _Pong: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.5.3 + + An unsolicited pong may serve as a unidirectional heartbeat. + + Args: + data: Payload of the pong. A :class:`str` will be encoded to UTF-8. + + Raises: + ConnectionClosed: When the connection is closed. + + """ + data = prepare_ctrl(data) + + with self.send_context(): + self.protocol.send_pong(data) + + # Private methods + + def process_event(self, event: Event) -> None: + """ + Process one incoming event. + + This method is overridden in subclasses to handle the handshake. + + """ + assert isinstance(event, Frame) + if event.opcode in DATA_OPCODES: + self.recv_messages.put(event) + + if event.opcode is Opcode.PONG: + self.acknowledge_pings(bytes(event.data)) + + def acknowledge_pings(self, data: bytes) -> None: + """ + Acknowledge pings when receiving a pong. + + """ + with self.protocol_mutex: + # Ignore unsolicited pong. + if data not in self.pings: + return + # Sending a pong for only the most recent ping is legal. + # Acknowledge all previous pings too in that case. + ping_id = None + ping_ids = [] + for ping_id, ping in self.pings.items(): + ping_ids.append(ping_id) + ping.set() + if ping_id == data: + break + else: + raise AssertionError("solicited pong not found in pings") + # Remove acknowledged pings from self.pings. + for ping_id in ping_ids: + del self.pings[ping_id] + + def recv_events(self) -> None: + """ + Read incoming data from the socket and process events. + + Run this method in a thread as long as the connection is alive. + + ``recv_events()`` exits immediately when the ``self.socket`` is closed. + + """ + try: + while True: + try: + if self.close_deadline is not None: + self.socket.settimeout(self.close_deadline.timeout()) + data = self.socket.recv(self.recv_bufsize) + except Exception as exc: + if self.debug: + self.logger.debug("error while receiving data", exc_info=True) + # When the closing handshake is initiated by our side, + # recv() may block until send_context() closes the socket. + # In that case, send_context() already set recv_events_exc. + # Calling set_recv_events_exc() avoids overwriting it. + with self.protocol_mutex: + self.set_recv_events_exc(exc) + break + + if data == b"": + break + + # Acquire the connection lock. + with self.protocol_mutex: + # Feed incoming data to the connection. + self.protocol.receive_data(data) + + # This isn't expected to raise an exception. + events = self.protocol.events_received() + + # Write outgoing data to the socket. + try: + self.send_data() + except Exception as exc: + if self.debug: + self.logger.debug("error while sending data", exc_info=True) + # Similarly to the above, avoid overriding an exception + # set by send_context(), in case of a race condition + # i.e. send_context() closes the socket after recv() + # returns above but before send_data() calls send(). + self.set_recv_events_exc(exc) + break + + if self.protocol.close_expected(): + # If the connection is expected to close soon, set the + # close deadline based on the close timeout. + if self.close_deadline is None: + self.close_deadline = Deadline(self.close_timeout) + + # Unlock conn_mutex before processing events. Else, the + # application can't send messages in response to events. + + # If self.send_data raised an exception, then events are lost. + # Given that automatic responses write small amounts of data, + # this should be uncommon, so we don't handle the edge case. + + try: + for event in events: + # This may raise EOFError if the closing handshake + # times out while a message is waiting to be read. + self.process_event(event) + except EOFError: + break + + # Breaking out of the while True: ... loop means that we believe + # that the socket doesn't work anymore. + with self.protocol_mutex: + # Feed the end of the data stream to the connection. + self.protocol.receive_eof() + + # This isn't expected to generate events. + assert not self.protocol.events_received() + + # There is no error handling because send_data() can only write + # the end of the data stream here and it handles errors itself. + self.send_data() + + except Exception as exc: + # This branch should never run. It's a safety net in case of bugs. + self.logger.error("unexpected internal error", exc_info=True) + with self.protocol_mutex: + self.set_recv_events_exc(exc) + # We don't know where we crashed. Force protocol state to CLOSED. + self.protocol.state = CLOSED + finally: + # This isn't expected to raise an exception. + self.close_socket() + + @contextlib.contextmanager + def send_context( + self, + *, + expected_state: State = OPEN, # CONNECTING during the opening handshake + ) -> Iterator[None]: + """ + Create a context for writing to the connection from user code. + + On entry, :meth:`send_context` acquires the connection lock and checks + that the connection is open; on exit, it writes outgoing data to the + socket:: + + with self.send_context(): + self.protocol.send_text(message.encode("utf-8")) + + When the connection isn't open on entry, when the connection is expected + to close on exit, or when an unexpected error happens, terminating the + connection, :meth:`send_context` waits until the connection is closed + then raises :exc:`~websockets.exceptions.ConnectionClosed`. + + """ + # Should we wait until the connection is closed? + wait_for_close = False + # Should we close the socket and raise ConnectionClosed? + raise_close_exc = False + # What exception should we chain ConnectionClosed to? + original_exc: Optional[BaseException] = None + + # Acquire the protocol lock. + with self.protocol_mutex: + if self.protocol.state is expected_state: + # Let the caller interact with the protocol. + try: + yield + except (ProtocolError, RuntimeError): + # The protocol state wasn't changed. Exit immediately. + raise + except Exception as exc: + self.logger.error("unexpected internal error", exc_info=True) + # This branch should never run. It's a safety net in case of + # bugs. Since we don't know what happened, we will close the + # connection and raise the exception to the caller. + wait_for_close = False + raise_close_exc = True + original_exc = exc + else: + # Check if the connection is expected to close soon. + if self.protocol.close_expected(): + wait_for_close = True + # If the connection is expected to close soon, set the + # close deadline based on the close timeout. + + # Since we tested earlier that protocol.state was OPEN + # (or CONNECTING) and we didn't release protocol_mutex, + # it is certain that self.close_deadline is still None. + assert self.close_deadline is None + self.close_deadline = Deadline(self.close_timeout) + # Write outgoing data to the socket. + try: + self.send_data() + except Exception as exc: + if self.debug: + self.logger.debug("error while sending data", exc_info=True) + # While the only expected exception here is OSError, + # other exceptions would be treated identically. + wait_for_close = False + raise_close_exc = True + original_exc = exc + + else: # self.protocol.state is not expected_state + # Minor layering violation: we assume that the connection + # will be closing soon if it isn't in the expected state. + wait_for_close = True + raise_close_exc = True + + # To avoid a deadlock, release the connection lock by exiting the + # context manager before waiting for recv_events() to terminate. + + # If the connection is expected to close soon and the close timeout + # elapses, close the socket to terminate the connection. + if wait_for_close: + if self.close_deadline is None: + timeout = self.close_timeout + else: + # Thread.join() returns immediately if timeout is negative. + timeout = self.close_deadline.timeout(raise_if_elapsed=False) + self.recv_events_thread.join(timeout) + + if self.recv_events_thread.is_alive(): + # There's no risk to overwrite another error because + # original_exc is never set when wait_for_close is True. + assert original_exc is None + original_exc = TimeoutError("timed out while closing connection") + # Set recv_events_exc before closing the socket in order to get + # proper exception reporting. + raise_close_exc = True + with self.protocol_mutex: + self.set_recv_events_exc(original_exc) + + # If an error occurred, close the socket to terminate the connection and + # raise an exception. + if raise_close_exc: + self.close_socket() + self.recv_events_thread.join() + raise self.protocol.close_exc from original_exc + + def send_data(self) -> None: + """ + Send outgoing data. + + This method requires holding protocol_mutex. + + Raises: + OSError: When a socket operations fails. + + """ + assert self.protocol_mutex.locked() + for data in self.protocol.data_to_send(): + if data: + if self.close_deadline is not None: + self.socket.settimeout(self.close_deadline.timeout()) + self.socket.sendall(data) + else: + try: + self.socket.shutdown(socket.SHUT_WR) + except OSError: # socket already closed + pass + + def set_recv_events_exc(self, exc: Optional[BaseException]) -> None: + """ + Set recv_events_exc, if not set yet. + + This method requires holding protocol_mutex. + + """ + assert self.protocol_mutex.locked() + if self.recv_events_exc is None: + self.recv_events_exc = exc + + def close_socket(self) -> None: + """ + Shutdown and close socket. Close message assembler. + + Calling close_socket() guarantees that recv_events() terminates. Indeed, + recv_events() may block only on socket.recv() or on recv_messages.put(). + + """ + # shutdown() is required to interrupt recv() on Linux. + try: + self.socket.shutdown(socket.SHUT_RDWR) + except OSError: + pass # socket is already closed + self.socket.close() + self.recv_messages.close() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/messages.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/messages.py new file mode 100644 index 0000000000000..67a22313ca163 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/messages.py @@ -0,0 +1,281 @@ +from __future__ import annotations + +import codecs +import queue +import threading +from typing import Iterator, List, Optional, cast + +from ..frames import Frame, Opcode +from ..typing import Data + + +__all__ = ["Assembler"] + +UTF8Decoder = codecs.getincrementaldecoder("utf-8") + + +class Assembler: + """ + Assemble messages from frames. + + """ + + def __init__(self) -> None: + # Serialize reads and writes -- except for reads via synchronization + # primitives provided by the threading and queue modules. + self.mutex = threading.Lock() + + # We create a latch with two events to ensure proper interleaving of + # writing and reading messages. + # put() sets this event to tell get() that a message can be fetched. + self.message_complete = threading.Event() + # get() sets this event to let put() that the message was fetched. + self.message_fetched = threading.Event() + + # This flag prevents concurrent calls to get() by user code. + self.get_in_progress = False + # This flag prevents concurrent calls to put() by library code. + self.put_in_progress = False + + # Decoder for text frames, None for binary frames. + self.decoder: Optional[codecs.IncrementalDecoder] = None + + # Buffer of frames belonging to the same message. + self.chunks: List[Data] = [] + + # When switching from "buffering" to "streaming", we use a thread-safe + # queue for transferring frames from the writing thread (library code) + # to the reading thread (user code). We're buffering when chunks_queue + # is None and streaming when it's a SimpleQueue. None is a sentinel + # value marking the end of the stream, superseding message_complete. + + # Stream data from frames belonging to the same message. + # Remove quotes around type when dropping Python < 3.9. + self.chunks_queue: Optional["queue.SimpleQueue[Optional[Data]]"] = None + + # This flag marks the end of the stream. + self.closed = False + + def get(self, timeout: Optional[float] = None) -> Data: + """ + Read the next message. + + :meth:`get` returns a single :class:`str` or :class:`bytes`. + + If the message is fragmented, :meth:`get` waits until the last frame is + received, then it reassembles the message and returns it. To receive + messages frame by frame, use :meth:`get_iter` instead. + + Args: + timeout: If a timeout is provided and elapses before a complete + message is received, :meth:`get` raises :exc:`TimeoutError`. + + Raises: + EOFError: If the stream of frames has ended. + RuntimeError: If two threads run :meth:`get` or :meth:``get_iter` + concurrently. + + """ + with self.mutex: + if self.closed: + raise EOFError("stream of frames ended") + + if self.get_in_progress: + raise RuntimeError("get or get_iter is already running") + + self.get_in_progress = True + + # If the message_complete event isn't set yet, release the lock to + # allow put() to run and eventually set it. + # Locking with get_in_progress ensures only one thread can get here. + completed = self.message_complete.wait(timeout) + + with self.mutex: + self.get_in_progress = False + + # Waiting for a complete message timed out. + if not completed: + raise TimeoutError(f"timed out in {timeout:.1f}s") + + # get() was unblocked by close() rather than put(). + if self.closed: + raise EOFError("stream of frames ended") + + assert self.message_complete.is_set() + self.message_complete.clear() + + joiner: Data = b"" if self.decoder is None else "" + # mypy cannot figure out that chunks have the proper type. + message: Data = joiner.join(self.chunks) # type: ignore + + assert not self.message_fetched.is_set() + self.message_fetched.set() + + self.chunks = [] + assert self.chunks_queue is None + + return message + + def get_iter(self) -> Iterator[Data]: + """ + Stream the next message. + + Iterating the return value of :meth:`get_iter` yields a :class:`str` or + :class:`bytes` for each frame in the message. + + The iterator must be fully consumed before calling :meth:`get_iter` or + :meth:`get` again. Else, :exc:`RuntimeError` is raised. + + This method only makes sense for fragmented messages. If messages aren't + fragmented, use :meth:`get` instead. + + Raises: + EOFError: If the stream of frames has ended. + RuntimeError: If two threads run :meth:`get` or :meth:``get_iter` + concurrently. + + """ + with self.mutex: + if self.closed: + raise EOFError("stream of frames ended") + + if self.get_in_progress: + raise RuntimeError("get or get_iter is already running") + + chunks = self.chunks + self.chunks = [] + self.chunks_queue = cast( + # Remove quotes around type when dropping Python < 3.9. + "queue.SimpleQueue[Optional[Data]]", + queue.SimpleQueue(), + ) + + # Sending None in chunk_queue supersedes setting message_complete + # when switching to "streaming". If message is already complete + # when the switch happens, put() didn't send None, so we have to. + if self.message_complete.is_set(): + self.chunks_queue.put(None) + + self.get_in_progress = True + + # Locking with get_in_progress ensures only one thread can get here. + yield from chunks + while True: + chunk = self.chunks_queue.get() + if chunk is None: + break + yield chunk + + with self.mutex: + self.get_in_progress = False + + assert self.message_complete.is_set() + self.message_complete.clear() + + # get_iter() was unblocked by close() rather than put(). + if self.closed: + raise EOFError("stream of frames ended") + + assert not self.message_fetched.is_set() + self.message_fetched.set() + + assert self.chunks == [] + self.chunks_queue = None + + def put(self, frame: Frame) -> None: + """ + Add ``frame`` to the next message. + + When ``frame`` is the final frame in a message, :meth:`put` waits until + the message is fetched, either by calling :meth:`get` or by fully + consuming the return value of :meth:`get_iter`. + + :meth:`put` assumes that the stream of frames respects the protocol. If + it doesn't, the behavior is undefined. + + Raises: + EOFError: If the stream of frames has ended. + RuntimeError: If two threads run :meth:`put` concurrently. + + """ + with self.mutex: + if self.closed: + raise EOFError("stream of frames ended") + + if self.put_in_progress: + raise RuntimeError("put is already running") + + if frame.opcode is Opcode.TEXT: + self.decoder = UTF8Decoder(errors="strict") + elif frame.opcode is Opcode.BINARY: + self.decoder = None + elif frame.opcode is Opcode.CONT: + pass + else: + # Ignore control frames. + return + + data: Data + if self.decoder is not None: + data = self.decoder.decode(frame.data, frame.fin) + else: + data = frame.data + + if self.chunks_queue is None: + self.chunks.append(data) + else: + self.chunks_queue.put(data) + + if not frame.fin: + return + + # Message is complete. Wait until it's fetched to return. + + assert not self.message_complete.is_set() + self.message_complete.set() + + if self.chunks_queue is not None: + self.chunks_queue.put(None) + + assert not self.message_fetched.is_set() + + self.put_in_progress = True + + # Release the lock to allow get() to run and eventually set the event. + self.message_fetched.wait() + + with self.mutex: + self.put_in_progress = False + + assert self.message_fetched.is_set() + self.message_fetched.clear() + + # put() was unblocked by close() rather than get() or get_iter(). + if self.closed: + raise EOFError("stream of frames ended") + + self.decoder = None + + def close(self) -> None: + """ + End the stream of frames. + + Callling :meth:`close` concurrently with :meth:`get`, :meth:`get_iter`, + or :meth:`put` is safe. They will raise :exc:`EOFError`. + + """ + with self.mutex: + if self.closed: + return + + self.closed = True + + # Unblock get or get_iter. + if self.get_in_progress: + self.message_complete.set() + if self.chunks_queue is not None: + self.chunks_queue.put(None) + + # Unblock put(). + if self.put_in_progress: + self.message_fetched.set() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/server.py new file mode 100644 index 0000000000000..14767968c97c7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/server.py @@ -0,0 +1,530 @@ +from __future__ import annotations + +import http +import logging +import os +import selectors +import socket +import ssl +import sys +import threading +from types import TracebackType +from typing import Any, Callable, Optional, Sequence, Type + +from websockets.frames import CloseCode + +from ..extensions.base import ServerExtensionFactory +from ..extensions.permessage_deflate import enable_server_permessage_deflate +from ..headers import validate_subprotocols +from ..http import USER_AGENT +from ..http11 import Request, Response +from ..protocol import CONNECTING, OPEN, Event +from ..server import ServerProtocol +from ..typing import LoggerLike, Origin, Subprotocol +from .connection import Connection +from .utils import Deadline + + +__all__ = ["serve", "unix_serve", "ServerConnection", "WebSocketServer"] + + +class ServerConnection(Connection): + """ + Threaded implementation of a WebSocket server connection. + + :class:`ServerConnection` provides :meth:`recv` and :meth:`send` methods for + receiving and sending messages. + + It supports iteration to receive messages:: + + for message in websocket: + process(message) + + The iterator exits normally when the connection is closed with close code + 1000 (OK) or 1001 (going away) or without a close code. It raises a + :exc:`~websockets.exceptions.ConnectionClosedError` when the connection is + closed with any other code. + + Args: + socket: Socket connected to a WebSocket client. + protocol: Sans-I/O connection. + close_timeout: Timeout for closing the connection in seconds. + + """ + + def __init__( + self, + socket: socket.socket, + protocol: ServerProtocol, + *, + close_timeout: Optional[float] = 10, + ) -> None: + self.protocol: ServerProtocol + self.request_rcvd = threading.Event() + super().__init__( + socket, + protocol, + close_timeout=close_timeout, + ) + + def handshake( + self, + process_request: Optional[ + Callable[ + [ServerConnection, Request], + Optional[Response], + ] + ] = None, + process_response: Optional[ + Callable[ + [ServerConnection, Request, Response], + Optional[Response], + ] + ] = None, + server_header: Optional[str] = USER_AGENT, + timeout: Optional[float] = None, + ) -> None: + """ + Perform the opening handshake. + + """ + if not self.request_rcvd.wait(timeout): + self.close_socket() + self.recv_events_thread.join() + raise TimeoutError("timed out during handshake") + + if self.request is None: + self.close_socket() + self.recv_events_thread.join() + raise ConnectionError("connection closed during handshake") + + with self.send_context(expected_state=CONNECTING): + self.response = None + + if process_request is not None: + try: + self.response = process_request(self, self.request) + except Exception as exc: + self.protocol.handshake_exc = exc + self.logger.error("opening handshake failed", exc_info=True) + self.response = self.protocol.reject( + http.HTTPStatus.INTERNAL_SERVER_ERROR, + ( + "Failed to open a WebSocket connection.\n" + "See server log for more information.\n" + ), + ) + + if self.response is None: + self.response = self.protocol.accept(self.request) + + if server_header is not None: + self.response.headers["Server"] = server_header + + if process_response is not None: + try: + response = process_response(self, self.request, self.response) + except Exception as exc: + self.protocol.handshake_exc = exc + self.logger.error("opening handshake failed", exc_info=True) + self.response = self.protocol.reject( + http.HTTPStatus.INTERNAL_SERVER_ERROR, + ( + "Failed to open a WebSocket connection.\n" + "See server log for more information.\n" + ), + ) + else: + if response is not None: + self.response = response + + self.protocol.send_response(self.response) + + if self.protocol.state is not OPEN: + self.recv_events_thread.join(self.close_timeout) + self.close_socket() + self.recv_events_thread.join() + + if self.protocol.handshake_exc is not None: + raise self.protocol.handshake_exc + + def process_event(self, event: Event) -> None: + """ + Process one incoming event. + + """ + # First event - handshake request. + if self.request is None: + assert isinstance(event, Request) + self.request = event + self.request_rcvd.set() + # Later events - frames. + else: + super().process_event(event) + + def recv_events(self) -> None: + """ + Read incoming data from the socket and process events. + + """ + try: + super().recv_events() + finally: + # If the connection is closed during the handshake, unblock it. + self.request_rcvd.set() + + +class WebSocketServer: + """ + WebSocket server returned by :func:`serve`. + + This class mirrors the API of :class:`~socketserver.BaseServer`, notably the + :meth:`~socketserver.BaseServer.serve_forever` and + :meth:`~socketserver.BaseServer.shutdown` methods, as well as the context + manager protocol. + + Args: + socket: Server socket listening for new connections. + handler: Handler for one connection. Receives the socket and address + returned by :meth:`~socket.socket.accept`. + logger: Logger for this server. + + """ + + def __init__( + self, + socket: socket.socket, + handler: Callable[[socket.socket, Any], None], + logger: Optional[LoggerLike] = None, + ): + self.socket = socket + self.handler = handler + if logger is None: + logger = logging.getLogger("websockets.server") + self.logger = logger + if sys.platform != "win32": + self.shutdown_watcher, self.shutdown_notifier = os.pipe() + + def serve_forever(self) -> None: + """ + See :meth:`socketserver.BaseServer.serve_forever`. + + This method doesn't return. Calling :meth:`shutdown` from another thread + stops the server. + + Typical use:: + + with serve(...) as server: + server.serve_forever() + + """ + poller = selectors.DefaultSelector() + poller.register(self.socket, selectors.EVENT_READ) + if sys.platform != "win32": + poller.register(self.shutdown_watcher, selectors.EVENT_READ) + + while True: + poller.select() + try: + # If the socket is closed, this will raise an exception and exit + # the loop. So we don't need to check the return value of select(). + sock, addr = self.socket.accept() + except OSError: + break + thread = threading.Thread(target=self.handler, args=(sock, addr)) + thread.start() + + def shutdown(self) -> None: + """ + See :meth:`socketserver.BaseServer.shutdown`. + + """ + self.socket.close() + if sys.platform != "win32": + os.write(self.shutdown_notifier, b"x") + + def fileno(self) -> int: + """ + See :meth:`socketserver.BaseServer.fileno`. + + """ + return self.socket.fileno() + + def __enter__(self) -> WebSocketServer: + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + self.shutdown() + + +def serve( + handler: Callable[[ServerConnection], None], + host: Optional[str] = None, + port: Optional[int] = None, + *, + # TCP/TLS — unix and path are only for unix_serve() + sock: Optional[socket.socket] = None, + ssl_context: Optional[ssl.SSLContext] = None, + unix: bool = False, + path: Optional[str] = None, + # WebSocket + origins: Optional[Sequence[Optional[Origin]]] = None, + extensions: Optional[Sequence[ServerExtensionFactory]] = None, + subprotocols: Optional[Sequence[Subprotocol]] = None, + select_subprotocol: Optional[ + Callable[ + [ServerConnection, Sequence[Subprotocol]], + Optional[Subprotocol], + ] + ] = None, + process_request: Optional[ + Callable[ + [ServerConnection, Request], + Optional[Response], + ] + ] = None, + process_response: Optional[ + Callable[ + [ServerConnection, Request, Response], + Optional[Response], + ] + ] = None, + server_header: Optional[str] = USER_AGENT, + compression: Optional[str] = "deflate", + # Timeouts + open_timeout: Optional[float] = 10, + close_timeout: Optional[float] = 10, + # Limits + max_size: Optional[int] = 2**20, + # Logging + logger: Optional[LoggerLike] = None, + # Escape hatch for advanced customization + create_connection: Optional[Type[ServerConnection]] = None, +) -> WebSocketServer: + """ + Create a WebSocket server listening on ``host`` and ``port``. + + Whenever a client connects, the server creates a :class:`ServerConnection`, + performs the opening handshake, and delegates to the ``handler``. + + The handler receives a :class:`ServerConnection` instance, which you can use + to send and receive messages. + + Once the handler completes, either normally or with an exception, the server + performs the closing handshake and closes the connection. + + :class:`WebSocketServer` mirrors the API of + :class:`~socketserver.BaseServer`. Treat it as a context manager to ensure + that it will be closed and call the :meth:`~WebSocketServer.serve_forever` + method to serve requests:: + + def handler(websocket): + ... + + with websockets.sync.server.serve(handler, ...) as server: + server.serve_forever() + + Args: + handler: Connection handler. It receives the WebSocket connection, + which is a :class:`ServerConnection`, in argument. + host: Network interfaces the server binds to. + See :func:`~socket.create_server` for details. + port: TCP port the server listens on. + See :func:`~socket.create_server` for details. + sock: Preexisting TCP socket. ``sock`` replaces ``host`` and ``port``. + You may call :func:`socket.create_server` to create a suitable TCP + socket. + ssl_context: Configuration for enabling TLS on the connection. + origins: Acceptable values of the ``Origin`` header, for defending + against Cross-Site WebSocket Hijacking attacks. Include :obj:`None` + in the list if the lack of an origin is acceptable. + extensions: List of supported extensions, in order in which they + should be negotiated and run. + subprotocols: List of supported subprotocols, in order of decreasing + preference. + select_subprotocol: Callback for selecting a subprotocol among + those supported by the client and the server. It receives a + :class:`ServerConnection` (not a + :class:`~websockets.server.ServerProtocol`!) instance and a list of + subprotocols offered by the client. Other than the first argument, + it has the same behavior as the + :meth:`ServerProtocol.select_subprotocol + ` method. + process_request: Intercept the request during the opening handshake. + Return an HTTP response to force the response or :obj:`None` to + continue normally. When you force an HTTP 101 Continue response, + the handshake is successful. Else, the connection is aborted. + process_response: Intercept the response during the opening handshake. + Return an HTTP response to force the response or :obj:`None` to + continue normally. When you force an HTTP 101 Continue response, + the handshake is successful. Else, the connection is aborted. + server_header: Value of the ``Server`` response header. + It defaults to ``"Python/x.y.z websockets/X.Y"``. Setting it to + :obj:`None` removes the header. + compression: The "permessage-deflate" extension is enabled by default. + Set ``compression`` to :obj:`None` to disable it. See the + :doc:`compression guide <../../topics/compression>` for details. + open_timeout: Timeout for opening connections in seconds. + :obj:`None` disables the timeout. + close_timeout: Timeout for closing connections in seconds. + :obj:`None` disables the timeout. + max_size: Maximum size of incoming messages in bytes. + :obj:`None` disables the limit. + logger: Logger for this server. + It defaults to ``logging.getLogger("websockets.server")``. See the + :doc:`logging guide <../../topics/logging>` for details. + create_connection: Factory for the :class:`ServerConnection` managing + the connection. Set it to a wrapper or a subclass to customize + connection handling. + """ + + # Process parameters + + if subprotocols is not None: + validate_subprotocols(subprotocols) + + if compression == "deflate": + extensions = enable_server_permessage_deflate(extensions) + elif compression is not None: + raise ValueError(f"unsupported compression: {compression}") + + if create_connection is None: + create_connection = ServerConnection + + # Bind socket and listen + + if sock is None: + if unix: + if path is None: + raise TypeError("missing path argument") + sock = socket.create_server(path, family=socket.AF_UNIX) + else: + sock = socket.create_server((host, port)) + else: + if path is not None: + raise TypeError("path and sock arguments are incompatible") + + # Initialize TLS wrapper + + if ssl_context is not None: + sock = ssl_context.wrap_socket( + sock, + server_side=True, + # Delay TLS handshake until after we set a timeout on the socket. + do_handshake_on_connect=False, + ) + + # Define request handler + + def conn_handler(sock: socket.socket, addr: Any) -> None: + # Calculate timeouts on the TLS and WebSocket handshakes. + # The TLS timeout must be set on the socket, then removed + # to avoid conflicting with the WebSocket timeout in handshake(). + deadline = Deadline(open_timeout) + + try: + # Disable Nagle algorithm + + if not unix: + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True) + + # Perform TLS handshake + + if ssl_context is not None: + sock.settimeout(deadline.timeout()) + assert isinstance(sock, ssl.SSLSocket) # mypy cannot figure this out + sock.do_handshake() + sock.settimeout(None) + + # Create a closure so that select_subprotocol has access to self. + + protocol_select_subprotocol: Optional[ + Callable[ + [ServerProtocol, Sequence[Subprotocol]], + Optional[Subprotocol], + ] + ] = None + + if select_subprotocol is not None: + + def protocol_select_subprotocol( + protocol: ServerProtocol, + subprotocols: Sequence[Subprotocol], + ) -> Optional[Subprotocol]: + # mypy doesn't know that select_subprotocol is immutable. + assert select_subprotocol is not None + # Ensure this function is only used in the intended context. + assert protocol is connection.protocol + return select_subprotocol(connection, subprotocols) + + # Initialize WebSocket connection + + protocol = ServerProtocol( + origins=origins, + extensions=extensions, + subprotocols=subprotocols, + select_subprotocol=protocol_select_subprotocol, + state=CONNECTING, + max_size=max_size, + logger=logger, + ) + + # Initialize WebSocket protocol + + assert create_connection is not None # help mypy + connection = create_connection( + sock, + protocol, + close_timeout=close_timeout, + ) + # On failure, handshake() closes the socket, raises an exception, and + # logs it. + connection.handshake( + process_request, + process_response, + server_header, + deadline.timeout(), + ) + + except Exception: + sock.close() + return + + try: + handler(connection) + except Exception: + protocol.logger.error("connection handler failed", exc_info=True) + connection.close(CloseCode.INTERNAL_ERROR) + else: + connection.close() + + # Initialize server + + return WebSocketServer(sock, conn_handler, logger) + + +def unix_serve( + handler: Callable[[ServerConnection], Any], + path: Optional[str] = None, + **kwargs: Any, +) -> WebSocketServer: + """ + Create a WebSocket server listening on a Unix socket. + + This function is identical to :func:`serve`, except the ``host`` and + ``port`` arguments are replaced by ``path``. It's only available on Unix. + + It's useful for deploying a server behind a reverse proxy such as nginx. + + Args: + handler: Connection handler. It receives the WebSocket connection, + which is a :class:`ServerConnection`, in argument. + path: File system path to the Unix socket. + + """ + return serve(handler, path=path, unix=True, **kwargs) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/utils.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/utils.py new file mode 100644 index 0000000000000..471f32e19d4eb --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/utils.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +import time +from typing import Optional + + +__all__ = ["Deadline"] + + +class Deadline: + """ + Manage timeouts across multiple steps. + + Args: + timeout: Time available in seconds or :obj:`None` if there is no limit. + + """ + + def __init__(self, timeout: Optional[float]) -> None: + self.deadline: Optional[float] + if timeout is None: + self.deadline = None + else: + self.deadline = time.monotonic() + timeout + + def timeout(self, *, raise_if_elapsed: bool = True) -> Optional[float]: + """ + Calculate a timeout from a deadline. + + Args: + raise_if_elapsed (bool): Whether to raise :exc:`TimeoutError` + if the deadline lapsed. + + Raises: + TimeoutError: If the deadline lapsed. + + Returns: + Time left in seconds or :obj:`None` if there is no limit. + + """ + if self.deadline is None: + return None + timeout = self.deadline - time.monotonic() + if raise_if_elapsed and timeout <= 0: + raise TimeoutError("timed out") + return timeout diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/w3c-import.log new file mode 100644 index 0000000000000..755120e4f1ddc --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/w3c-import.log @@ -0,0 +1,22 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/connection.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/messages.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/server.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/sync/utils.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/typing.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/typing.py index e672ba0069e5b..cc3e3ec0d96f4 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/typing.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/typing.py @@ -1,5 +1,6 @@ from __future__ import annotations +import http import logging from typing import List, NewType, Optional, Tuple, Union @@ -7,6 +8,7 @@ __all__ = [ "Data", "LoggerLike", + "StatusLike", "Origin", "Subprotocol", "ExtensionName", @@ -30,6 +32,11 @@ """Types accepted where a :class:`~logging.Logger` is expected.""" +StatusLike = Union[http.HTTPStatus, int] +""" +Types accepted where an :class:`~http.HTTPStatus` is expected.""" + + Origin = NewType("Origin", str) """Value of a ``Origin`` header.""" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/uri.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/uri.py index fff0c380645ba..385090f66ae36 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/uri.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/uri.py @@ -33,8 +33,8 @@ class WebSocketURI: port: int path: str query: str - username: Optional[str] - password: Optional[str] + username: Optional[str] = None + password: Optional[str] = None @property def resource_name(self) -> str: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/version.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/version.py index c30bfd68f31ab..d1c99458e2cf8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/version.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/version.py @@ -1,5 +1,7 @@ from __future__ import annotations +import importlib.metadata + __all__ = ["tag", "version", "commit"] @@ -18,7 +20,7 @@ released = True -tag = version = commit = "10.3" +tag = version = commit = "12.0" if not released: # pragma: no cover @@ -44,7 +46,11 @@ def get_version(tag: str) -> str: text=True, ).stdout.strip() # subprocess.run raises FileNotFoundError if git isn't on $PATH. - except (FileNotFoundError, subprocess.CalledProcessError): + except ( + FileNotFoundError, + subprocess.CalledProcessError, + subprocess.TimeoutExpired, + ): pass else: description_re = r"[0-9.]+-([0-9]+)-(g[0-9a-f]{7,}(?:-dirty)?)" @@ -56,8 +62,6 @@ def get_version(tag: str) -> str: # Read version from package metadata if it is installed. try: - import importlib.metadata # move up when dropping Python 3.7 - return importlib.metadata.version("websockets") except ImportError: pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/w3c-import.log new file mode 100644 index 0000000000000..d16dd011f0a4c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/w3c-import.log @@ -0,0 +1,38 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/__main__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/auth.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/connection.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/datastructures.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/exceptions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/frames.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/headers.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/http.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/http11.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/imports.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/protocol.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/py.typed +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/server.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/speedups.c +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/speedups.pyi +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/streams.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/typing.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/uri.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/utils.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/src/websockets/version.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/__init__.py new file mode 100644 index 0000000000000..dd78609f5ba12 --- /dev/null +++ b/LayoutTests/imported/w3c/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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/__init__.py new file mode 100644 index 0000000000000..a6834b8285a56 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/test_base.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/test_base.py new file mode 100644 index 0000000000000..b18ffb6fb8638 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/test_base.py @@ -0,0 +1,4 @@ +from websockets.extensions.base import * + + +# Abstract classes don't provide any behavior to test. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/test_permessage_deflate.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/test_permessage_deflate.py new file mode 100644 index 0000000000000..0e698566fb99a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/test_permessage_deflate.py @@ -0,0 +1,977 @@ +import dataclasses +import unittest + +from websockets.exceptions import ( + DuplicateParameter, + InvalidParameterName, + InvalidParameterValue, + NegotiationError, + PayloadTooBig, + ProtocolError, +) +from websockets.extensions.permessage_deflate import * +from websockets.frames import ( + OP_BINARY, + OP_CLOSE, + OP_CONT, + OP_PING, + OP_PONG, + OP_TEXT, + Close, + CloseCode, + Frame, +) + +from .utils import ClientNoOpExtensionFactory, ServerNoOpExtensionFactory + + +class PerMessageDeflateTestsMixin: + 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, PerMessageDeflateTestsMixin): + 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(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(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(OP_CLOSE, Close(CloseCode.NORMAL_CLOSURE, "").serialize()) + + 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(OP_TEXT, "café".encode("utf-8")) + + enc_frame = self.extension.encode(frame) + + self.assertEqual( + enc_frame, + dataclasses.replace(frame, 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(OP_BINARY, b"tea") + + enc_frame = self.extension.encode(frame) + + self.assertEqual( + enc_frame, + dataclasses.replace(frame, 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(OP_TEXT, "café".encode("utf-8"), fin=False) + frame2 = Frame(OP_CONT, " & ".encode("utf-8"), fin=False) + frame3 = Frame(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, + dataclasses.replace( + frame1, rsv1=True, data=b"JNL;\xbc\x12\x00\x00\x00\xff\xff" + ), + ) + self.assertEqual( + enc_frame2, + dataclasses.replace(frame2, data=b"RPS\x00\x00\x00\x00\xff\xff"), + ) + self.assertEqual( + enc_frame3, + dataclasses.replace(frame3, 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(OP_TEXT, b"tea ", fin=False) + frame2 = Frame(OP_CONT, b"time") + + enc_frame1 = self.extension.encode(frame1) + enc_frame2 = self.extension.encode(frame2) + + self.assertEqual( + enc_frame1, + dataclasses.replace( + frame1, rsv1=True, data=b"*IMT\x00\x00\x00\x00\xff\xff" + ), + ) + self.assertEqual( + enc_frame2, + dataclasses.replace(frame2, 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(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(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(OP_TEXT, "café".encode("utf-8"), fin=False) + frame2 = Frame(OP_CONT, " & ".encode("utf-8"), fin=False) + frame3 = Frame(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(OP_TEXT, b"tea ", fin=False) + frame2 = Frame(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(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(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(ProtocolError): + self.extension.decode(enc_frame2) + + def test_local_no_context_takeover(self): + # No context takeover when encoding and decoding messages. + self.extension = PerMessageDeflate(True, True, 15, 15) + + frame = Frame(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(OP_TEXT, "café".encode("utf-8")) + + enc_frame = extension.encode(frame) + + self.assertEqual( + enc_frame, + dataclasses.replace( + frame, + rsv1=True, + data=b"\x00\x05\x00\xfa\xffcaf\xc3\xa9\x00", # not compressed + ), + ) + + # Frames aren't decoded beyond max_size. + + def test_decompress_max_size(self): + frame = Frame(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, PerMessageDeflateTestsMixin +): + 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)] + ) + + def test_enable_client_permessage_deflate(self): + for extensions, ( + expected_len, + expected_position, + expected_compress_settings, + ) in [ + ( + None, + (1, 0, {"memLevel": 5}), + ), + ( + [], + (1, 0, {"memLevel": 5}), + ), + ( + [ClientNoOpExtensionFactory()], + (2, 1, {"memLevel": 5}), + ), + ( + [ClientPerMessageDeflateFactory(compress_settings={"memLevel": 7})], + (1, 0, {"memLevel": 7}), + ), + ( + [ + ClientPerMessageDeflateFactory(compress_settings={"memLevel": 7}), + ClientNoOpExtensionFactory(), + ], + (2, 0, {"memLevel": 7}), + ), + ( + [ + ClientNoOpExtensionFactory(), + ClientPerMessageDeflateFactory(compress_settings={"memLevel": 7}), + ], + (2, 1, {"memLevel": 7}), + ), + ]: + with self.subTest(extensions=extensions): + extensions = enable_client_permessage_deflate(extensions) + self.assertEqual(len(extensions), expected_len) + extension = extensions[expected_position] + self.assertIsInstance(extension, ClientPerMessageDeflateFactory) + self.assertEqual( + extension.compress_settings, + expected_compress_settings, + ) + + +class ServerPerMessageDeflateFactoryTests( + unittest.TestCase, PerMessageDeflateTestsMixin +): + 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}), + (False, False, None, 12, {}, True), # require_client_max_window_bits + ]: + 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}), + (False, False, None, None, {}, True), # require_client_max_window_bits + ]: + 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), + [], + [], + (False, False, 15, 15), + ), + ( + (False, False, None, 12, {}, True), + [], + 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)] + ) + + def test_enable_server_permessage_deflate(self): + for extensions, ( + expected_len, + expected_position, + expected_compress_settings, + ) in [ + ( + None, + (1, 0, {"memLevel": 5}), + ), + ( + [], + (1, 0, {"memLevel": 5}), + ), + ( + [ServerNoOpExtensionFactory()], + (2, 1, {"memLevel": 5}), + ), + ( + [ServerPerMessageDeflateFactory(compress_settings={"memLevel": 7})], + (1, 0, {"memLevel": 7}), + ), + ( + [ + ServerPerMessageDeflateFactory(compress_settings={"memLevel": 7}), + ServerNoOpExtensionFactory(), + ], + (2, 0, {"memLevel": 7}), + ), + ( + [ + ServerNoOpExtensionFactory(), + ServerPerMessageDeflateFactory(compress_settings={"memLevel": 7}), + ], + (2, 1, {"memLevel": 7}), + ), + ]: + with self.subTest(extensions=extensions): + extensions = enable_server_permessage_deflate(extensions) + self.assertEqual(len(extensions), expected_len) + extension = extensions[expected_position] + self.assertIsInstance(extension, ServerPerMessageDeflateFactory) + self.assertEqual( + extension.compress_settings, + expected_compress_settings, + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/utils.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/utils.py new file mode 100644 index 0000000000000..24fb74b4e6ede --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/utils.py @@ -0,0 +1,113 @@ +import dataclasses + +from websockets.exceptions import NegotiationError + + +class OpExtension: + name = "x-op" + + def __init__(self, op=None): + self.op = op + + def decode(self, frame, *, max_size=None): + return frame # pragma: no cover + + def encode(self, frame): + return frame # pragma: no cover + + def __eq__(self, other): + return isinstance(other, OpExtension) and self.op == other.op + + +class ClientOpExtensionFactory: + name = "x-op" + + def __init__(self, op=None): + self.op = op + + def get_request_params(self): + return [("op", self.op)] + + def process_response_params(self, params, accepted_extensions): + if params != [("op", self.op)]: + raise NegotiationError() + return OpExtension(self.op) + + +class ServerOpExtensionFactory: + name = "x-op" + + def __init__(self, op=None): + self.op = op + + def process_request_params(self, params, accepted_extensions): + if params != [("op", self.op)]: + raise NegotiationError() + return [("op", self.op)], OpExtension(self.op) + + +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 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 Rsv2Extension: + name = "x-rsv2" + + def decode(self, frame, *, max_size=None): + assert frame.rsv2 + return dataclasses.replace(frame, rsv2=False) + + def encode(self, frame): + assert not frame.rsv2 + return dataclasses.replace(frame, rsv2=True) + + def __eq__(self, other): + return isinstance(other, Rsv2Extension) + + +class ClientRsv2ExtensionFactory: + name = "x-rsv2" + + def get_request_params(self): + return [] + + def process_response_params(self, params, accepted_extensions): + return Rsv2Extension() + + +class ServerRsv2ExtensionFactory: + name = "x-rsv2" + + def process_request_params(self, params, accepted_extensions): + return [], Rsv2Extension() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/w3c-import.log new file mode 100644 index 0000000000000..bb1c6ab5fe580 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/test_base.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/test_permessage_deflate.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/extensions/utils.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/__init__.py new file mode 100644 index 0000000000000..a6834b8285a56 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_auth.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_auth.py new file mode 100644 index 0000000000000..3754bcf3a5db7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_auth.py @@ -0,0 +1,184 @@ +import hmac +import unittest +import urllib.error + +from websockets.exceptions import InvalidStatusCode +from websockets.headers import build_authorization_basic +from websockets.legacy.auth import * +from websockets.legacy.auth import is_credentials + +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 CustomWebSocketServerProtocol(BasicAuthWebSocketServerProtocol): + async def process_request(self, path, request_headers): + type(self).used = True + return await super().process_request(path, request_headers) + + +class CheckWebSocketServerProtocol(BasicAuthWebSocketServerProtocol): + async def check_credentials(self, username, password): + return hmac.compare_digest(password, "letmein") + + +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 hmac.compare_digest(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()) + + create_protocol_custom_protocol = basic_auth_protocol_factory( + realm="auth-tests", + credentials=[("hello", "iloveyou")], + create_protocol=CustomWebSocketServerProtocol, + ) + + @with_server(create_protocol=create_protocol_custom_protocol) + @with_client(user_info=("hello", "iloveyou")) + def test_basic_auth_custom_protocol(self): + self.assertTrue(CustomWebSocketServerProtocol.used) + del CustomWebSocketServerProtocol.used + self.loop.run_until_complete(self.client.send("Hello!")) + self.loop.run_until_complete(self.client.recv()) + + @with_server(create_protocol=CheckWebSocketServerProtocol) + @with_client(user_info=("hello", "letmein")) + def test_basic_auth_custom_protocol_subclass(self): + self.loop.run_until_complete(self.client.send("Hello!")) + self.loop.run_until_complete(self.client.recv()) + + # CustomWebSocketServerProtocol doesn't override check_credentials + @with_server(create_protocol=CustomWebSocketServerProtocol) + def test_basic_auth_defaults_to_deny_all(self): + with self.assertRaises(InvalidStatusCode) as raised: + self.start_client(user_info=("hello", "iloveyou")) + self.assertEqual(raised.exception.status_code, 401) + + @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_username(self): + with self.assertRaises(InvalidStatusCode) as raised: + self.start_client(user_info=("goodbye", "iloveyou")) + self.assertEqual(raised.exception.status_code, 401) + + @with_server(create_protocol=create_protocol) + def test_basic_auth_invalid_password(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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_client_server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_client_server.py new file mode 100644 index 0000000000000..c49d91b707581 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_client_server.py @@ -0,0 +1,1636 @@ +import asyncio +import contextlib +import functools +import http +import logging +import platform +import random +import socket +import ssl +import sys +import unittest +import unittest.mock +import urllib.error +import urllib.request +import warnings + +from websockets.datastructures import Headers +from websockets.exceptions import ( + ConnectionClosed, + InvalidHandshake, + InvalidHeader, + InvalidStatusCode, + NegotiationError, +) +from websockets.extensions.permessage_deflate import ( + ClientPerMessageDeflateFactory, + PerMessageDeflate, + ServerPerMessageDeflateFactory, +) +from websockets.frames import CloseCode +from websockets.http import USER_AGENT +from websockets.legacy.client import * +from websockets.legacy.compatibility import asyncio_timeout +from websockets.legacy.handshake import build_response +from websockets.legacy.http import read_response +from websockets.legacy.server import * +from websockets.protocol import State +from websockets.uri import parse_uri + +from ..extensions.utils import ( + ClientNoOpExtensionFactory, + NoOpExtension, + ServerNoOpExtensionFactory, +) +from ..utils import CERTIFICATE, MS, temp_unix_socket_path +from .utils import AsyncioTestCase + + +async def default_handler(ws): + if ws.path == "/deprecated_attributes": + await ws.recv() # delay that allows catching warnings + await ws.send(repr((ws.host, ws.port, ws.secure))) + elif ws.path == "/close_timeout": + await ws.send(repr(ws.close_timeout)) + elif ws.path == "/path": + await ws.send(str(ws.path)) + elif ws.path == "/headers": + await ws.send(repr(ws.request_headers)) + await ws.send(repr(ws.response_headers)) + elif ws.path == "/extensions": + await ws.send(repr(ws.extensions)) + elif ws.path == "/subprotocol": + await ws.send(repr(ws.subprotocol)) + elif ws.path == "/slow_stop": + await ws.wait_closed() + await asyncio.sleep(2 * MS) + else: + await ws.send((await ws.recv())) + + +async def redirect_request(path, headers, test, status): + if path == "/absolute_redirect": + location = get_server_uri(test.server, test.secure, "/") + elif path == "/relative_redirect": + location = "/" + elif path == "/infinite": + location = get_server_uri(test.server, test.secure, "/infinite") + elif path == "/force_insecure": + location = get_server_uri(test.server, False, "/") + elif path == "/missing_location": + return status, {}, b"" + else: + return None + return status, {"Location": location}, b"" + + +@contextlib.contextmanager +def temp_test_server(test, **kwargs): + test.start_server(**kwargs) + try: + yield + finally: + test.stop_server() + + +def temp_test_redirecting_server(test, status=http.HTTPStatus.FOUND, **kwargs): + process_request = functools.partial(redirect_request, test=test, status=status) + return temp_test_server(test, process_request=process_request, **kwargs) + + +@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_address(server): + """ + Return an address on which the given server listens. + + """ + # 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 + return server_socket.getsockname()[:2] # (no IPv6 on CI) + elif server_socket.family == socket.AF_INET: + return server_socket.getsockname() + else: # pragma: no cover + raise ValueError("expected an IPv6, IPv4, or Unix socket") + + +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 "" + host, port = get_server_address(server) + if ":" in host: # IPv6 address + host = f"[{host}]" + 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 ProcessRequestReturningIntProtocol(WebSocketServerProtocol): + async def process_request(self, path, request_headers): + assert path == "/__health__/" + return 200, [], b"OK\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 ClientServerTestsMixin: + secure = False + + def setUp(self): + super().setUp() + self.server = None + + def start_server(self, deprecation_warnings=None, **kwargs): + handler = kwargs.pop("handler", default_handler) + # Disable compression by default in tests. + kwargs.setdefault("compression", None) + # Disable pings by default in tests. + kwargs.setdefault("ping_interval", None) + + # This logic is encapsulated in a coroutine to prevent it from executing + # before the event loop is running which causes asyncio.get_event_loop() + # to raise a DeprecationWarning on Python ≥ 3.10. + async def start_server(): + return await serve(handler, "localhost", 0, **kwargs) + + with warnings.catch_warnings(record=True) as recorded_warnings: + warnings.simplefilter("always") + 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_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_uri = get_server_uri(self.server, secure, resource_name, user_info) + + # This logic is encapsulated in a coroutine to prevent it from executing + # before the event loop is running which causes asyncio.get_event_loop() + # to raise a DeprecationWarning on Python ≥ 3.10. + async def start_client(): + return await connect(server_uri, **kwargs) + + with warnings.catch_warnings(record=True) as recorded_warnings: + warnings.simplefilter("always") + 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): + self.loop.run_until_complete( + asyncio.wait_for(self.client.close_connection_task, timeout=1) + ) + + def stop_server(self): + self.server.close() + self.loop.run_until_complete( + asyncio.wait_for(self.server.wait_closed(), timeout=1) + ) + + @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?://:'. + 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(CERTIFICATE) + return ssl_context + + @property + def client_context(self): + ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ssl_context.load_verify_locations(CERTIFICATE) + 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!") + + 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 self.temp_client("/absolute_redirect"): + self.loop.run_until_complete(self.client.send("Hello!")) + reply = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(reply, "Hello!") + + def test_redirect_relative_location(self): + with temp_test_redirecting_server(self): + with self.temp_client("/relative_redirect"): + 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): + with self.assertRaises(InvalidHandshake): + with self.temp_client("/infinite"): + self.fail("did not raise") + + def test_redirect_missing_location(self): + with temp_test_redirecting_server(self): + with self.assertRaises(InvalidHeader): + with self.temp_client("/missing_location"): + self.fail("did not raise") + + def test_loop_backwards_compatibility(self): + with self.temp_server( + loop=self.loop, + deprecation_warnings=["remove loop argument"], + ): + with self.temp_client( + loop=self.loop, + deprecation_warnings=["remove loop argument"], + ): + 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. + scheme = "wss" if wsuri.secure else "ws" + port = 65535 - wsuri.port + changed_uri = f"{scheme}://example.com:{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 recv_into(self, *args, **kwargs): + self.used_for_read = True + return super().recv_into(*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): + 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 temp_unix_socket_path() as path: + # Like self.start_server() but with unix_serve(). + async def start_server(): + return await unix_serve(default_handler, path) + + self.server = self.loop.run_until_complete(start_server()) + + try: + # Like self.start_client() but with unix_connect() + async def start_client(): + return await unix_connect(path) + + self.client = self.loop.run_until_complete(start_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() + + def test_ws_handler_argument_backwards_compatibility(self): + async def handler_with_path(ws, path): + await ws.send(path) + + with self.temp_server( + handler=handler_with_path, + # Enable deprecation warning and announce deprecation in 11.0. + # deprecation_warnings=["remove second argument of ws_handler"], + ): + with self.temp_client("/path"): + self.assertEqual( + self.loop.run_until_complete(self.client.recv()), + "/path", + ) + + def test_ws_handler_argument_backwards_compatibility_partial(self): + async def handler_with_path(ws, path, extra): + await ws.send(path) + + bound_handler_with_path = functools.partial(handler_with_path, extra=None) + + with self.temp_server( + handler=bound_handler_with_path, + # Enable deprecation warning and announce deprecation in 11.0. + # deprecation_warnings=["remove second argument of ws_handler"], + ): + with self.temp_client("/path"): + self.assertEqual( + self.loop.run_until_complete(self.client.recv()), + "/path", + ) + + 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: + warnings.simplefilter("always") + 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: + warnings.simplefilter("always") + 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: + warnings.simplefilter("always") + 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: + warnings.simplefilter("always") + 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={"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={"User-Agent": "websockets"}) + def test_protocol_custom_user_agent_header_legacy(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', 'websockets')", req_headers) + + @with_server() + @with_client("/headers", user_agent_header=None) + def test_protocol_no_user_agent_header(self): + req_headers = self.loop.run_until_complete(self.client.recv()) + self.loop.run_until_complete(self.client.recv()) + self.assertNotIn("User-Agent", req_headers) + + @with_server() + @with_client("/headers", user_agent_header="websockets") + def test_protocol_custom_user_agent_header(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', 'websockets')", req_headers) + + @with_server(extra_headers=lambda p, r: {"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: 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={"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={"Server": "websockets"}) + @with_client("/headers") + def test_protocol_custom_server_header_legacy(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', 'websockets')", resp_headers) + + @with_server(server_header=None) + @with_client("/headers") + def test_protocol_no_server_header(self): + self.loop.run_until_complete(self.client.recv()) + resp_headers = self.loop.run_until_complete(self.client.recv()) + self.assertNotIn("Server", resp_headers) + + @with_server(server_header="websockets") + @with_client("/headers") + def test_protocol_custom_server_header(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', 'websockets')", resp_headers) + + @with_server(create_protocol=HealthCheckServerProtocol) + def test_http_request_http_endpoint(self): + # Making an HTTP request to an 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 an 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 an 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() + + @with_server(create_protocol=HealthCheckServerProtocol, server_header=None) + def test_http_request_no_server_header(self): + response = self.loop.run_until_complete(self.make_http_request("/__health__/")) + + with contextlib.closing(response): + self.assertNotIn("Server", response.headers) + + @with_server(create_protocol=HealthCheckServerProtocol, server_header="websockets") + def test_http_request_custom_server_header(self): + response = self.loop.run_until_complete(self.make_http_request("/__health__/")) + + with contextlib.closing(response): + self.assertEqual(response.headers["Server"], "websockets") + + @with_server(create_protocol=ProcessRequestReturningIntProtocol) + def test_process_request_returns_int_status(self): + 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"OK\n") + + 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, + require_client_max_window_bits=True, + ), + ServerPerMessageDeflateFactory(), + ] + ) + @with_client( + "/extensions", + extensions=[ + ClientPerMessageDeflateFactory(client_max_window_bits=None), + ], + ) + 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, 12, 12)]) + ) + self.assertEqual( + repr(self.client.extensions), + repr([PerMessageDeflate(False, False, 12, 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") + + def test_invalid_subprotocol_server(self): + with self.assertRaises(TypeError): + self.start_server(subprotocols="sip") + + @with_server() + def test_invalid_subprotocol_client(self): + with self.assertRaises(TypeError): + self.start_client(subprotocols="sip") + + @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.legacy.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.legacy.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.legacy.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.legacy.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.legacy.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.legacy.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(create_protocol=SlowOpeningHandshakeProtocol) + def test_client_connect_canceled_during_handshake(self): + sock = socket.create_connection(get_server_address(self.server)) + sock.send(b"") # socket is connected + + async def cancelled_client(): + start_client = connect(get_server_uri(self.server), sock=sock) + async with asyncio_timeout(5 * MS): + await start_client + + with self.assertRaises(asyncio.TimeoutError): + self.loop.run_until_complete(cancelled_client()) + + with self.assertRaises(OSError): + sock.send(b"") # socket is closed + + @with_server() + @unittest.mock.patch("websockets.legacy.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, CloseCode.INTERNAL_ERROR) + + @with_server() + @unittest.mock.patch("websockets.legacy.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, CloseCode.ABNORMAL_CLOSURE) + + @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.send("Hello!")) + self.loop.run_until_complete(self.client.recv()) + + # Server closed the connection with 1001 Going Away. + self.assertEqual(self.client.close_code, CloseCode.GOING_AWAY) + self.assertEqual(server_ws.close_code, CloseCode.GOING_AWAY) + + @with_server() + def test_server_shuts_down_gracefully_during_connection_handling(self): + with self.temp_client(): + server_ws = next(iter(self.server.websockets)) + self.server.close(close_connections=False) + self.loop.run_until_complete(self.client.send("Hello!")) + self.loop.run_until_complete(self.client.recv()) + + # Client closed the connection with 1000 OK. + self.assertEqual(self.client.close_code, CloseCode.NORMAL_CLOSURE) + self.assertEqual(server_ws.close_code, CloseCode.NORMAL_CLOSURE) + + @with_server() + def test_server_shuts_down_and_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.legacy.server.WebSocketServerProtocol.write_http_response" + ) + @unittest.mock.patch( + "websockets.legacy.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.legacy.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, CloseCode.ABNORMAL_CLOSURE) + + +class ClientServerTests( + CommonClientServerTests, ClientServerTestsMixin, AsyncioTestCase +): + pass + + +class SecureClientServerTests( + CommonClientServerTests, SecureClientServerTestsMixin, AsyncioTestCase +): + # The implementation of this test makes it hard to run it over TLS. + test_client_connect_canceled_during_handshake = None + + # TLS over Unix sockets doesn't make sense. + test_unix_socket = None + + # This test fails under PyPy due to a difference with CPython. + if platform.python_implementation() == "PyPy": # pragma: no cover + test_http_request_ws_endpoint = None + + @with_server() + def test_ws_uri_is_rejected(self): + with self.assertRaises(ValueError): + self.start_client( + uri=get_server_uri(self.server, secure=False), ssl=self.client_context + ) + + def test_redirect_insecure(self): + with temp_test_redirecting_server(self): + with self.assertRaises(InvalidHandshake): + with self.temp_client("/force_insecure"): + self.fail("did not raise") + + +class ClientServerOriginTests(ClientServerTestsMixin, AsyncioTestCase): + @with_server(origins=["http://localhost"]) + @with_client(origin="http://localhost") + def test_checking_origin_succeeds(self): + self.loop.run_until_complete(self.client.send("Hello!")) + self.assertEqual(self.loop.run_until_complete(self.client.recv()), "Hello!") + + @with_server(origins=["http://localhost"]) + def test_checking_origin_fails(self): + with self.assertRaisesRegex( + InvalidHandshake, "server rejected WebSocket connection: HTTP 403" + ): + self.start_client(origin="http://otherhost") + + @with_server(origins=["http://localhost"]) + def test_checking_origins_fails_with_multiple_headers(self): + with self.assertRaisesRegex( + InvalidHandshake, "server rejected WebSocket connection: HTTP 400" + ): + self.start_client( + origin="http://localhost", + extra_headers=[("Origin", "http://otherhost")], + ) + + @with_server(origins=[None]) + @with_client() + def test_checking_lack_of_origin_succeeds(self): + self.loop.run_until_complete(self.client.send("Hello!")) + self.assertEqual(self.loop.run_until_complete(self.client.recv()), "Hello!") + + @with_server(origins=[""]) + # The deprecation warning is raised when a client connects to the server. + @with_client(deprecation_warnings=["use None instead of '' in origins"]) + def test_checking_lack_of_origin_succeeds_backwards_compatibility(self): + self.loop.run_until_complete(self.client.send("Hello!")) + self.assertEqual(self.loop.run_until_complete(self.client.recv()), "Hello!") + + +@unittest.skipIf( + sys.version_info[:2] >= (3, 11), "asyncio.coroutine has been removed in Python 3.11" +) +class YieldFromTests(ClientServerTestsMixin, AsyncioTestCase): # pragma: no cover + @with_server() + def test_client(self): + # @asyncio.coroutine is deprecated on Python ≥ 3.8 + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + + @asyncio.coroutine + def run_client(): + # Yield from connect. + client = yield from connect(get_server_uri(self.server)) + self.assertEqual(client.state, State.OPEN) + yield from client.close() + self.assertEqual(client.state, State.CLOSED) + + self.loop.run_until_complete(run_client()) + + def test_server(self): + # @asyncio.coroutine is deprecated on Python ≥ 3.8 + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + + @asyncio.coroutine + def run_server(): + # Yield from serve. + server = yield from serve(default_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(ClientServerTestsMixin, AsyncioTestCase): + @with_server() + def test_client(self): + async def run_client(): + # Await connect. + client = await connect(get_server_uri(self.server)) + self.assertEqual(client.state, State.OPEN) + await client.close() + self.assertEqual(client.state, State.CLOSED) + + self.loop.run_until_complete(run_client()) + + def test_server(self): + async def run_server(): + # Await serve. + server = await serve(default_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(ClientServerTestsMixin, AsyncioTestCase): + @with_server() + def test_client(self): + async def run_client(): + # Use connect as an asynchronous context manager. + async with connect(get_server_uri(self.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()) + + def test_server(self): + async def run_server(): + # Use serve as an asynchronous context manager. + async with serve(default_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(default_handler, path) as server: + self.assertTrue(server.sockets) + + # Check that exiting the context manager closed the server. + self.assertFalse(server.sockets) + + with temp_unix_socket_path() as path: + self.loop.run_until_complete(run_server(path)) + + +class AsyncIteratorTests(ClientServerTestsMixin, 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!"] + + async def echo_handler(ws): + for message in AsyncIteratorTests.MESSAGES: + await ws.send(message) + + @with_server(handler=echo_handler) + def test_iterate_on_messages(self): + messages = [] + + async def run_client(): + nonlocal messages + async with connect(get_server_uri(self.server)) as ws: + async for message in ws: + messages.append(message) + + self.loop.run_until_complete(run_client()) + + self.assertEqual(messages, self.MESSAGES) + + async def echo_handler_going_away(ws): + for message in AsyncIteratorTests.MESSAGES: + await ws.send(message) + await ws.close(CloseCode.GOING_AWAY) + + @with_server(handler=echo_handler_going_away) + def test_iterate_on_messages_going_away_exit_ok(self): + messages = [] + + async def run_client(): + nonlocal messages + async with connect(get_server_uri(self.server)) as ws: + async for message in ws: + messages.append(message) + + self.loop.run_until_complete(run_client()) + + self.assertEqual(messages, self.MESSAGES) + + async def echo_handler_internal_error(ws): + for message in AsyncIteratorTests.MESSAGES: + await ws.send(message) + await ws.close(CloseCode.INTERNAL_ERROR) + + @with_server(handler=echo_handler_internal_error) + def test_iterate_on_messages_internal_error_exit_not_ok(self): + messages = [] + + async def run_client(): + nonlocal messages + async with connect(get_server_uri(self.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) + + +class ReconnectionTests(ClientServerTestsMixin, AsyncioTestCase): + async def echo_handler(ws): + async for msg in ws: + await ws.send(msg) + + service_available = True + + async def maybe_service_unavailable(path, headers): + if not ReconnectionTests.service_available: + return http.HTTPStatus.SERVICE_UNAVAILABLE, [], b"" + + async def disable_server(self, duration): + ReconnectionTests.service_available = False + await asyncio.sleep(duration) + ReconnectionTests.service_available = True + + @with_server(handler=echo_handler, process_request=maybe_service_unavailable) + def test_reconnect(self): + # Big, ugly integration test :-( + + async def run_client(): + iteration = 0 + connect_inst = connect(get_server_uri(self.server)) + connect_inst.BACKOFF_MIN = 10 * MS + connect_inst.BACKOFF_MAX = 99 * MS + connect_inst.BACKOFF_INITIAL = 0 + # coverage has a hard time dealing with this code - I give up. + async for ws in connect_inst: # pragma: no cover + await ws.send("spam") + msg = await ws.recv() + self.assertEqual(msg, "spam") + + iteration += 1 + if iteration == 1: + # Exit block normally. + pass + elif iteration == 2: + # Disable server for a little bit + asyncio.create_task(self.disable_server(50 * MS)) + await asyncio.sleep(0) + elif iteration == 3: + # Exit block after catching connection error. + server_ws = next(iter(self.server.websockets)) + await server_ws.close() + with self.assertRaises(ConnectionClosed): + await ws.recv() + else: + # Exit block with an exception. + raise Exception("BOOM") + pass # work around bug in coverage + + with self.assertLogs("websockets", logging.INFO) as logs: + with self.assertRaisesRegex(Exception, "BOOM"): + self.loop.run_until_complete(run_client()) + + # Iteration 1 + self.assertEqual( + [record.getMessage() for record in logs.records][:2], + [ + "connection open", + "connection closed", + ], + ) + # Iteration 2 + self.assertEqual( + [record.getMessage() for record in logs.records][2:4], + [ + "connection open", + "connection closed", + ], + ) + # Iteration 3 + self.assertEqual( + [record.getMessage() for record in logs.records][4:-1], + [ + "connection rejected (503 Service Unavailable)", + "connection closed", + "! connect failed; reconnecting in 0.0 seconds", + ] + + [ + "connection rejected (503 Service Unavailable)", + "connection closed", + "! connect failed again; retrying in 0 seconds", + ] + * ((len(logs.records) - 8) // 3) + + [ + "connection open", + "connection closed", + ], + ) + # Iteration 4 + self.assertEqual( + [record.getMessage() for record in logs.records][-1:], + [ + "connection open", + ], + ) + + +class LoggerTests(ClientServerTestsMixin, AsyncioTestCase): + def test_logger_client(self): + with self.assertLogs("test.server", logging.DEBUG) as server_logs: + self.start_server(logger=logging.getLogger("test.server")) + with self.assertLogs("test.client", logging.DEBUG) as client_logs: + self.start_client(logger=logging.getLogger("test.client")) + self.loop.run_until_complete(self.client.send("Hello!")) + self.loop.run_until_complete(self.client.recv()) + self.stop_client() + self.stop_server() + + self.assertGreater(len(server_logs.records), 0) + self.assertGreater(len(client_logs.records), 0) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_framing.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_framing.py new file mode 100644 index 0000000000000..e1e4c891b0307 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_framing.py @@ -0,0 +1,206 @@ +import asyncio +import codecs +import dataclasses +import unittest +import unittest.mock +import warnings + +from websockets.exceptions import PayloadTooBig, ProtocolError +from websockets.frames import OP_BINARY, OP_CLOSE, OP_PING, OP_PONG, OP_TEXT, CloseCode +from websockets.legacy.framing import * + +from .utils import AsyncioTestCase + + +class FramingTests(AsyncioTestCase): + def decode(self, message, mask=False, max_size=None, extensions=None): + stream = asyncio.StreamReader(loop=self.loop) + stream.feed_data(message) + stream.feed_eof() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + frame = self.loop.run_until_complete( + Frame.read( + stream.readexactly, + mask=mask, + max_size=max_size, + extensions=extensions, + ) + ) + # Make sure all the data was consumed. + self.assertTrue(stream.at_eof()) + return frame + + def encode(self, frame, mask=False, extensions=None): + write = unittest.mock.Mock() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + 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) + decoded.check() + 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 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_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_extensions(self): + class Rot13: + @staticmethod + def encode(frame): + assert frame.opcode == OP_TEXT + text = frame.data.decode() + data = codecs.encode(text, "rot13").encode() + return dataclasses.replace(frame, 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()] + ) + + +class ParseAndSerializeCloseTests(unittest.TestCase): + def assertCloseData(self, code, reason, data): + """ + Serializing code / reason yields data. Parsing data yields code / reason. + + """ + serialized = serialize_close(code, reason) + self.assertEqual(serialized, data) + parsed = parse_close(data) + self.assertEqual(parsed, (code, reason)) + + def test_parse_close_and_serialize_close(self): + self.assertCloseData(CloseCode.NORMAL_CLOSURE, "", b"\x03\xe8") + self.assertCloseData(CloseCode.NORMAL_CLOSURE, "OK", b"\x03\xe8OK") + + def test_parse_close_empty(self): + self.assertEqual(parse_close(b""), (CloseCode.NO_STATUS_RCVD, "")) + + 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, "") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_handshake.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_handshake.py new file mode 100644 index 0000000000000..661ae64fc4796 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_handshake.py @@ -0,0 +1,184 @@ +import contextlib +import unittest + +from websockets.datastructures import Headers +from websockets.exceptions import ( + InvalidHandshake, + InvalidHeader, + InvalidHeaderValue, + InvalidUpgrade, +) +from websockets.legacy.handshake import * +from websockets.utils import accept_key + + +class HandshakeTests(unittest.TestCase): + 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_key(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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_http.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_http.py new file mode 100644 index 0000000000000..15d53e08d229c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_http.py @@ -0,0 +1,135 @@ +import asyncio + +from websockets.exceptions import SecurityError +from websockets.legacy.http import * +from websockets.legacy.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" * 129 + b"\r\n") + with self.assertRaises(SecurityError): + await read_headers(self.stream) + + async def test_line_limit(self): + # Header line contains 5 + 8186 + 2 = 8193 bytes. + self.stream.feed_data(b"foo: " + b"a" * 8186 + 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) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_protocol.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_protocol.py new file mode 100644 index 0000000000000..f2eb0fea03f79 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_protocol.py @@ -0,0 +1,1708 @@ +import asyncio +import contextlib +import logging +import sys +import unittest +import unittest.mock +import warnings + +from websockets.exceptions import ConnectionClosed, InvalidState +from websockets.frames import ( + OP_BINARY, + OP_CLOSE, + OP_CONT, + OP_PING, + OP_PONG, + OP_TEXT, + Close, + CloseCode, +) +from websockets.legacy.framing import Frame +from websockets.legacy.protocol import WebSocketCommonProtocol, broadcast +from websockets.protocol import State + +from ..utils import MS +from .utils import 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() + + # This logic is encapsulated in a coroutine to prevent it from executing + # before the event loop is running which causes asyncio.get_event_loop() + # to raise a DeprecationWarning on Python ≥ 3.10. + + async def create_protocol(): + # Disable pings to make it easier to test what frames are sent exactly. + return WebSocketCommonProtocol(ping_interval=None) + + self.protocol = self.loop.run_until_complete(create_protocol()) + 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) + await original_drain() + + self.protocol._drain = delayed_drain + + close_frame = Frame( + True, + OP_CLOSE, + Close(CloseCode.NORMAL_CLOSURE, "close").serialize(), + ) + local_close = Frame( + True, + OP_CLOSE, + Close(CloseCode.NORMAL_CLOSURE, "local").serialize(), + ) + remote_close = Frame( + True, + OP_CLOSE, + Close(CloseCode.NORMAL_CLOSURE, "remote").serialize(), + ) + + 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=CloseCode.NORMAL_CLOSURE, reason="close"): + """ + Execute a closing handshake. + + This puts the connection in the CLOSED state. + + """ + close_frame_data = Close(code, reason).serialize() + # 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=CloseCode.NORMAL_CLOSURE, + 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 = Close(code, reason).serialize() + # Trigger the closing handshake from the local endpoint. + close_task = self.loop.create_task(self.protocol.close(code, reason)) + 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=CloseCode.NORMAL_CLOSURE, + 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 = Close(code, reason).serialize() + # 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, CloseCode.ABNORMAL_CLOSURE) + self.assertEqual(self.protocol.close_reason, "") + # A close frame was sent -- unless the connection was already lost. + if code == CloseCode.ABNORMAL_CLOSURE: + self.assertNoFrameSent() + else: + self.assertOneFrameSent(True, OP_CLOSE, Close(code, message).serialize()) + + @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): + async def create_protocol(): + return WebSocketCommonProtocol(ping_interval=None, timeout=5) + + with warnings.catch_warnings(record=True) as recorded: + warnings.simplefilter("always") + protocol = self.loop.run_until_complete(create_protocol()) + + self.assertEqual(protocol.close_timeout, 5) + self.assertDeprecationWarnings(recorded, ["rename timeout to close_timeout"]) + + def test_loop_backwards_compatibility(self): + loop = asyncio.new_event_loop() + self.addCleanup(loop.close) + + with warnings.catch_warnings(record=True) as recorded: + warnings.simplefilter("always") + protocol = WebSocketCommonProtocol(ping_interval=None, loop=loop) + + self.assertEqual(protocol.loop, loop) + self.assertDeprecationWarnings(recorded, ["remove loop argument"]) + + # 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()) + + def test_close_code(self): + self.close_connection(CloseCode.GOING_AWAY, "Bye!") + self.assertEqual(self.protocol.close_code, CloseCode.GOING_AWAY) + + def test_close_reason(self): + self.close_connection(CloseCode.GOING_AWAY, "Bye!") + self.assertEqual(self.protocol.close_reason, "Bye!") + + def test_close_code_not_set(self): + self.assertIsNone(self.protocol.close_code) + + def test_close_reason_not_set(self): + self.assertIsNone(self.protocol.close_reason) + + # 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(CloseCode.PROTOCOL_ERROR, "") + + def test_recv_unicode_error(self): + self.receive_frame(Frame(True, OP_TEXT, "café".encode("latin-1"))) + self.process_invalid_frames() + self.assertConnectionFailed(CloseCode.INVALID_DATA, "") + + 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(CloseCode.MESSAGE_TOO_BIG, "") + + 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(CloseCode.MESSAGE_TOO_BIG, "") + + 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(CloseCode.INTERNAL_ERROR, "") + + 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.assertRaises(RuntimeError) as raised: + self.loop.run_until_complete(self.protocol.recv()) + self.assertEqual( + str(raised.exception), + "cannot call recv while another coroutine " + "is already waiting for the next message", + ) + 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_dict(self): + with self.assertRaises(TypeError): + self.loop.run_until_complete(self.protocol.send({"not": "encoded"})) + self.assertNoFrameSent() + + 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_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, Close(CloseCode.INTERNAL_ERROR, "").serialize()), + ) + + 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") + + async def run_concurrently(): + await asyncio.gather( + send_iterable(), + send_concurrent(), + ) + + self.loop.run_until_complete(run_concurrently()) + + 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_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, Close(CloseCode.INTERNAL_ERROR, "").serialize()), + ) + + 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") + + async def run_concurrently(): + await asyncio.gather( + send_async_iterable(), + send_concurrent(), + ) + + self.loop.run_until_complete(run_concurrently()) + + 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_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_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_answer_ping_does_not_crash_if_connection_closing(self): + close_task = self.half_close_connection_local() + + self.receive_frame(Frame(True, OP_PING, b"test")) + self.run_loop_once() + + with self.assertNoLogs(): + self.loop.run_until_complete(self.protocol.close()) + + self.loop.run_until_complete(close_task) # cleanup + + def test_answer_ping_does_not_crash_if_connection_closed(self): + self.make_drain_slow() + # Drop the connection right after receiving a ping frame, + # which prevents responding with a pong frame properly. + self.receive_frame(Frame(True, OP_PING, b"test")) + self.receive_eof() + self.run_loop_once() + + with self.assertNoLogs(): + self.loop.run_until_complete(self.protocol.close()) + + 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): + pong_waiter = self.loop.run_until_complete(self.protocol.ping()) + self.assertFalse(pong_waiter.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(pong_waiter.done()) + + def test_abort_ping(self): + pong_waiter = self.loop.run_until_complete(self.protocol.ping()) + # Remove the frame from the buffer, else close_connection() complains. + self.last_sent_frame() + self.assertFalse(pong_waiter.done()) + self.close_connection() + self.assertTrue(pong_waiter.done()) + self.assertIsInstance(pong_waiter.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(). + ((pong_waiter, _timestamp),) = 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(pong_waiter._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): + pong_waiter = 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): + pong_waiter.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): + pong_waiter = self.loop.run_until_complete(self.protocol.ping()) + ping_frame = self.last_sent_frame() + pong_waiter.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(pong_waiter.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(RuntimeError): + self.loop.run_until_complete(self.protocol.ping(b"foobar")) + self.assertNoFrameSent() + + # Test the protocol's logic for measuring latency + + def test_record_latency_on_pong(self): + self.assertEqual(self.protocol.latency, 0) + self.loop.run_until_complete(self.protocol.ping(b"test")) + self.receive_frame(Frame(True, OP_PONG, b"test")) + self.run_loop_once() + self.assertGreater(self.protocol.latency, 0) + + def test_return_latency_on_pong(self): + pong_waiter = self.loop.run_until_complete(self.protocol.ping()) + ping_frame = self.last_sent_frame() + pong_frame = Frame(True, OP_PONG, ping_frame.data) + self.receive_frame(pong_frame) + latency = self.loop.run_until_complete(pong_waiter) + self.assertGreater(latency, 0) + + # 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(CloseCode.MESSAGE_TOO_BIG, "") + + 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(CloseCode.MESSAGE_TOO_BIG, "") + + 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(CloseCode.PROTOCOL_ERROR, "") + + 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(CloseCode.NO_STATUS_RCVD, "") + + 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(CloseCode.ABNORMAL_CLOSURE, "") + + # 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(CloseCode.ABNORMAL_CLOSURE, "") + + 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, CloseCode.NORMAL_CLOSURE) + 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 + + async def create_protocol(): + return WebSocketCommonProtocol( + ping_interval=ping_interval, + ping_timeout=ping_timeout, + ) + + self.protocol = self.loop.run_until_complete(create_protocol()) + + 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 acknowledged. + 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, + Close(CloseCode.INTERNAL_ERROR, "keepalive ping timeout").serialize(), + ) + + # 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)) + ((pong_waiter, _timestamp),) = 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): + pong_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 acknowledged. + 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(CloseCode.NORMAL_CLOSURE, "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(CloseCode.NORMAL_CLOSURE, "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(CloseCode.NORMAL_CLOSURE, "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(CloseCode.NORMAL_CLOSURE, "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() + self.run_loop_once() + + with self.assertNoLogs(): + self.loop.run_until_complete(self.protocol.close(reason="oh noes!")) + + self.assertConnectionClosed(CloseCode.NORMAL_CLOSURE, "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/python-websockets/websockets/issues/339 + self.loop.call_soon(self.receive_frame, self.remote_close) + self.loop.call_soon(self.receive_eof_if_client) + self.run_loop_once() + + self.loop.run_until_complete(self.protocol.close(reason="local")) + + self.assertConnectionClosed(CloseCode.NORMAL_CLOSURE, "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.run_loop_once() + + 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(CloseCode.NORMAL_CLOSURE, "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(CloseCode.PROTOCOL_ERROR, "") + + 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(CloseCode.ABNORMAL_CLOSURE, "") + + 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(CloseCode.NORMAL_CLOSURE, "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(CloseCode.NORMAL_CLOSURE, "close") + + # There is no test_local_close_during_send because this cannot really + # happen, considering that writes are serialized. + + def test_broadcast_text(self): + broadcast([self.protocol], "café") + self.assertOneFrameSent(True, OP_TEXT, "café".encode("utf-8")) + + def test_broadcast_binary(self): + broadcast([self.protocol], b"tea") + self.assertOneFrameSent(True, OP_BINARY, b"tea") + + def test_broadcast_type_error(self): + with self.assertRaises(TypeError): + broadcast([self.protocol], ["ca", "fé"]) + + def test_broadcast_no_clients(self): + broadcast([], "café") + self.assertNoFrameSent() + + def test_broadcast_two_clients(self): + broadcast([self.protocol, self.protocol], "café") + self.assertFramesSent( + (True, OP_TEXT, "café".encode("utf-8")), + (True, OP_TEXT, "café".encode("utf-8")), + ) + + def test_broadcast_skips_closed_connection(self): + self.close_connection() + + with self.assertNoLogs(): + broadcast([self.protocol], "café") + self.assertNoFrameSent() + + def test_broadcast_skips_closing_connection(self): + close_task = self.half_close_connection_local() + + with self.assertNoLogs(): + broadcast([self.protocol], "café") + self.assertNoFrameSent() + + self.loop.run_until_complete(close_task) # cleanup + + def test_broadcast_skips_connection_sending_fragmented_text(self): + self.make_drain_slow() + self.loop.create_task(self.protocol.send(["ca", "fé"])) + self.run_loop_once() + self.assertOneFrameSent(False, OP_TEXT, "ca".encode("utf-8")) + + with self.assertLogs("websockets", logging.WARNING) as logs: + broadcast([self.protocol], "café") + + self.assertEqual( + [record.getMessage() for record in logs.records][:2], + ["skipped broadcast: sending a fragmented message"], + ) + + @unittest.skipIf( + sys.version_info[:2] < (3, 11), "raise_exceptions requires Python 3.11+" + ) + def test_broadcast_reports_connection_sending_fragmented_text(self): + self.make_drain_slow() + self.loop.create_task(self.protocol.send(["ca", "fé"])) + self.run_loop_once() + self.assertOneFrameSent(False, OP_TEXT, "ca".encode("utf-8")) + + with self.assertRaises(ExceptionGroup) as raised: + broadcast([self.protocol], "café", raise_exceptions=True) + + self.assertEqual(str(raised.exception), "skipped broadcast (1 sub-exception)") + self.assertEqual( + str(raised.exception.exceptions[0]), "sending a fragmented message" + ) + + def test_broadcast_skips_connection_failing_to_send(self): + # Configure mock to raise an exception when writing to the network. + self.protocol.transport.write.side_effect = RuntimeError + + with self.assertLogs("websockets", logging.WARNING) as logs: + broadcast([self.protocol], "café") + + self.assertEqual( + [record.getMessage() for record in logs.records][:2], + ["skipped broadcast: failed to write message"], + ) + + @unittest.skipIf( + sys.version_info[:2] < (3, 11), "raise_exceptions requires Python 3.11+" + ) + def test_broadcast_reports_connection_failing_to_send(self): + # Configure mock to raise an exception when writing to the network. + self.protocol.transport.write.side_effect = RuntimeError("BOOM") + + with self.assertRaises(ExceptionGroup) as raised: + broadcast([self.protocol], "café", raise_exceptions=True) + + self.assertEqual(str(raised.exception), "skipped broadcast (1 sub-exception)") + self.assertEqual(str(raised.exception.exceptions[0]), "failed to write message") + self.assertEqual(str(raised.exception.exceptions[0].__cause__), "BOOM") + + +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(CloseCode.ABNORMAL_CLOSURE, "") + + 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(CloseCode.ABNORMAL_CLOSURE, "") + + 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.run_loop_once() + self.loop.run_until_complete(self.protocol.close(reason="close")) + # Due to a bug in coverage, this is erroneously reported as not covered. + self.assertConnectionClosed( # pragma: no cover + CloseCode.NORMAL_CLOSURE, + "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. + # Add another 10ms because this test is flaky and I don't understand. + with self.assertCompletesWithin(19 * 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.run_loop_once() + self.loop.run_until_complete(self.protocol.close(reason="close")) + # Due to a bug in coverage, this is erroneously reported as not covered. + self.assertConnectionClosed( # pragma: no cover + CloseCode.NORMAL_CLOSURE, + "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")) + # Due to a bug in coverage, this is erroneously reported as not covered. + self.assertConnectionClosed( # pragma: no cover + CloseCode.ABNORMAL_CLOSURE, + "", + ) + + 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")) + # Due to a bug in coverage, this is erroneously reported as not covered. + self.assertConnectionClosed( # pragma: no cover + CloseCode.ABNORMAL_CLOSURE, + "", + ) + + 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.run_loop_once() + self.loop.run_until_complete(self.protocol.close(reason="close")) + # Due to a bug in coverage, this is erroneously reported as not covered. + self.assertConnectionClosed( # pragma: no cover + CloseCode.NORMAL_CLOSURE, + "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 30ms. + # - 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. + # Add another 10ms because this test is flaky and I don't understand. + with self.assertCompletesWithin(29 * MS, 49 * 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.run_loop_once() + self.loop.run_until_complete(self.protocol.close(reason="close")) + # Due to a bug in coverage, this is erroneously reported as not covered. + self.assertConnectionClosed( # pragma: no cover + CloseCode.NORMAL_CLOSURE, + "close", + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/utils.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/utils.py new file mode 100644 index 0000000000000..4a21dcaeb5967 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/utils.py @@ -0,0 +1,84 @@ +import asyncio +import contextlib +import functools +import logging +import unittest + + +class AsyncioTestCase(unittest.TestCase): + """ + Base class for tests that sets up an isolated event loop for each test. + + IsolatedAsyncioTestCase was introduced in Python 3.8 for similar purposes + but isn't a drop-in replacement. + + """ + + def __init_subclass__(cls, **kwargs): + """ + Convert test coroutines to test functions. + + This supports asynchronous 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() + + # Remove when dropping Python < 3.10 + @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. + + """ + for recorded in recorded_warnings: + self.assertEqual(type(recorded.message), DeprecationWarning) + self.assertEqual( + set(str(recorded.message) for recorded in recorded_warnings), + set(expected_warnings), + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/w3c-import.log new file mode 100644 index 0000000000000..0e13c985d0789 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/w3c-import.log @@ -0,0 +1,24 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_auth.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_client_server.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_framing.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_handshake.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_http.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/test_protocol.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/legacy/utils.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/maxi_cov.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/maxi_cov.py new file mode 100644 index 0000000000000..2568dcf18bcac --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/maxi_cov.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python + +"""Measure coverage of each module by its test module.""" + +import glob +import os.path +import subprocess +import sys + + +UNMAPPED_SRC_FILES = ["websockets/version.py"] +UNMAPPED_TEST_FILES = ["tests/test_exports.py"] + + +def check_environment(): + """Check that prerequisites for running this script are met.""" + try: + import websockets # noqa: F401 + except ImportError: + print("failed to import websockets; is src on PYTHONPATH?") + return False + try: + import coverage # noqa: F401 + except ImportError: + print("failed to locate Coverage.py; is it installed?") + return False + return True + + +def get_mapping(src_dir="src"): + """Return a dict mapping each source file to its test file.""" + + # List source and test files. + + src_files = glob.glob( + os.path.join(src_dir, "websockets/**/*.py"), + recursive=True, + ) + test_files = glob.glob( + "tests/**/*.py", + recursive=True, + ) + + src_files = [ + os.path.relpath(src_file, src_dir) + for src_file in sorted(src_files) + if "legacy" not in os.path.dirname(src_file) + if os.path.basename(src_file) != "__init__.py" + and os.path.basename(src_file) != "__main__.py" + and os.path.basename(src_file) != "compatibility.py" + ] + test_files = [ + test_file + for test_file in sorted(test_files) + if "legacy" not in os.path.dirname(test_file) + and os.path.basename(test_file) != "__init__.py" + and os.path.basename(test_file).startswith("test_") + ] + + # Map source files to test files. + + mapping = {} + unmapped_test_files = [] + + for test_file in test_files: + dir_name, file_name = os.path.split(test_file) + assert dir_name.startswith("tests") + assert file_name.startswith("test_") + src_file = os.path.join( + "websockets" + dir_name[len("tests") :], + file_name[len("test_") :], + ) + if src_file in src_files: + mapping[src_file] = test_file + else: + unmapped_test_files.append(test_file) + + unmapped_src_files = list(set(src_files) - set(mapping)) + + # Ensure that all files are mapped. + + assert unmapped_src_files == UNMAPPED_SRC_FILES + assert unmapped_test_files == UNMAPPED_TEST_FILES + + return mapping + + +def get_ignored_files(src_dir="src"): + """Return the list of files to exclude from coverage measurement.""" + + return [ + # */websockets matches src/websockets and .tox/**/site-packages/websockets. + # There are no tests for the __main__ module and for compatibility modules. + "*/websockets/__main__.py", + "*/websockets/*/compatibility.py", + # This approach isn't applicable to the test suite of the legacy + # implementation, due to the huge test_client_server test module. + "*/websockets/legacy/*", + "tests/legacy/*", + ] + [ + # Exclude test utilities that are shared between several test modules. + # Also excludes this script. + test_file + for test_file in sorted(glob.glob("tests/**/*.py", recursive=True)) + if "legacy" not in os.path.dirname(test_file) + and os.path.basename(test_file) != "__init__.py" + and not os.path.basename(test_file).startswith("test_") + ] + + +def run_coverage(mapping, src_dir="src"): + # Initialize a new coverage measurement session. The --source option + # includes all files in the report, even if they're never imported. + print("\nInitializing session\n", flush=True) + subprocess.run( + [ + sys.executable, + "-m", + "coverage", + "run", + "--source", + ",".join([os.path.join(src_dir, "websockets"), "tests"]), + "--omit", + ",".join(get_ignored_files(src_dir)), + "-m", + "unittest", + ] + + UNMAPPED_TEST_FILES, + check=True, + ) + # Append coverage of each source module by the corresponding test module. + for src_file, test_file in mapping.items(): + print(f"\nTesting {src_file} with {test_file}\n", flush=True) + subprocess.run( + [ + sys.executable, + "-m", + "coverage", + "run", + "--append", + "--include", + ",".join([os.path.join(src_dir, src_file), test_file]), + "-m", + "unittest", + test_file, + ], + check=True, + ) + + +if __name__ == "__main__": + if not check_environment(): + sys.exit(1) + src_dir = sys.argv[1] if len(sys.argv) == 2 else "src" + mapping = get_mapping(src_dir) + run_coverage(mapping, src_dir) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/protocol.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/protocol.py new file mode 100644 index 0000000000000..4e843daab34f8 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/protocol.py @@ -0,0 +1,29 @@ +from websockets.protocol import Protocol + + +class RecordingProtocol(Protocol): + """ + Protocol subclass that records incoming frames. + + By interfacing with this protocol, you can check easily what the component + being testing sends during a test. + + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.frames_rcvd = [] + + def get_frames_rcvd(self): + """ + Get incoming frames received up to this point. + + Calling this method clears the list. Each frame is returned only once. + + """ + frames_rcvd, self.frames_rcvd = self.frames_rcvd, [] + return frames_rcvd + + def recv_frame(self, frame): + self.frames_rcvd.append(frame) + super().recv_frame(frame) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/__init__.py new file mode 100644 index 0000000000000..a6834b8285a56 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/client.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/client.py new file mode 100644 index 0000000000000..683893e88c96c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/client.py @@ -0,0 +1,39 @@ +import contextlib +import ssl + +from websockets.sync.client import * +from websockets.sync.server import WebSocketServer + +from ..utils import CERTIFICATE + + +__all__ = [ + "CLIENT_CONTEXT", + "run_client", + "run_unix_client", +] + + +CLIENT_CONTEXT = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) +CLIENT_CONTEXT.load_verify_locations(CERTIFICATE) + + +@contextlib.contextmanager +def run_client(wsuri_or_server, secure=None, resource_name="/", **kwargs): + if isinstance(wsuri_or_server, str): + wsuri = wsuri_or_server + else: + assert isinstance(wsuri_or_server, WebSocketServer) + if secure is None: + secure = "ssl_context" in kwargs + protocol = "wss" if secure else "ws" + host, port = wsuri_or_server.socket.getsockname() + wsuri = f"{protocol}://{host}:{port}{resource_name}" + with connect(wsuri, **kwargs) as client: + yield client + + +@contextlib.contextmanager +def run_unix_client(path, **kwargs): + with unix_connect(path, **kwargs) as client: + yield client diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/connection.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/connection.py new file mode 100644 index 0000000000000..89d4909ee1312 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/connection.py @@ -0,0 +1,109 @@ +import contextlib +import time + +from websockets.sync.connection import Connection + + +class InterceptingConnection(Connection): + """ + Connection subclass that can intercept outgoing packets. + + By interfacing with this connection, you can simulate network conditions + affecting what the component being tested receives during a test. + + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.socket = InterceptingSocket(self.socket) + + @contextlib.contextmanager + def delay_frames_sent(self, delay): + """ + Add a delay before sending frames. + + Delays cumulate: they're added before every frame or before EOF. + + """ + assert self.socket.delay_sendall is None + self.socket.delay_sendall = delay + try: + yield + finally: + self.socket.delay_sendall = None + + @contextlib.contextmanager + def delay_eof_sent(self, delay): + """ + Add a delay before sending EOF. + + Delays cumulate: they're added before every frame or before EOF. + + """ + assert self.socket.delay_shutdown is None + self.socket.delay_shutdown = delay + try: + yield + finally: + self.socket.delay_shutdown = None + + @contextlib.contextmanager + def drop_frames_sent(self): + """ + Prevent frames from being sent. + + Since TCP is reliable, sending frames or EOF afterwards is unrealistic. + + """ + assert not self.socket.drop_sendall + self.socket.drop_sendall = True + try: + yield + finally: + self.socket.drop_sendall = False + + @contextlib.contextmanager + def drop_eof_sent(self): + """ + Prevent EOF from being sent. + + Since TCP is reliable, sending frames or EOF afterwards is unrealistic. + + """ + assert not self.socket.drop_shutdown + self.socket.drop_shutdown = True + try: + yield + finally: + self.socket.drop_shutdown = False + + +class InterceptingSocket: + """ + Socket wrapper that intercepts calls to sendall and shutdown. + + This is coupled to the implementation, which relies on these two methods. + + """ + + def __init__(self, socket): + self.socket = socket + self.delay_sendall = None + self.delay_shutdown = None + self.drop_sendall = False + self.drop_shutdown = False + + def __getattr__(self, name): + return getattr(self.socket, name) + + def sendall(self, bytes, flags=0): + if self.delay_sendall is not None: + time.sleep(self.delay_sendall) + if not self.drop_sendall: + self.socket.sendall(bytes, flags) + + def shutdown(self, how): + if self.delay_shutdown is not None: + time.sleep(self.delay_shutdown) + if not self.drop_shutdown: + self.socket.shutdown(how) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/server.py new file mode 100644 index 0000000000000..a9a77438ca90d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/server.py @@ -0,0 +1,65 @@ +import contextlib +import ssl +import threading + +from websockets.sync.server import * + +from ..utils import CERTIFICATE + + +SERVER_CONTEXT = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) +SERVER_CONTEXT.load_cert_chain(CERTIFICATE) + +# Work around https://github.com/openssl/openssl/issues/7967 + +# This bug causes connect() to hang in tests for the client. Including this +# workaround acknowledges that the issue could happen outside of the test suite. + +# It shouldn't happen too often, or else OpenSSL 1.1.1 would be unusable. If it +# happens, we can look for a library-level fix, but it won't be easy. + +SERVER_CONTEXT.num_tickets = 0 + + +def crash(ws): + raise RuntimeError + + +def do_nothing(ws): + pass + + +def eval_shell(ws): + for expr in ws: + value = eval(expr) + ws.send(str(value)) + + +class EvalShellMixin: + def assertEval(self, client, expr, value): + client.send(expr) + self.assertEqual(client.recv(), value) + + +@contextlib.contextmanager +def run_server(ws_handler=eval_shell, host="localhost", port=0, **kwargs): + with serve(ws_handler, host, port, **kwargs) as server: + thread = threading.Thread(target=server.serve_forever) + thread.start() + try: + yield server + finally: + server.shutdown() + thread.join() + + +@contextlib.contextmanager +def run_unix_server(path, ws_handler=eval_shell, **kwargs): + with unix_serve(ws_handler, path, **kwargs) as server: + thread = threading.Thread(target=server.serve_forever) + thread.start() + try: + yield server + finally: + server.shutdown() + thread.join() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_client.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_client.py new file mode 100644 index 0000000000000..c900f3b0fe6e0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_client.py @@ -0,0 +1,274 @@ +import socket +import ssl +import threading +import unittest + +from websockets.exceptions import InvalidHandshake +from websockets.extensions.permessage_deflate import PerMessageDeflate +from websockets.sync.client import * + +from ..utils import MS, temp_unix_socket_path +from .client import CLIENT_CONTEXT, run_client, run_unix_client +from .server import SERVER_CONTEXT, do_nothing, run_server, run_unix_server + + +class ClientTests(unittest.TestCase): + def test_connection(self): + """Client connects to server and the handshake succeeds.""" + with run_server() as server: + with run_client(server) as client: + self.assertEqual(client.protocol.state.name, "OPEN") + + def test_connection_fails(self): + """Client connects to server but the handshake fails.""" + + def remove_accept_header(self, request, response): + del response.headers["Sec-WebSocket-Accept"] + + # The connection will be open for the server but failed for the client. + # Use a connection handler that exits immediately to avoid an exception. + with run_server(do_nothing, process_response=remove_accept_header) as server: + with self.assertRaisesRegex( + InvalidHandshake, + "missing Sec-WebSocket-Accept header", + ): + with run_client(server, close_timeout=MS): + self.fail("did not raise") + + def test_tcp_connection_fails(self): + """Client fails to connect to server.""" + with self.assertRaises(OSError): + with run_client("ws://localhost:54321"): # invalid port + self.fail("did not raise") + + def test_existing_socket(self): + """Client connects using a pre-existing socket.""" + with run_server() as server: + with socket.create_connection(server.socket.getsockname()) as sock: + # Use a non-existing domain to ensure we connect to the right socket. + with run_client("ws://invalid/", sock=sock) as client: + self.assertEqual(client.protocol.state.name, "OPEN") + + def test_additional_headers(self): + """Client can set additional headers with additional_headers.""" + with run_server() as server: + with run_client( + server, additional_headers={"Authorization": "Bearer ..."} + ) as client: + self.assertEqual(client.request.headers["Authorization"], "Bearer ...") + + def test_override_user_agent(self): + """Client can override User-Agent header with user_agent_header.""" + with run_server() as server: + with run_client(server, user_agent_header="Smith") as client: + self.assertEqual(client.request.headers["User-Agent"], "Smith") + + def test_remove_user_agent(self): + """Client can remove User-Agent header with user_agent_header.""" + with run_server() as server: + with run_client(server, user_agent_header=None) as client: + self.assertNotIn("User-Agent", client.request.headers) + + def test_compression_is_enabled(self): + """Client enables compression by default.""" + with run_server() as server: + with run_client(server) as client: + self.assertEqual( + [type(ext) for ext in client.protocol.extensions], + [PerMessageDeflate], + ) + + def test_disable_compression(self): + """Client disables compression.""" + with run_server() as server: + with run_client(server, compression=None) as client: + self.assertEqual(client.protocol.extensions, []) + + def test_custom_connection_factory(self): + """Client runs ClientConnection factory provided in create_connection.""" + + def create_connection(*args, **kwargs): + client = ClientConnection(*args, **kwargs) + client.create_connection_ran = True + return client + + with run_server() as server: + with run_client(server, create_connection=create_connection) as client: + self.assertTrue(client.create_connection_ran) + + def test_timeout_during_handshake(self): + """Client times out before receiving handshake response from server.""" + gate = threading.Event() + + def stall_connection(self, request): + gate.wait() + + # The connection will be open for the server but failed for the client. + # Use a connection handler that exits immediately to avoid an exception. + with run_server(do_nothing, process_request=stall_connection) as server: + try: + with self.assertRaisesRegex( + TimeoutError, + "timed out during handshake", + ): + # While it shouldn't take 50ms to open a connection, this + # test becomes flaky in CI when setting a smaller timeout, + # even after increasing WEBSOCKETS_TESTS_TIMEOUT_FACTOR. + with run_client(server, open_timeout=5 * MS): + self.fail("did not raise") + finally: + gate.set() + + def test_connection_closed_during_handshake(self): + """Client reads EOF before receiving handshake response from server.""" + + def close_connection(self, request): + self.close_socket() + + with run_server(process_request=close_connection) as server: + with self.assertRaisesRegex( + ConnectionError, + "connection closed during handshake", + ): + with run_client(server): + self.fail("did not raise") + + +class SecureClientTests(unittest.TestCase): + def test_connection(self): + """Client connects to server securely.""" + with run_server(ssl_context=SERVER_CONTEXT) as server: + with run_client(server, ssl_context=CLIENT_CONTEXT) as client: + self.assertEqual(client.protocol.state.name, "OPEN") + self.assertEqual(client.socket.version()[:3], "TLS") + + def test_set_server_hostname_implicitly(self): + """Client sets server_hostname to the host in the WebSocket URI.""" + with temp_unix_socket_path() as path: + with run_unix_server(path, ssl_context=SERVER_CONTEXT): + with run_unix_client( + path, + ssl_context=CLIENT_CONTEXT, + uri="wss://overridden/", + ) as client: + self.assertEqual(client.socket.server_hostname, "overridden") + + def test_set_server_hostname_explicitly(self): + """Client sets server_hostname to the value provided in argument.""" + with temp_unix_socket_path() as path: + with run_unix_server(path, ssl_context=SERVER_CONTEXT): + with run_unix_client( + path, + ssl_context=CLIENT_CONTEXT, + server_hostname="overridden", + ) as client: + self.assertEqual(client.socket.server_hostname, "overridden") + + def test_reject_invalid_server_certificate(self): + """Client rejects certificate where server certificate isn't trusted.""" + with run_server(ssl_context=SERVER_CONTEXT) as server: + with self.assertRaisesRegex( + ssl.SSLCertVerificationError, + r"certificate verify failed: self[ -]signed certificate", + ): + # The test certificate isn't trusted system-wide. + with run_client(server, secure=True): + self.fail("did not raise") + + def test_reject_invalid_server_hostname(self): + """Client rejects certificate where server hostname doesn't match.""" + with run_server(ssl_context=SERVER_CONTEXT) as server: + with self.assertRaisesRegex( + ssl.SSLCertVerificationError, + r"certificate verify failed: Hostname mismatch", + ): + # This hostname isn't included in the test certificate. + with run_client( + server, ssl_context=CLIENT_CONTEXT, server_hostname="invalid" + ): + self.fail("did not raise") + + +@unittest.skipUnless(hasattr(socket, "AF_UNIX"), "this test requires Unix sockets") +class UnixClientTests(unittest.TestCase): + def test_connection(self): + """Client connects to server over a Unix socket.""" + with temp_unix_socket_path() as path: + with run_unix_server(path): + with run_unix_client(path) as client: + self.assertEqual(client.protocol.state.name, "OPEN") + + def test_set_host_header(self): + """Client sets the Host header to the host in the WebSocket URI.""" + # This is part of the documented behavior of unix_connect(). + with temp_unix_socket_path() as path: + with run_unix_server(path): + with run_unix_client(path, uri="ws://overridden/") as client: + self.assertEqual(client.request.headers["Host"], "overridden") + + +@unittest.skipUnless(hasattr(socket, "AF_UNIX"), "this test requires Unix sockets") +class SecureUnixClientTests(unittest.TestCase): + def test_connection(self): + """Client connects to server securely over a Unix socket.""" + with temp_unix_socket_path() as path: + with run_unix_server(path, ssl_context=SERVER_CONTEXT): + with run_unix_client(path, ssl_context=CLIENT_CONTEXT) as client: + self.assertEqual(client.protocol.state.name, "OPEN") + self.assertEqual(client.socket.version()[:3], "TLS") + + def test_set_server_hostname(self): + """Client sets server_hostname to the host in the WebSocket URI.""" + # This is part of the documented behavior of unix_connect(). + with temp_unix_socket_path() as path: + with run_unix_server(path, ssl_context=SERVER_CONTEXT): + with run_unix_client( + path, + ssl_context=CLIENT_CONTEXT, + uri="wss://overridden/", + ) as client: + self.assertEqual(client.socket.server_hostname, "overridden") + + +class ClientUsageErrorsTests(unittest.TestCase): + def test_ssl_context_without_secure_uri(self): + """Client rejects ssl_context when URI isn't secure.""" + with self.assertRaisesRegex( + TypeError, + "ssl_context argument is incompatible with a ws:// URI", + ): + connect("ws://localhost/", ssl_context=CLIENT_CONTEXT) + + def test_unix_without_path_or_sock(self): + """Unix client requires path when sock isn't provided.""" + with self.assertRaisesRegex( + TypeError, + "missing path argument", + ): + unix_connect() + + def test_unix_with_path_and_sock(self): + """Unix client rejects path when sock is provided.""" + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.addCleanup(sock.close) + with self.assertRaisesRegex( + TypeError, + "path and sock arguments are incompatible", + ): + unix_connect(path="/", sock=sock) + + def test_invalid_subprotocol(self): + """Client rejects single value of subprotocols.""" + with self.assertRaisesRegex( + TypeError, + "subprotocols must be a list", + ): + connect("ws://localhost/", subprotocols="chat") + + def test_unsupported_compression(self): + """Client rejects incorrect value of compression.""" + with self.assertRaisesRegex( + ValueError, + "unsupported compression: False", + ): + connect("ws://localhost/", compression=False) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_connection.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_connection.py new file mode 100644 index 0000000000000..63544d4add7c1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_connection.py @@ -0,0 +1,752 @@ +import contextlib +import logging +import platform +import socket +import sys +import threading +import time +import unittest +import uuid +from unittest.mock import patch + +from websockets.exceptions import ConnectionClosedError, ConnectionClosedOK +from websockets.frames import CloseCode, Frame, Opcode +from websockets.protocol import CLIENT, SERVER, Protocol +from websockets.sync.connection import * + +from ..protocol import RecordingProtocol +from ..utils import MS +from .connection import InterceptingConnection + + +# Connection implements symmetrical behavior between clients and servers. +# All tests run on the client side and the server side to validate this. + + +class ClientConnectionTests(unittest.TestCase): + LOCAL = CLIENT + REMOTE = SERVER + + def setUp(self): + socket_, remote_socket = socket.socketpair() + protocol = Protocol(self.LOCAL) + remote_protocol = RecordingProtocol(self.REMOTE) + self.connection = Connection(socket_, protocol, close_timeout=2 * MS) + self.remote_connection = InterceptingConnection(remote_socket, remote_protocol) + + def tearDown(self): + self.remote_connection.close() + self.connection.close() + + # Test helpers built upon RecordingProtocol and InterceptingConnection. + + def assertFrameSent(self, frame): + """Check that a single frame was sent.""" + time.sleep(MS) # let the remote side process messages + self.assertEqual(self.remote_connection.protocol.get_frames_rcvd(), [frame]) + + def assertNoFrameSent(self): + """Check that no frame was sent.""" + time.sleep(MS) # let the remote side process messages + self.assertEqual(self.remote_connection.protocol.get_frames_rcvd(), []) + + @contextlib.contextmanager + def delay_frames_rcvd(self, delay): + """Delay frames before they're received by the connection.""" + with self.remote_connection.delay_frames_sent(delay): + yield + time.sleep(MS) # let the remote side process messages + + @contextlib.contextmanager + def delay_eof_rcvd(self, delay): + """Delay EOF before it's received by the connection.""" + with self.remote_connection.delay_eof_sent(delay): + yield + time.sleep(MS) # let the remote side process messages + + @contextlib.contextmanager + def drop_frames_rcvd(self): + """Drop frames before they're received by the connection.""" + with self.remote_connection.drop_frames_sent(): + yield + time.sleep(MS) # let the remote side process messages + + @contextlib.contextmanager + def drop_eof_rcvd(self): + """Drop EOF before it's received by the connection.""" + with self.remote_connection.drop_eof_sent(): + yield + time.sleep(MS) # let the remote side process messages + + # Test __enter__ and __exit__. + + def test_enter(self): + """__enter__ returns the connection itself.""" + with self.connection as connection: + self.assertIs(connection, self.connection) + + def test_exit(self): + """__exit__ closes the connection with code 1000.""" + with self.connection: + self.assertNoFrameSent() + self.assertFrameSent(Frame(Opcode.CLOSE, b"\x03\xe8")) + + def test_exit_with_exception(self): + """__exit__ with an exception closes the connection with code 1011.""" + with self.assertRaises(RuntimeError): + with self.connection: + raise RuntimeError + self.assertFrameSent(Frame(Opcode.CLOSE, b"\x03\xf3")) + + # Test __iter__. + + def test_iter_text(self): + """__iter__ yields text messages.""" + iterator = iter(self.connection) + self.remote_connection.send("😀") + self.assertEqual(next(iterator), "😀") + self.remote_connection.send("😀") + self.assertEqual(next(iterator), "😀") + + def test_iter_binary(self): + """__iter__ yields binary messages.""" + iterator = iter(self.connection) + self.remote_connection.send(b"\x01\x02\xfe\xff") + self.assertEqual(next(iterator), b"\x01\x02\xfe\xff") + self.remote_connection.send(b"\x01\x02\xfe\xff") + self.assertEqual(next(iterator), b"\x01\x02\xfe\xff") + + def test_iter_mixed(self): + """__iter__ yields a mix of text and binary messages.""" + iterator = iter(self.connection) + self.remote_connection.send("😀") + self.assertEqual(next(iterator), "😀") + self.remote_connection.send(b"\x01\x02\xfe\xff") + self.assertEqual(next(iterator), b"\x01\x02\xfe\xff") + + def test_iter_connection_closed_ok(self): + """__iter__ terminates after a normal closure.""" + iterator = iter(self.connection) + self.remote_connection.close() + with self.assertRaises(StopIteration): + next(iterator) + + def test_iter_connection_closed_error(self): + """__iter__ raises ConnnectionClosedError after an error.""" + iterator = iter(self.connection) + self.remote_connection.close(code=CloseCode.INTERNAL_ERROR) + with self.assertRaises(ConnectionClosedError): + next(iterator) + + # Test recv. + + def test_recv_text(self): + """recv receives a text message.""" + self.remote_connection.send("😀") + self.assertEqual(self.connection.recv(), "😀") + + def test_recv_binary(self): + """recv receives a binary message.""" + self.remote_connection.send(b"\x01\x02\xfe\xff") + self.assertEqual(self.connection.recv(), b"\x01\x02\xfe\xff") + + def test_recv_fragmented_text(self): + """recv receives a fragmented text message.""" + self.remote_connection.send(["😀", "😀"]) + self.assertEqual(self.connection.recv(), "😀😀") + + def test_recv_fragmented_binary(self): + """recv receives a fragmented binary message.""" + self.remote_connection.send([b"\x01\x02", b"\xfe\xff"]) + self.assertEqual(self.connection.recv(), b"\x01\x02\xfe\xff") + + def test_recv_connection_closed_ok(self): + """recv raises ConnectionClosedOK after a normal closure.""" + self.remote_connection.close() + with self.assertRaises(ConnectionClosedOK): + self.connection.recv() + + def test_recv_connection_closed_error(self): + """recv raises ConnectionClosedError after an error.""" + self.remote_connection.close(code=CloseCode.INTERNAL_ERROR) + with self.assertRaises(ConnectionClosedError): + self.connection.recv() + + def test_recv_during_recv(self): + """recv raises RuntimeError when called concurrently with itself.""" + recv_thread = threading.Thread(target=self.connection.recv) + recv_thread.start() + + with self.assertRaisesRegex( + RuntimeError, + "cannot call recv while another thread " + "is already running recv or recv_streaming", + ): + self.connection.recv() + + self.remote_connection.send("") + recv_thread.join() + + def test_recv_during_recv_streaming(self): + """recv raises RuntimeError when called concurrently with recv_streaming.""" + recv_streaming_thread = threading.Thread( + target=lambda: list(self.connection.recv_streaming()) + ) + recv_streaming_thread.start() + + with self.assertRaisesRegex( + RuntimeError, + "cannot call recv while another thread " + "is already running recv or recv_streaming", + ): + self.connection.recv() + + self.remote_connection.send("") + recv_streaming_thread.join() + + # Test recv_streaming. + + def test_recv_streaming_text(self): + """recv_streaming receives a text message.""" + self.remote_connection.send("😀") + self.assertEqual( + list(self.connection.recv_streaming()), + ["😀"], + ) + + def test_recv_streaming_binary(self): + """recv_streaming receives a binary message.""" + self.remote_connection.send(b"\x01\x02\xfe\xff") + self.assertEqual( + list(self.connection.recv_streaming()), + [b"\x01\x02\xfe\xff"], + ) + + def test_recv_streaming_fragmented_text(self): + """recv_streaming receives a fragmented text message.""" + self.remote_connection.send(["😀", "😀"]) + # websockets sends an trailing empty fragment. That's an implementation detail. + self.assertEqual( + list(self.connection.recv_streaming()), + ["😀", "😀", ""], + ) + + def test_recv_streaming_fragmented_binary(self): + """recv_streaming receives a fragmented binary message.""" + self.remote_connection.send([b"\x01\x02", b"\xfe\xff"]) + # websockets sends an trailing empty fragment. That's an implementation detail. + self.assertEqual( + list(self.connection.recv_streaming()), + [b"\x01\x02", b"\xfe\xff", b""], + ) + + def test_recv_streaming_connection_closed_ok(self): + """recv_streaming raises ConnectionClosedOK after a normal closure.""" + self.remote_connection.close() + with self.assertRaises(ConnectionClosedOK): + list(self.connection.recv_streaming()) + + def test_recv_streaming_connection_closed_error(self): + """recv_streaming raises ConnectionClosedError after an error.""" + self.remote_connection.close(code=CloseCode.INTERNAL_ERROR) + with self.assertRaises(ConnectionClosedError): + list(self.connection.recv_streaming()) + + def test_recv_streaming_during_recv(self): + """recv_streaming raises RuntimeError when called concurrently with recv.""" + recv_thread = threading.Thread(target=self.connection.recv) + recv_thread.start() + + with self.assertRaisesRegex( + RuntimeError, + "cannot call recv_streaming while another thread " + "is already running recv or recv_streaming", + ): + list(self.connection.recv_streaming()) + + self.remote_connection.send("") + recv_thread.join() + + def test_recv_streaming_during_recv_streaming(self): + """recv_streaming raises RuntimeError when called concurrently with itself.""" + recv_streaming_thread = threading.Thread( + target=lambda: list(self.connection.recv_streaming()) + ) + recv_streaming_thread.start() + + with self.assertRaisesRegex( + RuntimeError, + r"cannot call recv_streaming while another thread " + r"is already running recv or recv_streaming", + ): + list(self.connection.recv_streaming()) + + self.remote_connection.send("") + recv_streaming_thread.join() + + # Test send. + + def test_send_text(self): + """send sends a text message.""" + self.connection.send("😀") + self.assertEqual(self.remote_connection.recv(), "😀") + + def test_send_binary(self): + """send sends a binary message.""" + self.connection.send(b"\x01\x02\xfe\xff") + self.assertEqual(self.remote_connection.recv(), b"\x01\x02\xfe\xff") + + def test_send_fragmented_text(self): + """send sends a fragmented text message.""" + self.connection.send(["😀", "😀"]) + # websockets sends an trailing empty fragment. That's an implementation detail. + self.assertEqual( + list(self.remote_connection.recv_streaming()), + ["😀", "😀", ""], + ) + + def test_send_fragmented_binary(self): + """send sends a fragmented binary message.""" + self.connection.send([b"\x01\x02", b"\xfe\xff"]) + # websockets sends an trailing empty fragment. That's an implementation detail. + self.assertEqual( + list(self.remote_connection.recv_streaming()), + [b"\x01\x02", b"\xfe\xff", b""], + ) + + def test_send_connection_closed_ok(self): + """send raises ConnectionClosedOK after a normal closure.""" + self.remote_connection.close() + with self.assertRaises(ConnectionClosedOK): + self.connection.send("😀") + + def test_send_connection_closed_error(self): + """send raises ConnectionClosedError after an error.""" + self.remote_connection.close(code=CloseCode.INTERNAL_ERROR) + with self.assertRaises(ConnectionClosedError): + self.connection.send("😀") + + def test_send_during_send(self): + """send raises RuntimeError when called concurrently with itself.""" + recv_thread = threading.Thread(target=self.remote_connection.recv) + recv_thread.start() + + send_gate = threading.Event() + exit_gate = threading.Event() + + def fragments(): + yield "😀" + send_gate.set() + exit_gate.wait() + yield "😀" + + send_thread = threading.Thread( + target=self.connection.send, + args=(fragments(),), + ) + send_thread.start() + + send_gate.wait() + # The check happens in four code paths, depending on the argument. + for message in [ + "😀", + b"\x01\x02\xfe\xff", + ["😀", "😀"], + [b"\x01\x02", b"\xfe\xff"], + ]: + with self.subTest(message=message): + with self.assertRaisesRegex( + RuntimeError, + "cannot call send while another thread is already running send", + ): + self.connection.send(message) + + exit_gate.set() + send_thread.join() + recv_thread.join() + + def test_send_empty_iterable(self): + """send does nothing when called with an empty iterable.""" + self.connection.send([]) + self.connection.close() + self.assertEqual(list(iter(self.remote_connection)), []) + + def test_send_mixed_iterable(self): + """send raises TypeError when called with an iterable of inconsistent types.""" + with self.assertRaises(TypeError): + self.connection.send(["😀", b"\xfe\xff"]) + + def test_send_unsupported_iterable(self): + """send raises TypeError when called with an iterable of unsupported type.""" + with self.assertRaises(TypeError): + self.connection.send([None]) + + def test_send_dict(self): + """send raises TypeError when called with a dict.""" + with self.assertRaises(TypeError): + self.connection.send({"type": "object"}) + + def test_send_unsupported_type(self): + """send raises TypeError when called with an unsupported type.""" + with self.assertRaises(TypeError): + self.connection.send(None) + + # Test close. + + def test_close(self): + """close sends a close frame.""" + self.connection.close() + self.assertFrameSent(Frame(Opcode.CLOSE, b"\x03\xe8")) + + def test_close_explicit_code_reason(self): + """close sends a close frame with a given code and reason.""" + self.connection.close(CloseCode.GOING_AWAY, "bye!") + self.assertFrameSent(Frame(Opcode.CLOSE, b"\x03\xe9bye!")) + + def test_close_waits_for_close_frame(self): + """close waits for a close frame (then EOF) before returning.""" + with self.delay_frames_rcvd(MS): + self.connection.close() + + with self.assertRaises(ConnectionClosedOK) as raised: + self.connection.recv() + + exc = raised.exception + self.assertEqual(str(exc), "sent 1000 (OK); then received 1000 (OK)") + self.assertIsNone(exc.__cause__) + + def test_close_waits_for_connection_closed(self): + """close waits for EOF before returning.""" + if self.LOCAL is SERVER: + self.skipTest("only relevant on the client-side") + + with self.delay_eof_rcvd(MS): + self.connection.close() + + with self.assertRaises(ConnectionClosedOK) as raised: + self.connection.recv() + + exc = raised.exception + self.assertEqual(str(exc), "sent 1000 (OK); then received 1000 (OK)") + self.assertIsNone(exc.__cause__) + + def test_close_timeout_waiting_for_close_frame(self): + """close times out if no close frame is received.""" + with self.drop_eof_rcvd(), self.drop_frames_rcvd(): + self.connection.close() + + with self.assertRaises(ConnectionClosedError) as raised: + self.connection.recv() + + exc = raised.exception + self.assertEqual(str(exc), "sent 1000 (OK); no close frame received") + self.assertIsInstance(exc.__cause__, TimeoutError) + + def test_close_timeout_waiting_for_connection_closed(self): + """close times out if EOF isn't received.""" + if self.LOCAL is SERVER: + self.skipTest("only relevant on the client-side") + + with self.drop_eof_rcvd(): + self.connection.close() + + with self.assertRaises(ConnectionClosedOK) as raised: + self.connection.recv() + + exc = raised.exception + self.assertEqual(str(exc), "sent 1000 (OK); then received 1000 (OK)") + # Remove socket.timeout when dropping Python < 3.10. + self.assertIsInstance(exc.__cause__, (socket.timeout, TimeoutError)) + + def test_close_waits_for_recv(self): + self.remote_connection.send("😀") + + close_thread = threading.Thread(target=self.connection.close) + close_thread.start() + + # Let close() initiate the closing handshake and send a close frame. + time.sleep(MS) + self.assertTrue(close_thread.is_alive()) + + # Connection isn't closed yet. + self.connection.recv() + + # Let close() receive a close frame and finish the closing handshake. + time.sleep(MS) + self.assertFalse(close_thread.is_alive()) + + # Connection is closed now. + with self.assertRaises(ConnectionClosedOK) as raised: + self.connection.recv() + + exc = raised.exception + self.assertEqual(str(exc), "sent 1000 (OK); then received 1000 (OK)") + self.assertIsNone(exc.__cause__) + + def test_close_timeout_waiting_for_recv(self): + self.remote_connection.send("😀") + + close_thread = threading.Thread(target=self.connection.close) + close_thread.start() + + # Let close() time out during the closing handshake. + time.sleep(3 * MS) + self.assertFalse(close_thread.is_alive()) + + # Connection is closed now. + with self.assertRaises(ConnectionClosedError) as raised: + self.connection.recv() + + exc = raised.exception + self.assertEqual(str(exc), "sent 1000 (OK); no close frame received") + self.assertIsInstance(exc.__cause__, TimeoutError) + + def test_close_idempotency(self): + """close does nothing if the connection is already closed.""" + self.connection.close() + self.assertFrameSent(Frame(Opcode.CLOSE, b"\x03\xe8")) + + self.connection.close() + self.assertNoFrameSent() + + @unittest.skipIf( + platform.python_implementation() == "PyPy", + "this test fails randomly due to a bug in PyPy", # see #1314 for details + ) + def test_close_idempotency_race_condition(self): + """close waits if the connection is already closing.""" + + self.connection.close_timeout = 5 * MS + + def closer(): + with self.delay_frames_rcvd(3 * MS): + self.connection.close() + + close_thread = threading.Thread(target=closer) + close_thread.start() + + # Let closer() initiate the closing handshake and send a close frame. + time.sleep(MS) + self.assertFrameSent(Frame(Opcode.CLOSE, b"\x03\xe8")) + + # Connection isn't closed yet. + with self.assertRaises(TimeoutError): + self.connection.recv(timeout=0) + + self.connection.close() + self.assertNoFrameSent() + + # Connection is closed now. + with self.assertRaises(ConnectionClosedOK): + self.connection.recv(timeout=0) + + close_thread.join() + + def test_close_during_send(self): + """close fails the connection when called concurrently with send.""" + close_gate = threading.Event() + exit_gate = threading.Event() + + def closer(): + close_gate.wait() + self.connection.close() + exit_gate.set() + + def fragments(): + yield "😀" + close_gate.set() + exit_gate.wait() + yield "😀" + + close_thread = threading.Thread(target=closer) + close_thread.start() + + with self.assertRaises(ConnectionClosedError) as raised: + self.connection.send(fragments()) + + exc = raised.exception + self.assertEqual( + str(exc), + "sent 1011 (internal error) close during fragmented message; " + "no close frame received", + ) + self.assertIsNone(exc.__cause__) + + close_thread.join() + + # Test ping. + + @patch("random.getrandbits") + def test_ping(self, getrandbits): + """ping sends a ping frame with a random payload.""" + getrandbits.return_value = 1918987876 + self.connection.ping() + getrandbits.assert_called_once_with(32) + self.assertFrameSent(Frame(Opcode.PING, b"rand")) + + def test_ping_explicit_text(self): + """ping sends a ping frame with a payload provided as text.""" + self.connection.ping("ping") + self.assertFrameSent(Frame(Opcode.PING, b"ping")) + + def test_ping_explicit_binary(self): + """ping sends a ping frame with a payload provided as binary.""" + self.connection.ping(b"ping") + self.assertFrameSent(Frame(Opcode.PING, b"ping")) + + def test_ping_duplicate_payload(self): + """ping rejects the same payload until receiving the pong.""" + with self.remote_connection.protocol_mutex: # block response to ping + pong_waiter = self.connection.ping("idem") + with self.assertRaisesRegex( + RuntimeError, + "already waiting for a pong with the same data", + ): + self.connection.ping("idem") + self.assertTrue(pong_waiter.wait(MS)) + self.connection.ping("idem") # doesn't raise an exception + + def test_acknowledge_ping(self): + """ping is acknowledged by a pong with the same payload.""" + with self.drop_frames_rcvd(): + pong_waiter = self.connection.ping("this") + self.assertFalse(pong_waiter.wait(MS)) + self.remote_connection.pong("this") + self.assertTrue(pong_waiter.wait(MS)) + + def test_acknowledge_ping_non_matching_pong(self): + """ping isn't acknowledged by a pong with a different payload.""" + with self.drop_frames_rcvd(): + pong_waiter = self.connection.ping("this") + self.remote_connection.pong("that") + self.assertFalse(pong_waiter.wait(MS)) + + def test_acknowledge_previous_ping(self): + """ping is acknowledged by a pong with the same payload as a later ping.""" + with self.drop_frames_rcvd(): + pong_waiter = self.connection.ping("this") + self.connection.ping("that") + self.remote_connection.pong("that") + self.assertTrue(pong_waiter.wait(MS)) + + # Test pong. + + def test_pong(self): + """pong sends a pong frame.""" + self.connection.pong() + self.assertFrameSent(Frame(Opcode.PONG, b"")) + + def test_pong_explicit_text(self): + """pong sends a pong frame with a payload provided as text.""" + self.connection.pong("pong") + self.assertFrameSent(Frame(Opcode.PONG, b"pong")) + + def test_pong_explicit_binary(self): + """pong sends a pong frame with a payload provided as binary.""" + self.connection.pong(b"pong") + self.assertFrameSent(Frame(Opcode.PONG, b"pong")) + + # Test attributes. + + def test_id(self): + """Connection has an id attribute.""" + self.assertIsInstance(self.connection.id, uuid.UUID) + + def test_logger(self): + """Connection has a logger attribute.""" + self.assertIsInstance(self.connection.logger, logging.LoggerAdapter) + + def test_local_address(self): + """Connection has a local_address attribute.""" + self.assertIsNotNone(self.connection.local_address) + + def test_remote_address(self): + """Connection has a remote_address attribute.""" + self.assertIsNotNone(self.connection.remote_address) + + def test_request(self): + """Connection has a request attribute.""" + self.assertIsNone(self.connection.request) + + def test_response(self): + """Connection has a response attribute.""" + self.assertIsNone(self.connection.response) + + def test_subprotocol(self): + """Connection has a subprotocol attribute.""" + self.assertIsNone(self.connection.subprotocol) + + # Test reporting of network errors. + + @unittest.skipUnless(sys.platform == "darwin", "works only on BSD") + def test_reading_in_recv_events_fails(self): + """Error when reading incoming frames is correctly reported.""" + # Inject a fault by closing the socket. This works only on BSD. + # I cannot find a way to achieve the same effect on Linux. + self.connection.socket.close() + # The connection closed exception reports the injected fault. + with self.assertRaises(ConnectionClosedError) as raised: + self.connection.recv() + self.assertIsInstance(raised.exception.__cause__, IOError) + + def test_writing_in_recv_events_fails(self): + """Error when responding to incoming frames is correctly reported.""" + # Inject a fault by shutting down the socket for writing — but not by + # closing it because that would terminate the connection. + self.connection.socket.shutdown(socket.SHUT_WR) + # Receive a ping. Responding with a pong will fail. + self.remote_connection.ping() + # The connection closed exception reports the injected fault. + with self.assertRaises(ConnectionClosedError) as raised: + self.connection.recv() + self.assertIsInstance(raised.exception.__cause__, BrokenPipeError) + + def test_writing_in_send_context_fails(self): + """Error when sending outgoing frame is correctly reported.""" + # Inject a fault by shutting down the socket for writing — but not by + # closing it because that would terminate the connection. + self.connection.socket.shutdown(socket.SHUT_WR) + # Sending a pong will fail. + # The connection closed exception reports the injected fault. + with self.assertRaises(ConnectionClosedError) as raised: + self.connection.pong() + self.assertIsInstance(raised.exception.__cause__, BrokenPipeError) + + # Test safety nets — catching all exceptions in case of bugs. + + @patch("websockets.protocol.Protocol.events_received") + def test_unexpected_failure_in_recv_events(self, events_received): + """Unexpected internal error in recv_events() is correctly reported.""" + # Inject a fault in a random call in recv_events(). + # This test is tightly coupled to the implementation. + events_received.side_effect = AssertionError + # Receive a message to trigger the fault. + self.remote_connection.send("😀") + + with self.assertRaises(ConnectionClosedError) as raised: + self.connection.recv() + + exc = raised.exception + self.assertEqual(str(exc), "no close frame received or sent") + self.assertIsInstance(exc.__cause__, AssertionError) + + @patch("websockets.protocol.Protocol.send_text") + def test_unexpected_failure_in_send_context(self, send_text): + """Unexpected internal error in send_context() is correctly reported.""" + # Inject a fault in a random call in send_context(). + # This test is tightly coupled to the implementation. + send_text.side_effect = AssertionError + + # Send a message to trigger the fault. + # The connection closed exception reports the injected fault. + with self.assertRaises(ConnectionClosedError) as raised: + self.connection.send("😀") + + exc = raised.exception + self.assertEqual(str(exc), "no close frame received or sent") + self.assertIsInstance(exc.__cause__, AssertionError) + + +class ServerConnectionTests(ClientConnectionTests): + LOCAL = SERVER + REMOTE = CLIENT diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_messages.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_messages.py new file mode 100644 index 0000000000000..825eb879740c0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_messages.py @@ -0,0 +1,479 @@ +import time + +from websockets.frames import OP_BINARY, OP_CONT, OP_PING, OP_PONG, OP_TEXT, Frame +from websockets.sync.messages import * + +from ..utils import MS +from .utils import ThreadTestCase + + +class AssemblerTests(ThreadTestCase): + """ + Tests in this class interact a lot with hidden synchronization mechanisms: + + - get() / get_iter() and put() must run in separate threads when a final + frame is set because put() waits for get() / get_iter() to fetch the + message before returning. + + - run_in_thread() lets its target run before yielding back control on entry, + which guarantees the intended execution order of test cases. + + - run_in_thread() waits for its target to finish running before yielding + back control on exit, which allows making assertions immediately. + + - When the main thread performs actions that let another thread progress, it + must wait before making assertions, to avoid depending on scheduling. + + """ + + def setUp(self): + self.assembler = Assembler() + + def tearDown(self): + """ + Check that the assembler goes back to its default state after each test. + + This removes the need for testing various sequences. + + """ + self.assertFalse(self.assembler.mutex.locked()) + self.assertFalse(self.assembler.get_in_progress) + self.assertFalse(self.assembler.put_in_progress) + if not self.assembler.closed: + self.assertFalse(self.assembler.message_complete.is_set()) + self.assertFalse(self.assembler.message_fetched.is_set()) + self.assertIsNone(self.assembler.decoder) + self.assertEqual(self.assembler.chunks, []) + self.assertIsNone(self.assembler.chunks_queue) + + # Test get + + def test_get_text_message_already_received(self): + """get returns a text message that is already received.""" + + def putter(): + self.assembler.put(Frame(OP_TEXT, b"caf\xc3\xa9")) + + with self.run_in_thread(putter): + message = self.assembler.get() + + self.assertEqual(message, "café") + + def test_get_binary_message_already_received(self): + """get returns a binary message that is already received.""" + + def putter(): + self.assembler.put(Frame(OP_BINARY, b"tea")) + + with self.run_in_thread(putter): + message = self.assembler.get() + + self.assertEqual(message, b"tea") + + def test_get_text_message_not_received_yet(self): + """get returns a text message when it is received.""" + message = None + + def getter(): + nonlocal message + message = self.assembler.get() + + with self.run_in_thread(getter): + self.assembler.put(Frame(OP_TEXT, b"caf\xc3\xa9")) + + self.assertEqual(message, "café") + + def test_get_binary_message_not_received_yet(self): + """get returns a binary message when it is received.""" + message = None + + def getter(): + nonlocal message + message = self.assembler.get() + + with self.run_in_thread(getter): + self.assembler.put(Frame(OP_BINARY, b"tea")) + + self.assertEqual(message, b"tea") + + def test_get_fragmented_text_message_already_received(self): + """get reassembles a fragmented a text message that is already received.""" + + def putter(): + self.assembler.put(Frame(OP_TEXT, b"ca", fin=False)) + self.assembler.put(Frame(OP_CONT, b"f\xc3", fin=False)) + self.assembler.put(Frame(OP_CONT, b"\xa9")) + + with self.run_in_thread(putter): + message = self.assembler.get() + + self.assertEqual(message, "café") + + def test_get_fragmented_binary_message_already_received(self): + """get reassembles a fragmented binary message that is already received.""" + + def putter(): + self.assembler.put(Frame(OP_BINARY, b"t", fin=False)) + self.assembler.put(Frame(OP_CONT, b"e", fin=False)) + self.assembler.put(Frame(OP_CONT, b"a")) + + with self.run_in_thread(putter): + message = self.assembler.get() + + self.assertEqual(message, b"tea") + + def test_get_fragmented_text_message_being_received(self): + """get reassembles a fragmented text message that is partially received.""" + message = None + + def getter(): + nonlocal message + message = self.assembler.get() + + self.assembler.put(Frame(OP_TEXT, b"ca", fin=False)) + with self.run_in_thread(getter): + self.assembler.put(Frame(OP_CONT, b"f\xc3", fin=False)) + self.assembler.put(Frame(OP_CONT, b"\xa9")) + + self.assertEqual(message, "café") + + def test_get_fragmented_binary_message_being_received(self): + """get reassembles a fragmented binary message that is partially received.""" + message = None + + def getter(): + nonlocal message + message = self.assembler.get() + + self.assembler.put(Frame(OP_BINARY, b"t", fin=False)) + with self.run_in_thread(getter): + self.assembler.put(Frame(OP_CONT, b"e", fin=False)) + self.assembler.put(Frame(OP_CONT, b"a")) + + self.assertEqual(message, b"tea") + + def test_get_fragmented_text_message_not_received_yet(self): + """get reassembles a fragmented text message when it is received.""" + message = None + + def getter(): + nonlocal message + message = self.assembler.get() + + with self.run_in_thread(getter): + self.assembler.put(Frame(OP_TEXT, b"ca", fin=False)) + self.assembler.put(Frame(OP_CONT, b"f\xc3", fin=False)) + self.assembler.put(Frame(OP_CONT, b"\xa9")) + + self.assertEqual(message, "café") + + def test_get_fragmented_binary_message_not_received_yet(self): + """get reassembles a fragmented binary message when it is received.""" + message = None + + def getter(): + nonlocal message + message = self.assembler.get() + + with self.run_in_thread(getter): + self.assembler.put(Frame(OP_BINARY, b"t", fin=False)) + self.assembler.put(Frame(OP_CONT, b"e", fin=False)) + self.assembler.put(Frame(OP_CONT, b"a")) + + self.assertEqual(message, b"tea") + + # Test get_iter + + def test_get_iter_text_message_already_received(self): + """get_iter yields a text message that is already received.""" + + def putter(): + self.assembler.put(Frame(OP_TEXT, b"caf\xc3\xa9")) + + with self.run_in_thread(putter): + fragments = list(self.assembler.get_iter()) + + self.assertEqual(fragments, ["café"]) + + def test_get_iter_binary_message_already_received(self): + """get_iter yields a binary message that is already received.""" + + def putter(): + self.assembler.put(Frame(OP_BINARY, b"tea")) + + with self.run_in_thread(putter): + fragments = list(self.assembler.get_iter()) + + self.assertEqual(fragments, [b"tea"]) + + def test_get_iter_text_message_not_received_yet(self): + """get_iter yields a text message when it is received.""" + fragments = [] + + def getter(): + for fragment in self.assembler.get_iter(): + fragments.append(fragment) + + with self.run_in_thread(getter): + self.assembler.put(Frame(OP_TEXT, b"caf\xc3\xa9")) + + self.assertEqual(fragments, ["café"]) + + def test_get_iter_binary_message_not_received_yet(self): + """get_iter yields a binary message when it is received.""" + fragments = [] + + def getter(): + for fragment in self.assembler.get_iter(): + fragments.append(fragment) + + with self.run_in_thread(getter): + self.assembler.put(Frame(OP_BINARY, b"tea")) + + self.assertEqual(fragments, [b"tea"]) + + def test_get_iter_fragmented_text_message_already_received(self): + """get_iter yields a fragmented text message that is already received.""" + + def putter(): + self.assembler.put(Frame(OP_TEXT, b"ca", fin=False)) + self.assembler.put(Frame(OP_CONT, b"f\xc3", fin=False)) + self.assembler.put(Frame(OP_CONT, b"\xa9")) + + with self.run_in_thread(putter): + fragments = list(self.assembler.get_iter()) + + self.assertEqual(fragments, ["ca", "f", "é"]) + + def test_get_iter_fragmented_binary_message_already_received(self): + """get_iter yields a fragmented binary message that is already received.""" + + def putter(): + self.assembler.put(Frame(OP_BINARY, b"t", fin=False)) + self.assembler.put(Frame(OP_CONT, b"e", fin=False)) + self.assembler.put(Frame(OP_CONT, b"a")) + + with self.run_in_thread(putter): + fragments = list(self.assembler.get_iter()) + + self.assertEqual(fragments, [b"t", b"e", b"a"]) + + def test_get_iter_fragmented_text_message_being_received(self): + """get_iter yields a fragmented text message that is partially received.""" + fragments = [] + + def getter(): + for fragment in self.assembler.get_iter(): + fragments.append(fragment) + + self.assembler.put(Frame(OP_TEXT, b"ca", fin=False)) + with self.run_in_thread(getter): + self.assertEqual(fragments, ["ca"]) + self.assembler.put(Frame(OP_CONT, b"f\xc3", fin=False)) + time.sleep(MS) + self.assertEqual(fragments, ["ca", "f"]) + self.assembler.put(Frame(OP_CONT, b"\xa9")) + + self.assertEqual(fragments, ["ca", "f", "é"]) + + def test_get_iter_fragmented_binary_message_being_received(self): + """get_iter yields a fragmented binary message that is partially received.""" + fragments = [] + + def getter(): + for fragment in self.assembler.get_iter(): + fragments.append(fragment) + + self.assembler.put(Frame(OP_BINARY, b"t", fin=False)) + with self.run_in_thread(getter): + self.assertEqual(fragments, [b"t"]) + self.assembler.put(Frame(OP_CONT, b"e", fin=False)) + time.sleep(MS) + self.assertEqual(fragments, [b"t", b"e"]) + self.assembler.put(Frame(OP_CONT, b"a")) + + self.assertEqual(fragments, [b"t", b"e", b"a"]) + + def test_get_iter_fragmented_text_message_not_received_yet(self): + """get_iter yields a fragmented text message when it is received.""" + fragments = [] + + def getter(): + for fragment in self.assembler.get_iter(): + fragments.append(fragment) + + with self.run_in_thread(getter): + self.assembler.put(Frame(OP_TEXT, b"ca", fin=False)) + time.sleep(MS) + self.assertEqual(fragments, ["ca"]) + self.assembler.put(Frame(OP_CONT, b"f\xc3", fin=False)) + time.sleep(MS) + self.assertEqual(fragments, ["ca", "f"]) + self.assembler.put(Frame(OP_CONT, b"\xa9")) + + self.assertEqual(fragments, ["ca", "f", "é"]) + + def test_get_iter_fragmented_binary_message_not_received_yet(self): + """get_iter yields a fragmented binary message when it is received.""" + fragments = [] + + def getter(): + for fragment in self.assembler.get_iter(): + fragments.append(fragment) + + with self.run_in_thread(getter): + self.assembler.put(Frame(OP_BINARY, b"t", fin=False)) + time.sleep(MS) + self.assertEqual(fragments, [b"t"]) + self.assembler.put(Frame(OP_CONT, b"e", fin=False)) + time.sleep(MS) + self.assertEqual(fragments, [b"t", b"e"]) + self.assembler.put(Frame(OP_CONT, b"a")) + + self.assertEqual(fragments, [b"t", b"e", b"a"]) + + # Test timeouts + + def test_get_with_timeout_completes(self): + """get returns a message when it is received before the timeout.""" + + def putter(): + self.assembler.put(Frame(OP_TEXT, b"caf\xc3\xa9")) + + with self.run_in_thread(putter): + message = self.assembler.get(MS) + + self.assertEqual(message, "café") + + def test_get_with_timeout_times_out(self): + """get raises TimeoutError when no message is received before the timeout.""" + with self.assertRaises(TimeoutError): + self.assembler.get(MS) + + # Test control frames + + def test_control_frame_before_message_is_ignored(self): + """get ignores control frames between messages.""" + + def putter(): + self.assembler.put(Frame(OP_PING, b"")) + self.assembler.put(Frame(OP_TEXT, b"caf\xc3\xa9")) + + with self.run_in_thread(putter): + message = self.assembler.get() + + self.assertEqual(message, "café") + + def test_control_frame_in_fragmented_message_is_ignored(self): + """get ignores control frames within fragmented messages.""" + + def putter(): + self.assembler.put(Frame(OP_BINARY, b"t", fin=False)) + self.assembler.put(Frame(OP_PING, b"")) + self.assembler.put(Frame(OP_CONT, b"e", fin=False)) + self.assembler.put(Frame(OP_PONG, b"")) + self.assembler.put(Frame(OP_CONT, b"a")) + + with self.run_in_thread(putter): + message = self.assembler.get() + + self.assertEqual(message, b"tea") + + # Test concurrency + + def test_get_fails_when_get_is_running(self): + """get cannot be called concurrently with itself.""" + with self.run_in_thread(self.assembler.get): + with self.assertRaises(RuntimeError): + self.assembler.get() + self.assembler.put(Frame(OP_TEXT, b"")) # unlock other thread + + def test_get_fails_when_get_iter_is_running(self): + """get cannot be called concurrently with get_iter.""" + with self.run_in_thread(lambda: list(self.assembler.get_iter())): + with self.assertRaises(RuntimeError): + self.assembler.get() + self.assembler.put(Frame(OP_TEXT, b"")) # unlock other thread + + def test_get_iter_fails_when_get_is_running(self): + """get_iter cannot be called concurrently with get.""" + with self.run_in_thread(self.assembler.get): + with self.assertRaises(RuntimeError): + list(self.assembler.get_iter()) + self.assembler.put(Frame(OP_TEXT, b"")) # unlock other thread + + def test_get_iter_fails_when_get_iter_is_running(self): + """get_iter cannot be called concurrently with itself.""" + with self.run_in_thread(lambda: list(self.assembler.get_iter())): + with self.assertRaises(RuntimeError): + list(self.assembler.get_iter()) + self.assembler.put(Frame(OP_TEXT, b"")) # unlock other thread + + def test_put_fails_when_put_is_running(self): + """put cannot be called concurrently with itself.""" + + def putter(): + self.assembler.put(Frame(OP_TEXT, b"caf\xc3\xa9")) + + with self.run_in_thread(putter): + with self.assertRaises(RuntimeError): + self.assembler.put(Frame(OP_BINARY, b"tea")) + self.assembler.get() # unblock other thread + + # Test termination + + def test_get_fails_when_interrupted_by_close(self): + """get raises EOFError when close is called.""" + + def closer(): + time.sleep(2 * MS) + self.assembler.close() + + with self.run_in_thread(closer): + with self.assertRaises(EOFError): + self.assembler.get() + + def test_get_iter_fails_when_interrupted_by_close(self): + """get_iter raises EOFError when close is called.""" + + def closer(): + time.sleep(2 * MS) + self.assembler.close() + + with self.run_in_thread(closer): + with self.assertRaises(EOFError): + list(self.assembler.get_iter()) + + def test_put_fails_when_interrupted_by_close(self): + """put raises EOFError when close is called.""" + + def closer(): + time.sleep(2 * MS) + self.assembler.close() + + with self.run_in_thread(closer): + with self.assertRaises(EOFError): + self.assembler.put(Frame(OP_TEXT, b"caf\xc3\xa9")) + + def test_get_fails_after_close(self): + """get raises EOFError after close is called.""" + self.assembler.close() + with self.assertRaises(EOFError): + self.assembler.get() + + def test_get_iter_fails_after_close(self): + """get_iter raises EOFError after close is called.""" + self.assembler.close() + with self.assertRaises(EOFError): + list(self.assembler.get_iter()) + + def test_put_fails_after_close(self): + """put raises EOFError after close is called.""" + self.assembler.close() + with self.assertRaises(EOFError): + self.assembler.put(Frame(OP_TEXT, b"caf\xc3\xa9")) + + def test_close_is_idempotent(self): + """close can be called multiple times safely.""" + self.assembler.close() + self.assembler.close() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_server.py new file mode 100644 index 0000000000000..f9db842468316 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_server.py @@ -0,0 +1,388 @@ +import dataclasses +import http +import logging +import socket +import threading +import unittest + +from websockets.exceptions import ( + ConnectionClosedError, + ConnectionClosedOK, + InvalidStatus, + NegotiationError, +) +from websockets.http11 import Request, Response +from websockets.sync.server import * + +from ..utils import MS, temp_unix_socket_path +from .client import CLIENT_CONTEXT, run_client, run_unix_client +from .server import ( + SERVER_CONTEXT, + EvalShellMixin, + crash, + do_nothing, + eval_shell, + run_server, + run_unix_server, +) + + +class ServerTests(EvalShellMixin, unittest.TestCase): + def test_connection(self): + """Server receives connection from client and the handshake succeeds.""" + with run_server() as server: + with run_client(server) as client: + self.assertEval(client, "ws.protocol.state.name", "OPEN") + + def test_connection_fails(self): + """Server receives connection from client but the handshake fails.""" + + def remove_key_header(self, request): + del request.headers["Sec-WebSocket-Key"] + + with run_server(process_request=remove_key_header) as server: + with self.assertRaisesRegex( + InvalidStatus, + "server rejected WebSocket connection: HTTP 400", + ): + with run_client(server): + self.fail("did not raise") + + def test_connection_handler_returns(self): + """Connection handler returns.""" + with run_server(do_nothing) as server: + with run_client(server) as client: + with self.assertRaisesRegex( + ConnectionClosedOK, + r"received 1000 \(OK\); then sent 1000 \(OK\)", + ): + client.recv() + + def test_connection_handler_raises_exception(self): + """Connection handler raises an exception.""" + with run_server(crash) as server: + with run_client(server) as client: + with self.assertRaisesRegex( + ConnectionClosedError, + r"received 1011 \(internal error\); " + r"then sent 1011 \(internal error\)", + ): + client.recv() + + def test_existing_socket(self): + """Server receives connection using a pre-existing socket.""" + with socket.create_server(("localhost", 0)) as sock: + with run_server(sock=sock): + # Build WebSocket URI to ensure we connect to the right socket. + with run_client("ws://{}:{}/".format(*sock.getsockname())) as client: + self.assertEval(client, "ws.protocol.state.name", "OPEN") + + def test_select_subprotocol(self): + """Server selects a subprotocol with the select_subprotocol callable.""" + + def select_subprotocol(ws, subprotocols): + ws.select_subprotocol_ran = True + assert "chat" in subprotocols + return "chat" + + with run_server( + subprotocols=["chat"], + select_subprotocol=select_subprotocol, + ) as server: + with run_client(server, subprotocols=["chat"]) as client: + self.assertEval(client, "ws.select_subprotocol_ran", "True") + self.assertEval(client, "ws.subprotocol", "chat") + + def test_select_subprotocol_rejects_handshake(self): + """Server rejects handshake if select_subprotocol raises NegotiationError.""" + + def select_subprotocol(ws, subprotocols): + raise NegotiationError + + with run_server(select_subprotocol=select_subprotocol) as server: + with self.assertRaisesRegex( + InvalidStatus, + "server rejected WebSocket connection: HTTP 400", + ): + with run_client(server): + self.fail("did not raise") + + def test_select_subprotocol_raises_exception(self): + """Server returns an error if select_subprotocol raises an exception.""" + + def select_subprotocol(ws, subprotocols): + raise RuntimeError + + with run_server(select_subprotocol=select_subprotocol) as server: + with self.assertRaisesRegex( + InvalidStatus, + "server rejected WebSocket connection: HTTP 500", + ): + with run_client(server): + self.fail("did not raise") + + def test_process_request(self): + """Server runs process_request before processing the handshake.""" + + def process_request(ws, request): + self.assertIsInstance(request, Request) + ws.process_request_ran = True + + with run_server(process_request=process_request) as server: + with run_client(server) as client: + self.assertEval(client, "ws.process_request_ran", "True") + + def test_process_request_abort_handshake(self): + """Server aborts handshake if process_request returns a response.""" + + def process_request(ws, request): + return ws.protocol.reject(http.HTTPStatus.FORBIDDEN, "Forbidden") + + with run_server(process_request=process_request) as server: + with self.assertRaisesRegex( + InvalidStatus, + "server rejected WebSocket connection: HTTP 403", + ): + with run_client(server): + self.fail("did not raise") + + def test_process_request_raises_exception(self): + """Server returns an error if process_request raises an exception.""" + + def process_request(ws, request): + raise RuntimeError + + with run_server(process_request=process_request) as server: + with self.assertRaisesRegex( + InvalidStatus, + "server rejected WebSocket connection: HTTP 500", + ): + with run_client(server): + self.fail("did not raise") + + def test_process_response(self): + """Server runs process_response after processing the handshake.""" + + def process_response(ws, request, response): + self.assertIsInstance(request, Request) + self.assertIsInstance(response, Response) + ws.process_response_ran = True + + with run_server(process_response=process_response) as server: + with run_client(server) as client: + self.assertEval(client, "ws.process_response_ran", "True") + + def test_process_response_override_response(self): + """Server runs process_response after processing the handshake.""" + + def process_response(ws, request, response): + headers = response.headers.copy() + headers["X-ProcessResponse-Ran"] = "true" + return dataclasses.replace(response, headers=headers) + + with run_server(process_response=process_response) as server: + with run_client(server) as client: + self.assertEqual( + client.response.headers["X-ProcessResponse-Ran"], "true" + ) + + def test_process_response_raises_exception(self): + """Server returns an error if process_response raises an exception.""" + + def process_response(ws, request, response): + raise RuntimeError + + with run_server(process_response=process_response) as server: + with self.assertRaisesRegex( + InvalidStatus, + "server rejected WebSocket connection: HTTP 500", + ): + with run_client(server): + self.fail("did not raise") + + def test_override_server(self): + """Server can override Server header with server_header.""" + with run_server(server_header="Neo") as server: + with run_client(server) as client: + self.assertEval(client, "ws.response.headers['Server']", "Neo") + + def test_remove_server(self): + """Server can remove Server header with server_header.""" + with run_server(server_header=None) as server: + with run_client(server) as client: + self.assertEval(client, "'Server' in ws.response.headers", "False") + + def test_compression_is_enabled(self): + """Server enables compression by default.""" + with run_server() as server: + with run_client(server) as client: + self.assertEval( + client, + "[type(ext).__name__ for ext in ws.protocol.extensions]", + "['PerMessageDeflate']", + ) + + def test_disable_compression(self): + """Server disables compression.""" + with run_server(compression=None) as server: + with run_client(server) as client: + self.assertEval(client, "ws.protocol.extensions", "[]") + + def test_custom_connection_factory(self): + """Server runs ServerConnection factory provided in create_connection.""" + + def create_connection(*args, **kwargs): + server = ServerConnection(*args, **kwargs) + server.create_connection_ran = True + return server + + with run_server(create_connection=create_connection) as server: + with run_client(server) as client: + self.assertEval(client, "ws.create_connection_ran", "True") + + def test_timeout_during_handshake(self): + """Server times out before receiving handshake request from client.""" + with run_server(open_timeout=MS) as server: + with socket.create_connection(server.socket.getsockname()) as sock: + self.assertEqual(sock.recv(4096), b"") + + def test_connection_closed_during_handshake(self): + """Server reads EOF before receiving handshake request from client.""" + with run_server() as server: + # Patch handler to record a reference to the thread running it. + server_thread = None + conn_received = threading.Event() + original_handler = server.handler + + def handler(sock, addr): + nonlocal server_thread + server_thread = threading.current_thread() + nonlocal conn_received + conn_received.set() + original_handler(sock, addr) + + server.handler = handler + + with socket.create_connection(server.socket.getsockname()): + # Wait for the server to receive the connection, then close it. + conn_received.wait() + + # Wait for the server thread to terminate. + server_thread.join() + + +class SecureServerTests(EvalShellMixin, unittest.TestCase): + def test_connection(self): + """Server receives secure connection from client.""" + with run_server(ssl_context=SERVER_CONTEXT) as server: + with run_client(server, ssl_context=CLIENT_CONTEXT) as client: + self.assertEval(client, "ws.protocol.state.name", "OPEN") + self.assertEval(client, "ws.socket.version()[:3]", "TLS") + + def test_timeout_during_tls_handshake(self): + """Server times out before receiving TLS handshake request from client.""" + with run_server(ssl_context=SERVER_CONTEXT, open_timeout=MS) as server: + with socket.create_connection(server.socket.getsockname()) as sock: + self.assertEqual(sock.recv(4096), b"") + + def test_connection_closed_during_tls_handshake(self): + """Server reads EOF before receiving TLS handshake request from client.""" + with run_server(ssl_context=SERVER_CONTEXT) as server: + # Patch handler to record a reference to the thread running it. + server_thread = None + conn_received = threading.Event() + original_handler = server.handler + + def handler(sock, addr): + nonlocal server_thread + server_thread = threading.current_thread() + nonlocal conn_received + conn_received.set() + original_handler(sock, addr) + + server.handler = handler + + with socket.create_connection(server.socket.getsockname()): + # Wait for the server to receive the connection, then close it. + conn_received.wait() + + # Wait for the server thread to terminate. + server_thread.join() + + +@unittest.skipUnless(hasattr(socket, "AF_UNIX"), "this test requires Unix sockets") +class UnixServerTests(EvalShellMixin, unittest.TestCase): + def test_connection(self): + """Server receives connection from client over a Unix socket.""" + with temp_unix_socket_path() as path: + with run_unix_server(path): + with run_unix_client(path) as client: + self.assertEval(client, "ws.protocol.state.name", "OPEN") + + +@unittest.skipUnless(hasattr(socket, "AF_UNIX"), "this test requires Unix sockets") +class SecureUnixServerTests(EvalShellMixin, unittest.TestCase): + def test_connection(self): + """Server receives secure connection from client over a Unix socket.""" + with temp_unix_socket_path() as path: + with run_unix_server(path, ssl_context=SERVER_CONTEXT): + with run_unix_client(path, ssl_context=CLIENT_CONTEXT) as client: + self.assertEval(client, "ws.protocol.state.name", "OPEN") + self.assertEval(client, "ws.socket.version()[:3]", "TLS") + + +class ServerUsageErrorsTests(unittest.TestCase): + def test_unix_without_path_or_sock(self): + """Unix server requires path when sock isn't provided.""" + with self.assertRaisesRegex( + TypeError, + "missing path argument", + ): + unix_serve(eval_shell) + + def test_unix_with_path_and_sock(self): + """Unix server rejects path when sock is provided.""" + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.addCleanup(sock.close) + with self.assertRaisesRegex( + TypeError, + "path and sock arguments are incompatible", + ): + unix_serve(eval_shell, path="/", sock=sock) + + def test_invalid_subprotocol(self): + """Server rejects single value of subprotocols.""" + with self.assertRaisesRegex( + TypeError, + "subprotocols must be a list", + ): + serve(eval_shell, subprotocols="chat") + + def test_unsupported_compression(self): + """Server rejects incorrect value of compression.""" + with self.assertRaisesRegex( + ValueError, + "unsupported compression: False", + ): + serve(eval_shell, compression=False) + + +class WebSocketServerTests(unittest.TestCase): + def test_logger(self): + """WebSocketServer accepts a logger argument.""" + logger = logging.getLogger("test") + with run_server(logger=logger) as server: + self.assertIs(server.logger, logger) + + def test_fileno(self): + """WebSocketServer provides a fileno attribute.""" + with run_server() as server: + self.assertIsInstance(server.fileno(), int) + + def test_shutdown(self): + """WebSocketServer provides a shutdown method.""" + with run_server() as server: + server.shutdown() + # Check that the server socket is closed. + with self.assertRaises(OSError): + server.socket.accept() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_utils.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_utils.py new file mode 100644 index 0000000000000..2980a97b428f5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_utils.py @@ -0,0 +1,33 @@ +import unittest + +from websockets.sync.utils import * + +from ..utils import MS + + +class DeadlineTests(unittest.TestCase): + def test_timeout_pending(self): + """timeout returns remaining time if deadline is in the future.""" + deadline = Deadline(MS) + timeout = deadline.timeout() + self.assertGreater(timeout, 0) + self.assertLess(timeout, MS) + + def test_timeout_elapsed_exception(self): + """timeout raises TimeoutError if deadline is in the past.""" + deadline = Deadline(-MS) + with self.assertRaises(TimeoutError): + deadline.timeout() + + def test_timeout_elapsed_no_exception(self): + """timeout doesn't raise TimeoutError when raise_if_elapsed is disabled.""" + deadline = Deadline(-MS) + timeout = deadline.timeout(raise_if_elapsed=False) + self.assertGreater(timeout, -2 * MS) + self.assertLess(timeout, -MS) + + def test_no_timeout(self): + """timeout returns None when no deadline is set.""" + deadline = Deadline(None) + timeout = deadline.timeout() + self.assertIsNone(timeout, None) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/utils.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/utils.py new file mode 100644 index 0000000000000..8903cd349923a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/utils.py @@ -0,0 +1,26 @@ +import contextlib +import threading +import time +import unittest + +from ..utils import MS + + +class ThreadTestCase(unittest.TestCase): + @contextlib.contextmanager + def run_in_thread(self, target): + """ + Run ``target`` function without arguments in a thread. + + In order to facilitate writing tests, this helper lets the thread run + for 1ms on entry and joins the thread with a 1ms timeout on exit. + + """ + thread = threading.Thread(target=target) + thread.start() + time.sleep(MS) + try: + yield + finally: + thread.join(MS) + self.assertFalse(thread.is_alive()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/w3c-import.log new file mode 100644 index 0000000000000..82b1cf2bd0c87 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/w3c-import.log @@ -0,0 +1,26 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/connection.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/server.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_connection.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_messages.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_server.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/test_utils.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/sync/utils.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_auth.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_auth.py new file mode 100644 index 0000000000000..28db931552190 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_auth.py @@ -0,0 +1 @@ +from websockets.auth import * diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_client.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_client.py new file mode 100644 index 0000000000000..c83c87038f6ed --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_client.py @@ -0,0 +1,614 @@ +import logging +import unittest +import unittest.mock + +from websockets.client import * +from websockets.datastructures import Headers +from websockets.exceptions import InvalidHandshake, InvalidHeader +from websockets.frames import OP_TEXT, Frame +from websockets.http11 import Request, Response +from websockets.protocol import CONNECTING, OPEN +from websockets.uri import parse_uri +from websockets.utils import accept_key + +from .extensions.utils import ( + ClientOpExtensionFactory, + ClientRsv2ExtensionFactory, + OpExtension, + Rsv2Extension, +) +from .test_utils import ACCEPT, KEY +from .utils import DATE, DeprecationTestCase + + +class ConnectTests(unittest.TestCase): + def test_send_connect(self): + with unittest.mock.patch("websockets.client.generate_key", return_value=KEY): + client = ClientProtocol(parse_uri("wss://example.com/test")) + request = client.connect() + self.assertIsInstance(request, Request) + client.send_request(request) + self.assertEqual( + client.data_to_send(), + [ + f"GET /test HTTP/1.1\r\n" + f"Host: example.com\r\n" + f"Upgrade: websocket\r\n" + f"Connection: Upgrade\r\n" + f"Sec-WebSocket-Key: {KEY}\r\n" + f"Sec-WebSocket-Version: 13\r\n" + f"\r\n".encode() + ], + ) + self.assertFalse(client.close_expected()) + + def test_connect_request(self): + with unittest.mock.patch("websockets.client.generate_key", return_value=KEY): + client = ClientProtocol(parse_uri("wss://example.com/test")) + request = client.connect() + self.assertEqual(request.path, "/test") + self.assertEqual( + request.headers, + Headers( + { + "Host": "example.com", + "Upgrade": "websocket", + "Connection": "Upgrade", + "Sec-WebSocket-Key": KEY, + "Sec-WebSocket-Version": "13", + } + ), + ) + + def test_path(self): + client = ClientProtocol(parse_uri("wss://example.com/endpoint?test=1")) + request = client.connect() + + self.assertEqual(request.path, "/endpoint?test=1") + + def test_port(self): + for uri, host in [ + ("ws://example.com/", "example.com"), + ("ws://example.com:80/", "example.com"), + ("ws://example.com:8080/", "example.com:8080"), + ("wss://example.com/", "example.com"), + ("wss://example.com:443/", "example.com"), + ("wss://example.com:8443/", "example.com:8443"), + ]: + with self.subTest(uri=uri): + client = ClientProtocol(parse_uri(uri)) + request = client.connect() + + self.assertEqual(request.headers["Host"], host) + + def test_user_info(self): + client = ClientProtocol(parse_uri("wss://hello:iloveyou@example.com/")) + request = client.connect() + + self.assertEqual(request.headers["Authorization"], "Basic aGVsbG86aWxvdmV5b3U=") + + def test_origin(self): + client = ClientProtocol( + parse_uri("wss://example.com/"), + origin="https://example.com", + ) + request = client.connect() + + self.assertEqual(request.headers["Origin"], "https://example.com") + + def test_extensions(self): + client = ClientProtocol( + parse_uri("wss://example.com/"), + extensions=[ClientOpExtensionFactory()], + ) + request = client.connect() + + self.assertEqual(request.headers["Sec-WebSocket-Extensions"], "x-op; op") + + def test_subprotocols(self): + client = ClientProtocol( + parse_uri("wss://example.com/"), + subprotocols=["chat"], + ) + request = client.connect() + + self.assertEqual(request.headers["Sec-WebSocket-Protocol"], "chat") + + +class AcceptRejectTests(unittest.TestCase): + def test_receive_accept(self): + with unittest.mock.patch("websockets.client.generate_key", return_value=KEY): + client = ClientProtocol(parse_uri("ws://example.com/test")) + client.connect() + client.receive_data( + ( + f"HTTP/1.1 101 Switching Protocols\r\n" + f"Upgrade: websocket\r\n" + f"Connection: Upgrade\r\n" + f"Sec-WebSocket-Accept: {ACCEPT}\r\n" + f"Date: {DATE}\r\n" + f"\r\n" + ).encode(), + ) + [response] = client.events_received() + self.assertIsInstance(response, Response) + self.assertEqual(client.data_to_send(), []) + self.assertFalse(client.close_expected()) + self.assertEqual(client.state, OPEN) + + def test_receive_reject(self): + with unittest.mock.patch("websockets.client.generate_key", return_value=KEY): + client = ClientProtocol(parse_uri("ws://example.com/test")) + client.connect() + client.receive_data( + ( + f"HTTP/1.1 404 Not Found\r\n" + f"Date: {DATE}\r\n" + f"Content-Length: 13\r\n" + f"Content-Type: text/plain; charset=utf-8\r\n" + f"Connection: close\r\n" + f"\r\n" + f"Sorry folks.\n" + ).encode(), + ) + [response] = client.events_received() + self.assertIsInstance(response, Response) + self.assertEqual(client.data_to_send(), []) + self.assertTrue(client.close_expected()) + self.assertEqual(client.state, CONNECTING) + + def test_accept_response(self): + with unittest.mock.patch("websockets.client.generate_key", return_value=KEY): + client = ClientProtocol(parse_uri("ws://example.com/test")) + client.connect() + client.receive_data( + ( + f"HTTP/1.1 101 Switching Protocols\r\n" + f"Upgrade: websocket\r\n" + f"Connection: Upgrade\r\n" + f"Sec-WebSocket-Accept: {ACCEPT}\r\n" + f"Date: {DATE}\r\n" + f"\r\n" + ).encode(), + ) + [response] = client.events_received() + self.assertEqual(response.status_code, 101) + self.assertEqual(response.reason_phrase, "Switching Protocols") + self.assertEqual( + response.headers, + Headers( + { + "Upgrade": "websocket", + "Connection": "Upgrade", + "Sec-WebSocket-Accept": ACCEPT, + "Date": DATE, + } + ), + ) + self.assertIsNone(response.body) + + def test_reject_response(self): + with unittest.mock.patch("websockets.client.generate_key", return_value=KEY): + client = ClientProtocol(parse_uri("ws://example.com/test")) + client.connect() + client.receive_data( + ( + f"HTTP/1.1 404 Not Found\r\n" + f"Date: {DATE}\r\n" + f"Content-Length: 13\r\n" + f"Content-Type: text/plain; charset=utf-8\r\n" + f"Connection: close\r\n" + f"\r\n" + f"Sorry folks.\n" + ).encode(), + ) + [response] = client.events_received() + self.assertEqual(response.status_code, 404) + self.assertEqual(response.reason_phrase, "Not Found") + self.assertEqual( + response.headers, + Headers( + { + "Date": DATE, + "Content-Length": "13", + "Content-Type": "text/plain; charset=utf-8", + "Connection": "close", + } + ), + ) + self.assertEqual(response.body, b"Sorry folks.\n") + + def test_no_response(self): + with unittest.mock.patch("websockets.client.generate_key", return_value=KEY): + client = ClientProtocol(parse_uri("ws://example.com/test")) + client.connect() + client.receive_eof() + self.assertEqual(client.events_received(), []) + + def test_partial_response(self): + with unittest.mock.patch("websockets.client.generate_key", return_value=KEY): + client = ClientProtocol(parse_uri("ws://example.com/test")) + client.connect() + client.receive_data(b"HTTP/1.1 101 Switching Protocols\r\n") + client.receive_eof() + self.assertEqual(client.events_received(), []) + + def test_random_response(self): + with unittest.mock.patch("websockets.client.generate_key", return_value=KEY): + client = ClientProtocol(parse_uri("ws://example.com/test")) + client.connect() + client.receive_data(b"220 smtp.invalid\r\n") + client.receive_data(b"250 Hello relay.invalid\r\n") + client.receive_data(b"250 Ok\r\n") + client.receive_data(b"250 Ok\r\n") + client.receive_eof() + self.assertEqual(client.events_received(), []) + + def make_accept_response(self, client): + request = client.connect() + return Response( + status_code=101, + reason_phrase="Switching Protocols", + headers=Headers( + { + "Upgrade": "websocket", + "Connection": "Upgrade", + "Sec-WebSocket-Accept": accept_key( + request.headers["Sec-WebSocket-Key"] + ), + } + ), + ) + + def test_basic(self): + client = ClientProtocol(parse_uri("wss://example.com/")) + response = self.make_accept_response(client) + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, OPEN) + + def test_missing_connection(self): + client = ClientProtocol(parse_uri("wss://example.com/")) + response = self.make_accept_response(client) + del response.headers["Connection"] + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, CONNECTING) + with self.assertRaises(InvalidHeader) as raised: + raise client.handshake_exc + self.assertEqual(str(raised.exception), "missing Connection header") + + def test_invalid_connection(self): + client = ClientProtocol(parse_uri("wss://example.com/")) + response = self.make_accept_response(client) + del response.headers["Connection"] + response.headers["Connection"] = "close" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, CONNECTING) + with self.assertRaises(InvalidHeader) as raised: + raise client.handshake_exc + self.assertEqual(str(raised.exception), "invalid Connection header: close") + + def test_missing_upgrade(self): + client = ClientProtocol(parse_uri("wss://example.com/")) + response = self.make_accept_response(client) + del response.headers["Upgrade"] + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, CONNECTING) + with self.assertRaises(InvalidHeader) as raised: + raise client.handshake_exc + self.assertEqual(str(raised.exception), "missing Upgrade header") + + def test_invalid_upgrade(self): + client = ClientProtocol(parse_uri("wss://example.com/")) + response = self.make_accept_response(client) + del response.headers["Upgrade"] + response.headers["Upgrade"] = "h2c" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, CONNECTING) + with self.assertRaises(InvalidHeader) as raised: + raise client.handshake_exc + self.assertEqual(str(raised.exception), "invalid Upgrade header: h2c") + + def test_missing_accept(self): + client = ClientProtocol(parse_uri("wss://example.com/")) + response = self.make_accept_response(client) + del response.headers["Sec-WebSocket-Accept"] + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, CONNECTING) + with self.assertRaises(InvalidHeader) as raised: + raise client.handshake_exc + self.assertEqual(str(raised.exception), "missing Sec-WebSocket-Accept header") + + def test_multiple_accept(self): + client = ClientProtocol(parse_uri("wss://example.com/")) + response = self.make_accept_response(client) + response.headers["Sec-WebSocket-Accept"] = ACCEPT + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, CONNECTING) + with self.assertRaises(InvalidHeader) as raised: + raise client.handshake_exc + self.assertEqual( + str(raised.exception), + "invalid Sec-WebSocket-Accept header: " + "more than one Sec-WebSocket-Accept header found", + ) + + def test_invalid_accept(self): + client = ClientProtocol(parse_uri("wss://example.com/")) + response = self.make_accept_response(client) + del response.headers["Sec-WebSocket-Accept"] + response.headers["Sec-WebSocket-Accept"] = ACCEPT + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, CONNECTING) + with self.assertRaises(InvalidHeader) as raised: + raise client.handshake_exc + self.assertEqual( + str(raised.exception), f"invalid Sec-WebSocket-Accept header: {ACCEPT}" + ) + + def test_no_extensions(self): + client = ClientProtocol(parse_uri("wss://example.com/")) + response = self.make_accept_response(client) + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, OPEN) + self.assertEqual(client.extensions, []) + + def test_no_extension(self): + client = ClientProtocol( + parse_uri("wss://example.com/"), + extensions=[ClientOpExtensionFactory()], + ) + response = self.make_accept_response(client) + response.headers["Sec-WebSocket-Extensions"] = "x-op; op" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, OPEN) + self.assertEqual(client.extensions, [OpExtension()]) + + def test_extension(self): + client = ClientProtocol( + parse_uri("wss://example.com/"), + extensions=[ClientRsv2ExtensionFactory()], + ) + response = self.make_accept_response(client) + response.headers["Sec-WebSocket-Extensions"] = "x-rsv2" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, OPEN) + self.assertEqual(client.extensions, [Rsv2Extension()]) + + def test_unexpected_extension(self): + client = ClientProtocol(parse_uri("wss://example.com/")) + response = self.make_accept_response(client) + response.headers["Sec-WebSocket-Extensions"] = "x-op; op" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, CONNECTING) + with self.assertRaises(InvalidHandshake) as raised: + raise client.handshake_exc + self.assertEqual(str(raised.exception), "no extensions supported") + + def test_unsupported_extension(self): + client = ClientProtocol( + parse_uri("wss://example.com/"), + extensions=[ClientRsv2ExtensionFactory()], + ) + response = self.make_accept_response(client) + response.headers["Sec-WebSocket-Extensions"] = "x-op; op" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, CONNECTING) + with self.assertRaises(InvalidHandshake) as raised: + raise client.handshake_exc + self.assertEqual( + str(raised.exception), + "Unsupported extension: name = x-op, params = [('op', None)]", + ) + + def test_supported_extension_parameters(self): + client = ClientProtocol( + parse_uri("wss://example.com/"), + extensions=[ClientOpExtensionFactory("this")], + ) + response = self.make_accept_response(client) + response.headers["Sec-WebSocket-Extensions"] = "x-op; op=this" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, OPEN) + self.assertEqual(client.extensions, [OpExtension("this")]) + + def test_unsupported_extension_parameters(self): + client = ClientProtocol( + parse_uri("wss://example.com/"), + extensions=[ClientOpExtensionFactory("this")], + ) + response = self.make_accept_response(client) + response.headers["Sec-WebSocket-Extensions"] = "x-op; op=that" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, CONNECTING) + with self.assertRaises(InvalidHandshake) as raised: + raise client.handshake_exc + self.assertEqual( + str(raised.exception), + "Unsupported extension: name = x-op, params = [('op', 'that')]", + ) + + def test_multiple_supported_extension_parameters(self): + client = ClientProtocol( + parse_uri("wss://example.com/"), + extensions=[ + ClientOpExtensionFactory("this"), + ClientOpExtensionFactory("that"), + ], + ) + response = self.make_accept_response(client) + response.headers["Sec-WebSocket-Extensions"] = "x-op; op=that" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, OPEN) + self.assertEqual(client.extensions, [OpExtension("that")]) + + def test_multiple_extensions(self): + client = ClientProtocol( + parse_uri("wss://example.com/"), + extensions=[ClientOpExtensionFactory(), ClientRsv2ExtensionFactory()], + ) + response = self.make_accept_response(client) + response.headers["Sec-WebSocket-Extensions"] = "x-op; op" + response.headers["Sec-WebSocket-Extensions"] = "x-rsv2" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, OPEN) + self.assertEqual(client.extensions, [OpExtension(), Rsv2Extension()]) + + def test_multiple_extensions_order(self): + client = ClientProtocol( + parse_uri("wss://example.com/"), + extensions=[ClientOpExtensionFactory(), ClientRsv2ExtensionFactory()], + ) + response = self.make_accept_response(client) + response.headers["Sec-WebSocket-Extensions"] = "x-rsv2" + response.headers["Sec-WebSocket-Extensions"] = "x-op; op" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, OPEN) + self.assertEqual(client.extensions, [Rsv2Extension(), OpExtension()]) + + def test_no_subprotocols(self): + client = ClientProtocol(parse_uri("wss://example.com/")) + response = self.make_accept_response(client) + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, OPEN) + self.assertIsNone(client.subprotocol) + + def test_no_subprotocol(self): + client = ClientProtocol(parse_uri("wss://example.com/"), subprotocols=["chat"]) + response = self.make_accept_response(client) + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, OPEN) + self.assertIsNone(client.subprotocol) + + def test_subprotocol(self): + client = ClientProtocol(parse_uri("wss://example.com/"), subprotocols=["chat"]) + response = self.make_accept_response(client) + response.headers["Sec-WebSocket-Protocol"] = "chat" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, OPEN) + self.assertEqual(client.subprotocol, "chat") + + def test_unexpected_subprotocol(self): + client = ClientProtocol(parse_uri("wss://example.com/")) + response = self.make_accept_response(client) + response.headers["Sec-WebSocket-Protocol"] = "chat" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, CONNECTING) + with self.assertRaises(InvalidHandshake) as raised: + raise client.handshake_exc + self.assertEqual(str(raised.exception), "no subprotocols supported") + + def test_multiple_subprotocols(self): + client = ClientProtocol( + parse_uri("wss://example.com/"), + subprotocols=["superchat", "chat"], + ) + response = self.make_accept_response(client) + response.headers["Sec-WebSocket-Protocol"] = "superchat" + response.headers["Sec-WebSocket-Protocol"] = "chat" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, CONNECTING) + with self.assertRaises(InvalidHandshake) as raised: + raise client.handshake_exc + self.assertEqual( + str(raised.exception), "multiple subprotocols: superchat, chat" + ) + + def test_supported_subprotocol(self): + client = ClientProtocol( + parse_uri("wss://example.com/"), + subprotocols=["superchat", "chat"], + ) + response = self.make_accept_response(client) + response.headers["Sec-WebSocket-Protocol"] = "chat" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, OPEN) + self.assertEqual(client.subprotocol, "chat") + + def test_unsupported_subprotocol(self): + client = ClientProtocol( + parse_uri("wss://example.com/"), + subprotocols=["superchat", "chat"], + ) + response = self.make_accept_response(client) + response.headers["Sec-WebSocket-Protocol"] = "otherchat" + client.receive_data(response.serialize()) + [response] = client.events_received() + + self.assertEqual(client.state, CONNECTING) + with self.assertRaises(InvalidHandshake) as raised: + raise client.handshake_exc + self.assertEqual(str(raised.exception), "unsupported subprotocol: otherchat") + + +class MiscTests(unittest.TestCase): + def test_bypass_handshake(self): + client = ClientProtocol(parse_uri("ws://example.com/test"), state=OPEN) + client.receive_data(b"\x81\x06Hello!") + [frame] = client.events_received() + self.assertEqual(frame, Frame(OP_TEXT, b"Hello!")) + + def test_custom_logger(self): + logger = logging.getLogger("test") + with self.assertLogs("test", logging.DEBUG) as logs: + ClientProtocol(parse_uri("wss://example.com/test"), logger=logger) + self.assertEqual(len(logs.records), 1) + + +class BackwardsCompatibilityTests(DeprecationTestCase): + def test_client_connection_class(self): + with self.assertDeprecationWarning( + "ClientConnection was renamed to ClientProtocol" + ): + from websockets.client import ClientConnection + + client = ClientConnection("ws://localhost/") + + self.assertIsInstance(client, ClientProtocol) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_connection.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_connection.py new file mode 100644 index 0000000000000..6592d67d0d4a5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_connection.py @@ -0,0 +1,14 @@ +from websockets.protocol import Protocol + +from .utils import DeprecationTestCase + + +class BackwardsCompatibilityTests(DeprecationTestCase): + def test_connection_class(self): + with self.assertDeprecationWarning( + "websockets.connection was renamed to websockets.protocol " + "and Connection was renamed to Protocol" + ): + from websockets.connection import Connection + + self.assertIs(Connection, Protocol) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_datastructures.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_datastructures.py new file mode 100644 index 0000000000000..32b79817ae754 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_datastructures.py @@ -0,0 +1,236 @@ +import unittest + +from websockets.datastructures import * + + +class MultipleValuesErrorTests(unittest.TestCase): + def test_multiple_values_error_str(self): + self.assertEqual(str(MultipleValuesError("Connection")), "'Connection'") + self.assertEqual(str(MultipleValuesError()), "") + + +class HeadersTests(unittest.TestCase): + def setUp(self): + self.headers = Headers([("Connection", "Upgrade"), ("Server", "websockets")]) + + def test_init(self): + self.assertEqual( + Headers(), + Headers(), + ) + + def test_init_from_kwargs(self): + self.assertEqual( + Headers(connection="Upgrade", server="websockets"), + self.headers, + ) + + def test_init_from_headers(self): + self.assertEqual( + Headers(self.headers), + self.headers, + ) + + def test_init_from_headers_and_kwargs(self): + self.assertEqual( + Headers(Headers(connection="Upgrade"), server="websockets"), + self.headers, + ) + + def test_init_from_mapping(self): + self.assertEqual( + Headers({"Connection": "Upgrade", "Server": "websockets"}), + self.headers, + ) + + def test_init_from_mapping_and_kwargs(self): + self.assertEqual( + Headers({"Connection": "Upgrade"}, server="websockets"), + self.headers, + ) + + def test_init_from_iterable(self): + self.assertEqual( + Headers([("Connection", "Upgrade"), ("Server", "websockets")]), + self.headers, + ) + + def test_init_from_iterable_and_kwargs(self): + self.assertEqual( + Headers([("Connection", "Upgrade")], server="websockets"), + self.headers, + ) + + def test_init_multiple_positional_arguments(self): + with self.assertRaises(TypeError): + Headers(Headers(connection="Upgrade"), Headers(server="websockets")) + + def test_str(self): + self.assertEqual( + str(self.headers), "Connection: Upgrade\r\nServer: websockets\r\n\r\n" + ) + + def test_repr(self): + self.assertEqual( + repr(self.headers), + "Headers([('Connection', 'Upgrade'), ('Server', 'websockets')])", + ) + + def test_copy(self): + self.assertEqual(repr(self.headers.copy()), repr(self.headers)) + + def test_serialize(self): + self.assertEqual( + self.headers.serialize(), + b"Connection: Upgrade\r\nServer: websockets\r\n\r\n", + ) + + 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"], "websockets") + + def test_getitem_case_insensitive(self): + self.assertEqual(self.headers["server"], "websockets") + + def test_getitem_key_error(self): + with self.assertRaises(KeyError): + self.headers["Upgrade"] + + 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_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_eq(self): + other_headers = Headers([("Connection", "Upgrade"), ("Server", "websockets")]) + self.assertEqual(self.headers, other_headers) + + def test_eq_case_insensitive(self): + other_headers = Headers(connection="Upgrade", server="websockets") + self.assertEqual(self.headers, other_headers) + + def test_eq_not_equal(self): + other_headers = Headers([("Connection", "close"), ("Server", "websockets")]) + self.assertNotEqual(self.headers, other_headers) + + def test_eq_other_type(self): + self.assertNotEqual( + self.headers, "Connection: Upgrade\r\nServer: websockets\r\n\r\n" + ) + + 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_raw_items(self): + self.assertEqual( + list(self.headers.raw_items()), + [("Connection", "Upgrade"), ("Server", "websockets")], + ) + + +class MultiValueHeadersTests(unittest.TestCase): + def setUp(self): + self.headers = Headers([("Server", "Python"), ("Server", "websockets")]) + + def test_init_from_headers(self): + self.assertEqual( + Headers(self.headers), + self.headers, + ) + + def test_init_from_headers_and_kwargs(self): + self.assertEqual( + Headers(Headers(server="Python"), server="websockets"), + self.headers, + ) + + def test_str(self): + self.assertEqual( + str(self.headers), "Server: Python\r\nServer: websockets\r\n\r\n" + ) + + def test_repr(self): + self.assertEqual( + repr(self.headers), + "Headers([('Server', 'Python'), ('Server', 'websockets')])", + ) + + def test_copy(self): + self.assertEqual(repr(self.headers.copy()), repr(self.headers)) + + def test_serialize(self): + self.assertEqual( + self.headers.serialize(), + b"Server: Python\r\nServer: websockets\r\n\r\n", + ) + + def test_iter(self): + self.assertEqual(set(iter(self.headers)), {"server"}) + + def test_len(self): + self.assertEqual(len(self.headers), 1) + + def test_getitem_multiple_values_error(self): + with self.assertRaises(MultipleValuesError): + self.headers["Server"] + + def test_setitem(self): + self.headers["Server"] = "redux" + self.assertEqual( + self.headers.get_all("Server"), ["Python", "websockets", "redux"] + ) + + def test_delitem(self): + del self.headers["Server"] + with self.assertRaises(KeyError): + self.headers["Server"] + + def test_get_all(self): + self.assertEqual(self.headers.get_all("Server"), ["Python", "websockets"]) + + def test_raw_items(self): + self.assertEqual( + list(self.headers.raw_items()), + [("Server", "Python"), ("Server", "websockets")], + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_exceptions.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_exceptions.py new file mode 100644 index 0000000000000..1e6f58fad583a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_exceptions.py @@ -0,0 +1,196 @@ +import unittest + +from websockets.datastructures import Headers +from websockets.exceptions import * +from websockets.frames import Close, CloseCode +from websockets.http11 import Response + + +class ExceptionsTests(unittest.TestCase): + def test_str(self): + for exception, exception_str in [ + ( + WebSocketException("something went wrong"), + "something went wrong", + ), + ( + ConnectionClosed( + Close(CloseCode.NORMAL_CLOSURE, ""), + Close(CloseCode.NORMAL_CLOSURE, ""), + True, + ), + "received 1000 (OK); then sent 1000 (OK)", + ), + ( + ConnectionClosed( + Close(CloseCode.GOING_AWAY, "Bye!"), + Close(CloseCode.GOING_AWAY, "Bye!"), + False, + ), + "sent 1001 (going away) Bye!; then received 1001 (going away) Bye!", + ), + ( + ConnectionClosed( + Close(CloseCode.NORMAL_CLOSURE, "race"), + Close(CloseCode.NORMAL_CLOSURE, "cond"), + True, + ), + "received 1000 (OK) race; then sent 1000 (OK) cond", + ), + ( + ConnectionClosed( + Close(CloseCode.NORMAL_CLOSURE, "cond"), + Close(CloseCode.NORMAL_CLOSURE, "race"), + False, + ), + "sent 1000 (OK) race; then received 1000 (OK) cond", + ), + ( + ConnectionClosed( + None, + Close(CloseCode.MESSAGE_TOO_BIG, ""), + None, + ), + "sent 1009 (message too big); no close frame received", + ), + ( + ConnectionClosed( + Close(CloseCode.PROTOCOL_ERROR, ""), + None, + None, + ), + "received 1002 (protocol error); no close frame sent", + ), + ( + ConnectionClosedOK( + Close(CloseCode.NORMAL_CLOSURE, ""), + Close(CloseCode.NORMAL_CLOSURE, ""), + True, + ), + "received 1000 (OK); then sent 1000 (OK)", + ), + ( + ConnectionClosedError( + None, + None, + None, + ), + "no close frame received or sent", + ), + ( + 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", "exp. token", "a=|", 3), + "invalid Sec-WebSocket-Protocol header: exp. 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", + ), + ( + InvalidStatus(Response(401, "Unauthorized", Headers())), + "server rejected WebSocket connection: HTTP 401", + ), + ( + InvalidStatusCode(403, Headers()), + "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("|", "not at all!"), + "| isn't a valid URI: not at all!", + ), + ( + PayloadTooBig("payload length exceeds limit: 2 > 1 bytes"), + "payload length exceeds limit: 2 > 1 bytes", + ), + ( + ProtocolError("invalid opcode: 7"), + "invalid opcode: 7", + ), + ]: + with self.subTest(exception=exception): + self.assertEqual(str(exception), exception_str) + + def test_connection_closed_attributes_backwards_compatibility(self): + exception = ConnectionClosed(Close(CloseCode.NORMAL_CLOSURE, "OK"), None, None) + self.assertEqual(exception.code, CloseCode.NORMAL_CLOSURE) + self.assertEqual(exception.reason, "OK") + + def test_connection_closed_attributes_backwards_compatibility_defaults(self): + exception = ConnectionClosed(None, None, None) + self.assertEqual(exception.code, CloseCode.ABNORMAL_CLOSURE) + self.assertEqual(exception.reason, "") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_exports.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_exports.py new file mode 100644 index 0000000000000..67a1a6f994fb4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_exports.py @@ -0,0 +1,30 @@ +import unittest + +import websockets +import websockets.auth +import websockets.client +import websockets.datastructures +import websockets.exceptions +import websockets.legacy.protocol +import websockets.server +import websockets.typing +import websockets.uri + + +combined_exports = ( + websockets.auth.__all__ + + websockets.client.__all__ + + websockets.datastructures.__all__ + + websockets.exceptions.__all__ + + websockets.legacy.protocol.__all__ + + websockets.server.__all__ + + websockets.typing.__all__ +) + + +class ExportsTests(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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_frames.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_frames.py new file mode 100644 index 0000000000000..e323b3b57c823 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_frames.py @@ -0,0 +1,495 @@ +import codecs +import dataclasses +import unittest +import unittest.mock + +from websockets.exceptions import PayloadTooBig, ProtocolError +from websockets.frames import * +from websockets.frames import CloseCode +from websockets.streams import StreamReader + +from .utils import GeneratorTestCase + + +class FramesTestCase(GeneratorTestCase): + def enforce_mask(self, mask): + return unittest.mock.patch("secrets.token_bytes", return_value=mask) + + def parse(self, data, mask, max_size=None, extensions=None): + """ + Parse a frame from a bytestring. + + """ + reader = StreamReader() + reader.feed_data(data) + reader.feed_eof() + parser = Frame.parse( + reader.read_exact, mask=mask, max_size=max_size, extensions=extensions + ) + return self.assertGeneratorReturns(parser) + + def assertFrameData(self, frame, data, mask, extensions=None): + """ + Serializing frame yields data. Parsing data yields frame. + + """ + # Compare frames first, because test failures are easier to read, + # especially when mask = True. + parsed = self.parse(data, mask=mask, extensions=extensions) + self.assertEqual(parsed, frame) + + # Make masking deterministic by reusing the same "random" mask. + # This has an effect only when mask is True. + mask_bytes = data[2:6] if mask else b"" + with self.enforce_mask(mask_bytes): + serialized = frame.serialize(mask=mask, extensions=extensions) + self.assertEqual(serialized, data) + + +class FrameTests(FramesTestCase): + def test_text_unmasked(self): + self.assertFrameData( + Frame(OP_TEXT, b"Spam"), + b"\x81\x04Spam", + mask=False, + ) + + def test_text_masked(self): + self.assertFrameData( + Frame(OP_TEXT, b"Spam"), + b"\x81\x84\x5b\xfb\xe1\xa8\x08\x8b\x80\xc5", + mask=True, + ) + + def test_binary_unmasked(self): + self.assertFrameData( + Frame(OP_BINARY, b"Eggs"), + b"\x82\x04Eggs", + mask=False, + ) + + def test_binary_masked(self): + self.assertFrameData( + Frame(OP_BINARY, b"Eggs"), + b"\x82\x84\x53\xcd\xe2\x89\x16\xaa\x85\xfa", + mask=True, + ) + + def test_non_ascii_text_unmasked(self): + self.assertFrameData( + Frame(OP_TEXT, "café".encode("utf-8")), + b"\x81\x05caf\xc3\xa9", + mask=False, + ) + + def test_non_ascii_text_masked(self): + self.assertFrameData( + Frame(OP_TEXT, "café".encode("utf-8")), + b"\x81\x85\x64\xbe\xee\x7e\x07\xdf\x88\xbd\xcd", + mask=True, + ) + + def test_close(self): + self.assertFrameData( + Frame(OP_CLOSE, b""), + b"\x88\x00", + mask=False, + ) + + def test_ping(self): + self.assertFrameData( + Frame(OP_PING, b"ping"), + b"\x89\x04ping", + mask=False, + ) + + def test_pong(self): + self.assertFrameData( + Frame(OP_PONG, b"pong"), + b"\x8a\x04pong", + mask=False, + ) + + def test_long(self): + self.assertFrameData( + Frame(OP_BINARY, 126 * b"a"), + b"\x82\x7e\x00\x7e" + 126 * b"a", + mask=False, + ) + + def test_very_long(self): + self.assertFrameData( + Frame(OP_BINARY, 65536 * b"a"), + b"\x82\x7f\x00\x00\x00\x00\x00\x01\x00\x00" + 65536 * b"a", + mask=False, + ) + + def test_payload_too_big(self): + with self.assertRaises(PayloadTooBig): + self.parse(b"\x82\x7e\x04\x01" + 1025 * b"a", mask=False, max_size=1024) + + def test_bad_reserved_bits(self): + for data in [b"\xc0\x00", b"\xa0\x00", b"\x90\x00"]: + with self.subTest(data=data): + with self.assertRaises(ProtocolError): + self.parse(data, mask=False) + + def test_good_opcode(self): + for opcode in list(range(0x00, 0x03)) + list(range(0x08, 0x0B)): + data = bytes([0x80 | opcode, 0]) + with self.subTest(data=data): + self.parse(data, mask=False) # does not raise an exception + + def test_bad_opcode(self): + for opcode in list(range(0x03, 0x08)) + list(range(0x0B, 0x10)): + data = bytes([0x80 | opcode, 0]) + with self.subTest(data=data): + with self.assertRaises(ProtocolError): + self.parse(data, mask=False) + + def test_mask_flag(self): + # Mask flag correctly set. + self.parse(b"\x80\x80\x00\x00\x00\x00", mask=True) + # Mask flag incorrectly unset. + with self.assertRaises(ProtocolError): + self.parse(b"\x80\x80\x00\x00\x00\x00", mask=False) + # Mask flag correctly unset. + self.parse(b"\x80\x00", mask=False) + # Mask flag incorrectly set. + with self.assertRaises(ProtocolError): + self.parse(b"\x80\x00", mask=True) + + def test_control_frame_max_length(self): + # At maximum allowed length. + self.parse(b"\x88\x7e\x00\x7d" + 125 * b"a", mask=False) + # Above maximum allowed length. + with self.assertRaises(ProtocolError): + self.parse(b"\x88\x7e\x00\x7e" + 126 * b"a", mask=False) + + def test_fragmented_control_frame(self): + # Fin bit correctly set. + self.parse(b"\x88\x00", mask=False) + # Fin bit incorrectly unset. + with self.assertRaises(ProtocolError): + self.parse(b"\x08\x00", mask=False) + + 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 dataclasses.replace(frame, data=data) + + # This extensions is symmetrical. + @staticmethod + def decode(frame, *, max_size=None): + return Rot13.encode(frame) + + self.assertFrameData( + Frame(OP_TEXT, b"hello"), + b"\x81\x05uryyb", + mask=False, + extensions=[Rot13()], + ) + + +class StrTests(unittest.TestCase): + def test_cont_text(self): + self.assertEqual( + str(Frame(OP_CONT, b" cr\xc3\xa8me", fin=False)), + "CONT ' crème' [text, 7 bytes, continued]", + ) + + def test_cont_binary(self): + self.assertEqual( + str(Frame(OP_CONT, b"\xfc\xfd\xfe\xff", fin=False)), + "CONT fc fd fe ff [binary, 4 bytes, continued]", + ) + + def test_cont_binary_from_memoryview(self): + self.assertEqual( + str(Frame(OP_CONT, memoryview(b"\xfc\xfd\xfe\xff"), fin=False)), + "CONT fc fd fe ff [binary, 4 bytes, continued]", + ) + + def test_cont_final_text(self): + self.assertEqual( + str(Frame(OP_CONT, b" cr\xc3\xa8me")), + "CONT ' crème' [text, 7 bytes]", + ) + + def test_cont_final_binary(self): + self.assertEqual( + str(Frame(OP_CONT, b"\xfc\xfd\xfe\xff")), + "CONT fc fd fe ff [binary, 4 bytes]", + ) + + def test_cont_final_binary_from_memoryview(self): + self.assertEqual( + str(Frame(OP_CONT, memoryview(b"\xfc\xfd\xfe\xff"))), + "CONT fc fd fe ff [binary, 4 bytes]", + ) + + def test_cont_text_truncated(self): + self.assertEqual( + str(Frame(OP_CONT, b"caf\xc3\xa9 " * 16, fin=False)), + "CONT 'café café café café café café café café café ca..." + "fé café café café café ' [text, 96 bytes, continued]", + ) + + def test_cont_binary_truncated(self): + self.assertEqual( + str(Frame(OP_CONT, bytes(range(256)), fin=False)), + "CONT 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ..." + " f8 f9 fa fb fc fd fe ff [binary, 256 bytes, continued]", + ) + + def test_cont_binary_truncated_from_memoryview(self): + self.assertEqual( + str(Frame(OP_CONT, memoryview(bytes(range(256))), fin=False)), + "CONT 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ..." + " f8 f9 fa fb fc fd fe ff [binary, 256 bytes, continued]", + ) + + def test_text(self): + self.assertEqual( + str(Frame(OP_TEXT, b"caf\xc3\xa9")), + "TEXT 'café' [5 bytes]", + ) + + def test_text_non_final(self): + self.assertEqual( + str(Frame(OP_TEXT, b"caf\xc3\xa9", fin=False)), + "TEXT 'café' [5 bytes, continued]", + ) + + def test_text_truncated(self): + self.assertEqual( + str(Frame(OP_TEXT, b"caf\xc3\xa9 " * 16)), + "TEXT 'café café café café café café café café café ca..." + "fé café café café café ' [96 bytes]", + ) + + def test_text_with_newline(self): + self.assertEqual( + str(Frame(OP_TEXT, b"Hello\nworld!")), + "TEXT 'Hello\\nworld!' [12 bytes]", + ) + + def test_binary(self): + self.assertEqual( + str(Frame(OP_BINARY, b"\x00\x01\x02\x03")), + "BINARY 00 01 02 03 [4 bytes]", + ) + + def test_binary_from_memoryview(self): + self.assertEqual( + str(Frame(OP_BINARY, memoryview(b"\x00\x01\x02\x03"))), + "BINARY 00 01 02 03 [4 bytes]", + ) + + def test_binary_non_final(self): + self.assertEqual( + str(Frame(OP_BINARY, b"\x00\x01\x02\x03", fin=False)), + "BINARY 00 01 02 03 [4 bytes, continued]", + ) + + def test_binary_non_final_from_memoryview(self): + self.assertEqual( + str(Frame(OP_BINARY, memoryview(b"\x00\x01\x02\x03"), fin=False)), + "BINARY 00 01 02 03 [4 bytes, continued]", + ) + + def test_binary_truncated(self): + self.assertEqual( + str(Frame(OP_BINARY, bytes(range(256)))), + "BINARY 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ..." + " f8 f9 fa fb fc fd fe ff [256 bytes]", + ) + + def test_binary_truncated_from_memoryview(self): + self.assertEqual( + str(Frame(OP_BINARY, memoryview(bytes(range(256))))), + "BINARY 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ..." + " f8 f9 fa fb fc fd fe ff [256 bytes]", + ) + + def test_close(self): + self.assertEqual( + str(Frame(OP_CLOSE, b"\x03\xe8")), + "CLOSE 1000 (OK) [2 bytes]", + ) + + def test_close_reason(self): + self.assertEqual( + str(Frame(OP_CLOSE, b"\x03\xe9Bye!")), + "CLOSE 1001 (going away) Bye! [6 bytes]", + ) + + def test_ping(self): + self.assertEqual( + str(Frame(OP_PING, b"")), + "PING '' [0 bytes]", + ) + + def test_ping_text(self): + self.assertEqual( + str(Frame(OP_PING, b"ping")), + "PING 'ping' [text, 4 bytes]", + ) + + def test_ping_text_with_newline(self): + self.assertEqual( + str(Frame(OP_PING, b"ping\n")), + "PING 'ping\\n' [text, 5 bytes]", + ) + + def test_ping_binary(self): + self.assertEqual( + str(Frame(OP_PING, b"\xff\x00\xff\x00")), + "PING ff 00 ff 00 [binary, 4 bytes]", + ) + + def test_pong(self): + self.assertEqual( + str(Frame(OP_PONG, b"")), + "PONG '' [0 bytes]", + ) + + def test_pong_text(self): + self.assertEqual( + str(Frame(OP_PONG, b"pong")), + "PONG 'pong' [text, 4 bytes]", + ) + + def test_pong_text_with_newline(self): + self.assertEqual( + str(Frame(OP_PONG, b"pong\n")), + "PONG 'pong\\n' [text, 5 bytes]", + ) + + def test_pong_binary(self): + self.assertEqual( + str(Frame(OP_PONG, b"\xff\x00\xff\x00")), + "PONG ff 00 ff 00 [binary, 4 bytes]", + ) + + +class PrepareDataTests(unittest.TestCase): + 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_list(self): + with self.assertRaises(TypeError): + prepare_data([]) + + def test_prepare_data_none(self): + with self.assertRaises(TypeError): + prepare_data(None) + + +class PrepareCtrlTests(unittest.TestCase): + def test_prepare_ctrl_str(self): + self.assertEqual(prepare_ctrl("café"), b"caf\xc3\xa9") + + def test_prepare_ctrl_bytes(self): + self.assertEqual(prepare_ctrl(b"tea"), b"tea") + + def test_prepare_ctrl_bytearray(self): + self.assertEqual(prepare_ctrl(bytearray(b"tea")), b"tea") + + def test_prepare_ctrl_memoryview(self): + self.assertEqual(prepare_ctrl(memoryview(b"tea")), b"tea") + + def test_prepare_ctrl_list(self): + with self.assertRaises(TypeError): + prepare_ctrl([]) + + def test_prepare_ctrl_none(self): + with self.assertRaises(TypeError): + prepare_ctrl(None) + + +class CloseTests(unittest.TestCase): + def assertCloseData(self, close, data): + """ + Serializing close yields data. Parsing data yields close. + + """ + serialized = close.serialize() + self.assertEqual(serialized, data) + parsed = Close.parse(data) + self.assertEqual(parsed, close) + + def test_str(self): + self.assertEqual( + str(Close(CloseCode.NORMAL_CLOSURE, "")), + "1000 (OK)", + ) + self.assertEqual( + str(Close(CloseCode.GOING_AWAY, "Bye!")), + "1001 (going away) Bye!", + ) + self.assertEqual( + str(Close(3000, "")), + "3000 (registered)", + ) + self.assertEqual( + str(Close(4000, "")), + "4000 (private use)", + ) + self.assertEqual( + str(Close(5000, "")), + "5000 (unknown)", + ) + + def test_parse_and_serialize(self): + self.assertCloseData( + Close(CloseCode.NORMAL_CLOSURE, "OK"), + b"\x03\xe8OK", + ) + self.assertCloseData( + Close(CloseCode.GOING_AWAY, ""), + b"\x03\xe9", + ) + + def test_parse_empty(self): + self.assertEqual( + Close.parse(b""), + Close(CloseCode.NO_STATUS_RCVD, ""), + ) + + def test_parse_errors(self): + with self.assertRaises(ProtocolError): + Close.parse(b"\x03") + with self.assertRaises(ProtocolError): + Close.parse(b"\x03\xe7") + with self.assertRaises(UnicodeDecodeError): + Close.parse(b"\x03\xe8\xff\xff") + + def test_serialize_errors(self): + with self.assertRaises(ProtocolError): + Close(999, "").serialize() diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_headers.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_headers.py new file mode 100644 index 0000000000000..4ebd8b90cfefd --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_headers.py @@ -0,0 +1,222 @@ +import unittest + +from websockets.exceptions import InvalidHeaderFormat, InvalidHeaderValue +from websockets.headers import * + + +class HeadersTests(unittest.TestCase): + def test_build_host(self): + for (host, port, secure), result in [ + (("localhost", 80, False), "localhost"), + (("localhost", 8000, False), "localhost:8000"), + (("localhost", 443, True), "localhost"), + (("localhost", 8443, True), "localhost:8443"), + (("example.com", 80, False), "example.com"), + (("example.com", 8000, False), "example.com:8000"), + (("example.com", 443, True), "example.com"), + (("example.com", 8443, True), "example.com:8443"), + (("127.0.0.1", 80, False), "127.0.0.1"), + (("127.0.0.1", 8000, False), "127.0.0.1:8000"), + (("127.0.0.1", 443, True), "127.0.0.1"), + (("127.0.0.1", 8443, True), "127.0.0.1:8443"), + (("::1", 80, False), "[::1]"), + (("::1", 8000, False), "[::1]:8000"), + (("::1", 443, True), "[::1]"), + (("::1", 8443, True), "[::1]:8443"), + ]: + with self.subTest(host=host, port=port, secure=secure): + self.assertEqual(build_host(host, port, secure), result) + + 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_validate_subprotocols(self): + for subprotocols in [[], ["sip"], ["v1.usp"], ["sip", "v1.usp"]]: + with self.subTest(subprotocols=subprotocols): + validate_subprotocols(subprotocols) + + def test_validate_subprotocols_invalid(self): + for subprotocols, exception in [ + ({"sip": None}, TypeError), + ("sip", TypeError), + ([""], ValueError), + ]: + with self.subTest(subprotocols=subprotocols): + with self.assertRaises(exception): + validate_subprotocols(subprotocols) + + 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/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_http.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_http.py new file mode 100644 index 0000000000000..036bc14102b64 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_http.py @@ -0,0 +1 @@ +from websockets.http import * diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_http11.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_http11.py new file mode 100644 index 0000000000000..d2e5e04627c2c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_http11.py @@ -0,0 +1,344 @@ +from websockets.datastructures import Headers +from websockets.exceptions import SecurityError +from websockets.http11 import * +from websockets.http11 import parse_headers +from websockets.streams import StreamReader + +from .utils import GeneratorTestCase + + +class RequestTests(GeneratorTestCase): + def setUp(self): + super().setUp() + self.reader = StreamReader() + + def parse(self): + return Request.parse(self.reader.read_line) + + def test_parse(self): + # Example from the protocol overview in RFC 6455 + self.reader.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" + ) + request = self.assertGeneratorReturns(self.parse()) + self.assertEqual(request.path, "/chat") + self.assertEqual(request.headers["Upgrade"], "websocket") + + def test_parse_empty(self): + self.reader.feed_eof() + with self.assertRaises(EOFError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "connection closed while reading HTTP request line", + ) + + def test_parse_invalid_request_line(self): + self.reader.feed_data(b"GET /\r\n\r\n") + with self.assertRaises(ValueError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "invalid HTTP request line: GET /", + ) + + def test_parse_unsupported_method(self): + self.reader.feed_data(b"OPTIONS * HTTP/1.1\r\n\r\n") + with self.assertRaises(ValueError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "unsupported HTTP method: OPTIONS", + ) + + def test_parse_unsupported_version(self): + self.reader.feed_data(b"GET /chat HTTP/1.0\r\n\r\n") + with self.assertRaises(ValueError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "unsupported HTTP version: HTTP/1.0", + ) + + def test_parse_invalid_header(self): + self.reader.feed_data(b"GET /chat HTTP/1.1\r\nOops\r\n") + with self.assertRaises(ValueError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "invalid HTTP header line: Oops", + ) + + def test_parse_body(self): + self.reader.feed_data(b"GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\nYo\n") + with self.assertRaises(ValueError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "unsupported request body", + ) + + def test_parse_body_with_transfer_encoding(self): + self.reader.feed_data(b"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n") + with self.assertRaises(NotImplementedError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "transfer codings aren't supported", + ) + + def test_serialize(self): + # Example from the protocol overview in RFC 6455 + request = Request( + "/chat", + Headers( + [ + ("Host", "server.example.com"), + ("Upgrade", "websocket"), + ("Connection", "Upgrade"), + ("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="), + ("Origin", "http://example.com"), + ("Sec-WebSocket-Protocol", "chat, superchat"), + ("Sec-WebSocket-Version", "13"), + ] + ), + ) + self.assertEqual( + request.serialize(), + 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", + ) + + +class ResponseTests(GeneratorTestCase): + def setUp(self): + super().setUp() + self.reader = StreamReader() + + def parse(self): + return Response.parse( + self.reader.read_line, + self.reader.read_exact, + self.reader.read_to_eof, + ) + + def test_parse(self): + # Example from the protocol overview in RFC 6455 + self.reader.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" + ) + response = self.assertGeneratorReturns(self.parse()) + self.assertEqual(response.status_code, 101) + self.assertEqual(response.reason_phrase, "Switching Protocols") + self.assertEqual(response.headers["Upgrade"], "websocket") + self.assertIsNone(response.body) + + def test_parse_empty(self): + self.reader.feed_eof() + with self.assertRaises(EOFError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "connection closed while reading HTTP status line", + ) + + def test_parse_invalid_status_line(self): + self.reader.feed_data(b"Hello!\r\n") + with self.assertRaises(ValueError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "invalid HTTP status line: Hello!", + ) + + def test_parse_unsupported_version(self): + self.reader.feed_data(b"HTTP/1.0 400 Bad Request\r\n\r\n") + with self.assertRaises(ValueError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "unsupported HTTP version: HTTP/1.0", + ) + + def test_parse_invalid_status(self): + self.reader.feed_data(b"HTTP/1.1 OMG WTF\r\n\r\n") + with self.assertRaises(ValueError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "invalid HTTP status code: OMG", + ) + + def test_parse_unsupported_status(self): + self.reader.feed_data(b"HTTP/1.1 007 My name is Bond\r\n\r\n") + with self.assertRaises(ValueError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "unsupported HTTP status code: 007", + ) + + def test_parse_invalid_reason(self): + self.reader.feed_data(b"HTTP/1.1 200 \x7f\r\n\r\n") + with self.assertRaises(ValueError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "invalid HTTP reason phrase: \x7f", + ) + + def test_parse_invalid_header(self): + self.reader.feed_data(b"HTTP/1.1 500 Internal Server Error\r\nOops\r\n") + with self.assertRaises(ValueError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "invalid HTTP header line: Oops", + ) + + def test_parse_body_with_content_length(self): + self.reader.feed_data( + b"HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello world!\n" + ) + response = self.assertGeneratorReturns(self.parse()) + self.assertEqual(response.body, b"Hello world!\n") + + def test_parse_body_without_content_length(self): + self.reader.feed_data(b"HTTP/1.1 200 OK\r\n\r\nHello world!\n") + gen = self.parse() + self.assertGeneratorRunning(gen) + self.reader.feed_eof() + response = self.assertGeneratorReturns(gen) + self.assertEqual(response.body, b"Hello world!\n") + + def test_parse_body_with_content_length_too_long(self): + self.reader.feed_data(b"HTTP/1.1 200 OK\r\nContent-Length: 1048577\r\n\r\n") + with self.assertRaises(SecurityError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "body too large: 1048577 bytes", + ) + + def test_parse_body_without_content_length_too_long(self): + self.reader.feed_data(b"HTTP/1.1 200 OK\r\n\r\n" + b"a" * 1048577) + with self.assertRaises(SecurityError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "body too large: over 1048576 bytes", + ) + + def test_parse_body_with_transfer_encoding(self): + self.reader.feed_data(b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n") + with self.assertRaises(NotImplementedError) as raised: + next(self.parse()) + self.assertEqual( + str(raised.exception), + "transfer codings aren't supported", + ) + + def test_parse_body_no_content(self): + self.reader.feed_data(b"HTTP/1.1 204 No Content\r\n\r\n") + response = self.assertGeneratorReturns(self.parse()) + self.assertIsNone(response.body) + + def test_parse_body_not_modified(self): + self.reader.feed_data(b"HTTP/1.1 304 Not Modified\r\n\r\n") + response = self.assertGeneratorReturns(self.parse()) + self.assertIsNone(response.body) + + def test_serialize(self): + # Example from the protocol overview in RFC 6455 + response = Response( + 101, + "Switching Protocols", + Headers( + [ + ("Upgrade", "websocket"), + ("Connection", "Upgrade"), + ("Sec-WebSocket-Accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="), + ("Sec-WebSocket-Protocol", "chat"), + ] + ), + ) + self.assertEqual( + response.serialize(), + 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", + ) + + def test_serialize_with_body(self): + response = Response( + 200, + "OK", + Headers([("Content-Length", "13"), ("Content-Type", "text/plain")]), + b"Hello world!\n", + ) + self.assertEqual( + response.serialize(), + b"HTTP/1.1 200 OK\r\n" + b"Content-Length: 13\r\n" + b"Content-Type: text/plain\r\n" + b"\r\n" + b"Hello world!\n", + ) + + +class HeadersTests(GeneratorTestCase): + def setUp(self): + super().setUp() + self.reader = StreamReader() + + def parse_headers(self): + return parse_headers(self.reader.read_line) + + def test_parse_invalid_name(self): + self.reader.feed_data(b"foo bar: baz qux\r\n\r\n") + with self.assertRaises(ValueError): + next(self.parse_headers()) + + def test_parse_invalid_value(self): + self.reader.feed_data(b"foo: \x00\x00\x0f\r\n\r\n") + with self.assertRaises(ValueError): + next(self.parse_headers()) + + def test_parse_too_long_value(self): + self.reader.feed_data(b"foo: bar\r\n" * 129 + b"\r\n") + with self.assertRaises(SecurityError): + next(self.parse_headers()) + + def test_parse_too_long_line(self): + # Header line contains 5 + 8186 + 2 = 8193 bytes. + self.reader.feed_data(b"foo: " + b"a" * 8186 + b"\r\n\r\n") + with self.assertRaises(SecurityError): + next(self.parse_headers()) + + def test_parse_invalid_line_ending(self): + self.reader.feed_data(b"foo: bar\n\n") + with self.assertRaises(EOFError): + next(self.parse_headers()) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_imports.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_imports.py new file mode 100644 index 0000000000000..b69ed931626e9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_imports.py @@ -0,0 +1,64 @@ +import types +import unittest +import warnings + +from websockets.imports import * + + +foo = object() + +bar = object() + + +class ImportsTests(unittest.TestCase): + def setUp(self): + self.mod = types.ModuleType("tests.test_imports.test_alias") + self.mod.__package__ = self.mod.__name__ + + def test_get_alias(self): + lazy_import( + vars(self.mod), + aliases={"foo": "...test_imports"}, + ) + + self.assertEqual(self.mod.foo, foo) + + def test_get_deprecated_alias(self): + lazy_import( + vars(self.mod), + deprecated_aliases={"bar": "...test_imports"}, + ) + + with warnings.catch_warnings(record=True) as recorded_warnings: + warnings.simplefilter("always") + self.assertEqual(self.mod.bar, bar) + + self.assertEqual(len(recorded_warnings), 1) + warning = recorded_warnings[0].message + self.assertEqual( + str(warning), "tests.test_imports.test_alias.bar is deprecated" + ) + self.assertEqual(type(warning), DeprecationWarning) + + def test_dir(self): + lazy_import( + vars(self.mod), + aliases={"foo": "...test_imports"}, + deprecated_aliases={"bar": "...test_imports"}, + ) + + self.assertEqual( + [item for item in dir(self.mod) if not item[:2] == item[-2:] == "__"], + ["bar", "foo"], + ) + + def test_attribute_error(self): + lazy_import(vars(self.mod)) + + with self.assertRaises(AttributeError) as raised: + self.mod.foo + + self.assertEqual( + str(raised.exception), + "module 'tests.test_imports.test_alias' has no attribute 'foo'", + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_localhost.cnf b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_localhost.cnf new file mode 100644 index 0000000000000..4069e39670c98 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_localhost.cnf @@ -0,0 +1,27 @@ +[ 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 +DNS.2 = overridden +IP.3 = 127.0.0.1 +IP.4 = ::1 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_localhost.pem b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_localhost.pem new file mode 100644 index 0000000000000..8df63ec8f4b3a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_localhost.pem @@ -0,0 +1,48 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDYOOQyq8yYtn5x +K3yRborFxTFse16JIVb4x/ZhZgGm49eARCi09fmczQxJdQpHz81Ij6z0xi7AUYH7 +9wS8T0Lh3uGFDDS1GzITUVPIqSUi0xim2T6XPzXFVQYI1D/OjUxlHm+3/up+WwbL +sBgBO/lDmzoa3ZN7kt9HQoGc/14oQz1Qsv1QTDQs69r+o7mmBJr/hf/g7S0Csyy3 +iC6aaq+yCUyzDbjXceTI7WJqbTGNnK0/DjdFD/SJS/uSDNEg0AH53eqcCSjm+Ei/ +UF8qR5Pu4sSsNwToOW2MVgjtHFazc+kG3rzD6+3Dp+t6x6uI/npyuudOMCmOtd6z +kX0UPQaNAgMBAAECggEAS4eMBztGC+5rusKTEAZKSY15l0h9HG/d/qdzJFDKsO6T +/8VPZu8pk6F48kwFHFK1hexSYWq9OAcA3fBK4jDZzybZJm2+F6l5U5AsMUMMqt6M +lPP8Tj8RXG433muuIkvvbL82DVLpvNu1Qv+vUvcNOpWFtY7DDv6eKjlMJ3h4/pzh +89MNt26VMCYOlq1NSjuZBzFohL2u9nsFehlOpcVsqNfNfcYCq9+5yoH8fWJP90Op +hqhvqUoGLN7DRKV1f+AWHSA4nmGgvVviV5PQgMhtk5exlN7kG+rDc3LbzhefS1Sp +Tat1qIgm8fK2n+Q/obQPjHOGOGuvE5cIF7E275ZKgQKBgQDt87BqALKWnbkbQnb7 +GS1h6LRcKyZhFbxnO2qbviBWSo15LEF8jPGV33Dj+T56hqufa/rUkbZiUbIR9yOX +dnOwpAVTo+ObAwZfGfHvrnufiIbHFqJBumaYLqjRZ7AC0QtS3G+kjS9dbllrr7ok +fO4JdfKRXzBJKrkQdCn8hR22rQKBgQDon0b49Dxs1EfdSDbDode2TSwE83fI3vmR +SKUkNY8ma6CRbomVRWijhBM458wJeuhpjPZOvjNMsnDzGwrtdAp2VfFlMIDnA8ZC +fEWIAAH2QYKXKGmkoXOcWB2QbvbI154zCm6zFGtzvRKOCGmTXuhFajO8VPwOyJVt +aSJA3bLrYQKBgQDJM2/tAfAAKRdW9GlUwqI8Ep9G+/l0yANJqtTnIemH7XwYhJJO +9YJlPszfB2aMBgliQNSUHy1/jyKpzDYdITyLlPUoFwEilnkxuud2yiuf5rpH51yF +hU6wyWtXvXv3tbkEdH42PmdZcjBMPQeBSN2hxEi6ISncBDL9tau26PwJ9QKBgQCs +cNYl2reoXTzgtpWSNDk6NL769JjJWTFcF6QD0YhKjOI8rNpkw00sWc3+EybXqDr9 +c7dq6+gPZQAB1vwkxi6zRkZqIqiLl+qygnjwtkC+EhYCg7y8g8q2DUPtO7TJcb0e +TQ9+xRZad8B3dZj93A8G1hF//OfU9bB/qL3xo+bsQQKBgC/9YJvgLIWA/UziLcB2 +29Ai0nbPkN5df7z4PifUHHSlbQJHKak8UKbMP+8S064Ul0F7g8UCjZMk2LzSbaNY +XU5+2j0sIOnGUFoSlvcpdowzYrD2LN5PkKBot7AOq/v7HlcOoR8J8RGWAMpCrHsI +a/u/dlZs+/K16RcavQwx8rag +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDWTCCAkGgAwIBAgIJAOL9UKiOOxupMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV +BAYTAkZSMQ4wDAYDVQQHDAVQYXJpczEZMBcGA1UECgwQQXltZXJpYyBBdWd1c3Rp +bjESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTIyMTAxNTE5Mjg0MVoYDzIwNjQxMDE0 +MTkyODQxWjBMMQswCQYDVQQGEwJGUjEOMAwGA1UEBwwFUGFyaXMxGTAXBgNVBAoM +EEF5bWVyaWMgQXVndXN0aW4xEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANg45DKrzJi2fnErfJFuisXFMWx7XokhVvjH +9mFmAabj14BEKLT1+ZzNDEl1CkfPzUiPrPTGLsBRgfv3BLxPQuHe4YUMNLUbMhNR +U8ipJSLTGKbZPpc/NcVVBgjUP86NTGUeb7f+6n5bBsuwGAE7+UObOhrdk3uS30dC +gZz/XihDPVCy/VBMNCzr2v6juaYEmv+F/+DtLQKzLLeILppqr7IJTLMNuNdx5Mjt +YmptMY2crT8ON0UP9IlL+5IM0SDQAfnd6pwJKOb4SL9QXypHk+7ixKw3BOg5bYxW +CO0cVrNz6QbevMPr7cOn63rHq4j+enK6504wKY613rORfRQ9Bo0CAwEAAaM8MDow +OAYDVR0RBDEwL4IJbG9jYWxob3N0ggpvdmVycmlkZGVuhwR/AAABhxAAAAAAAAAA +AAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQBPNDGDdl4wsCRlDuyCHBC8o+vW +Vb14thUw9Z6UrlsQRXLONxHOXbNAj1sYQACNwIWuNz36HXu5m8Xw/ID/bOhnIg+b +Y6l/JU/kZQYB7SV1aR3ZdbCK0gjfkE0POBHuKOjUFIOPBCtJ4tIBUX94zlgJrR9v +2rqJC3TIYrR7pVQumHZsI5GZEMpM5NxfreWwxcgltgxmGdm7elcizHfz7k5+szwh +4eZ/rxK9bw1q8BIvVBWelRvUR55mIrCjzfZp5ZObSYQTZlW7PzXBe5Jk+1w31YHM +RSBA2EpPhYlGNqPidi7bg7rnQcsc6+hE0OqzTL/hWxPm9Vbp9dj3HFTik1wa +-----END CERTIFICATE----- diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_protocol.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_protocol.py new file mode 100644 index 0000000000000..a64172b539801 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_protocol.py @@ -0,0 +1,1790 @@ +import logging +import unittest.mock + +from websockets.exceptions import ( + ConnectionClosedError, + ConnectionClosedOK, + InvalidState, + PayloadTooBig, + ProtocolError, +) +from websockets.frames import ( + OP_BINARY, + OP_CLOSE, + OP_CONT, + OP_PING, + OP_PONG, + OP_TEXT, + Close, + CloseCode, + Frame, +) +from websockets.protocol import * +from websockets.protocol import CLIENT, CLOSED, CLOSING, SERVER + +from .extensions.utils import Rsv2Extension +from .test_frames import FramesTestCase + + +class ProtocolTestCase(FramesTestCase): + def assertFrameSent(self, connection, frame, eof=False): + """ + Outgoing data for ``connection`` contains the given frame. + + ``frame`` may be ``None`` if no frame is expected. + + When ``eof`` is ``True``, the end of the stream is also expected. + + """ + frames_sent = [ + None + if write is SEND_EOF + else self.parse( + write, + mask=connection.side is CLIENT, + extensions=connection.extensions, + ) + for write in connection.data_to_send() + ] + frames_expected = [] if frame is None else [frame] + if eof: + frames_expected += [None] + self.assertEqual(frames_sent, frames_expected) + + def assertFrameReceived(self, connection, frame): + """ + Incoming data for ``connection`` contains the given frame. + + ``frame`` may be ``None`` if no frame is expected. + + """ + frames_received = connection.events_received() + frames_expected = [] if frame is None else [frame] + self.assertEqual(frames_received, frames_expected) + + def assertConnectionClosing(self, connection, code=None, reason=""): + """ + Incoming data caused the "Start the WebSocket Closing Handshake" process. + + """ + close_frame = Frame( + OP_CLOSE, + b"" if code is None else Close(code, reason).serialize(), + ) + # A close frame was received. + self.assertFrameReceived(connection, close_frame) + # A close frame and possibly the end of stream were sent. + self.assertFrameSent(connection, close_frame, eof=connection.side is SERVER) + + def assertConnectionFailing(self, connection, code=None, reason=""): + """ + Incoming data caused the "Fail the WebSocket Connection" process. + + """ + close_frame = Frame( + OP_CLOSE, + b"" if code is None else Close(code, reason).serialize(), + ) + # No frame was received. + self.assertFrameReceived(connection, None) + # A close frame and possibly the end of stream were sent. + self.assertFrameSent(connection, close_frame, eof=connection.side is SERVER) + + +class MaskingTests(ProtocolTestCase): + """ + Test frame masking. + + 5.1. Overview + + """ + + unmasked_text_frame_date = b"\x81\x04Spam" + masked_text_frame_data = b"\x81\x84\x00\xff\x00\xff\x53\x8f\x61\x92" + + def test_client_sends_masked_frame(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x00\xff\x00\xff"): + client.send_text(b"Spam", True) + self.assertEqual(client.data_to_send(), [self.masked_text_frame_data]) + + def test_server_sends_unmasked_frame(self): + server = Protocol(SERVER) + server.send_text(b"Spam", True) + self.assertEqual(server.data_to_send(), [self.unmasked_text_frame_date]) + + def test_client_receives_unmasked_frame(self): + client = Protocol(CLIENT) + client.receive_data(self.unmasked_text_frame_date) + self.assertFrameReceived( + client, + Frame(OP_TEXT, b"Spam"), + ) + + def test_server_receives_masked_frame(self): + server = Protocol(SERVER) + server.receive_data(self.masked_text_frame_data) + self.assertFrameReceived( + server, + Frame(OP_TEXT, b"Spam"), + ) + + def test_client_receives_masked_frame(self): + client = Protocol(CLIENT) + client.receive_data(self.masked_text_frame_data) + self.assertIsInstance(client.parser_exc, ProtocolError) + self.assertEqual(str(client.parser_exc), "incorrect masking") + self.assertConnectionFailing( + client, CloseCode.PROTOCOL_ERROR, "incorrect masking" + ) + + def test_server_receives_unmasked_frame(self): + server = Protocol(SERVER) + server.receive_data(self.unmasked_text_frame_date) + self.assertIsInstance(server.parser_exc, ProtocolError) + self.assertEqual(str(server.parser_exc), "incorrect masking") + self.assertConnectionFailing( + server, CloseCode.PROTOCOL_ERROR, "incorrect masking" + ) + + +class ContinuationTests(ProtocolTestCase): + """ + Test continuation frames without text or binary frames. + + """ + + def test_client_sends_unexpected_continuation(self): + client = Protocol(CLIENT) + with self.assertRaises(ProtocolError) as raised: + client.send_continuation(b"", fin=False) + self.assertEqual(str(raised.exception), "unexpected continuation frame") + + def test_server_sends_unexpected_continuation(self): + server = Protocol(SERVER) + with self.assertRaises(ProtocolError) as raised: + server.send_continuation(b"", fin=False) + self.assertEqual(str(raised.exception), "unexpected continuation frame") + + def test_client_receives_unexpected_continuation(self): + client = Protocol(CLIENT) + client.receive_data(b"\x00\x00") + self.assertIsInstance(client.parser_exc, ProtocolError) + self.assertEqual(str(client.parser_exc), "unexpected continuation frame") + self.assertConnectionFailing( + client, CloseCode.PROTOCOL_ERROR, "unexpected continuation frame" + ) + + def test_server_receives_unexpected_continuation(self): + server = Protocol(SERVER) + server.receive_data(b"\x00\x80\x00\x00\x00\x00") + self.assertIsInstance(server.parser_exc, ProtocolError) + self.assertEqual(str(server.parser_exc), "unexpected continuation frame") + self.assertConnectionFailing( + server, CloseCode.PROTOCOL_ERROR, "unexpected continuation frame" + ) + + def test_client_sends_continuation_after_sending_close(self): + client = Protocol(CLIENT) + # Since it isn't possible to send a close frame in a fragmented + # message (see test_client_send_close_in_fragmented_message), in fact, + # this is the same test as test_client_sends_unexpected_continuation. + with self.enforce_mask(b"\x00\x00\x00\x00"): + client.send_close(CloseCode.GOING_AWAY) + self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"]) + with self.assertRaises(ProtocolError) as raised: + client.send_continuation(b"", fin=False) + self.assertEqual(str(raised.exception), "unexpected continuation frame") + + def test_server_sends_continuation_after_sending_close(self): + # Since it isn't possible to send a close frame in a fragmented + # message (see test_server_send_close_in_fragmented_message), in fact, + # this is the same test as test_server_sends_unexpected_continuation. + server = Protocol(SERVER) + server.send_close(CloseCode.NORMAL_CLOSURE) + self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"]) + with self.assertRaises(ProtocolError) as raised: + server.send_continuation(b"", fin=False) + self.assertEqual(str(raised.exception), "unexpected continuation frame") + + def test_client_receives_continuation_after_receiving_close(self): + client = Protocol(CLIENT) + client.receive_data(b"\x88\x02\x03\xe8") + self.assertConnectionClosing(client, CloseCode.NORMAL_CLOSURE) + client.receive_data(b"\x00\x00") + self.assertFrameReceived(client, None) + self.assertFrameSent(client, None) + + def test_server_receives_continuation_after_receiving_close(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9") + self.assertConnectionClosing(server, CloseCode.GOING_AWAY) + server.receive_data(b"\x00\x80\x00\xff\x00\xff") + self.assertFrameReceived(server, None) + self.assertFrameSent(server, None) + + +class TextTests(ProtocolTestCase): + """ + Test text frames and continuation frames. + + """ + + def test_client_sends_text(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x00\x00\x00\x00"): + client.send_text("😀".encode()) + self.assertEqual( + client.data_to_send(), [b"\x81\x84\x00\x00\x00\x00\xf0\x9f\x98\x80"] + ) + + def test_server_sends_text(self): + server = Protocol(SERVER) + server.send_text("😀".encode()) + self.assertEqual(server.data_to_send(), [b"\x81\x04\xf0\x9f\x98\x80"]) + + def test_client_receives_text(self): + client = Protocol(CLIENT) + client.receive_data(b"\x81\x04\xf0\x9f\x98\x80") + self.assertFrameReceived( + client, + Frame(OP_TEXT, "😀".encode()), + ) + + def test_server_receives_text(self): + server = Protocol(SERVER) + server.receive_data(b"\x81\x84\x00\x00\x00\x00\xf0\x9f\x98\x80") + self.assertFrameReceived( + server, + Frame(OP_TEXT, "😀".encode()), + ) + + def test_client_receives_text_over_size_limit(self): + client = Protocol(CLIENT, max_size=3) + client.receive_data(b"\x81\x04\xf0\x9f\x98\x80") + self.assertIsInstance(client.parser_exc, PayloadTooBig) + self.assertEqual(str(client.parser_exc), "over size limit (4 > 3 bytes)") + self.assertConnectionFailing( + client, CloseCode.MESSAGE_TOO_BIG, "over size limit (4 > 3 bytes)" + ) + + def test_server_receives_text_over_size_limit(self): + server = Protocol(SERVER, max_size=3) + server.receive_data(b"\x81\x84\x00\x00\x00\x00\xf0\x9f\x98\x80") + self.assertIsInstance(server.parser_exc, PayloadTooBig) + self.assertEqual(str(server.parser_exc), "over size limit (4 > 3 bytes)") + self.assertConnectionFailing( + server, CloseCode.MESSAGE_TOO_BIG, "over size limit (4 > 3 bytes)" + ) + + def test_client_receives_text_without_size_limit(self): + client = Protocol(CLIENT, max_size=None) + client.receive_data(b"\x81\x04\xf0\x9f\x98\x80") + self.assertFrameReceived( + client, + Frame(OP_TEXT, "😀".encode()), + ) + + def test_server_receives_text_without_size_limit(self): + server = Protocol(SERVER, max_size=None) + server.receive_data(b"\x81\x84\x00\x00\x00\x00\xf0\x9f\x98\x80") + self.assertFrameReceived( + server, + Frame(OP_TEXT, "😀".encode()), + ) + + def test_client_sends_fragmented_text(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x00\x00\x00\x00"): + client.send_text("😀".encode()[:2], fin=False) + self.assertEqual(client.data_to_send(), [b"\x01\x82\x00\x00\x00\x00\xf0\x9f"]) + with self.enforce_mask(b"\x00\x00\x00\x00"): + client.send_continuation("😀😀".encode()[2:6], fin=False) + self.assertEqual( + client.data_to_send(), [b"\x00\x84\x00\x00\x00\x00\x98\x80\xf0\x9f"] + ) + with self.enforce_mask(b"\x00\x00\x00\x00"): + client.send_continuation("😀".encode()[2:], fin=True) + self.assertEqual(client.data_to_send(), [b"\x80\x82\x00\x00\x00\x00\x98\x80"]) + + def test_server_sends_fragmented_text(self): + server = Protocol(SERVER) + server.send_text("😀".encode()[:2], fin=False) + self.assertEqual(server.data_to_send(), [b"\x01\x02\xf0\x9f"]) + server.send_continuation("😀😀".encode()[2:6], fin=False) + self.assertEqual(server.data_to_send(), [b"\x00\x04\x98\x80\xf0\x9f"]) + server.send_continuation("😀".encode()[2:], fin=True) + self.assertEqual(server.data_to_send(), [b"\x80\x02\x98\x80"]) + + def test_client_receives_fragmented_text(self): + client = Protocol(CLIENT) + client.receive_data(b"\x01\x02\xf0\x9f") + self.assertFrameReceived( + client, + Frame(OP_TEXT, "😀".encode()[:2], fin=False), + ) + client.receive_data(b"\x00\x04\x98\x80\xf0\x9f") + self.assertFrameReceived( + client, + Frame(OP_CONT, "😀😀".encode()[2:6], fin=False), + ) + client.receive_data(b"\x80\x02\x98\x80") + self.assertFrameReceived( + client, + Frame(OP_CONT, "😀".encode()[2:]), + ) + + def test_server_receives_fragmented_text(self): + server = Protocol(SERVER) + server.receive_data(b"\x01\x82\x00\x00\x00\x00\xf0\x9f") + self.assertFrameReceived( + server, + Frame(OP_TEXT, "😀".encode()[:2], fin=False), + ) + server.receive_data(b"\x00\x84\x00\x00\x00\x00\x98\x80\xf0\x9f") + self.assertFrameReceived( + server, + Frame(OP_CONT, "😀😀".encode()[2:6], fin=False), + ) + server.receive_data(b"\x80\x82\x00\x00\x00\x00\x98\x80") + self.assertFrameReceived( + server, + Frame(OP_CONT, "😀".encode()[2:]), + ) + + def test_client_receives_fragmented_text_over_size_limit(self): + client = Protocol(CLIENT, max_size=3) + client.receive_data(b"\x01\x02\xf0\x9f") + self.assertFrameReceived( + client, + Frame(OP_TEXT, "😀".encode()[:2], fin=False), + ) + client.receive_data(b"\x80\x02\x98\x80") + self.assertIsInstance(client.parser_exc, PayloadTooBig) + self.assertEqual(str(client.parser_exc), "over size limit (2 > 1 bytes)") + self.assertConnectionFailing( + client, CloseCode.MESSAGE_TOO_BIG, "over size limit (2 > 1 bytes)" + ) + + def test_server_receives_fragmented_text_over_size_limit(self): + server = Protocol(SERVER, max_size=3) + server.receive_data(b"\x01\x82\x00\x00\x00\x00\xf0\x9f") + self.assertFrameReceived( + server, + Frame(OP_TEXT, "😀".encode()[:2], fin=False), + ) + server.receive_data(b"\x80\x82\x00\x00\x00\x00\x98\x80") + self.assertIsInstance(server.parser_exc, PayloadTooBig) + self.assertEqual(str(server.parser_exc), "over size limit (2 > 1 bytes)") + self.assertConnectionFailing( + server, CloseCode.MESSAGE_TOO_BIG, "over size limit (2 > 1 bytes)" + ) + + def test_client_receives_fragmented_text_without_size_limit(self): + client = Protocol(CLIENT, max_size=None) + client.receive_data(b"\x01\x02\xf0\x9f") + self.assertFrameReceived( + client, + Frame(OP_TEXT, "😀".encode()[:2], fin=False), + ) + client.receive_data(b"\x00\x04\x98\x80\xf0\x9f") + self.assertFrameReceived( + client, + Frame(OP_CONT, "😀😀".encode()[2:6], fin=False), + ) + client.receive_data(b"\x80\x02\x98\x80") + self.assertFrameReceived( + client, + Frame(OP_CONT, "😀".encode()[2:]), + ) + + def test_server_receives_fragmented_text_without_size_limit(self): + server = Protocol(SERVER, max_size=None) + server.receive_data(b"\x01\x82\x00\x00\x00\x00\xf0\x9f") + self.assertFrameReceived( + server, + Frame(OP_TEXT, "😀".encode()[:2], fin=False), + ) + server.receive_data(b"\x00\x84\x00\x00\x00\x00\x98\x80\xf0\x9f") + self.assertFrameReceived( + server, + Frame(OP_CONT, "😀😀".encode()[2:6], fin=False), + ) + server.receive_data(b"\x80\x82\x00\x00\x00\x00\x98\x80") + self.assertFrameReceived( + server, + Frame(OP_CONT, "😀".encode()[2:]), + ) + + def test_client_sends_unexpected_text(self): + client = Protocol(CLIENT) + client.send_text(b"", fin=False) + with self.assertRaises(ProtocolError) as raised: + client.send_text(b"", fin=False) + self.assertEqual(str(raised.exception), "expected a continuation frame") + + def test_server_sends_unexpected_text(self): + server = Protocol(SERVER) + server.send_text(b"", fin=False) + with self.assertRaises(ProtocolError) as raised: + server.send_text(b"", fin=False) + self.assertEqual(str(raised.exception), "expected a continuation frame") + + def test_client_receives_unexpected_text(self): + client = Protocol(CLIENT) + client.receive_data(b"\x01\x00") + self.assertFrameReceived( + client, + Frame(OP_TEXT, b"", fin=False), + ) + client.receive_data(b"\x01\x00") + self.assertIsInstance(client.parser_exc, ProtocolError) + self.assertEqual(str(client.parser_exc), "expected a continuation frame") + self.assertConnectionFailing( + client, CloseCode.PROTOCOL_ERROR, "expected a continuation frame" + ) + + def test_server_receives_unexpected_text(self): + server = Protocol(SERVER) + server.receive_data(b"\x01\x80\x00\x00\x00\x00") + self.assertFrameReceived( + server, + Frame(OP_TEXT, b"", fin=False), + ) + server.receive_data(b"\x01\x80\x00\x00\x00\x00") + self.assertIsInstance(server.parser_exc, ProtocolError) + self.assertEqual(str(server.parser_exc), "expected a continuation frame") + self.assertConnectionFailing( + server, CloseCode.PROTOCOL_ERROR, "expected a continuation frame" + ) + + def test_client_sends_text_after_sending_close(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x00\x00\x00\x00"): + client.send_close(CloseCode.GOING_AWAY) + self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"]) + with self.assertRaises(InvalidState): + client.send_text(b"") + + def test_server_sends_text_after_sending_close(self): + server = Protocol(SERVER) + server.send_close(CloseCode.NORMAL_CLOSURE) + self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"]) + with self.assertRaises(InvalidState): + server.send_text(b"") + + def test_client_receives_text_after_receiving_close(self): + client = Protocol(CLIENT) + client.receive_data(b"\x88\x02\x03\xe8") + self.assertConnectionClosing(client, CloseCode.NORMAL_CLOSURE) + client.receive_data(b"\x81\x00") + self.assertFrameReceived(client, None) + self.assertFrameSent(client, None) + + def test_server_receives_text_after_receiving_close(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9") + self.assertConnectionClosing(server, CloseCode.GOING_AWAY) + server.receive_data(b"\x81\x80\x00\xff\x00\xff") + self.assertFrameReceived(server, None) + self.assertFrameSent(server, None) + + +class BinaryTests(ProtocolTestCase): + """ + Test binary frames and continuation frames. + + """ + + def test_client_sends_binary(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x00\x00\x00\x00"): + client.send_binary(b"\x01\x02\xfe\xff") + self.assertEqual( + client.data_to_send(), [b"\x82\x84\x00\x00\x00\x00\x01\x02\xfe\xff"] + ) + + def test_server_sends_binary(self): + server = Protocol(SERVER) + server.send_binary(b"\x01\x02\xfe\xff") + self.assertEqual(server.data_to_send(), [b"\x82\x04\x01\x02\xfe\xff"]) + + def test_client_receives_binary(self): + client = Protocol(CLIENT) + client.receive_data(b"\x82\x04\x01\x02\xfe\xff") + self.assertFrameReceived( + client, + Frame(OP_BINARY, b"\x01\x02\xfe\xff"), + ) + + def test_server_receives_binary(self): + server = Protocol(SERVER) + server.receive_data(b"\x82\x84\x00\x00\x00\x00\x01\x02\xfe\xff") + self.assertFrameReceived( + server, + Frame(OP_BINARY, b"\x01\x02\xfe\xff"), + ) + + def test_client_receives_binary_over_size_limit(self): + client = Protocol(CLIENT, max_size=3) + client.receive_data(b"\x82\x04\x01\x02\xfe\xff") + self.assertIsInstance(client.parser_exc, PayloadTooBig) + self.assertEqual(str(client.parser_exc), "over size limit (4 > 3 bytes)") + self.assertConnectionFailing( + client, CloseCode.MESSAGE_TOO_BIG, "over size limit (4 > 3 bytes)" + ) + + def test_server_receives_binary_over_size_limit(self): + server = Protocol(SERVER, max_size=3) + server.receive_data(b"\x82\x84\x00\x00\x00\x00\x01\x02\xfe\xff") + self.assertIsInstance(server.parser_exc, PayloadTooBig) + self.assertEqual(str(server.parser_exc), "over size limit (4 > 3 bytes)") + self.assertConnectionFailing( + server, CloseCode.MESSAGE_TOO_BIG, "over size limit (4 > 3 bytes)" + ) + + def test_client_sends_fragmented_binary(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x00\x00\x00\x00"): + client.send_binary(b"\x01\x02", fin=False) + self.assertEqual(client.data_to_send(), [b"\x02\x82\x00\x00\x00\x00\x01\x02"]) + with self.enforce_mask(b"\x00\x00\x00\x00"): + client.send_continuation(b"\xee\xff\x01\x02", fin=False) + self.assertEqual( + client.data_to_send(), [b"\x00\x84\x00\x00\x00\x00\xee\xff\x01\x02"] + ) + with self.enforce_mask(b"\x00\x00\x00\x00"): + client.send_continuation(b"\xee\xff", fin=True) + self.assertEqual(client.data_to_send(), [b"\x80\x82\x00\x00\x00\x00\xee\xff"]) + + def test_server_sends_fragmented_binary(self): + server = Protocol(SERVER) + server.send_binary(b"\x01\x02", fin=False) + self.assertEqual(server.data_to_send(), [b"\x02\x02\x01\x02"]) + server.send_continuation(b"\xee\xff\x01\x02", fin=False) + self.assertEqual(server.data_to_send(), [b"\x00\x04\xee\xff\x01\x02"]) + server.send_continuation(b"\xee\xff", fin=True) + self.assertEqual(server.data_to_send(), [b"\x80\x02\xee\xff"]) + + def test_client_receives_fragmented_binary(self): + client = Protocol(CLIENT) + client.receive_data(b"\x02\x02\x01\x02") + self.assertFrameReceived( + client, + Frame(OP_BINARY, b"\x01\x02", fin=False), + ) + client.receive_data(b"\x00\x04\xfe\xff\x01\x02") + self.assertFrameReceived( + client, + Frame(OP_CONT, b"\xfe\xff\x01\x02", fin=False), + ) + client.receive_data(b"\x80\x02\xfe\xff") + self.assertFrameReceived( + client, + Frame(OP_CONT, b"\xfe\xff"), + ) + + def test_server_receives_fragmented_binary(self): + server = Protocol(SERVER) + server.receive_data(b"\x02\x82\x00\x00\x00\x00\x01\x02") + self.assertFrameReceived( + server, + Frame(OP_BINARY, b"\x01\x02", fin=False), + ) + server.receive_data(b"\x00\x84\x00\x00\x00\x00\xee\xff\x01\x02") + self.assertFrameReceived( + server, + Frame(OP_CONT, b"\xee\xff\x01\x02", fin=False), + ) + server.receive_data(b"\x80\x82\x00\x00\x00\x00\xfe\xff") + self.assertFrameReceived( + server, + Frame(OP_CONT, b"\xfe\xff"), + ) + + def test_client_receives_fragmented_binary_over_size_limit(self): + client = Protocol(CLIENT, max_size=3) + client.receive_data(b"\x02\x02\x01\x02") + self.assertFrameReceived( + client, + Frame(OP_BINARY, b"\x01\x02", fin=False), + ) + client.receive_data(b"\x80\x02\xfe\xff") + self.assertIsInstance(client.parser_exc, PayloadTooBig) + self.assertEqual(str(client.parser_exc), "over size limit (2 > 1 bytes)") + self.assertConnectionFailing( + client, CloseCode.MESSAGE_TOO_BIG, "over size limit (2 > 1 bytes)" + ) + + def test_server_receives_fragmented_binary_over_size_limit(self): + server = Protocol(SERVER, max_size=3) + server.receive_data(b"\x02\x82\x00\x00\x00\x00\x01\x02") + self.assertFrameReceived( + server, + Frame(OP_BINARY, b"\x01\x02", fin=False), + ) + server.receive_data(b"\x80\x82\x00\x00\x00\x00\xfe\xff") + self.assertIsInstance(server.parser_exc, PayloadTooBig) + self.assertEqual(str(server.parser_exc), "over size limit (2 > 1 bytes)") + self.assertConnectionFailing( + server, CloseCode.MESSAGE_TOO_BIG, "over size limit (2 > 1 bytes)" + ) + + def test_client_sends_unexpected_binary(self): + client = Protocol(CLIENT) + client.send_binary(b"", fin=False) + with self.assertRaises(ProtocolError) as raised: + client.send_binary(b"", fin=False) + self.assertEqual(str(raised.exception), "expected a continuation frame") + + def test_server_sends_unexpected_binary(self): + server = Protocol(SERVER) + server.send_binary(b"", fin=False) + with self.assertRaises(ProtocolError) as raised: + server.send_binary(b"", fin=False) + self.assertEqual(str(raised.exception), "expected a continuation frame") + + def test_client_receives_unexpected_binary(self): + client = Protocol(CLIENT) + client.receive_data(b"\x02\x00") + self.assertFrameReceived( + client, + Frame(OP_BINARY, b"", fin=False), + ) + client.receive_data(b"\x02\x00") + self.assertIsInstance(client.parser_exc, ProtocolError) + self.assertEqual(str(client.parser_exc), "expected a continuation frame") + self.assertConnectionFailing( + client, CloseCode.PROTOCOL_ERROR, "expected a continuation frame" + ) + + def test_server_receives_unexpected_binary(self): + server = Protocol(SERVER) + server.receive_data(b"\x02\x80\x00\x00\x00\x00") + self.assertFrameReceived( + server, + Frame(OP_BINARY, b"", fin=False), + ) + server.receive_data(b"\x02\x80\x00\x00\x00\x00") + self.assertIsInstance(server.parser_exc, ProtocolError) + self.assertEqual(str(server.parser_exc), "expected a continuation frame") + self.assertConnectionFailing( + server, CloseCode.PROTOCOL_ERROR, "expected a continuation frame" + ) + + def test_client_sends_binary_after_sending_close(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x00\x00\x00\x00"): + client.send_close(CloseCode.GOING_AWAY) + self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"]) + with self.assertRaises(InvalidState): + client.send_binary(b"") + + def test_server_sends_binary_after_sending_close(self): + server = Protocol(SERVER) + server.send_close(CloseCode.NORMAL_CLOSURE) + self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"]) + with self.assertRaises(InvalidState): + server.send_binary(b"") + + def test_client_receives_binary_after_receiving_close(self): + client = Protocol(CLIENT) + client.receive_data(b"\x88\x02\x03\xe8") + self.assertConnectionClosing(client, CloseCode.NORMAL_CLOSURE) + client.receive_data(b"\x82\x00") + self.assertFrameReceived(client, None) + self.assertFrameSent(client, None) + + def test_server_receives_binary_after_receiving_close(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9") + self.assertConnectionClosing(server, CloseCode.GOING_AWAY) + server.receive_data(b"\x82\x80\x00\xff\x00\xff") + self.assertFrameReceived(server, None) + self.assertFrameSent(server, None) + + +class CloseTests(ProtocolTestCase): + """ + Test close frames. + + See RFC 6544: + + 5.5.1. Close + 7.1.6. The WebSocket Connection Close Reason + 7.1.7. Fail the WebSocket Connection + + """ + + def test_close_code(self): + client = Protocol(CLIENT) + client.receive_data(b"\x88\x04\x03\xe8OK") + client.receive_eof() + self.assertEqual(client.close_code, CloseCode.NORMAL_CLOSURE) + + def test_close_reason(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x84\x00\x00\x00\x00\x03\xe8OK") + server.receive_eof() + self.assertEqual(server.close_reason, "OK") + + def test_close_code_not_provided(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x80\x00\x00\x00\x00") + server.receive_eof() + self.assertEqual(server.close_code, CloseCode.NO_STATUS_RCVD) + + def test_close_reason_not_provided(self): + client = Protocol(CLIENT) + client.receive_data(b"\x88\x00") + client.receive_eof() + self.assertEqual(client.close_reason, "") + + def test_close_code_not_available(self): + client = Protocol(CLIENT) + client.receive_eof() + self.assertEqual(client.close_code, CloseCode.ABNORMAL_CLOSURE) + + def test_close_reason_not_available(self): + server = Protocol(SERVER) + server.receive_eof() + self.assertEqual(server.close_reason, "") + + def test_close_code_not_available_yet(self): + server = Protocol(SERVER) + self.assertIsNone(server.close_code) + + def test_close_reason_not_available_yet(self): + client = Protocol(CLIENT) + self.assertIsNone(client.close_reason) + + def test_client_sends_close(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x3c\x3c\x3c\x3c"): + client.send_close() + self.assertEqual(client.data_to_send(), [b"\x88\x80\x3c\x3c\x3c\x3c"]) + self.assertIs(client.state, CLOSING) + + def test_server_sends_close(self): + server = Protocol(SERVER) + server.send_close() + self.assertEqual(server.data_to_send(), [b"\x88\x00"]) + self.assertIs(server.state, CLOSING) + + def test_client_receives_close(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x3c\x3c\x3c\x3c"): + client.receive_data(b"\x88\x00") + self.assertEqual(client.events_received(), [Frame(OP_CLOSE, b"")]) + self.assertEqual(client.data_to_send(), [b"\x88\x80\x3c\x3c\x3c\x3c"]) + self.assertIs(client.state, CLOSING) + + def test_server_receives_close(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c") + self.assertEqual(server.events_received(), [Frame(OP_CLOSE, b"")]) + self.assertEqual(server.data_to_send(), [b"\x88\x00", b""]) + self.assertIs(server.state, CLOSING) + + def test_client_sends_close_then_receives_close(self): + # Client-initiated close handshake on the client side. + client = Protocol(CLIENT) + + client.send_close() + self.assertFrameReceived(client, None) + self.assertFrameSent(client, Frame(OP_CLOSE, b"")) + + client.receive_data(b"\x88\x00") + self.assertFrameReceived(client, Frame(OP_CLOSE, b"")) + self.assertFrameSent(client, None) + + client.receive_eof() + self.assertFrameReceived(client, None) + self.assertFrameSent(client, None, eof=True) + + def test_server_sends_close_then_receives_close(self): + # Server-initiated close handshake on the server side. + server = Protocol(SERVER) + + server.send_close() + self.assertFrameReceived(server, None) + self.assertFrameSent(server, Frame(OP_CLOSE, b"")) + + server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c") + self.assertFrameReceived(server, Frame(OP_CLOSE, b"")) + self.assertFrameSent(server, None, eof=True) + + server.receive_eof() + self.assertFrameReceived(server, None) + self.assertFrameSent(server, None) + + def test_client_receives_close_then_sends_close(self): + # Server-initiated close handshake on the client side. + client = Protocol(CLIENT) + + client.receive_data(b"\x88\x00") + self.assertFrameReceived(client, Frame(OP_CLOSE, b"")) + self.assertFrameSent(client, Frame(OP_CLOSE, b"")) + + client.receive_eof() + self.assertFrameReceived(client, None) + self.assertFrameSent(client, None, eof=True) + + def test_server_receives_close_then_sends_close(self): + # Client-initiated close handshake on the server side. + server = Protocol(SERVER) + + server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c") + self.assertFrameReceived(server, Frame(OP_CLOSE, b"")) + self.assertFrameSent(server, Frame(OP_CLOSE, b""), eof=True) + + server.receive_eof() + self.assertFrameReceived(server, None) + self.assertFrameSent(server, None) + + def test_client_sends_close_with_code(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x00\x00\x00\x00"): + client.send_close(CloseCode.GOING_AWAY) + self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"]) + self.assertIs(client.state, CLOSING) + + def test_server_sends_close_with_code(self): + server = Protocol(SERVER) + server.send_close(CloseCode.NORMAL_CLOSURE) + self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"]) + self.assertIs(server.state, CLOSING) + + def test_client_receives_close_with_code(self): + client = Protocol(CLIENT) + client.receive_data(b"\x88\x02\x03\xe8") + self.assertConnectionClosing(client, CloseCode.NORMAL_CLOSURE, "") + self.assertIs(client.state, CLOSING) + + def test_server_receives_close_with_code(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9") + self.assertConnectionClosing(server, CloseCode.GOING_AWAY, "") + self.assertIs(server.state, CLOSING) + + def test_client_sends_close_with_code_and_reason(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x00\x00\x00\x00"): + client.send_close(CloseCode.GOING_AWAY, "going away") + self.assertEqual( + client.data_to_send(), [b"\x88\x8c\x00\x00\x00\x00\x03\xe9going away"] + ) + self.assertIs(client.state, CLOSING) + + def test_server_sends_close_with_code_and_reason(self): + server = Protocol(SERVER) + server.send_close(CloseCode.NORMAL_CLOSURE, "OK") + self.assertEqual(server.data_to_send(), [b"\x88\x04\x03\xe8OK"]) + self.assertIs(server.state, CLOSING) + + def test_client_receives_close_with_code_and_reason(self): + client = Protocol(CLIENT) + client.receive_data(b"\x88\x04\x03\xe8OK") + self.assertConnectionClosing(client, CloseCode.NORMAL_CLOSURE, "OK") + self.assertIs(client.state, CLOSING) + + def test_server_receives_close_with_code_and_reason(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x8c\x00\x00\x00\x00\x03\xe9going away") + self.assertConnectionClosing(server, CloseCode.GOING_AWAY, "going away") + self.assertIs(server.state, CLOSING) + + def test_client_sends_close_with_reason_only(self): + client = Protocol(CLIENT) + with self.assertRaises(ProtocolError) as raised: + client.send_close(reason="going away") + self.assertEqual(str(raised.exception), "cannot send a reason without a code") + + def test_server_sends_close_with_reason_only(self): + server = Protocol(SERVER) + with self.assertRaises(ProtocolError) as raised: + server.send_close(reason="OK") + self.assertEqual(str(raised.exception), "cannot send a reason without a code") + + def test_client_receives_close_with_truncated_code(self): + client = Protocol(CLIENT) + client.receive_data(b"\x88\x01\x03") + self.assertIsInstance(client.parser_exc, ProtocolError) + self.assertEqual(str(client.parser_exc), "close frame too short") + self.assertConnectionFailing( + client, CloseCode.PROTOCOL_ERROR, "close frame too short" + ) + self.assertIs(client.state, CLOSING) + + def test_server_receives_close_with_truncated_code(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x81\x00\x00\x00\x00\x03") + self.assertIsInstance(server.parser_exc, ProtocolError) + self.assertEqual(str(server.parser_exc), "close frame too short") + self.assertConnectionFailing( + server, CloseCode.PROTOCOL_ERROR, "close frame too short" + ) + self.assertIs(server.state, CLOSING) + + def test_client_receives_close_with_non_utf8_reason(self): + client = Protocol(CLIENT) + + client.receive_data(b"\x88\x04\x03\xe8\xff\xff") + self.assertIsInstance(client.parser_exc, UnicodeDecodeError) + self.assertEqual( + str(client.parser_exc), + "'utf-8' codec can't decode byte 0xff in position 0: invalid start byte", + ) + self.assertConnectionFailing( + client, CloseCode.INVALID_DATA, "invalid start byte at position 0" + ) + self.assertIs(client.state, CLOSING) + + def test_server_receives_close_with_non_utf8_reason(self): + server = Protocol(SERVER) + + server.receive_data(b"\x88\x84\x00\x00\x00\x00\x03\xe9\xff\xff") + self.assertIsInstance(server.parser_exc, UnicodeDecodeError) + self.assertEqual( + str(server.parser_exc), + "'utf-8' codec can't decode byte 0xff in position 0: invalid start byte", + ) + self.assertConnectionFailing( + server, CloseCode.INVALID_DATA, "invalid start byte at position 0" + ) + self.assertIs(server.state, CLOSING) + + +class PingTests(ProtocolTestCase): + """ + Test ping. See 5.5.2. Ping in RFC 6544. + + """ + + def test_client_sends_ping(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x00\x44\x88\xcc"): + client.send_ping(b"") + self.assertEqual(client.data_to_send(), [b"\x89\x80\x00\x44\x88\xcc"]) + + def test_server_sends_ping(self): + server = Protocol(SERVER) + server.send_ping(b"") + self.assertEqual(server.data_to_send(), [b"\x89\x00"]) + + def test_client_receives_ping(self): + client = Protocol(CLIENT) + client.receive_data(b"\x89\x00") + self.assertFrameReceived( + client, + Frame(OP_PING, b""), + ) + self.assertFrameSent( + client, + Frame(OP_PONG, b""), + ) + + def test_server_receives_ping(self): + server = Protocol(SERVER) + server.receive_data(b"\x89\x80\x00\x44\x88\xcc") + self.assertFrameReceived( + server, + Frame(OP_PING, b""), + ) + self.assertFrameSent( + server, + Frame(OP_PONG, b""), + ) + + def test_client_sends_ping_with_data(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x00\x44\x88\xcc"): + client.send_ping(b"\x22\x66\xaa\xee") + self.assertEqual( + client.data_to_send(), [b"\x89\x84\x00\x44\x88\xcc\x22\x22\x22\x22"] + ) + + def test_server_sends_ping_with_data(self): + server = Protocol(SERVER) + server.send_ping(b"\x22\x66\xaa\xee") + self.assertEqual(server.data_to_send(), [b"\x89\x04\x22\x66\xaa\xee"]) + + def test_client_receives_ping_with_data(self): + client = Protocol(CLIENT) + client.receive_data(b"\x89\x04\x22\x66\xaa\xee") + self.assertFrameReceived( + client, + Frame(OP_PING, b"\x22\x66\xaa\xee"), + ) + self.assertFrameSent( + client, + Frame(OP_PONG, b"\x22\x66\xaa\xee"), + ) + + def test_server_receives_ping_with_data(self): + server = Protocol(SERVER) + server.receive_data(b"\x89\x84\x00\x44\x88\xcc\x22\x22\x22\x22") + self.assertFrameReceived( + server, + Frame(OP_PING, b"\x22\x66\xaa\xee"), + ) + self.assertFrameSent( + server, + Frame(OP_PONG, b"\x22\x66\xaa\xee"), + ) + + def test_client_sends_fragmented_ping_frame(self): + client = Protocol(CLIENT) + # This is only possible through a private API. + with self.assertRaises(ProtocolError) as raised: + client.send_frame(Frame(OP_PING, b"", fin=False)) + self.assertEqual(str(raised.exception), "fragmented control frame") + + def test_server_sends_fragmented_ping_frame(self): + server = Protocol(SERVER) + # This is only possible through a private API. + with self.assertRaises(ProtocolError) as raised: + server.send_frame(Frame(OP_PING, b"", fin=False)) + self.assertEqual(str(raised.exception), "fragmented control frame") + + def test_client_receives_fragmented_ping_frame(self): + client = Protocol(CLIENT) + client.receive_data(b"\x09\x00") + self.assertIsInstance(client.parser_exc, ProtocolError) + self.assertEqual(str(client.parser_exc), "fragmented control frame") + self.assertConnectionFailing( + client, CloseCode.PROTOCOL_ERROR, "fragmented control frame" + ) + + def test_server_receives_fragmented_ping_frame(self): + server = Protocol(SERVER) + server.receive_data(b"\x09\x80\x3c\x3c\x3c\x3c") + self.assertIsInstance(server.parser_exc, ProtocolError) + self.assertEqual(str(server.parser_exc), "fragmented control frame") + self.assertConnectionFailing( + server, CloseCode.PROTOCOL_ERROR, "fragmented control frame" + ) + + def test_client_sends_ping_after_sending_close(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x00\x00\x00\x00"): + client.send_close(CloseCode.GOING_AWAY) + self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"]) + # The spec says: "An endpoint MAY send a Ping frame any time (...) + # before the connection is closed" but websockets doesn't support + # sending a Ping frame after a Close frame. + with self.assertRaises(InvalidState) as raised: + client.send_ping(b"") + self.assertEqual( + str(raised.exception), + "cannot write to a WebSocket in the CLOSING state", + ) + + def test_server_sends_ping_after_sending_close(self): + server = Protocol(SERVER) + server.send_close(CloseCode.NORMAL_CLOSURE) + self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"]) + # The spec says: "An endpoint MAY send a Ping frame any time (...) + # before the connection is closed" but websockets doesn't support + # sending a Ping frame after a Close frame. + with self.assertRaises(InvalidState) as raised: + server.send_ping(b"") + self.assertEqual( + str(raised.exception), + "cannot write to a WebSocket in the CLOSING state", + ) + + def test_client_receives_ping_after_receiving_close(self): + client = Protocol(CLIENT) + client.receive_data(b"\x88\x02\x03\xe8") + self.assertConnectionClosing(client, CloseCode.NORMAL_CLOSURE) + client.receive_data(b"\x89\x04\x22\x66\xaa\xee") + self.assertFrameReceived(client, None) + self.assertFrameSent(client, None) + + def test_server_receives_ping_after_receiving_close(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9") + self.assertConnectionClosing(server, CloseCode.GOING_AWAY) + server.receive_data(b"\x89\x84\x00\x44\x88\xcc\x22\x22\x22\x22") + self.assertFrameReceived(server, None) + self.assertFrameSent(server, None) + + +class PongTests(ProtocolTestCase): + """ + Test pong frames. See 5.5.3. Pong in RFC 6544. + + """ + + def test_client_sends_pong(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x00\x44\x88\xcc"): + client.send_pong(b"") + self.assertEqual(client.data_to_send(), [b"\x8a\x80\x00\x44\x88\xcc"]) + + def test_server_sends_pong(self): + server = Protocol(SERVER) + server.send_pong(b"") + self.assertEqual(server.data_to_send(), [b"\x8a\x00"]) + + def test_client_receives_pong(self): + client = Protocol(CLIENT) + client.receive_data(b"\x8a\x00") + self.assertFrameReceived( + client, + Frame(OP_PONG, b""), + ) + + def test_server_receives_pong(self): + server = Protocol(SERVER) + server.receive_data(b"\x8a\x80\x00\x44\x88\xcc") + self.assertFrameReceived( + server, + Frame(OP_PONG, b""), + ) + + def test_client_sends_pong_with_data(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x00\x44\x88\xcc"): + client.send_pong(b"\x22\x66\xaa\xee") + self.assertEqual( + client.data_to_send(), [b"\x8a\x84\x00\x44\x88\xcc\x22\x22\x22\x22"] + ) + + def test_server_sends_pong_with_data(self): + server = Protocol(SERVER) + server.send_pong(b"\x22\x66\xaa\xee") + self.assertEqual(server.data_to_send(), [b"\x8a\x04\x22\x66\xaa\xee"]) + + def test_client_receives_pong_with_data(self): + client = Protocol(CLIENT) + client.receive_data(b"\x8a\x04\x22\x66\xaa\xee") + self.assertFrameReceived( + client, + Frame(OP_PONG, b"\x22\x66\xaa\xee"), + ) + + def test_server_receives_pong_with_data(self): + server = Protocol(SERVER) + server.receive_data(b"\x8a\x84\x00\x44\x88\xcc\x22\x22\x22\x22") + self.assertFrameReceived( + server, + Frame(OP_PONG, b"\x22\x66\xaa\xee"), + ) + + def test_client_sends_fragmented_pong_frame(self): + client = Protocol(CLIENT) + # This is only possible through a private API. + with self.assertRaises(ProtocolError) as raised: + client.send_frame(Frame(OP_PONG, b"", fin=False)) + self.assertEqual(str(raised.exception), "fragmented control frame") + + def test_server_sends_fragmented_pong_frame(self): + server = Protocol(SERVER) + # This is only possible through a private API. + with self.assertRaises(ProtocolError) as raised: + server.send_frame(Frame(OP_PONG, b"", fin=False)) + self.assertEqual(str(raised.exception), "fragmented control frame") + + def test_client_receives_fragmented_pong_frame(self): + client = Protocol(CLIENT) + client.receive_data(b"\x0a\x00") + self.assertIsInstance(client.parser_exc, ProtocolError) + self.assertEqual(str(client.parser_exc), "fragmented control frame") + self.assertConnectionFailing( + client, CloseCode.PROTOCOL_ERROR, "fragmented control frame" + ) + + def test_server_receives_fragmented_pong_frame(self): + server = Protocol(SERVER) + server.receive_data(b"\x0a\x80\x3c\x3c\x3c\x3c") + self.assertIsInstance(server.parser_exc, ProtocolError) + self.assertEqual(str(server.parser_exc), "fragmented control frame") + self.assertConnectionFailing( + server, CloseCode.PROTOCOL_ERROR, "fragmented control frame" + ) + + def test_client_sends_pong_after_sending_close(self): + client = Protocol(CLIENT) + with self.enforce_mask(b"\x00\x00\x00\x00"): + client.send_close(CloseCode.GOING_AWAY) + self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"]) + # websockets doesn't support sending a Pong frame after a Close frame. + with self.assertRaises(InvalidState): + client.send_pong(b"") + + def test_server_sends_pong_after_sending_close(self): + server = Protocol(SERVER) + server.send_close(CloseCode.NORMAL_CLOSURE) + self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"]) + # websockets doesn't support sending a Pong frame after a Close frame. + with self.assertRaises(InvalidState): + server.send_pong(b"") + + def test_client_receives_pong_after_receiving_close(self): + client = Protocol(CLIENT) + client.receive_data(b"\x88\x02\x03\xe8") + self.assertConnectionClosing(client, CloseCode.NORMAL_CLOSURE) + client.receive_data(b"\x8a\x04\x22\x66\xaa\xee") + self.assertFrameReceived(client, None) + self.assertFrameSent(client, None) + + def test_server_receives_pong_after_receiving_close(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9") + self.assertConnectionClosing(server, CloseCode.GOING_AWAY) + server.receive_data(b"\x8a\x84\x00\x44\x88\xcc\x22\x22\x22\x22") + self.assertFrameReceived(server, None) + self.assertFrameSent(server, None) + + +class FailTests(ProtocolTestCase): + """ + Test failing the connection. + + See 7.1.7. Fail the WebSocket Connection in RFC 6544. + + """ + + def test_client_stops_processing_frames_after_fail(self): + client = Protocol(CLIENT) + client.fail(CloseCode.PROTOCOL_ERROR) + self.assertConnectionFailing(client, CloseCode.PROTOCOL_ERROR) + client.receive_data(b"\x88\x02\x03\xea") + self.assertFrameReceived(client, None) + + def test_server_stops_processing_frames_after_fail(self): + server = Protocol(SERVER) + server.fail(CloseCode.PROTOCOL_ERROR) + self.assertConnectionFailing(server, CloseCode.PROTOCOL_ERROR) + server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xea") + self.assertFrameReceived(server, None) + + +class FragmentationTests(ProtocolTestCase): + """ + Test message fragmentation. + + See 5.4. Fragmentation in RFC 6544. + + """ + + def test_client_send_ping_pong_in_fragmented_message(self): + client = Protocol(CLIENT) + client.send_text(b"Spam", fin=False) + self.assertFrameSent(client, Frame(OP_TEXT, b"Spam", fin=False)) + client.send_ping(b"Ping") + self.assertFrameSent(client, Frame(OP_PING, b"Ping")) + client.send_continuation(b"Ham", fin=False) + self.assertFrameSent(client, Frame(OP_CONT, b"Ham", fin=False)) + client.send_pong(b"Pong") + self.assertFrameSent(client, Frame(OP_PONG, b"Pong")) + client.send_continuation(b"Eggs", fin=True) + self.assertFrameSent(client, Frame(OP_CONT, b"Eggs")) + + def test_server_send_ping_pong_in_fragmented_message(self): + server = Protocol(SERVER) + server.send_text(b"Spam", fin=False) + self.assertFrameSent(server, Frame(OP_TEXT, b"Spam", fin=False)) + server.send_ping(b"Ping") + self.assertFrameSent(server, Frame(OP_PING, b"Ping")) + server.send_continuation(b"Ham", fin=False) + self.assertFrameSent(server, Frame(OP_CONT, b"Ham", fin=False)) + server.send_pong(b"Pong") + self.assertFrameSent(server, Frame(OP_PONG, b"Pong")) + server.send_continuation(b"Eggs", fin=True) + self.assertFrameSent(server, Frame(OP_CONT, b"Eggs")) + + def test_client_receive_ping_pong_in_fragmented_message(self): + client = Protocol(CLIENT) + client.receive_data(b"\x01\x04Spam") + self.assertFrameReceived( + client, + Frame(OP_TEXT, b"Spam", fin=False), + ) + client.receive_data(b"\x89\x04Ping") + self.assertFrameReceived( + client, + Frame(OP_PING, b"Ping"), + ) + self.assertFrameSent( + client, + Frame(OP_PONG, b"Ping"), + ) + client.receive_data(b"\x00\x03Ham") + self.assertFrameReceived( + client, + Frame(OP_CONT, b"Ham", fin=False), + ) + client.receive_data(b"\x8a\x04Pong") + self.assertFrameReceived( + client, + Frame(OP_PONG, b"Pong"), + ) + client.receive_data(b"\x80\x04Eggs") + self.assertFrameReceived( + client, + Frame(OP_CONT, b"Eggs"), + ) + + def test_server_receive_ping_pong_in_fragmented_message(self): + server = Protocol(SERVER) + server.receive_data(b"\x01\x84\x00\x00\x00\x00Spam") + self.assertFrameReceived( + server, + Frame(OP_TEXT, b"Spam", fin=False), + ) + server.receive_data(b"\x89\x84\x00\x00\x00\x00Ping") + self.assertFrameReceived( + server, + Frame(OP_PING, b"Ping"), + ) + self.assertFrameSent( + server, + Frame(OP_PONG, b"Ping"), + ) + server.receive_data(b"\x00\x83\x00\x00\x00\x00Ham") + self.assertFrameReceived( + server, + Frame(OP_CONT, b"Ham", fin=False), + ) + server.receive_data(b"\x8a\x84\x00\x00\x00\x00Pong") + self.assertFrameReceived( + server, + Frame(OP_PONG, b"Pong"), + ) + server.receive_data(b"\x80\x84\x00\x00\x00\x00Eggs") + self.assertFrameReceived( + server, + Frame(OP_CONT, b"Eggs"), + ) + + def test_client_send_close_in_fragmented_message(self): + client = Protocol(CLIENT) + client.send_text(b"Spam", fin=False) + self.assertFrameSent(client, Frame(OP_TEXT, b"Spam", fin=False)) + # The spec says: "An endpoint MUST be capable of handling control + # frames in the middle of a fragmented message." However, since the + # endpoint must not send a data frame after a close frame, a close + # frame can't be "in the middle" of a fragmented message. + with self.assertRaises(ProtocolError) as raised: + client.send_close(CloseCode.GOING_AWAY) + self.assertEqual(str(raised.exception), "expected a continuation frame") + client.send_continuation(b"Eggs", fin=True) + + def test_server_send_close_in_fragmented_message(self): + server = Protocol(CLIENT) + server.send_text(b"Spam", fin=False) + self.assertFrameSent(server, Frame(OP_TEXT, b"Spam", fin=False)) + # The spec says: "An endpoint MUST be capable of handling control + # frames in the middle of a fragmented message." However, since the + # endpoint must not send a data frame after a close frame, a close + # frame can't be "in the middle" of a fragmented message. + with self.assertRaises(ProtocolError) as raised: + server.send_close(CloseCode.NORMAL_CLOSURE) + self.assertEqual(str(raised.exception), "expected a continuation frame") + + def test_client_receive_close_in_fragmented_message(self): + client = Protocol(CLIENT) + client.receive_data(b"\x01\x04Spam") + self.assertFrameReceived( + client, + Frame(OP_TEXT, b"Spam", fin=False), + ) + # The spec says: "An endpoint MUST be capable of handling control + # frames in the middle of a fragmented message." However, since the + # endpoint must not send a data frame after a close frame, a close + # frame can't be "in the middle" of a fragmented message. + client.receive_data(b"\x88\x02\x03\xe8") + self.assertIsInstance(client.parser_exc, ProtocolError) + self.assertEqual(str(client.parser_exc), "incomplete fragmented message") + self.assertConnectionFailing( + client, CloseCode.PROTOCOL_ERROR, "incomplete fragmented message" + ) + + def test_server_receive_close_in_fragmented_message(self): + server = Protocol(SERVER) + server.receive_data(b"\x01\x84\x00\x00\x00\x00Spam") + self.assertFrameReceived( + server, + Frame(OP_TEXT, b"Spam", fin=False), + ) + # The spec says: "An endpoint MUST be capable of handling control + # frames in the middle of a fragmented message." However, since the + # endpoint must not send a data frame after a close frame, a close + # frame can't be "in the middle" of a fragmented message. + server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9") + self.assertIsInstance(server.parser_exc, ProtocolError) + self.assertEqual(str(server.parser_exc), "incomplete fragmented message") + self.assertConnectionFailing( + server, CloseCode.PROTOCOL_ERROR, "incomplete fragmented message" + ) + + +class EOFTests(ProtocolTestCase): + """ + Test half-closes on connection termination. + + """ + + def test_client_receives_eof(self): + client = Protocol(CLIENT) + client.receive_data(b"\x88\x00") + self.assertConnectionClosing(client) + client.receive_eof() + self.assertIs(client.state, CLOSED) + + def test_server_receives_eof(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c") + self.assertConnectionClosing(server) + server.receive_eof() + self.assertIs(server.state, CLOSED) + + def test_client_receives_eof_between_frames(self): + client = Protocol(CLIENT) + client.receive_eof() + self.assertIsInstance(client.parser_exc, EOFError) + self.assertEqual(str(client.parser_exc), "unexpected end of stream") + self.assertIs(client.state, CLOSED) + + def test_server_receives_eof_between_frames(self): + server = Protocol(SERVER) + server.receive_eof() + self.assertIsInstance(server.parser_exc, EOFError) + self.assertEqual(str(server.parser_exc), "unexpected end of stream") + self.assertIs(server.state, CLOSED) + + def test_client_receives_eof_inside_frame(self): + client = Protocol(CLIENT) + client.receive_data(b"\x81") + client.receive_eof() + self.assertIsInstance(client.parser_exc, EOFError) + self.assertEqual( + str(client.parser_exc), + "stream ends after 1 bytes, expected 2 bytes", + ) + self.assertIs(client.state, CLOSED) + + def test_server_receives_eof_inside_frame(self): + server = Protocol(SERVER) + server.receive_data(b"\x81") + server.receive_eof() + self.assertIsInstance(server.parser_exc, EOFError) + self.assertEqual( + str(server.parser_exc), + "stream ends after 1 bytes, expected 2 bytes", + ) + self.assertIs(server.state, CLOSED) + + def test_client_receives_data_after_exception(self): + client = Protocol(CLIENT) + client.receive_data(b"\xff\xff") + self.assertConnectionFailing(client, CloseCode.PROTOCOL_ERROR, "invalid opcode") + client.receive_data(b"\x00\x00") + self.assertFrameSent(client, None) + + def test_server_receives_data_after_exception(self): + server = Protocol(SERVER) + server.receive_data(b"\xff\xff") + self.assertConnectionFailing(server, CloseCode.PROTOCOL_ERROR, "invalid opcode") + server.receive_data(b"\x00\x00") + self.assertFrameSent(server, None) + + def test_client_receives_eof_after_exception(self): + client = Protocol(CLIENT) + client.receive_data(b"\xff\xff") + self.assertConnectionFailing(client, CloseCode.PROTOCOL_ERROR, "invalid opcode") + client.receive_eof() + self.assertFrameSent(client, None, eof=True) + + def test_server_receives_eof_after_exception(self): + server = Protocol(SERVER) + server.receive_data(b"\xff\xff") + self.assertConnectionFailing(server, CloseCode.PROTOCOL_ERROR, "invalid opcode") + server.receive_eof() + self.assertFrameSent(server, None) + + def test_client_receives_data_and_eof_after_exception(self): + client = Protocol(CLIENT) + client.receive_data(b"\xff\xff") + self.assertConnectionFailing(client, CloseCode.PROTOCOL_ERROR, "invalid opcode") + client.receive_data(b"\x00\x00") + client.receive_eof() + self.assertFrameSent(client, None, eof=True) + + def test_server_receives_data_and_eof_after_exception(self): + server = Protocol(SERVER) + server.receive_data(b"\xff\xff") + self.assertConnectionFailing(server, CloseCode.PROTOCOL_ERROR, "invalid opcode") + server.receive_data(b"\x00\x00") + server.receive_eof() + self.assertFrameSent(server, None) + + def test_client_receives_data_after_eof(self): + client = Protocol(CLIENT) + client.receive_data(b"\x88\x00") + self.assertConnectionClosing(client) + client.receive_eof() + with self.assertRaises(EOFError) as raised: + client.receive_data(b"\x88\x00") + self.assertEqual(str(raised.exception), "stream ended") + + def test_server_receives_data_after_eof(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c") + self.assertConnectionClosing(server) + server.receive_eof() + with self.assertRaises(EOFError) as raised: + server.receive_data(b"\x88\x80\x00\x00\x00\x00") + self.assertEqual(str(raised.exception), "stream ended") + + def test_client_receives_eof_after_eof(self): + client = Protocol(CLIENT) + client.receive_data(b"\x88\x00") + self.assertConnectionClosing(client) + client.receive_eof() + with self.assertRaises(EOFError) as raised: + client.receive_eof() + self.assertEqual(str(raised.exception), "stream ended") + + def test_server_receives_eof_after_eof(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c") + self.assertConnectionClosing(server) + server.receive_eof() + with self.assertRaises(EOFError) as raised: + server.receive_eof() + self.assertEqual(str(raised.exception), "stream ended") + + +class TCPCloseTests(ProtocolTestCase): + """ + Test expectation of TCP close on connection termination. + + """ + + def test_client_default(self): + client = Protocol(CLIENT) + self.assertFalse(client.close_expected()) + + def test_server_default(self): + server = Protocol(SERVER) + self.assertFalse(server.close_expected()) + + def test_client_sends_close(self): + client = Protocol(CLIENT) + client.send_close() + self.assertTrue(client.close_expected()) + + def test_server_sends_close(self): + server = Protocol(SERVER) + server.send_close() + self.assertTrue(server.close_expected()) + + def test_client_receives_close(self): + client = Protocol(CLIENT) + client.receive_data(b"\x88\x00") + self.assertTrue(client.close_expected()) + + def test_client_receives_close_then_eof(self): + client = Protocol(CLIENT) + client.receive_data(b"\x88\x00") + client.receive_eof() + self.assertFalse(client.close_expected()) + + def test_server_receives_close_then_eof(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c") + server.receive_eof() + self.assertFalse(server.close_expected()) + + def test_server_receives_close(self): + server = Protocol(SERVER) + server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c") + self.assertTrue(server.close_expected()) + + def test_client_fails_connection(self): + client = Protocol(CLIENT) + client.fail(CloseCode.PROTOCOL_ERROR) + self.assertTrue(client.close_expected()) + + def test_server_fails_connection(self): + server = Protocol(SERVER) + server.fail(CloseCode.PROTOCOL_ERROR) + self.assertTrue(server.close_expected()) + + +class ConnectionClosedTests(ProtocolTestCase): + """ + Test connection closed exception. + + """ + + def test_client_sends_close_then_receives_close(self): + # Client-initiated close handshake on the client side complete. + client = Protocol(CLIENT) + client.send_close(CloseCode.NORMAL_CLOSURE, "") + client.receive_data(b"\x88\x02\x03\xe8") + client.receive_eof() + exc = client.close_exc + self.assertIsInstance(exc, ConnectionClosedOK) + self.assertEqual(exc.rcvd, Close(CloseCode.NORMAL_CLOSURE, "")) + self.assertEqual(exc.sent, Close(CloseCode.NORMAL_CLOSURE, "")) + self.assertFalse(exc.rcvd_then_sent) + + def test_server_sends_close_then_receives_close(self): + # Server-initiated close handshake on the server side complete. + server = Protocol(SERVER) + server.send_close(CloseCode.NORMAL_CLOSURE, "") + server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe8") + server.receive_eof() + exc = server.close_exc + self.assertIsInstance(exc, ConnectionClosedOK) + self.assertEqual(exc.rcvd, Close(CloseCode.NORMAL_CLOSURE, "")) + self.assertEqual(exc.sent, Close(CloseCode.NORMAL_CLOSURE, "")) + self.assertFalse(exc.rcvd_then_sent) + + def test_client_receives_close_then_sends_close(self): + # Server-initiated close handshake on the client side complete. + client = Protocol(CLIENT) + client.receive_data(b"\x88\x02\x03\xe8") + client.receive_eof() + exc = client.close_exc + self.assertIsInstance(exc, ConnectionClosedOK) + self.assertEqual(exc.rcvd, Close(CloseCode.NORMAL_CLOSURE, "")) + self.assertEqual(exc.sent, Close(CloseCode.NORMAL_CLOSURE, "")) + self.assertTrue(exc.rcvd_then_sent) + + def test_server_receives_close_then_sends_close(self): + # Client-initiated close handshake on the server side complete. + server = Protocol(SERVER) + server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe8") + server.receive_eof() + exc = server.close_exc + self.assertIsInstance(exc, ConnectionClosedOK) + self.assertEqual(exc.rcvd, Close(CloseCode.NORMAL_CLOSURE, "")) + self.assertEqual(exc.sent, Close(CloseCode.NORMAL_CLOSURE, "")) + self.assertTrue(exc.rcvd_then_sent) + + def test_client_sends_close_then_receives_eof(self): + # Client-initiated close handshake on the client side times out. + client = Protocol(CLIENT) + client.send_close(CloseCode.NORMAL_CLOSURE, "") + client.receive_eof() + exc = client.close_exc + self.assertIsInstance(exc, ConnectionClosedError) + self.assertIsNone(exc.rcvd) + self.assertEqual(exc.sent, Close(CloseCode.NORMAL_CLOSURE, "")) + self.assertIsNone(exc.rcvd_then_sent) + + def test_server_sends_close_then_receives_eof(self): + # Server-initiated close handshake on the server side times out. + server = Protocol(SERVER) + server.send_close(CloseCode.NORMAL_CLOSURE, "") + server.receive_eof() + exc = server.close_exc + self.assertIsInstance(exc, ConnectionClosedError) + self.assertIsNone(exc.rcvd) + self.assertEqual(exc.sent, Close(CloseCode.NORMAL_CLOSURE, "")) + self.assertIsNone(exc.rcvd_then_sent) + + def test_client_receives_eof(self): + # Server-initiated close handshake on the client side times out. + client = Protocol(CLIENT) + client.receive_eof() + exc = client.close_exc + self.assertIsInstance(exc, ConnectionClosedError) + self.assertIsNone(exc.rcvd) + self.assertIsNone(exc.sent) + self.assertIsNone(exc.rcvd_then_sent) + + def test_server_receives_eof(self): + # Client-initiated close handshake on the server side times out. + server = Protocol(SERVER) + server.receive_eof() + exc = server.close_exc + self.assertIsInstance(exc, ConnectionClosedError) + self.assertIsNone(exc.rcvd) + self.assertIsNone(exc.sent) + self.assertIsNone(exc.rcvd_then_sent) + + +class ErrorTests(ProtocolTestCase): + """ + Test other error cases. + + """ + + def test_client_hits_internal_error_reading_frame(self): + client = Protocol(CLIENT) + # This isn't supposed to happen, so we're simulating it. + with unittest.mock.patch("struct.unpack", side_effect=RuntimeError("BOOM")): + client.receive_data(b"\x81\x00") + self.assertIsInstance(client.parser_exc, RuntimeError) + self.assertEqual(str(client.parser_exc), "BOOM") + self.assertConnectionFailing(client, CloseCode.INTERNAL_ERROR, "") + + def test_server_hits_internal_error_reading_frame(self): + server = Protocol(SERVER) + # This isn't supposed to happen, so we're simulating it. + with unittest.mock.patch("struct.unpack", side_effect=RuntimeError("BOOM")): + server.receive_data(b"\x81\x80\x00\x00\x00\x00") + self.assertIsInstance(server.parser_exc, RuntimeError) + self.assertEqual(str(server.parser_exc), "BOOM") + self.assertConnectionFailing(server, CloseCode.INTERNAL_ERROR, "") + + +class ExtensionsTests(ProtocolTestCase): + """ + Test how extensions affect frames. + + """ + + def test_client_extension_encodes_frame(self): + client = Protocol(CLIENT) + client.extensions = [Rsv2Extension()] + with self.enforce_mask(b"\x00\x44\x88\xcc"): + client.send_ping(b"") + self.assertEqual(client.data_to_send(), [b"\xa9\x80\x00\x44\x88\xcc"]) + + def test_server_extension_encodes_frame(self): + server = Protocol(SERVER) + server.extensions = [Rsv2Extension()] + server.send_ping(b"") + self.assertEqual(server.data_to_send(), [b"\xa9\x00"]) + + def test_client_extension_decodes_frame(self): + client = Protocol(CLIENT) + client.extensions = [Rsv2Extension()] + client.receive_data(b"\xaa\x00") + self.assertEqual(client.events_received(), [Frame(OP_PONG, b"")]) + + def test_server_extension_decodes_frame(self): + server = Protocol(SERVER) + server.extensions = [Rsv2Extension()] + server.receive_data(b"\xaa\x80\x00\x44\x88\xcc") + self.assertEqual(server.events_received(), [Frame(OP_PONG, b"")]) + + +class MiscTests(unittest.TestCase): + def test_client_default_logger(self): + client = Protocol(CLIENT) + logger = logging.getLogger("websockets.client") + self.assertIs(client.logger, logger) + + def test_server_default_logger(self): + server = Protocol(SERVER) + logger = logging.getLogger("websockets.server") + self.assertIs(server.logger, logger) + + def test_client_custom_logger(self): + logger = logging.getLogger("test") + client = Protocol(CLIENT, logger=logger) + self.assertIs(client.logger, logger) + + def test_server_custom_logger(self): + logger = logging.getLogger("test") + server = Protocol(SERVER, logger=logger) + self.assertIs(server.logger, logger) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_server.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_server.py new file mode 100644 index 0000000000000..b6f5e3568195e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_server.py @@ -0,0 +1,686 @@ +import http +import logging +import unittest +import unittest.mock + +from websockets.datastructures import Headers +from websockets.exceptions import ( + InvalidHeader, + InvalidOrigin, + InvalidUpgrade, + NegotiationError, +) +from websockets.frames import OP_TEXT, Frame +from websockets.http11 import Request, Response +from websockets.protocol import CONNECTING, OPEN +from websockets.server import * + +from .extensions.utils import ( + OpExtension, + Rsv2Extension, + ServerOpExtensionFactory, + ServerRsv2ExtensionFactory, +) +from .test_utils import ACCEPT, KEY +from .utils import DATE, DeprecationTestCase + + +class ConnectTests(unittest.TestCase): + def test_receive_connect(self): + server = ServerProtocol() + server.receive_data( + ( + f"GET /test HTTP/1.1\r\n" + f"Host: example.com\r\n" + f"Upgrade: websocket\r\n" + f"Connection: Upgrade\r\n" + f"Sec-WebSocket-Key: {KEY}\r\n" + f"Sec-WebSocket-Version: 13\r\n" + f"\r\n" + ).encode(), + ) + [request] = server.events_received() + self.assertIsInstance(request, Request) + self.assertEqual(server.data_to_send(), []) + self.assertFalse(server.close_expected()) + + def test_connect_request(self): + server = ServerProtocol() + server.receive_data( + ( + f"GET /test HTTP/1.1\r\n" + f"Host: example.com\r\n" + f"Upgrade: websocket\r\n" + f"Connection: Upgrade\r\n" + f"Sec-WebSocket-Key: {KEY}\r\n" + f"Sec-WebSocket-Version: 13\r\n" + f"\r\n" + ).encode(), + ) + [request] = server.events_received() + self.assertEqual(request.path, "/test") + self.assertEqual( + request.headers, + Headers( + { + "Host": "example.com", + "Upgrade": "websocket", + "Connection": "Upgrade", + "Sec-WebSocket-Key": KEY, + "Sec-WebSocket-Version": "13", + } + ), + ) + + def test_no_request(self): + server = ServerProtocol() + server.receive_eof() + self.assertEqual(server.events_received(), []) + + def test_partial_request(self): + server = ServerProtocol() + server.receive_data(b"GET /test HTTP/1.1\r\n") + server.receive_eof() + self.assertEqual(server.events_received(), []) + + def test_random_request(self): + server = ServerProtocol() + server.receive_data(b"HELO relay.invalid\r\n") + server.receive_data(b"MAIL FROM: \r\n") + server.receive_data(b"RCPT TO: \r\n") + self.assertEqual(server.events_received(), []) + + +class AcceptRejectTests(unittest.TestCase): + def make_request(self): + return Request( + path="/test", + headers=Headers( + { + "Host": "example.com", + "Upgrade": "websocket", + "Connection": "Upgrade", + "Sec-WebSocket-Key": KEY, + "Sec-WebSocket-Version": "13", + } + ), + ) + + def test_send_accept(self): + server = ServerProtocol() + with unittest.mock.patch("email.utils.formatdate", return_value=DATE): + response = server.accept(self.make_request()) + self.assertIsInstance(response, Response) + server.send_response(response) + self.assertEqual( + server.data_to_send(), + [ + f"HTTP/1.1 101 Switching Protocols\r\n" + f"Date: {DATE}\r\n" + f"Upgrade: websocket\r\n" + f"Connection: Upgrade\r\n" + f"Sec-WebSocket-Accept: {ACCEPT}\r\n" + f"\r\n".encode() + ], + ) + self.assertFalse(server.close_expected()) + self.assertEqual(server.state, OPEN) + + def test_send_reject(self): + server = ServerProtocol() + with unittest.mock.patch("email.utils.formatdate", return_value=DATE): + response = server.reject(http.HTTPStatus.NOT_FOUND, "Sorry folks.\n") + self.assertIsInstance(response, Response) + server.send_response(response) + self.assertEqual( + server.data_to_send(), + [ + f"HTTP/1.1 404 Not Found\r\n" + f"Date: {DATE}\r\n" + f"Connection: close\r\n" + f"Content-Length: 13\r\n" + f"Content-Type: text/plain; charset=utf-8\r\n" + f"\r\n" + f"Sorry folks.\n".encode(), + b"", + ], + ) + self.assertTrue(server.close_expected()) + self.assertEqual(server.state, CONNECTING) + + def test_accept_response(self): + server = ServerProtocol() + with unittest.mock.patch("email.utils.formatdate", return_value=DATE): + response = server.accept(self.make_request()) + self.assertIsInstance(response, Response) + self.assertEqual(response.status_code, 101) + self.assertEqual(response.reason_phrase, "Switching Protocols") + self.assertEqual( + response.headers, + Headers( + { + "Date": DATE, + "Upgrade": "websocket", + "Connection": "Upgrade", + "Sec-WebSocket-Accept": ACCEPT, + } + ), + ) + self.assertIsNone(response.body) + + def test_reject_response(self): + server = ServerProtocol() + with unittest.mock.patch("email.utils.formatdate", return_value=DATE): + response = server.reject(http.HTTPStatus.NOT_FOUND, "Sorry folks.\n") + self.assertIsInstance(response, Response) + self.assertEqual(response.status_code, 404) + self.assertEqual(response.reason_phrase, "Not Found") + self.assertEqual( + response.headers, + Headers( + { + "Date": DATE, + "Connection": "close", + "Content-Length": "13", + "Content-Type": "text/plain; charset=utf-8", + } + ), + ) + self.assertEqual(response.body, b"Sorry folks.\n") + + def test_reject_response_supports_int_status(self): + server = ServerProtocol() + response = server.reject(404, "Sorry folks.\n") + self.assertEqual(response.status_code, 404) + self.assertEqual(response.reason_phrase, "Not Found") + + def test_basic(self): + server = ServerProtocol() + request = self.make_request() + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + + def test_unexpected_exception(self): + server = ServerProtocol() + request = self.make_request() + with unittest.mock.patch( + "websockets.server.ServerProtocol.process_request", + side_effect=Exception("BOOM"), + ): + response = server.accept(request) + + self.assertEqual(response.status_code, 500) + with self.assertRaises(Exception) as raised: + raise server.handshake_exc + self.assertEqual(str(raised.exception), "BOOM") + + def test_missing_connection(self): + server = ServerProtocol() + request = self.make_request() + del request.headers["Connection"] + response = server.accept(request) + + self.assertEqual(response.status_code, 426) + self.assertEqual(response.headers["Upgrade"], "websocket") + with self.assertRaises(InvalidUpgrade) as raised: + raise server.handshake_exc + self.assertEqual(str(raised.exception), "missing Connection header") + + def test_invalid_connection(self): + server = ServerProtocol() + request = self.make_request() + del request.headers["Connection"] + request.headers["Connection"] = "close" + response = server.accept(request) + + self.assertEqual(response.status_code, 426) + self.assertEqual(response.headers["Upgrade"], "websocket") + with self.assertRaises(InvalidUpgrade) as raised: + raise server.handshake_exc + self.assertEqual(str(raised.exception), "invalid Connection header: close") + + def test_missing_upgrade(self): + server = ServerProtocol() + request = self.make_request() + del request.headers["Upgrade"] + response = server.accept(request) + + self.assertEqual(response.status_code, 426) + self.assertEqual(response.headers["Upgrade"], "websocket") + with self.assertRaises(InvalidUpgrade) as raised: + raise server.handshake_exc + self.assertEqual(str(raised.exception), "missing Upgrade header") + + def test_invalid_upgrade(self): + server = ServerProtocol() + request = self.make_request() + del request.headers["Upgrade"] + request.headers["Upgrade"] = "h2c" + response = server.accept(request) + + self.assertEqual(response.status_code, 426) + self.assertEqual(response.headers["Upgrade"], "websocket") + with self.assertRaises(InvalidUpgrade) as raised: + raise server.handshake_exc + self.assertEqual(str(raised.exception), "invalid Upgrade header: h2c") + + def test_missing_key(self): + server = ServerProtocol() + request = self.make_request() + del request.headers["Sec-WebSocket-Key"] + response = server.accept(request) + + self.assertEqual(response.status_code, 400) + with self.assertRaises(InvalidHeader) as raised: + raise server.handshake_exc + self.assertEqual(str(raised.exception), "missing Sec-WebSocket-Key header") + + def test_multiple_key(self): + server = ServerProtocol() + request = self.make_request() + request.headers["Sec-WebSocket-Key"] = KEY + response = server.accept(request) + + self.assertEqual(response.status_code, 400) + with self.assertRaises(InvalidHeader) as raised: + raise server.handshake_exc + self.assertEqual( + str(raised.exception), + "invalid Sec-WebSocket-Key header: " + "more than one Sec-WebSocket-Key header found", + ) + + def test_invalid_key(self): + server = ServerProtocol() + request = self.make_request() + del request.headers["Sec-WebSocket-Key"] + request.headers["Sec-WebSocket-Key"] = "not Base64 data!" + response = server.accept(request) + + self.assertEqual(response.status_code, 400) + with self.assertRaises(InvalidHeader) as raised: + raise server.handshake_exc + self.assertEqual( + str(raised.exception), "invalid Sec-WebSocket-Key header: not Base64 data!" + ) + + def test_truncated_key(self): + server = ServerProtocol() + request = self.make_request() + del request.headers["Sec-WebSocket-Key"] + request.headers["Sec-WebSocket-Key"] = KEY[ + :16 + ] # 12 bytes instead of 16, Base64-encoded + response = server.accept(request) + + self.assertEqual(response.status_code, 400) + with self.assertRaises(InvalidHeader) as raised: + raise server.handshake_exc + self.assertEqual( + str(raised.exception), f"invalid Sec-WebSocket-Key header: {KEY[:16]}" + ) + + def test_missing_version(self): + server = ServerProtocol() + request = self.make_request() + del request.headers["Sec-WebSocket-Version"] + response = server.accept(request) + + self.assertEqual(response.status_code, 400) + with self.assertRaises(InvalidHeader) as raised: + raise server.handshake_exc + self.assertEqual(str(raised.exception), "missing Sec-WebSocket-Version header") + + def test_multiple_version(self): + server = ServerProtocol() + request = self.make_request() + request.headers["Sec-WebSocket-Version"] = "11" + response = server.accept(request) + + self.assertEqual(response.status_code, 400) + with self.assertRaises(InvalidHeader) as raised: + raise server.handshake_exc + self.assertEqual( + str(raised.exception), + "invalid Sec-WebSocket-Version header: " + "more than one Sec-WebSocket-Version header found", + ) + + def test_invalid_version(self): + server = ServerProtocol() + request = self.make_request() + del request.headers["Sec-WebSocket-Version"] + request.headers["Sec-WebSocket-Version"] = "11" + response = server.accept(request) + + self.assertEqual(response.status_code, 400) + with self.assertRaises(InvalidHeader) as raised: + raise server.handshake_exc + self.assertEqual( + str(raised.exception), "invalid Sec-WebSocket-Version header: 11" + ) + + def test_no_origin(self): + server = ServerProtocol(origins=["https://example.com"]) + request = self.make_request() + response = server.accept(request) + + self.assertEqual(response.status_code, 403) + with self.assertRaises(InvalidOrigin) as raised: + raise server.handshake_exc + self.assertEqual(str(raised.exception), "missing Origin header") + + def test_origin(self): + server = ServerProtocol(origins=["https://example.com"]) + request = self.make_request() + request.headers["Origin"] = "https://example.com" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertEqual(server.origin, "https://example.com") + + def test_unexpected_origin(self): + server = ServerProtocol(origins=["https://example.com"]) + request = self.make_request() + request.headers["Origin"] = "https://other.example.com" + response = server.accept(request) + + self.assertEqual(response.status_code, 403) + with self.assertRaises(InvalidOrigin) as raised: + raise server.handshake_exc + self.assertEqual( + str(raised.exception), "invalid Origin header: https://other.example.com" + ) + + def test_multiple_origin(self): + server = ServerProtocol( + origins=["https://example.com", "https://other.example.com"] + ) + request = self.make_request() + request.headers["Origin"] = "https://example.com" + request.headers["Origin"] = "https://other.example.com" + response = server.accept(request) + + # This is prohibited by the HTTP specification, so the return code is + # 400 Bad Request rather than 403 Forbidden. + self.assertEqual(response.status_code, 400) + with self.assertRaises(InvalidHeader) as raised: + raise server.handshake_exc + self.assertEqual( + str(raised.exception), + "invalid Origin header: more than one Origin header found", + ) + + def test_supported_origin(self): + server = ServerProtocol( + origins=["https://example.com", "https://other.example.com"] + ) + request = self.make_request() + request.headers["Origin"] = "https://other.example.com" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertEqual(server.origin, "https://other.example.com") + + def test_unsupported_origin(self): + server = ServerProtocol( + origins=["https://example.com", "https://other.example.com"] + ) + request = self.make_request() + request.headers["Origin"] = "https://original.example.com" + response = server.accept(request) + + self.assertEqual(response.status_code, 403) + with self.assertRaises(InvalidOrigin) as raised: + raise server.handshake_exc + self.assertEqual( + str(raised.exception), "invalid Origin header: https://original.example.com" + ) + + def test_no_origin_accepted(self): + server = ServerProtocol(origins=[None]) + request = self.make_request() + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertIsNone(server.origin) + + def test_no_extensions(self): + server = ServerProtocol() + request = self.make_request() + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertNotIn("Sec-WebSocket-Extensions", response.headers) + self.assertEqual(server.extensions, []) + + def test_no_extension(self): + server = ServerProtocol(extensions=[ServerOpExtensionFactory()]) + request = self.make_request() + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertNotIn("Sec-WebSocket-Extensions", response.headers) + self.assertEqual(server.extensions, []) + + def test_extension(self): + server = ServerProtocol(extensions=[ServerOpExtensionFactory()]) + request = self.make_request() + request.headers["Sec-WebSocket-Extensions"] = "x-op; op" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertEqual(response.headers["Sec-WebSocket-Extensions"], "x-op; op") + self.assertEqual(server.extensions, [OpExtension()]) + + def test_unexpected_extension(self): + server = ServerProtocol() + request = self.make_request() + request.headers["Sec-WebSocket-Extensions"] = "x-op; op" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertNotIn("Sec-WebSocket-Extensions", response.headers) + self.assertEqual(server.extensions, []) + + def test_unsupported_extension(self): + server = ServerProtocol(extensions=[ServerRsv2ExtensionFactory()]) + request = self.make_request() + request.headers["Sec-WebSocket-Extensions"] = "x-op; op" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertNotIn("Sec-WebSocket-Extensions", response.headers) + self.assertEqual(server.extensions, []) + + def test_supported_extension_parameters(self): + server = ServerProtocol(extensions=[ServerOpExtensionFactory("this")]) + request = self.make_request() + request.headers["Sec-WebSocket-Extensions"] = "x-op; op=this" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertEqual(response.headers["Sec-WebSocket-Extensions"], "x-op; op=this") + self.assertEqual(server.extensions, [OpExtension("this")]) + + def test_unsupported_extension_parameters(self): + server = ServerProtocol(extensions=[ServerOpExtensionFactory("this")]) + request = self.make_request() + request.headers["Sec-WebSocket-Extensions"] = "x-op; op=that" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertNotIn("Sec-WebSocket-Extensions", response.headers) + self.assertEqual(server.extensions, []) + + def test_multiple_supported_extension_parameters(self): + server = ServerProtocol( + extensions=[ + ServerOpExtensionFactory("this"), + ServerOpExtensionFactory("that"), + ] + ) + request = self.make_request() + request.headers["Sec-WebSocket-Extensions"] = "x-op; op=that" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertEqual(response.headers["Sec-WebSocket-Extensions"], "x-op; op=that") + self.assertEqual(server.extensions, [OpExtension("that")]) + + def test_multiple_extensions(self): + server = ServerProtocol( + extensions=[ServerOpExtensionFactory(), ServerRsv2ExtensionFactory()] + ) + request = self.make_request() + request.headers["Sec-WebSocket-Extensions"] = "x-op; op" + request.headers["Sec-WebSocket-Extensions"] = "x-rsv2" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertEqual( + response.headers["Sec-WebSocket-Extensions"], "x-op; op, x-rsv2" + ) + self.assertEqual(server.extensions, [OpExtension(), Rsv2Extension()]) + + def test_multiple_extensions_order(self): + server = ServerProtocol( + extensions=[ServerOpExtensionFactory(), ServerRsv2ExtensionFactory()] + ) + request = self.make_request() + request.headers["Sec-WebSocket-Extensions"] = "x-rsv2" + request.headers["Sec-WebSocket-Extensions"] = "x-op; op" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertEqual( + response.headers["Sec-WebSocket-Extensions"], "x-rsv2, x-op; op" + ) + self.assertEqual(server.extensions, [Rsv2Extension(), OpExtension()]) + + def test_no_subprotocols(self): + server = ServerProtocol() + request = self.make_request() + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertNotIn("Sec-WebSocket-Protocol", response.headers) + self.assertIsNone(server.subprotocol) + + def test_no_subprotocol(self): + server = ServerProtocol(subprotocols=["chat"]) + request = self.make_request() + response = server.accept(request) + + self.assertEqual(response.status_code, 400) + with self.assertRaisesRegex( + NegotiationError, + r"missing subprotocol", + ): + raise server.handshake_exc + + def test_subprotocol(self): + server = ServerProtocol(subprotocols=["chat"]) + request = self.make_request() + request.headers["Sec-WebSocket-Protocol"] = "chat" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertEqual(response.headers["Sec-WebSocket-Protocol"], "chat") + self.assertEqual(server.subprotocol, "chat") + + def test_unexpected_subprotocol(self): + server = ServerProtocol() + request = self.make_request() + request.headers["Sec-WebSocket-Protocol"] = "chat" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertNotIn("Sec-WebSocket-Protocol", response.headers) + self.assertIsNone(server.subprotocol) + + def test_multiple_subprotocols(self): + server = ServerProtocol(subprotocols=["superchat", "chat"]) + request = self.make_request() + request.headers["Sec-WebSocket-Protocol"] = "chat" + request.headers["Sec-WebSocket-Protocol"] = "superchat" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertEqual(response.headers["Sec-WebSocket-Protocol"], "superchat") + self.assertEqual(server.subprotocol, "superchat") + + def test_supported_subprotocol(self): + server = ServerProtocol(subprotocols=["superchat", "chat"]) + request = self.make_request() + request.headers["Sec-WebSocket-Protocol"] = "chat" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertEqual(response.headers["Sec-WebSocket-Protocol"], "chat") + self.assertEqual(server.subprotocol, "chat") + + def test_unsupported_subprotocol(self): + server = ServerProtocol(subprotocols=["superchat", "chat"]) + request = self.make_request() + request.headers["Sec-WebSocket-Protocol"] = "otherchat" + response = server.accept(request) + + self.assertEqual(response.status_code, 400) + with self.assertRaisesRegex( + NegotiationError, + r"invalid subprotocol; expected one of superchat, chat", + ): + raise server.handshake_exc + + @staticmethod + def optional_chat(protocol, subprotocols): + if "chat" in subprotocols: + return "chat" + + def test_select_subprotocol(self): + server = ServerProtocol(select_subprotocol=self.optional_chat) + request = self.make_request() + request.headers["Sec-WebSocket-Protocol"] = "chat" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertEqual(response.headers["Sec-WebSocket-Protocol"], "chat") + self.assertEqual(server.subprotocol, "chat") + + def test_select_no_subprotocol(self): + server = ServerProtocol(select_subprotocol=self.optional_chat) + request = self.make_request() + request.headers["Sec-WebSocket-Protocol"] = "otherchat" + response = server.accept(request) + + self.assertEqual(response.status_code, 101) + self.assertNotIn("Sec-WebSocket-Protocol", response.headers) + self.assertIsNone(server.subprotocol) + + +class MiscTests(unittest.TestCase): + def test_bypass_handshake(self): + server = ServerProtocol(state=OPEN) + server.receive_data(b"\x81\x86\x00\x00\x00\x00Hello!") + [frame] = server.events_received() + self.assertEqual(frame, Frame(OP_TEXT, b"Hello!")) + + def test_custom_logger(self): + logger = logging.getLogger("test") + with self.assertLogs("test", logging.DEBUG) as logs: + ServerProtocol(logger=logger) + self.assertEqual(len(logs.records), 1) + + +class BackwardsCompatibilityTests(DeprecationTestCase): + def test_server_connection_class(self): + with self.assertDeprecationWarning( + "ServerConnection was renamed to ServerProtocol" + ): + from websockets.server import ServerConnection + + server = ServerConnection() + + self.assertIsInstance(server, ServerProtocol) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_streams.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_streams.py new file mode 100644 index 0000000000000..fd7c66a0bdc95 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_streams.py @@ -0,0 +1,198 @@ +from websockets.streams import StreamReader + +from .utils import GeneratorTestCase + + +class StreamReaderTests(GeneratorTestCase): + def setUp(self): + self.reader = StreamReader() + + def test_read_line(self): + self.reader.feed_data(b"spam\neggs\n") + + gen = self.reader.read_line(32) + line = self.assertGeneratorReturns(gen) + self.assertEqual(line, b"spam\n") + + gen = self.reader.read_line(32) + line = self.assertGeneratorReturns(gen) + self.assertEqual(line, b"eggs\n") + + def test_read_line_need_more_data(self): + self.reader.feed_data(b"spa") + + gen = self.reader.read_line(32) + self.assertGeneratorRunning(gen) + self.reader.feed_data(b"m\neg") + line = self.assertGeneratorReturns(gen) + self.assertEqual(line, b"spam\n") + + gen = self.reader.read_line(32) + self.assertGeneratorRunning(gen) + self.reader.feed_data(b"gs\n") + line = self.assertGeneratorReturns(gen) + self.assertEqual(line, b"eggs\n") + + def test_read_line_not_enough_data(self): + self.reader.feed_data(b"spa") + self.reader.feed_eof() + + gen = self.reader.read_line(32) + with self.assertRaises(EOFError) as raised: + next(gen) + self.assertEqual( + str(raised.exception), + "stream ends after 3 bytes, before end of line", + ) + + def test_read_line_too_long(self): + self.reader.feed_data(b"spam\neggs\n") + + gen = self.reader.read_line(2) + with self.assertRaises(RuntimeError) as raised: + next(gen) + self.assertEqual( + str(raised.exception), + "read 5 bytes, expected no more than 2 bytes", + ) + + def test_read_line_too_long_need_more_data(self): + self.reader.feed_data(b"spa") + + gen = self.reader.read_line(2) + with self.assertRaises(RuntimeError) as raised: + next(gen) + self.assertEqual( + str(raised.exception), + "read 3 bytes, expected no more than 2 bytes", + ) + + def test_read_exact(self): + self.reader.feed_data(b"spameggs") + + gen = self.reader.read_exact(4) + data = self.assertGeneratorReturns(gen) + self.assertEqual(data, b"spam") + + gen = self.reader.read_exact(4) + data = self.assertGeneratorReturns(gen) + self.assertEqual(data, b"eggs") + + def test_read_exact_need_more_data(self): + self.reader.feed_data(b"spa") + + gen = self.reader.read_exact(4) + self.assertGeneratorRunning(gen) + self.reader.feed_data(b"meg") + data = self.assertGeneratorReturns(gen) + self.assertEqual(data, b"spam") + + gen = self.reader.read_exact(4) + self.assertGeneratorRunning(gen) + self.reader.feed_data(b"gs") + data = self.assertGeneratorReturns(gen) + self.assertEqual(data, b"eggs") + + def test_read_exact_not_enough_data(self): + self.reader.feed_data(b"spa") + self.reader.feed_eof() + + gen = self.reader.read_exact(4) + with self.assertRaises(EOFError) as raised: + next(gen) + self.assertEqual( + str(raised.exception), + "stream ends after 3 bytes, expected 4 bytes", + ) + + def test_read_to_eof(self): + gen = self.reader.read_to_eof(32) + + self.reader.feed_data(b"spam") + self.assertGeneratorRunning(gen) + + self.reader.feed_eof() + data = self.assertGeneratorReturns(gen) + self.assertEqual(data, b"spam") + + def test_read_to_eof_at_eof(self): + self.reader.feed_eof() + + gen = self.reader.read_to_eof(32) + data = self.assertGeneratorReturns(gen) + self.assertEqual(data, b"") + + def test_read_to_eof_too_long(self): + gen = self.reader.read_to_eof(2) + + self.reader.feed_data(b"spam") + with self.assertRaises(RuntimeError) as raised: + next(gen) + self.assertEqual( + str(raised.exception), + "read 4 bytes, expected no more than 2 bytes", + ) + + def test_at_eof_after_feed_data(self): + gen = self.reader.at_eof() + self.assertGeneratorRunning(gen) + self.reader.feed_data(b"spam") + eof = self.assertGeneratorReturns(gen) + self.assertFalse(eof) + + def test_at_eof_after_feed_eof(self): + gen = self.reader.at_eof() + self.assertGeneratorRunning(gen) + self.reader.feed_eof() + eof = self.assertGeneratorReturns(gen) + self.assertTrue(eof) + + def test_feed_data_after_feed_data(self): + self.reader.feed_data(b"spam") + self.reader.feed_data(b"eggs") + + gen = self.reader.read_exact(8) + data = self.assertGeneratorReturns(gen) + self.assertEqual(data, b"spameggs") + gen = self.reader.at_eof() + self.assertGeneratorRunning(gen) + + def test_feed_eof_after_feed_data(self): + self.reader.feed_data(b"spam") + self.reader.feed_eof() + + gen = self.reader.read_exact(4) + data = self.assertGeneratorReturns(gen) + self.assertEqual(data, b"spam") + gen = self.reader.at_eof() + eof = self.assertGeneratorReturns(gen) + self.assertTrue(eof) + + def test_feed_data_after_feed_eof(self): + self.reader.feed_eof() + with self.assertRaises(EOFError) as raised: + self.reader.feed_data(b"spam") + self.assertEqual( + str(raised.exception), + "stream ended", + ) + + def test_feed_eof_after_feed_eof(self): + self.reader.feed_eof() + with self.assertRaises(EOFError) as raised: + self.reader.feed_eof() + self.assertEqual( + str(raised.exception), + "stream ended", + ) + + def test_discard(self): + gen = self.reader.read_to_eof(32) + + self.reader.feed_data(b"spam") + self.reader.discard() + self.assertGeneratorRunning(gen) + + self.reader.feed_eof() + data = self.assertGeneratorReturns(gen) + self.assertEqual(data, b"") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_typing.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_typing.py new file mode 100644 index 0000000000000..202de840f359a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_typing.py @@ -0,0 +1 @@ +from websockets.typing import * diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_uri.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_uri.py new file mode 100644 index 0000000000000..8acc01c18716a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_uri.py @@ -0,0 +1,96 @@ +import unittest + +from websockets.exceptions import InvalidURI +from websockets.uri import * + + +VALID_URIS = [ + ( + "ws://localhost/", + WebSocketURI(False, "localhost", 80, "/", "", None, None), + ), + ( + "wss://localhost/", + WebSocketURI(True, "localhost", 443, "/", "", None, None), + ), + ( + "ws://localhost", + WebSocketURI(False, "localhost", 80, "", "", None, None), + ), + ( + "ws://localhost/path?query", + WebSocketURI(False, "localhost", 80, "/path", "query", None, None), + ), + ( + "ws://localhost/path;params", + WebSocketURI(False, "localhost", 80, "/path;params", "", None, None), + ), + ( + "WS://LOCALHOST/PATH?QUERY", + WebSocketURI(False, "localhost", 80, "/PATH", "QUERY", None, None), + ), + ( + "ws://user:pass@localhost/", + WebSocketURI(False, "localhost", 80, "/", "", "user", "pass"), + ), + ( + "ws://høst/", + WebSocketURI(False, "xn--hst-0na", 80, "/", "", None, None), + ), + ( + "ws://üser:påss@høst/πass?qùéry", + WebSocketURI( + False, + "xn--hst-0na", + 80, + "/%CF%80ass", + "q%C3%B9%C3%A9ry", + "%C3%BCser", + "p%C3%A5ss", + ), + ), +] + +INVALID_URIS = [ + "http://localhost/", + "https://localhost/", + "ws://localhost/path#fragment", + "ws://user@localhost/", + "ws:///path", +] + +RESOURCE_NAMES = [ + ("ws://localhost/", "/"), + ("ws://localhost", "/"), + ("ws://localhost/path?query", "/path?query"), + ("ws://høst/πass?qùéry", "/%CF%80ass?q%C3%B9%C3%A9ry"), +] + +USER_INFOS = [ + ("ws://localhost/", None), + ("ws://user:pass@localhost/", ("user", "pass")), + ("ws://üser:påss@høst/", ("%C3%BCser", "p%C3%A5ss")), +] + + +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) + + def test_resource_name(self): + for uri, resource_name in RESOURCE_NAMES: + with self.subTest(uri=uri): + self.assertEqual(parse_uri(uri).resource_name, resource_name) + + def test_user_info(self): + for uri, user_info in USER_INFOS: + with self.subTest(uri=uri): + self.assertEqual(parse_uri(uri).user_info, user_info) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_utils.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_utils.py new file mode 100644 index 0000000000000..678fcfe798ec7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_utils.py @@ -0,0 +1,103 @@ +import base64 +import itertools +import platform +import unittest + +from websockets.utils import accept_key, apply_mask as py_apply_mask, generate_key + + +# Test vector from RFC 6455 +KEY = "dGhlIHNhbXBsZSBub25jZQ==" +ACCEPT = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" + + +class UtilsTests(unittest.TestCase): + def test_generate_key(self): + key = generate_key() + self.assertEqual(len(base64.b64decode(key.encode())), 16) + + def test_accept_key(self): + self.assertEqual(accept_key(KEY), ACCEPT) + + +class ApplyMaskTests(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 mask_type in [bytes, bytearray]: + for data_in, mask, data_out in self.apply_mask_test_values: + data_in, mask = memoryview(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_non_contiguous_memoryview(self): + for mask_type in [bytes, bytearray]: + for data_in, mask, data_out in self.apply_mask_test_values: + data_in, mask = memoryview(data_in)[::-1], mask_type(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: + pass +else: + + class SpeedupsTests(ApplyMaskTests): + @staticmethod + def apply_mask(*args, **kwargs): + try: + return c_apply_mask(*args, **kwargs) + except NotImplementedError as exc: # pragma: no cover + # PyPy doesn't implement creating contiguous readonly buffer + # from non-contiguous. We don't care about this edge case. + if ( + platform.python_implementation() == "PyPy" + and "not implemented yet" in str(exc) + ): + raise unittest.SkipTest(str(exc)) + else: + raise diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/utils.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/utils.py new file mode 100644 index 0000000000000..2937a2f15e40a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/utils.py @@ -0,0 +1,88 @@ +import contextlib +import email.utils +import os +import pathlib +import platform +import tempfile +import time +import unittest +import warnings + + +# 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 + +CERTIFICATE = bytes(pathlib.Path(__file__).with_name("test_localhost.pem")) + + +DATE = email.utils.formatdate(usegmt=True) + + +# Unit for timeouts. May be increased on slow machines by setting the +# WEBSOCKETS_TESTS_TIMEOUT_FACTOR environment variable. +MS = 0.001 * float(os.environ.get("WEBSOCKETS_TESTS_TIMEOUT_FACTOR", "1")) + +# PyPy has a performance penalty for this test suite. +if platform.python_implementation() == "PyPy": # pragma: no cover + MS *= 5 + +# 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) + + +class GeneratorTestCase(unittest.TestCase): + """ + Base class for testing generator-based coroutines. + + """ + + def assertGeneratorRunning(self, gen): + """ + Check that a generator-based coroutine hasn't completed yet. + + """ + next(gen) + + def assertGeneratorReturns(self, gen): + """ + Check that a generator-based coroutine completes and return its value. + + """ + with self.assertRaises(StopIteration) as raised: + next(gen) + return raised.exception.value + + +class DeprecationTestCase(unittest.TestCase): + """ + Base class for testing deprecations. + + """ + + @contextlib.contextmanager + def assertDeprecationWarning(self, message): + """ + Check that a deprecation warning was raised with the given message. + + """ + with warnings.catch_warnings(record=True) as recorded_warnings: + warnings.simplefilter("always") + yield + + self.assertEqual(len(recorded_warnings), 1) + warning = recorded_warnings[0] + self.assertEqual(warning.category, DeprecationWarning) + self.assertEqual(str(warning.message), message) + + +@contextlib.contextmanager +def temp_unix_socket_path(): + with tempfile.TemporaryDirectory() as temp_dir: + yield str(pathlib.Path(temp_dir) / "websockets") diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/w3c-import.log new file mode 100644 index 0000000000000..45bba3a944b57 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/w3c-import.log @@ -0,0 +1,39 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/maxi_cov.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/protocol.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_auth.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_connection.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_datastructures.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_exceptions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_exports.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_frames.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_headers.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_http.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_http11.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_imports.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_localhost.cnf +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_localhost.pem +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_protocol.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_server.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_streams.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_typing.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_uri.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/test_utils.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tests/utils.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tox.ini b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tox.ini new file mode 100644 index 0000000000000..939d8c0cd807d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tox.ini @@ -0,0 +1,39 @@ +[tox] +envlist = + py37 + py38 + py39 + py310 + py311 + coverage + black + ruff + mypy + +[testenv] +commands = python -W error::DeprecationWarning -W error::PendingDeprecationWarning -m unittest {posargs} + +[testenv:coverage] +commands = + python -m coverage erase + python -m coverage run --source {envsitepackagesdir}/websockets,tests -m unittest {posargs} + python -m coverage report --show-missing --fail-under=100 +deps = coverage + +[testenv:maxi_cov] +commands = + python tests/maxi_cov.py {envsitepackagesdir} + python -m coverage report --show-missing --fail-under=100 +deps = coverage + +[testenv:black] +commands = black --check src tests +deps = black + +[testenv:ruff] +commands = ruff src tests +deps = ruff + +[testenv:mypy] +commands = mypy --strict src +deps = mypy diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/w3c-import.log new file mode 100644 index 0000000000000..a40fad642085f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/w3c-import.log @@ -0,0 +1,25 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/CODE_OF_CONDUCT.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/MANIFEST.in +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/Makefile +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/SECURITY.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/pyproject.toml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/setup.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/websockets/tox.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/docs/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/docs/w3c-import.log new file mode 100644 index 0000000000000..0cb69c2b8e01e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/docs/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/docs/conf.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/docs/history.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/docs/index.rst diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/w3c-import.log new file mode 100644 index 0000000000000..59a4f8d1c86c5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/w3c-import.log @@ -0,0 +1,29 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/CHANGES.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/PKG-INFO +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/README.rst +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/appveyor.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/pyproject.toml +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/setup.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/setup.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/skeleton.md +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/test_zipp.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/tox.ini +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/zipp.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/zipp.egg-info/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/zipp.egg-info/w3c-import.log new file mode 100644 index 0000000000000..faaf43709e402 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/zipp.egg-info/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/zipp.egg-info/PKG-INFO +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/zipp.egg-info/SOURCES.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/zipp.egg-info/dependency_links.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/zipp.egg-info/requires.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party/zipp/zipp.egg-info/top_level.txt diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/capture.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/capture.py index 75717d62c8a90..78da63be417f2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/capture.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/capture.py @@ -88,7 +88,7 @@ def __exit__(self, *args, **kwargs): while not self.logging_queue.empty(): try: self.logger.warning( - "Dropping log message: %r", self.logging_queue.get() + "Dropping log message: %r" % self.logging_queue.get() ) except Exception: pass diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/html/html.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/html/html.py index 040de1ae5381b..87fa4a638a7d8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/html/html.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/html/html.py @@ -6,7 +6,7 @@ import json import os from collections import defaultdict -from datetime import datetime +from datetime import datetime, timezone from .. import base @@ -242,7 +242,7 @@ def make_result_html(self, data): ) def generate_html(self): - generated = datetime.utcnow() + generated = datetime.now(timezone.utc) with open(os.path.join(base_path, "main.js")) as main_f: doc = html.html( self.head, diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/html/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/html/w3c-import.log new file mode 100644 index 0000000000000..f04fffbb96174 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/html/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/html/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/html/html.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/html/main.js +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/html/style.css +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/html/xmlgen.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/w3c-import.log new file mode 100644 index 0000000000000..5f6264e01171c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/w3c-import.log @@ -0,0 +1,25 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/base.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/errorsummary.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/grouping.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/machformatter.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/process.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/tbplformatter.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/unittest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/formatters/xunit.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/handlers/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/handlers/w3c-import.log new file mode 100644 index 0000000000000..d8219b4127ab2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/handlers/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/handlers/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/handlers/base.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/handlers/bufferhandler.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/handlers/messagehandler.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/handlers/statushandler.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/handlers/summaryhandler.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/handlers/valgrindhandler.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/pytest_mozlog/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/pytest_mozlog/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/pytest_mozlog/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/pytest_mozlog/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/pytest_mozlog/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/pytest_mozlog/w3c-import.log new file mode 100644 index 0000000000000..d4143fd44bdaa --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/pytest_mozlog/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/pytest_mozlog/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/pytest_mozlog/plugin.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/scripts/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/scripts/w3c-import.log new file mode 100644 index 0000000000000..6e38440a3b215 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/scripts/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/scripts/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/scripts/format.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/scripts/logmerge.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/scripts/unstable.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/unstructured/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/unstructured/w3c-import.log new file mode 100644 index 0000000000000..cbca3a948dc3e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/unstructured/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/unstructured/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/unstructured/logger.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/unstructured/loggingmixin.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/unstructured/loglistener.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/w3c-import.log new file mode 100644 index 0000000000000..0b2d25d1a9b2c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/w3c-import.log @@ -0,0 +1,24 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/capture.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/commandline.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/logtypes.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/proxy.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/reader.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/stdadapter.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/mozlog/structuredlog.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/tests/w3c-import.log new file mode 100644 index 0000000000000..3193e9758fbe9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/tests/w3c-import.log @@ -0,0 +1,25 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/tests/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/tests/manifest.ini +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/tests/test_capture.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/tests/test_errorsummary.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/tests/test_formatters.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/tests/test_logger.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/tests/test_logtypes.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/tests/test_structured.py +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/tests/test_terminal_colors.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/w3c-import.log new file mode 100644 index 0000000000000..67f62163a904f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/setup.cfg +/LayoutTests/imported/w3c/web-platform-tests/tools/third_party_modified/mozlog/setup.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/tox.ini b/LayoutTests/imported/w3c/web-platform-tests/tools/tox.ini index a4b747ac4fa1a..61442932f3552 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/tox.ini +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py37,py38,py39,py310,py311,{py37,py38,py39,py310,py311}-{flake8,mypy} +envlist = py38,py39,py310,py311,py312,{py38,py39,py310,py311,py312}-{flake8,mypy} skipsdist=True skip_missing_interpreters=False diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/w3c-import.log new file mode 100644 index 0000000000000..b09e082ef4c16 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/w3c-import.log @@ -0,0 +1,28 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/META.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/conftest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/flake8.ini +/LayoutTests/imported/w3c/web-platform-tests/tools/localpaths.py +/LayoutTests/imported/w3c/web-platform-tests/tools/mypy.ini +/LayoutTests/imported/w3c/web-platform-tests/tools/pytest.ini +/LayoutTests/imported/w3c/web-platform-tests/tools/requirements_flake8.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/requirements_mypy.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/requirements_pytest.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/requirements_tests.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/tox.ini diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/exceptions/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/exceptions/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/exceptions/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/exceptions/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/exceptions/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/exceptions/w3c-import.log new file mode 100644 index 0000000000000..04f9c06018598 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/exceptions/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/exceptions/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/exceptions/duplicate_exception.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/exceptions/invalid_data_exception.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/exceptions/not_found_exception.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/exceptions/permission_denied_exception.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/w3c-import.log new file mode 100644 index 0000000000000..6b0b556082b22 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/device.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/event_listener.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/http_polling_client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/http_polling_event_listener.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/data/session.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/w3c-import.log new file mode 100644 index 0000000000000..79e8c045e3ee0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/w3c-import.log @@ -0,0 +1,32 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/configuration_page_bottom.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/configuration_page_exclude_add_malfunctioning.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/configuration_page_exclude_add_prev_excluded.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/configuration_page_exclude_add_raw.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/configuration_page_top.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/landing_page.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/overview_page_sessions.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/overview_page_sessions_filtered.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/overview_page_sessions_pinned_recent.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/overview_page_top.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/results_page_api_results.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/results_page_api_results_export.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/results_page_bottom.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/results_page_last_timed_out.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/results_page_malfunctioning_list.jpg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/res/results_page_top.jpg diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/devices-api/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/devices-api/w3c-import.log new file mode 100644 index 0000000000000..c176ff7f229d2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/devices-api/w3c-import.log @@ -0,0 +1,24 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/devices-api/create.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/devices-api/event-types.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/devices-api/read-device.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/devices-api/read-devices.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/devices-api/register-global.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/devices-api/register.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/devices-api/send-event.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/devices-api/send-global-event.md diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/general-api/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/general-api/w3c-import.log new file mode 100644 index 0000000000000..b180a8822eb12 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/general-api/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/general-api/status.md diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/guides/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/guides/w3c-import.log new file mode 100644 index 0000000000000..8e89436b156f1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/guides/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/guides/README.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/guides/session-events.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/guides/session-start-devices-api.md diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/results-api/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/results-api/w3c-import.log new file mode 100644 index 0000000000000..b3b58ac7a8a07 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/results-api/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/results-api/config.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/results-api/create.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/results-api/download.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/results-api/import.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/results-api/read-compact.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/results-api/read.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/results-api/view.md diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/sessions-api/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/sessions-api/w3c-import.log new file mode 100644 index 0000000000000..21f2785350cdd --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/sessions-api/w3c-import.log @@ -0,0 +1,28 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/sessions-api/control.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/sessions-api/create.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/sessions-api/delete.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/sessions-api/event-types.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/sessions-api/events.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/sessions-api/find.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/sessions-api/labels.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/sessions-api/read-public.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/sessions-api/read.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/sessions-api/read_sessions.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/sessions-api/status.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/sessions-api/update.md diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/tests-api/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/tests-api/w3c-import.log new file mode 100644 index 0000000000000..78d0a1e93e5f0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/tests-api/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/tests-api/read-all.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/tests-api/read-available-apis.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/tests-api/read-last-completed.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/tests-api/read-malfunctioning.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/tests-api/read-next.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/tests-api/read-session.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/tests-api/update-malfunctioning.md diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/w3c-import.log new file mode 100644 index 0000000000000..ca792c053d489 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/rest-api/README.md diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/usage/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/usage/w3c-import.log new file mode 100644 index 0000000000000..28304cd8b12a9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/usage/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/usage/usage.md diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/w3c-import.log new file mode 100644 index 0000000000000..236531d168788 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/README.md +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/docs/config.md diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/css/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/css/w3c-import.log new file mode 100644 index 0000000000000..8988d0dc19f32 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/css/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/css/bulma.min.css +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/css/result.css diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/lib/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/lib/w3c-import.log new file mode 100644 index 0000000000000..463815c5fdfeb --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/lib/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/lib/ui.js +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/lib/utils.js diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/res/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/res/w3c-import.log new file mode 100644 index 0000000000000..c972e35d98e80 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/res/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/res/wavelogo_2016.jpg diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/w3c-import.log new file mode 100644 index 0000000000000..547e21f57e7c5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/export/index.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/api/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/api/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/api/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/api/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/api/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/api/w3c-import.log new file mode 100644 index 0000000000000..7a10ec39a0465 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/api/w3c-import.log @@ -0,0 +1,23 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/api/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/api/api_handler.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/api/devices_api_handler.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/api/general_api_handler.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/api/results_api_handler.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/api/sessions_api_handler.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/api/tests_api_handler.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/w3c-import.log new file mode 100644 index 0000000000000..219ffe923199b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/http_handler.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/network/static_handler.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/requirements.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/requirements.txt index b2c151b8fe96a..3bb476fd968b2 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/requirements.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/requirements.txt @@ -1,2 +1,2 @@ ua-parser==0.18.0 -python-dateutil==2.8.2 +python-dateutil==2.9.0.post0 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/resources/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/resources/w3c-import.log new file mode 100644 index 0000000000000..9f65a560fdcc2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/resources/w3c-import.log @@ -0,0 +1,17 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/resources/testharnessreport.js diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/test/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/test/w3c-import.log new file mode 100644 index 0000000000000..b45fa45a368d6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/test/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/test/WAVE Local.postman_environment.json +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/test/WAVE Server REST API Tests.postman_collection.json diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/testing/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/testing/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/testing/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/testing/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/testing/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/testing/w3c-import.log new file mode 100644 index 0000000000000..d0105811722fc --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/testing/w3c-import.log @@ -0,0 +1,24 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/testing/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/testing/devices_manager.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/testing/event_dispatcher.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/testing/results_manager.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/testing/sessions_manager.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/testing/test_loader.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/testing/tests_manager.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/testing/wpt_report.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tests/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tests/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tests/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tests/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tests/w3c-import.log new file mode 100644 index 0000000000000..e7d50dd0afa23 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tests/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tests/WAVE Local.postman_environment.json +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tests/WAVE Server REST API Tests.postman_collection.json +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tests/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tests/config.json +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tests/test_wave.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tox.ini b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tox.ini index 06bdfcd674383..88c76096f4598 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tox.ini +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py37,py38,py39,py310,py311 +envlist = py38,py39,py310,py311,py312 skipsdist=True skip_missing_interpreters = False diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/utils/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/utils/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/utils/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/utils/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/utils/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/utils/w3c-import.log new file mode 100644 index 0000000000000..489353334cc0b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/utils/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/utils/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/utils/deserializer.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/utils/serializer.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/utils/user_agent_parser.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/w3c-import.log new file mode 100644 index 0000000000000..944b2e6c59e0f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/w3c-import.log @@ -0,0 +1,24 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/config.default.json +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/configuration_loader.py +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/package-lock.json +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/package.json +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/requirements.txt +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/tox.ini +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/wave_server.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/bulma-0.7.5/bulma.css b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/bulma-0.7.5/bulma.css index e793432ae31c3..c1f0d51e41abd 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/bulma-0.7.5/bulma.css +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/bulma-0.7.5/bulma.css @@ -28,7 +28,7 @@ -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; - user-select: none; + -webkit-user-select: none; } .select:not(.is-multiple):not(.is-loading)::after, .navbar-link:not(.is-arrowless)::after { @@ -338,7 +338,7 @@ html { -webkit-text-size-adjust: 100%; -moz-text-size-adjust: 100%; -ms-text-size-adjust: 100%; - text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; } article, diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/bulma-0.7.5/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/bulma-0.7.5/w3c-import.log new file mode 100644 index 0000000000000..210898fcf67fb --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/bulma-0.7.5/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +user-select +text-size-adjust +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/bulma-0.7.5/bulma.css +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/bulma-0.7.5/bulma.css.map +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/bulma-0.7.5/bulma.min.css diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/w3c-import.log new file mode 100644 index 0000000000000..d86cbf043db31 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/fontawesome-5.7.2.min.css +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/fontawesome.min.css +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/main.css +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/result.css +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/css/style.css diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/davidshimjs/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/davidshimjs/w3c-import.log new file mode 100644 index 0000000000000..cef20311bab17 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/davidshimjs/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/davidshimjs/LICENSE +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/davidshimjs/qrcode.js diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/w3c-import.log new file mode 100644 index 0000000000000..aee78141625b7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/w3c-import.log @@ -0,0 +1,24 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/jszip.min.js +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/keycodes.js +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/qrcode.js +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/query-parser.js +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/screen-console.js +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/ui.js +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/utils.js +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/lib/wave-service.js diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/res/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/res/w3c-import.log new file mode 100644 index 0000000000000..c5e8052c6e96f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/res/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/res/spinner-solid.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/res/wavelogo_2016.jpg diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/w3c-import.log new file mode 100644 index 0000000000000..2b20373ddc594 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/w3c-import.log @@ -0,0 +1,28 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/comparison.html +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/configuration.html +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/favicon.ico +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/finish.html +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/index.html +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/newsession.html +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/next.html +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/overview.html +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/pause.html +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/results.html +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/submitresult.html +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/test.html diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/w3c-import.log new file mode 100644 index 0000000000000..47694d4fd81f6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/w3c-import.log @@ -0,0 +1,31 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/fa-brands-400.eot +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/fa-brands-400.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/fa-brands-400.ttf +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/fa-brands-400.woff +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/fa-brands-400.woff2 +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/fa-regular-400.eot +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/fa-regular-400.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/fa-regular-400.ttf +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/fa-regular-400.woff +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/fa-regular-400.woff2 +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/fa-solid-900.eot +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/fa-solid-900.svg +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/fa-solid-900.ttf +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/fa-solid-900.woff +/LayoutTests/imported/w3c/web-platform-tests/tools/wave/www/webfonts/fa-solid-900.woff2 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/MANIFEST_SCHEMA.json b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/MANIFEST_SCHEMA.json new file mode 100644 index 0000000000000..9fe4b68eb4587 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/MANIFEST_SCHEMA.json @@ -0,0 +1,35 @@ +{ + "$schema":"http://json-schema.org/draft-06/schema#", + "$ref":"#/definitions/File", + "definitions":{ + "File":{ + "type":"object", + "additionalProperties":false, + "properties":{ + "version":{ + "type":"integer", + "description":"Schema version of the file.", + "enum":[ + 1 + ] + }, + "data":{ + "type":"object", + "description":"High level container for the data. Object key is the web-features identifier.", + "additionalProperties":{ + "type":"array", + "items":{ + "type":"string", + "description":"The url field in tools.manifest.item.URLManifestItem" + } + } + } + }, + "required":[ + "data", + "version" + ], + "title":"File" + } + } +} \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/__init__.py new file mode 100644 index 0000000000000..a6834b8285a56 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/commands.json b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/commands.json new file mode 100644 index 0000000000000..9a54b1b00da67 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/commands.json @@ -0,0 +1,12 @@ +{ + "web-features-manifest": { + "path": "manifest.py", + "script": "main", + "parser": "create_parser", + "help": "Create the WEB_FEATURES_MANIFEST.json", + "virtualenv": true, + "requirements": [ + "../metadata/yaml/requirements.txt" + ] + } +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/manifest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/manifest.py new file mode 100644 index 0000000000000..15ec5362b4e81 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/manifest.py @@ -0,0 +1,206 @@ +import argparse +import json +import logging +import os + +from dataclasses import dataclass +from pathlib import Path +from typing import Any, List, Optional + +from ..manifest.item import SupportFile +from ..manifest.sourcefile import SourceFile +from ..metadata.yaml.load import load_data_to_dict +from ..web_features.web_feature_map import WebFeatureToTestsDirMapper, WebFeaturesMap +from .. import localpaths +from ..metadata.webfeatures.schema import WEB_FEATURES_YML_FILENAME, WebFeaturesFile + +""" +This command generates a manifest file containing a mapping of web-feature +identifiers to test paths. + +The web-feature identifiers are sourced from https://github.com/web-platform-dx/web-features. +They are used in WEB_FEATURES.yml files located throughout the WPT source code. +Each file defines which test files correspond to a specific identifier. +Refer to RFC 163 (https://github.com/web-platform-tests/rfcs/pull/163) for more +file details. + +This command processes all WEB_FEATURES.yml files, extracts the list of test +paths from the test files, and writes them to a manifest file. The manifest +file maps web-feature identifiers to their corresponding test paths. + +The file written is a JSON file. An example file looks like: + +{ + "version": 1, + "data": { + "async-clipboard": [ + "/clipboard-apis/async-custom-formats-write-fail.tentative.https.html", + "/clipboard-apis/async-custom-formats-write-read-web-prefix.tentative.https.html" + ], + "idle-detection": [ + "/idle-detection/basics.tentative.https.window.html", + "/idle-detection/idle-detection-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html" + ] + } +} + + +The JSON Schema for the file format can be found at MANIFEST_SCHEMA.json + +This file does not follow the same format as the original manifest file, +MANIFEST.json. +""" + +logger = logging.getLogger(__name__) + +MANIFEST_FILE_NAME = "WEB_FEATURES_MANIFEST.json" + + +def abs_path(path: str) -> str: + return os.path.abspath(os.path.expanduser(path)) + +def create_parser() -> argparse.ArgumentParser: + """ + Creates an argument parser for the script. + + Returns: + argparse.ArgumentParser: The configured argument parser. + """ + parser = argparse.ArgumentParser( + description="Maps tests to web-features within a repo root." + ) + parser.add_argument( + "-p", "--path", type=abs_path, help="Path to manifest file.") + return parser + + +def find_all_test_files_in_dir(root_dir: str, rel_dir_path: str, url_base: str) -> List[SourceFile]: + """ + Finds all test files within a given directory. + + Ignores any SourceFiles that are marked as non_test or the type + is SupportFile.item_type + + Args: + root_dir (str): The root directory of the repository. + rel_dir_path (str): The relative path of the directory to search. + url_base (str): Base url to use as the mount point for tests in this manifest. + + Returns: + List[SourceFile]: A list of SourceFile objects representing the found test files. + """ + rv: List[SourceFile] = [] + full_dir_path = os.path.join(root_dir, rel_dir_path) + for file in os.listdir(full_dir_path): + full_path = os.path.join(full_dir_path, file) + rel_file_path = os.path.relpath(full_path, root_dir) + source_file = SourceFile(root_dir, rel_file_path, url_base) + if not source_file.name_is_non_test and source_file.type != SupportFile.item_type: + rv.append(source_file) + return rv + +@dataclass +class CmdConfig(): + """ + Configuration for the command-line options. + """ + + repo_root: str # The root directory of the WPT repository + url_base: str # Base URL used when converting file paths to urls + + +def map_tests_to_web_features( + cmd_cfg: CmdConfig, + rel_dir_path: str, + result: WebFeaturesMap, + prev_inherited_features: List[str] = []) -> None: + """ + Recursively maps tests to web-features within a directory structure. + + Args: + cmd_cfg (CmdConfig): The configuration for the command-line options. + rel_dir_path (str): The relative path of the directory to process. + result (WebFeaturesMap): The object to store the mapping results. + prev_inherited_features (List[str], optional): A list of inherited web-features from parent directories. Defaults to []. + """ + # Sometimes it will add a . at the beginning. Let's resolve the absolute path to disambiguate. + # current_path = Path(os.path.join(cmd_cfg.repo_root, rel_dir_path)).resolve() + current_dir = str(Path(os.path.join(cmd_cfg.repo_root, rel_dir_path)).resolve()) + + # Create a copy that may be built upon or cleared during this iteration. + inherited_features = prev_inherited_features.copy() + + rel_dir_path = os.path.relpath(current_dir, cmd_cfg.repo_root) + + web_feature_yml_full_path = os.path.join(current_dir, WEB_FEATURES_YML_FILENAME) + web_feature_file: Optional[WebFeaturesFile] = None + if os.path.isfile(web_feature_yml_full_path): + try: + web_feature_file = WebFeaturesFile(load_data_to_dict( + open(web_feature_yml_full_path, "rb"))) + except Exception as e: + raise e + + WebFeatureToTestsDirMapper( + find_all_test_files_in_dir(cmd_cfg.repo_root, rel_dir_path, cmd_cfg.url_base), + web_feature_file + ).run(result, inherited_features) + + sub_dirs = [f for f in os.listdir(current_dir) if os.path.isdir(os.path.join(current_dir, f))] + for sub_dir in sub_dirs: + map_tests_to_web_features( + cmd_cfg, + os.path.join(rel_dir_path, sub_dir), + result, + inherited_features + ) + +class WebFeatureManifestEncoder(json.JSONEncoder): + """ + Custom JSON encoder. + + WebFeaturesMap contains a dictionary where the value is of type set. + Sets cannot serialize to JSON by default. This encoder handles that by + calling WebFeaturesMap's to_dict() method. + """ + def default(self, obj: Any) -> Any: + if isinstance(obj, WebFeaturesMap): + return obj.to_dict() + return super().default(obj) + + +def write_manifest_file(path: str, web_features_map: WebFeaturesMap) -> None: + """ + Serializes the WebFeaturesMap to a JSON manifest file at the specified path. + + The generated JSON file adheres to the schema defined in the "MANIFEST_SCHEMA.json" file. The + serialization process uses the custom `WebFeatureManifestEncoder` to ensure correct formatting. + + Args: + path (str): The file path where the manifest file will be created or overwritten. + web_features_map (WebFeaturesMap): The object containing the mapping between + web-features and their corresponding test paths. + """ + with open(path, "w") as outfile: + outfile.write( + json.dumps( + { + "version": 1, + "data": web_features_map + }, cls=WebFeatureManifestEncoder, sort_keys=True)) + + +def main(venv: Any = None, **kwargs: Any) -> int: + + assert logger is not None + + repo_root = localpaths.repo_root + url_base = "/" + path = kwargs.get("path") or os.path.join(repo_root, MANIFEST_FILE_NAME) + + cmd_cfg = CmdConfig(repo_root, url_base) + feature_map = WebFeaturesMap() + map_tests_to_web_features(cmd_cfg, "", feature_map) + write_manifest_file(path, feature_map) + + return 0 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/tests/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/tests/__init__.py new file mode 100644 index 0000000000000..a6834b8285a56 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/tests/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/tests/test_manifest.py b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/tests/test_manifest.py new file mode 100644 index 0000000000000..b83829308a238 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/tests/test_manifest.py @@ -0,0 +1,259 @@ +# mypy: ignore-errors + +import json +import os +from jsonschema import validate +from unittest.mock import ANY, Mock, call, mock_open, patch + +import pytest + +from ..manifest import create_parser, find_all_test_files_in_dir, main, map_tests_to_web_features, write_manifest_file, CmdConfig +from ..web_feature_map import WebFeatureToTestsDirMapper, WebFeaturesMap +from ...metadata.webfeatures.schema import WEB_FEATURES_YML_FILENAME +from ...manifest.sourcefile import SourceFile +from ...manifest.item import SupportFile, URLManifestItem +from ... import localpaths + + +@patch("os.listdir") +@patch("tools.web_features.manifest.SourceFile") +def test_find_all_test_files_in_dir(mock_source_file_class, mock_listdir): + mock_listdir.return_value = ["test1.html", "support.py", "test2.html", "test3.html"] + + def create_source_file_mock(root_dir, rel_file_path, separator): + source_file = Mock(spec=SourceFile) + if rel_file_path.endswith("support.py"): + source_file.name_is_non_test = True + source_file.type = SupportFile.item_type + else: + source_file.name_is_non_test = False + return source_file + + mock_source_file_class.side_effect = create_source_file_mock + + test_files = find_all_test_files_in_dir("root_dir", "rel_dir_path", "/") + + # Assert calls to the mocked constructor with expected arguments + mock_source_file_class.assert_has_calls([ + call("root_dir", os.path.join("rel_dir_path", "test1.html"), "/"), + call("root_dir", os.path.join("rel_dir_path", "support.py"), "/"), + call("root_dir", os.path.join("rel_dir_path", "test2.html"), "/"), + call("root_dir", os.path.join("rel_dir_path", "test3.html"), "/"), + ]) + assert mock_source_file_class.call_count == 4 + + + # Assert attributes of the resulting test files + assert all( + not file.name_is_non_test and file.type != SupportFile.item_type + for file in test_files + ) + + # Should only have 3 items instead of the original 4 + assert len(test_files) == 3 + +@patch("builtins.open", new_callable=mock_open, read_data="data") +@patch("os.listdir") +@patch("os.path.isdir") +@patch("os.path.isfile") +@patch("tools.web_features.manifest.load_data_to_dict", return_value={}) +@patch("tools.web_features.manifest.find_all_test_files_in_dir") +@patch("tools.web_features.manifest.WebFeaturesFile") +@patch("tools.web_features.manifest.WebFeatureToTestsDirMapper", spec=WebFeatureToTestsDirMapper) +def test_map_tests_to_web_features_recursive( + mock_mapper, + mock_web_features_file, + mock_find_all_test_files_in_dir, + mock_load_data_to_dict, + mock_isfile, + mock_isdir, + mock_listdir, + mock_file +): + def fake_listdir(path): + if path.endswith("repo_root"): + return ["subdir1", "subdir2"] + elif path.endswith(os.path.join("repo_root", "subdir1")): + return ["subdir1_1", "subdir1_2", WEB_FEATURES_YML_FILENAME] + elif path.endswith(os.path.join("repo_root", "subdir1", "subdir1_1")): + return [WEB_FEATURES_YML_FILENAME] + elif path.endswith(os.path.join("repo_root", "subdir1", "subdir1_2")): + return [] + elif path.endswith(os.path.join("repo_root", "subdir2")): + return [WEB_FEATURES_YML_FILENAME] + else: + [] + mock_listdir.side_effect = fake_listdir + + def fake_isdir(path): + if (path.endswith(os.path.join("repo_root", "subdir1")) or + path.endswith(os.path.join("repo_root", "subdir1", "subdir1_1")) or + path.endswith(os.path.join("repo_root", "subdir1", "subdir1_2")) or + path.endswith(os.path.join("repo_root", "subdir2"))): + return True + return False + mock_isdir.side_effect = fake_isdir + + def fake_isfile(path): + if (path.endswith(os.path.join("repo_root", "subdir1", "WEB_FEATURES.yml")) or + path.endswith(os.path.join("repo_root", "subdir1", "subdir1_1", "WEB_FEATURES.yml")) or + path.endswith(os.path.join("repo_root", "subdir2", "WEB_FEATURES.yml"))): + return True + return False + mock_isfile.side_effect = fake_isfile + + + expected_root_files = [ + Mock(name="root_test_1"), + ] + + expected_subdir1_files = [ + Mock(name="subdir1_test_1"), + Mock(name="subdir1_test_2"), + ] + + expected_subdir2_files = [ + Mock(name="subdir2_test_1"), + ] + + expected_subdir1_1_files = [ + Mock(name="subdir1_1_test_1"), + Mock(name="subdir1_1_test_2"), + ] + + expected_subdir1_2_files = [ + Mock(name="subdir1_2_test_1"), + Mock(name="subdir1_2_test_2"), + ] + + expected_subdir1_web_feature_file = Mock() + expected_subdir1_1_web_feature_file = Mock() + expected_subdir2_web_feature_file = Mock() + mock_web_features_file.side_effect = [ + expected_subdir1_web_feature_file, + expected_subdir1_1_web_feature_file, + expected_subdir2_web_feature_file, + ] + + def fake_find_all_test_files_in_dir(root, rel_path, url_root): + # All cases should use url_root == "/" + if url_root != "/": + return None + elif (root == "repo_root" and rel_path == "."): + return expected_root_files + elif (root == "repo_root" and rel_path == "subdir1"): + return expected_subdir1_files + elif (root == "repo_root" and rel_path == os.path.join("subdir1", "subdir1_1")): + return expected_subdir1_1_files + elif (root == "repo_root" and rel_path == os.path.join("subdir1", "subdir1_2")): + return expected_subdir1_2_files + elif (root == "repo_root" and rel_path == "subdir2"): + return expected_subdir2_files + mock_find_all_test_files_in_dir.side_effect = fake_find_all_test_files_in_dir + cmd_cfg = CmdConfig("repo_root", "/") + result = WebFeaturesMap() + + map_tests_to_web_features(cmd_cfg, "", result) + + assert mock_isfile.call_count == 5 + assert mock_mapper.call_count == 5 + + # Check for the constructor calls. + # In between also assert that the run() call is executed. + mock_mapper.assert_has_calls([ + call(expected_root_files, None), + call().run(ANY, []), + call(expected_subdir1_files, expected_subdir1_web_feature_file), + call().run(ANY, []), + call(expected_subdir1_1_files, expected_subdir1_1_web_feature_file), + call().run(ANY, []), + call(expected_subdir1_2_files, None), + call().run(ANY, []), + call(expected_subdir2_files, expected_subdir2_web_feature_file), + call().run(ANY, []), + ]) + + + # Only five times to the constructor + assert mock_mapper.call_count == 5 + + +def test_parser_with_path_provided_abs_path(): + parser = create_parser() + args = parser.parse_args(["--path", f"{os.path.abspath(os.sep)}manifest-path"]) + assert args.path == f"{os.path.abspath(os.sep)}manifest-path" + +def populate_test_web_features_map(web_features_map): + web_features_map.add("grid", [ + Mock(spec=URLManifestItem, url="/grid_test1.js"), + Mock(spec=URLManifestItem, url="/grid_test2.js"), + ]) + web_features_map.add("avif", [Mock(spec=URLManifestItem, url="/avif_test1.js")]) + + +def test_valid_schema(): + with open(os.path.join(os.path.dirname(__file__), '..', 'MANIFEST_SCHEMA.json'), 'r') as schema_file: + schema_dict = json.load(schema_file) + + web_features_map = WebFeaturesMap() + populate_test_web_features_map(web_features_map) + + with patch('builtins.open', new_callable=mock_open) as mock_file: + write_manifest_file("test_file.json", web_features_map) + mock_file.assert_called_once_with("test_file.json", "w") + mock_file.return_value.write.assert_called_once_with( + '{"data": {"avif": ["/avif_test1.js"], "grid": ["/grid_test1.js", "/grid_test2.js"]}, "version": 1}') + args = mock_file.return_value.write.call_args + file_dict = json.loads(args[0][0]) + # Should not throw an exception + try: + validate(file_dict, schema_dict) + except Exception as e: + assert False, f"'validate' raised an exception {e}" + + +@pytest.mark.parametrize('main_kwargs,expected_repo_root,expected_path', [ + # No flags. All default + ( + {}, + localpaths.repo_root, + os.path.join(localpaths.repo_root, "WEB_FEATURES_MANIFEST.json") + ), + # Provide the path flag + ( + { + "path": os.path.join(os.sep, "test_path", "WEB_FEATURES_MANIFEST.json"), + }, + localpaths.repo_root, + os.path.join(os.sep, "test_path", "WEB_FEATURES_MANIFEST.json") + ), +]) +@patch("tools.web_features.manifest.map_tests_to_web_features") +@patch("tools.web_features.manifest.write_manifest_file") +def test_main( + mock_write_manifest_file, + mock_map_tests_to_web_features, + main_kwargs, + expected_repo_root, + expected_path): + + def fake_map_tests_to_web_features( + cmd_cfg, + rel_dir_path, + result, + prev_inherited_features = []): + populate_test_web_features_map(result) + + default_kwargs = {"url_base": "/"} + main_kwargs.update(default_kwargs) + mock_map_tests_to_web_features.side_effect = fake_map_tests_to_web_features + main(**main_kwargs) + mock_map_tests_to_web_features.assert_called_once_with(CmdConfig(repo_root=expected_repo_root, url_base="/"), "", ANY) + mock_write_manifest_file.assert_called_once() + args = mock_write_manifest_file.call_args + path = args[0][0] + file = args[0][1] + assert path == expected_path + assert file.to_dict() == { + 'avif': ['/avif_test1.js'], + 'grid': ['/grid_test1.js', '/grid_test2.js']} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/tests/test_web_feature_map.py b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/tests/test_web_feature_map.py new file mode 100644 index 0000000000000..06afa181fe570 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/tests/test_web_feature_map.py @@ -0,0 +1,157 @@ +# mypy: allow-untyped-defs + +from unittest.mock import Mock, patch + +from ...manifest.item import URLManifestItem +from ...metadata.webfeatures.schema import FeatureFile +from ..web_feature_map import WebFeaturesMap, WebFeatureToTestsDirMapper + + +TEST_FILES = [ + Mock( + path="root/blob-range.any.js", + manifest_items=Mock( + return_value=( + None, + [ + Mock(spec=URLManifestItem, url="/root/blob-range.any.html"), + Mock(spec=URLManifestItem, url="/root/blob-range.any.worker.html"), + ]) + ) + ), + Mock( + path="root/foo-range.any.js", + manifest_items=Mock( + return_value=( + None, + [ + Mock(spec=URLManifestItem, url="/root/foo-range.any.html"), + Mock(spec=URLManifestItem, url="/root/foo-range.any.worker.html"), + ]) + ) + ), +] + +def test_process_recursive_feature(): + mapper = WebFeatureToTestsDirMapper(TEST_FILES, None) + result = WebFeaturesMap() + inherited_features = [] + + feature_entry = Mock() + feature_entry.name = "grid" + mapper._process_recursive_feature(inherited_features, feature_entry, result) + + assert result.to_dict() == { + "grid": [ + "/root/blob-range.any.html", + "/root/blob-range.any.worker.html", + "/root/foo-range.any.html", + "/root/foo-range.any.worker.html", + ], + } + assert inherited_features == ["grid"] + + +def test_process_non_recursive_feature(): + feature_name = "feature1" + feature_files = [ + FeatureFile("blob-range.any.js"), # Matches blob-range.any.js + FeatureFile("blob-range.html"), # Doesn't match any test file + ] + + mapper = WebFeatureToTestsDirMapper(TEST_FILES, None) + result = WebFeaturesMap() + + mapper._process_non_recursive_feature(feature_name, feature_files, result) + + assert result.to_dict() == { + "feature1": [ + "/root/blob-range.any.html", + "/root/blob-range.any.worker.html", + ] + } + + +def test_process_inherited_features(): + mapper = WebFeatureToTestsDirMapper(TEST_FILES, None) + result = WebFeaturesMap() + result.add("avif", [ + Mock(spec=URLManifestItem, path="root/bar-range.any.html", url="/root/bar-range.any.html"), + Mock(spec=URLManifestItem, path="root/bar-range.any.worker.html", url="/root/bar-range.any.worker.html"), + ]) + inherited_features = ["avif", "grid"] + + mapper._process_inherited_features(inherited_features, result) + + assert result.to_dict() == { + "avif": [ + "/root/bar-range.any.html", + "/root/bar-range.any.worker.html", + "/root/blob-range.any.html", + "/root/blob-range.any.worker.html", + "/root/foo-range.any.html", + "/root/foo-range.any.worker.html", + ], + "grid": [ + "/root/blob-range.any.html", + "/root/blob-range.any.worker.html", + "/root/foo-range.any.html", + "/root/foo-range.any.worker.html", + ], + } + assert inherited_features == ["avif", "grid"] + +def create_feature_entry(name, recursive=False, files=None): + rv = Mock(does_feature_apply_recursively=Mock(return_value=recursive)) + rv.name = name + rv.files = files + return rv + + +@patch("tools.web_features.web_feature_map.WebFeatureToTestsDirMapper._process_recursive_feature") +@patch("tools.web_features.web_feature_map.WebFeatureToTestsDirMapper._process_non_recursive_feature") +@patch("tools.web_features.web_feature_map.WebFeatureToTestsDirMapper._process_inherited_features") +def test_run_with_web_feature_file( + _process_inherited_features, + _process_non_recursive_feature, + _process_recursive_feature): + feature_entry1 = create_feature_entry("feature1", True) + feature_entry2 = create_feature_entry("feature2", files=[FeatureFile("test_file1.py")]) + mock_web_feature_file = Mock( + features=[ + feature_entry1, + feature_entry2, + ]) + mapper = WebFeatureToTestsDirMapper(TEST_FILES, mock_web_feature_file) + + + result = WebFeaturesMap() + mapper.run(result, ["foo", "bar"]) + + _process_recursive_feature.assert_called_once_with( + [], feature_entry1, result + ) + _process_non_recursive_feature.assert_called_once_with( + "feature2", [FeatureFile("test_file1.py")], result + ) + + assert not _process_inherited_features.called + +@patch("tools.web_features.web_feature_map.WebFeatureToTestsDirMapper._process_recursive_feature") +@patch("tools.web_features.web_feature_map.WebFeatureToTestsDirMapper._process_non_recursive_feature") +@patch("tools.web_features.web_feature_map.WebFeatureToTestsDirMapper._process_inherited_features") +def test_run_without_web_feature_file( + _process_inherited_features, + _process_non_recursive_feature, + _process_recursive_feature): + mapper = WebFeatureToTestsDirMapper(TEST_FILES, None) + + result = WebFeaturesMap() + mapper.run(result, ["foo", "bar"]) + + assert not _process_recursive_feature.called + assert not _process_non_recursive_feature.called + + _process_inherited_features.assert_called_once_with( + ["foo", "bar"], result + ) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/tests/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/tests/w3c-import.log new file mode 100644 index 0000000000000..a63e78a965e42 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/tests/w3c-import.log @@ -0,0 +1,19 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/tests/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/tests/test_manifest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/tests/test_web_feature_map.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/w3c-import.log new file mode 100644 index 0000000000000..22d1bb340de88 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/MANIFEST_SCHEMA.json +/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/commands.json +/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/manifest.py +/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/web_feature_map.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/web_feature_map.py b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/web_feature_map.py new file mode 100644 index 0000000000000..d66b07e1146f9 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/web_features/web_feature_map.py @@ -0,0 +1,119 @@ +import itertools + +from collections import OrderedDict +from os.path import basename +from typing import Dict, List, Optional, Sequence, Set + +from ..manifest.item import ManifestItem, URLManifestItem +from ..manifest.sourcefile import SourceFile +from ..metadata.webfeatures.schema import FeatureEntry, FeatureFile, WebFeaturesFile + + +class WebFeaturesMap: + """ + Stores a mapping of web-features to their associated test paths. + """ + + def __init__(self) -> None: + """ + Initializes the WebFeaturesMap with an OrderedDict to maintain feature order. + """ + self._feature_tests_map_: OrderedDict[str, Set[str]] = OrderedDict() + + + def add(self, feature: str, manifest_items: List[ManifestItem]) -> None: + """ + Adds a web feature and its associated test paths to the map. + + Args: + feature: The web-features identifier. + manifest_items: The ManifestItem objects representing the test paths. + """ + tests = self._feature_tests_map_.get(feature, set()) + self._feature_tests_map_[feature] = tests.union([ + manifest_item.url for manifest_item in manifest_items if isinstance(manifest_item, URLManifestItem)]) + + + def to_dict(self) -> Dict[str, List[str]]: + """ + Returns: + The plain dictionary representation of the map. + """ + rv: Dict[str, List[str]] = {} + for feature, manifest_items in self._feature_tests_map_.items(): + # Sort the list to keep output stable + rv[feature] = sorted(manifest_items) + return rv + + +class WebFeatureToTestsDirMapper: + """ + Maps web-features to tests within a specified directory. + """ + + def __init__( + self, + all_test_files_in_dir: List[SourceFile], + web_feature_file: Optional[WebFeaturesFile]): + """ + Initializes the mapper with test paths and web feature information. + """ + + self.all_test_files_in_dir = all_test_files_in_dir + self.test_path_to_manifest_items_map = dict([(basename(f.path), f.manifest_items()[1]) for f in self.all_test_files_in_dir]) + # Used to check if the current directory has a WEB_FEATURE_FILENAME + self.web_feature_file = web_feature_file + # Gets the manifest items for each test path and returns them into a single list. + self. get_all_manifest_items_for_dir = list(itertools.chain.from_iterable([ + items for _, items in self.test_path_to_manifest_items_map.items()])) + + + def _process_inherited_features( + self, + inherited_features: List[str], + result: WebFeaturesMap) -> None: + # No WEB_FEATURE.yml in this directory. Simply add the current features to the inherited features + for inherited_feature in inherited_features: + result.add(inherited_feature, self.get_all_manifest_items_for_dir) + + def _process_recursive_feature( + self, + inherited_features: List[str], + feature: FeatureEntry, + result: WebFeaturesMap) -> None: + inherited_features.append(feature.name) + result.add(feature.name, self.get_all_manifest_items_for_dir) + + def _process_non_recursive_feature( + self, + feature_name: str, + files: Sequence[FeatureFile], + result: WebFeaturesMap) -> None: + # If the feature does not apply recursively, look at the individual + # files and match them against all_test_files_in_dir. + test_file_paths: List[ManifestItem] = [] + base_test_file_names = [basename(f.path) for f in self.all_test_files_in_dir] + for test_file in files: + matched_base_file_names = test_file.match_files(base_test_file_names) + test_file_paths.extend(itertools.chain.from_iterable([ + self.test_path_to_manifest_items_map[f] for f in matched_base_file_names])) + + result.add(feature_name, test_file_paths) + + def run(self, result: WebFeaturesMap, inherited_features: List[str]) -> None: + if self.web_feature_file: + # Do not copy the inherited features because the presence of a + # WEB_FEATURES.yml file indicates new instructions. + inherited_features.clear() + + # Iterate over all the features in this new file + for feature in self.web_feature_file.features: + # Handle the "**" case + if feature.does_feature_apply_recursively(): + self._process_recursive_feature(inherited_features, feature, result) + + # Handle the non recursive case. + elif isinstance(feature.files, List) and feature.files: + self._process_non_recursive_feature(feature.name, feature.files, result) + else: + self._process_inherited_features(inherited_features, result) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/w3c-import.log new file mode 100644 index 0000000000000..195dbc732efd7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/w3c-import.log @@ -0,0 +1,18 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/README.md +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/setup.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/client.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/client.py index a9637c9cfa422..ee3ef1ea95d5a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/client.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/client.py @@ -94,8 +94,10 @@ def __init__(self, self.browsing_context = modules.BrowsingContext(self) self.input = modules.Input(self) self.network = modules.Network(self) + self.permissions = modules.Permissions(self) self.script = modules.Script(self) self.session = modules.Session(self) + self.storage = modules.Storage(self) @property def event_loop(self): @@ -203,15 +205,15 @@ async def on_message(self, data: Mapping[str, Any]) -> None: if not listeners: listeners = self.event_listeners.get(None, []) for listener in listeners: - await listener(data["method"], data["params"]) + asyncio.create_task(listener(data["method"], data["params"])) else: raise ValueError(f"Unexpected message: {data!r}") async def end(self) -> None: """Close websocket connection.""" - assert self.transport is not None - await self.transport.end() - self.transport = None + if self.transport is not None: + await self.transport.end() + self.transport = None def add_event_listener( self, diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/error.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/error.py index 7e411d3b83ba8..c77b2e41b3b9b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/error.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/error.py @@ -12,14 +12,14 @@ class BidiException(Exception): error_code: ClassVar[str] def __init__(self, message: str, stacktrace: Optional[str] = None): - super() + super().__init__() self.message = message self.stacktrace = stacktrace def __repr__(self): """Return the object representation in string format.""" - return f"{self.__class__.__name__}({self.error}, {self.message}, {self.stacktrace})" + return f"{self.__class__.__name__}({self.error_code}, {self.message}, {self.stacktrace})" def __str__(self): """Return the string representation of the object.""" @@ -83,10 +83,26 @@ class NoSuchScriptException(BidiException): error_code = "no such script" +class NoSuchUserContextException(BidiException): + error_code = "no such user context" + + class UnableToCaptureScreenException(BidiException): error_code = "unable to capture screen" +class UnableToSetCookieException(BidiException): + error_code = "unable to set cookie" + + +class UnableToSetFileInputException(BidiException): + error_code = "unable to set file input" + + +class UnderspecifiedStoragePartitionException(BidiException): + error_code = "underspecified storage partition" + + class UnknownCommandException(BidiException): error_code = "unknown command" diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/__init__.py index acbe117902e46..0a2ef500c42ce 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/__init__.py @@ -4,5 +4,7 @@ from .browsing_context import BrowsingContext from .input import Input from .network import Network +from .permissions import Permissions from .script import Script from .session import Session +from .storage import Storage diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/browser.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/browser.py index adb9779950549..8c5a29a53c87c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/browser.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/browser.py @@ -1,4 +1,4 @@ -from typing import Any, Mapping +from typing import Any, Mapping, MutableMapping from ._module import BidiModule, command @@ -7,3 +7,38 @@ class Browser(BidiModule): @command def close(self) -> Mapping[str, Any]: return {} + + @command + def create_user_context(self) -> Mapping[str, Any]: + return {} + + @create_user_context.result + def _create_user_context(self, result: Mapping[str, Any]) -> Any: + assert result["userContext"] is not None + assert isinstance(result["userContext"], str) + + return result["userContext"] + + @command + def get_user_contexts(self) -> Mapping[str, Any]: + return {} + + @get_user_contexts.result + def _get_user_contexts(self, result: Mapping[str, Any]) -> Any: + assert result["userContexts"] is not None + assert isinstance(result["userContexts"], list) + for user_context_info in result["userContexts"]: + assert isinstance(user_context_info["userContext"], str) + + return result["userContexts"] + + @command + def remove_user_context( + self, user_context: str + ) -> Mapping[str, Any]: + params: MutableMapping[str, Any] = {} + + if user_context is not None: + params["userContext"] = user_context + + return params diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/browsing_context.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/browsing_context.py index fdb18ccbddf63..7f3f7c50e5956 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/browsing_context.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/browsing_context.py @@ -3,11 +3,10 @@ from typing import Any, Dict, List, Mapping, MutableMapping, Optional, Union from ._module import BidiModule, command -from .script import OwnershipModel, SerializationOptions +from .script import SerializationOptions from ..undefined import UNDEFINED, Undefined - class ElementOptions(Dict[str, Any]): def __init__(self, element: Mapping[str, Any]): self["type"] = "element" @@ -69,11 +68,13 @@ def _capture_screenshot(self, result: Mapping[str, Any]) -> bytes: return base64.b64decode(result["data"]) @command - def close(self, context: Optional[str] = None) -> Mapping[str, Any]: + def close(self, context: Optional[str] = None, prompt_unload: Optional[bool] = None) -> Mapping[str, Any]: params: MutableMapping[str, Any] = {} if context is not None: params["context"] = context + if prompt_unload is not None: + params["promptUnload"] = prompt_unload return params @@ -81,7 +82,8 @@ def close(self, context: Optional[str] = None) -> Mapping[str, Any]: def create(self, type_hint: str, reference_context: Optional[str] = None, - background: Optional[bool] = None) -> Mapping[str, Any]: + background: Optional[bool] = None, + user_context: Optional[str] = None) -> Mapping[str, Any]: params: MutableMapping[str, Any] = {"type": type_hint} if reference_context is not None: @@ -90,6 +92,9 @@ def create(self, if background is not None: params["background"] = background + if user_context is not None: + params["userContext"] = user_context + return params @create.result @@ -136,17 +141,11 @@ def locate_nodes(self, context: str, locator: Mapping[str, Any], max_node_count: Optional[int] = None, - ownership: Optional[OwnershipModel] = None, - sandbox: Optional[str] = None, serialization_options: Optional[SerializationOptions] = None, start_nodes: Optional[List[Mapping[str, Any]]] = None) -> Mapping[str, Any]: params: MutableMapping[str, Any] = {"context": context, "locator": locator} if max_node_count is not None: params["maxNodeCount"] = max_node_count - if ownership is not None: - params["ownership"] = ownership - if sandbox is not None: - params["sandbox"] = sandbox if serialization_options is not None: params["serializationOptions"] = serialization_options if start_nodes is not None: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/input.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/input.py index b2703843b1819..ee4f8136e9d69 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/input.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/input.py @@ -410,6 +410,17 @@ def release_actions(self, context: str) -> Mapping[str, Any]: params: MutableMapping[str, Any] = {"context": context} return params + @command + def set_files( + self, context: str, element: Any, files: List[str] + ) -> Mapping[str, Any]: + params: MutableMapping[str, Any] = { + "context": context, + "element": element, + "files": files, + } + return params + def get_element_origin(element: Any) -> Mapping[str, Any]: return {"type": "element", "element": {"sharedId": element["sharedId"]}} diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/network.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/network.py index 073aa637c977f..46c5885271afd 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/network.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/network.py @@ -8,6 +8,66 @@ def __init__(self, username: str, password: str): dict.__init__(self, type="password", username=username, password=password) +class NetworkBase64Value(Dict[str, Any]): + def __init__(self, value: str): + dict.__init__(self, type="base64", value=value) + + +class NetworkStringValue(Dict[str, Any]): + def __init__(self, value: str): + dict.__init__(self, type="string", value=value) + + +NetworkBytesValue = Union[NetworkStringValue, NetworkBase64Value] + + +class CookieHeader(Dict[str, Any]): + def __init__(self, name: str, value: NetworkBytesValue): + dict.__init__(self, name=name, value=value) + + +class Header(Dict[str, Any]): + def __init__(self, name: str, value: NetworkBytesValue): + dict.__init__(self, name=name, value=value) + + +class SetCookieHeader(Dict[str, Any]): + def __init__( + self, + name: str, + value: NetworkBytesValue, + domain: Optional[str] = None, + expiry: Optional[str] = None, + http_only: Optional[bool] = None, + max_age: Optional[int] = None, + path: Optional[str] = None, + same_site: Optional[str] = None, + secure: Optional[bool] = None, + ): + dict.__init__(self, name=name, value=value) + + if domain is not None: + self["domain"] = domain + + if expiry is not None: + self["expiry"] = expiry + + if http_only is not None: + self["httpOnly"] = http_only + + if max_age is not None: + self["maxAge"] = max_age + + if path is not None: + self["path"] = path + + if same_site is not None: + self["sameSite"] = same_site + + if secure is not None: + self["secure"] = secure + + class URLPatternPattern(Dict[str, Any]): def __init__( self, @@ -46,7 +106,7 @@ def __init__(self, pattern: str): class Network(BidiModule): @command def add_intercept( - self, phases: List[str], url_patterns: Optional[List[URLPattern]] = None + self, phases: List[str], url_patterns: Optional[List[URLPattern]] = None, contexts: Optional[List[str]] = None ) -> Mapping[str, Any]: params: MutableMapping[str, Any] = { "phases": phases, @@ -55,6 +115,9 @@ def add_intercept( if url_patterns is not None: params["urlPatterns"] = url_patterns + if contexts is not None: + params["contexts"] = contexts + return params @add_intercept.result @@ -82,39 +145,60 @@ def continue_with_auth( @command def continue_request(self, request: str, + body: Optional[NetworkBytesValue] = None, + cookies: Optional[List[CookieHeader]] = None, + headers: Optional[List[Header]] = None, method: Optional[str] = None, url: Optional[str] = None) -> Mapping[str, Any]: params: MutableMapping[str, Any] = { "request": request, } + if body is not None: + params["body"] = body + + if cookies is not None: + params["cookies"] = cookies + + if headers is not None: + params["headers"] = headers + if method is not None: params["method"] = method if url is not None: params["url"] = url - # TODO: Add support for missing parameters: body, cookies, headers - return params @command def continue_response( self, request: str, + cookies: Optional[List[SetCookieHeader]] = None, + credentials: Optional[AuthCredentials] = None, + headers: Optional[List[Header]] = None, reason_phrase: Optional[str] = None, status_code: Optional[int] = None) -> Mapping[str, Any]: params: MutableMapping[str, Any] = { "request": request, } + if cookies is not None: + params["cookies"] = cookies + + if credentials is not None: + params["credentials"] = credentials + + if headers is not None: + params["headers"] = headers + if reason_phrase is not None: params["reasonPhrase"] = reason_phrase if status_code is not None: params["statusCode"] = status_code - # TODO: Add support for missing parameters: body, credentials, headers return params @@ -127,23 +211,44 @@ def fail_request(self, request: str) -> Mapping[str, Any]: def provide_response( self, request: str, + body: Optional[NetworkBytesValue] = None, + cookies: Optional[List[SetCookieHeader]] = None, + headers: Optional[List[Header]] = None, reason_phrase: Optional[str] = None, status_code: Optional[int] = None) -> Mapping[str, Any]: params: MutableMapping[str, Any] = { "request": request, } + if body is not None: + params["body"] = body + + if cookies is not None: + params["cookies"] = cookies + + if headers is not None: + params["headers"] = headers + if reason_phrase is not None: params["reasonPhrase"] = reason_phrase if status_code is not None: params["statusCode"] = status_code - # TODO: Add support for missing parameters: body, cookies, headers - return params @command def remove_intercept(self, intercept: str) -> Mapping[str, Any]: params: MutableMapping[str, Any] = {"intercept": intercept} return params + + @command + def set_cache_bypass( + self, bypass: bool, contexts: Optional[List[str]] = None + ) -> Mapping[str, Any]: + params: MutableMapping[str, Any] = {"bypass": bypass} + + if contexts is not None: + params["contexts"] = contexts + + return params diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/permissions.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/permissions.py new file mode 100644 index 0000000000000..a081e060e94ed --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/permissions.py @@ -0,0 +1,20 @@ +from typing import Any, Optional, Mapping, MutableMapping, Union +from webdriver.bidi.undefined import UNDEFINED, Undefined + +from ._module import BidiModule, command + + +class Permissions(BidiModule): + @command + def set_permission(self, + descriptor: Union[Optional[Mapping[str, Any]], Undefined] = UNDEFINED, + state: Union[Optional[str], Undefined] = UNDEFINED, + origin: Union[Optional[str], Undefined] = UNDEFINED, + user_context: Union[Optional[str], Undefined] = UNDEFINED) -> Mapping[str, Any]: + params: MutableMapping[str, Any] = { + "descriptor": descriptor, + "state": state, + "origin": origin, + "userContext": user_context, + } + return params diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/script.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/script.py index f128b0d089c28..01855766d8c1e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/script.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/script.py @@ -3,6 +3,7 @@ from ..error import UnknownErrorException from ._module import BidiModule, command +from ..undefined import UNDEFINED, Undefined class ScriptEvaluateResultException(Exception): @@ -78,15 +79,15 @@ def __init__(self, context: str, sandbox: Optional[str] = None): class SerializationOptions(Dict[str, Any]): def __init__( self, - max_dom_depth: Optional[int] = None, - max_object_depth: Optional[int] = None, - include_shadow_tree: Optional[str] = None + max_dom_depth: Union[Optional[int], Undefined] = UNDEFINED, + max_object_depth: Union[Optional[int], Undefined] = UNDEFINED, + include_shadow_tree: Union[Optional[str], Undefined] = UNDEFINED ): - if max_dom_depth is not None: + if max_dom_depth is not UNDEFINED: self["maxDomDepth"] = max_dom_depth - if max_object_depth is not None: + if max_object_depth is not UNDEFINED: self["maxObjectDepth"] = max_object_depth - if include_shadow_tree is not None: + if include_shadow_tree is not UNDEFINED and include_shadow_tree is not None: self["includeShadowTree"] = include_shadow_tree @@ -96,6 +97,7 @@ def add_preload_script( self, function_declaration: str, arguments: Optional[List[Mapping[str, Any]]] = None, + contexts: Optional[List[str]] = None, sandbox: Optional[str] = None ) -> Mapping[str, Any]: params: MutableMapping[str, Any] = { @@ -104,6 +106,8 @@ def add_preload_script( if arguments is not None: params["arguments"] = arguments + if contexts is not None: + params["contexts"] = contexts if sandbox is not None: params["sandbox"] = sandbox diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/session.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/session.py index fe1c0385105a1..725aab1bec72f 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/session.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/session.py @@ -40,9 +40,9 @@ def subscribe(self, @command def unsubscribe(self, - events: Optional[List[str]] = None, + events: List[str], contexts: Optional[List[str]] = None) -> Mapping[str, Any]: - params: MutableMapping[str, Any] = {"events": events if events is not None else []} + params: MutableMapping[str, Any] = {"events": events} if contexts is not None: params["contexts"] = contexts return params diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/storage.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/storage.py new file mode 100644 index 0000000000000..14e8fa9434c0b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/storage.py @@ -0,0 +1,126 @@ +from typing import Any, Dict, Mapping, MutableMapping, Optional, Union +from ._module import BidiModule, command +from webdriver.bidi.modules.network import NetworkBytesValue + + +class BrowsingContextPartitionDescriptor(Dict[str, Any]): + def __init__(self, context: str): + dict.__init__(self, type="context", context=context) + + +class StorageKeyPartitionDescriptor(Dict[str, Any]): + def __init__(self, user_context: Optional[str] = None, + source_origin: Optional[str] = None): + dict.__init__(self, type="storageKey") + if user_context is not None: + self["userContext"] = user_context + if source_origin is not None: + self["sourceOrigin"] = source_origin + + +class PartialCookie(Dict[str, Any]): + def __init__( + self, + name: str, + value: NetworkBytesValue, + domain: str, + path: Optional[str] = None, + http_only: Optional[bool] = None, + secure: Optional[bool] = None, + same_site: Optional[str] = None, + expiry: Optional[int] = None, + ): + dict.__init__(self, name=name, value=value, domain=domain) + if path is not None: + self["path"] = path + if http_only is not None: + self["httpOnly"] = http_only + if secure is not None: + self["secure"] = secure + if same_site is not None: + self["sameSite"] = same_site + if expiry is not None: + self["expiry"] = expiry + + +PartitionDescriptor = Union[StorageKeyPartitionDescriptor, BrowsingContextPartitionDescriptor] + + +class CookieFilter(Dict[str, Any]): + def __init__( + self, + name: Optional[str] = None, + value: Optional[NetworkBytesValue] = None, + domain: Optional[str] = None, + path: Optional[str] = None, + http_only: Optional[bool] = None, + secure: Optional[bool] = None, + same_site: Optional[str] = None, + size: Optional[int] = None, + expiry: Optional[int] = None, + ): + if name is not None: + self["name"] = name + if value is not None: + self["value"] = value + if domain is not None: + self["domain"] = domain + if path is not None: + self["path"] = path + if http_only is not None: + self["httpOnly"] = http_only + if secure is not None: + self["secure"] = secure + if same_site is not None: + self["sameSite"] = same_site + if size is not None: + self["size"] = size + if expiry is not None: + self["expiry"] = expiry + + +class Storage(BidiModule): + @command + def get_cookies( + self, + filter: Optional[CookieFilter] = None, + partition: Optional[PartitionDescriptor] = None, + ) -> Mapping[str, Any]: + params: MutableMapping[str, Any] = {} + + if filter is not None: + params["filter"] = filter + if partition is not None: + params["partition"] = partition + return params + + @command + def delete_cookies( + self, + filter: Optional[CookieFilter] = None, + partition: Optional[PartitionDescriptor] = None, + ) -> Mapping[str, Any]: + params: MutableMapping[str, Any] = {} + + if filter is not None: + params["filter"] = filter + if partition is not None: + params["partition"] = partition + return params + + @command + def set_cookie( + self, + cookie: PartialCookie, + partition: Optional[PartitionDescriptor] = None + ) -> Mapping[str, Any]: + """ + Use with caution: this command will not clean the cookie up after the test is done, which can lead to unexpected + test failures. Use `set_cookie` fixture instead. + """ + params: MutableMapping[str, Any] = { + "cookie": cookie + } + if partition is not None: + params["partition"] = partition + return params diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/w3c-import.log new file mode 100644 index 0000000000000..1b6e8352f8c7b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/w3c-import.log @@ -0,0 +1,26 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/_module.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/browser.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/browsing_context.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/input.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/network.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/permissions.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/script.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/session.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/modules/storage.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/transport.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/transport.py index d61ebaddea7b7..14b990f971d23 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/transport.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/transport.py @@ -6,6 +6,8 @@ import websockets +from websockets.exceptions import ConnectionClosed + logger = logging.getLogger("webdriver.bidi") @@ -70,10 +72,13 @@ async def end(self) -> None: async def read_messages(self) -> None: assert self.connection is not None - async for msg in self.connection: - if not isinstance(msg, str): - raise ValueError("Got a binary message") - await self.handle(msg) + try: + async for msg in self.connection: + if not isinstance(msg, str): + raise ValueError("Got a binary message") + await self.handle(msg) + except ConnectionClosed: + logger.debug("connection closed while reading messages") async def wait_closed(self) -> None: if self.connection and not self.connection.closed: diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/w3c-import.log new file mode 100644 index 0000000000000..61afc621fdfa0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/error.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/transport.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/bidi/undefined.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/error.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/error.py index 25e4b39ae694f..b19693e2d25ad 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/error.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/error.py @@ -16,7 +16,7 @@ class WebDriverException(Exception): status_code: ClassVar[str] def __init__(self, http_status=None, status_code=None, message=None, stacktrace=None): - super() + super().__init__() if http_status is not None: self.http_status = http_status diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/w3c-import.log new file mode 100644 index 0000000000000..ebe38513f1fc0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/client.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/error.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/protocol.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webdriver/webdriver/transport.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/capsule.py b/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/capsule.py index 74ca71ade9cce..fc8183a65f03a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/capsule.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/capsule.py @@ -108,7 +108,7 @@ def __iter__(self) -> Iterator[H3Capsule]: if self._final: raise e if not self._buffer: - return 0 + return size = self._buffer.capacity - self._buffer.tell() if size >= UINT_VAR_MAX_SIZE: raise e diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/w3c-import.log new file mode 100644 index 0000000000000..9aa2e89ea7946 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/w3c-import.log @@ -0,0 +1,21 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/capsule.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/handler.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/test_capsule.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/h3/webtransport_h3_server.py diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/w3c-import.log new file mode 100644 index 0000000000000..656ba63d310a6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/w3c-import.log @@ -0,0 +1,20 @@ +The tests in this directory were imported from the W3C repository. +Do NOT modify these tests directly in WebKit. +Instead, create a pull request on the WPT github: + https://github.com/web-platform-tests/wpt + +Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport + +Do NOT modify or remove this file. + +------------------------------------------------------------------------ +Properties requiring vendor prefixes: +None +Property values requiring vendor prefixes: +None +------------------------------------------------------------------------ +List of files: +/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/META.yml +/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/README.md +/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/__init__.py +/LayoutTests/imported/w3c/web-platform-tests/tools/webtransport/requirements.txt diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/android.py b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/android.py index 3def5cede44a7..f25350db078a8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/android.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/android.py @@ -17,9 +17,9 @@ wpt_root = os.path.abspath(os.path.join(here, os.pardir, os.pardir)) -NDK_VERSION = "r25c" -CMDLINE_TOOLS_VERSION_STRING = "11.0" -CMDLINE_TOOLS_VERSION = "9644228" +NDK_VERSION = "r26c" +CMDLINE_TOOLS_VERSION_STRING = "12.0" +CMDLINE_TOOLS_VERSION = "11076708" AVD_MANIFEST_X86_64 = { "emulator_package": "system-images;android-24;default;x86_64", @@ -100,6 +100,8 @@ def install_fixed_emulator_version(logger, paths): emulator_path = os.path.join(paths["sdk"], "emulator") latest_emulator_path = os.path.join(paths["sdk"], "emulator_latest") + if os.path.exists(latest_emulator_path): + shutil.rmtree(latest_emulator_path) os.rename(emulator_path, latest_emulator_path) download_and_extract(url, paths["sdk"]) @@ -288,8 +290,8 @@ def install(logger, dest=None, reinstall=False, prompt=True): if new_install: packages = ["platform-tools", - "build-tools;33.0.1", - "platforms;android-33", + "build-tools;34.0.0", + "platforms;android-34", "emulator"] install_android_packages(logger, paths, packages, prompt=prompt) @@ -323,7 +325,16 @@ def start(logger, dest=None, reinstall=False, prompt=True, device_serial=None): emulator.start() timer = threading.Timer(300, cancel_start(threading.get_ident())) timer.start() - emulator.wait_for_start() + for i in range(10): + logger.info(f"Wait for emulator to start attempt {i + 1}/10") + try: + emulator.wait_for_start() + except Exception: + import traceback + logger.warning(f"""emulator.wait_for_start() failed: +{traceback.format_exc()}""") + else: + break timer.cancel() return emulator diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/browser.py b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/browser.py index ba7413bae5446..ba716cd14f6d7 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/browser.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/browser.py @@ -9,6 +9,7 @@ from abc import ABCMeta, abstractmethod from datetime import datetime, timedelta, timezone from shutil import which +from typing import Optional from urllib.parse import urlsplit import html5lib @@ -583,7 +584,7 @@ class ChromeChromiumBase(Browser): see https://web-platform-tests.org/running-tests/chrome-chromium-installation-detection.html """ - requirements = "requirements_chromium.txt" + requirements: Optional[str] = "requirements_chromium.txt" platform = { "Linux": "Linux", "Windows": "Win", @@ -703,12 +704,12 @@ def install_mojojs(self, dest, browser_binary): try: # MojoJS version url must match the browser binary version exactly. - url = ("https://storage.googleapis.com/chrome-wpt-mojom/" - f"{chrome_version}/linux64/mojojs.zip") + url = ("https://storage.googleapis.com/chrome-for-testing-public/" + f"{chrome_version}/mojojs.zip") # Check the status without downloading the content (this is a streaming request). get(url) except requests.RequestException: - # If a valid matching version cannot be found in the wpt archive, + # If a valid matching version cannot be found in the CfT archive, # download from Chromium snapshots bucket. However, # MojoJS is only bundled with Linux from Chromium snapshots. if self.platform == "Linux": @@ -1282,6 +1283,11 @@ def install_webdriver(self, dest=None, channel=None, browser_binary=None): version = self.version(browser_binary) if version is None: + # Check if the user has given a Chromium binary. + chromium = Chromium(self.logger) + if chromium.version(browser_binary): + raise ValueError("Provided binary is a Chromium binary and should be run using " + "\"./wpt run chromium\" or similar.") raise ValueError(f"Unable to detect browser version from binary at {browser_binary}. " " Cannot install ChromeDriver without a valid version to match.") @@ -1387,14 +1393,17 @@ def webdriver_version(self, webdriver_binary): return m.group(1) -class ContentShell(Browser): - """Interface for the Chromium content shell. +class HeadlessShell(ChromeChromiumBase): + """Interface for the Chromium headless shell [0]. + + [0]: https://chromium.googlesource.com/chromium/src/+/HEAD/headless/README.md """ - product = "content_shell" + product = "headless_shell" requirements = None def download(self, dest=None, channel=None, rename=None): + # TODO(crbug.com/344669542): Download binaries via CfT. raise NotImplementedError def install(self, dest=None, channel=None): @@ -1404,17 +1413,16 @@ def install_webdriver(self, dest=None, channel=None, browser_binary=None): raise NotImplementedError def find_binary(self, venv_path=None, channel=None): - if uname[0] == "Darwin": - return which("Content Shell.app/Contents/MacOS/Content Shell") - return which("content_shell") # .exe is added automatically for Windows - - def find_webdriver(self, venv_path=None, channel=None): - return None + # `which()` adds `.exe` extension automatically for Windows. + # Chromium builds an executable named `headless_shell`, whereas CfT + # ships under the name `chrome-headless-shell`. + return which("headless_shell") or which("chrome-headless-shell") def version(self, binary=None, webdriver_binary=None): - # content_shell does not return version information. + # TODO(crbug.com/327767951): Support `headless_shell --version`. return "N/A" + class ChromeAndroidBase(Browser): """A base class for ChromeAndroid and AndroidWebView. @@ -1447,7 +1455,7 @@ def install_webdriver(self, dest=None, channel=None, browser_binary=None): if browser_binary is None: browser_binary = self.find_binary(channel) chrome = Chrome(self.logger) - return chrome.install_webdriver_by_version(self.version(browser_binary), dest) + return chrome.install_webdriver_by_version(self.version(browser_binary), dest, channel) def version(self, binary=None, webdriver_binary=None): if not binary: @@ -1484,20 +1492,6 @@ def find_binary(self, venv_path=None, channel=None): return "com.android.chrome" -# TODO(aluo): This is largely copied from the AndroidWebView implementation. -# Tests are not running for weblayer yet (crbug/1019521), this initial -# implementation will help to reproduce and debug any issues. -class AndroidWeblayer(ChromeAndroidBase): - """Weblayer-specific interface for Android.""" - - product = "android_weblayer" - # TODO(aluo): replace this with weblayer version after tests are working. - requirements = "requirements_chromium.txt" - - def find_binary(self, venv_path=None, channel=None): - return "org.chromium.weblayer.shell" - - class AndroidWebview(ChromeAndroidBase): """Webview-specific interface for Android. @@ -1554,7 +1548,23 @@ def install_webdriver(self, dest=None, channel=None, browser_binary=None): raise NotImplementedError def version(self, binary=None, webdriver_binary=None): - return None + if webdriver_binary is None: + self.logger.warning( + "Cannot find ChromeiOS version without CWTChromeDriver") + return None + # Use `chrome iOS driver --version` to get the version. Example output: + # "125.0.6378.0" + try: + version_string = call(webdriver_binary, "--version").strip() + except subprocess.CalledProcessError as e: + self.logger.warning(f"Failed to call {webdriver_binary}: {e}") + return None + m = re.match(r"[\d][\d\.]*", version_string) + if not m: + self.logger.warning( + f"Failed to extract version from: {version_string}") + return None + return m.group(0) class Opera(Browser): @@ -1635,10 +1645,10 @@ def version(self, binary=None, webdriver_binary=None): return m.group(0) -class EdgeChromium(Browser): +class Edge(Browser): """Microsoft Edge Chromium Browser class.""" - product = "edgechromium" + product = "edge" requirements = "requirements_chromium.txt" platform = { "Linux": "linux", @@ -1669,17 +1679,49 @@ def _remove_existing_edgedriver_binary(self, path): existing_driver_notes_path = os.path.join(path, "Driver_notes") if os.path.isdir(existing_driver_notes_path): self.logger.info(f"Removing existing MSEdgeDriver binary: {existing_driver_notes_path}") - print(f"Delete {existing_driver_notes_path} folder") rmtree(existing_driver_notes_path) def download(self, dest=None, channel=None, rename=None): raise NotImplementedError def install_mojojs(self, dest, browser_binary): - # TODO: Install MojoJS web framework. # MojoJS is platform agnostic, but the version number must be an # exact match of the Edge version to be compatible. - return None + edge_version = self.version(binary=browser_binary) + if not edge_version: + return None + + try: + # MojoJS version url must match the browser binary version exactly. + url = ("https://msedgedriver.azureedge.net/wpt-mojom/" + f"{edge_version}/linux64/mojojs.zip") + # Check the status without downloading the content (this is a + # streaming request). + get(url) + except requests.RequestException: + self.logger.error("A valid MojoJS version cannot be found " + f"for browser binary version {edge_version}.") + return None + + extracted = os.path.join(dest, "mojojs", "gen") + last_url_file = os.path.join(extracted, "DOWNLOADED_FROM") + if os.path.exists(last_url_file): + with open(last_url_file, "rt") as f: + last_url = f.read().strip() + if last_url == url: + self.logger.info("Mojo bindings already up to date") + return extracted + rmtree(extracted) + + try: + self.logger.info(f"Downloading Mojo bindings from {url}") + unzip(get(url).raw, os.path.join(dest, "mojojs")) + with open(last_url_file, "wt") as f: + f.write(url) + return extracted + except Exception as e: + self.logger.error(f"Cannot enable MojoJS: {e}") + return None def find_binary(self, venv_path=None, channel=None): # TODO: Check for binary in virtual environment first @@ -1824,65 +1866,6 @@ def webdriver_version(self, webdriver_binary): return m.group(1) -class Edge(Browser): - """Edge-specific interface.""" - - product = "edge" - requirements = "requirements_edge.txt" - - def download(self, dest=None, channel=None, rename=None): - raise NotImplementedError - - def install(self, dest=None, channel=None): - raise NotImplementedError - - def find_binary(self, venv_path=None, channel=None): - raise NotImplementedError - - def find_webdriver(self, venv_path=None, channel=None): - return which("MicrosoftWebDriver") - - def install_webdriver(self, dest=None, channel=None, browser_binary=None): - raise NotImplementedError - - def version(self, binary=None, webdriver_binary=None): - command = "(Get-AppxPackage Microsoft.MicrosoftEdge).Version" - try: - return call("powershell.exe", command).strip() - except (subprocess.CalledProcessError, OSError): - self.logger.warning("Failed to call %s in PowerShell" % command) - return None - - -class EdgeWebDriver(Edge): - product = "edge_webdriver" - - -class InternetExplorer(Browser): - """Internet Explorer-specific interface.""" - - product = "ie" - requirements = "requirements_ie.txt" - - def download(self, dest=None, channel=None, rename=None): - raise NotImplementedError - - def install(self, dest=None, channel=None): - raise NotImplementedError - - def find_binary(self, venv_path=None, channel=None): - raise NotImplementedError - - def find_webdriver(self, venv_path=None, channel=None): - return which("IEDriverServer.exe") - - def install_webdriver(self, dest=None, channel=None, browser_binary=None): - raise NotImplementedError - - def version(self, binary=None, webdriver_binary=None): - return None - - class Safari(Browser): """Safari-specific interface. diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/install.py b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/install.py index 382c1e2eb80ef..1e6408b0be633 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/install.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/install.py @@ -4,14 +4,13 @@ from . import browser latest_channels = { - 'android_weblayer': 'dev', 'android_webview': 'dev', 'firefox': 'nightly', 'firefox_android': 'nightly', 'chrome': 'canary', 'chrome_android': 'dev', 'chromium': 'nightly', - 'edgechromium': 'dev', + 'edge': 'dev', 'safari': 'preview', 'servo': 'nightly', 'webkitgtk_minibrowser': 'nightly', diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/paths b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/paths index 7e9ae837ecf40..5a1303362b368 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/paths +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/paths @@ -5,3 +5,4 @@ tools/lint/ tools/manifest/ tools/serve/ tools/wpt/ +tools/web_features/ diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/requirements.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/requirements.txt index 2c24336eb3167..d80d9fc2a3a2b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/requirements.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/requirements.txt @@ -1 +1 @@ -requests==2.31.0 +requests==2.32.3 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/requirements_android.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/requirements_android.txt index 17672383cb895..e8205caa70102 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/requirements_android.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/requirements_android.txt @@ -1 +1 @@ -mozrunner==8.3.0 +mozrunner==8.3.1 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/requirements_install.txt b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/requirements_install.txt index 55bed99f8c594..f7df06548c942 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/requirements_install.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/requirements_install.txt @@ -1,2 +1,2 @@ mozinstall==2.1.0 -packaging==23.1 +packaging==24.0 diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/run.py b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/run.py index dcf721ef39f83..9c6acbc2a4652 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/run.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/run.py @@ -111,10 +111,9 @@ def set_if_none(name, value): def check_environ(product): - if product not in ("android_weblayer", "android_webview", "chrome", - "chrome_android", "chrome_ios", "content_shell", - "edgechromium", "firefox", "firefox_android", "ladybird", "servo", - "wktr"): + if product not in ("android_webview", "chrome", "chrome_android", "chrome_ios", + "edge", "firefox", "firefox_android", "headless_shell", + "ladybird", "servo", "wktr"): config_builder = serve.build_config(os.path.join(wpt_root, "config.json")) # Override the ports to avoid looking for free ports config_builder.ssl = {"type": "none"} @@ -503,6 +502,9 @@ def setup_kwargs(self, kwargs): kwargs["webdriver_binary"] = webdriver_binary else: raise WptrunError("Unable to locate or install matching ChromeDriver binary") + if kwargs["headless"] is None and not kwargs["debug_test"]: + kwargs["headless"] = True + logger.info("Running in headless mode, pass --no-headless to disable") if browser_channel in self.experimental_channels: # HACK(Hexcles): work around https://github.com/web-platform-tests/wpt/issues/16448 kwargs["webdriver_args"].append("--disable-build-check") @@ -519,9 +521,9 @@ def setup_kwargs(self, kwargs): kwargs["binary_args"].append("--no-sandbox") -class ContentShell(BrowserSetup): - name = "content_shell" - browser_cls = browser.ContentShell +class HeadlessShell(BrowserSetup): + name = "headless_shell" + browser_cls = browser.HeadlessShell experimental_channels = ("dev", "canary", "nightly") def setup_kwargs(self, kwargs): @@ -531,7 +533,7 @@ def setup_kwargs(self, kwargs): if binary: kwargs["binary"] = binary else: - raise WptrunError(f"Unable to locate {self.name.capitalize()} binary") + raise WptrunError(f"Unable to locate {self.name!r} binary") if kwargs["mojojs_path"]: kwargs["enable_mojojs"] = True @@ -542,7 +544,13 @@ def setup_kwargs(self, kwargs): "Provide '--mojojs-path' explicitly instead.") logger.warning("MojoJS is disabled for this run.") - kwargs["enable_webtransport_h3"] = True + # Never pause after test, since headless shell is not interactive. + kwargs["pause_after_test"] = False + # Don't add a `--headless` switch. + kwargs["headless"] = False + + if kwargs["enable_webtransport_h3"] is None: + kwargs["enable_webtransport_h3"] = True class Chromium(Chrome): @@ -563,6 +571,8 @@ def setup_kwargs(self, kwargs): if kwargs["package_name"] is None: kwargs["package_name"] = self.browser.find_binary( channel=browser_channel) + if not kwargs["device_serial"]: + kwargs["device_serial"] = ["emulator-5554"] if kwargs["webdriver_binary"] is None: webdriver_binary = None if not kwargs["install_webdriver"]: @@ -610,17 +620,6 @@ def setup_kwargs(self, kwargs): raise WptrunError("Unable to locate or install chromedriver binary") -class AndroidWeblayer(ChromeAndroidBase): - name = "android_weblayer" - browser_cls = browser.AndroidWeblayer - - def setup_kwargs(self, kwargs): - super().setup_kwargs(kwargs) - if kwargs["browser_channel"] in self.experimental_channels and kwargs["enable_experimental"] is None: - logger.info("Automatically turning on experimental features for WebLayer Dev/Canary") - kwargs["enable_experimental"] = True - - class AndroidWebview(ChromeAndroidBase): name = "android_webview" browser_cls = browser.AndroidWebview @@ -658,9 +657,9 @@ def setup_kwargs(self, kwargs): raise WptrunError("Unable to locate or install operadriver binary") -class EdgeChromium(BrowserSetup): +class Edge(BrowserSetup): name = "MicrosoftEdge" - browser_cls = browser.EdgeChromium + browser_cls = browser.Edge experimental_channels: ClassVar[Tuple[str, ...]] = ("dev", "canary") def setup_kwargs(self, kwargs): @@ -726,53 +725,6 @@ def setup_kwargs(self, kwargs): kwargs["binary_args"].append("--no-sandbox") -class Edge(BrowserSetup): - name = "edge" - browser_cls = browser.Edge - - def install(self, channel=None): - raise NotImplementedError - - def setup_kwargs(self, kwargs): - if kwargs["webdriver_binary"] is None: - webdriver_binary = self.browser.find_webdriver() - - if webdriver_binary is None: - raise WptrunError("""Unable to find WebDriver and we aren't yet clever enough to work out which -version to download. Please go to the following URL and install the correct -version for your Edge/Windows release somewhere on the %PATH%: - -https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ -""") - kwargs["webdriver_binary"] = webdriver_binary - - -class EdgeWebDriver(Edge): - name = "edge_webdriver" - browser_cls = browser.EdgeWebDriver - - -class InternetExplorer(BrowserSetup): - name = "ie" - browser_cls = browser.InternetExplorer - - def install(self, channel=None): - raise NotImplementedError - - def setup_kwargs(self, kwargs): - if kwargs["webdriver_binary"] is None: - webdriver_binary = self.browser.find_webdriver() - - if webdriver_binary is None: - raise WptrunError("""Unable to find WebDriver and we aren't yet clever enough to work out which -version to download. Please go to the following URL and install the driver for Internet Explorer -somewhere on the %PATH%: - -https://selenium-release.storage.googleapis.com/index.html -""") - kwargs["webdriver_binary"] = webdriver_binary - - class Safari(BrowserSetup): name = "safari" browser_cls = browser.Safari @@ -914,7 +866,6 @@ def setup_kwargs(self, kwargs): product_setup = { - "android_weblayer": AndroidWeblayer, "android_webview": AndroidWebview, "firefox": Firefox, "firefox_android": FirefoxAndroid, @@ -922,11 +873,8 @@ def setup_kwargs(self, kwargs): "chrome_android": ChromeAndroid, "chrome_ios": ChromeiOS, "chromium": Chromium, - "content_shell": ContentShell, - "edgechromium": EdgeChromium, "edge": Edge, - "edge_webdriver": EdgeWebDriver, - "ie": InternetExplorer, + "headless_shell": HeadlessShell, "safari": Safari, "servo": Servo, "servodriver": ServoWebDriver, @@ -969,6 +917,9 @@ def setup_wptrunner(venv, **kwargs): args_general(kwargs) if kwargs["product"] not in product_setup: + if kwargs["product"] == "edgechromium": + raise WptrunError("edgechromium has been renamed to edge.") + raise WptrunError("Unsupported product %s" % kwargs["product"]) setup_cls = product_setup[kwargs["product"]](venv, kwargs["prompt"]) diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/tests/__init__.py b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/tests/__init__.py index e69de29bb2d1d..a6834b8285a56 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/tests/__init__.py +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/tests/__init__.py @@ -0,0 +1 @@ +# This file is required for Python to search this directory for modules. \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/tests/safari-downloads/2018-05-17.html b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/tests/safari-downloads/2018-05-17.html index 950b539de5974..b992f8266e6a8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/tests/safari-downloads/2018-05-17.html +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/tests/safari-downloads/2018-05-17.html @@ -9,19 +9,15 @@

    Safari Technology Preview

    Safari Technology Preview for macOS High Sierra
    Requires macOS 10.13. + >Safari Technology Preview for macOS High Sierra
    Requires macOS 10.13.
  • Safari Technology Preview for macOS Sierra
    Requires macOS 10.12.6 or later. + >Safari Technology Preview for macOS Sierra
    Requires macOS 10.12.6 or later.
  • Release Notes diff --git a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/tests/safari-downloads/2018-09-19.html b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/tests/safari-downloads/2018-09-19.html index 5c00c72219293..66307cb36cf92 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/tests/safari-downloads/2018-09-19.html +++ b/LayoutTests/imported/w3c/web-platform-tests/tools/wpt/tests/safari-downloads/2018-09-19.html @@ -3,8 +3,7 @@

    Safari Technology Preview

    Get a sneak peek at upcoming web technologies in macOS and iOS with Safari Technology Preview + >Safari Technology Preview and experiment with these technologies in your websites and extensions.

  • - {{range .Preamble}}

    {{. | markupPipeWords | markupRFC}}

    {{end}} + {{if .Preamble}}
    {{.Preamble | markupComment}}
    {{end}} -
      +
        {{range .Sections}} {{if not .IsPrivate}} {{if .Anchor}}
      1. {{.Preamble | firstSentence | markupPipeWordsNoLink}}
      2. {{end}} @@ -638,18 +799,12 @@ func generate(outPath string, config *Config) (map[string]string, error) { {{range .Sections}} {{if not .IsPrivate}}
        - {{if .Preamble}} -
        - {{range .Preamble}}

        {{. | markupPipeWords | markupRFC}}

        {{end}} -
        - {{end}} + {{if .Preamble}}
        {{.Preamble | markupComment}}
        {{end}} {{range .Decls}}
        - {{range .Comment}} -

        {{. | markupPipeWords | newlinesToBR | markupFirstWord | markupRFC}}

        - {{end}} -
        {{.Decl}}
        + {{if .Comment}}
        {{.Comment | markupComment}}
        {{end}} + {{if .Decl}}
        {{.Decl}}
        {{end}}
        {{end}}
        diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/acvp.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/acvp.go index e92424b77aa06..60a952ab71d47 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/acvp.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/acvp.go @@ -529,28 +529,6 @@ func uploadFromFile(file string, config *Config, sessionTokensCacheDir string) { func main() { flag.Parse() - var config Config - if err := jsonFromFile(&config, *configFilename); err != nil { - log.Fatalf("Failed to load config file: %s", err) - } - - var sessionTokensCacheDir string - if len(config.SessionTokensCache) > 0 { - sessionTokensCacheDir = config.SessionTokensCache - if strings.HasPrefix(sessionTokensCacheDir, "~/") { - home := os.Getenv("HOME") - if len(home) == 0 { - log.Fatal("~ used in config file but $HOME not set") - } - sessionTokensCacheDir = filepath.Join(home, sessionTokensCacheDir[2:]) - } - } - - if len(*uploadInputFile) > 0 { - uploadFromFile(*uploadInputFile, &config, sessionTokensCacheDir) - return - } - middle, err := subprocess.New(*wrapperPath) if err != nil { log.Fatalf("failed to initialise middle: %s", err) @@ -598,14 +576,14 @@ func main() { } os.Stdout.Write(regcapBytes) os.Stdout.WriteString("\n") - os.Exit(0) + return } if len(*jsonInputFile) > 0 { if err := processFile(*jsonInputFile, supportedAlgos, middle); err != nil { log.Fatalf("failed to process input file: %s", err) } - os.Exit(0) + return } var requestedAlgosFlag string @@ -667,6 +645,28 @@ func main() { } } + var config Config + if err := jsonFromFile(&config, *configFilename); err != nil { + log.Fatalf("Failed to load config file: %s", err) + } + + var sessionTokensCacheDir string + if len(config.SessionTokensCache) > 0 { + sessionTokensCacheDir = config.SessionTokensCache + if strings.HasPrefix(sessionTokensCacheDir, "~/") { + home := os.Getenv("HOME") + if len(home) == 0 { + log.Fatal("~ used in config file but $HOME not set") + } + sessionTokensCacheDir = filepath.Join(home, sessionTokensCacheDir[2:]) + } + } + + if len(*uploadInputFile) > 0 { + uploadFromFile(*uploadInputFile, &config, sessionTokensCacheDir) + return + } + server, err := connect(&config, sessionTokensCacheDir) if err != nil { log.Fatal(err) @@ -789,7 +789,7 @@ func main() { if len(*fetchFlag) > 0 { io.WriteString(fetchOutputTee, "]\n") - os.Exit(0) + return } if ok, err := getResultsWithRetry(server, url); err != nil { diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/aead.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/aead.go index ba0eee9666333..dd7e75cf78f0c 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/aead.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/aead.go @@ -32,12 +32,13 @@ type aeadVectorSet struct { } type aeadTestGroup struct { - ID uint64 `json:"tgId"` - Type string `json:"testType"` - Direction string `json:"direction"` - KeyBits int `json:"keyLen"` - TagBits int `json:"tagLen"` - Tests []struct { + ID uint64 `json:"tgId"` + Type string `json:"testType"` + Direction string `json:"direction"` + KeyBits int `json:"keyLen"` + TagBits int `json:"tagLen"` + NonceSource string `json:"ivGen"` + Tests []struct { ID uint64 `json:"tcId"` PlaintextHex string `json:"pt"` CiphertextHex string `json:"ct"` @@ -57,6 +58,7 @@ type aeadTestResponse struct { ID uint64 `json:"tcId"` CiphertextHex *string `json:"ct,omitempty"` TagHex string `json:"tag,omitempty"` + NonceHex string `json:"iv,omitempty"` PlaintextHex *string `json:"pt,omitempty"` Passed *bool `json:"testPassed,omitempty"` } @@ -72,6 +74,7 @@ func (a *aead) Process(vectorSet []byte, m Transactable) (any, error) { // versions of the ACVP documents. You can find fragments in // https://github.com/usnistgov/ACVP.) for _, group := range parsed.Groups { + group := group response := aeadTestGroupResponse{ ID: group.ID, } @@ -86,9 +89,24 @@ func (a *aead) Process(vectorSet []byte, m Transactable) (any, error) { return nil, fmt.Errorf("test group %d has unknown direction %q", group.ID, group.Direction) } - op := a.algo + "/seal" - if !encrypt { - op = a.algo + "/open" + var randnonce bool + switch group.NonceSource { + case "internal": + randnonce = true + case "external", "": + randnonce = false + default: + return nil, fmt.Errorf("test group %d has unknown nonce source %q", group.ID, group.NonceSource) + } + + op := a.algo + if randnonce { + op += "-randnonce" + } + if encrypt { + op += "/seal" + } else { + op += "/open" } if group.KeyBits%8 != 0 || group.KeyBits < 0 { @@ -102,6 +120,8 @@ func (a *aead) Process(vectorSet []byte, m Transactable) (any, error) { tagBytes := group.TagBits / 8 for _, test := range group.Tests { + test := test + if len(test.KeyHex) != keyBytes*2 { return nil, fmt.Errorf("test case %d/%d contains key %q of length %d, but expected %d-bit key", group.ID, test.ID, test.KeyHex, len(test.KeyHex), group.KeyBits) } @@ -170,17 +190,27 @@ func (a *aead) Process(vectorSet []byte, m Transactable) (any, error) { ciphertextHex := hex.EncodeToString(result[0]) testResp.CiphertextHex = &ciphertextHex } else { - ciphertext := result[0][:len(result[0])-tagBytes] + ciphertext := result[0] + if randnonce { + var nonce []byte + ciphertext, nonce = splitOffRight(ciphertext, 12) + testResp.NonceHex = hex.EncodeToString(nonce) + } + ciphertext, tag := splitOffRight(ciphertext, tagBytes) ciphertextHex := hex.EncodeToString(ciphertext) testResp.CiphertextHex = &ciphertextHex - tag := result[0][len(result[0])-tagBytes:] testResp.TagHex = hex.EncodeToString(tag) } response.Tests = append(response.Tests, testResp) return nil }) } else { - m.TransactAsync(op, 2, [][]byte{uint32le(uint32(tagBytes)), key, append(input, tag...), nonce, aad}, func(result [][]byte) error { + ciphertext := append(input, tag...) + if randnonce { + ciphertext = append(ciphertext, nonce...) + nonce = []byte{} + } + m.TransactAsync(op, 2, [][]byte{uint32le(uint32(tagBytes)), key, ciphertext, nonce, aad}, func(result [][]byte) error { if len(result[0]) != 1 || (result[0][0]&0xfe) != 0 { return fmt.Errorf("invalid AEAD status result from subprocess") } @@ -207,3 +237,11 @@ func (a *aead) Process(vectorSet []byte, m Transactable) (any, error) { return ret, nil } + +func splitOffRight(in []byte, suffixSize int) ([]byte, []byte) { + if len(in) < suffixSize { + panic("input too small to split") + } + split := len(in) - suffixSize + return in[:split], in[split:] +} diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/block.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/block.go index 2f058027ec7fc..bcc6613a2f8a3 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/block.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/block.go @@ -299,6 +299,7 @@ func (b *blockCipher) Process(vectorSet []byte, m Transactable) (any, error) { // http://usnistgov.github.io/ACVP/artifacts/draft-celi-acvp-block-ciph-00.html#rfc.section.5.2 // for details about the tests. for _, group := range parsed.Groups { + group := group response := blockCipherTestGroupResponse{ ID: group.ID, } @@ -346,6 +347,8 @@ func (b *blockCipher) Process(vectorSet []byte, m Transactable) (any, error) { } for _, test := range group.Tests { + test := test + if len(test.KeyHex) == 0 && len(test.Key1Hex) > 0 { // 3DES encodes the key differently. test.KeyHex = test.Key1Hex + test.Key2Hex + test.Key3Hex diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/drbg.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/drbg.go index b403f046140df..87584d63d7835 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/drbg.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/drbg.go @@ -84,6 +84,7 @@ func (d *drbg) Process(vectorSet []byte, m Transactable) (any, error) { // https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#name-test-vectors // for details about the tests. for _, group := range parsed.Groups { + group := group response := drbgTestGroupResponse{ ID: group.ID, } @@ -97,6 +98,8 @@ func (d *drbg) Process(vectorSet []byte, m Transactable) (any, error) { } for _, test := range group.Tests { + test := test + ent, err := extractField(test.EntropyHex, group.EntropyBits) if err != nil { return nil, fmt.Errorf("failed to extract entropy hex from test case %d/%d: %s", group.ID, test.ID, err) diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/ecdsa.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/ecdsa.go index 16d3a833554d8..69706bdda6504 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/ecdsa.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/ecdsa.go @@ -83,6 +83,8 @@ func (e *ecdsa) Process(vectorSet []byte, m Transactable) (any, error) { // https://pages.nist.gov/ACVP/draft-fussell-acvp-ecdsa.html#name-test-vectors // for details about the tests. for _, group := range parsed.Groups { + group := group + if _, ok := e.curves[group.Curve]; !ok { return nil, fmt.Errorf("curve %q in test group %d not supported", group.Curve, group.ID) } @@ -93,6 +95,8 @@ func (e *ecdsa) Process(vectorSet []byte, m Transactable) (any, error) { var sigGenPrivateKey []byte for _, test := range group.Tests { + test := test + var testResp ecdsaTestResponse testResp.ID = test.ID diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/hash.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/hash.go index 1f34d1a9063c6..aeac6d669d764 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/hash.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/hash.go @@ -73,11 +73,14 @@ func (h *hashPrimitive) Process(vectorSet []byte, m Transactable) (any, error) { // https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html#name-test-vectors // for details about the tests. for _, group := range parsed.Groups { + group := group response := hashTestGroupResponse{ ID: group.ID, } for _, test := range group.Tests { + test := test + if uint64(len(test.MsgHex))*4 != test.BitLength { return nil, fmt.Errorf("test case %d/%d contains hex message of length %d but specifies a bit length of %d", group.ID, test.ID, len(test.MsgHex), test.BitLength) } diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/hkdf.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/hkdf.go index 3a6ba04c66087..c64e2b862dd05 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/hkdf.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/hkdf.go @@ -124,6 +124,7 @@ func (k *hkdf) Process(vectorSet []byte, m Transactable) (any, error) { var respGroups []hkdfTestGroupResponse for _, group := range parsed.Groups { + group := group groupResp := hkdfTestGroupResponse{ID: group.ID} var isValidationTest bool @@ -142,6 +143,7 @@ func (k *hkdf) Process(vectorSet []byte, m Transactable) (any, error) { } for _, test := range group.Tests { + test := test testResp := hkdfTestResponse{ID: test.ID} key, salt, err := test.Params.extract() diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/hmac.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/hmac.go index 8fc76951c3d95..6b8a3cfaecad7 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/hmac.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/hmac.go @@ -87,6 +87,7 @@ func (h *hmacPrimitive) Process(vectorSet []byte, m Transactable) (any, error) { // https://pages.nist.gov/ACVP/draft-fussell-acvp-mac.html#name-test-vectors // for details about the tests. for _, group := range parsed.Groups { + group := group response := hmacTestGroupResponse{ ID: group.ID, } @@ -99,6 +100,8 @@ func (h *hmacPrimitive) Process(vectorSet []byte, m Transactable) (any, error) { outBytes := group.MACBits / 8 for _, test := range group.Tests { + test := test + if len(test.MsgHex)*4 != group.MsgBits { return nil, fmt.Errorf("test case %d/%d contains hex message of length %d but specifies a bit length of %d", group.ID, test.ID, len(test.MsgHex), group.MsgBits) } diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/kas.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/kas.go index cbc99ed58dcf0..4c99f8aa38e43 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/kas.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/kas.go @@ -77,6 +77,7 @@ func (k *kas) Process(vectorSet []byte, m Transactable) (any, error) { // See https://pages.nist.gov/ACVP/draft-fussell-acvp-kas-ecc.html#name-test-vectors var ret []kasTestGroupResponse for _, group := range parsed.Groups { + group := group response := kasTestGroupResponse{ ID: group.ID, } @@ -119,6 +120,8 @@ func (k *kas) Process(vectorSet []byte, m Transactable) (any, error) { method := "ECDH/" + group.Curve for _, test := range group.Tests { + test := test + var xHex, yHex, privateKeyHex string if useStaticNamedFields { xHex, yHex, privateKeyHex = test.StaticXHex, test.StaticYHex, test.StaticPrivateKeyHex diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/kasdh.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/kasdh.go index f262b82003636..212dd316578fa 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/kasdh.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/kasdh.go @@ -68,6 +68,7 @@ func (k *kasDH) Process(vectorSet []byte, m Transactable) (any, error) { // See https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-ffc-sp800-56ar3.html var ret []kasDHTestGroupResponse for _, group := range parsed.Groups { + group := group response := kasDHTestGroupResponse{ ID: group.ID, } @@ -110,6 +111,8 @@ func (k *kasDH) Process(vectorSet []byte, m Transactable) (any, error) { const method = "FFDH" for _, test := range group.Tests { + test := test + if len(test.PeerPublicHex) == 0 { return nil, fmt.Errorf("%d/%d is missing peer's key", group.ID, test.ID) } diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/kdf.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/kdf.go index e27fcaa9ae37b..6e4145890afda 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/kdf.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/kdf.go @@ -68,6 +68,7 @@ func (k *kdfPrimitive) Process(vectorSet []byte, m Transactable) (any, error) { var respGroups []kdfTestGroupResponse for _, group := range parsed.Groups { + group := group groupResp := kdfTestGroupResponse{ID: group.ID} if group.OutputBits%8 != 0 { @@ -91,6 +92,7 @@ func (k *kdfPrimitive) Process(vectorSet []byte, m Transactable) (any, error) { outputBytes := uint32le(group.OutputBits / 8) for _, test := range group.Tests { + test := test testResp := kdfTestResponse{ID: test.ID} var key []byte diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/keyedMac.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/keyedMac.go index e43ab5d5314a3..c91bb4160721e 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/keyedMac.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/keyedMac.go @@ -65,6 +65,7 @@ func (k *keyedMACPrimitive) Process(vectorSet []byte, m Transactable) (any, erro var respGroups []keyedMACTestGroupResponse for _, group := range vs.Groups { + group := group respGroup := keyedMACTestGroupResponse{ID: group.ID} if group.KeyBits%8 != 0 { @@ -90,6 +91,7 @@ func (k *keyedMACPrimitive) Process(vectorSet []byte, m Transactable) (any, erro outputBytes := uint32le(group.MACBits / 8) for _, test := range group.Tests { + test := test respTest := keyedMACTestResponse{ID: test.ID} // Validate input. diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/rsa.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/rsa.go index d975026e667c9..923cdad0af450 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/rsa.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/rsa.go @@ -126,6 +126,8 @@ func processKeyGen(vectorSet []byte, m Transactable) (any, error) { var ret []rsaKeyGenTestGroupResponse for _, group := range parsed.Groups { + group := group + // GDT means "Generated data test", i.e. "please generate an RSA key". const expectedType = "GDT" if group.Type != expectedType { @@ -137,6 +139,8 @@ func processKeyGen(vectorSet []byte, m Transactable) (any, error) { } for _, test := range group.Tests { + test := test + m.TransactAsync("RSA/keyGen", 5, [][]byte{uint32le(group.ModulusBits)}, func(result [][]byte) error { response.Tests = append(response.Tests, rsaKeyGenTestResponse{ ID: test.ID, @@ -171,6 +175,8 @@ func processSigGen(vectorSet []byte, m Transactable) (any, error) { var ret []rsaSigGenTestGroupResponse for _, group := range parsed.Groups { + group := group + // GDT means "Generated data test", i.e. "please generate an RSA signature". const expectedType = "GDT" if group.Type != expectedType { @@ -184,6 +190,8 @@ func processSigGen(vectorSet []byte, m Transactable) (any, error) { operation := "RSA/sigGen/" + group.Hash + "/" + group.SigType for _, test := range group.Tests { + test := test + msg, err := hex.DecodeString(test.MessageHex) if err != nil { return nil, fmt.Errorf("test case %d/%d contains invalid hex: %s", group.ID, test.ID, err) @@ -226,6 +234,8 @@ func processSigVer(vectorSet []byte, m Transactable) (any, error) { var ret []rsaSigVerTestGroupResponse for _, group := range parsed.Groups { + group := group + // GDT means "Generated data test", which makes no sense in this context. const expectedType = "GDT" if group.Type != expectedType { @@ -248,6 +258,7 @@ func processSigVer(vectorSet []byte, m Transactable) (any, error) { operation := "RSA/sigVer/" + group.Hash + "/" + group.SigType for _, test := range group.Tests { + test := test msg, err := hex.DecodeString(test.MessageHex) if err != nil { return nil, fmt.Errorf("test case %d/%d contains invalid hex: %s", group.ID, test.ID, err) diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/subprocess.go index 9167b47205b15..f1cb5fa885cd7 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/subprocess.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/subprocess.go @@ -48,8 +48,6 @@ type Subprocess struct { pendingReads chan pendingRead // readerFinished is a channel that is closed if `readerRoutine` has finished (e.g. because of a read error). readerFinished chan struct{} - // readerError is set iff readerFinished is closed. If non-nil then it is the read error that caused `readerRoutine` to finished. - readerError error } // pendingRead represents an expected response from the modulewrapper. @@ -153,6 +151,7 @@ func (m *Subprocess) Close() { m.stdout.Close() m.stdin.Close() m.cmd.Wait() + close(m.pendingReads) <-m.readerFinished } @@ -176,7 +175,7 @@ func (m *Subprocess) flush() error { func (m *Subprocess) enqueueRead(pending pendingRead) error { select { case <-m.readerFinished: - return m.readerError + panic("attempted to enqueue request after the reader failed") default: } @@ -266,7 +265,7 @@ func (m *Subprocess) Transact(cmd string, expectedNumResults int, args ...[]byte case <-done: return result, nil case <-m.readerFinished: - return nil, m.readerError + panic("was still waiting for a result when the reader finished") } } @@ -284,13 +283,11 @@ func (m *Subprocess) readerRoutine() { result, err := m.readResult(pendingRead.cmd, pendingRead.expectedNumResults) if err != nil { - m.readerError = err - return + panic(fmt.Errorf("failed to read from subprocess: %w", err)) } if err := pendingRead.callback(result); err != nil { - m.readerError = err - return + panic(fmt.Errorf("result from subprocess was rejected: %w", err)) } } } diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/tls13.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/tls13.go index af2aae83586b0..bd121422ed7a4 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/tls13.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/tls13.go @@ -77,9 +77,11 @@ func (k *tls13) Process(vectorSet []byte, m Transactable) (any, error) { var respGroups []tls13TestGroupResponse for _, group := range parsed.Groups { + group := group groupResp := tls13TestGroupResponse{ID: group.ID} for _, test := range group.Tests { + test := test testResp := tls13TestResponse{ID: test.ID} clientHello, err := hex.DecodeString(test.ClientHelloHex) diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/tlskdf.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/tlskdf.go index 3a0d7ceaf9683..251b53e6d37d2 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/tlskdf.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/tlskdf.go @@ -64,6 +64,7 @@ func (k *tlsKDF) Process(vectorSet []byte, m Transactable) (any, error) { // See https://pages.nist.gov/ACVP/draft-celi-acvp-kdf-tls.html var ret []tlsKDFTestGroupResponse for _, group := range parsed.Groups { + group := group response := tlsKDFTestGroupResponse{ ID: group.ID, } @@ -82,6 +83,7 @@ func (k *tlsKDF) Process(vectorSet []byte, m Transactable) (any, error) { method := "TLSKDF/1.2/" + group.Hash for _, test := range group.Tests { + test := test pms, err := hex.DecodeString(test.PMSHex) if err != nil { return nil, err diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/xts.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/xts.go index e8134097381ba..5a9e7402a98c7 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/xts.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/subprocess/xts.go @@ -67,6 +67,7 @@ func (h *xts) Process(vectorSet []byte, m Transactable) (any, error) { var ret []xtsTestGroupResponse for _, group := range parsed.Groups { + group := group response := xtsTestGroupResponse{ ID: group.ID, } @@ -88,6 +89,7 @@ func (h *xts) Process(vectorSet []byte, m Transactable) (any, error) { funcName := "AES-XTS/" + group.Direction for _, test := range group.Tests { + test := test if group.KeyLen != len(test.KeyHex)*4/2 { return nil, fmt.Errorf("test case %d/%d contains hex message of length %d but specifies a key length of %d (remember that XTS keys are twice the length of the underlying key size)", group.ID, test.ID, len(test.KeyHex), group.KeyLen) } diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-GCM-randnonce.bz2 b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-GCM-randnonce.bz2 new file mode 100644 index 0000000000000..edab948a34a7a Binary files /dev/null and b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-GCM-randnonce.bz2 differ diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/expected/TLS12.bz2 b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/expected/TLS12.bz2 index d83b691619b9a..ff4ded067d905 100644 Binary files a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/expected/TLS12.bz2 and b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/expected/TLS12.bz2 differ diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/tests.json b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/tests.json index 421e25351f91d..6804b23b49d47 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/tests.json +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/tests.json @@ -5,6 +5,7 @@ {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-CTR.bz2", "Out": "expected/ACVP-AES-CTR.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-ECB.bz2", "Out": "expected/ACVP-AES-ECB.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-GCM.bz2", "Out": "expected/ACVP-AES-GCM.bz2"}, +{"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-GCM-randnonce.bz2", "Out": "expected/ACVP-AES-GCM-randnonce.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-GMAC.bz2", "Out": "expected/ACVP-AES-GMAC.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-KW.bz2", "Out": "expected/ACVP-AES-KW.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-KWP.bz2", "Out": "expected/ACVP-AES-KWP.bz2"}, diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-GCM-randnonce.bz2 b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-GCM-randnonce.bz2 new file mode 100644 index 0000000000000..0880f69026615 Binary files /dev/null and b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-GCM-randnonce.bz2 differ diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/vectors/TLS12.bz2 b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/vectors/TLS12.bz2 index d1911ab9dde67..00d9bbbed120c 100644 Binary files a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/vectors/TLS12.bz2 and b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/acvptool/test/vectors/TLS12.bz2 differ diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/modulewrapper/modulewrapper.cc index 0c1359e7f0ad0..dd17f563f08de 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/modulewrapper/modulewrapper.cc +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/acvp/modulewrapper/modulewrapper.cc @@ -75,7 +75,7 @@ class RequestBufferImpl : public RequestBuffer { // static std::unique_ptr RequestBuffer::New() { - return std::unique_ptr(new RequestBufferImpl); + return std::make_unique(); } static bool ReadAll(int fd, void *in_data, size_t data_len) { @@ -344,7 +344,7 @@ static bool GetConfig(const Span args[], ReplyCallback write_repl "algorithm": "ACVP-AES-GCM", "revision": "1.0", "direction": ["encrypt", "decrypt"], - "keyLen": [128, 192, 256], + "keyLen": [128, 256], "payloadLen": [{ "min": 0, "max": 65536, "increment": 8 }], @@ -353,7 +353,8 @@ static bool GetConfig(const Span args[], ReplyCallback write_repl }], "tagLen": [32, 64, 96, 104, 112, 120, 128], "ivLen": [96], - "ivGen": "external" + "ivGen": "internal", + "ivGenMode": "8.2.2" }, { "algorithm": "ACVP-AES-GMAC", @@ -549,7 +550,6 @@ static bool GetConfig(const Span args[], ReplyCallback write_repl "P-521" ], "hashAlg": [ - "SHA-1", "SHA2-224", "SHA2-256", "SHA2-384", @@ -561,7 +561,7 @@ static bool GetConfig(const Span args[], ReplyCallback write_repl { "algorithm": "RSA", "mode": "keyGen", - "revision": "FIPS186-4", + "revision": "FIPS186-5", "infoGeneratedByServer": true, "pubExpMode": "fixed", "fixedPubExp": "010001", @@ -589,7 +589,7 @@ static bool GetConfig(const Span args[], ReplyCallback write_repl { "algorithm": "RSA", "mode": "sigGen", - "revision": "FIPS186-4", + "revision": "FIPS186-5", "capabilities": [{ "sigType": "pkcs1v1.5", "properties": [{ @@ -700,26 +700,10 @@ static bool GetConfig(const Span args[], ReplyCallback write_repl { "algorithm": "RSA", "mode": "sigVer", - "revision": "FIPS186-4", + "revision": "FIPS186-5", "pubExpMode": "fixed", "fixedPubExp": "010001", "capabilities": [{ - "sigType": "pkcs1v1.5", - "properties": [{ - "modulo": 1024, - "hashPair": [{ - "hashAlg": "SHA2-224" - }, { - "hashAlg": "SHA2-256" - }, { - "hashAlg": "SHA2-384" - }, { - "hashAlg": "SHA2-512" - }, { - "hashAlg": "SHA-1" - }] - }] - },{ "sigType": "pkcs1v1.5", "properties": [{ "modulo": 2048, @@ -731,8 +715,6 @@ static bool GetConfig(const Span args[], ReplyCallback write_repl "hashAlg": "SHA2-384" }, { "hashAlg": "SHA2-512" - }, { - "hashAlg": "SHA-1" }] }] },{ @@ -747,8 +729,6 @@ static bool GetConfig(const Span args[], ReplyCallback write_repl "hashAlg": "SHA2-384" }, { "hashAlg": "SHA2-512" - }, { - "hashAlg": "SHA-1" }] }] },{ @@ -763,29 +743,6 @@ static bool GetConfig(const Span args[], ReplyCallback write_repl "hashAlg": "SHA2-384" }, { "hashAlg": "SHA2-512" - }, { - "hashAlg": "SHA-1" - }] - }] - },{ - "sigType": "pss", - "properties": [{ - "modulo": 1024, - "hashPair": [{ - "hashAlg": "SHA2-224", - "saltLen": 28 - }, { - "hashAlg": "SHA2-256", - "saltLen": 32 - }, { - "hashAlg": "SHA2-384", - "saltLen": 48 - }, { - "hashAlg": "SHA2-512/256", - "saltLen": 32 - }, { - "hashAlg": "SHA-1", - "saltLen": 20 }] }] },{ @@ -807,9 +764,6 @@ static bool GetConfig(const Span args[], ReplyCallback write_repl }, { "hashAlg": "SHA2-512/256", "saltLen": 32 - }, { - "hashAlg": "SHA-1", - "saltLen": 20 }] }] },{ @@ -831,9 +785,6 @@ static bool GetConfig(const Span args[], ReplyCallback write_repl }, { "hashAlg": "SHA2-512/256", "saltLen": 32 - }, { - "hashAlg": "SHA-1", - "saltLen": 20 }] }] },{ @@ -855,9 +806,6 @@ static bool GetConfig(const Span args[], ReplyCallback write_repl }, { "hashAlg": "SHA2-512/256", "saltLen": 32 - }, { - "hashAlg": "SHA-1", - "saltLen": 20 }] }] }] @@ -1148,13 +1096,12 @@ static bool AES_CTR(const Span args[], ReplyCallback write_reply) static bool AESGCMSetup(EVP_AEAD_CTX *ctx, Span tag_len_span, Span key) { - uint32_t tag_len_32; - if (tag_len_span.size() != sizeof(tag_len_32)) { + if (tag_len_span.size() != sizeof(uint32_t)) { LOG_ERROR("Tag size value is %u bytes, not an uint32_t\n", static_cast(tag_len_span.size())); return false; } - memcpy(&tag_len_32, tag_len_span.data(), sizeof(tag_len_32)); + const uint32_t tag_len_32 = CRYPTO_load_u32_le(tag_len_span.data()); const EVP_AEAD *aead; switch (key.size()) { @@ -1168,7 +1115,8 @@ static bool AESGCMSetup(EVP_AEAD_CTX *ctx, Span tag_len_span, aead = EVP_aead_aes_256_gcm(); break; default: - LOG_ERROR("Bad AES-GCM key length %u\n", static_cast(key.size())); + LOG_ERROR("Bad AES-GCM key length %u\n", + static_cast(key.size())); return false; } @@ -1182,6 +1130,41 @@ static bool AESGCMSetup(EVP_AEAD_CTX *ctx, Span tag_len_span, return true; } +static bool AESGCMRandNonceSetup(EVP_AEAD_CTX *ctx, + Span tag_len_span, + Span key) { + if (tag_len_span.size() != sizeof(uint32_t)) { + LOG_ERROR("Tag size value is %u bytes, not an uint32_t\n", + static_cast(tag_len_span.size())); + return false; + } + const uint32_t tag_len_32 = CRYPTO_load_u32_le(tag_len_span.data()); + + const EVP_AEAD *aead; + switch (key.size()) { + case 16: + aead = EVP_aead_aes_128_gcm_randnonce(); + break; + case 32: + aead = EVP_aead_aes_256_gcm_randnonce(); + break; + default: + LOG_ERROR("Bad AES-GCM key length %u\n", + static_cast(key.size())); + return false; + } + + constexpr size_t kNonceLength = 12; + if (!EVP_AEAD_CTX_init(ctx, aead, key.data(), key.size(), + tag_len_32 + kNonceLength, nullptr)) { + LOG_ERROR("Failed to setup AES-GCM with tag length %u\n", + static_cast(tag_len_32)); + return false; + } + + return true; +} + static bool AESCCMSetup(EVP_AEAD_CTX *ctx, Span tag_len_span, Span key) { uint32_t tag_len_32; @@ -2123,6 +2106,8 @@ static constexpr struct { {"AES-CTR/decrypt", 4, AES_CTR}, {"AES-GCM/seal", 5, AEADSeal}, {"AES-GCM/open", 5, AEADOpen}, + {"AES-GCM-randnonce/seal", 5, AEADSeal}, + {"AES-GCM-randnonce/open", 5, AEADOpen}, {"AES-KW/seal", 5, AESKeyWrapSeal}, {"AES-KW/open", 5, AESKeyWrapOpen}, {"AES-KWP/seal", 5, AESPaddedKeyWrapSeal}, diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/break-kat.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/break-kat.go index e4d323abcbbda..67c3300278e1b 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/break-kat.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/break-kat.go @@ -1,5 +1,3 @@ -//go:build - // break-kat corrupts a known-answer-test input in a binary and writes the // corrupted binary to stdout. This is used to demonstrate that the KATs in the // binary notice the error. @@ -27,7 +25,8 @@ var ( "SHA-1": "132fd9bad5c1826263bafbb699f707a5", "SHA-256": "ff3b857da7236a2baa0f396b51522217", "SHA-512": "212512f8d2ad8322781c6c4d69a9daa1", - "TLS-KDF": "abc3657b094c7628a0b282996fe75a75f4984fd94d4ecc2fcf53a2c469a3f731", + "TLS10-KDF": "abc3657b094c7628a0b282996fe75a75f4984fd94d4ecc2fcf53a2c469a3f731", + "TLS12-KDF": "c5438ee26fd4acbd259fc91855dc69bf884ee29322fcbfd2966a4623d42ec781", "TLS13-KDF": "024a0d80f357f2499a1244dac26dab66fc13ed85fca71dace146211119525874", "RSA-sign": "d2b56e53306f720d7929d8708bf46f1c22300305582b115bedcac722d8aa5ab2", "RSA-verify": "abe2cbc13d6bd39d48db5334ddbf8d070a93bdcb104e2cc5d0ee486ee295f6b31bda126c41890b98b73e70e6b65d82f95c663121755a90744c8d1c21148a1960be0eca446e9ff497f1345c537ef8119b9a4398e95c5c6de2b1c955905c5299d8ce7a3b6ab76380d9babdd15f610237e1f3f2aa1c1f1e770b62fbb596381b2ebdd77ecef9c90d4c92f7b6b05fed2936285fa94826e62055322a33b6f04c74ce69e5d8d737fb838b79d2d48e3daf71387531882531a95ac964d02ea413bf85952982bbc089527daff5b845c9a0f4d14ef1956d9c3acae882d12da66da0f35794f5ee32232333517db9315232a183b991654dbea41615345c885325926744a53915", diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/break-tests.sh b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/break-tests.sh index 695b62931bb51..736d066413b4d 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/break-tests.sh +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/break-tests.sh @@ -46,20 +46,24 @@ inferred_mode() { fi } +MODE=`inferred_mode` # Prefer mode from command line if present. -case "$1" in - local|device) - MODE=$1 - ;; - - "") - MODE=`inferred_mode` - ;; - - *) - usage - ;; -esac +while [ "$1" ]; do + case "$1" in + local|device) + MODE=$1 + ;; + + "32") + TEST32BIT="true" + ;; + + *) + usage + ;; + esac + shift +done check_directory() { test -d "$1" || die "Directory $1 not found." @@ -145,10 +149,16 @@ else # Device mode test "$ANDROID_BUILD_TOP" || die "'lunch aosp_arm64-eng' first" check_directory "$ANDROID_PRODUCT_OUT" - TEST_FIPS_BIN="$ANDROID_PRODUCT_OUT/system/bin/test_fips" + if [ "$TEST32BIT" ]; then + TEST_FIPS_BIN="$ANDROID_PRODUCT_OUT/system/bin/test_fips32" + LIBCRYPTO_BIN="$ANDROID_PRODUCT_OUT/system/lib/libcrypto.so" + LIBCRYPTO_BREAK_BIN="$ANDROID_PRODUCT_OUT/system/lib/libcrypto_for_testing.so" + else + TEST_FIPS_BIN="$ANDROID_PRODUCT_OUT/system/bin/test_fips" + LIBCRYPTO_BIN="$ANDROID_PRODUCT_OUT/system/lib64/libcrypto.so" + LIBCRYPTO_BREAK_BIN="$ANDROID_PRODUCT_OUT/system/lib64/libcrypto_for_testing.so" + fi check_file "$TEST_FIPS_BIN" - LIBCRYPTO_BIN="$ANDROID_PRODUCT_OUT/system/lib64/libcrypto.so" - LIBCRYPTO_BREAK_BIN="libcrypto.so" check_file "$LIBCRYPTO_BIN" check_file "$LIBCRYPTO_BREAK_BIN" diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/delocate.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/delocate.go index c28be558c2cd4..063d6ac6e7a5b 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/delocate.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/delocate.go @@ -35,7 +35,7 @@ import ( // inputFile represents a textual assembly file. type inputFile struct { path string - // index is a unique identifer given to this file. It's used for + // index is a unique identifier given to this file. It's used for // mapping local symbols. index int // isArchive indicates that the input should be processed as an ar @@ -263,6 +263,47 @@ func (d *delocation) processDirective(statement, directive *node32) (*node32, er return statement, nil } +func (d *delocation) processSymbolExpr(expr *node32, b *strings.Builder) bool { + changed := false + assertNodeType(expr, ruleSymbolExpr) + + for expr != nil { + atom := expr.up + assertNodeType(atom, ruleSymbolAtom) + + for term := atom.up; term != nil; term = skipWS(term.next) { + if term.pegRule == ruleSymbolExpr { + changed = d.processSymbolExpr(term, b) || changed + continue + } + + if term.pegRule != ruleLocalSymbol { + b.WriteString(d.contents(term)) + continue + } + + oldSymbol := d.contents(term) + newSymbol := d.mapLocalSymbol(oldSymbol) + if newSymbol != oldSymbol { + changed = true + } + + b.WriteString(newSymbol) + } + + next := skipWS(atom.next) + if next == nil { + break + } + assertNodeType(next, ruleSymbolOperator) + b.WriteString(d.contents(next)) + next = skipWS(next.next) + assertNodeType(next, ruleSymbolExpr) + expr = next + } + return changed +} + func (d *delocation) processLabelContainingDirective(statement, directive *node32) (*node32, error) { // The symbols within directives need to be mapped so that local // symbols in two different .s inputs don't collide. @@ -280,24 +321,12 @@ func (d *delocation) processLabelContainingDirective(statement, directive *node3 for node = skipWS(node.up); node != nil; node = skipWS(node.next) { assertNodeType(node, ruleSymbolArg) arg := node.up - var mapped string + assertNodeType(arg, ruleSymbolExpr) - for term := arg; term != nil; term = term.next { - if term.pegRule != ruleLocalSymbol { - mapped += d.contents(term) - continue - } - - oldSymbol := d.contents(term) - newSymbol := d.mapLocalSymbol(oldSymbol) - if newSymbol != oldSymbol { - changed = true - } - - mapped += newSymbol - } + var b strings.Builder + changed = d.processSymbolExpr(arg, &b) || changed - args = append(args, mapped) + args = append(args, b.String()) } if !changed { @@ -1134,6 +1163,22 @@ Args: args = append(args, argStr) + case ruleGOTAddress: + if instructionName != "leaq" { + return nil, fmt.Errorf("_GLOBAL_OFFSET_TABLE_ used outside of lea") + } + if i != 0 || len(argNodes) != 2 { + return nil, fmt.Errorf("Load of _GLOBAL_OFFSET_TABLE_ address didn't have expected form") + } + d.gotDeltaNeeded = true + changed = true + targetReg := d.contents(argNodes[1]) + args = append(args, ".Lboringssl_got_delta(%rip)") + wrappers = append(wrappers, func(k func()) { + k() + d.output.WriteString(fmt.Sprintf("\taddq .Lboringssl_got_delta(%%rip), %s\n", targetReg)) + }) + case ruleGOTLocation: if instructionName != "movabsq" { return nil, fmt.Errorf("_GLOBAL_OFFSET_TABLE_ lookup didn't use movabsq") @@ -1260,6 +1305,16 @@ func writeAarch64Function(w stringWriter, funcName string, writeContents func(st w.WriteString(".type " + funcName + ", @function\n") w.WriteString(funcName + ":\n") w.WriteString(".cfi_startproc\n") + // We insert a landing pad (`bti c` instruction) unconditionally at the beginning of + // every generated function so that they can be called indirectly (with `blr` or + // `br x16/x17`). The instruction is encoded in the HINT space as `hint #34` and is + // a no-op on machines or program states not supporting BTI (Branch Target Identification). + // None of the generated function bodies call other functions (with bl or blr), so we only + // insert a landing pad instead of signing and validating $lr with `paciasp` and `autiasp`. + // Normally we would also generate a .note.gnu.property section to annotate the assembly + // file as BTI-compatible, but if the input assembly files are BTI-compatible, they should + // already have those sections so there is no need to add an extra one ourselves. + w.WriteString("\thint #34 // bti c\n") writeContents(w) w.WriteString(".cfi_endproc\n") w.WriteString(".size " + funcName + ", .-" + funcName + "\n") @@ -1637,9 +1692,6 @@ func main() { // preprocessor, but we don't want the compiler complaining that // "argument unused during compilation". cppCommand = append(cppCommand, "-Wno-unused-command-line-argument") - // We are preprocessing for assembly output and need to simulate that - // environment for arm_arch.h. - cppCommand = append(cppCommand, "-D__ASSEMBLER__=1") for includePath := range includePaths { cppCommand = append(cppCommand, "-I"+includePath) diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/delocate.peg b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/delocate.peg index 9db3e8cfa9b6c..3ce01edf262ea 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/delocate.peg +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/delocate.peg @@ -45,19 +45,16 @@ Arg <- QuotedArg / [[0-9a-z%+\-*_@.]]* QuotedArg <- '"' QuotedText '"' QuotedText <- (EscapedChar / [^"])* LabelContainingDirective <- LabelContainingDirectiveName WS SymbolArgs -LabelContainingDirectiveName <- ".xword" / ".word" / ".long" / ".set" / ".byte" / ".8byte" / ".4byte" / ".quad" / ".tc" / ".localentry" / ".size" / ".type" / ".uleb128" / ".sleb128" +LabelContainingDirectiveName <- ".xword" / ".word" / ".hword" / ".long" / ".set" / ".byte" / ".8byte" / ".4byte" / ".quad" / ".tc" / ".localentry" / ".size" / ".type" / ".uleb128" / ".sleb128" SymbolArgs <- SymbolArg ((WS? ',' WS?) SymbolArg)* -SymbolShift <- ('<<' / '>>') WS? [0-9]+ -SymbolArg <- (OpenParen WS?)? ( - Offset / - SymbolType / - (Offset / LocalSymbol / SymbolName / Dot) (WS? Operator WS? (Offset / LocalSymbol / SymbolName))* / - LocalSymbol TCMarker? / - SymbolName Offset / - SymbolName TCMarker?) - (WS? CloseParen)? (WS? SymbolShift)? + +SymbolArg <- SymbolExpr +SymbolExpr <- SymbolAtom (WS? SymbolOperator WS? SymbolExpr)? +SymbolAtom <- Offset / SymbolType / LocalSymbol TCMarker? / SymbolName Offset / SymbolName TCMarker? / Dot / OpenParen WS? SymbolExpr WS? CloseParen +SymbolOperator <- '+' / '-' / '|' / '<<' / '>>' OpenParen <- '(' CloseParen <- ')' + SymbolType <- [@%] ('function' / 'object') Dot <- '.' TCMarker <- '[TC]' @@ -71,8 +68,9 @@ LocalLabel <- [0-9][0-9$]* LocalLabelRef <- [0-9][0-9$]*[bf] Instruction <- InstructionName (WS InstructionArg ((WS? ',' WS?) InstructionArg)*)? InstructionName <- [[A-Z]][[A-Z.0-9]]* [.+\-]? -InstructionArg <- IndirectionIndicator? (ARMConstantTweak / RegisterOrConstant / LocalLabelRef / TOCRefHigh / TOCRefLow / GOTLocation / GOTSymbolOffset / MemoryRef) AVX512Token* +InstructionArg <- IndirectionIndicator? (ARMConstantTweak / RegisterOrConstant / LocalLabelRef / TOCRefHigh / TOCRefLow / GOTLocation / GOTAddress / GOTSymbolOffset / MemoryRef) AVX512Token* GOTLocation <- '$_GLOBAL_OFFSET_TABLE_-' LocalSymbol +GOTAddress <- '_GLOBAL_OFFSET_TABLE_(%rip)' GOTSymbolOffset <- ('$' SymbolName '@GOT' 'OFF'?) / (":got:" SymbolName) AVX512Token <- WS? '{' '%'? [0-9a-z]* '}' TOCRefHigh <- '.TOC.-' ('0b' / ('.L' [a-zA-Z_0-9]+)) "@ha" @@ -84,9 +82,17 @@ RegisterOrConstant <- (('%'[[A-Z]][[A-Z0-9]]*) / ('#' '~'? '(' [0-9] WS? "<<" WS? [0-9] ')' ) / ARMRegister) ![fb:(+\-] -ARMConstantTweak <- (([us] "xt" [xwhb]) / "lsl" / "lsr" / "ror" / "asr") (WS '#' Offset)? -ARMRegister <- "sp" / ([xwdqshb] [0-9] [0-9]?) / "xzr" / "wzr" / "NZCV" / ARMVectorRegister / ('{' WS? ARMVectorRegister (',' WS? ARMVectorRegister)* WS? '}' ('[' [0-9] [0-9]? ']')? ) -ARMVectorRegister <- "v" [0-9] [0-9]? ('.' [0-9]* [bsdhq] ('[' [0-9] [0-9]? ']')? )? +ARMConstantTweak <- ((([us] "xt" [xwhb]) / "lsl" / "lsr" / "ror" / "asr") (WS '#' Offset)?) / + "mul vl" / # multiply offset by the hardware's vector length + "mul #" [0-9] +ARMRegister <- "sp" / + ([xwdqshb] [0-9] [0-9]?) / + "xzr" / "wzr" / "NZCV" / SVE2PredicateRegister / ARMVectorRegister / SVE2SpecialValue / + ('{' WS? ARMVectorRegister (',' WS? ARMVectorRegister)* WS? '}' ('[' [0-9] [0-9]? ']')? ) +ARMVectorRegister <- [pvz] [0-9] [0-9]? ![[0-9a-z_]] ('.' [0-9]* [bsdhq] ('[' [0-9] [0-9]? ']')? )? +SVE2PredicateRegister <- "p" [0-9] [0-9]? "/" [[mz]] +# https://developer.arm.com/documentation/ddi0596/2020-12/SVE-Instructions/INCD--INCH--INCW--vector---Increment-vector-by-multiple-of-predicate-constraint-element-count- +SVE2SpecialValue <- ("pow2" / ("vl" [12345678] ![0-9] ) / "vl16" / "vl32" / "vl64" / "vl128" / "vl256" / "mul3" / "mul4" / "all") ![[0-9a-z_]] # Compilers only output a very limited number of expression forms. Rather than # implement a full expression parser, this enumerate those forms plus a few # that appear in our hand-written assembly. diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/delocate.peg.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/delocate.peg.go index 01a1fc2c68ef3..856518d9dc557 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/delocate.peg.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/delocate.peg.go @@ -33,8 +33,10 @@ const ( ruleLabelContainingDirective ruleLabelContainingDirectiveName ruleSymbolArgs - ruleSymbolShift ruleSymbolArg + ruleSymbolExpr + ruleSymbolAtom + ruleSymbolOperator ruleOpenParen ruleCloseParen ruleSymbolType @@ -52,6 +54,7 @@ const ( ruleInstructionName ruleInstructionArg ruleGOTLocation + ruleGOTAddress ruleGOTSymbolOffset ruleAVX512Token ruleTOCRefHigh @@ -61,6 +64,8 @@ const ( ruleARMConstantTweak ruleARMRegister ruleARMVectorRegister + ruleSVE2PredicateRegister + ruleSVE2SpecialValue ruleMemoryRef ruleSymbolRef ruleLow12BitsSymbolRef @@ -91,8 +96,10 @@ var rul3s = [...]string{ "LabelContainingDirective", "LabelContainingDirectiveName", "SymbolArgs", - "SymbolShift", "SymbolArg", + "SymbolExpr", + "SymbolAtom", + "SymbolOperator", "OpenParen", "CloseParen", "SymbolType", @@ -110,6 +117,7 @@ var rul3s = [...]string{ "InstructionName", "InstructionArg", "GOTLocation", + "GOTAddress", "GOTSymbolOffset", "AVX512Token", "TOCRefHigh", @@ -119,6 +127,8 @@ var rul3s = [...]string{ "ARMConstantTweak", "ARMRegister", "ARMVectorRegister", + "SVE2PredicateRegister", + "SVE2SpecialValue", "MemoryRef", "SymbolRef", "Low12BitsSymbolRef", @@ -244,7 +254,7 @@ func (t *tokens32) Tokens() []token32 { type Asm struct { Buffer string buffer []rune - rules [55]func() bool + rules [60]func() bool parse func(rule ...int) error reset func() Pretty bool @@ -1421,7 +1431,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position145, tokenIndex145 return false }, - /* 13 LabelContainingDirectiveName <- <(('.' ('x' / 'X') ('w' / 'W') ('o' / 'O') ('r' / 'R') ('d' / 'D')) / ('.' ('w' / 'W') ('o' / 'O') ('r' / 'R') ('d' / 'D')) / ('.' ('l' / 'L') ('o' / 'O') ('n' / 'N') ('g' / 'G')) / ('.' ('s' / 'S') ('e' / 'E') ('t' / 'T')) / ('.' ('b' / 'B') ('y' / 'Y') ('t' / 'T') ('e' / 'E')) / ('.' '8' ('b' / 'B') ('y' / 'Y') ('t' / 'T') ('e' / 'E')) / ('.' '4' ('b' / 'B') ('y' / 'Y') ('t' / 'T') ('e' / 'E')) / ('.' ('q' / 'Q') ('u' / 'U') ('a' / 'A') ('d' / 'D')) / ('.' ('t' / 'T') ('c' / 'C')) / ('.' ('l' / 'L') ('o' / 'O') ('c' / 'C') ('a' / 'A') ('l' / 'L') ('e' / 'E') ('n' / 'N') ('t' / 'T') ('r' / 'R') ('y' / 'Y')) / ('.' ('s' / 'S') ('i' / 'I') ('z' / 'Z') ('e' / 'E')) / ('.' ('t' / 'T') ('y' / 'Y') ('p' / 'P') ('e' / 'E')) / ('.' ('u' / 'U') ('l' / 'L') ('e' / 'E') ('b' / 'B') '1' '2' '8') / ('.' ('s' / 'S') ('l' / 'L') ('e' / 'E') ('b' / 'B') '1' '2' '8'))> */ + /* 13 LabelContainingDirectiveName <- <(('.' ('x' / 'X') ('w' / 'W') ('o' / 'O') ('r' / 'R') ('d' / 'D')) / ('.' ('w' / 'W') ('o' / 'O') ('r' / 'R') ('d' / 'D')) / ('.' ('h' / 'H') ('w' / 'W') ('o' / 'O') ('r' / 'R') ('d' / 'D')) / ('.' ('l' / 'L') ('o' / 'O') ('n' / 'N') ('g' / 'G')) / ('.' ('s' / 'S') ('e' / 'E') ('t' / 'T')) / ('.' ('b' / 'B') ('y' / 'Y') ('t' / 'T') ('e' / 'E')) / ('.' '8' ('b' / 'B') ('y' / 'Y') ('t' / 'T') ('e' / 'E')) / ('.' '4' ('b' / 'B') ('y' / 'Y') ('t' / 'T') ('e' / 'E')) / ('.' ('q' / 'Q') ('u' / 'U') ('a' / 'A') ('d' / 'D')) / ('.' ('t' / 'T') ('c' / 'C')) / ('.' ('l' / 'L') ('o' / 'O') ('c' / 'C') ('a' / 'A') ('l' / 'L') ('e' / 'E') ('n' / 'N') ('t' / 'T') ('r' / 'R') ('y' / 'Y')) / ('.' ('s' / 'S') ('i' / 'I') ('z' / 'Z') ('e' / 'E')) / ('.' ('t' / 'T') ('y' / 'Y') ('p' / 'P') ('e' / 'E')) / ('.' ('u' / 'U') ('l' / 'L') ('e' / 'E') ('b' / 'B') '1' '2' '8') / ('.' ('s' / 'S') ('l' / 'L') ('e' / 'E') ('b' / 'B') '1' '2' '8'))> */ func() bool { position147, tokenIndex147 := position, tokenIndex { @@ -1583,14 +1593,14 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position++ { position171, tokenIndex171 := position, tokenIndex - if buffer[position] != rune('l') { + if buffer[position] != rune('h') { goto l172 } position++ goto l171 l172: position, tokenIndex = position171, tokenIndex171 - if buffer[position] != rune('L') { + if buffer[position] != rune('H') { goto l170 } position++ @@ -1598,14 +1608,14 @@ func (p *Asm) Init(options ...func(*Asm) error) error { l171: { position173, tokenIndex173 := position, tokenIndex - if buffer[position] != rune('o') { + if buffer[position] != rune('w') { goto l174 } position++ goto l173 l174: position, tokenIndex = position173, tokenIndex173 - if buffer[position] != rune('O') { + if buffer[position] != rune('W') { goto l170 } position++ @@ -1613,14 +1623,14 @@ func (p *Asm) Init(options ...func(*Asm) error) error { l173: { position175, tokenIndex175 := position, tokenIndex - if buffer[position] != rune('n') { + if buffer[position] != rune('o') { goto l176 } position++ goto l175 l176: position, tokenIndex = position175, tokenIndex175 - if buffer[position] != rune('N') { + if buffer[position] != rune('O') { goto l170 } position++ @@ -1628,119 +1638,119 @@ func (p *Asm) Init(options ...func(*Asm) error) error { l175: { position177, tokenIndex177 := position, tokenIndex - if buffer[position] != rune('g') { + if buffer[position] != rune('r') { goto l178 } position++ goto l177 l178: position, tokenIndex = position177, tokenIndex177 - if buffer[position] != rune('G') { + if buffer[position] != rune('R') { goto l170 } position++ } l177: - goto l149 - l170: - position, tokenIndex = position149, tokenIndex149 - if buffer[position] != rune('.') { - goto l179 - } - position++ { - position180, tokenIndex180 := position, tokenIndex - if buffer[position] != rune('s') { - goto l181 + position179, tokenIndex179 := position, tokenIndex + if buffer[position] != rune('d') { + goto l180 } position++ - goto l180 - l181: - position, tokenIndex = position180, tokenIndex180 - if buffer[position] != rune('S') { - goto l179 + goto l179 + l180: + position, tokenIndex = position179, tokenIndex179 + if buffer[position] != rune('D') { + goto l170 } position++ } - l180: + l179: + goto l149 + l170: + position, tokenIndex = position149, tokenIndex149 + if buffer[position] != rune('.') { + goto l181 + } + position++ { position182, tokenIndex182 := position, tokenIndex - if buffer[position] != rune('e') { + if buffer[position] != rune('l') { goto l183 } position++ goto l182 l183: position, tokenIndex = position182, tokenIndex182 - if buffer[position] != rune('E') { - goto l179 + if buffer[position] != rune('L') { + goto l181 } position++ } l182: { position184, tokenIndex184 := position, tokenIndex - if buffer[position] != rune('t') { + if buffer[position] != rune('o') { goto l185 } position++ goto l184 l185: position, tokenIndex = position184, tokenIndex184 - if buffer[position] != rune('T') { - goto l179 + if buffer[position] != rune('O') { + goto l181 } position++ } l184: - goto l149 - l179: - position, tokenIndex = position149, tokenIndex149 - if buffer[position] != rune('.') { - goto l186 - } - position++ { - position187, tokenIndex187 := position, tokenIndex - if buffer[position] != rune('b') { - goto l188 + position186, tokenIndex186 := position, tokenIndex + if buffer[position] != rune('n') { + goto l187 } position++ - goto l187 - l188: - position, tokenIndex = position187, tokenIndex187 - if buffer[position] != rune('B') { - goto l186 + goto l186 + l187: + position, tokenIndex = position186, tokenIndex186 + if buffer[position] != rune('N') { + goto l181 } position++ } - l187: + l186: { - position189, tokenIndex189 := position, tokenIndex - if buffer[position] != rune('y') { - goto l190 + position188, tokenIndex188 := position, tokenIndex + if buffer[position] != rune('g') { + goto l189 } position++ - goto l189 - l190: - position, tokenIndex = position189, tokenIndex189 - if buffer[position] != rune('Y') { - goto l186 + goto l188 + l189: + position, tokenIndex = position188, tokenIndex188 + if buffer[position] != rune('G') { + goto l181 } position++ } - l189: + l188: + goto l149 + l181: + position, tokenIndex = position149, tokenIndex149 + if buffer[position] != rune('.') { + goto l190 + } + position++ { position191, tokenIndex191 := position, tokenIndex - if buffer[position] != rune('t') { + if buffer[position] != rune('s') { goto l192 } position++ goto l191 l192: position, tokenIndex = position191, tokenIndex191 - if buffer[position] != rune('T') { - goto l186 + if buffer[position] != rune('S') { + goto l190 } position++ } @@ -1755,694 +1765,776 @@ func (p *Asm) Init(options ...func(*Asm) error) error { l194: position, tokenIndex = position193, tokenIndex193 if buffer[position] != rune('E') { - goto l186 + goto l190 } position++ } l193: - goto l149 - l186: - position, tokenIndex = position149, tokenIndex149 - if buffer[position] != rune('.') { - goto l195 - } - position++ - if buffer[position] != rune('8') { - goto l195 - } - position++ { - position196, tokenIndex196 := position, tokenIndex - if buffer[position] != rune('b') { - goto l197 + position195, tokenIndex195 := position, tokenIndex + if buffer[position] != rune('t') { + goto l196 } position++ - goto l196 - l197: - position, tokenIndex = position196, tokenIndex196 - if buffer[position] != rune('B') { - goto l195 + goto l195 + l196: + position, tokenIndex = position195, tokenIndex195 + if buffer[position] != rune('T') { + goto l190 } position++ } - l196: + l195: + goto l149 + l190: + position, tokenIndex = position149, tokenIndex149 + if buffer[position] != rune('.') { + goto l197 + } + position++ { position198, tokenIndex198 := position, tokenIndex - if buffer[position] != rune('y') { + if buffer[position] != rune('b') { goto l199 } position++ goto l198 l199: position, tokenIndex = position198, tokenIndex198 - if buffer[position] != rune('Y') { - goto l195 + if buffer[position] != rune('B') { + goto l197 } position++ } l198: { position200, tokenIndex200 := position, tokenIndex - if buffer[position] != rune('t') { + if buffer[position] != rune('y') { goto l201 } position++ goto l200 l201: position, tokenIndex = position200, tokenIndex200 - if buffer[position] != rune('T') { - goto l195 + if buffer[position] != rune('Y') { + goto l197 } position++ } l200: { position202, tokenIndex202 := position, tokenIndex - if buffer[position] != rune('e') { + if buffer[position] != rune('t') { goto l203 } position++ goto l202 l203: position, tokenIndex = position202, tokenIndex202 - if buffer[position] != rune('E') { - goto l195 + if buffer[position] != rune('T') { + goto l197 } position++ } l202: + { + position204, tokenIndex204 := position, tokenIndex + if buffer[position] != rune('e') { + goto l205 + } + position++ + goto l204 + l205: + position, tokenIndex = position204, tokenIndex204 + if buffer[position] != rune('E') { + goto l197 + } + position++ + } + l204: goto l149 - l195: + l197: position, tokenIndex = position149, tokenIndex149 if buffer[position] != rune('.') { - goto l204 + goto l206 } position++ - if buffer[position] != rune('4') { - goto l204 + if buffer[position] != rune('8') { + goto l206 } position++ - { - position205, tokenIndex205 := position, tokenIndex - if buffer[position] != rune('b') { - goto l206 - } - position++ - goto l205 - l206: - position, tokenIndex = position205, tokenIndex205 - if buffer[position] != rune('B') { - goto l204 - } - position++ - } - l205: { position207, tokenIndex207 := position, tokenIndex - if buffer[position] != rune('y') { + if buffer[position] != rune('b') { goto l208 } position++ goto l207 l208: position, tokenIndex = position207, tokenIndex207 - if buffer[position] != rune('Y') { - goto l204 + if buffer[position] != rune('B') { + goto l206 } position++ } l207: { position209, tokenIndex209 := position, tokenIndex - if buffer[position] != rune('t') { + if buffer[position] != rune('y') { goto l210 } position++ goto l209 l210: position, tokenIndex = position209, tokenIndex209 - if buffer[position] != rune('T') { - goto l204 + if buffer[position] != rune('Y') { + goto l206 } position++ } l209: { position211, tokenIndex211 := position, tokenIndex - if buffer[position] != rune('e') { + if buffer[position] != rune('t') { goto l212 } position++ goto l211 l212: position, tokenIndex = position211, tokenIndex211 - if buffer[position] != rune('E') { - goto l204 + if buffer[position] != rune('T') { + goto l206 } position++ } l211: - goto l149 - l204: - position, tokenIndex = position149, tokenIndex149 - if buffer[position] != rune('.') { - goto l213 - } - position++ { - position214, tokenIndex214 := position, tokenIndex - if buffer[position] != rune('q') { - goto l215 + position213, tokenIndex213 := position, tokenIndex + if buffer[position] != rune('e') { + goto l214 } position++ - goto l214 - l215: - position, tokenIndex = position214, tokenIndex214 - if buffer[position] != rune('Q') { - goto l213 + goto l213 + l214: + position, tokenIndex = position213, tokenIndex213 + if buffer[position] != rune('E') { + goto l206 } position++ } - l214: + l213: + goto l149 + l206: + position, tokenIndex = position149, tokenIndex149 + if buffer[position] != rune('.') { + goto l215 + } + position++ + if buffer[position] != rune('4') { + goto l215 + } + position++ { position216, tokenIndex216 := position, tokenIndex - if buffer[position] != rune('u') { + if buffer[position] != rune('b') { goto l217 } position++ goto l216 l217: position, tokenIndex = position216, tokenIndex216 - if buffer[position] != rune('U') { - goto l213 + if buffer[position] != rune('B') { + goto l215 } position++ } l216: { position218, tokenIndex218 := position, tokenIndex - if buffer[position] != rune('a') { + if buffer[position] != rune('y') { goto l219 } position++ goto l218 l219: position, tokenIndex = position218, tokenIndex218 - if buffer[position] != rune('A') { - goto l213 + if buffer[position] != rune('Y') { + goto l215 } position++ } l218: { position220, tokenIndex220 := position, tokenIndex - if buffer[position] != rune('d') { + if buffer[position] != rune('t') { goto l221 } position++ goto l220 l221: position, tokenIndex = position220, tokenIndex220 - if buffer[position] != rune('D') { - goto l213 + if buffer[position] != rune('T') { + goto l215 } position++ } l220: - goto l149 - l213: - position, tokenIndex = position149, tokenIndex149 - if buffer[position] != rune('.') { - goto l222 - } - position++ { - position223, tokenIndex223 := position, tokenIndex - if buffer[position] != rune('t') { - goto l224 + position222, tokenIndex222 := position, tokenIndex + if buffer[position] != rune('e') { + goto l223 } position++ - goto l223 - l224: - position, tokenIndex = position223, tokenIndex223 - if buffer[position] != rune('T') { - goto l222 + goto l222 + l223: + position, tokenIndex = position222, tokenIndex222 + if buffer[position] != rune('E') { + goto l215 } position++ } - l223: + l222: + goto l149 + l215: + position, tokenIndex = position149, tokenIndex149 + if buffer[position] != rune('.') { + goto l224 + } + position++ { position225, tokenIndex225 := position, tokenIndex - if buffer[position] != rune('c') { + if buffer[position] != rune('q') { goto l226 } position++ goto l225 l226: position, tokenIndex = position225, tokenIndex225 - if buffer[position] != rune('C') { - goto l222 + if buffer[position] != rune('Q') { + goto l224 } position++ } l225: - goto l149 - l222: - position, tokenIndex = position149, tokenIndex149 - if buffer[position] != rune('.') { - goto l227 - } - position++ { - position228, tokenIndex228 := position, tokenIndex - if buffer[position] != rune('l') { - goto l229 + position227, tokenIndex227 := position, tokenIndex + if buffer[position] != rune('u') { + goto l228 } position++ - goto l228 - l229: - position, tokenIndex = position228, tokenIndex228 - if buffer[position] != rune('L') { - goto l227 + goto l227 + l228: + position, tokenIndex = position227, tokenIndex227 + if buffer[position] != rune('U') { + goto l224 } position++ } - l228: + l227: { - position230, tokenIndex230 := position, tokenIndex - if buffer[position] != rune('o') { - goto l231 + position229, tokenIndex229 := position, tokenIndex + if buffer[position] != rune('a') { + goto l230 } position++ - goto l230 - l231: - position, tokenIndex = position230, tokenIndex230 - if buffer[position] != rune('O') { - goto l227 + goto l229 + l230: + position, tokenIndex = position229, tokenIndex229 + if buffer[position] != rune('A') { + goto l224 } position++ } - l230: + l229: { - position232, tokenIndex232 := position, tokenIndex - if buffer[position] != rune('c') { - goto l233 + position231, tokenIndex231 := position, tokenIndex + if buffer[position] != rune('d') { + goto l232 } position++ - goto l232 - l233: - position, tokenIndex = position232, tokenIndex232 - if buffer[position] != rune('C') { - goto l227 + goto l231 + l232: + position, tokenIndex = position231, tokenIndex231 + if buffer[position] != rune('D') { + goto l224 } position++ } - l232: + l231: + goto l149 + l224: + position, tokenIndex = position149, tokenIndex149 + if buffer[position] != rune('.') { + goto l233 + } + position++ { position234, tokenIndex234 := position, tokenIndex - if buffer[position] != rune('a') { + if buffer[position] != rune('t') { goto l235 } position++ goto l234 l235: position, tokenIndex = position234, tokenIndex234 - if buffer[position] != rune('A') { - goto l227 + if buffer[position] != rune('T') { + goto l233 } position++ } l234: { position236, tokenIndex236 := position, tokenIndex - if buffer[position] != rune('l') { + if buffer[position] != rune('c') { goto l237 } position++ goto l236 l237: position, tokenIndex = position236, tokenIndex236 - if buffer[position] != rune('L') { - goto l227 + if buffer[position] != rune('C') { + goto l233 } position++ } l236: + goto l149 + l233: + position, tokenIndex = position149, tokenIndex149 + if buffer[position] != rune('.') { + goto l238 + } + position++ { - position238, tokenIndex238 := position, tokenIndex - if buffer[position] != rune('e') { - goto l239 + position239, tokenIndex239 := position, tokenIndex + if buffer[position] != rune('l') { + goto l240 } position++ - goto l238 - l239: - position, tokenIndex = position238, tokenIndex238 - if buffer[position] != rune('E') { - goto l227 + goto l239 + l240: + position, tokenIndex = position239, tokenIndex239 + if buffer[position] != rune('L') { + goto l238 } position++ } - l238: + l239: { - position240, tokenIndex240 := position, tokenIndex - if buffer[position] != rune('n') { - goto l241 + position241, tokenIndex241 := position, tokenIndex + if buffer[position] != rune('o') { + goto l242 } position++ - goto l240 - l241: - position, tokenIndex = position240, tokenIndex240 - if buffer[position] != rune('N') { - goto l227 + goto l241 + l242: + position, tokenIndex = position241, tokenIndex241 + if buffer[position] != rune('O') { + goto l238 } position++ } - l240: + l241: { - position242, tokenIndex242 := position, tokenIndex - if buffer[position] != rune('t') { - goto l243 + position243, tokenIndex243 := position, tokenIndex + if buffer[position] != rune('c') { + goto l244 } position++ - goto l242 - l243: - position, tokenIndex = position242, tokenIndex242 - if buffer[position] != rune('T') { - goto l227 + goto l243 + l244: + position, tokenIndex = position243, tokenIndex243 + if buffer[position] != rune('C') { + goto l238 } position++ } - l242: + l243: { - position244, tokenIndex244 := position, tokenIndex - if buffer[position] != rune('r') { - goto l245 + position245, tokenIndex245 := position, tokenIndex + if buffer[position] != rune('a') { + goto l246 } position++ - goto l244 - l245: - position, tokenIndex = position244, tokenIndex244 - if buffer[position] != rune('R') { - goto l227 + goto l245 + l246: + position, tokenIndex = position245, tokenIndex245 + if buffer[position] != rune('A') { + goto l238 } position++ } - l244: + l245: { - position246, tokenIndex246 := position, tokenIndex - if buffer[position] != rune('y') { - goto l247 + position247, tokenIndex247 := position, tokenIndex + if buffer[position] != rune('l') { + goto l248 } position++ - goto l246 - l247: - position, tokenIndex = position246, tokenIndex246 - if buffer[position] != rune('Y') { - goto l227 + goto l247 + l248: + position, tokenIndex = position247, tokenIndex247 + if buffer[position] != rune('L') { + goto l238 } position++ } - l246: - goto l149 - l227: - position, tokenIndex = position149, tokenIndex149 - if buffer[position] != rune('.') { - goto l248 - } - position++ + l247: { position249, tokenIndex249 := position, tokenIndex - if buffer[position] != rune('s') { + if buffer[position] != rune('e') { goto l250 } position++ goto l249 l250: position, tokenIndex = position249, tokenIndex249 - if buffer[position] != rune('S') { - goto l248 + if buffer[position] != rune('E') { + goto l238 } position++ } l249: { position251, tokenIndex251 := position, tokenIndex - if buffer[position] != rune('i') { + if buffer[position] != rune('n') { goto l252 } position++ goto l251 l252: position, tokenIndex = position251, tokenIndex251 - if buffer[position] != rune('I') { - goto l248 + if buffer[position] != rune('N') { + goto l238 } position++ } l251: { position253, tokenIndex253 := position, tokenIndex - if buffer[position] != rune('z') { + if buffer[position] != rune('t') { goto l254 } position++ goto l253 l254: position, tokenIndex = position253, tokenIndex253 - if buffer[position] != rune('Z') { - goto l248 + if buffer[position] != rune('T') { + goto l238 } position++ } l253: { position255, tokenIndex255 := position, tokenIndex - if buffer[position] != rune('e') { + if buffer[position] != rune('r') { goto l256 } position++ goto l255 l256: position, tokenIndex = position255, tokenIndex255 - if buffer[position] != rune('E') { - goto l248 + if buffer[position] != rune('R') { + goto l238 } position++ } l255: - goto l149 - l248: - position, tokenIndex = position149, tokenIndex149 - if buffer[position] != rune('.') { - goto l257 - } - position++ { - position258, tokenIndex258 := position, tokenIndex - if buffer[position] != rune('t') { - goto l259 + position257, tokenIndex257 := position, tokenIndex + if buffer[position] != rune('y') { + goto l258 } position++ - goto l258 - l259: - position, tokenIndex = position258, tokenIndex258 - if buffer[position] != rune('T') { - goto l257 + goto l257 + l258: + position, tokenIndex = position257, tokenIndex257 + if buffer[position] != rune('Y') { + goto l238 } position++ } - l258: + l257: + goto l149 + l238: + position, tokenIndex = position149, tokenIndex149 + if buffer[position] != rune('.') { + goto l259 + } + position++ { position260, tokenIndex260 := position, tokenIndex - if buffer[position] != rune('y') { + if buffer[position] != rune('s') { goto l261 } position++ goto l260 l261: position, tokenIndex = position260, tokenIndex260 - if buffer[position] != rune('Y') { - goto l257 + if buffer[position] != rune('S') { + goto l259 } position++ } l260: { position262, tokenIndex262 := position, tokenIndex - if buffer[position] != rune('p') { + if buffer[position] != rune('i') { goto l263 } position++ goto l262 l263: position, tokenIndex = position262, tokenIndex262 - if buffer[position] != rune('P') { - goto l257 + if buffer[position] != rune('I') { + goto l259 } position++ } l262: { position264, tokenIndex264 := position, tokenIndex - if buffer[position] != rune('e') { + if buffer[position] != rune('z') { goto l265 } position++ goto l264 l265: position, tokenIndex = position264, tokenIndex264 - if buffer[position] != rune('E') { - goto l257 + if buffer[position] != rune('Z') { + goto l259 } position++ } l264: - goto l149 - l257: - position, tokenIndex = position149, tokenIndex149 - if buffer[position] != rune('.') { - goto l266 - } - position++ { - position267, tokenIndex267 := position, tokenIndex - if buffer[position] != rune('u') { - goto l268 + position266, tokenIndex266 := position, tokenIndex + if buffer[position] != rune('e') { + goto l267 } position++ - goto l267 - l268: - position, tokenIndex = position267, tokenIndex267 - if buffer[position] != rune('U') { - goto l266 + goto l266 + l267: + position, tokenIndex = position266, tokenIndex266 + if buffer[position] != rune('E') { + goto l259 } position++ } - l267: + l266: + goto l149 + l259: + position, tokenIndex = position149, tokenIndex149 + if buffer[position] != rune('.') { + goto l268 + } + position++ { position269, tokenIndex269 := position, tokenIndex - if buffer[position] != rune('l') { + if buffer[position] != rune('t') { goto l270 } position++ goto l269 l270: position, tokenIndex = position269, tokenIndex269 - if buffer[position] != rune('L') { - goto l266 + if buffer[position] != rune('T') { + goto l268 } position++ } l269: { position271, tokenIndex271 := position, tokenIndex - if buffer[position] != rune('e') { + if buffer[position] != rune('y') { goto l272 } position++ goto l271 l272: position, tokenIndex = position271, tokenIndex271 - if buffer[position] != rune('E') { - goto l266 + if buffer[position] != rune('Y') { + goto l268 } position++ } l271: { position273, tokenIndex273 := position, tokenIndex - if buffer[position] != rune('b') { + if buffer[position] != rune('p') { goto l274 } position++ goto l273 l274: position, tokenIndex = position273, tokenIndex273 - if buffer[position] != rune('B') { - goto l266 + if buffer[position] != rune('P') { + goto l268 } position++ } l273: + { + position275, tokenIndex275 := position, tokenIndex + if buffer[position] != rune('e') { + goto l276 + } + position++ + goto l275 + l276: + position, tokenIndex = position275, tokenIndex275 + if buffer[position] != rune('E') { + goto l268 + } + position++ + } + l275: + goto l149 + l268: + position, tokenIndex = position149, tokenIndex149 + if buffer[position] != rune('.') { + goto l277 + } + position++ + { + position278, tokenIndex278 := position, tokenIndex + if buffer[position] != rune('u') { + goto l279 + } + position++ + goto l278 + l279: + position, tokenIndex = position278, tokenIndex278 + if buffer[position] != rune('U') { + goto l277 + } + position++ + } + l278: + { + position280, tokenIndex280 := position, tokenIndex + if buffer[position] != rune('l') { + goto l281 + } + position++ + goto l280 + l281: + position, tokenIndex = position280, tokenIndex280 + if buffer[position] != rune('L') { + goto l277 + } + position++ + } + l280: + { + position282, tokenIndex282 := position, tokenIndex + if buffer[position] != rune('e') { + goto l283 + } + position++ + goto l282 + l283: + position, tokenIndex = position282, tokenIndex282 + if buffer[position] != rune('E') { + goto l277 + } + position++ + } + l282: + { + position284, tokenIndex284 := position, tokenIndex + if buffer[position] != rune('b') { + goto l285 + } + position++ + goto l284 + l285: + position, tokenIndex = position284, tokenIndex284 + if buffer[position] != rune('B') { + goto l277 + } + position++ + } + l284: if buffer[position] != rune('1') { - goto l266 + goto l277 } position++ if buffer[position] != rune('2') { - goto l266 + goto l277 } position++ if buffer[position] != rune('8') { - goto l266 + goto l277 } position++ goto l149 - l266: + l277: position, tokenIndex = position149, tokenIndex149 if buffer[position] != rune('.') { goto l147 } position++ { - position275, tokenIndex275 := position, tokenIndex + position286, tokenIndex286 := position, tokenIndex if buffer[position] != rune('s') { - goto l276 + goto l287 } position++ - goto l275 - l276: - position, tokenIndex = position275, tokenIndex275 + goto l286 + l287: + position, tokenIndex = position286, tokenIndex286 if buffer[position] != rune('S') { goto l147 } position++ } - l275: + l286: { - position277, tokenIndex277 := position, tokenIndex + position288, tokenIndex288 := position, tokenIndex if buffer[position] != rune('l') { - goto l278 + goto l289 } position++ - goto l277 - l278: - position, tokenIndex = position277, tokenIndex277 + goto l288 + l289: + position, tokenIndex = position288, tokenIndex288 if buffer[position] != rune('L') { goto l147 } position++ } - l277: + l288: { - position279, tokenIndex279 := position, tokenIndex + position290, tokenIndex290 := position, tokenIndex if buffer[position] != rune('e') { - goto l280 + goto l291 } position++ - goto l279 - l280: - position, tokenIndex = position279, tokenIndex279 + goto l290 + l291: + position, tokenIndex = position290, tokenIndex290 if buffer[position] != rune('E') { goto l147 } position++ } - l279: + l290: { - position281, tokenIndex281 := position, tokenIndex + position292, tokenIndex292 := position, tokenIndex if buffer[position] != rune('b') { - goto l282 + goto l293 } position++ - goto l281 - l282: - position, tokenIndex = position281, tokenIndex281 + goto l292 + l293: + position, tokenIndex = position292, tokenIndex292 if buffer[position] != rune('B') { goto l147 } position++ } - l281: + l292: if buffer[position] != rune('1') { goto l147 } @@ -2466,317 +2558,277 @@ func (p *Asm) Init(options ...func(*Asm) error) error { }, /* 14 SymbolArgs <- <(SymbolArg (WS? ',' WS? SymbolArg)*)> */ func() bool { - position283, tokenIndex283 := position, tokenIndex + position294, tokenIndex294 := position, tokenIndex { - position284 := position + position295 := position if !_rules[ruleSymbolArg]() { - goto l283 + goto l294 } - l285: + l296: { - position286, tokenIndex286 := position, tokenIndex + position297, tokenIndex297 := position, tokenIndex { - position287, tokenIndex287 := position, tokenIndex + position298, tokenIndex298 := position, tokenIndex if !_rules[ruleWS]() { - goto l287 + goto l298 } - goto l288 - l287: - position, tokenIndex = position287, tokenIndex287 + goto l299 + l298: + position, tokenIndex = position298, tokenIndex298 } - l288: + l299: if buffer[position] != rune(',') { - goto l286 + goto l297 } position++ { - position289, tokenIndex289 := position, tokenIndex + position300, tokenIndex300 := position, tokenIndex if !_rules[ruleWS]() { - goto l289 + goto l300 } - goto l290 - l289: - position, tokenIndex = position289, tokenIndex289 + goto l301 + l300: + position, tokenIndex = position300, tokenIndex300 } - l290: + l301: if !_rules[ruleSymbolArg]() { - goto l286 + goto l297 } - goto l285 - l286: - position, tokenIndex = position286, tokenIndex286 + goto l296 + l297: + position, tokenIndex = position297, tokenIndex297 } - add(ruleSymbolArgs, position284) + add(ruleSymbolArgs, position295) } return true - l283: - position, tokenIndex = position283, tokenIndex283 + l294: + position, tokenIndex = position294, tokenIndex294 return false }, - /* 15 SymbolShift <- <((('<' '<') / ('>' '>')) WS? [0-9]+)> */ + /* 15 SymbolArg <- */ func() bool { - position291, tokenIndex291 := position, tokenIndex + position302, tokenIndex302 := position, tokenIndex { - position292 := position - { - position293, tokenIndex293 := position, tokenIndex - if buffer[position] != rune('<') { - goto l294 - } - position++ - if buffer[position] != rune('<') { - goto l294 - } - position++ - goto l293 - l294: - position, tokenIndex = position293, tokenIndex293 - if buffer[position] != rune('>') { - goto l291 - } - position++ - if buffer[position] != rune('>') { - goto l291 - } - position++ - } - l293: - { - position295, tokenIndex295 := position, tokenIndex - if !_rules[ruleWS]() { - goto l295 - } - goto l296 - l295: - position, tokenIndex = position295, tokenIndex295 - } - l296: - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l291 - } - position++ - l297: - { - position298, tokenIndex298 := position, tokenIndex - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l298 - } - position++ - goto l297 - l298: - position, tokenIndex = position298, tokenIndex298 + position303 := position + if !_rules[ruleSymbolExpr]() { + goto l302 } - add(ruleSymbolShift, position292) + add(ruleSymbolArg, position303) } return true - l291: - position, tokenIndex = position291, tokenIndex291 + l302: + position, tokenIndex = position302, tokenIndex302 return false }, - /* 16 SymbolArg <- <((OpenParen WS?)? (Offset / SymbolType / ((Offset / LocalSymbol / SymbolName / Dot) (WS? Operator WS? (Offset / LocalSymbol / SymbolName))*) / (LocalSymbol TCMarker?) / (SymbolName Offset) / (SymbolName TCMarker?)) (WS? CloseParen)? (WS? SymbolShift)?)> */ + /* 16 SymbolExpr <- <(SymbolAtom (WS? SymbolOperator WS? SymbolExpr)?)> */ func() bool { - position299, tokenIndex299 := position, tokenIndex + position304, tokenIndex304 := position, tokenIndex { - position300 := position + position305 := position + if !_rules[ruleSymbolAtom]() { + goto l304 + } { - position301, tokenIndex301 := position, tokenIndex - if !_rules[ruleOpenParen]() { - goto l301 - } + position306, tokenIndex306 := position, tokenIndex { - position303, tokenIndex303 := position, tokenIndex + position308, tokenIndex308 := position, tokenIndex if !_rules[ruleWS]() { - goto l303 + goto l308 } - goto l304 - l303: - position, tokenIndex = position303, tokenIndex303 + goto l309 + l308: + position, tokenIndex = position308, tokenIndex308 } - l304: - goto l302 - l301: - position, tokenIndex = position301, tokenIndex301 - } - l302: - { - position305, tokenIndex305 := position, tokenIndex - if !_rules[ruleOffset]() { + l309: + if !_rules[ruleSymbolOperator]() { goto l306 } - goto l305 - l306: - position, tokenIndex = position305, tokenIndex305 - if !_rules[ruleSymbolType]() { - goto l307 - } - goto l305 - l307: - position, tokenIndex = position305, tokenIndex305 { - position309, tokenIndex309 := position, tokenIndex - if !_rules[ruleOffset]() { + position310, tokenIndex310 := position, tokenIndex + if !_rules[ruleWS]() { goto l310 } - goto l309 + goto l311 l310: - position, tokenIndex = position309, tokenIndex309 - if !_rules[ruleLocalSymbol]() { - goto l311 - } - goto l309 - l311: - position, tokenIndex = position309, tokenIndex309 - if !_rules[ruleSymbolName]() { - goto l312 - } - goto l309 - l312: - position, tokenIndex = position309, tokenIndex309 - if !_rules[ruleDot]() { - goto l308 - } + position, tokenIndex = position310, tokenIndex310 } - l309: - l313: - { - position314, tokenIndex314 := position, tokenIndex - { - position315, tokenIndex315 := position, tokenIndex - if !_rules[ruleWS]() { - goto l315 - } - goto l316 - l315: - position, tokenIndex = position315, tokenIndex315 - } - l316: - if !_rules[ruleOperator]() { - goto l314 - } - { - position317, tokenIndex317 := position, tokenIndex - if !_rules[ruleWS]() { - goto l317 - } - goto l318 - l317: - position, tokenIndex = position317, tokenIndex317 - } - l318: - { - position319, tokenIndex319 := position, tokenIndex - if !_rules[ruleOffset]() { - goto l320 - } - goto l319 - l320: - position, tokenIndex = position319, tokenIndex319 - if !_rules[ruleLocalSymbol]() { - goto l321 - } - goto l319 - l321: - position, tokenIndex = position319, tokenIndex319 - if !_rules[ruleSymbolName]() { - goto l314 - } - } - l319: - goto l313 - l314: - position, tokenIndex = position314, tokenIndex314 + l311: + if !_rules[ruleSymbolExpr]() { + goto l306 + } + goto l307 + l306: + position, tokenIndex = position306, tokenIndex306 + } + l307: + add(ruleSymbolExpr, position305) + } + return true + l304: + position, tokenIndex = position304, tokenIndex304 + return false + }, + /* 17 SymbolAtom <- <(Offset / SymbolType / (LocalSymbol TCMarker?) / (SymbolName Offset) / (SymbolName TCMarker?) / Dot / (OpenParen WS? SymbolExpr WS? CloseParen))> */ + func() bool { + position312, tokenIndex312 := position, tokenIndex + { + position313 := position + { + position314, tokenIndex314 := position, tokenIndex + if !_rules[ruleOffset]() { + goto l315 } - goto l305 - l308: - position, tokenIndex = position305, tokenIndex305 + goto l314 + l315: + position, tokenIndex = position314, tokenIndex314 + if !_rules[ruleSymbolType]() { + goto l316 + } + goto l314 + l316: + position, tokenIndex = position314, tokenIndex314 if !_rules[ruleLocalSymbol]() { - goto l322 + goto l317 } { - position323, tokenIndex323 := position, tokenIndex + position318, tokenIndex318 := position, tokenIndex if !_rules[ruleTCMarker]() { - goto l323 + goto l318 } - goto l324 - l323: - position, tokenIndex = position323, tokenIndex323 + goto l319 + l318: + position, tokenIndex = position318, tokenIndex318 } - l324: - goto l305 - l322: - position, tokenIndex = position305, tokenIndex305 + l319: + goto l314 + l317: + position, tokenIndex = position314, tokenIndex314 if !_rules[ruleSymbolName]() { - goto l325 + goto l320 } if !_rules[ruleOffset]() { - goto l325 + goto l320 } - goto l305 - l325: - position, tokenIndex = position305, tokenIndex305 + goto l314 + l320: + position, tokenIndex = position314, tokenIndex314 if !_rules[ruleSymbolName]() { - goto l299 + goto l321 } { - position326, tokenIndex326 := position, tokenIndex + position322, tokenIndex322 := position, tokenIndex if !_rules[ruleTCMarker]() { - goto l326 + goto l322 } - goto l327 - l326: - position, tokenIndex = position326, tokenIndex326 + goto l323 + l322: + position, tokenIndex = position322, tokenIndex322 + } + l323: + goto l314 + l321: + position, tokenIndex = position314, tokenIndex314 + if !_rules[ruleDot]() { + goto l324 + } + goto l314 + l324: + position, tokenIndex = position314, tokenIndex314 + if !_rules[ruleOpenParen]() { + goto l312 } - l327: - } - l305: - { - position328, tokenIndex328 := position, tokenIndex { - position330, tokenIndex330 := position, tokenIndex + position325, tokenIndex325 := position, tokenIndex if !_rules[ruleWS]() { - goto l330 + goto l325 } - goto l331 - l330: - position, tokenIndex = position330, tokenIndex330 + goto l326 + l325: + position, tokenIndex = position325, tokenIndex325 } - l331: - if !_rules[ruleCloseParen]() { + l326: + if !_rules[ruleSymbolExpr]() { + goto l312 + } + { + position327, tokenIndex327 := position, tokenIndex + if !_rules[ruleWS]() { + goto l327 + } goto l328 + l327: + position, tokenIndex = position327, tokenIndex327 } - goto l329 l328: - position, tokenIndex = position328, tokenIndex328 + if !_rules[ruleCloseParen]() { + goto l312 + } } - l329: + l314: + add(ruleSymbolAtom, position313) + } + return true + l312: + position, tokenIndex = position312, tokenIndex312 + return false + }, + /* 18 SymbolOperator <- <('+' / '-' / '|' / ('<' '<') / ('>' '>'))> */ + func() bool { + position329, tokenIndex329 := position, tokenIndex + { + position330 := position { - position332, tokenIndex332 := position, tokenIndex - { - position334, tokenIndex334 := position, tokenIndex - if !_rules[ruleWS]() { - goto l334 - } + position331, tokenIndex331 := position, tokenIndex + if buffer[position] != rune('+') { + goto l332 + } + position++ + goto l331 + l332: + position, tokenIndex = position331, tokenIndex331 + if buffer[position] != rune('-') { + goto l333 + } + position++ + goto l331 + l333: + position, tokenIndex = position331, tokenIndex331 + if buffer[position] != rune('|') { + goto l334 + } + position++ + goto l331 + l334: + position, tokenIndex = position331, tokenIndex331 + if buffer[position] != rune('<') { + goto l335 + } + position++ + if buffer[position] != rune('<') { goto l335 - l334: - position, tokenIndex = position334, tokenIndex334 } + position++ + goto l331 l335: - if !_rules[ruleSymbolShift]() { - goto l332 + position, tokenIndex = position331, tokenIndex331 + if buffer[position] != rune('>') { + goto l329 } - goto l333 - l332: - position, tokenIndex = position332, tokenIndex332 + position++ + if buffer[position] != rune('>') { + goto l329 + } + position++ } - l333: - add(ruleSymbolArg, position300) + l331: + add(ruleSymbolOperator, position330) } return true - l299: - position, tokenIndex = position299, tokenIndex299 + l329: + position, tokenIndex = position329, tokenIndex329 return false }, - /* 17 OpenParen <- <'('> */ + /* 19 OpenParen <- <'('> */ func() bool { position336, tokenIndex336 := position, tokenIndex { @@ -2792,7 +2844,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position336, tokenIndex336 return false }, - /* 18 CloseParen <- <')'> */ + /* 20 CloseParen <- <')'> */ func() bool { position338, tokenIndex338 := position, tokenIndex { @@ -2808,7 +2860,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position338, tokenIndex338 return false }, - /* 19 SymbolType <- <(('@' / '%') (('f' 'u' 'n' 'c' 't' 'i' 'o' 'n') / ('o' 'b' 'j' 'e' 'c' 't')))> */ + /* 21 SymbolType <- <(('@' / '%') (('f' 'u' 'n' 'c' 't' 'i' 'o' 'n') / ('o' 'b' 'j' 'e' 'c' 't')))> */ func() bool { position340, tokenIndex340 := position, tokenIndex { @@ -2898,7 +2950,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position340, tokenIndex340 return false }, - /* 20 Dot <- <'.'> */ + /* 22 Dot <- <'.'> */ func() bool { position346, tokenIndex346 := position, tokenIndex { @@ -2914,7 +2966,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position346, tokenIndex346 return false }, - /* 21 TCMarker <- <('[' 'T' 'C' ']')> */ + /* 23 TCMarker <- <('[' 'T' 'C' ']')> */ func() bool { position348, tokenIndex348 := position, tokenIndex { @@ -2942,7 +2994,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position348, tokenIndex348 return false }, - /* 22 EscapedChar <- <('\\' .)> */ + /* 24 EscapedChar <- <('\\' .)> */ func() bool { position350, tokenIndex350 := position, tokenIndex { @@ -2961,7 +3013,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position350, tokenIndex350 return false }, - /* 23 WS <- <(' ' / '\t')+> */ + /* 25 WS <- <(' ' / '\t')+> */ func() bool { position352, tokenIndex352 := position, tokenIndex { @@ -3010,7 +3062,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position352, tokenIndex352 return false }, - /* 24 Comment <- <((('/' '/') / '#') (!'\n' .)*)> */ + /* 26 Comment <- <((('/' '/') / '#') (!'\n' .)*)> */ func() bool { position360, tokenIndex360 := position, tokenIndex { @@ -3061,7 +3113,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position360, tokenIndex360 return false }, - /* 25 Label <- <((LocalSymbol / LocalLabel / SymbolName) ':')> */ + /* 27 Label <- <((LocalSymbol / LocalLabel / SymbolName) ':')> */ func() bool { position367, tokenIndex367 := position, tokenIndex { @@ -3096,7 +3148,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position367, tokenIndex367 return false }, - /* 26 SymbolName <- <(([a-z] / [A-Z] / '.' / '_') ([a-z] / [A-Z] / '.' / ([0-9] / [0-9]) / '$' / '_')*)> */ + /* 28 SymbolName <- <(([a-z] / [A-Z] / '.' / '_') ([a-z] / [A-Z] / '.' / ([0-9] / [0-9]) / '$' / '_')*)> */ func() bool { position372, tokenIndex372 := position, tokenIndex { @@ -3198,7 +3250,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position372, tokenIndex372 return false }, - /* 27 LocalSymbol <- <('.' 'L' ([a-z] / [A-Z] / ([a-z] / [A-Z]) / '.' / ([0-9] / [0-9]) / '$' / '_')+)> */ + /* 29 LocalSymbol <- <('.' 'L' ([a-z] / [A-Z] / ([a-z] / [A-Z]) / '.' / ([0-9] / [0-9]) / '$' / '_')+)> */ func() bool { position388, tokenIndex388 := position, tokenIndex { @@ -3369,7 +3421,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position388, tokenIndex388 return false }, - /* 28 LocalLabel <- <([0-9] ([0-9] / '$')*)> */ + /* 30 LocalLabel <- <([0-9] ([0-9] / '$')*)> */ func() bool { position414, tokenIndex414 := position, tokenIndex { @@ -3407,7 +3459,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position414, tokenIndex414 return false }, - /* 29 LocalLabelRef <- <([0-9] ([0-9] / '$')* ('b' / 'f'))> */ + /* 31 LocalLabelRef <- <([0-9] ([0-9] / '$')* ('b' / 'f'))> */ func() bool { position420, tokenIndex420 := position, tokenIndex { @@ -3460,7 +3512,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position420, tokenIndex420 return false }, - /* 30 Instruction <- <(InstructionName (WS InstructionArg (WS? ',' WS? InstructionArg)*)?)> */ + /* 32 Instruction <- <(InstructionName (WS InstructionArg (WS? ',' WS? InstructionArg)*)?)> */ func() bool { position428, tokenIndex428 := position, tokenIndex { @@ -3522,7 +3574,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position428, tokenIndex428 return false }, - /* 31 InstructionName <- <(([a-z] / [A-Z]) ([a-z] / [A-Z] / '.' / ([0-9] / [0-9]))* ('.' / '+' / '-')?)> */ + /* 33 InstructionName <- <(([a-z] / [A-Z]) ([a-z] / [A-Z] / '.' / ([0-9] / [0-9]))* ('.' / '+' / '-')?)> */ func() bool { position438, tokenIndex438 := position, tokenIndex { @@ -3625,7 +3677,7 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position438, tokenIndex438 return false }, - /* 32 InstructionArg <- <(IndirectionIndicator? (ARMConstantTweak / RegisterOrConstant / LocalLabelRef / TOCRefHigh / TOCRefLow / GOTLocation / GOTSymbolOffset / MemoryRef) AVX512Token*)> */ + /* 34 InstructionArg <- <(IndirectionIndicator? (ARMConstantTweak / RegisterOrConstant / LocalLabelRef / TOCRefHigh / TOCRefLow / GOTLocation / GOTAddress / GOTSymbolOffset / MemoryRef) AVX512Token*)> */ func() bool { position455, tokenIndex455 := position, tokenIndex { @@ -3678,26 +3730,32 @@ func (p *Asm) Init(options ...func(*Asm) error) error { goto l459 l465: position, tokenIndex = position459, tokenIndex459 - if !_rules[ruleGOTSymbolOffset]() { + if !_rules[ruleGOTAddress]() { goto l466 } goto l459 l466: + position, tokenIndex = position459, tokenIndex459 + if !_rules[ruleGOTSymbolOffset]() { + goto l467 + } + goto l459 + l467: position, tokenIndex = position459, tokenIndex459 if !_rules[ruleMemoryRef]() { goto l455 } } l459: - l467: + l468: { - position468, tokenIndex468 := position, tokenIndex + position469, tokenIndex469 := position, tokenIndex if !_rules[ruleAVX512Token]() { - goto l468 + goto l469 } - goto l467 - l468: - position, tokenIndex = position468, tokenIndex468 + goto l468 + l469: + position, tokenIndex = position469, tokenIndex469 } add(ruleInstructionArg, position456) } @@ -3706,2717 +3764,3703 @@ func (p *Asm) Init(options ...func(*Asm) error) error { position, tokenIndex = position455, tokenIndex455 return false }, - /* 33 GOTLocation <- <('$' '_' 'G' 'L' 'O' 'B' 'A' 'L' '_' 'O' 'F' 'F' 'S' 'E' 'T' '_' 'T' 'A' 'B' 'L' 'E' '_' '-' LocalSymbol)> */ + /* 35 GOTLocation <- <('$' '_' 'G' 'L' 'O' 'B' 'A' 'L' '_' 'O' 'F' 'F' 'S' 'E' 'T' '_' 'T' 'A' 'B' 'L' 'E' '_' '-' LocalSymbol)> */ func() bool { - position469, tokenIndex469 := position, tokenIndex + position470, tokenIndex470 := position, tokenIndex { - position470 := position + position471 := position if buffer[position] != rune('$') { - goto l469 + goto l470 } position++ if buffer[position] != rune('_') { - goto l469 + goto l470 } position++ if buffer[position] != rune('G') { - goto l469 + goto l470 } position++ if buffer[position] != rune('L') { - goto l469 + goto l470 } position++ if buffer[position] != rune('O') { - goto l469 + goto l470 } position++ if buffer[position] != rune('B') { - goto l469 + goto l470 } position++ if buffer[position] != rune('A') { - goto l469 + goto l470 } position++ if buffer[position] != rune('L') { - goto l469 + goto l470 } position++ if buffer[position] != rune('_') { - goto l469 + goto l470 } position++ if buffer[position] != rune('O') { - goto l469 + goto l470 } position++ if buffer[position] != rune('F') { - goto l469 + goto l470 } position++ if buffer[position] != rune('F') { - goto l469 + goto l470 } position++ if buffer[position] != rune('S') { - goto l469 + goto l470 } position++ if buffer[position] != rune('E') { - goto l469 + goto l470 } position++ if buffer[position] != rune('T') { - goto l469 + goto l470 } position++ if buffer[position] != rune('_') { - goto l469 + goto l470 } position++ if buffer[position] != rune('T') { - goto l469 + goto l470 } position++ if buffer[position] != rune('A') { - goto l469 + goto l470 } position++ if buffer[position] != rune('B') { - goto l469 + goto l470 } position++ if buffer[position] != rune('L') { - goto l469 + goto l470 } position++ if buffer[position] != rune('E') { - goto l469 + goto l470 } position++ if buffer[position] != rune('_') { - goto l469 + goto l470 } position++ if buffer[position] != rune('-') { - goto l469 + goto l470 } position++ if !_rules[ruleLocalSymbol]() { - goto l469 + goto l470 + } + add(ruleGOTLocation, position471) + } + return true + l470: + position, tokenIndex = position470, tokenIndex470 + return false + }, + /* 36 GOTAddress <- <('_' 'G' 'L' 'O' 'B' 'A' 'L' '_' 'O' 'F' 'F' 'S' 'E' 'T' '_' 'T' 'A' 'B' 'L' 'E' '_' '(' '%' 'r' 'i' 'p' ')')> */ + func() bool { + position472, tokenIndex472 := position, tokenIndex + { + position473 := position + if buffer[position] != rune('_') { + goto l472 + } + position++ + if buffer[position] != rune('G') { + goto l472 + } + position++ + if buffer[position] != rune('L') { + goto l472 + } + position++ + if buffer[position] != rune('O') { + goto l472 + } + position++ + if buffer[position] != rune('B') { + goto l472 + } + position++ + if buffer[position] != rune('A') { + goto l472 + } + position++ + if buffer[position] != rune('L') { + goto l472 + } + position++ + if buffer[position] != rune('_') { + goto l472 + } + position++ + if buffer[position] != rune('O') { + goto l472 + } + position++ + if buffer[position] != rune('F') { + goto l472 + } + position++ + if buffer[position] != rune('F') { + goto l472 + } + position++ + if buffer[position] != rune('S') { + goto l472 + } + position++ + if buffer[position] != rune('E') { + goto l472 + } + position++ + if buffer[position] != rune('T') { + goto l472 + } + position++ + if buffer[position] != rune('_') { + goto l472 + } + position++ + if buffer[position] != rune('T') { + goto l472 + } + position++ + if buffer[position] != rune('A') { + goto l472 + } + position++ + if buffer[position] != rune('B') { + goto l472 + } + position++ + if buffer[position] != rune('L') { + goto l472 + } + position++ + if buffer[position] != rune('E') { + goto l472 + } + position++ + if buffer[position] != rune('_') { + goto l472 + } + position++ + if buffer[position] != rune('(') { + goto l472 + } + position++ + if buffer[position] != rune('%') { + goto l472 + } + position++ + if buffer[position] != rune('r') { + goto l472 + } + position++ + if buffer[position] != rune('i') { + goto l472 } - add(ruleGOTLocation, position470) + position++ + if buffer[position] != rune('p') { + goto l472 + } + position++ + if buffer[position] != rune(')') { + goto l472 + } + position++ + add(ruleGOTAddress, position473) } return true - l469: - position, tokenIndex = position469, tokenIndex469 + l472: + position, tokenIndex = position472, tokenIndex472 return false }, - /* 34 GOTSymbolOffset <- <(('$' SymbolName ('@' 'G' 'O' 'T') ('O' 'F' 'F')?) / (':' ('g' / 'G') ('o' / 'O') ('t' / 'T') ':' SymbolName))> */ + /* 37 GOTSymbolOffset <- <(('$' SymbolName ('@' 'G' 'O' 'T') ('O' 'F' 'F')?) / (':' ('g' / 'G') ('o' / 'O') ('t' / 'T') ':' SymbolName))> */ func() bool { - position471, tokenIndex471 := position, tokenIndex + position474, tokenIndex474 := position, tokenIndex { - position472 := position + position475 := position { - position473, tokenIndex473 := position, tokenIndex + position476, tokenIndex476 := position, tokenIndex if buffer[position] != rune('$') { - goto l474 + goto l477 } position++ if !_rules[ruleSymbolName]() { - goto l474 + goto l477 } if buffer[position] != rune('@') { - goto l474 + goto l477 } position++ if buffer[position] != rune('G') { - goto l474 + goto l477 } position++ if buffer[position] != rune('O') { - goto l474 + goto l477 } position++ if buffer[position] != rune('T') { - goto l474 + goto l477 } position++ { - position475, tokenIndex475 := position, tokenIndex + position478, tokenIndex478 := position, tokenIndex if buffer[position] != rune('O') { - goto l475 + goto l478 } position++ if buffer[position] != rune('F') { - goto l475 + goto l478 } position++ if buffer[position] != rune('F') { - goto l475 + goto l478 } position++ - goto l476 - l475: - position, tokenIndex = position475, tokenIndex475 + goto l479 + l478: + position, tokenIndex = position478, tokenIndex478 } - l476: - goto l473 - l474: - position, tokenIndex = position473, tokenIndex473 + l479: + goto l476 + l477: + position, tokenIndex = position476, tokenIndex476 if buffer[position] != rune(':') { - goto l471 + goto l474 } position++ { - position477, tokenIndex477 := position, tokenIndex + position480, tokenIndex480 := position, tokenIndex if buffer[position] != rune('g') { - goto l478 + goto l481 } position++ - goto l477 - l478: - position, tokenIndex = position477, tokenIndex477 + goto l480 + l481: + position, tokenIndex = position480, tokenIndex480 if buffer[position] != rune('G') { - goto l471 + goto l474 } position++ } - l477: + l480: { - position479, tokenIndex479 := position, tokenIndex + position482, tokenIndex482 := position, tokenIndex if buffer[position] != rune('o') { - goto l480 + goto l483 } position++ - goto l479 - l480: - position, tokenIndex = position479, tokenIndex479 + goto l482 + l483: + position, tokenIndex = position482, tokenIndex482 if buffer[position] != rune('O') { - goto l471 + goto l474 } position++ } - l479: + l482: { - position481, tokenIndex481 := position, tokenIndex + position484, tokenIndex484 := position, tokenIndex if buffer[position] != rune('t') { - goto l482 + goto l485 } position++ - goto l481 - l482: - position, tokenIndex = position481, tokenIndex481 + goto l484 + l485: + position, tokenIndex = position484, tokenIndex484 if buffer[position] != rune('T') { - goto l471 + goto l474 } position++ } - l481: + l484: if buffer[position] != rune(':') { - goto l471 + goto l474 } position++ if !_rules[ruleSymbolName]() { - goto l471 + goto l474 } } - l473: - add(ruleGOTSymbolOffset, position472) + l476: + add(ruleGOTSymbolOffset, position475) } return true - l471: - position, tokenIndex = position471, tokenIndex471 + l474: + position, tokenIndex = position474, tokenIndex474 return false }, - /* 35 AVX512Token <- <(WS? '{' '%'? ([0-9] / [a-z])* '}')> */ + /* 38 AVX512Token <- <(WS? '{' '%'? ([0-9] / [a-z])* '}')> */ func() bool { - position483, tokenIndex483 := position, tokenIndex + position486, tokenIndex486 := position, tokenIndex { - position484 := position + position487 := position { - position485, tokenIndex485 := position, tokenIndex + position488, tokenIndex488 := position, tokenIndex if !_rules[ruleWS]() { - goto l485 + goto l488 } - goto l486 - l485: - position, tokenIndex = position485, tokenIndex485 + goto l489 + l488: + position, tokenIndex = position488, tokenIndex488 } - l486: + l489: if buffer[position] != rune('{') { - goto l483 + goto l486 } position++ { - position487, tokenIndex487 := position, tokenIndex + position490, tokenIndex490 := position, tokenIndex if buffer[position] != rune('%') { - goto l487 + goto l490 } position++ - goto l488 - l487: - position, tokenIndex = position487, tokenIndex487 + goto l491 + l490: + position, tokenIndex = position490, tokenIndex490 } - l488: - l489: + l491: + l492: { - position490, tokenIndex490 := position, tokenIndex + position493, tokenIndex493 := position, tokenIndex { - position491, tokenIndex491 := position, tokenIndex + position494, tokenIndex494 := position, tokenIndex if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l492 + goto l495 } position++ - goto l491 - l492: - position, tokenIndex = position491, tokenIndex491 + goto l494 + l495: + position, tokenIndex = position494, tokenIndex494 if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l490 + goto l493 } position++ } - l491: - goto l489 - l490: - position, tokenIndex = position490, tokenIndex490 + l494: + goto l492 + l493: + position, tokenIndex = position493, tokenIndex493 } if buffer[position] != rune('}') { - goto l483 + goto l486 } position++ - add(ruleAVX512Token, position484) + add(ruleAVX512Token, position487) } return true - l483: - position, tokenIndex = position483, tokenIndex483 + l486: + position, tokenIndex = position486, tokenIndex486 return false }, - /* 36 TOCRefHigh <- <('.' 'T' 'O' 'C' '.' '-' (('0' 'b') / ('.' 'L' ([a-z] / [A-Z] / '_' / [0-9])+)) ('@' ('h' / 'H') ('a' / 'A')))> */ + /* 39 TOCRefHigh <- <('.' 'T' 'O' 'C' '.' '-' (('0' 'b') / ('.' 'L' ([a-z] / [A-Z] / '_' / [0-9])+)) ('@' ('h' / 'H') ('a' / 'A')))> */ func() bool { - position493, tokenIndex493 := position, tokenIndex + position496, tokenIndex496 := position, tokenIndex { - position494 := position + position497 := position if buffer[position] != rune('.') { - goto l493 + goto l496 } position++ if buffer[position] != rune('T') { - goto l493 + goto l496 } position++ if buffer[position] != rune('O') { - goto l493 + goto l496 } position++ if buffer[position] != rune('C') { - goto l493 + goto l496 } position++ if buffer[position] != rune('.') { - goto l493 + goto l496 } position++ if buffer[position] != rune('-') { - goto l493 + goto l496 } position++ { - position495, tokenIndex495 := position, tokenIndex + position498, tokenIndex498 := position, tokenIndex if buffer[position] != rune('0') { - goto l496 + goto l499 } position++ if buffer[position] != rune('b') { - goto l496 + goto l499 } position++ - goto l495 - l496: - position, tokenIndex = position495, tokenIndex495 + goto l498 + l499: + position, tokenIndex = position498, tokenIndex498 if buffer[position] != rune('.') { - goto l493 + goto l496 } position++ if buffer[position] != rune('L') { - goto l493 + goto l496 } position++ { - position499, tokenIndex499 := position, tokenIndex + position502, tokenIndex502 := position, tokenIndex if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l500 + goto l503 } position++ - goto l499 - l500: - position, tokenIndex = position499, tokenIndex499 + goto l502 + l503: + position, tokenIndex = position502, tokenIndex502 if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l501 + goto l504 } position++ - goto l499 - l501: - position, tokenIndex = position499, tokenIndex499 + goto l502 + l504: + position, tokenIndex = position502, tokenIndex502 if buffer[position] != rune('_') { - goto l502 + goto l505 } position++ - goto l499 - l502: - position, tokenIndex = position499, tokenIndex499 + goto l502 + l505: + position, tokenIndex = position502, tokenIndex502 if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l493 + goto l496 } position++ } - l499: - l497: + l502: + l500: { - position498, tokenIndex498 := position, tokenIndex + position501, tokenIndex501 := position, tokenIndex { - position503, tokenIndex503 := position, tokenIndex + position506, tokenIndex506 := position, tokenIndex if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l504 + goto l507 } position++ - goto l503 - l504: - position, tokenIndex = position503, tokenIndex503 + goto l506 + l507: + position, tokenIndex = position506, tokenIndex506 if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l505 + goto l508 } position++ - goto l503 - l505: - position, tokenIndex = position503, tokenIndex503 + goto l506 + l508: + position, tokenIndex = position506, tokenIndex506 if buffer[position] != rune('_') { - goto l506 + goto l509 } position++ - goto l503 - l506: - position, tokenIndex = position503, tokenIndex503 + goto l506 + l509: + position, tokenIndex = position506, tokenIndex506 if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l498 + goto l501 } position++ } - l503: - goto l497 - l498: - position, tokenIndex = position498, tokenIndex498 + l506: + goto l500 + l501: + position, tokenIndex = position501, tokenIndex501 } } - l495: + l498: if buffer[position] != rune('@') { - goto l493 + goto l496 } position++ { - position507, tokenIndex507 := position, tokenIndex + position510, tokenIndex510 := position, tokenIndex if buffer[position] != rune('h') { - goto l508 + goto l511 } position++ - goto l507 - l508: - position, tokenIndex = position507, tokenIndex507 + goto l510 + l511: + position, tokenIndex = position510, tokenIndex510 if buffer[position] != rune('H') { - goto l493 + goto l496 } position++ } - l507: + l510: { - position509, tokenIndex509 := position, tokenIndex + position512, tokenIndex512 := position, tokenIndex if buffer[position] != rune('a') { - goto l510 + goto l513 } position++ - goto l509 - l510: - position, tokenIndex = position509, tokenIndex509 + goto l512 + l513: + position, tokenIndex = position512, tokenIndex512 if buffer[position] != rune('A') { - goto l493 + goto l496 } position++ } - l509: - add(ruleTOCRefHigh, position494) + l512: + add(ruleTOCRefHigh, position497) } return true - l493: - position, tokenIndex = position493, tokenIndex493 + l496: + position, tokenIndex = position496, tokenIndex496 return false }, - /* 37 TOCRefLow <- <('.' 'T' 'O' 'C' '.' '-' (('0' 'b') / ('.' 'L' ([a-z] / [A-Z] / '_' / [0-9])+)) ('@' ('l' / 'L')))> */ + /* 40 TOCRefLow <- <('.' 'T' 'O' 'C' '.' '-' (('0' 'b') / ('.' 'L' ([a-z] / [A-Z] / '_' / [0-9])+)) ('@' ('l' / 'L')))> */ func() bool { - position511, tokenIndex511 := position, tokenIndex + position514, tokenIndex514 := position, tokenIndex { - position512 := position + position515 := position if buffer[position] != rune('.') { - goto l511 + goto l514 } position++ if buffer[position] != rune('T') { - goto l511 + goto l514 } position++ if buffer[position] != rune('O') { - goto l511 + goto l514 } position++ if buffer[position] != rune('C') { - goto l511 + goto l514 } position++ if buffer[position] != rune('.') { - goto l511 + goto l514 } position++ if buffer[position] != rune('-') { - goto l511 + goto l514 } position++ { - position513, tokenIndex513 := position, tokenIndex + position516, tokenIndex516 := position, tokenIndex if buffer[position] != rune('0') { - goto l514 + goto l517 } position++ if buffer[position] != rune('b') { - goto l514 + goto l517 } position++ - goto l513 - l514: - position, tokenIndex = position513, tokenIndex513 + goto l516 + l517: + position, tokenIndex = position516, tokenIndex516 if buffer[position] != rune('.') { - goto l511 + goto l514 } position++ if buffer[position] != rune('L') { - goto l511 + goto l514 } position++ { - position517, tokenIndex517 := position, tokenIndex + position520, tokenIndex520 := position, tokenIndex if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l518 + goto l521 } position++ - goto l517 - l518: - position, tokenIndex = position517, tokenIndex517 + goto l520 + l521: + position, tokenIndex = position520, tokenIndex520 if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l519 + goto l522 } position++ - goto l517 - l519: - position, tokenIndex = position517, tokenIndex517 + goto l520 + l522: + position, tokenIndex = position520, tokenIndex520 if buffer[position] != rune('_') { - goto l520 + goto l523 } position++ - goto l517 - l520: - position, tokenIndex = position517, tokenIndex517 + goto l520 + l523: + position, tokenIndex = position520, tokenIndex520 if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l511 + goto l514 } position++ } - l517: - l515: + l520: + l518: { - position516, tokenIndex516 := position, tokenIndex + position519, tokenIndex519 := position, tokenIndex { - position521, tokenIndex521 := position, tokenIndex + position524, tokenIndex524 := position, tokenIndex if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l522 + goto l525 } position++ - goto l521 - l522: - position, tokenIndex = position521, tokenIndex521 + goto l524 + l525: + position, tokenIndex = position524, tokenIndex524 if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l523 + goto l526 } position++ - goto l521 - l523: - position, tokenIndex = position521, tokenIndex521 + goto l524 + l526: + position, tokenIndex = position524, tokenIndex524 if buffer[position] != rune('_') { - goto l524 + goto l527 } position++ - goto l521 - l524: - position, tokenIndex = position521, tokenIndex521 + goto l524 + l527: + position, tokenIndex = position524, tokenIndex524 if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l516 + goto l519 } position++ } - l521: - goto l515 - l516: - position, tokenIndex = position516, tokenIndex516 + l524: + goto l518 + l519: + position, tokenIndex = position519, tokenIndex519 } } - l513: + l516: if buffer[position] != rune('@') { - goto l511 + goto l514 } position++ { - position525, tokenIndex525 := position, tokenIndex + position528, tokenIndex528 := position, tokenIndex if buffer[position] != rune('l') { - goto l526 + goto l529 } position++ - goto l525 - l526: - position, tokenIndex = position525, tokenIndex525 + goto l528 + l529: + position, tokenIndex = position528, tokenIndex528 if buffer[position] != rune('L') { - goto l511 + goto l514 } position++ } - l525: - add(ruleTOCRefLow, position512) + l528: + add(ruleTOCRefLow, position515) } return true - l511: - position, tokenIndex = position511, tokenIndex511 + l514: + position, tokenIndex = position514, tokenIndex514 return false }, - /* 38 IndirectionIndicator <- <'*'> */ + /* 41 IndirectionIndicator <- <'*'> */ func() bool { - position527, tokenIndex527 := position, tokenIndex + position530, tokenIndex530 := position, tokenIndex { - position528 := position + position531 := position if buffer[position] != rune('*') { - goto l527 + goto l530 } position++ - add(ruleIndirectionIndicator, position528) + add(ruleIndirectionIndicator, position531) } return true - l527: - position, tokenIndex = position527, tokenIndex527 + l530: + position, tokenIndex = position530, tokenIndex530 return false }, - /* 39 RegisterOrConstant <- <((('%' ([a-z] / [A-Z]) ([a-z] / [A-Z] / ([0-9] / [0-9]))*) / ('$'? ((Offset Offset) / Offset)) / ('#' Offset ('*' [0-9]+ ('-' [0-9] [0-9]*)?)?) / ('#' '~'? '(' [0-9] WS? ('<' '<') WS? [0-9] ')') / ARMRegister) !('f' / 'b' / ':' / '(' / '+' / '-'))> */ + /* 42 RegisterOrConstant <- <((('%' ([a-z] / [A-Z]) ([a-z] / [A-Z] / ([0-9] / [0-9]))*) / ('$'? ((Offset Offset) / Offset)) / ('#' Offset ('*' [0-9]+ ('-' [0-9] [0-9]*)?)?) / ('#' '~'? '(' [0-9] WS? ('<' '<') WS? [0-9] ')') / ARMRegister) !('f' / 'b' / ':' / '(' / '+' / '-'))> */ func() bool { - position529, tokenIndex529 := position, tokenIndex + position532, tokenIndex532 := position, tokenIndex { - position530 := position + position533 := position { - position531, tokenIndex531 := position, tokenIndex + position534, tokenIndex534 := position, tokenIndex if buffer[position] != rune('%') { - goto l532 + goto l535 } position++ { - position533, tokenIndex533 := position, tokenIndex + position536, tokenIndex536 := position, tokenIndex if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l534 + goto l537 } position++ - goto l533 - l534: - position, tokenIndex = position533, tokenIndex533 + goto l536 + l537: + position, tokenIndex = position536, tokenIndex536 if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l532 + goto l535 } position++ } - l533: - l535: + l536: + l538: { - position536, tokenIndex536 := position, tokenIndex + position539, tokenIndex539 := position, tokenIndex { - position537, tokenIndex537 := position, tokenIndex + position540, tokenIndex540 := position, tokenIndex if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l538 + goto l541 } position++ - goto l537 - l538: - position, tokenIndex = position537, tokenIndex537 + goto l540 + l541: + position, tokenIndex = position540, tokenIndex540 if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l539 + goto l542 } position++ - goto l537 - l539: - position, tokenIndex = position537, tokenIndex537 + goto l540 + l542: + position, tokenIndex = position540, tokenIndex540 { - position540, tokenIndex540 := position, tokenIndex + position543, tokenIndex543 := position, tokenIndex if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l541 + goto l544 } position++ - goto l540 - l541: - position, tokenIndex = position540, tokenIndex540 + goto l543 + l544: + position, tokenIndex = position543, tokenIndex543 if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l536 + goto l539 } position++ } - l540: + l543: } - l537: - goto l535 - l536: - position, tokenIndex = position536, tokenIndex536 + l540: + goto l538 + l539: + position, tokenIndex = position539, tokenIndex539 } - goto l531 - l532: - position, tokenIndex = position531, tokenIndex531 + goto l534 + l535: + position, tokenIndex = position534, tokenIndex534 { - position543, tokenIndex543 := position, tokenIndex + position546, tokenIndex546 := position, tokenIndex if buffer[position] != rune('$') { - goto l543 + goto l546 } position++ - goto l544 - l543: - position, tokenIndex = position543, tokenIndex543 + goto l547 + l546: + position, tokenIndex = position546, tokenIndex546 } - l544: + l547: { - position545, tokenIndex545 := position, tokenIndex + position548, tokenIndex548 := position, tokenIndex if !_rules[ruleOffset]() { - goto l546 + goto l549 } if !_rules[ruleOffset]() { - goto l546 + goto l549 } - goto l545 - l546: - position, tokenIndex = position545, tokenIndex545 + goto l548 + l549: + position, tokenIndex = position548, tokenIndex548 if !_rules[ruleOffset]() { - goto l542 + goto l545 } } + l548: + goto l534 l545: - goto l531 - l542: - position, tokenIndex = position531, tokenIndex531 + position, tokenIndex = position534, tokenIndex534 if buffer[position] != rune('#') { - goto l547 + goto l550 } position++ if !_rules[ruleOffset]() { - goto l547 + goto l550 } { - position548, tokenIndex548 := position, tokenIndex + position551, tokenIndex551 := position, tokenIndex if buffer[position] != rune('*') { - goto l548 + goto l551 } position++ if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l548 + goto l551 } position++ - l550: + l553: { - position551, tokenIndex551 := position, tokenIndex + position554, tokenIndex554 := position, tokenIndex if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l551 + goto l554 } position++ - goto l550 - l551: - position, tokenIndex = position551, tokenIndex551 + goto l553 + l554: + position, tokenIndex = position554, tokenIndex554 } { - position552, tokenIndex552 := position, tokenIndex + position555, tokenIndex555 := position, tokenIndex if buffer[position] != rune('-') { - goto l552 + goto l555 } position++ if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l552 + goto l555 } position++ - l554: + l557: { - position555, tokenIndex555 := position, tokenIndex + position558, tokenIndex558 := position, tokenIndex if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l555 + goto l558 } position++ - goto l554 - l555: - position, tokenIndex = position555, tokenIndex555 + goto l557 + l558: + position, tokenIndex = position558, tokenIndex558 } - goto l553 - l552: - position, tokenIndex = position552, tokenIndex552 - } - l553: - goto l549 - l548: - position, tokenIndex = position548, tokenIndex548 - } - l549: - goto l531 - l547: - position, tokenIndex = position531, tokenIndex531 + goto l556 + l555: + position, tokenIndex = position555, tokenIndex555 + } + l556: + goto l552 + l551: + position, tokenIndex = position551, tokenIndex551 + } + l552: + goto l534 + l550: + position, tokenIndex = position534, tokenIndex534 if buffer[position] != rune('#') { - goto l556 + goto l559 } position++ { - position557, tokenIndex557 := position, tokenIndex + position560, tokenIndex560 := position, tokenIndex if buffer[position] != rune('~') { - goto l557 + goto l560 } position++ - goto l558 - l557: - position, tokenIndex = position557, tokenIndex557 + goto l561 + l560: + position, tokenIndex = position560, tokenIndex560 } - l558: + l561: if buffer[position] != rune('(') { - goto l556 + goto l559 } position++ if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l556 + goto l559 } position++ { - position559, tokenIndex559 := position, tokenIndex + position562, tokenIndex562 := position, tokenIndex if !_rules[ruleWS]() { - goto l559 + goto l562 } - goto l560 - l559: - position, tokenIndex = position559, tokenIndex559 + goto l563 + l562: + position, tokenIndex = position562, tokenIndex562 } - l560: + l563: if buffer[position] != rune('<') { - goto l556 + goto l559 } position++ if buffer[position] != rune('<') { - goto l556 + goto l559 } position++ { - position561, tokenIndex561 := position, tokenIndex + position564, tokenIndex564 := position, tokenIndex if !_rules[ruleWS]() { - goto l561 + goto l564 } - goto l562 - l561: - position, tokenIndex = position561, tokenIndex561 + goto l565 + l564: + position, tokenIndex = position564, tokenIndex564 } - l562: + l565: if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l556 + goto l559 } position++ if buffer[position] != rune(')') { - goto l556 + goto l559 } position++ - goto l531 - l556: - position, tokenIndex = position531, tokenIndex531 + goto l534 + l559: + position, tokenIndex = position534, tokenIndex534 if !_rules[ruleARMRegister]() { - goto l529 + goto l532 } } - l531: + l534: { - position563, tokenIndex563 := position, tokenIndex + position566, tokenIndex566 := position, tokenIndex { - position564, tokenIndex564 := position, tokenIndex + position567, tokenIndex567 := position, tokenIndex if buffer[position] != rune('f') { - goto l565 + goto l568 } position++ - goto l564 - l565: - position, tokenIndex = position564, tokenIndex564 + goto l567 + l568: + position, tokenIndex = position567, tokenIndex567 if buffer[position] != rune('b') { - goto l566 + goto l569 } position++ - goto l564 - l566: - position, tokenIndex = position564, tokenIndex564 + goto l567 + l569: + position, tokenIndex = position567, tokenIndex567 if buffer[position] != rune(':') { - goto l567 + goto l570 } position++ - goto l564 - l567: - position, tokenIndex = position564, tokenIndex564 + goto l567 + l570: + position, tokenIndex = position567, tokenIndex567 if buffer[position] != rune('(') { - goto l568 + goto l571 } position++ - goto l564 - l568: - position, tokenIndex = position564, tokenIndex564 + goto l567 + l571: + position, tokenIndex = position567, tokenIndex567 if buffer[position] != rune('+') { - goto l569 + goto l572 } position++ - goto l564 - l569: - position, tokenIndex = position564, tokenIndex564 + goto l567 + l572: + position, tokenIndex = position567, tokenIndex567 if buffer[position] != rune('-') { - goto l563 + goto l566 } position++ } - l564: - goto l529 - l563: - position, tokenIndex = position563, tokenIndex563 + l567: + goto l532 + l566: + position, tokenIndex = position566, tokenIndex566 } - add(ruleRegisterOrConstant, position530) + add(ruleRegisterOrConstant, position533) } return true - l529: - position, tokenIndex = position529, tokenIndex529 + l532: + position, tokenIndex = position532, tokenIndex532 return false }, - /* 40 ARMConstantTweak <- <(((('u' / 's') (('x' / 'X') ('t' / 'T')) ('x' / 'w' / 'h' / 'b')) / (('l' / 'L') ('s' / 'S') ('l' / 'L')) / (('l' / 'L') ('s' / 'S') ('r' / 'R')) / (('r' / 'R') ('o' / 'O') ('r' / 'R')) / (('a' / 'A') ('s' / 'S') ('r' / 'R'))) (WS '#' Offset)?)> */ + /* 43 ARMConstantTweak <- <((((('u' / 's') (('x' / 'X') ('t' / 'T')) ('x' / 'w' / 'h' / 'b')) / (('l' / 'L') ('s' / 'S') ('l' / 'L')) / (('l' / 'L') ('s' / 'S') ('r' / 'R')) / (('r' / 'R') ('o' / 'O') ('r' / 'R')) / (('a' / 'A') ('s' / 'S') ('r' / 'R'))) (WS '#' Offset)?) / (('m' / 'M') ('u' / 'U') ('l' / 'L') ' ' ('v' / 'V') ('l' / 'L')) / (('m' / 'M') ('u' / 'U') ('l' / 'L') ' ' '#' [0-9]))> */ func() bool { - position570, tokenIndex570 := position, tokenIndex + position573, tokenIndex573 := position, tokenIndex { - position571 := position + position574 := position { - position572, tokenIndex572 := position, tokenIndex + position575, tokenIndex575 := position, tokenIndex { - position574, tokenIndex574 := position, tokenIndex - if buffer[position] != rune('u') { - goto l575 + position577, tokenIndex577 := position, tokenIndex + { + position579, tokenIndex579 := position, tokenIndex + if buffer[position] != rune('u') { + goto l580 + } + position++ + goto l579 + l580: + position, tokenIndex = position579, tokenIndex579 + if buffer[position] != rune('s') { + goto l578 + } + position++ } - position++ - goto l574 - l575: - position, tokenIndex = position574, tokenIndex574 - if buffer[position] != rune('s') { - goto l573 + l579: + { + position581, tokenIndex581 := position, tokenIndex + if buffer[position] != rune('x') { + goto l582 + } + position++ + goto l581 + l582: + position, tokenIndex = position581, tokenIndex581 + if buffer[position] != rune('X') { + goto l578 + } + position++ } - position++ + l581: + { + position583, tokenIndex583 := position, tokenIndex + if buffer[position] != rune('t') { + goto l584 + } + position++ + goto l583 + l584: + position, tokenIndex = position583, tokenIndex583 + if buffer[position] != rune('T') { + goto l578 + } + position++ + } + l583: + { + position585, tokenIndex585 := position, tokenIndex + if buffer[position] != rune('x') { + goto l586 + } + position++ + goto l585 + l586: + position, tokenIndex = position585, tokenIndex585 + if buffer[position] != rune('w') { + goto l587 + } + position++ + goto l585 + l587: + position, tokenIndex = position585, tokenIndex585 + if buffer[position] != rune('h') { + goto l588 + } + position++ + goto l585 + l588: + position, tokenIndex = position585, tokenIndex585 + if buffer[position] != rune('b') { + goto l578 + } + position++ + } + l585: + goto l577 + l578: + position, tokenIndex = position577, tokenIndex577 + { + position590, tokenIndex590 := position, tokenIndex + if buffer[position] != rune('l') { + goto l591 + } + position++ + goto l590 + l591: + position, tokenIndex = position590, tokenIndex590 + if buffer[position] != rune('L') { + goto l589 + } + position++ + } + l590: + { + position592, tokenIndex592 := position, tokenIndex + if buffer[position] != rune('s') { + goto l593 + } + position++ + goto l592 + l593: + position, tokenIndex = position592, tokenIndex592 + if buffer[position] != rune('S') { + goto l589 + } + position++ + } + l592: + { + position594, tokenIndex594 := position, tokenIndex + if buffer[position] != rune('l') { + goto l595 + } + position++ + goto l594 + l595: + position, tokenIndex = position594, tokenIndex594 + if buffer[position] != rune('L') { + goto l589 + } + position++ + } + l594: + goto l577 + l589: + position, tokenIndex = position577, tokenIndex577 + { + position597, tokenIndex597 := position, tokenIndex + if buffer[position] != rune('l') { + goto l598 + } + position++ + goto l597 + l598: + position, tokenIndex = position597, tokenIndex597 + if buffer[position] != rune('L') { + goto l596 + } + position++ + } + l597: + { + position599, tokenIndex599 := position, tokenIndex + if buffer[position] != rune('s') { + goto l600 + } + position++ + goto l599 + l600: + position, tokenIndex = position599, tokenIndex599 + if buffer[position] != rune('S') { + goto l596 + } + position++ + } + l599: + { + position601, tokenIndex601 := position, tokenIndex + if buffer[position] != rune('r') { + goto l602 + } + position++ + goto l601 + l602: + position, tokenIndex = position601, tokenIndex601 + if buffer[position] != rune('R') { + goto l596 + } + position++ + } + l601: + goto l577 + l596: + position, tokenIndex = position577, tokenIndex577 + { + position604, tokenIndex604 := position, tokenIndex + if buffer[position] != rune('r') { + goto l605 + } + position++ + goto l604 + l605: + position, tokenIndex = position604, tokenIndex604 + if buffer[position] != rune('R') { + goto l603 + } + position++ + } + l604: + { + position606, tokenIndex606 := position, tokenIndex + if buffer[position] != rune('o') { + goto l607 + } + position++ + goto l606 + l607: + position, tokenIndex = position606, tokenIndex606 + if buffer[position] != rune('O') { + goto l603 + } + position++ + } + l606: + { + position608, tokenIndex608 := position, tokenIndex + if buffer[position] != rune('r') { + goto l609 + } + position++ + goto l608 + l609: + position, tokenIndex = position608, tokenIndex608 + if buffer[position] != rune('R') { + goto l603 + } + position++ + } + l608: + goto l577 + l603: + position, tokenIndex = position577, tokenIndex577 + { + position610, tokenIndex610 := position, tokenIndex + if buffer[position] != rune('a') { + goto l611 + } + position++ + goto l610 + l611: + position, tokenIndex = position610, tokenIndex610 + if buffer[position] != rune('A') { + goto l576 + } + position++ + } + l610: + { + position612, tokenIndex612 := position, tokenIndex + if buffer[position] != rune('s') { + goto l613 + } + position++ + goto l612 + l613: + position, tokenIndex = position612, tokenIndex612 + if buffer[position] != rune('S') { + goto l576 + } + position++ + } + l612: + { + position614, tokenIndex614 := position, tokenIndex + if buffer[position] != rune('r') { + goto l615 + } + position++ + goto l614 + l615: + position, tokenIndex = position614, tokenIndex614 + if buffer[position] != rune('R') { + goto l576 + } + position++ + } + l614: } - l574: + l577: { - position576, tokenIndex576 := position, tokenIndex - if buffer[position] != rune('x') { - goto l577 + position616, tokenIndex616 := position, tokenIndex + if !_rules[ruleWS]() { + goto l616 } - position++ - goto l576 - l577: - position, tokenIndex = position576, tokenIndex576 - if buffer[position] != rune('X') { - goto l573 + if buffer[position] != rune('#') { + goto l616 } position++ + if !_rules[ruleOffset]() { + goto l616 + } + goto l617 + l616: + position, tokenIndex = position616, tokenIndex616 } + l617: + goto l575 l576: + position, tokenIndex = position575, tokenIndex575 { - position578, tokenIndex578 := position, tokenIndex - if buffer[position] != rune('t') { - goto l579 + position619, tokenIndex619 := position, tokenIndex + if buffer[position] != rune('m') { + goto l620 } position++ - goto l578 - l579: - position, tokenIndex = position578, tokenIndex578 - if buffer[position] != rune('T') { - goto l573 + goto l619 + l620: + position, tokenIndex = position619, tokenIndex619 + if buffer[position] != rune('M') { + goto l618 } position++ } - l578: + l619: { - position580, tokenIndex580 := position, tokenIndex - if buffer[position] != rune('x') { - goto l581 - } - position++ - goto l580 - l581: - position, tokenIndex = position580, tokenIndex580 - if buffer[position] != rune('w') { - goto l582 - } - position++ - goto l580 - l582: - position, tokenIndex = position580, tokenIndex580 - if buffer[position] != rune('h') { - goto l583 + position621, tokenIndex621 := position, tokenIndex + if buffer[position] != rune('u') { + goto l622 } position++ - goto l580 - l583: - position, tokenIndex = position580, tokenIndex580 - if buffer[position] != rune('b') { - goto l573 + goto l621 + l622: + position, tokenIndex = position621, tokenIndex621 + if buffer[position] != rune('U') { + goto l618 } position++ } - l580: - goto l572 - l573: - position, tokenIndex = position572, tokenIndex572 + l621: { - position585, tokenIndex585 := position, tokenIndex + position623, tokenIndex623 := position, tokenIndex if buffer[position] != rune('l') { - goto l586 + goto l624 } position++ - goto l585 - l586: - position, tokenIndex = position585, tokenIndex585 + goto l623 + l624: + position, tokenIndex = position623, tokenIndex623 if buffer[position] != rune('L') { - goto l584 + goto l618 } position++ } - l585: + l623: + if buffer[position] != rune(' ') { + goto l618 + } + position++ { - position587, tokenIndex587 := position, tokenIndex - if buffer[position] != rune('s') { - goto l588 - } - position++ - goto l587 - l588: - position, tokenIndex = position587, tokenIndex587 - if buffer[position] != rune('S') { - goto l584 - } - position++ - } - l587: - { - position589, tokenIndex589 := position, tokenIndex - if buffer[position] != rune('l') { - goto l590 + position625, tokenIndex625 := position, tokenIndex + if buffer[position] != rune('v') { + goto l626 } position++ - goto l589 - l590: - position, tokenIndex = position589, tokenIndex589 - if buffer[position] != rune('L') { - goto l584 + goto l625 + l626: + position, tokenIndex = position625, tokenIndex625 + if buffer[position] != rune('V') { + goto l618 } position++ } - l589: - goto l572 - l584: - position, tokenIndex = position572, tokenIndex572 + l625: { - position592, tokenIndex592 := position, tokenIndex + position627, tokenIndex627 := position, tokenIndex if buffer[position] != rune('l') { - goto l593 + goto l628 } position++ - goto l592 - l593: - position, tokenIndex = position592, tokenIndex592 + goto l627 + l628: + position, tokenIndex = position627, tokenIndex627 if buffer[position] != rune('L') { - goto l591 - } - position++ - } - l592: - { - position594, tokenIndex594 := position, tokenIndex - if buffer[position] != rune('s') { - goto l595 - } - position++ - goto l594 - l595: - position, tokenIndex = position594, tokenIndex594 - if buffer[position] != rune('S') { - goto l591 - } - position++ - } - l594: - { - position596, tokenIndex596 := position, tokenIndex - if buffer[position] != rune('r') { - goto l597 - } - position++ - goto l596 - l597: - position, tokenIndex = position596, tokenIndex596 - if buffer[position] != rune('R') { - goto l591 - } - position++ - } - l596: - goto l572 - l591: - position, tokenIndex = position572, tokenIndex572 - { - position599, tokenIndex599 := position, tokenIndex - if buffer[position] != rune('r') { - goto l600 - } - position++ - goto l599 - l600: - position, tokenIndex = position599, tokenIndex599 - if buffer[position] != rune('R') { - goto l598 - } - position++ - } - l599: - { - position601, tokenIndex601 := position, tokenIndex - if buffer[position] != rune('o') { - goto l602 - } - position++ - goto l601 - l602: - position, tokenIndex = position601, tokenIndex601 - if buffer[position] != rune('O') { - goto l598 - } - position++ - } - l601: - { - position603, tokenIndex603 := position, tokenIndex - if buffer[position] != rune('r') { - goto l604 - } - position++ - goto l603 - l604: - position, tokenIndex = position603, tokenIndex603 - if buffer[position] != rune('R') { - goto l598 + goto l618 } position++ } - l603: - goto l572 - l598: - position, tokenIndex = position572, tokenIndex572 + l627: + goto l575 + l618: + position, tokenIndex = position575, tokenIndex575 { - position605, tokenIndex605 := position, tokenIndex - if buffer[position] != rune('a') { - goto l606 + position629, tokenIndex629 := position, tokenIndex + if buffer[position] != rune('m') { + goto l630 } position++ - goto l605 - l606: - position, tokenIndex = position605, tokenIndex605 - if buffer[position] != rune('A') { - goto l570 + goto l629 + l630: + position, tokenIndex = position629, tokenIndex629 + if buffer[position] != rune('M') { + goto l573 } position++ } - l605: + l629: { - position607, tokenIndex607 := position, tokenIndex - if buffer[position] != rune('s') { - goto l608 + position631, tokenIndex631 := position, tokenIndex + if buffer[position] != rune('u') { + goto l632 } position++ - goto l607 - l608: - position, tokenIndex = position607, tokenIndex607 - if buffer[position] != rune('S') { - goto l570 + goto l631 + l632: + position, tokenIndex = position631, tokenIndex631 + if buffer[position] != rune('U') { + goto l573 } position++ } - l607: + l631: { - position609, tokenIndex609 := position, tokenIndex - if buffer[position] != rune('r') { - goto l610 + position633, tokenIndex633 := position, tokenIndex + if buffer[position] != rune('l') { + goto l634 } position++ - goto l609 - l610: - position, tokenIndex = position609, tokenIndex609 - if buffer[position] != rune('R') { - goto l570 + goto l633 + l634: + position, tokenIndex = position633, tokenIndex633 + if buffer[position] != rune('L') { + goto l573 } position++ } - l609: - } - l572: - { - position611, tokenIndex611 := position, tokenIndex - if !_rules[ruleWS]() { - goto l611 + l633: + if buffer[position] != rune(' ') { + goto l573 } + position++ if buffer[position] != rune('#') { - goto l611 + goto l573 } position++ - if !_rules[ruleOffset]() { - goto l611 + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l573 } - goto l612 - l611: - position, tokenIndex = position611, tokenIndex611 + position++ } - l612: - add(ruleARMConstantTweak, position571) + l575: + add(ruleARMConstantTweak, position574) } return true - l570: - position, tokenIndex = position570, tokenIndex570 + l573: + position, tokenIndex = position573, tokenIndex573 return false }, - /* 41 ARMRegister <- <((('s' / 'S') ('p' / 'P')) / (('x' / 'w' / 'd' / 'q' / 's' / 'h' / 'b') [0-9] [0-9]?) / (('x' / 'X') ('z' / 'Z') ('r' / 'R')) / (('w' / 'W') ('z' / 'Z') ('r' / 'R')) / (('n' / 'N') ('z' / 'Z') ('c' / 'C') ('v' / 'V')) / ARMVectorRegister / ('{' WS? ARMVectorRegister (',' WS? ARMVectorRegister)* WS? '}' ('[' [0-9] [0-9]? ']')?))> */ + /* 44 ARMRegister <- <((('s' / 'S') ('p' / 'P')) / (('x' / 'w' / 'd' / 'q' / 's' / 'h' / 'b') [0-9] [0-9]?) / (('x' / 'X') ('z' / 'Z') ('r' / 'R')) / (('w' / 'W') ('z' / 'Z') ('r' / 'R')) / (('n' / 'N') ('z' / 'Z') ('c' / 'C') ('v' / 'V')) / SVE2PredicateRegister / ARMVectorRegister / SVE2SpecialValue / ('{' WS? ARMVectorRegister (',' WS? ARMVectorRegister)* WS? '}' ('[' [0-9] [0-9]? ']')?))> */ func() bool { - position613, tokenIndex613 := position, tokenIndex + position635, tokenIndex635 := position, tokenIndex { - position614 := position + position636 := position { - position615, tokenIndex615 := position, tokenIndex + position637, tokenIndex637 := position, tokenIndex { - position617, tokenIndex617 := position, tokenIndex + position639, tokenIndex639 := position, tokenIndex if buffer[position] != rune('s') { - goto l618 + goto l640 } position++ - goto l617 - l618: - position, tokenIndex = position617, tokenIndex617 + goto l639 + l640: + position, tokenIndex = position639, tokenIndex639 if buffer[position] != rune('S') { - goto l616 + goto l638 } position++ } - l617: + l639: { - position619, tokenIndex619 := position, tokenIndex + position641, tokenIndex641 := position, tokenIndex if buffer[position] != rune('p') { - goto l620 + goto l642 } position++ - goto l619 - l620: - position, tokenIndex = position619, tokenIndex619 + goto l641 + l642: + position, tokenIndex = position641, tokenIndex641 if buffer[position] != rune('P') { - goto l616 + goto l638 } position++ } - l619: - goto l615 - l616: - position, tokenIndex = position615, tokenIndex615 + l641: + goto l637 + l638: + position, tokenIndex = position637, tokenIndex637 { - position622, tokenIndex622 := position, tokenIndex + position644, tokenIndex644 := position, tokenIndex if buffer[position] != rune('x') { - goto l623 + goto l645 } position++ - goto l622 - l623: - position, tokenIndex = position622, tokenIndex622 + goto l644 + l645: + position, tokenIndex = position644, tokenIndex644 if buffer[position] != rune('w') { - goto l624 + goto l646 } position++ - goto l622 - l624: - position, tokenIndex = position622, tokenIndex622 + goto l644 + l646: + position, tokenIndex = position644, tokenIndex644 if buffer[position] != rune('d') { - goto l625 + goto l647 } position++ - goto l622 - l625: - position, tokenIndex = position622, tokenIndex622 + goto l644 + l647: + position, tokenIndex = position644, tokenIndex644 if buffer[position] != rune('q') { - goto l626 + goto l648 } position++ - goto l622 - l626: - position, tokenIndex = position622, tokenIndex622 + goto l644 + l648: + position, tokenIndex = position644, tokenIndex644 if buffer[position] != rune('s') { - goto l627 + goto l649 } position++ - goto l622 - l627: - position, tokenIndex = position622, tokenIndex622 + goto l644 + l649: + position, tokenIndex = position644, tokenIndex644 if buffer[position] != rune('h') { - goto l628 + goto l650 } position++ - goto l622 - l628: - position, tokenIndex = position622, tokenIndex622 + goto l644 + l650: + position, tokenIndex = position644, tokenIndex644 if buffer[position] != rune('b') { - goto l621 + goto l643 } position++ } - l622: + l644: if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l621 + goto l643 } position++ { - position629, tokenIndex629 := position, tokenIndex + position651, tokenIndex651 := position, tokenIndex if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l629 + goto l651 } position++ - goto l630 - l629: - position, tokenIndex = position629, tokenIndex629 + goto l652 + l651: + position, tokenIndex = position651, tokenIndex651 } - l630: - goto l615 - l621: - position, tokenIndex = position615, tokenIndex615 + l652: + goto l637 + l643: + position, tokenIndex = position637, tokenIndex637 { - position632, tokenIndex632 := position, tokenIndex + position654, tokenIndex654 := position, tokenIndex if buffer[position] != rune('x') { - goto l633 + goto l655 } position++ - goto l632 - l633: - position, tokenIndex = position632, tokenIndex632 + goto l654 + l655: + position, tokenIndex = position654, tokenIndex654 if buffer[position] != rune('X') { - goto l631 + goto l653 } position++ } - l632: + l654: { - position634, tokenIndex634 := position, tokenIndex + position656, tokenIndex656 := position, tokenIndex if buffer[position] != rune('z') { - goto l635 + goto l657 } position++ - goto l634 - l635: - position, tokenIndex = position634, tokenIndex634 + goto l656 + l657: + position, tokenIndex = position656, tokenIndex656 if buffer[position] != rune('Z') { - goto l631 + goto l653 } position++ } - l634: + l656: { - position636, tokenIndex636 := position, tokenIndex + position658, tokenIndex658 := position, tokenIndex if buffer[position] != rune('r') { - goto l637 + goto l659 } position++ - goto l636 - l637: - position, tokenIndex = position636, tokenIndex636 + goto l658 + l659: + position, tokenIndex = position658, tokenIndex658 if buffer[position] != rune('R') { - goto l631 + goto l653 } position++ } - l636: - goto l615 - l631: - position, tokenIndex = position615, tokenIndex615 + l658: + goto l637 + l653: + position, tokenIndex = position637, tokenIndex637 { - position639, tokenIndex639 := position, tokenIndex + position661, tokenIndex661 := position, tokenIndex if buffer[position] != rune('w') { - goto l640 + goto l662 } position++ - goto l639 - l640: - position, tokenIndex = position639, tokenIndex639 + goto l661 + l662: + position, tokenIndex = position661, tokenIndex661 if buffer[position] != rune('W') { - goto l638 + goto l660 } position++ } - l639: + l661: { - position641, tokenIndex641 := position, tokenIndex + position663, tokenIndex663 := position, tokenIndex if buffer[position] != rune('z') { - goto l642 + goto l664 } position++ - goto l641 - l642: - position, tokenIndex = position641, tokenIndex641 + goto l663 + l664: + position, tokenIndex = position663, tokenIndex663 if buffer[position] != rune('Z') { - goto l638 + goto l660 } position++ } - l641: + l663: { - position643, tokenIndex643 := position, tokenIndex + position665, tokenIndex665 := position, tokenIndex if buffer[position] != rune('r') { - goto l644 + goto l666 } position++ - goto l643 - l644: - position, tokenIndex = position643, tokenIndex643 + goto l665 + l666: + position, tokenIndex = position665, tokenIndex665 if buffer[position] != rune('R') { - goto l638 + goto l660 } position++ } - l643: - goto l615 - l638: - position, tokenIndex = position615, tokenIndex615 + l665: + goto l637 + l660: + position, tokenIndex = position637, tokenIndex637 { - position646, tokenIndex646 := position, tokenIndex + position668, tokenIndex668 := position, tokenIndex if buffer[position] != rune('n') { - goto l647 + goto l669 } position++ - goto l646 - l647: - position, tokenIndex = position646, tokenIndex646 + goto l668 + l669: + position, tokenIndex = position668, tokenIndex668 if buffer[position] != rune('N') { - goto l645 + goto l667 } position++ } - l646: + l668: { - position648, tokenIndex648 := position, tokenIndex + position670, tokenIndex670 := position, tokenIndex if buffer[position] != rune('z') { - goto l649 + goto l671 } position++ - goto l648 - l649: - position, tokenIndex = position648, tokenIndex648 + goto l670 + l671: + position, tokenIndex = position670, tokenIndex670 if buffer[position] != rune('Z') { - goto l645 + goto l667 } position++ } - l648: + l670: { - position650, tokenIndex650 := position, tokenIndex + position672, tokenIndex672 := position, tokenIndex if buffer[position] != rune('c') { - goto l651 + goto l673 } position++ - goto l650 - l651: - position, tokenIndex = position650, tokenIndex650 + goto l672 + l673: + position, tokenIndex = position672, tokenIndex672 if buffer[position] != rune('C') { - goto l645 + goto l667 } position++ } - l650: + l672: { - position652, tokenIndex652 := position, tokenIndex + position674, tokenIndex674 := position, tokenIndex if buffer[position] != rune('v') { - goto l653 + goto l675 } position++ - goto l652 - l653: - position, tokenIndex = position652, tokenIndex652 + goto l674 + l675: + position, tokenIndex = position674, tokenIndex674 if buffer[position] != rune('V') { - goto l645 + goto l667 } position++ } - l652: - goto l615 - l645: - position, tokenIndex = position615, tokenIndex615 + l674: + goto l637 + l667: + position, tokenIndex = position637, tokenIndex637 + if !_rules[ruleSVE2PredicateRegister]() { + goto l676 + } + goto l637 + l676: + position, tokenIndex = position637, tokenIndex637 if !_rules[ruleARMVectorRegister]() { - goto l654 + goto l677 } - goto l615 - l654: - position, tokenIndex = position615, tokenIndex615 + goto l637 + l677: + position, tokenIndex = position637, tokenIndex637 + if !_rules[ruleSVE2SpecialValue]() { + goto l678 + } + goto l637 + l678: + position, tokenIndex = position637, tokenIndex637 if buffer[position] != rune('{') { - goto l613 + goto l635 } position++ { - position655, tokenIndex655 := position, tokenIndex + position679, tokenIndex679 := position, tokenIndex if !_rules[ruleWS]() { - goto l655 + goto l679 + } + goto l680 + l679: + position, tokenIndex = position679, tokenIndex679 + } + l680: + if !_rules[ruleARMVectorRegister]() { + goto l635 + } + l681: + { + position682, tokenIndex682 := position, tokenIndex + if buffer[position] != rune(',') { + goto l682 + } + position++ + { + position683, tokenIndex683 := position, tokenIndex + if !_rules[ruleWS]() { + goto l683 + } + goto l684 + l683: + position, tokenIndex = position683, tokenIndex683 + } + l684: + if !_rules[ruleARMVectorRegister]() { + goto l682 + } + goto l681 + l682: + position, tokenIndex = position682, tokenIndex682 + } + { + position685, tokenIndex685 := position, tokenIndex + if !_rules[ruleWS]() { + goto l685 + } + goto l686 + l685: + position, tokenIndex = position685, tokenIndex685 + } + l686: + if buffer[position] != rune('}') { + goto l635 + } + position++ + { + position687, tokenIndex687 := position, tokenIndex + if buffer[position] != rune('[') { + goto l687 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l687 + } + position++ + { + position689, tokenIndex689 := position, tokenIndex + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l689 + } + position++ + goto l690 + l689: + position, tokenIndex = position689, tokenIndex689 + } + l690: + if buffer[position] != rune(']') { + goto l687 + } + position++ + goto l688 + l687: + position, tokenIndex = position687, tokenIndex687 + } + l688: + } + l637: + add(ruleARMRegister, position636) + } + return true + l635: + position, tokenIndex = position635, tokenIndex635 + return false + }, + /* 45 ARMVectorRegister <- <(('p' / 'v' / 'z') [0-9] [0-9]? !([0-9] / [0-9] / ([a-z] / [A-Z]) / '_') ('.' [0-9]* ('b' / 's' / 'd' / 'h' / 'q') ('[' [0-9] [0-9]? ']')?)?)> */ + func() bool { + position691, tokenIndex691 := position, tokenIndex + { + position692 := position + { + position693, tokenIndex693 := position, tokenIndex + if buffer[position] != rune('p') { + goto l694 + } + position++ + goto l693 + l694: + position, tokenIndex = position693, tokenIndex693 + if buffer[position] != rune('v') { + goto l695 + } + position++ + goto l693 + l695: + position, tokenIndex = position693, tokenIndex693 + if buffer[position] != rune('z') { + goto l691 + } + position++ + } + l693: + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l691 + } + position++ + { + position696, tokenIndex696 := position, tokenIndex + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l696 + } + position++ + goto l697 + l696: + position, tokenIndex = position696, tokenIndex696 + } + l697: + { + position698, tokenIndex698 := position, tokenIndex + { + position699, tokenIndex699 := position, tokenIndex + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l700 + } + position++ + goto l699 + l700: + position, tokenIndex = position699, tokenIndex699 + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l701 + } + position++ + goto l699 + l701: + position, tokenIndex = position699, tokenIndex699 + { + position703, tokenIndex703 := position, tokenIndex + if c := buffer[position]; c < rune('a') || c > rune('z') { + goto l704 + } + position++ + goto l703 + l704: + position, tokenIndex = position703, tokenIndex703 + if c := buffer[position]; c < rune('A') || c > rune('Z') { + goto l702 + } + position++ + } + l703: + goto l699 + l702: + position, tokenIndex = position699, tokenIndex699 + if buffer[position] != rune('_') { + goto l698 + } + position++ + } + l699: + goto l691 + l698: + position, tokenIndex = position698, tokenIndex698 + } + { + position705, tokenIndex705 := position, tokenIndex + if buffer[position] != rune('.') { + goto l705 + } + position++ + l707: + { + position708, tokenIndex708 := position, tokenIndex + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l708 + } + position++ + goto l707 + l708: + position, tokenIndex = position708, tokenIndex708 + } + { + position709, tokenIndex709 := position, tokenIndex + if buffer[position] != rune('b') { + goto l710 + } + position++ + goto l709 + l710: + position, tokenIndex = position709, tokenIndex709 + if buffer[position] != rune('s') { + goto l711 + } + position++ + goto l709 + l711: + position, tokenIndex = position709, tokenIndex709 + if buffer[position] != rune('d') { + goto l712 + } + position++ + goto l709 + l712: + position, tokenIndex = position709, tokenIndex709 + if buffer[position] != rune('h') { + goto l713 + } + position++ + goto l709 + l713: + position, tokenIndex = position709, tokenIndex709 + if buffer[position] != rune('q') { + goto l705 + } + position++ + } + l709: + { + position714, tokenIndex714 := position, tokenIndex + if buffer[position] != rune('[') { + goto l714 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l714 + } + position++ + { + position716, tokenIndex716 := position, tokenIndex + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l716 + } + position++ + goto l717 + l716: + position, tokenIndex = position716, tokenIndex716 + } + l717: + if buffer[position] != rune(']') { + goto l714 + } + position++ + goto l715 + l714: + position, tokenIndex = position714, tokenIndex714 + } + l715: + goto l706 + l705: + position, tokenIndex = position705, tokenIndex705 + } + l706: + add(ruleARMVectorRegister, position692) + } + return true + l691: + position, tokenIndex = position691, tokenIndex691 + return false + }, + /* 46 SVE2PredicateRegister <- <(('p' / 'P') [0-9] [0-9]? '/' ('m' / 'M' / ('z' / 'Z')))> */ + func() bool { + position718, tokenIndex718 := position, tokenIndex + { + position719 := position + { + position720, tokenIndex720 := position, tokenIndex + if buffer[position] != rune('p') { + goto l721 + } + position++ + goto l720 + l721: + position, tokenIndex = position720, tokenIndex720 + if buffer[position] != rune('P') { + goto l718 + } + position++ + } + l720: + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l718 + } + position++ + { + position722, tokenIndex722 := position, tokenIndex + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l722 + } + position++ + goto l723 + l722: + position, tokenIndex = position722, tokenIndex722 + } + l723: + if buffer[position] != rune('/') { + goto l718 + } + position++ + { + position724, tokenIndex724 := position, tokenIndex + if buffer[position] != rune('m') { + goto l725 + } + position++ + goto l724 + l725: + position, tokenIndex = position724, tokenIndex724 + if buffer[position] != rune('M') { + goto l726 + } + position++ + goto l724 + l726: + position, tokenIndex = position724, tokenIndex724 + { + position727, tokenIndex727 := position, tokenIndex + if buffer[position] != rune('z') { + goto l728 + } + position++ + goto l727 + l728: + position, tokenIndex = position727, tokenIndex727 + if buffer[position] != rune('Z') { + goto l718 + } + position++ + } + l727: + } + l724: + add(ruleSVE2PredicateRegister, position719) + } + return true + l718: + position, tokenIndex = position718, tokenIndex718 + return false + }, + /* 47 SVE2SpecialValue <- <(((('p' / 'P') ('o' / 'O') ('w' / 'W') '2') / (('v' / 'V') ('l' / 'L') ('1' / '2' / '3' / '4' / '5' / '6' / '7' / '8') ![0-9]) / (('v' / 'V') ('l' / 'L') '1' '6') / (('v' / 'V') ('l' / 'L') '3' '2') / (('v' / 'V') ('l' / 'L') '6' '4') / (('v' / 'V') ('l' / 'L') '1' '2' '8') / (('v' / 'V') ('l' / 'L') '2' '5' '6') / (('m' / 'M') ('u' / 'U') ('l' / 'L') '3') / (('m' / 'M') ('u' / 'U') ('l' / 'L') '4') / (('a' / 'A') ('l' / 'L') ('l' / 'L'))) !([0-9] / [0-9] / ([a-z] / [A-Z]) / '_'))> */ + func() bool { + position729, tokenIndex729 := position, tokenIndex + { + position730 := position + { + position731, tokenIndex731 := position, tokenIndex + { + position733, tokenIndex733 := position, tokenIndex + if buffer[position] != rune('p') { + goto l734 + } + position++ + goto l733 + l734: + position, tokenIndex = position733, tokenIndex733 + if buffer[position] != rune('P') { + goto l732 + } + position++ + } + l733: + { + position735, tokenIndex735 := position, tokenIndex + if buffer[position] != rune('o') { + goto l736 + } + position++ + goto l735 + l736: + position, tokenIndex = position735, tokenIndex735 + if buffer[position] != rune('O') { + goto l732 + } + position++ + } + l735: + { + position737, tokenIndex737 := position, tokenIndex + if buffer[position] != rune('w') { + goto l738 + } + position++ + goto l737 + l738: + position, tokenIndex = position737, tokenIndex737 + if buffer[position] != rune('W') { + goto l732 + } + position++ + } + l737: + if buffer[position] != rune('2') { + goto l732 + } + position++ + goto l731 + l732: + position, tokenIndex = position731, tokenIndex731 + { + position740, tokenIndex740 := position, tokenIndex + if buffer[position] != rune('v') { + goto l741 + } + position++ + goto l740 + l741: + position, tokenIndex = position740, tokenIndex740 + if buffer[position] != rune('V') { + goto l739 + } + position++ + } + l740: + { + position742, tokenIndex742 := position, tokenIndex + if buffer[position] != rune('l') { + goto l743 + } + position++ + goto l742 + l743: + position, tokenIndex = position742, tokenIndex742 + if buffer[position] != rune('L') { + goto l739 + } + position++ + } + l742: + { + position744, tokenIndex744 := position, tokenIndex + if buffer[position] != rune('1') { + goto l745 + } + position++ + goto l744 + l745: + position, tokenIndex = position744, tokenIndex744 + if buffer[position] != rune('2') { + goto l746 + } + position++ + goto l744 + l746: + position, tokenIndex = position744, tokenIndex744 + if buffer[position] != rune('3') { + goto l747 + } + position++ + goto l744 + l747: + position, tokenIndex = position744, tokenIndex744 + if buffer[position] != rune('4') { + goto l748 + } + position++ + goto l744 + l748: + position, tokenIndex = position744, tokenIndex744 + if buffer[position] != rune('5') { + goto l749 + } + position++ + goto l744 + l749: + position, tokenIndex = position744, tokenIndex744 + if buffer[position] != rune('6') { + goto l750 + } + position++ + goto l744 + l750: + position, tokenIndex = position744, tokenIndex744 + if buffer[position] != rune('7') { + goto l751 + } + position++ + goto l744 + l751: + position, tokenIndex = position744, tokenIndex744 + if buffer[position] != rune('8') { + goto l739 + } + position++ + } + l744: + { + position752, tokenIndex752 := position, tokenIndex + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l752 + } + position++ + goto l739 + l752: + position, tokenIndex = position752, tokenIndex752 + } + goto l731 + l739: + position, tokenIndex = position731, tokenIndex731 + { + position754, tokenIndex754 := position, tokenIndex + if buffer[position] != rune('v') { + goto l755 + } + position++ + goto l754 + l755: + position, tokenIndex = position754, tokenIndex754 + if buffer[position] != rune('V') { + goto l753 + } + position++ + } + l754: + { + position756, tokenIndex756 := position, tokenIndex + if buffer[position] != rune('l') { + goto l757 + } + position++ + goto l756 + l757: + position, tokenIndex = position756, tokenIndex756 + if buffer[position] != rune('L') { + goto l753 + } + position++ + } + l756: + if buffer[position] != rune('1') { + goto l753 + } + position++ + if buffer[position] != rune('6') { + goto l753 + } + position++ + goto l731 + l753: + position, tokenIndex = position731, tokenIndex731 + { + position759, tokenIndex759 := position, tokenIndex + if buffer[position] != rune('v') { + goto l760 + } + position++ + goto l759 + l760: + position, tokenIndex = position759, tokenIndex759 + if buffer[position] != rune('V') { + goto l758 + } + position++ + } + l759: + { + position761, tokenIndex761 := position, tokenIndex + if buffer[position] != rune('l') { + goto l762 + } + position++ + goto l761 + l762: + position, tokenIndex = position761, tokenIndex761 + if buffer[position] != rune('L') { + goto l758 + } + position++ + } + l761: + if buffer[position] != rune('3') { + goto l758 + } + position++ + if buffer[position] != rune('2') { + goto l758 + } + position++ + goto l731 + l758: + position, tokenIndex = position731, tokenIndex731 + { + position764, tokenIndex764 := position, tokenIndex + if buffer[position] != rune('v') { + goto l765 + } + position++ + goto l764 + l765: + position, tokenIndex = position764, tokenIndex764 + if buffer[position] != rune('V') { + goto l763 + } + position++ + } + l764: + { + position766, tokenIndex766 := position, tokenIndex + if buffer[position] != rune('l') { + goto l767 + } + position++ + goto l766 + l767: + position, tokenIndex = position766, tokenIndex766 + if buffer[position] != rune('L') { + goto l763 + } + position++ + } + l766: + if buffer[position] != rune('6') { + goto l763 + } + position++ + if buffer[position] != rune('4') { + goto l763 + } + position++ + goto l731 + l763: + position, tokenIndex = position731, tokenIndex731 + { + position769, tokenIndex769 := position, tokenIndex + if buffer[position] != rune('v') { + goto l770 + } + position++ + goto l769 + l770: + position, tokenIndex = position769, tokenIndex769 + if buffer[position] != rune('V') { + goto l768 + } + position++ + } + l769: + { + position771, tokenIndex771 := position, tokenIndex + if buffer[position] != rune('l') { + goto l772 + } + position++ + goto l771 + l772: + position, tokenIndex = position771, tokenIndex771 + if buffer[position] != rune('L') { + goto l768 + } + position++ + } + l771: + if buffer[position] != rune('1') { + goto l768 + } + position++ + if buffer[position] != rune('2') { + goto l768 + } + position++ + if buffer[position] != rune('8') { + goto l768 + } + position++ + goto l731 + l768: + position, tokenIndex = position731, tokenIndex731 + { + position774, tokenIndex774 := position, tokenIndex + if buffer[position] != rune('v') { + goto l775 + } + position++ + goto l774 + l775: + position, tokenIndex = position774, tokenIndex774 + if buffer[position] != rune('V') { + goto l773 + } + position++ + } + l774: + { + position776, tokenIndex776 := position, tokenIndex + if buffer[position] != rune('l') { + goto l777 + } + position++ + goto l776 + l777: + position, tokenIndex = position776, tokenIndex776 + if buffer[position] != rune('L') { + goto l773 + } + position++ + } + l776: + if buffer[position] != rune('2') { + goto l773 + } + position++ + if buffer[position] != rune('5') { + goto l773 + } + position++ + if buffer[position] != rune('6') { + goto l773 + } + position++ + goto l731 + l773: + position, tokenIndex = position731, tokenIndex731 + { + position779, tokenIndex779 := position, tokenIndex + if buffer[position] != rune('m') { + goto l780 + } + position++ + goto l779 + l780: + position, tokenIndex = position779, tokenIndex779 + if buffer[position] != rune('M') { + goto l778 } - goto l656 - l655: - position, tokenIndex = position655, tokenIndex655 - } - l656: - if !_rules[ruleARMVectorRegister]() { - goto l613 + position++ } - l657: + l779: { - position658, tokenIndex658 := position, tokenIndex - if buffer[position] != rune(',') { - goto l658 + position781, tokenIndex781 := position, tokenIndex + if buffer[position] != rune('u') { + goto l782 } position++ - { - position659, tokenIndex659 := position, tokenIndex - if !_rules[ruleWS]() { - goto l659 - } - goto l660 - l659: - position, tokenIndex = position659, tokenIndex659 - } - l660: - if !_rules[ruleARMVectorRegister]() { - goto l658 + goto l781 + l782: + position, tokenIndex = position781, tokenIndex781 + if buffer[position] != rune('U') { + goto l778 } - goto l657 - l658: - position, tokenIndex = position658, tokenIndex658 + position++ } + l781: { - position661, tokenIndex661 := position, tokenIndex - if !_rules[ruleWS]() { - goto l661 + position783, tokenIndex783 := position, tokenIndex + if buffer[position] != rune('l') { + goto l784 } - goto l662 - l661: - position, tokenIndex = position661, tokenIndex661 + position++ + goto l783 + l784: + position, tokenIndex = position783, tokenIndex783 + if buffer[position] != rune('L') { + goto l778 + } + position++ } - l662: - if buffer[position] != rune('}') { - goto l613 + l783: + if buffer[position] != rune('3') { + goto l778 } position++ + goto l731 + l778: + position, tokenIndex = position731, tokenIndex731 { - position663, tokenIndex663 := position, tokenIndex - if buffer[position] != rune('[') { - goto l663 + position786, tokenIndex786 := position, tokenIndex + if buffer[position] != rune('m') { + goto l787 } position++ - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l663 + goto l786 + l787: + position, tokenIndex = position786, tokenIndex786 + if buffer[position] != rune('M') { + goto l785 } position++ - { - position665, tokenIndex665 := position, tokenIndex - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l665 - } - position++ - goto l666 - l665: - position, tokenIndex = position665, tokenIndex665 + } + l786: + { + position788, tokenIndex788 := position, tokenIndex + if buffer[position] != rune('u') { + goto l789 } - l666: - if buffer[position] != rune(']') { - goto l663 + position++ + goto l788 + l789: + position, tokenIndex = position788, tokenIndex788 + if buffer[position] != rune('U') { + goto l785 } position++ - goto l664 - l663: - position, tokenIndex = position663, tokenIndex663 - } - l664: - } - l615: - add(ruleARMRegister, position614) - } - return true - l613: - position, tokenIndex = position613, tokenIndex613 - return false - }, - /* 42 ARMVectorRegister <- <(('v' / 'V') [0-9] [0-9]? ('.' [0-9]* ('b' / 's' / 'd' / 'h' / 'q') ('[' [0-9] [0-9]? ']')?)?)> */ - func() bool { - position667, tokenIndex667 := position, tokenIndex - { - position668 := position - { - position669, tokenIndex669 := position, tokenIndex - if buffer[position] != rune('v') { - goto l670 } - position++ - goto l669 - l670: - position, tokenIndex = position669, tokenIndex669 - if buffer[position] != rune('V') { - goto l667 - } - position++ - } - l669: - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l667 - } - position++ - { - position671, tokenIndex671 := position, tokenIndex - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l671 + l788: + { + position790, tokenIndex790 := position, tokenIndex + if buffer[position] != rune('l') { + goto l791 + } + position++ + goto l790 + l791: + position, tokenIndex = position790, tokenIndex790 + if buffer[position] != rune('L') { + goto l785 + } + position++ } - position++ - goto l672 - l671: - position, tokenIndex = position671, tokenIndex671 - } - l672: - { - position673, tokenIndex673 := position, tokenIndex - if buffer[position] != rune('.') { - goto l673 + l790: + if buffer[position] != rune('4') { + goto l785 } position++ - l675: + goto l731 + l785: + position, tokenIndex = position731, tokenIndex731 { - position676, tokenIndex676 := position, tokenIndex - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l676 + position792, tokenIndex792 := position, tokenIndex + if buffer[position] != rune('a') { + goto l793 } position++ - goto l675 - l676: - position, tokenIndex = position676, tokenIndex676 - } - { - position677, tokenIndex677 := position, tokenIndex - if buffer[position] != rune('b') { - goto l678 + goto l792 + l793: + position, tokenIndex = position792, tokenIndex792 + if buffer[position] != rune('A') { + goto l729 } position++ - goto l677 - l678: - position, tokenIndex = position677, tokenIndex677 - if buffer[position] != rune('s') { - goto l679 + } + l792: + { + position794, tokenIndex794 := position, tokenIndex + if buffer[position] != rune('l') { + goto l795 } position++ - goto l677 - l679: - position, tokenIndex = position677, tokenIndex677 - if buffer[position] != rune('d') { - goto l680 + goto l794 + l795: + position, tokenIndex = position794, tokenIndex794 + if buffer[position] != rune('L') { + goto l729 } position++ - goto l677 - l680: - position, tokenIndex = position677, tokenIndex677 - if buffer[position] != rune('h') { - goto l681 + } + l794: + { + position796, tokenIndex796 := position, tokenIndex + if buffer[position] != rune('l') { + goto l797 } position++ - goto l677 - l681: - position, tokenIndex = position677, tokenIndex677 - if buffer[position] != rune('q') { - goto l673 + goto l796 + l797: + position, tokenIndex = position796, tokenIndex796 + if buffer[position] != rune('L') { + goto l729 } position++ } - l677: + l796: + } + l731: + { + position798, tokenIndex798 := position, tokenIndex { - position682, tokenIndex682 := position, tokenIndex - if buffer[position] != rune('[') { - goto l682 + position799, tokenIndex799 := position, tokenIndex + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l800 } position++ + goto l799 + l800: + position, tokenIndex = position799, tokenIndex799 if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l682 + goto l801 } position++ + goto l799 + l801: + position, tokenIndex = position799, tokenIndex799 { - position684, tokenIndex684 := position, tokenIndex - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l684 + position803, tokenIndex803 := position, tokenIndex + if c := buffer[position]; c < rune('a') || c > rune('z') { + goto l804 + } + position++ + goto l803 + l804: + position, tokenIndex = position803, tokenIndex803 + if c := buffer[position]; c < rune('A') || c > rune('Z') { + goto l802 } position++ - goto l685 - l684: - position, tokenIndex = position684, tokenIndex684 } - l685: - if buffer[position] != rune(']') { - goto l682 + l803: + goto l799 + l802: + position, tokenIndex = position799, tokenIndex799 + if buffer[position] != rune('_') { + goto l798 } position++ - goto l683 - l682: - position, tokenIndex = position682, tokenIndex682 } - l683: - goto l674 - l673: - position, tokenIndex = position673, tokenIndex673 + l799: + goto l729 + l798: + position, tokenIndex = position798, tokenIndex798 } - l674: - add(ruleARMVectorRegister, position668) + add(ruleSVE2SpecialValue, position730) } return true - l667: - position, tokenIndex = position667, tokenIndex667 + l729: + position, tokenIndex = position729, tokenIndex729 return false }, - /* 43 MemoryRef <- <((SymbolRef BaseIndexScale) / SymbolRef / Low12BitsSymbolRef / (Offset* BaseIndexScale) / (SegmentRegister Offset BaseIndexScale) / (SegmentRegister BaseIndexScale) / (SegmentRegister Offset) / ARMBaseIndexScale / BaseIndexScale)> */ + /* 48 MemoryRef <- <((SymbolRef BaseIndexScale) / SymbolRef / Low12BitsSymbolRef / (Offset* BaseIndexScale) / (SegmentRegister Offset BaseIndexScale) / (SegmentRegister BaseIndexScale) / (SegmentRegister Offset) / ARMBaseIndexScale / BaseIndexScale)> */ func() bool { - position686, tokenIndex686 := position, tokenIndex + position805, tokenIndex805 := position, tokenIndex { - position687 := position + position806 := position { - position688, tokenIndex688 := position, tokenIndex + position807, tokenIndex807 := position, tokenIndex if !_rules[ruleSymbolRef]() { - goto l689 + goto l808 } if !_rules[ruleBaseIndexScale]() { - goto l689 + goto l808 } - goto l688 - l689: - position, tokenIndex = position688, tokenIndex688 + goto l807 + l808: + position, tokenIndex = position807, tokenIndex807 if !_rules[ruleSymbolRef]() { - goto l690 + goto l809 } - goto l688 - l690: - position, tokenIndex = position688, tokenIndex688 + goto l807 + l809: + position, tokenIndex = position807, tokenIndex807 if !_rules[ruleLow12BitsSymbolRef]() { - goto l691 + goto l810 } - goto l688 - l691: - position, tokenIndex = position688, tokenIndex688 - l693: + goto l807 + l810: + position, tokenIndex = position807, tokenIndex807 + l812: { - position694, tokenIndex694 := position, tokenIndex + position813, tokenIndex813 := position, tokenIndex if !_rules[ruleOffset]() { - goto l694 + goto l813 } - goto l693 - l694: - position, tokenIndex = position694, tokenIndex694 + goto l812 + l813: + position, tokenIndex = position813, tokenIndex813 } if !_rules[ruleBaseIndexScale]() { - goto l692 + goto l811 } - goto l688 - l692: - position, tokenIndex = position688, tokenIndex688 + goto l807 + l811: + position, tokenIndex = position807, tokenIndex807 if !_rules[ruleSegmentRegister]() { - goto l695 + goto l814 } if !_rules[ruleOffset]() { - goto l695 + goto l814 } if !_rules[ruleBaseIndexScale]() { - goto l695 + goto l814 } - goto l688 - l695: - position, tokenIndex = position688, tokenIndex688 + goto l807 + l814: + position, tokenIndex = position807, tokenIndex807 if !_rules[ruleSegmentRegister]() { - goto l696 + goto l815 } if !_rules[ruleBaseIndexScale]() { - goto l696 + goto l815 } - goto l688 - l696: - position, tokenIndex = position688, tokenIndex688 + goto l807 + l815: + position, tokenIndex = position807, tokenIndex807 if !_rules[ruleSegmentRegister]() { - goto l697 + goto l816 } if !_rules[ruleOffset]() { - goto l697 + goto l816 } - goto l688 - l697: - position, tokenIndex = position688, tokenIndex688 + goto l807 + l816: + position, tokenIndex = position807, tokenIndex807 if !_rules[ruleARMBaseIndexScale]() { - goto l698 + goto l817 } - goto l688 - l698: - position, tokenIndex = position688, tokenIndex688 + goto l807 + l817: + position, tokenIndex = position807, tokenIndex807 if !_rules[ruleBaseIndexScale]() { - goto l686 + goto l805 } } - l688: - add(ruleMemoryRef, position687) + l807: + add(ruleMemoryRef, position806) } return true - l686: - position, tokenIndex = position686, tokenIndex686 + l805: + position, tokenIndex = position805, tokenIndex805 return false }, - /* 44 SymbolRef <- <((Offset* '+')? (LocalSymbol / SymbolName) Offset* ('@' Section Offset*)?)> */ + /* 49 SymbolRef <- <((Offset* '+')? (LocalSymbol / SymbolName) Offset* ('@' Section Offset*)?)> */ func() bool { - position699, tokenIndex699 := position, tokenIndex + position818, tokenIndex818 := position, tokenIndex { - position700 := position + position819 := position { - position701, tokenIndex701 := position, tokenIndex - l703: + position820, tokenIndex820 := position, tokenIndex + l822: { - position704, tokenIndex704 := position, tokenIndex + position823, tokenIndex823 := position, tokenIndex if !_rules[ruleOffset]() { - goto l704 + goto l823 } - goto l703 - l704: - position, tokenIndex = position704, tokenIndex704 + goto l822 + l823: + position, tokenIndex = position823, tokenIndex823 } if buffer[position] != rune('+') { - goto l701 + goto l820 } position++ - goto l702 - l701: - position, tokenIndex = position701, tokenIndex701 + goto l821 + l820: + position, tokenIndex = position820, tokenIndex820 } - l702: + l821: { - position705, tokenIndex705 := position, tokenIndex + position824, tokenIndex824 := position, tokenIndex if !_rules[ruleLocalSymbol]() { - goto l706 + goto l825 } - goto l705 - l706: - position, tokenIndex = position705, tokenIndex705 + goto l824 + l825: + position, tokenIndex = position824, tokenIndex824 if !_rules[ruleSymbolName]() { - goto l699 + goto l818 } } - l705: - l707: + l824: + l826: { - position708, tokenIndex708 := position, tokenIndex + position827, tokenIndex827 := position, tokenIndex if !_rules[ruleOffset]() { - goto l708 + goto l827 } - goto l707 - l708: - position, tokenIndex = position708, tokenIndex708 + goto l826 + l827: + position, tokenIndex = position827, tokenIndex827 } { - position709, tokenIndex709 := position, tokenIndex + position828, tokenIndex828 := position, tokenIndex if buffer[position] != rune('@') { - goto l709 + goto l828 } position++ if !_rules[ruleSection]() { - goto l709 + goto l828 } - l711: + l830: { - position712, tokenIndex712 := position, tokenIndex + position831, tokenIndex831 := position, tokenIndex if !_rules[ruleOffset]() { - goto l712 + goto l831 } - goto l711 - l712: - position, tokenIndex = position712, tokenIndex712 + goto l830 + l831: + position, tokenIndex = position831, tokenIndex831 } - goto l710 - l709: - position, tokenIndex = position709, tokenIndex709 + goto l829 + l828: + position, tokenIndex = position828, tokenIndex828 } - l710: - add(ruleSymbolRef, position700) + l829: + add(ruleSymbolRef, position819) } return true - l699: - position, tokenIndex = position699, tokenIndex699 + l818: + position, tokenIndex = position818, tokenIndex818 return false }, - /* 45 Low12BitsSymbolRef <- <(':' ('l' / 'L') ('o' / 'O') '1' '2' ':' (LocalSymbol / SymbolName) Offset?)> */ + /* 50 Low12BitsSymbolRef <- <(':' ('l' / 'L') ('o' / 'O') '1' '2' ':' (LocalSymbol / SymbolName) Offset?)> */ func() bool { - position713, tokenIndex713 := position, tokenIndex + position832, tokenIndex832 := position, tokenIndex { - position714 := position + position833 := position if buffer[position] != rune(':') { - goto l713 + goto l832 } position++ { - position715, tokenIndex715 := position, tokenIndex + position834, tokenIndex834 := position, tokenIndex if buffer[position] != rune('l') { - goto l716 + goto l835 } position++ - goto l715 - l716: - position, tokenIndex = position715, tokenIndex715 + goto l834 + l835: + position, tokenIndex = position834, tokenIndex834 if buffer[position] != rune('L') { - goto l713 + goto l832 } position++ } - l715: + l834: { - position717, tokenIndex717 := position, tokenIndex + position836, tokenIndex836 := position, tokenIndex if buffer[position] != rune('o') { - goto l718 + goto l837 } position++ - goto l717 - l718: - position, tokenIndex = position717, tokenIndex717 + goto l836 + l837: + position, tokenIndex = position836, tokenIndex836 if buffer[position] != rune('O') { - goto l713 + goto l832 } position++ } - l717: + l836: if buffer[position] != rune('1') { - goto l713 + goto l832 } position++ if buffer[position] != rune('2') { - goto l713 + goto l832 } position++ if buffer[position] != rune(':') { - goto l713 + goto l832 } position++ { - position719, tokenIndex719 := position, tokenIndex + position838, tokenIndex838 := position, tokenIndex if !_rules[ruleLocalSymbol]() { - goto l720 + goto l839 } - goto l719 - l720: - position, tokenIndex = position719, tokenIndex719 + goto l838 + l839: + position, tokenIndex = position838, tokenIndex838 if !_rules[ruleSymbolName]() { - goto l713 + goto l832 } } - l719: + l838: { - position721, tokenIndex721 := position, tokenIndex + position840, tokenIndex840 := position, tokenIndex if !_rules[ruleOffset]() { - goto l721 + goto l840 } - goto l722 - l721: - position, tokenIndex = position721, tokenIndex721 + goto l841 + l840: + position, tokenIndex = position840, tokenIndex840 } - l722: - add(ruleLow12BitsSymbolRef, position714) + l841: + add(ruleLow12BitsSymbolRef, position833) } return true - l713: - position, tokenIndex = position713, tokenIndex713 + l832: + position, tokenIndex = position832, tokenIndex832 return false }, - /* 46 ARMBaseIndexScale <- <('[' ARMRegister (',' WS? (('#' Offset (('*' [0-9]+) / ('*' '(' [0-9]+ Operator [0-9]+ ')') / ('+' [0-9]+)*)?) / ARMGOTLow12 / Low12BitsSymbolRef / ARMRegister) (',' WS? ARMConstantTweak)?)? ']' ARMPostincrement?)> */ + /* 51 ARMBaseIndexScale <- <('[' ARMRegister (',' WS? (('#' Offset (('*' [0-9]+) / ('*' '(' [0-9]+ Operator [0-9]+ ')') / ('+' [0-9]+)*)?) / ARMGOTLow12 / Low12BitsSymbolRef / ARMRegister) (',' WS? ARMConstantTweak)?)? ']' ARMPostincrement?)> */ func() bool { - position723, tokenIndex723 := position, tokenIndex + position842, tokenIndex842 := position, tokenIndex { - position724 := position + position843 := position if buffer[position] != rune('[') { - goto l723 + goto l842 } position++ if !_rules[ruleARMRegister]() { - goto l723 + goto l842 } { - position725, tokenIndex725 := position, tokenIndex + position844, tokenIndex844 := position, tokenIndex if buffer[position] != rune(',') { - goto l725 + goto l844 } position++ { - position727, tokenIndex727 := position, tokenIndex + position846, tokenIndex846 := position, tokenIndex if !_rules[ruleWS]() { - goto l727 + goto l846 } - goto l728 - l727: - position, tokenIndex = position727, tokenIndex727 + goto l847 + l846: + position, tokenIndex = position846, tokenIndex846 } - l728: + l847: { - position729, tokenIndex729 := position, tokenIndex + position848, tokenIndex848 := position, tokenIndex if buffer[position] != rune('#') { - goto l730 + goto l849 } position++ if !_rules[ruleOffset]() { - goto l730 + goto l849 } { - position731, tokenIndex731 := position, tokenIndex + position850, tokenIndex850 := position, tokenIndex { - position733, tokenIndex733 := position, tokenIndex + position852, tokenIndex852 := position, tokenIndex if buffer[position] != rune('*') { - goto l734 + goto l853 } position++ if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l734 + goto l853 } position++ - l735: + l854: { - position736, tokenIndex736 := position, tokenIndex + position855, tokenIndex855 := position, tokenIndex if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l736 + goto l855 } position++ - goto l735 - l736: - position, tokenIndex = position736, tokenIndex736 + goto l854 + l855: + position, tokenIndex = position855, tokenIndex855 } - goto l733 - l734: - position, tokenIndex = position733, tokenIndex733 + goto l852 + l853: + position, tokenIndex = position852, tokenIndex852 if buffer[position] != rune('*') { - goto l737 + goto l856 } position++ if buffer[position] != rune('(') { - goto l737 + goto l856 } position++ if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l737 + goto l856 } position++ - l738: + l857: { - position739, tokenIndex739 := position, tokenIndex + position858, tokenIndex858 := position, tokenIndex if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l739 + goto l858 } position++ - goto l738 - l739: - position, tokenIndex = position739, tokenIndex739 + goto l857 + l858: + position, tokenIndex = position858, tokenIndex858 } if !_rules[ruleOperator]() { - goto l737 + goto l856 } if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l737 + goto l856 } position++ - l740: + l859: { - position741, tokenIndex741 := position, tokenIndex + position860, tokenIndex860 := position, tokenIndex if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l741 + goto l860 } position++ - goto l740 - l741: - position, tokenIndex = position741, tokenIndex741 + goto l859 + l860: + position, tokenIndex = position860, tokenIndex860 } if buffer[position] != rune(')') { - goto l737 + goto l856 } position++ - goto l733 - l737: - position, tokenIndex = position733, tokenIndex733 - l742: + goto l852 + l856: + position, tokenIndex = position852, tokenIndex852 + l861: { - position743, tokenIndex743 := position, tokenIndex + position862, tokenIndex862 := position, tokenIndex if buffer[position] != rune('+') { - goto l743 + goto l862 } position++ if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l743 + goto l862 } position++ - l744: + l863: { - position745, tokenIndex745 := position, tokenIndex + position864, tokenIndex864 := position, tokenIndex if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l745 + goto l864 } position++ - goto l744 - l745: - position, tokenIndex = position745, tokenIndex745 + goto l863 + l864: + position, tokenIndex = position864, tokenIndex864 } - goto l742 - l743: - position, tokenIndex = position743, tokenIndex743 + goto l861 + l862: + position, tokenIndex = position862, tokenIndex862 } } - l733: - goto l732 + l852: + goto l851 - position, tokenIndex = position731, tokenIndex731 + position, tokenIndex = position850, tokenIndex850 } - l732: - goto l729 - l730: - position, tokenIndex = position729, tokenIndex729 + l851: + goto l848 + l849: + position, tokenIndex = position848, tokenIndex848 if !_rules[ruleARMGOTLow12]() { - goto l746 + goto l865 } - goto l729 - l746: - position, tokenIndex = position729, tokenIndex729 + goto l848 + l865: + position, tokenIndex = position848, tokenIndex848 if !_rules[ruleLow12BitsSymbolRef]() { - goto l747 + goto l866 } - goto l729 - l747: - position, tokenIndex = position729, tokenIndex729 + goto l848 + l866: + position, tokenIndex = position848, tokenIndex848 if !_rules[ruleARMRegister]() { - goto l725 + goto l844 } } - l729: + l848: { - position748, tokenIndex748 := position, tokenIndex + position867, tokenIndex867 := position, tokenIndex if buffer[position] != rune(',') { - goto l748 + goto l867 } position++ { - position750, tokenIndex750 := position, tokenIndex + position869, tokenIndex869 := position, tokenIndex if !_rules[ruleWS]() { - goto l750 + goto l869 } - goto l751 - l750: - position, tokenIndex = position750, tokenIndex750 + goto l870 + l869: + position, tokenIndex = position869, tokenIndex869 } - l751: + l870: if !_rules[ruleARMConstantTweak]() { - goto l748 + goto l867 } - goto l749 - l748: - position, tokenIndex = position748, tokenIndex748 + goto l868 + l867: + position, tokenIndex = position867, tokenIndex867 } - l749: - goto l726 - l725: - position, tokenIndex = position725, tokenIndex725 + l868: + goto l845 + l844: + position, tokenIndex = position844, tokenIndex844 } - l726: + l845: if buffer[position] != rune(']') { - goto l723 + goto l842 } position++ { - position752, tokenIndex752 := position, tokenIndex + position871, tokenIndex871 := position, tokenIndex if !_rules[ruleARMPostincrement]() { - goto l752 + goto l871 } - goto l753 - l752: - position, tokenIndex = position752, tokenIndex752 + goto l872 + l871: + position, tokenIndex = position871, tokenIndex871 } - l753: - add(ruleARMBaseIndexScale, position724) + l872: + add(ruleARMBaseIndexScale, position843) } return true - l723: - position, tokenIndex = position723, tokenIndex723 + l842: + position, tokenIndex = position842, tokenIndex842 return false }, - /* 47 ARMGOTLow12 <- <(':' ('g' / 'G') ('o' / 'O') ('t' / 'T') '_' ('l' / 'L') ('o' / 'O') '1' '2' ':' SymbolName)> */ + /* 52 ARMGOTLow12 <- <(':' ('g' / 'G') ('o' / 'O') ('t' / 'T') '_' ('l' / 'L') ('o' / 'O') '1' '2' ':' SymbolName)> */ func() bool { - position754, tokenIndex754 := position, tokenIndex + position873, tokenIndex873 := position, tokenIndex { - position755 := position + position874 := position if buffer[position] != rune(':') { - goto l754 + goto l873 } position++ { - position756, tokenIndex756 := position, tokenIndex + position875, tokenIndex875 := position, tokenIndex if buffer[position] != rune('g') { - goto l757 + goto l876 } position++ - goto l756 - l757: - position, tokenIndex = position756, tokenIndex756 + goto l875 + l876: + position, tokenIndex = position875, tokenIndex875 if buffer[position] != rune('G') { - goto l754 + goto l873 } position++ } - l756: + l875: { - position758, tokenIndex758 := position, tokenIndex + position877, tokenIndex877 := position, tokenIndex if buffer[position] != rune('o') { - goto l759 + goto l878 } position++ - goto l758 - l759: - position, tokenIndex = position758, tokenIndex758 + goto l877 + l878: + position, tokenIndex = position877, tokenIndex877 if buffer[position] != rune('O') { - goto l754 + goto l873 } position++ } - l758: + l877: { - position760, tokenIndex760 := position, tokenIndex + position879, tokenIndex879 := position, tokenIndex if buffer[position] != rune('t') { - goto l761 + goto l880 } position++ - goto l760 - l761: - position, tokenIndex = position760, tokenIndex760 + goto l879 + l880: + position, tokenIndex = position879, tokenIndex879 if buffer[position] != rune('T') { - goto l754 + goto l873 } position++ } - l760: + l879: if buffer[position] != rune('_') { - goto l754 + goto l873 } position++ { - position762, tokenIndex762 := position, tokenIndex + position881, tokenIndex881 := position, tokenIndex if buffer[position] != rune('l') { - goto l763 + goto l882 } position++ - goto l762 - l763: - position, tokenIndex = position762, tokenIndex762 + goto l881 + l882: + position, tokenIndex = position881, tokenIndex881 if buffer[position] != rune('L') { - goto l754 + goto l873 } position++ } - l762: + l881: { - position764, tokenIndex764 := position, tokenIndex + position883, tokenIndex883 := position, tokenIndex if buffer[position] != rune('o') { - goto l765 + goto l884 } position++ - goto l764 - l765: - position, tokenIndex = position764, tokenIndex764 + goto l883 + l884: + position, tokenIndex = position883, tokenIndex883 if buffer[position] != rune('O') { - goto l754 + goto l873 } position++ } - l764: + l883: if buffer[position] != rune('1') { - goto l754 + goto l873 } position++ if buffer[position] != rune('2') { - goto l754 + goto l873 } position++ if buffer[position] != rune(':') { - goto l754 + goto l873 } position++ if !_rules[ruleSymbolName]() { - goto l754 + goto l873 } - add(ruleARMGOTLow12, position755) + add(ruleARMGOTLow12, position874) } return true - l754: - position, tokenIndex = position754, tokenIndex754 + l873: + position, tokenIndex = position873, tokenIndex873 return false }, - /* 48 ARMPostincrement <- <'!'> */ + /* 53 ARMPostincrement <- <'!'> */ func() bool { - position766, tokenIndex766 := position, tokenIndex + position885, tokenIndex885 := position, tokenIndex { - position767 := position + position886 := position if buffer[position] != rune('!') { - goto l766 + goto l885 } position++ - add(ruleARMPostincrement, position767) + add(ruleARMPostincrement, position886) } return true - l766: - position, tokenIndex = position766, tokenIndex766 + l885: + position, tokenIndex = position885, tokenIndex885 return false }, - /* 49 BaseIndexScale <- <('(' RegisterOrConstant? WS? (',' WS? RegisterOrConstant WS? (',' [0-9]+)?)? ')')> */ + /* 54 BaseIndexScale <- <('(' RegisterOrConstant? WS? (',' WS? RegisterOrConstant WS? (',' [0-9]+)?)? ')')> */ func() bool { - position768, tokenIndex768 := position, tokenIndex + position887, tokenIndex887 := position, tokenIndex { - position769 := position + position888 := position if buffer[position] != rune('(') { - goto l768 + goto l887 } position++ { - position770, tokenIndex770 := position, tokenIndex + position889, tokenIndex889 := position, tokenIndex if !_rules[ruleRegisterOrConstant]() { - goto l770 + goto l889 } - goto l771 - l770: - position, tokenIndex = position770, tokenIndex770 + goto l890 + l889: + position, tokenIndex = position889, tokenIndex889 } - l771: + l890: { - position772, tokenIndex772 := position, tokenIndex + position891, tokenIndex891 := position, tokenIndex if !_rules[ruleWS]() { - goto l772 + goto l891 } - goto l773 - l772: - position, tokenIndex = position772, tokenIndex772 + goto l892 + l891: + position, tokenIndex = position891, tokenIndex891 } - l773: + l892: { - position774, tokenIndex774 := position, tokenIndex + position893, tokenIndex893 := position, tokenIndex if buffer[position] != rune(',') { - goto l774 + goto l893 } position++ { - position776, tokenIndex776 := position, tokenIndex + position895, tokenIndex895 := position, tokenIndex if !_rules[ruleWS]() { - goto l776 + goto l895 } - goto l777 - l776: - position, tokenIndex = position776, tokenIndex776 + goto l896 + l895: + position, tokenIndex = position895, tokenIndex895 } - l777: + l896: if !_rules[ruleRegisterOrConstant]() { - goto l774 + goto l893 } { - position778, tokenIndex778 := position, tokenIndex + position897, tokenIndex897 := position, tokenIndex if !_rules[ruleWS]() { - goto l778 + goto l897 } - goto l779 - l778: - position, tokenIndex = position778, tokenIndex778 + goto l898 + l897: + position, tokenIndex = position897, tokenIndex897 } - l779: + l898: { - position780, tokenIndex780 := position, tokenIndex + position899, tokenIndex899 := position, tokenIndex if buffer[position] != rune(',') { - goto l780 + goto l899 } position++ if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l780 + goto l899 } position++ - l782: + l901: { - position783, tokenIndex783 := position, tokenIndex + position902, tokenIndex902 := position, tokenIndex if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l783 + goto l902 } position++ - goto l782 - l783: - position, tokenIndex = position783, tokenIndex783 + goto l901 + l902: + position, tokenIndex = position902, tokenIndex902 } - goto l781 - l780: - position, tokenIndex = position780, tokenIndex780 + goto l900 + l899: + position, tokenIndex = position899, tokenIndex899 } - l781: - goto l775 - l774: - position, tokenIndex = position774, tokenIndex774 + l900: + goto l894 + l893: + position, tokenIndex = position893, tokenIndex893 } - l775: + l894: if buffer[position] != rune(')') { - goto l768 + goto l887 } position++ - add(ruleBaseIndexScale, position769) + add(ruleBaseIndexScale, position888) } return true - l768: - position, tokenIndex = position768, tokenIndex768 + l887: + position, tokenIndex = position887, tokenIndex887 return false }, - /* 50 Operator <- <('+' / '-')> */ + /* 55 Operator <- <('+' / '-')> */ func() bool { - position784, tokenIndex784 := position, tokenIndex + position903, tokenIndex903 := position, tokenIndex { - position785 := position + position904 := position { - position786, tokenIndex786 := position, tokenIndex + position905, tokenIndex905 := position, tokenIndex if buffer[position] != rune('+') { - goto l787 + goto l906 } position++ - goto l786 - l787: - position, tokenIndex = position786, tokenIndex786 + goto l905 + l906: + position, tokenIndex = position905, tokenIndex905 if buffer[position] != rune('-') { - goto l784 + goto l903 } position++ } - l786: - add(ruleOperator, position785) + l905: + add(ruleOperator, position904) } return true - l784: - position, tokenIndex = position784, tokenIndex784 + l903: + position, tokenIndex = position903, tokenIndex903 return false }, - /* 51 Offset <- <('+'? '-'? (('0' ('b' / 'B') ('0' / '1')+) / ('0' ('x' / 'X') ([0-9] / [0-9] / ([a-f] / [A-F]))+) / [0-9]+))> */ + /* 56 Offset <- <('+'? '-'? (('0' ('b' / 'B') ('0' / '1')+) / ('0' ('x' / 'X') ([0-9] / [0-9] / ([a-f] / [A-F]))+) / [0-9]+))> */ func() bool { - position788, tokenIndex788 := position, tokenIndex + position907, tokenIndex907 := position, tokenIndex { - position789 := position + position908 := position { - position790, tokenIndex790 := position, tokenIndex + position909, tokenIndex909 := position, tokenIndex if buffer[position] != rune('+') { - goto l790 + goto l909 } position++ - goto l791 - l790: - position, tokenIndex = position790, tokenIndex790 + goto l910 + l909: + position, tokenIndex = position909, tokenIndex909 } - l791: + l910: { - position792, tokenIndex792 := position, tokenIndex + position911, tokenIndex911 := position, tokenIndex if buffer[position] != rune('-') { - goto l792 + goto l911 } position++ - goto l793 - l792: - position, tokenIndex = position792, tokenIndex792 + goto l912 + l911: + position, tokenIndex = position911, tokenIndex911 } - l793: + l912: { - position794, tokenIndex794 := position, tokenIndex + position913, tokenIndex913 := position, tokenIndex if buffer[position] != rune('0') { - goto l795 + goto l914 } position++ { - position796, tokenIndex796 := position, tokenIndex + position915, tokenIndex915 := position, tokenIndex if buffer[position] != rune('b') { - goto l797 + goto l916 } position++ - goto l796 - l797: - position, tokenIndex = position796, tokenIndex796 + goto l915 + l916: + position, tokenIndex = position915, tokenIndex915 if buffer[position] != rune('B') { - goto l795 + goto l914 } position++ } - l796: + l915: { - position800, tokenIndex800 := position, tokenIndex + position919, tokenIndex919 := position, tokenIndex if buffer[position] != rune('0') { - goto l801 + goto l920 } position++ - goto l800 - l801: - position, tokenIndex = position800, tokenIndex800 + goto l919 + l920: + position, tokenIndex = position919, tokenIndex919 if buffer[position] != rune('1') { - goto l795 + goto l914 } position++ } - l800: - l798: + l919: + l917: { - position799, tokenIndex799 := position, tokenIndex + position918, tokenIndex918 := position, tokenIndex { - position802, tokenIndex802 := position, tokenIndex + position921, tokenIndex921 := position, tokenIndex if buffer[position] != rune('0') { - goto l803 + goto l922 } position++ - goto l802 - l803: - position, tokenIndex = position802, tokenIndex802 + goto l921 + l922: + position, tokenIndex = position921, tokenIndex921 if buffer[position] != rune('1') { - goto l799 + goto l918 } position++ } - l802: - goto l798 - l799: - position, tokenIndex = position799, tokenIndex799 + l921: + goto l917 + l918: + position, tokenIndex = position918, tokenIndex918 } - goto l794 - l795: - position, tokenIndex = position794, tokenIndex794 + goto l913 + l914: + position, tokenIndex = position913, tokenIndex913 if buffer[position] != rune('0') { - goto l804 + goto l923 } position++ { - position805, tokenIndex805 := position, tokenIndex + position924, tokenIndex924 := position, tokenIndex if buffer[position] != rune('x') { - goto l806 + goto l925 } position++ - goto l805 - l806: - position, tokenIndex = position805, tokenIndex805 + goto l924 + l925: + position, tokenIndex = position924, tokenIndex924 if buffer[position] != rune('X') { - goto l804 + goto l923 } position++ } - l805: + l924: { - position809, tokenIndex809 := position, tokenIndex + position928, tokenIndex928 := position, tokenIndex if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l810 + goto l929 } position++ - goto l809 - l810: - position, tokenIndex = position809, tokenIndex809 + goto l928 + l929: + position, tokenIndex = position928, tokenIndex928 if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l811 + goto l930 } position++ - goto l809 - l811: - position, tokenIndex = position809, tokenIndex809 + goto l928 + l930: + position, tokenIndex = position928, tokenIndex928 { - position812, tokenIndex812 := position, tokenIndex + position931, tokenIndex931 := position, tokenIndex if c := buffer[position]; c < rune('a') || c > rune('f') { - goto l813 + goto l932 } position++ - goto l812 - l813: - position, tokenIndex = position812, tokenIndex812 + goto l931 + l932: + position, tokenIndex = position931, tokenIndex931 if c := buffer[position]; c < rune('A') || c > rune('F') { - goto l804 + goto l923 } position++ } - l812: + l931: } - l809: - l807: + l928: + l926: { - position808, tokenIndex808 := position, tokenIndex + position927, tokenIndex927 := position, tokenIndex { - position814, tokenIndex814 := position, tokenIndex + position933, tokenIndex933 := position, tokenIndex if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l815 + goto l934 } position++ - goto l814 - l815: - position, tokenIndex = position814, tokenIndex814 + goto l933 + l934: + position, tokenIndex = position933, tokenIndex933 if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l816 + goto l935 } position++ - goto l814 - l816: - position, tokenIndex = position814, tokenIndex814 + goto l933 + l935: + position, tokenIndex = position933, tokenIndex933 { - position817, tokenIndex817 := position, tokenIndex + position936, tokenIndex936 := position, tokenIndex if c := buffer[position]; c < rune('a') || c > rune('f') { - goto l818 + goto l937 } position++ - goto l817 - l818: - position, tokenIndex = position817, tokenIndex817 + goto l936 + l937: + position, tokenIndex = position936, tokenIndex936 if c := buffer[position]; c < rune('A') || c > rune('F') { - goto l808 + goto l927 } position++ } - l817: + l936: } - l814: - goto l807 - l808: - position, tokenIndex = position808, tokenIndex808 + l933: + goto l926 + l927: + position, tokenIndex = position927, tokenIndex927 } - goto l794 - l804: - position, tokenIndex = position794, tokenIndex794 + goto l913 + l923: + position, tokenIndex = position913, tokenIndex913 if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l788 + goto l907 } position++ - l819: + l938: { - position820, tokenIndex820 := position, tokenIndex + position939, tokenIndex939 := position, tokenIndex if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l820 + goto l939 } position++ - goto l819 - l820: - position, tokenIndex = position820, tokenIndex820 + goto l938 + l939: + position, tokenIndex = position939, tokenIndex939 } } - l794: - add(ruleOffset, position789) + l913: + add(ruleOffset, position908) } return true - l788: - position, tokenIndex = position788, tokenIndex788 + l907: + position, tokenIndex = position907, tokenIndex907 return false }, - /* 52 Section <- <([a-z] / [A-Z] / '@')+> */ + /* 57 Section <- <([a-z] / [A-Z] / '@')+> */ func() bool { - position821, tokenIndex821 := position, tokenIndex + position940, tokenIndex940 := position, tokenIndex { - position822 := position + position941 := position { - position825, tokenIndex825 := position, tokenIndex + position944, tokenIndex944 := position, tokenIndex if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l826 + goto l945 } position++ - goto l825 - l826: - position, tokenIndex = position825, tokenIndex825 + goto l944 + l945: + position, tokenIndex = position944, tokenIndex944 if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l827 + goto l946 } position++ - goto l825 - l827: - position, tokenIndex = position825, tokenIndex825 + goto l944 + l946: + position, tokenIndex = position944, tokenIndex944 if buffer[position] != rune('@') { - goto l821 + goto l940 } position++ } - l825: - l823: + l944: + l942: { - position824, tokenIndex824 := position, tokenIndex + position943, tokenIndex943 := position, tokenIndex { - position828, tokenIndex828 := position, tokenIndex + position947, tokenIndex947 := position, tokenIndex if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l829 + goto l948 } position++ - goto l828 - l829: - position, tokenIndex = position828, tokenIndex828 + goto l947 + l948: + position, tokenIndex = position947, tokenIndex947 if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l830 + goto l949 } position++ - goto l828 - l830: - position, tokenIndex = position828, tokenIndex828 + goto l947 + l949: + position, tokenIndex = position947, tokenIndex947 if buffer[position] != rune('@') { - goto l824 + goto l943 } position++ } - l828: - goto l823 - l824: - position, tokenIndex = position824, tokenIndex824 + l947: + goto l942 + l943: + position, tokenIndex = position943, tokenIndex943 } - add(ruleSection, position822) + add(ruleSection, position941) } return true - l821: - position, tokenIndex = position821, tokenIndex821 + l940: + position, tokenIndex = position940, tokenIndex940 return false }, - /* 53 SegmentRegister <- <('%' ([c-g] / 's') ('s' ':'))> */ + /* 58 SegmentRegister <- <('%' ([c-g] / 's') ('s' ':'))> */ func() bool { - position831, tokenIndex831 := position, tokenIndex + position950, tokenIndex950 := position, tokenIndex { - position832 := position + position951 := position if buffer[position] != rune('%') { - goto l831 + goto l950 } position++ { - position833, tokenIndex833 := position, tokenIndex + position952, tokenIndex952 := position, tokenIndex if c := buffer[position]; c < rune('c') || c > rune('g') { - goto l834 + goto l953 } position++ - goto l833 - l834: - position, tokenIndex = position833, tokenIndex833 + goto l952 + l953: + position, tokenIndex = position952, tokenIndex952 if buffer[position] != rune('s') { - goto l831 + goto l950 } position++ } - l833: + l952: if buffer[position] != rune('s') { - goto l831 + goto l950 } position++ if buffer[position] != rune(':') { - goto l831 + goto l950 } position++ - add(ruleSegmentRegister, position832) + add(ruleSegmentRegister, position951) } return true - l831: - position, tokenIndex = position831, tokenIndex831 + l950: + position, tokenIndex = position950, tokenIndex950 return false }, } diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/aarch64-Basic/in.s b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/aarch64-Basic/in.s index 8b45e25fdfe06..f151c23856e3b 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/aarch64-Basic/in.s +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/aarch64-Basic/in.s @@ -78,6 +78,15 @@ foo: add w0, w1, b2, sxtw add w0, w1, b2, sxtx + // Aarch64 SVE2 added these forms: + ld1d { z1.d }, p91/z, [x13, x11, lsl #3] + ld1b { z11.b }, p15/z, [x10, #1, mul vl] + st2d { z6.d, z7.d }, p0, [x12] + // Check that "p22" here isn't parsed as the "p22" register. + bl p224_point_add + ptrue p0.d, vl1 + // The "#7" here isn't a comment, it's now valid Aarch64 assembly. + cnth x8, all, mul #7 local_function: diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/aarch64-Basic/out.s b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/aarch64-Basic/out.s index 0b9828f8515e5..c02461083c86b 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/aarch64-Basic/out.s +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/aarch64-Basic/out.s @@ -125,6 +125,16 @@ foo: add w0, w1, b2, sxtw add w0, w1, b2, sxtx + // Aarch64 SVE2 added these forms: + ld1d { z1.d }, p91/z, [x13, x11, lsl #3] + ld1b { z11.b }, p15/z, [x10, #1, mul vl] + st2d { z6.d, z7.d }, p0, [x12] + // Check that "p22" here isn't parsed as the "p22" register. +// WAS bl p224_point_add + bl bcm_redirector_p224_point_add + ptrue p0.d, vl1 + // The "#7" here isn't a comment, it's now valid Aarch64 assembly. + cnth x8, all, mul #7 .Llocal_function_local_target: local_function: @@ -141,10 +151,20 @@ bss_symbol: .loc 1 2 0 BORINGSSL_bcm_text_end: .p2align 2 +.hidden bcm_redirector_p224_point_add +.type bcm_redirector_p224_point_add, @function +bcm_redirector_p224_point_add: +.cfi_startproc + hint #34 // bti c + b p224_point_add +.cfi_endproc +.size bcm_redirector_p224_point_add, .-bcm_redirector_p224_point_add +.p2align 2 .hidden bcm_redirector_remote_function .type bcm_redirector_remote_function, @function bcm_redirector_remote_function: .cfi_startproc + hint #34 // bti c b remote_function .cfi_endproc .size bcm_redirector_remote_function, .-bcm_redirector_remote_function @@ -153,6 +173,7 @@ bcm_redirector_remote_function: .type bcm_redirector_y0, @function bcm_redirector_y0: .cfi_startproc + hint #34 // bti c b y0 .cfi_endproc .size bcm_redirector_y0, .-bcm_redirector_y0 @@ -161,6 +182,7 @@ bcm_redirector_y0: .type bcm_redirector_y12, @function bcm_redirector_y12: .cfi_startproc + hint #34 // bti c b y12 .cfi_endproc .size bcm_redirector_y12, .-bcm_redirector_y12 @@ -169,6 +191,7 @@ bcm_redirector_y12: .type bss_symbol_bss_get, @function bss_symbol_bss_get: .cfi_startproc + hint #34 // bti c adrp x0, .Lbss_symbol_local_target add x0, x0, :lo12:.Lbss_symbol_local_target ret @@ -179,6 +202,7 @@ bss_symbol_bss_get: .type .Lboringssl_loadgot_stderr, @function .Lboringssl_loadgot_stderr: .cfi_startproc + hint #34 // bti c adrp x0, :got:stderr ldr x0, [x0, :got_lo12:stderr] ret @@ -189,6 +213,7 @@ bss_symbol_bss_get: .type .LOPENSSL_armcap_P_addr, @function .LOPENSSL_armcap_P_addr: .cfi_startproc + hint #34 // bti c adrp x0, OPENSSL_armcap_P add x0, x0, :lo12:OPENSSL_armcap_P ret diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/in.s b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/in.s index a00a69147c2c1..3b7a1ed156782 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/in.s +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/in.s @@ -65,4 +65,7 @@ bar: cmpq foo@GOTPCREL(%rip), %rax cmpq %rax, foo@GOTPCREL(%rip) + # With -mcmodel=medium, the code may load the address of the GOT directly. + leaq _GLOBAL_OFFSET_TABLE_(%rip), %rcx + .comm foobar,64,32 diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/out.s b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/out.s index f118e4fa9d0c0..ae47e4117776a 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/out.s +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/out.s @@ -268,6 +268,11 @@ bar: popq %rbx leaq 128(%rsp), %rsp + # With -mcmodel=medium, the code may load the address of the GOT directly. +# WAS leaq _GLOBAL_OFFSET_TABLE_(%rip), %rcx + leaq .Lboringssl_got_delta(%rip), %rcx + addq .Lboringssl_got_delta(%rip), %rcx + .comm foobar,64,32 .text .loc 1 2 0 @@ -302,6 +307,8 @@ OPENSSL_ia32cap_get: .size OPENSSL_ia32cap_addr_delta, 8 OPENSSL_ia32cap_addr_delta: .quad OPENSSL_ia32cap_P-OPENSSL_ia32cap_addr_delta +.Lboringssl_got_delta: + .quad _GLOBAL_OFFSET_TABLE_-.Lboringssl_got_delta .type BORINGSSL_bcm_text_hash, @object .size BORINGSSL_bcm_text_hash, 32 BORINGSSL_bcm_text_hash: diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/x86_64-LabelRewrite/out.s b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/x86_64-LabelRewrite/out.s index 6549db71d9d05..03580d10ee26c 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/x86_64-LabelRewrite/out.s +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/delocate/testdata/x86_64-LabelRewrite/out.s @@ -93,7 +93,7 @@ bar: # assumption that it's too small to hold a pointer. But Clang # will store offsets in it. # WAS .byte (.LBB231_40-.LBB231_19)>>2, 4, .Lfoo, (.Lfoo), .Lfoo<<400, ( .Lfoo ) << 66 - .byte (.LBB231_40_BCM_1-.LBB231_19_BCM_1)>>2, 4, .Lfoo_BCM_1, (.Lfoo_BCM_1), .Lfoo_BCM_1<<400, ( .Lfoo_BCM_1 ) << 66 + .byte (.LBB231_40_BCM_1-.LBB231_19_BCM_1)>>2, 4, .Lfoo_BCM_1, (.Lfoo_BCM_1), .Lfoo_BCM_1<<400, (.Lfoo_BCM_1)<<66 .byte 421 .text .loc 1 2 0 diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/test_fips.c b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/test_fips.c index 3a1f7fcebb970..bd0ec46646d55 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/test_fips.c +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/fipstools/test_fips.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -54,7 +55,8 @@ int main(int argc, char **argv) { printf("No module version set\n"); goto err; } - printf("Module version: %" PRIu32 "\n", module_version); + printf("Module: '%s', version: %" PRIu32 "\n", FIPS_module_name(), + module_version); static const uint8_t kAESKey[16] = "BoringCrypto Key"; static const uint8_t kPlaintext[64] = @@ -216,6 +218,18 @@ int main(int argc, char **argv) { RSA_free(rsa_key); + /* Generating a key with a null output parameter. */ + printf("About to generate RSA key with null output\n"); + if (!RSA_generate_key_fips(NULL, 2048, NULL)) { + printf("RSA_generate_key_fips failed with null output parameter\n"); + ERR_clear_error(); + } else { + printf( + "RSA_generate_key_fips unexpectedly succeeded with null output " + "parameter\n"); + goto err; + } + EC_KEY *ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); if (ec_key == NULL) { printf("invalid ECDSA key\n"); @@ -261,6 +275,30 @@ int main(int argc, char **argv) { ECDSA_SIG_free(sig); EC_KEY_free(ec_key); + /* Generating a key with a null output pointer. */ + printf("About to generate P-256 key with NULL output\n"); + if (!EC_KEY_generate_key_fips(NULL)) { + printf("EC_KEY_generate_key_fips failed with a NULL output pointer.\n"); + ERR_clear_error(); + } else { + printf( + "EC_KEY_generate_key_fips unexpectedly succeeded with a NULL output " + "pointer.\n"); + goto err; + } + + /* ECDSA with an invalid public key. */ + ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + static const uint8_t kNotValidX926[] = {1,2,3,4,5,6}; + if (!EC_KEY_oct2key(ec_key, kNotValidX926, sizeof(kNotValidX926), + /*ctx=*/NULL)) { + printf("Error while parsing invalid ECDSA public key"); + } else { + printf("Unexpected success while parsing invalid ECDSA public key"); + goto err; + } + EC_KEY_free(ec_key); + /* DBRG */ CTR_DRBG_STATE drbg; printf("About to seed CTR-DRBG with "); @@ -291,6 +329,19 @@ int main(int argc, char **argv) { printf(" got "); hexdump(hkdf_output, sizeof(hkdf_output)); + /* TLS v1.0 KDF */ + printf("About to run TLS v1.0 KDF\n"); + uint8_t tls10_output[32]; + if (!CRYPTO_tls1_prf(EVP_md5_sha1(), tls10_output, sizeof(tls10_output), + kAESKey, sizeof(kAESKey), "foo", 3, kPlaintextSHA256, + sizeof(kPlaintextSHA256), kPlaintextSHA256, + sizeof(kPlaintextSHA256))) { + fprintf(stderr, "TLS v1.0 KDF failed.\n"); + goto err; + } + printf(" got "); + hexdump(tls10_output, sizeof(tls10_output)); + /* TLS v1.2 KDF */ printf("About to run TLS v1.2 KDF\n"); uint8_t tls12_output[32]; diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/generate_build_files.py b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/generate_build_files.py index 386910b15b7be..31b39f4a2fc1d 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/generate_build_files.py +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/generate_build_files.py @@ -23,39 +23,7 @@ import json -# OS_ARCH_COMBOS maps from OS and platform to the OpenSSL assembly "style" for -# that platform and the extension used by asm files. -# -# TODO(https://crbug.com/boringssl/524): This probably should be a map, but some -# downstream scripts import this to find what folders to add/remove from git. -OS_ARCH_COMBOS = [ - ('apple', 'arm', 'ios32', [], 'S'), - ('apple', 'aarch64', 'ios64', [], 'S'), - ('apple', 'x86', 'macosx', ['-fPIC', '-DOPENSSL_IA32_SSE2'], 'S'), - ('apple', 'x86_64', 'macosx', [], 'S'), - ('linux', 'arm', 'linux32', [], 'S'), - ('linux', 'aarch64', 'linux64', [], 'S'), - ('linux', 'x86', 'elf', ['-fPIC', '-DOPENSSL_IA32_SSE2'], 'S'), - ('linux', 'x86_64', 'elf', [], 'S'), - ('win', 'x86', 'win32n', ['-DOPENSSL_IA32_SSE2'], 'asm'), - ('win', 'x86_64', 'nasm', [], 'asm'), - ('win', 'aarch64', 'win64', [], 'S'), -] - -# NON_PERL_FILES enumerates assembly files that are not processed by the -# perlasm system. -NON_PERL_FILES = { - ('linux', 'arm'): [ - 'src/crypto/curve25519/asm/x25519-asm-arm.S', - 'src/crypto/poly1305/poly1305_arm_asm.S', - ], - ('linux', 'x86_64'): [ - 'src/crypto/hrss/asm/poly_rq_mul.S', - ], -} - PREFIX = None -EMBED_TEST_DATA = True def PathOf(x): @@ -100,7 +68,7 @@ def PrintVariableSection(self, out, name, files): out.write(' %s\\\n' % f) out.write('\n') - def WriteFiles(self, files, asm_outputs): + def WriteFiles(self, files): # New Android.bp format with open('sources.bp', 'w+') as blueprint: blueprint.write(self.header.replace('#', '//')) @@ -108,31 +76,35 @@ def WriteFiles(self, files, asm_outputs): # Separate out BCM files to allow different compilation rules (specific to Android FIPS) bcm_c_files = files['bcm_crypto'] non_bcm_c_files = [file for file in files['crypto'] if file not in bcm_c_files] - non_bcm_asm = self.FilterBcmAsm(asm_outputs, False) - bcm_asm = self.FilterBcmAsm(asm_outputs, True) + non_bcm_asm = self.FilterBcmAsm(files['crypto_asm'], False) + bcm_asm = self.FilterBcmAsm(files['crypto_asm'], True) - self.PrintDefaults(blueprint, 'libcrypto_sources', non_bcm_c_files, non_bcm_asm) - self.PrintDefaults(blueprint, 'libcrypto_bcm_sources', bcm_c_files, bcm_asm) + self.PrintDefaults(blueprint, 'libcrypto_sources', non_bcm_c_files, asm_files=non_bcm_asm) + self.PrintDefaults(blueprint, 'libcrypto_bcm_sources', bcm_c_files, asm_files=bcm_asm) self.PrintDefaults(blueprint, 'libssl_sources', files['ssl']) self.PrintDefaults(blueprint, 'bssl_sources', files['tool']) self.PrintDefaults(blueprint, 'boringssl_test_support_sources', files['test_support']) - self.PrintDefaults(blueprint, 'boringssl_crypto_test_sources', files['crypto_test']) + self.PrintDefaults(blueprint, 'boringssl_crypto_test_sources', files['crypto_test'], data=files['crypto_test_data']) self.PrintDefaults(blueprint, 'boringssl_ssl_test_sources', files['ssl_test']) + self.PrintDefaults(blueprint, 'libpki_sources', files['pki']) # Legacy Android.mk format, only used by Trusty in new branches with open('sources.mk', 'w+') as makefile: makefile.write(self.header) makefile.write('\n') self.PrintVariableSection(makefile, 'crypto_sources', files['crypto']) + self.PrintVariableSection(makefile, 'crypto_sources_asm', + files['crypto_asm']) - for ((osname, arch), asm_files) in asm_outputs: - if osname != 'linux': - continue - self.PrintVariableSection( - makefile, '%s_%s_sources' % (osname, arch), asm_files) - - def PrintDefaults(self, blueprint, name, files, asm_outputs={}): + def PrintDefaults(self, blueprint, name, files, asm_files=[], data=[]): """Print a cc_defaults section from a list of C files and optionally assembly outputs""" + if asm_files: + blueprint.write('\n') + blueprint.write('%s_asm = [\n' % name) + for f in sorted(asm_files): + blueprint.write(' "%s",\n' % f) + blueprint.write(']\n') + blueprint.write('\n') blueprint.write('cc_defaults {\n') blueprint.write(' name: "%s",\n' % name) @@ -140,21 +112,26 @@ def PrintDefaults(self, blueprint, name, files, asm_outputs={}): for f in sorted(files): blueprint.write(' "%s",\n' % f) blueprint.write(' ],\n') + if data: + blueprint.write(' data: [\n') + for f in sorted(data): + blueprint.write(' "%s",\n' % f) + blueprint.write(' ],\n') - if asm_outputs: + if asm_files: blueprint.write(' target: {\n') - for ((osname, arch), asm_files) in asm_outputs: - if osname != 'linux': - continue - if arch == 'aarch64': - arch = 'arm64' - - blueprint.write(' linux_%s: {\n' % arch) - blueprint.write(' srcs: [\n') - for f in sorted(asm_files): - blueprint.write(' "%s",\n' % f) - blueprint.write(' ],\n') - blueprint.write(' },\n') + # Only emit asm for Linux. On Windows, BoringSSL requires NASM, which is + # not available in AOSP. On Darwin, the assembly works fine, but it + # conflicts with Android's FIPS build. See b/294399371. + blueprint.write(' linux: {\n') + blueprint.write(' srcs: %s_asm,\n' % name) + blueprint.write(' },\n') + blueprint.write(' darwin: {\n') + blueprint.write(' cflags: ["-DOPENSSL_NO_ASM"],\n') + blueprint.write(' },\n') + blueprint.write(' windows: {\n') + blueprint.write(' cflags: ["-DOPENSSL_NO_ASM"],\n') + blueprint.write(' },\n') blueprint.write(' },\n') blueprint.write('}\n') @@ -163,7 +140,7 @@ def FilterBcmAsm(self, asm, want_bcm): """Filter a list of assembly outputs based on whether they belong in BCM Args: - asm: Assembly file lists to filter + asm: Assembly file list to filter want_bcm: If true then include BCM files, otherwise do not Returns: @@ -172,8 +149,7 @@ def FilterBcmAsm(self, asm, want_bcm): # TODO(https://crbug.com/boringssl/542): Rather than filtering by filename, # use the variable listed in the CMake perlasm line, available in # ExtractPerlAsmFromCMakeFile. - return [(archinfo, filter(lambda p: ("/crypto/fipsmodule/" in p) == want_bcm, files)) - for (archinfo, files) in asm] + return filter(lambda p: ("/crypto/fipsmodule/" in p) == want_bcm, asm) class AndroidCMake(object): @@ -196,15 +172,18 @@ def PrintVariableSection(self, out, name, files): out.write(' ${BORINGSSL_ROOT}%s\n' % f) out.write(')\n') - def WriteFiles(self, files, asm_outputs): + def WriteFiles(self, files): # The Android emulator uses a custom CMake buildsystem. # - # TODO(davidben): Move our various source lists into sources.cmake and have - # Android consume that directly. + # TODO(crbug.com/boringssl/542): Move our various source lists into + # sources.cmake and have Android consume that directly. with open('android-sources.cmake', 'w+') as out: out.write(self.header) self.PrintVariableSection(out, 'crypto_sources', files['crypto']) + self.PrintVariableSection(out, 'crypto_sources_asm', files['crypto_asm']) + self.PrintVariableSection(out, 'crypto_sources_nasm', + files['crypto_nasm']) self.PrintVariableSection(out, 'ssl_sources', files['ssl']) self.PrintVariableSection(out, 'tool_sources', files['tool']) self.PrintVariableSection(out, 'test_support_sources', @@ -213,10 +192,6 @@ def WriteFiles(self, files, asm_outputs): files['crypto_test']) self.PrintVariableSection(out, 'ssl_test_sources', files['ssl_test']) - for ((osname, arch), asm_files) in asm_outputs: - self.PrintVariableSection( - out, 'crypto_sources_%s_%s' % (osname, arch), asm_files) - class Bazel(object): """Bazel outputs files suitable for including in Bazel files.""" @@ -238,7 +213,7 @@ def PrintVariableSection(self, out, name, files): out.write(' "%s",\n' % PathOf(f)) out.write(']\n') - def WriteFiles(self, files, asm_outputs): + def WriteFiles(self, files): with open('BUILD.generated.bzl', 'w+') as out: out.write(self.header) @@ -251,30 +226,17 @@ def WriteFiles(self, files, asm_outputs): self.PrintVariableSection( out, 'crypto_internal_headers', files['crypto_internal_headers']) self.PrintVariableSection(out, 'crypto_sources', files['crypto']) + self.PrintVariableSection(out, 'crypto_sources_asm', files['crypto_asm']) + self.PrintVariableSection(out, 'crypto_sources_nasm', files['crypto_nasm']) + self.PrintVariableSection(out, 'pki_headers', files['pki_headers']) + self.PrintVariableSection( + out, 'pki_internal_headers', files['pki_internal_headers']) + self.PrintVariableSection(out, 'pki_sources', files['pki']) + self.PrintVariableSection(out, 'rust_bssl_sys', files['rust_bssl_sys']) + self.PrintVariableSection(out, 'rust_bssl_crypto', files['rust_bssl_crypto']) self.PrintVariableSection(out, 'tool_sources', files['tool']) self.PrintVariableSection(out, 'tool_headers', files['tool_headers']) - for ((osname, arch), asm_files) in asm_outputs: - self.PrintVariableSection( - out, 'crypto_sources_%s_%s' % (osname, arch), asm_files) - - # Generate combined source lists for gas and nasm. Consumers have a choice - # of using the per-platform ones or the combined ones. In the combined - # mode, Windows x86 and Windows x86_64 must still be special-cased, but - # otherwise all assembly files can be linked together. - out.write('\n') - out.write('crypto_sources_asm = []\n') - for (osname, arch, _, _, asm_ext) in OS_ARCH_COMBOS: - if asm_ext == 'S': - out.write('crypto_sources_asm.extend(crypto_sources_%s_%s)\n' % - (osname, arch)) - out.write('\n') - out.write('crypto_sources_nasm = []\n') - for (osname, arch, _, _, asm_ext) in OS_ARCH_COMBOS: - if asm_ext == 'asm': - out.write('crypto_sources_nasm.extend(crypto_sources_%s_%s)\n' % - (osname, arch)) - with open('BUILD.generated_tests.bzl', 'w+') as out: out.write(self.header) @@ -282,9 +244,8 @@ def WriteFiles(self, files, asm_outputs): for filename in sorted(files['test_support'] + files['test_support_headers'] + files['crypto_internal_headers'] + + files['pki_internal_headers'] + files['ssl_internal_headers']): - if os.path.basename(filename) == 'malloc.cc': - continue out.write(' "%s",\n' % PathOf(filename)) out.write(']\n') @@ -292,8 +253,12 @@ def WriteFiles(self, files, asm_outputs): self.PrintVariableSection(out, 'crypto_test_sources', files['crypto_test']) self.PrintVariableSection(out, 'ssl_test_sources', files['ssl_test']) + self.PrintVariableSection(out, 'pki_test_sources', + files['pki_test']) self.PrintVariableSection(out, 'crypto_test_data', files['crypto_test_data']) + self.PrintVariableSection(out, 'pki_test_data', + files['pki_test_data']) self.PrintVariableSection(out, 'urandom_test_sources', files['urandom_test']) @@ -312,21 +277,19 @@ def PrintVariableSection(self, out, name, files): out.write(' %s\\\n' % f) out.write('\n') - def WriteFiles(self, files, asm_outputs): + def WriteFiles(self, files): # Legacy Android.mk format with open('eureka.mk', 'w+') as makefile: makefile.write(self.header) self.PrintVariableSection(makefile, 'crypto_sources', files['crypto']) + self.PrintVariableSection(makefile, 'crypto_sources_asm', + files['crypto_asm']) + self.PrintVariableSection(makefile, 'crypto_sources_nasm', + files['crypto_nasm']) self.PrintVariableSection(makefile, 'ssl_sources', files['ssl']) self.PrintVariableSection(makefile, 'tool_sources', files['tool']) - for ((osname, arch), asm_files) in asm_outputs: - if osname != 'linux': - continue - self.PrintVariableSection( - makefile, '%s_%s_sources' % (osname, arch), asm_files) - class GN(object): @@ -342,30 +305,40 @@ def PrintVariableSection(self, out, name, files): out.write('\n') self.firstSection = False - out.write('%s = [\n' % name) - for f in sorted(files): - out.write(' "%s",\n' % f) - out.write(']\n') + if len(files) == 0: + out.write('%s = []\n' % name) + elif len(files) == 1: + out.write('%s = [ "%s" ]\n' % (name, files[0])) + else: + out.write('%s = [\n' % name) + for f in sorted(files): + out.write(' "%s",\n' % f) + out.write(']\n') - def WriteFiles(self, files, asm_outputs): + def WriteFiles(self, files): with open('BUILD.generated.gni', 'w+') as out: out.write(self.header) self.PrintVariableSection(out, 'crypto_sources', files['crypto'] + files['crypto_internal_headers']) - self.PrintVariableSection(out, 'crypto_headers', - files['crypto_headers']) + self.PrintVariableSection(out, 'crypto_sources_asm', files['crypto_asm']) + self.PrintVariableSection(out, 'crypto_sources_nasm', + files['crypto_nasm']) + self.PrintVariableSection(out, 'crypto_headers', files['crypto_headers']) + self.PrintVariableSection(out, 'rust_bssl_sys', files['rust_bssl_sys']) + self.PrintVariableSection(out, 'rust_bssl_crypto', + files['rust_bssl_crypto']) self.PrintVariableSection(out, 'ssl_sources', files['ssl'] + files['ssl_internal_headers']) self.PrintVariableSection(out, 'ssl_headers', files['ssl_headers']) + self.PrintVariableSection(out, 'pki_sources', files['pki']) + self.PrintVariableSection(out, 'pki_internal_headers', + files['pki_internal_headers']) + self.PrintVariableSection(out, 'pki_headers', files['pki_headers']) self.PrintVariableSection(out, 'tool_sources', files['tool'] + files['tool_headers']) - for ((osname, arch), asm_files) in asm_outputs: - self.PrintVariableSection( - out, 'crypto_sources_%s_%s' % (osname, arch), asm_files) - fuzzers = [os.path.splitext(os.path.basename(fuzzer))[0] for fuzzer in files['fuzz']] self.PrintVariableSection(out, 'fuzzers', fuzzers) @@ -381,7 +354,10 @@ def WriteFiles(self, files, asm_outputs): files['crypto_test']) self.PrintVariableSection(out, 'crypto_test_data', files['crypto_test_data']) + self.PrintVariableSection(out, 'pki_test_data', + files['pki_test_data']) self.PrintVariableSection(out, 'ssl_test_sources', files['ssl_test']) + self.PrintVariableSection(out, 'pki_test_sources', files['pki_test']) class GYP(object): @@ -398,7 +374,7 @@ def PrintVariableSection(self, out, name, files): out.write(' \'%s\',\n' % f) out.write(' ],\n') - def WriteFiles(self, files, asm_outputs): + def WriteFiles(self, files): with open('boringssl.gypi', 'w+') as gypi: gypi.write(self.header + '{\n \'variables\': {\n') @@ -408,10 +384,10 @@ def WriteFiles(self, files, asm_outputs): self.PrintVariableSection(gypi, 'boringssl_crypto_sources', files['crypto'] + files['crypto_headers'] + files['crypto_internal_headers']) - - for ((osname, arch), asm_files) in asm_outputs: - self.PrintVariableSection(gypi, 'boringssl_%s_%s_sources' % - (osname, arch), asm_files) + self.PrintVariableSection(gypi, 'boringssl_crypto_asm_sources', + files['crypto_asm']) + self.PrintVariableSection(gypi, 'boringssl_crypto_nasm_sources', + files['crypto_nasm']) gypi.write(' }\n}\n') @@ -421,7 +397,7 @@ def __init__(self): self.header = LicenseHeader("#") + R''' # This file is created by generate_build_files.py. Do not edit manually. -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.12) project(BoringSSL LANGUAGES C CXX) @@ -517,19 +493,12 @@ def PrintVariable(self, out, name, files): out.write(' %s\n' % PathOf(f)) out.write(')\n\n') - def WriteFiles(self, files, asm_outputs): + def WriteFiles(self, files): with open('CMakeLists.txt', 'w+') as cmake: cmake.write(self.header) - asm_sources = [] - nasm_sources = [] - for ((osname, arch), asm_files) in asm_outputs: - if (osname, arch) in (('win', 'x86'), ('win', 'x86_64')): - nasm_sources.extend(asm_files) - else: - asm_sources.extend(asm_files) - self.PrintVariable(cmake, 'CRYPTO_SOURCES_ASM', sorted(asm_sources)) - self.PrintVariable(cmake, 'CRYPTO_SOURCES_NASM', sorted(nasm_sources)) + self.PrintVariable(cmake, 'CRYPTO_SOURCES_ASM', files['crypto_asm']) + self.PrintVariable(cmake, 'CRYPTO_SOURCES_NASM', files['crypto_nasm']) cmake.write( R'''if(OPENSSL_ASM) @@ -548,7 +517,7 @@ def WriteFiles(self, files, asm_outputs): self.PrintExe(cmake, 'bssl', files['tool'], ['ssl', 'crypto']) cmake.write( -R'''if(NOT ANDROID) +R'''if(NOT CMAKE_SYSTEM_NAME STREQUAL "Android") find_package(Threads REQUIRED) target_link_libraries(crypto Threads::Threads) endif() @@ -560,28 +529,9 @@ def WriteFiles(self, files, asm_outputs): ''') class JSON(object): - def WriteFiles(self, files, asm_outputs): - sources = dict(files) - for ((osname, arch), asm_files) in asm_outputs: - sources['crypto_%s_%s' % (osname, arch)] = asm_files + def WriteFiles(self, files): with open('sources.json', 'w+') as f: - json.dump(sources, f, sort_keys=True, indent=2) - -def FindCMakeFiles(directory): - """Returns list of all CMakeLists.txt files recursively in directory.""" - cmakefiles = [] - - for (path, _, filenames) in os.walk(directory): - for filename in filenames: - if filename == 'CMakeLists.txt': - cmakefiles.append(os.path.join(path, filename)) - - return cmakefiles - -def OnlyFIPSFragments(path, dent, is_dir): - return is_dir or (path.startswith( - os.path.join('src', 'crypto', 'fipsmodule', '')) and - NoTests(path, dent, is_dir)) + json.dump(files, f, sort_keys=True, indent=2) def NoTestsNorFIPSFragments(path, dent, is_dir): return (NoTests(path, dent, is_dir) and @@ -595,14 +545,6 @@ def NoTests(path, dent, is_dir): return 'test.' not in dent -def OnlyTests(path, dent, is_dir): - """Filter function that can be passed to FindCFiles in order to remove - non-test sources.""" - if is_dir: - return dent != 'test' - return '_test.' in dent - - def AllFiles(path, dent, is_dir): """Filter function that can be passed to FindCFiles in order to include all sources.""" @@ -617,14 +559,6 @@ def NoTestRunnerFiles(path, dent, is_dir): return not is_dir or dent != 'runner' -def NotGTestSupport(path, dent, is_dir): - return 'gtest' not in dent and 'abi_test' not in dent - - -def SSLHeaderFiles(path, dent, is_dir): - return dent in ['ssl.h', 'tls1.h', 'ssl23.h', 'ssl3.h', 'dtls1.h', 'srtp.h'] - - def FindCFiles(directory, filter_func): """Recurses through directory and returns a list of paths to all the C source files that pass filter_func.""" @@ -646,6 +580,21 @@ def FindCFiles(directory, filter_func): return cfiles +def FindRustFiles(directory): + """Recurses through directory and returns a list of paths to all the Rust source + files.""" + rust_files = [] + + for (path, dirnames, filenames) in os.walk(directory): + for filename in filenames: + if not filename.endswith('.rs'): + continue + rust_files.append(os.path.join(path, filename)) + + rust_files.sort() + return rust_files + + def FindHeaderFiles(directory, filter_func): """Recurses through directory and returns a list of paths to all the header files that pass filter_func.""" hfiles = [] @@ -666,214 +615,61 @@ def FindHeaderFiles(directory, filter_func): return hfiles -def ExtractPerlAsmFromCMakeFile(cmakefile): - """Parses the contents of the CMakeLists.txt file passed as an argument and - returns a list of all the perlasm() directives found in the file.""" - perlasms = [] - with open(cmakefile) as f: - for line in f: - line = line.strip() - if not line.startswith('perlasm('): - continue - if not line.endswith(')'): - raise ValueError('Bad perlasm line in %s' % cmakefile) - # Remove "perlasm(" from start and ")" from end - params = line[8:-1].split() - if len(params) != 4: - raise ValueError('Bad perlasm line in %s' % cmakefile) - perlasms.append({ - 'arch': params[1], - 'output': os.path.join(os.path.dirname(cmakefile), params[2]), - 'input': os.path.join(os.path.dirname(cmakefile), params[3]), - }) - - return perlasms - - -def ReadPerlAsmOperations(): - """Returns a list of all perlasm() directives found in CMake config files in - src/.""" - perlasms = [] - cmakefiles = FindCMakeFiles('src') - - for cmakefile in cmakefiles: - perlasms.extend(ExtractPerlAsmFromCMakeFile(cmakefile)) - - return perlasms - - -def PerlAsm(output_filename, input_filename, perlasm_style, extra_args): - """Runs the a perlasm script and puts the output into output_filename.""" - base_dir = os.path.dirname(output_filename) - if not os.path.isdir(base_dir): - os.makedirs(base_dir) - subprocess.check_call( - ['perl', input_filename, perlasm_style] + extra_args + [output_filename]) - - -def WriteAsmFiles(perlasms): - """Generates asm files from perlasm directives for each supported OS x - platform combination.""" - asmfiles = {} - - for perlasm in perlasms: - for (osname, arch, perlasm_style, extra_args, asm_ext) in OS_ARCH_COMBOS: - if arch != perlasm['arch']: - continue - # TODO(https://crbug.com/boringssl/524): Now that we incorporate osname in - # the output filename, the asm files can just go in a single directory. - # For now, we keep them in target-specific directories to avoid breaking - # downstream scripts. - key = (osname, arch) - outDir = '%s-%s' % key - output = perlasm['output'] - if not output.startswith('src'): - raise ValueError('output missing src: %s' % output) - output = os.path.join(outDir, output[4:]) - output = '%s-%s.%s' % (output, osname, asm_ext) - PerlAsm(output, perlasm['input'], perlasm_style, extra_args) - asmfiles.setdefault(key, []).append(output) - - for (key, non_perl_asm_files) in NON_PERL_FILES.items(): - asmfiles.setdefault(key, []).extend(non_perl_asm_files) - - for files in asmfiles.values(): - files.sort() - - return asmfiles - - -def ExtractVariablesFromCMakeFile(cmakefile): - """Parses the contents of the CMakeLists.txt file passed as an argument and - returns a dictionary of exported source lists.""" - variables = {} - in_set_command = False - set_command = [] - with open(cmakefile) as f: - for line in f: - if '#' in line: - line = line[:line.index('#')] - line = line.strip() - - if not in_set_command: - if line.startswith('set('): - in_set_command = True - set_command = [] - elif line == ')': - in_set_command = False - if not set_command: - raise ValueError('Empty set command') - variables[set_command[0]] = set_command[1:] - else: - set_command.extend([c for c in line.split(' ') if c]) - - if in_set_command: - raise ValueError('Unfinished set command') - return variables +def PrefixWithSrc(files): + return ['src/' + x for x in files] def main(platforms): - cmake = ExtractVariablesFromCMakeFile(os.path.join('src', 'sources.cmake')) - crypto_c_files = (FindCFiles(os.path.join('src', 'crypto'), NoTestsNorFIPSFragments) + - FindCFiles(os.path.join('src', 'third_party', 'fiat'), NoTestsNorFIPSFragments)) - fips_fragments = FindCFiles(os.path.join('src', 'crypto', 'fipsmodule'), OnlyFIPSFragments) - ssl_source_files = FindCFiles(os.path.join('src', 'ssl'), NoTests) - tool_c_files = FindCFiles(os.path.join('src', 'tool'), NoTests) - tool_h_files = FindHeaderFiles(os.path.join('src', 'tool'), AllFiles) - - # BCM shared library C files - bcm_crypto_c_files = [ - os.path.join('src', 'crypto', 'fipsmodule', 'bcm.c') - ] - - # Generate err_data.c - with open('err_data.c', 'w+') as err_data: - subprocess.check_call(['go', 'run', 'err_data_generate.go'], - cwd=os.path.join('src', 'crypto', 'err'), - stdout=err_data) - crypto_c_files.append('err_data.c') - crypto_c_files.sort() - - test_support_c_files = FindCFiles(os.path.join('src', 'crypto', 'test'), - NotGTestSupport) - test_support_h_files = ( - FindHeaderFiles(os.path.join('src', 'crypto', 'test'), AllFiles) + - FindHeaderFiles(os.path.join('src', 'ssl', 'test'), NoTestRunnerFiles)) - - crypto_test_files = [] - if EMBED_TEST_DATA: - # Generate crypto_test_data.cc - with open('crypto_test_data.cc', 'w+') as out: - subprocess.check_call( - ['go', 'run', 'util/embed_test_data.go'] + cmake['CRYPTO_TEST_DATA'], - cwd='src', - stdout=out) - crypto_test_files += ['crypto_test_data.cc'] - - crypto_test_files += FindCFiles(os.path.join('src', 'crypto'), OnlyTests) - crypto_test_files += [ - 'src/crypto/test/abi_test.cc', - 'src/crypto/test/file_test_gtest.cc', - 'src/crypto/test/gtest_main.cc', - ] - # urandom_test.cc is in a separate binary so that it can be test PRNG - # initialisation. - crypto_test_files = [ - file for file in crypto_test_files - if not file.endswith('/urandom_test.cc') - ] - crypto_test_files.sort() - - ssl_test_files = FindCFiles(os.path.join('src', 'ssl'), OnlyTests) - ssl_test_files += [ - 'src/crypto/test/abi_test.cc', - 'src/crypto/test/gtest_main.cc', - ] - ssl_test_files.sort() - - urandom_test_files = [ - 'src/crypto/fipsmodule/rand/urandom_test.cc', - ] - - fuzz_c_files = FindCFiles(os.path.join('src', 'fuzz'), NoTests) + with open(os.path.join('src', 'gen', 'sources.json')) as f: + sources = json.load(f) - ssl_h_files = FindHeaderFiles(os.path.join('src', 'include', 'openssl'), - SSLHeaderFiles) + bssl_sys_files = FindRustFiles(os.path.join('src', 'rust', 'bssl-sys', 'src')) + bssl_crypto_files = FindRustFiles(os.path.join('src', 'rust', 'bssl-crypto', 'src')) - def NotSSLHeaderFiles(path, filename, is_dir): - return not SSLHeaderFiles(path, filename, is_dir) - crypto_h_files = FindHeaderFiles(os.path.join('src', 'include', 'openssl'), - NotSSLHeaderFiles) + fuzz_c_files = FindCFiles(os.path.join('src', 'fuzz'), NoTests) - ssl_internal_h_files = FindHeaderFiles(os.path.join('src', 'ssl'), NoTests) - crypto_internal_h_files = ( - FindHeaderFiles(os.path.join('src', 'crypto'), NoTests) + - FindHeaderFiles(os.path.join('src', 'third_party', 'fiat'), NoTests)) + # TODO(crbug.com/boringssl/542): generate_build_files.py historically reported + # all the assembly files as part of libcrypto. Merge them for now, but we + # should split them out later. + crypto = sorted(sources['bcm']['srcs'] + sources['crypto']['srcs']) + crypto_asm = sorted(sources['bcm']['asm'] + sources['crypto']['asm'] + + sources['test_support']['asm']) + crypto_nasm = sorted(sources['bcm']['nasm'] + sources['crypto']['nasm'] + + sources['test_support']['nasm']) files = { - 'bcm_crypto': bcm_crypto_c_files, - 'crypto': crypto_c_files, - 'crypto_headers': crypto_h_files, - 'crypto_internal_headers': crypto_internal_h_files, - 'crypto_test': crypto_test_files, - 'crypto_test_data': sorted('src/' + x for x in cmake['CRYPTO_TEST_DATA']), - 'fips_fragments': fips_fragments, + 'bcm_crypto': PrefixWithSrc(sources['bcm']['srcs']), + 'crypto': PrefixWithSrc(crypto), + 'crypto_asm': PrefixWithSrc(crypto_asm), + 'crypto_nasm': PrefixWithSrc(crypto_nasm), + 'crypto_headers': PrefixWithSrc(sources['crypto']['hdrs']), + 'crypto_internal_headers': + PrefixWithSrc(sources['crypto']['internal_hdrs']), + 'crypto_test': PrefixWithSrc(sources['crypto_test']['srcs']), + 'crypto_test_data': PrefixWithSrc(sources['crypto_test']['data']), + 'fips_fragments': PrefixWithSrc(sources['bcm']['internal_hdrs']), 'fuzz': fuzz_c_files, - 'ssl': ssl_source_files, - 'ssl_headers': ssl_h_files, - 'ssl_internal_headers': ssl_internal_h_files, - 'ssl_test': ssl_test_files, - 'tool': tool_c_files, - 'tool_headers': tool_h_files, - 'test_support': test_support_c_files, - 'test_support_headers': test_support_h_files, - 'urandom_test': urandom_test_files, + 'pki': PrefixWithSrc(sources['pki']['srcs']), + 'pki_headers': PrefixWithSrc(sources['pki']['hdrs']), + 'pki_internal_headers': PrefixWithSrc(sources['pki']['internal_hdrs']), + 'pki_test': PrefixWithSrc(sources['pki_test']['srcs']), + 'pki_test_data': PrefixWithSrc(sources['pki_test']['data']), + 'rust_bssl_crypto': bssl_crypto_files, + 'rust_bssl_sys': bssl_sys_files, + 'ssl': PrefixWithSrc(sources['ssl']['srcs']), + 'ssl_headers': PrefixWithSrc(sources['ssl']['hdrs']), + 'ssl_internal_headers': PrefixWithSrc(sources['ssl']['internal_hdrs']), + 'ssl_test': PrefixWithSrc(sources['ssl_test']['srcs']), + 'tool': PrefixWithSrc(sources['bssl']['srcs']), + 'tool_headers': PrefixWithSrc(sources['bssl']['internal_hdrs']), + 'test_support': PrefixWithSrc(sources['test_support']['srcs']), + 'test_support_headers': + PrefixWithSrc(sources['test_support']['internal_hdrs']), + 'urandom_test': PrefixWithSrc(sources['urandom_test']['srcs']), } - asm_outputs = sorted(WriteAsmFiles(ReadPerlAsmOperations()).items()) - for platform in platforms: - platform.WriteFiles(files, asm_outputs) + platform.WriteFiles(files) return 0 @@ -894,13 +690,8 @@ def NotSSLHeaderFiles(path, filename, is_dir): '|'.join(sorted(ALL_PLATFORMS.keys()))) parser.add_option('--prefix', dest='prefix', help='For Bazel, prepend argument to all source files') - parser.add_option( - '--embed_test_data', type='choice', dest='embed_test_data', - action='store', default="true", choices=["true", "false"], - help='For Bazel or GN, don\'t embed data files in crypto_test_data.cc') options, args = parser.parse_args(sys.argv[1:]) PREFIX = options.prefix - EMBED_TEST_DATA = (options.embed_test_data == "true") if not args: parser.print_help() diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/make_errors.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/make_errors.go index 874a001b29433..01506f0f9aeeb 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/make_errors.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/make_errors.go @@ -59,6 +59,11 @@ func getLibraryInfo(lib string) libraryInfo { info.sourceDirs = append(info.sourceDirs, filepath.Join("crypto", "hpke")) } + if lib == "x509v3" { + info.headerName = "x509v3_errors.h" + info.sourceDirs = append(info.sourceDirs, filepath.Join("crypto", "x509")) + } + return info } @@ -321,7 +326,7 @@ func assignNewValues(assignments map[string]int, reserved int) { } } -func handleDeclareMacro(line, join, macroName string, m map[string]int) { +func handleDeclareMacro(line, prefix, join, macroName string, m map[string]int) { if i := strings.Index(line, macroName); i >= 0 { contents := line[i+len(macroName):] if i := strings.Index(contents, ")"); i >= 0 { @@ -333,9 +338,11 @@ func handleDeclareMacro(line, join, macroName string, m map[string]int) { if len(args) != 2 { panic("Bad macro line: " + line) } - token := args[0] + join + args[1] - if _, ok := m[token]; !ok { - m[token] = -1 + if args[0] == prefix { + token := args[0] + join + args[1] + if _, ok := m[token]; !ok { + m[token] = -1 + } } } } @@ -354,7 +361,7 @@ func addReasons(reasons map[string]int, filename, prefix string) error { for scanner.Scan() { line := scanner.Text() - handleDeclareMacro(line, "_R_", "OPENSSL_DECLARE_ERROR_REASON(", reasons) + handleDeclareMacro(line, prefix, "_R_", "OPENSSL_DECLARE_ERROR_REASON(", reasons) for len(line) > 0 { i := strings.Index(line, prefix+"_") diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/pregenerate/build.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/pregenerate/build.go new file mode 100644 index 0000000000000..4d8bbcd63f285 --- /dev/null +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/pregenerate/build.go @@ -0,0 +1,313 @@ +// Copyright (c) 2024, Google Inc. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package main + +import ( + "bytes" + "cmp" + "encoding/json" + "fmt" + "path" + "path/filepath" + "slices" + "strings" + + "boringssl.googlesource.com/boringssl/util/build" +) + +// An InputTarget is a build target with build inputs that still need to be +// pregenerated. All file lists in InputTarget are interpreted with glob +// patterns as in filepath.Glob. +type InputTarget struct { + build.Target + // ErrData contains a list of errordata files to combine into err_data.c. + ErrData []string `json:"err_data,omitempty"` + // The following fields define perlasm sources for the corresponding + // architecture. + PerlasmAarch64 []PerlasmSource `json:"perlasm_aarch64,omitempty"` + PerlasmArm []PerlasmSource `json:"perlasm_arm,omitempty"` + PerlasmX86 []PerlasmSource `json:"perlasm_x86,omitempty"` + PerlasmX86_64 []PerlasmSource `json:"perlasm_x86_64,omitempty"` +} + +type PerlasmSource struct { + // Src the path to the input perlasm file. + Src string `json:"src"` + // Dst, if not empty, is base name of the destination file. If empty, this + // is determined from Src by default. It should be overriden if a single + // source file generates multiple functions (e.g. SHA-256 vs SHA-512) or + // multiple architectures (e.g. the "armx" files). + Dst string `json:"dst,omitempty"` + // Args is a list of extra parameters to pass to the script. + Args []string `json:"args,omitempty"` +} + +// Pregenerate converts an input target to an output target. It returns the +// result alongside a list of tasks that must be run to build the referenced +// files. +func (in *InputTarget) Pregenerate(name string) (out build.Target, tasks []Task, err error) { + // Expand wildcards. + out.Srcs, err = glob(in.Srcs) + if err != nil { + return + } + out.Hdrs, err = glob(in.Hdrs) + if err != nil { + return + } + out.InternalHdrs, err = glob(in.InternalHdrs) + if err != nil { + return + } + out.Asm, err = glob(in.Asm) + if err != nil { + return + } + out.Nasm, err = glob(in.Nasm) + if err != nil { + return + } + out.Data, err = glob(in.Data) + if err != nil { + return + } + + addTask := func(list *[]string, t Task) { + tasks = append(tasks, t) + *list = append(*list, t.Destination()) + } + + if len(in.ErrData) != 0 { + var inputs []string + inputs, err = glob(in.ErrData) + if err != nil { + return + } + addTask(&out.Srcs, &ErrDataTask{TargetName: name, Inputs: inputs}) + } + + addPerlasmTask := func(list *[]string, p *PerlasmSource, fileSuffix string, args []string) { + dst := p.Dst + if len(p.Dst) == 0 { + dst = strings.TrimSuffix(path.Base(p.Src), ".pl") + } + dst = path.Join("gen", name, dst+fileSuffix) + args = append(slices.Clone(args), p.Args...) + addTask(list, &PerlasmTask{Src: p.Src, Dst: dst, Args: args}) + } + + for _, p := range in.PerlasmAarch64 { + addPerlasmTask(&out.Asm, &p, "-apple.S", []string{"ios64"}) + addPerlasmTask(&out.Asm, &p, "-linux.S", []string{"linux64"}) + addPerlasmTask(&out.Asm, &p, "-win.S", []string{"win64"}) + } + for _, p := range in.PerlasmArm { + addPerlasmTask(&out.Asm, &p, "-linux.S", []string{"linux32"}) + } + for _, p := range in.PerlasmX86 { + addPerlasmTask(&out.Asm, &p, "-apple.S", []string{"macosx", "-fPIC", "-DOPENSSL_IA32_SSE2"}) + addPerlasmTask(&out.Asm, &p, "-linux.S", []string{"elf", "-fPIC", "-DOPENSSL_IA32_SSE2"}) + addPerlasmTask(&out.Nasm, &p, "-win.asm", []string{"win32n", "-fPIC", "-DOPENSSL_IA32_SSE2"}) + } + for _, p := range in.PerlasmX86_64 { + addPerlasmTask(&out.Asm, &p, "-apple.S", []string{"macosx"}) + addPerlasmTask(&out.Asm, &p, "-linux.S", []string{"elf"}) + addPerlasmTask(&out.Nasm, &p, "-win.asm", []string{"nasm"}) + } + + // Re-sort the modified fields. + slices.Sort(out.Srcs) + slices.Sort(out.Asm) + slices.Sort(out.Nasm) + + return +} + +func glob(paths []string) ([]string, error) { + var ret []string + for _, path := range paths { + if !strings.ContainsRune(path, '*') { + ret = append(ret, path) + continue + } + matches, err := filepath.Glob(path) + if err != nil { + return nil, err + } + if len(matches) == 0 { + return nil, fmt.Errorf("glob matched no files: %q", path) + } + // Switch from Windows to POSIX paths. + for _, match := range matches { + ret = append(ret, strings.ReplaceAll(match, "\\", "/")) + } + } + slices.Sort(ret) + return ret, nil +} + +func sortedKeys[K cmp.Ordered, V any](m map[K]V) []K { + keys := make([]K, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + slices.Sort(keys) + return keys +} + +func writeHeader(b *bytes.Buffer, comment string) { + fmt.Fprintf(b, "%s Copyright (c) 2024, Google Inc.\n", comment) + fmt.Fprintf(b, "%s\n", comment) + fmt.Fprintf(b, "%s Permission to use, copy, modify, and/or distribute this software for any\n", comment) + fmt.Fprintf(b, "%s purpose with or without fee is hereby granted, provided that the above\n", comment) + fmt.Fprintf(b, "%s copyright notice and this permission notice appear in all copies.\n", comment) + fmt.Fprintf(b, "%s\n", comment) + fmt.Fprintf(b, "%s THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n", comment) + fmt.Fprintf(b, "%s WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n", comment) + fmt.Fprintf(b, "%s MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\n", comment) + fmt.Fprintf(b, "%s SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n", comment) + fmt.Fprintf(b, "%s WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION\n", comment) + fmt.Fprintf(b, "%s OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN\n", comment) + fmt.Fprintf(b, "%s CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n", comment) + fmt.Fprintf(b, "%s\n", comment) + fmt.Fprintf(b, "%s Generated by go ./util/pregenerate. Do not edit manually.\n", comment) +} + +func buildVariablesTask(targets map[string]build.Target, dst, comment string, writeVariable func(b *bytes.Buffer, name string, val []string)) Task { + return NewSimpleTask(dst, func() ([]byte, error) { + var b bytes.Buffer + writeHeader(&b, comment) + + for _, name := range sortedKeys(targets) { + target := targets[name] + if len(target.Srcs) != 0 { + writeVariable(&b, name+"_sources", target.Srcs) + } + if len(target.Hdrs) != 0 { + writeVariable(&b, name+"_headers", target.Hdrs) + } + if len(target.InternalHdrs) != 0 { + writeVariable(&b, name+"_internal_headers", target.InternalHdrs) + } + if len(target.Asm) != 0 { + writeVariable(&b, name+"_sources_asm", target.Asm) + } + if len(target.Nasm) != 0 { + writeVariable(&b, name+"_sources_nasm", target.Nasm) + } + if len(target.Data) != 0 { + writeVariable(&b, name+"_data", target.Data) + } + } + + return b.Bytes(), nil + }) +} + +func writeBazelVariable(b *bytes.Buffer, name string, val []string) { + fmt.Fprintf(b, "\n%s = [\n", name) + for _, v := range val { + fmt.Fprintf(b, " %q,\n", v) + } + fmt.Fprintf(b, "]\n") +} + +func writeCMakeVariable(b *bytes.Buffer, name string, val []string) { + fmt.Fprintf(b, "\nset(\n") + fmt.Fprintf(b, " %s\n\n", strings.ToUpper(name)) + for _, v := range val { + fmt.Fprintf(b, " %s\n", v) + } + fmt.Fprintf(b, ")\n") +} + +func writeMakeVariable(b *bytes.Buffer, name string, val []string) { + fmt.Fprintf(b, "\n%s := \\\n", name) + for i, v := range val { + if i == len(val)-1 { + fmt.Fprintf(b, " %s\n", v) + } else { + fmt.Fprintf(b, " %s \\\n", v) + } + } +} + +func writeGNVariable(b *bytes.Buffer, name string, val []string) { + // Bazel and GN have the same syntax similar syntax. + writeBazelVariable(b, name, val) +} + +func jsonTask(targets map[string]build.Target, dst string) Task { + return NewSimpleTask(dst, func() ([]byte, error) { + return json.MarshalIndent(targets, "", " ") + }) +} + +func soongTask(targets map[string]build.Target, dst string) Task { + return NewSimpleTask(dst, func() ([]byte, error) { + var b bytes.Buffer + writeHeader(&b, "//") + + writeAttribute := func(indent, name string, val []string) { + fmt.Fprintf(&b, "%s%s: [\n", indent, name) + for _, v := range val { + fmt.Fprintf(&b, "%s %q,\n", indent, v) + } + fmt.Fprintf(&b, "%s],\n", indent) + + } + + for _, name := range sortedKeys(targets) { + target := targets[name] + fmt.Fprintf(&b, "\ncc_defaults {\n") + fmt.Fprintf(&b, " name: %q\n", "boringssl_"+name+"_sources") + if len(target.Srcs) != 0 { + writeAttribute(" ", "srcs", target.Srcs) + } + if len(target.Data) != 0 { + writeAttribute(" ", "data", target.Data) + } + if len(target.Asm) != 0 { + fmt.Fprintf(&b, " target: {\n") + // Only emit asm for Linux. On Windows, BoringSSL requires NASM, which is + // not available in AOSP. On Darwin, the assembly works fine, but it + // conflicts with Android's FIPS build. See b/294399371. + fmt.Fprintf(&b, " linux: {\n") + writeAttribute(" ", "srcs", target.Asm) + fmt.Fprintf(&b, " },\n") + fmt.Fprintf(&b, " darwin: {\n") + fmt.Fprintf(&b, " cflags: [\"-DOPENSSL_NO_ASM\"],\n") + fmt.Fprintf(&b, " },\n") + fmt.Fprintf(&b, " windows: {\n") + fmt.Fprintf(&b, " cflags: [\"-DOPENSSL_NO_ASM\"],\n") + fmt.Fprintf(&b, " },\n") + fmt.Fprintf(&b, " },\n") + } + fmt.Fprintf(&b, "},\n") + } + + return b.Bytes(), nil + }) +} + +func MakeBuildFiles(targets map[string]build.Target) []Task { + // TODO(crbug.com/boringssl/542): Generate the build files for the other + // types as well. + return []Task{ + buildVariablesTask(targets, "gen/sources.bzl", "#", writeBazelVariable), + buildVariablesTask(targets, "gen/sources.cmake", "#", writeCMakeVariable), + jsonTask(targets, "gen/sources.json"), + } +} diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/pregenerate/err_data.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/pregenerate/err_data.go new file mode 100644 index 0000000000000..8d89d99d4222a --- /dev/null +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/pregenerate/err_data.go @@ -0,0 +1,257 @@ +// Copyright (c) 2015, Google Inc. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package main + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "os" + "path" + "sort" + "strconv" +) + +// libraryNames must be kept in sync with the enum in err.h. The generated code +// will contain static assertions to enforce this. +var libraryNames = []string{ + "NONE", + "SYS", + "BN", + "RSA", + "DH", + "EVP", + "BUF", + "OBJ", + "PEM", + "DSA", + "X509", + "ASN1", + "CONF", + "CRYPTO", + "EC", + "SSL", + "BIO", + "PKCS7", + "PKCS8", + "X509V3", + "RAND", + "ENGINE", + "OCSP", + "UI", + "COMP", + "ECDSA", + "ECDH", + "HMAC", + "DIGEST", + "CIPHER", + "HKDF", + "TRUST_TOKEN", + "USER", +} + +// stringList is a map from uint32 -> string which can output data for a sorted +// list as C literals. +type stringList struct { + // entries is an array of keys and offsets into |stringData|. The + // offsets are in the bottom 15 bits of each uint32 and the key is the + // top 17 bits. + entries []uint32 + // internedStrings contains the same strings as are in |stringData|, + // but allows for easy deduplication. It maps a string to its offset in + // |stringData|. + internedStrings map[string]uint32 + stringData []byte +} + +func newStringList() *stringList { + return &stringList{ + internedStrings: make(map[string]uint32), + } +} + +// offsetMask is the bottom 15 bits. It's a mask that selects the offset from a +// uint32 in entries. +const offsetMask = 0x7fff + +func (st *stringList) Add(key uint32, value string) error { + if key&offsetMask != 0 { + return errors.New("need bottom 15 bits of the key for the offset") + } + offset, ok := st.internedStrings[value] + if !ok { + offset = uint32(len(st.stringData)) + if offset&offsetMask != offset { + return errors.New("stringList overflow") + } + st.stringData = append(st.stringData, []byte(value)...) + st.stringData = append(st.stringData, 0) + st.internedStrings[value] = offset + } + + for _, existing := range st.entries { + if existing>>15 == key>>15 { + panic("duplicate entry") + } + } + st.entries = append(st.entries, key|offset) + return nil +} + +func (st *stringList) buildList() []uint32 { + sort.Slice(st.entries, func(i, j int) bool { return (st.entries[i] >> 15) < (st.entries[j] >> 15) }) + return st.entries +} + +type stringWriter interface { + io.Writer + WriteString(string) (int, error) +} + +func (st *stringList) WriteTo(out stringWriter, name string) { + list := st.buildList() + values := "kOpenSSL" + name + "Values" + out.WriteString("const uint32_t " + values + "[] = {\n") + for _, v := range list { + fmt.Fprintf(out, " 0x%x,\n", v) + } + out.WriteString("};\n\n") + out.WriteString("const size_t " + values + "Len = sizeof(" + values + ") / sizeof(" + values + "[0]);\n\n") + + stringData := "kOpenSSL" + name + "StringData" + out.WriteString("const char " + stringData + "[] =\n \"") + for i, c := range st.stringData { + if c == 0 { + out.WriteString("\\0\"\n \"") + continue + } + out.Write(st.stringData[i : i+1]) + } + out.WriteString("\";\n\n") +} + +type errorData struct { + reasons *stringList + libraryMap map[string]uint32 +} + +func (e *errorData) readErrorDataFile(filename string) error { + inFile, err := os.Open(filename) + if err != nil { + return err + } + defer inFile.Close() + + scanner := bufio.NewScanner(inFile) + comma := []byte(",") + + lineNo := 0 + for scanner.Scan() { + lineNo++ + + line := scanner.Bytes() + if len(line) == 0 { + continue + } + parts := bytes.Split(line, comma) + if len(parts) != 3 { + return fmt.Errorf("bad line %d in %s: found %d values but want 3", lineNo, filename, len(parts)) + } + libNum, ok := e.libraryMap[string(parts[0])] + if !ok { + return fmt.Errorf("bad line %d in %s: unknown library", lineNo, filename) + } + if libNum >= 64 { + return fmt.Errorf("bad line %d in %s: library value too large", lineNo, filename) + } + key, err := strconv.ParseUint(string(parts[1]), 10 /* base */, 32 /* bit size */) + if err != nil { + return fmt.Errorf("bad line %d in %s: %s", lineNo, filename, err) + } + if key >= 2048 { + return fmt.Errorf("bad line %d in %s: key too large", lineNo, filename) + } + value := string(parts[2]) + + listKey := libNum<<26 | uint32(key)<<15 + + err = e.reasons.Add(listKey, value) + if err != nil { + return err + } + } + + return scanner.Err() +} + +type ErrDataTask struct { + TargetName string + Inputs []string +} + +func (t *ErrDataTask) Destination() string { + return path.Join("gen", t.TargetName, "err_data.c") +} + +func (t *ErrDataTask) Run() ([]byte, error) { + e := &errorData{ + reasons: newStringList(), + libraryMap: make(map[string]uint32), + } + for i, name := range libraryNames { + e.libraryMap[name] = uint32(i) + 1 + } + + for _, input := range t.Inputs { + if err := e.readErrorDataFile(input); err != nil { + return nil, err + } + } + + var out bytes.Buffer + out.WriteString(`/* Copyright (c) 2015, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + + /* This file was generated by go run ./util/pregenerate. */ + +#include +#include + +#include + +`) + + for i, name := range libraryNames { + fmt.Fprintf(&out, "static_assert(ERR_LIB_%s == %d, \"library value changed\");\n", name, i+1) + } + fmt.Fprintf(&out, "static_assert(ERR_NUM_LIBS == %d, \"number of libraries changed\");\n", len(libraryNames)+1) + out.WriteString("\n") + + e.reasons.WriteTo(&out, "Reason") + return out.Bytes(), nil +} diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/pregenerate/pregenerate.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/pregenerate/pregenerate.go new file mode 100644 index 0000000000000..1a3710ea82a91 --- /dev/null +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/pregenerate/pregenerate.go @@ -0,0 +1,223 @@ +// Copyright (c) 2024, Google Inc. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +// pregenerate manages generated files in BoringSSL +package main + +import ( + "bytes" + "encoding/json" + "errors" + "flag" + "fmt" + "os" + "path/filepath" + "runtime" + "slices" + "strings" + "sync" + + "boringssl.googlesource.com/boringssl/util/build" +) + +var ( + check = flag.Bool("check", false, "Check whether any files need to be updated, without actually updating them") + numWorkers = flag.Int("num-workers", runtime.NumCPU(), "Runs the given number of workers") + dryRun = flag.Bool("dry-run", false, "Skip actually writing any files") + perlPath = flag.String("perl", "perl", "Path to the perl command") + list = flag.Bool("list", false, "List all generated files, rather than actually run them") +) + +func runTask(t Task) error { + expected, err := t.Run() + if err != nil { + return err + } + + dst := t.Destination() + dstPath := filepath.FromSlash(dst) + if *check { + actual, err := os.ReadFile(dstPath) + if err != nil { + if os.IsNotExist(err) { + err = errors.New("missing file") + } + return err + } + + if !bytes.Equal(expected, actual) { + return errors.New("file out of date") + } + return nil + } + + if *dryRun { + fmt.Printf("Would write %d bytes to %q\n", len(expected), dst) + return nil + } + + if err := os.MkdirAll(filepath.Dir(dstPath), 0777); err != nil { + return err + } + return os.WriteFile(dstPath, expected, 0666) +} + +type taskError struct { + dst string + err error +} + +func worker(taskChan <-chan Task, errorChan chan<- taskError, wg *sync.WaitGroup) { + defer wg.Done() + for t := range taskChan { + if err := runTask(t); err != nil { + errorChan <- taskError{t.Destination(), err} + } + } +} + +func run() error { + if _, err := os.Stat("BUILDING.md"); err != nil { + return fmt.Errorf("must be run from BoringSSL source root") + } + + buildJSON, err := os.ReadFile("build.json") + if err != nil { + return err + } + + // Remove comments. For now, just do a very basic preprocessing step. If + // needed, we can switch to something well-defined like one of the many + // dozen different extended JSONs like JSON5. + lines := bytes.Split(buildJSON, []byte("\n")) + for i := range lines { + if idx := bytes.Index(lines[i], []byte("//")); idx >= 0 { + lines[i] = lines[i][:idx] + } + } + buildJSON = bytes.Join(lines, []byte("\n")) + + var targetsIn map[string]InputTarget + if err := json.Unmarshal(buildJSON, &targetsIn); err != nil { + return fmt.Errorf("error decoding build config: %s", err) + } + + var tasks []Task + targetsOut := make(map[string]build.Target) + for name, targetIn := range targetsIn { + targetOut, targetTasks, err := targetIn.Pregenerate(name) + if err != nil { + return err + } + targetsOut[name] = targetOut + tasks = append(tasks, targetTasks...) + } + + tasks = append(tasks, MakeBuildFiles(targetsOut)...) + tasks = append(tasks, NewSimpleTask("gen/README.md", func() ([]byte, error) { + return []byte(readme), nil + })) + + // Filter tasks by command-line argument. + if args := flag.Args(); len(args) != 0 { + var filtered []Task + for _, t := range tasks { + dst := t.Destination() + for _, arg := range args { + if strings.Contains(dst, arg) { + filtered = append(filtered, t) + break + } + } + } + tasks = filtered + } + + if *list { + paths := make([]string, len(tasks)) + for i, t := range tasks { + paths[i] = t.Destination() + } + slices.Sort(paths) + for _, p := range paths { + fmt.Println(p) + } + return nil + } + + // Schedule tasks in parallel. Perlasm benefits from running in parallel. The + // others likely do not, but it is simpler to parallelize them all. + var wg sync.WaitGroup + taskChan := make(chan Task, *numWorkers) + errorChan := make(chan taskError, *numWorkers) + for i := 0; i < *numWorkers; i++ { + wg.Add(1) + go worker(taskChan, errorChan, &wg) + } + + go func() { + for _, t := range tasks { + taskChan <- t + } + close(taskChan) + wg.Wait() + close(errorChan) + }() + + var failed bool + for err := range errorChan { + fmt.Fprintf(os.Stderr, "Error in file %q: %s\n", err.dst, err.err) + failed = true + } + if failed { + return errors.New("some files had errors") + } + return nil +} + +func main() { + flag.Parse() + if err := run(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %s\n", err) + os.Exit(1) + } +} + +const readme = `# Pre-generated files + +This directory contains a number of pre-generated build artifacts. To simplify +downstream builds, they are checked into the repository, rather than dynamically +generated as part of the build. + +When developing on BoringSSL, if any inputs to these files are modified, callers +must run the following command to update the generated files: + + go run ./util/pregenerate + +To check that files are up-to-date without updating files, run: + + go run ./util/pregenerate -check + +This is run on CI to ensure the generated files remain up-to-date. + +To speed up local iteration, the tool accepts additional arguments to filter the +files generated. For example, if editing ` + "`aesni-x86_64.pl`" + `, this +command will only update files with "aesni-x86_64" as a substring. + + go run ./util/pregenerate aesni-x86_64 + +For convenience, all files in this directory, including this README, are managed +by the tool. This means the whole directory may be deleted and regenerated from +scratch at any time. +` diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/pregenerate/task.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/pregenerate/task.go new file mode 100644 index 0000000000000..f04fc43d8a1f4 --- /dev/null +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/pregenerate/task.go @@ -0,0 +1,82 @@ +// Copyright (c) 2024, Google Inc. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package main + +import ( + "bytes" + "os" + "os/exec" + "path" + "path/filepath" +) + +type Task interface { + // Destination returns the destination path for this task, using forward + // slashes and relative to the source directory. That is, use the "path" + // package, not "path/filepath". + Destination() string + + // Run computes the output for this task. It should be written to the + // destination path. + Run() ([]byte, error) +} + +type SimpleTask struct { + Dst string + RunFunc func() ([]byte, error) +} + +func (t *SimpleTask) Destination() string { return t.Dst } +func (t *SimpleTask) Run() ([]byte, error) { return t.RunFunc() } + +func NewSimpleTask(dst string, runFunc func() ([]byte, error)) *SimpleTask { + return &SimpleTask{Dst: dst, RunFunc: runFunc} +} + +type PerlasmTask struct { + Src, Dst string + Args []string +} + +func (t *PerlasmTask) Destination() string { return t.Dst } +func (t *PerlasmTask) Run() ([]byte, error) { + base := path.Base(t.Dst) + out, err := os.CreateTemp("", "*."+base) + if err != nil { + return nil, err + } + defer os.Remove(out.Name()) + + args := make([]string, 0, 2+len(t.Args)) + args = append(args, filepath.FromSlash(t.Src)) + args = append(args, t.Args...) + args = append(args, out.Name()) + cmd := exec.Command(*perlPath, args...) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err := cmd.Run(); err != nil { + return nil, err + } + + data, err := os.ReadFile(out.Name()) + if err != nil { + return nil, err + } + + // On Windows, perl emits CRLF line endings. Normalize this so that the tool + // can be run on Windows too. + data = bytes.ReplaceAll(data, []byte("\r\n"), []byte("\n")) + return data, nil +} diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/read_symbols.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/read_symbols.go index 1d8ec85955468..c5024f4737d4e 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/read_symbols.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/read_symbols.go @@ -142,7 +142,7 @@ func main() { break } } - if skip || isCXXSymbol(s) || strings.HasPrefix(s, "__real@") || strings.HasPrefix(s, "__x86.get_pc_thunk.") { + if skip || isCXXSymbol(s) || strings.HasPrefix(s, "__real@") || strings.HasPrefix(s, "__x86.get_pc_thunk.") || strings.HasPrefix(s, "DW.") { continue } if _, err := fmt.Fprintln(out, s); err != nil { diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/run_android_tests.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/run_android_tests.go index ce878195fc412..ea7e4247e06fd 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/run_android_tests.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/run_android_tests.go @@ -19,6 +19,7 @@ package main import ( "bufio" "bytes" + "encoding/json" "errors" "flag" "fmt" @@ -30,6 +31,7 @@ import ( "strconv" "strings" + "boringssl.googlesource.com/boringssl/util/build" "boringssl.googlesource.com/boringssl/util/testconfig" ) @@ -149,7 +151,7 @@ func goTool(args ...string) error { targetPrefix = fmt.Sprintf("aarch64-linux-android%d-", *apiLevel) cmd.Env = append(cmd.Env, "GOARCH=arm64") default: - fmt.Errorf("unknown Android ABI: %q", *abi) + return fmt.Errorf("unknown Android ABI: %q", *abi) } // Go's Android support requires cgo and compilers from the NDK. See @@ -288,6 +290,17 @@ func main() { os.Exit(1) } + targetsJSON, err := os.ReadFile("gen/sources.json") + if err != nil { + fmt.Printf("Error reading sources.json: %s.\n", err) + os.Exit(1) + } + var targets map[string]build.Target + if err := json.Unmarshal(targetsJSON, &targets); err != nil { + fmt.Printf("Error reading sources.json: %s.\n", err) + os.Exit(1) + } + // Clear the target directory. if err := adb("shell", "rm -Rf /data/local/tmp/boringssl-tmp"); err != nil { fmt.Printf("Failed to clear target directory: %s\n", err) @@ -309,6 +322,9 @@ func main() { "util/all_tests.json", "BUILDING.md", ) + for _, target := range targets { + files = append(files, target.Data...) + } tests, err := testconfig.ParseTestConfig("util/all_tests.json") if err != nil { @@ -338,28 +354,6 @@ func main() { if enableSSLTests() { binaries = append(binaries, "ssl/test/bssl_shim") - files = append(files, - "BUILDING.md", - "ssl/test/runner/cert.pem", - "ssl/test/runner/channel_id_key.pem", - "ssl/test/runner/ecdsa_p224_cert.pem", - "ssl/test/runner/ecdsa_p224_key.pem", - "ssl/test/runner/ecdsa_p256_cert.pem", - "ssl/test/runner/ecdsa_p256_key.pem", - "ssl/test/runner/ecdsa_p384_cert.pem", - "ssl/test/runner/ecdsa_p384_key.pem", - "ssl/test/runner/ecdsa_p521_cert.pem", - "ssl/test/runner/ecdsa_p521_key.pem", - "ssl/test/runner/ed25519_cert.pem", - "ssl/test/runner/ed25519_key.pem", - "ssl/test/runner/key.pem", - "ssl/test/runner/rsa_1024_cert.pem", - "ssl/test/runner/rsa_1024_key.pem", - "ssl/test/runner/rsa_chain_cert.pem", - "ssl/test/runner/rsa_chain_key.pem", - "util/all_tests.json", - ) - fmt.Printf("Building runner...\n") if err := goTool("test", "-c", "-o", filepath.Join(tmpDir, "ssl/test/runner/runner"), "./ssl/test/runner/"); err != nil { fmt.Printf("Error building runner: %s\n", err) @@ -368,15 +362,16 @@ func main() { } var libraries []string - if _, err := os.Stat(filepath.Join(*buildDir, "crypto/libcrypto.so")); err == nil { + if _, err := os.Stat(filepath.Join(*buildDir, "libcrypto.so")); err == nil { libraries = []string{ "libboringssl_gtest.so", - "crypto/libcrypto.so", - "decrepit/libdecrepit.so", - "ssl/libssl.so", + "libcrypto.so", + "libdecrepit.so", + "libpki.so", + "libssl.so", } } else if !os.IsNotExist(err) { - fmt.Printf("Failed to stat crypto/libcrypto.so: %s\n", err) + fmt.Printf("Failed to stat libcrypto.so: %s\n", err) os.Exit(1) } diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/testconfig/testconfig.go b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/testconfig/testconfig.go index 1efcab11c8b38..c3247410be7c9 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/testconfig/testconfig.go +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/testconfig/testconfig.go @@ -23,6 +23,7 @@ type Test struct { Cmd []string `json:"cmd"` Env []string `json:"env"` SkipSDE bool `json:"skip_sde"` + Shard bool `json:"shard"` } func ParseTestConfig(filename string) ([]Test, error) { diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/util.bzl b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/util.bzl new file mode 100644 index 0000000000000..434efc7aaa8eb --- /dev/null +++ b/Source/ThirdParty/libwebrtc/Source/third_party/boringssl/src/util/util.bzl @@ -0,0 +1,299 @@ +# Copyright (c) 2024, Google Inc. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") + +# Configure C, C++, and common flags for GCC-compatible toolchains. +# +# TODO(davidben): Can we remove some of these? In Bazel, are warnings the +# toolchain or project's responsibility? -fno-common did not become default +# until https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85678. +gcc_copts = [ + # This list of warnings should match those in the top-level CMakeLists.txt. + "-Wall", + "-Werror", + "-Wformat=2", + "-Wsign-compare", + "-Wmissing-field-initializers", + "-Wwrite-strings", + "-Wshadow", + "-fno-common", +] + +gcc_copts_cxx = [ + "-Wmissing-declarations", +] + +gcc_copts_c = [ + "-Wmissing-prototypes", + "-Wold-style-definition", + "-Wstrict-prototypes", +] + +boringssl_copts_common = select({ + # This condition and the asm_srcs_used one below must be kept in sync. + "@platforms//os:windows": ["-DOPENSSL_NO_ASM"], + "//conditions:default": [], +}) + select({ + # We assume that non-Windows builds use a GCC-compatible toolchain and that + # Windows builds do not. + # + # TODO(davidben): Should these be querying something in @bazel_tools? + # Unfortunately, @bazel_tools is undocumented. See + # https://github.com/bazelbuild/bazel/issues/14914 + "@platforms//os:windows": [], + "//conditions:default": gcc_copts, +}) + select({ + # This is needed on glibc systems to get rwlock in pthreads, but it should + # not be set on Apple platforms or FreeBSD, where it instead disables APIs + # we use. + # See compat(5), sys/cdefs.h, and https://crbug.com/boringssl/471 + "@platforms//os:linux": ["-D_XOPEN_SOURCE=700"], + # Without WIN32_LEAN_AND_MEAN, pulls in wincrypt.h, which + # conflicts with our . + "@platforms//os:windows": ["-DWIN32_LEAN_AND_MEAN", "-utf-8"], + "//conditions:default": [], +}) + +# We do not specify the C++ version here because Bazel expects C++ version +# to come from the top-level toolchain. The concern is that different C++ +# versions may cause ABIs, notably Abseil's, to change. +boringssl_copts_cxx = boringssl_copts_common + select({ + "@platforms//os:windows": [], + "//conditions:default": gcc_copts_cxx, +}) + +# We specify the C version because Bazel projects often do not remember to +# specify the C version. We do not expect ABIs to vary by C versions, at least +# for our code or the headers we include, so configure the C version inside the +# library. If Bazel's C/C++ version handling improves, we may reconsider this. +boringssl_copts_c = boringssl_copts_common + select({ + "@platforms//os:windows": ["/std:c11"], + "//conditions:default": ["-std=c11"] + gcc_copts_c, +}) + +def handle_mixed_c_cxx( + name, + copts, + deps, + internal_hdrs, + includes, + linkopts, + srcs, + testonly): + """ + Works around https://github.com/bazelbuild/bazel/issues/22041. Determines + whether a target contains C, C++, or both. If the target is multi-language, + the C sources are split into a separate library. Returns a tuple of updated + (copts, deps, srcs) to apply. + """ + has_c, has_cxx = False, False + for src in srcs: + if src.endswith(".c"): + has_c = True + elif src.endswith(".cc"): + has_cxx = True + + # If a target has both C and C++, we need to split it in two. + if has_c and has_cxx: + srcs_c = [src for src in srcs if src.endswith(".c") or src.endswith(".h")] + name_c = name + "_c" + cc_library( + name = name_c, + srcs = srcs_c + internal_hdrs, + copts = copts + boringssl_copts_c, + includes = includes, + # This target only exists to be linked into the main library, so + # always link it statically. + linkstatic = True, + linkopts = linkopts, + deps = deps, + testonly = testonly, + ) + + # Build the remainder as a C++-only target. + deps += [":" + name_c] + srcs = [src for src in srcs if not src.endswith(".c")] + has_c = False + + if has_c: + copts += boringssl_copts_c + else: + copts += boringssl_copts_cxx + + return copts, deps, srcs + +def handle_asm_srcs(asm_srcs): + if not asm_srcs: + return [] + + # By default, the C files will expect assembly files, if any, to be linked + # in with the build. This default can be flipped with -DOPENSSL_NO_ASM. If + # building in a configuration where we have no assembly optimizations, + # -DOPENSSL_NO_ASM has no effect, and either value is fine. + # + # Like C files, assembly files are wrapped in #ifdef (or NASM equivalent), + # so it is safe to include a file for the wrong platform in the build. It + # will just output an empty object file. However, we need some platform + # selectors to distinguish between gas or NASM syntax. + # + # For all non-Windows platforms, we use gas assembly syntax and can assume + # any GCC-compatible toolchain includes a gas-compatible assembler. + # + # For Windows, we use NASM on x86 and x86_64 and gas, specifically + # clang-assembler, on aarch64. We have not yet added NASM support to this + # build, and would need to detect MSVC vs clang-cl for aarch64 so, for now, + # we just disable assembly on Windows across the board. + # + # This select and the corresponding one in boringssl_copts_common must be + # kept in sync. + # + # TODO(https://crbug.com/boringssl/531): Enable assembly for Windows. + return select({ + "@platforms//os:windows": [], + "//conditions:default": asm_srcs, + }) + +def linkstatic_kwargs(linkstatic): + # Although Bazel's documentation says linkstatic defaults to True or False + # for the various target types, this is not true. The defaults differ by + # platform non-Windows and True on Windows. There is now way to request the + # default except to omit the parameter, so we must use kwargs. + kwargs = {} + if linkstatic != None: + kwargs["linkstatic"] = linkstatic + return kwargs + +def bssl_cc_library( + name, + asm_srcs = [], + copts = [], + deps = [], + hdrs = [], + includes = [], + internal_hdrs = [], + linkopts = [], + linkstatic = None, + srcs = [], + testonly = False, + visibility = []): + copts, deps, srcs = handle_mixed_c_cxx( + name = name, + copts = copts, + deps = deps, + internal_hdrs = hdrs + internal_hdrs, + includes = includes, + linkopts = linkopts, + srcs = srcs, + testonly = testonly, + ) + + # BoringSSL's notion of internal headers are slightly different from + # Bazel's. libcrypto's internal headers may be used by libssl, but they + # cannot be used outside the library. To express this, we make separate + # internal and external targets. This impact's Bazel's layering check. + name_internal = name + if visibility: + name_internal = name + "_internal" + + cc_library( + name = name_internal, + srcs = srcs + handle_asm_srcs(asm_srcs), + hdrs = hdrs + internal_hdrs, + copts = copts, + includes = includes, + linkopts = linkopts, + deps = deps, + testonly = testonly, + **linkstatic_kwargs(linkstatic) + ) + + if visibility: + cc_library( + name = name, + hdrs = hdrs, + deps = [":" + name_internal], + visibility = visibility, + ) + +def bssl_cc_binary( + name, + srcs = [], + asm_srcs = [], + copts = [], + includes = [], + linkstatic = None, + linkopts = [], + deps = [], + testonly = False, + visibility = []): + copts, deps, srcs = handle_mixed_c_cxx( + name = name, + copts = copts, + deps = deps, + internal_hdrs = [], + includes = includes, + linkopts = linkopts, + srcs = srcs, + testonly = testonly, + ) + + cc_binary( + name = name, + srcs = srcs + handle_asm_srcs(asm_srcs), + copts = copts, + includes = includes, + linkopts = linkopts, + deps = deps, + testonly = testonly, + visibility = visibility, + **linkstatic_kwargs(linkstatic) + ) + +def bssl_cc_test( + name, + srcs = [], + asm_srcs = [], + data = [], + size = "medium", + internal_hdrs = [], + copts = [], + includes = [], + linkopts = [], + linkstatic = None, + deps = [], + shard_count = None): + copts, deps, srcs = handle_mixed_c_cxx( + name = name, + copts = copts, + deps = deps, + internal_hdrs = [], + includes = includes, + linkopts = linkopts, + srcs = srcs, + testonly = True, + ) + + cc_test( + name = name, + data = data, + deps = deps, + srcs = srcs + handle_asm_srcs(asm_srcs), + copts = copts, + includes = includes, + linkopts = linkopts, + shard_count = shard_count, + size = size, + **linkstatic_kwargs(linkstatic) + ) diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/libaom/source/libaom/av1/encoder/pass2_strategy.c b/Source/ThirdParty/libwebrtc/Source/third_party/libaom/source/libaom/av1/encoder/pass2_strategy.c index 6b63afc399656..ccac2226d9c8c 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/libaom/source/libaom/av1/encoder/pass2_strategy.c +++ b/Source/ThirdParty/libwebrtc/Source/third_party/libaom/source/libaom/av1/encoder/pass2_strategy.c @@ -944,7 +944,10 @@ static void allocate_gf_group_bits(GF_GROUP *gf_group, case ARF_UPDATE: case INTNL_ARF_UPDATE: arf_extra_bits = layer_extra_bits[gf_group->layer_depth[idx]]; - gf_group->bit_allocation[idx] = base_frame_bits + arf_extra_bits; + gf_group->bit_allocation[idx] = + (base_frame_bits > INT_MAX - arf_extra_bits) + ? INT_MAX + : (base_frame_bits + arf_extra_bits); break; case INTNL_OVERLAY_UPDATE: case OVERLAY_UPDATE: gf_group->bit_allocation[idx] = 0; break; @@ -4136,7 +4139,12 @@ void av1_twopass_postencode_update(AV1_COMP *cpi) { } // Target vs actual bits for this arf group. - twopass->rolling_arf_group_target_bits += rc->base_frame_target; + if (twopass->rolling_arf_group_target_bits > + INT_MAX - rc->base_frame_target) { + twopass->rolling_arf_group_target_bits = INT_MAX; + } else { + twopass->rolling_arf_group_target_bits += rc->base_frame_target; + } twopass->rolling_arf_group_actual_bits += rc->projected_frame_size; // Calculate the pct rc error. diff --git a/Source/ThirdParty/libwebrtc/Source/third_party/libaom/source/libaom/av1/encoder/ratectrl.c b/Source/ThirdParty/libwebrtc/Source/third_party/libaom/source/libaom/av1/encoder/ratectrl.c index 7191da44cdd8f..91a5076c60439 100644 --- a/Source/ThirdParty/libwebrtc/Source/third_party/libaom/source/libaom/av1/encoder/ratectrl.c +++ b/Source/ThirdParty/libwebrtc/Source/third_party/libaom/source/libaom/av1/encoder/ratectrl.c @@ -2227,8 +2227,8 @@ void av1_rc_compute_frame_size_bounds(const AV1_COMP *cpi, int frame_target, const int tolerance = (int)AOMMAX( 100, ((int64_t)cpi->sf.hl_sf.recode_tolerance * frame_target) / 100); *frame_under_shoot_limit = AOMMAX(frame_target - tolerance, 0); - *frame_over_shoot_limit = - AOMMIN(frame_target + tolerance, cpi->rc.max_frame_bandwidth); + *frame_over_shoot_limit = (int)AOMMIN((int64_t)frame_target + tolerance, + cpi->rc.max_frame_bandwidth); } } @@ -2353,9 +2353,9 @@ void av1_rc_postencode_update(AV1_COMP *cpi, uint64_t bytes_used) { cm->width, cm->height)); if (current_frame->frame_type != KEY_FRAME) { p_rc->rolling_target_bits = (int)ROUND_POWER_OF_TWO_64( - p_rc->rolling_target_bits * 3 + rc->this_frame_target, 2); + (int64_t)p_rc->rolling_target_bits * 3 + rc->this_frame_target, 2); p_rc->rolling_actual_bits = (int)ROUND_POWER_OF_TWO_64( - p_rc->rolling_actual_bits * 3 + rc->projected_frame_size, 2); + (int64_t)p_rc->rolling_actual_bits * 3 + rc->projected_frame_size, 2); } // Actual bits spent diff --git a/Source/ThirdParty/libwebrtc/Source/webrtc/modules/rtp_rtcp/source/rtp_packetizer_h265.cc b/Source/ThirdParty/libwebrtc/Source/webrtc/modules/rtp_rtcp/source/rtp_packetizer_h265.cc index 5f10120d81c30..12fa51946defe 100644 --- a/Source/ThirdParty/libwebrtc/Source/webrtc/modules/rtp_rtcp/source/rtp_packetizer_h265.cc +++ b/Source/ThirdParty/libwebrtc/Source/webrtc/modules/rtp_rtcp/source/rtp_packetizer_h265.cc @@ -26,6 +26,12 @@ RtpPacketizerH265::RtpPacketizerH265(rtc::ArrayView payload, : limits_(limits), num_packets_left_(0) { for (const auto& nalu : H264::FindNaluIndices(payload.data(), payload.size())) { +#if WEBRTC_WEBKIT_BUILD + if (!nalu.payload_size) { + input_fragments_.clear(); + return; + } +#endif input_fragments_.push_back( payload.subview(nalu.payload_start_offset, nalu.payload_size)); } diff --git a/Source/ThirdParty/libwebrtc/Source/webrtc/modules/video_coding/h26x_packet_buffer.cc b/Source/ThirdParty/libwebrtc/Source/webrtc/modules/video_coding/h26x_packet_buffer.cc index 133ab4708aa1b..62f9cda7637b4 100644 --- a/Source/ThirdParty/libwebrtc/Source/webrtc/modules/video_coding/h26x_packet_buffer.cc +++ b/Source/ThirdParty/libwebrtc/Source/webrtc/modules/video_coding/h26x_packet_buffer.cc @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -81,6 +82,16 @@ bool HasSps(const H26xPacketBuffer::Packet& packet) { }); } +int64_t* GetContinuousSequence(rtc::ArrayView last_continuous, + int64_t unwrapped_seq_num) { + for (int64_t& last : last_continuous) { + if (unwrapped_seq_num - 1 == last) { + return &last; + } + } + return nullptr; +} + #ifdef RTC_ENABLE_H265 bool HasVps(const H26xPacketBuffer::Packet& packet) { std::vector nalu_indices = H265::FindNaluIndices( @@ -97,7 +108,9 @@ bool HasVps(const H26xPacketBuffer::Packet& packet) { } // namespace H26xPacketBuffer::H26xPacketBuffer(bool h264_idr_only_keyframes_allowed) - : h264_idr_only_keyframes_allowed_(h264_idr_only_keyframes_allowed) {} + : h264_idr_only_keyframes_allowed_(h264_idr_only_keyframes_allowed) { + last_continuous_in_sequence_.fill(std::numeric_limits::min()); +} H26xPacketBuffer::InsertResult H26xPacketBuffer::InsertPacket( std::unique_ptr packet) { @@ -147,18 +160,25 @@ H26xPacketBuffer::InsertResult H26xPacketBuffer::FindFrames( // Check if the packet is continuous or the beginning of a new coded video // sequence. - if (unwrapped_seq_num - 1 != last_continuous_unwrapped_seq_num_) { - if (unwrapped_seq_num <= last_continuous_unwrapped_seq_num_ || - !BeginningOfStream(*packet)) { + int64_t* last_continuous_unwrapped_seq_num = + GetContinuousSequence(last_continuous_in_sequence_, unwrapped_seq_num); + if (last_continuous_unwrapped_seq_num == nullptr) { + if (!BeginningOfStream(*packet)) { return result; } - last_continuous_unwrapped_seq_num_ = unwrapped_seq_num; + last_continuous_in_sequence_[last_continuous_in_sequence_index_] = + unwrapped_seq_num; + last_continuous_unwrapped_seq_num = + &last_continuous_in_sequence_[last_continuous_in_sequence_index_]; + last_continuous_in_sequence_index_ = + (last_continuous_in_sequence_index_ + 1) % + last_continuous_in_sequence_.size(); } for (int64_t seq_num = unwrapped_seq_num; seq_num < unwrapped_seq_num + kBufferSize;) { - RTC_DCHECK_GE(seq_num, *last_continuous_unwrapped_seq_num_); + RTC_DCHECK_GE(seq_num, *last_continuous_unwrapped_seq_num); // Packets that were never assembled into a completed frame will stay in // the 'buffer_'. Check that the `packet` sequence number match the expected @@ -167,7 +187,7 @@ H26xPacketBuffer::InsertResult H26xPacketBuffer::FindFrames( return result; } - last_continuous_unwrapped_seq_num_ = seq_num; + *last_continuous_unwrapped_seq_num = seq_num; // Last packet of the frame, try to assemble the frame. if (packet->marker_bit) { uint32_t rtp_timestamp = packet->timestamp; diff --git a/Source/ThirdParty/libwebrtc/Source/webrtc/modules/video_coding/h26x_packet_buffer.h b/Source/ThirdParty/libwebrtc/Source/webrtc/modules/video_coding/h26x_packet_buffer.h index 8bfae71f7b70b..7872aba1ac160 100644 --- a/Source/ThirdParty/libwebrtc/Source/webrtc/modules/video_coding/h26x_packet_buffer.h +++ b/Source/ThirdParty/libwebrtc/Source/webrtc/modules/video_coding/h26x_packet_buffer.h @@ -72,6 +72,7 @@ class H26xPacketBuffer { }; static constexpr int kBufferSize = 2048; + static constexpr int kNumTrackedSequences = 5; std::unique_ptr& GetPacket(int64_t unwrapped_seq_num); bool BeginningOfStream(const Packet& packet) const; @@ -91,7 +92,8 @@ class H26xPacketBuffer { // Indicates whether IDR frames without SPS and PPS are allowed. const bool h264_idr_only_keyframes_allowed_; std::array, kBufferSize> buffer_; - absl::optional last_continuous_unwrapped_seq_num_; + std::array last_continuous_in_sequence_; + int64_t last_continuous_in_sequence_index_ = 0; SeqNumUnwrapper seq_num_unwrapper_; // Map from pps_pic_parameter_set_id to the PPS payload associated with this diff --git a/Source/ThirdParty/libwebrtc/Source/webrtc/modules/video_coding/h26x_packet_buffer_unittest.cc b/Source/ThirdParty/libwebrtc/Source/webrtc/modules/video_coding/h26x_packet_buffer_unittest.cc index 8d6d69136df31..73ee035ea7cb0 100644 --- a/Source/ThirdParty/libwebrtc/Source/webrtc/modules/video_coding/h26x_packet_buffer_unittest.cc +++ b/Source/ThirdParty/libwebrtc/Source/webrtc/modules/video_coding/h26x_packet_buffer_unittest.cc @@ -1062,6 +1062,72 @@ TEST(H26xPacketBufferTest, TooManyNalusInPacket) { EXPECT_THAT(packet_buffer.InsertPacket(std::move(packet)).packets, IsEmpty()); } +TEST(H26xPacketBufferTest, AssembleFrameAfterReordering) { + H26xPacketBuffer packet_buffer(/*h264_allow_idr_only_keyframes=*/false); + + EXPECT_THAT(packet_buffer + .InsertPacket(H264Packet(kH264StapA) + .Sps() + .Pps() + .Idr() + .SeqNum(2) + .Time(2) + .Marker() + .Build()) + .packets, + SizeIs(1)); + + EXPECT_THAT(packet_buffer + .InsertPacket(H264Packet(kH264SingleNalu) + .Slice() + .SeqNum(1) + .Time(1) + .Marker() + .Build()) + .packets, + IsEmpty()); + + EXPECT_THAT(packet_buffer + .InsertPacket(H264Packet(kH264StapA) + .Sps() + .Pps() + .Idr() + .SeqNum(0) + .Time(0) + .Marker() + .Build()) + .packets, + SizeIs(2)); +} + +TEST(H26xPacketBufferTest, AssembleFrameAfterLoss) { + H26xPacketBuffer packet_buffer(/*h264_allow_idr_only_keyframes=*/false); + + EXPECT_THAT(packet_buffer + .InsertPacket(H264Packet(kH264StapA) + .Sps() + .Pps() + .Idr() + .SeqNum(0) + .Time(0) + .Marker() + .Build()) + .packets, + SizeIs(1)); + + EXPECT_THAT(packet_buffer + .InsertPacket(H264Packet(kH264StapA) + .Sps() + .Pps() + .Idr() + .SeqNum(2) + .Time(2) + .Marker() + .Build()) + .packets, + SizeIs(1)); +} + #ifdef RTC_ENABLE_H265 TEST(H26xPacketBufferTest, H265VpsSpsPpsIdrIsKeyframe) { H26xPacketBuffer packet_buffer(/*allow_idr_only_keyframes=*/false); diff --git a/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj b/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj index 9fb47b8d52887..4a30b1a584d3b 100644 --- a/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj +++ b/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj @@ -475,7 +475,6 @@ 411927612A375B2A007C09F6 /* digestsign.c in Sources */ = {isa = PBXBuildFile; fileRef = 411927602A375B2A007C09F6 /* digestsign.c */; }; 411927642A375B50007C09F6 /* hkdf.c in Sources */ = {isa = PBXBuildFile; fileRef = 411927632A375B50007C09F6 /* hkdf.c */; }; 411927682A375B8B007C09F6 /* kyber.c in Sources */ = {isa = PBXBuildFile; fileRef = 411927662A375B8A007C09F6 /* kyber.c */; }; - 411927692A375B8B007C09F6 /* keccak.c in Sources */ = {isa = PBXBuildFile; fileRef = 411927672A375B8B007C09F6 /* keccak.c */; }; 4119276B2A375BB0007C09F6 /* rsa_crypt.c in Sources */ = {isa = PBXBuildFile; fileRef = 4119276A2A375BB0007C09F6 /* rsa_crypt.c */; }; 4119276D2A375C48007C09F6 /* extensions.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4119276C2A375C47007C09F6 /* extensions.cc */; }; 411927712A375C93007C09F6 /* evp_do_all.c in Sources */ = {isa = PBXBuildFile; fileRef = 411927702A375C93007C09F6 /* evp_do_all.c */; }; @@ -1754,7 +1753,6 @@ 4153CD2821CC664800C3E188 /* vq_sse2.c in Sources */ = {isa = PBXBuildFile; fileRef = 4100CDED21CAD1F500F9B87D /* vq_sse2.c */; }; 415449A021CAC330001C0A55 /* ecdh.c in Sources */ = {isa = PBXBuildFile; fileRef = 4154499F21CAC330001C0A55 /* ecdh.c */; }; 415449A221CAC34D001C0A55 /* unicode.c in Sources */ = {isa = PBXBuildFile; fileRef = 415449A121CAC34D001C0A55 /* unicode.c */; }; - 415449A421CAC372001C0A55 /* v3_ocsp.c in Sources */ = {isa = PBXBuildFile; fileRef = 415449A321CAC371001C0A55 /* v3_ocsp.c */; }; 415449A721CAC39A001C0A55 /* div_extra.c in Sources */ = {isa = PBXBuildFile; fileRef = 415449A521CAC399001C0A55 /* div_extra.c */; }; 415449A821CAC39A001C0A55 /* gcd_extra.c in Sources */ = {isa = PBXBuildFile; fileRef = 415449A621CAC399001C0A55 /* gcd_extra.c */; }; 415449AB21CAC3CF001C0A55 /* kdf.c in Sources */ = {isa = PBXBuildFile; fileRef = 415449AA21CAC3CF001C0A55 /* kdf.c */; }; @@ -2522,6 +2520,41 @@ 41ABB1B12BFE3E5D0035D075 /* highbd_sse_neon.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1AE2BFE3E5C0035D075 /* highbd_sse_neon.c */; }; 41ABB1B22BFE3E5D0035D075 /* sse_neon.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1AF2BFE3E5D0035D075 /* sse_neon.c */; }; 41ABB1B32BFE3E5D0035D075 /* sse_neon_dotprod.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1B02BFE3E5D0035D075 /* sse_neon_dotprod.c */; }; + 41ABB1272BFE00220035D075 /* keccak.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1252BFE00210035D075 /* keccak.c */; }; + 41ABB1282BFE00220035D075 /* internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 41ABB1262BFE00210035D075 /* internal.h */; }; + 41ABB12A2BFE00810035D075 /* p256_64.h in Headers */ = {isa = PBXBuildFile; fileRef = 41ABB1292BFE00810035D075 /* p256_64.h */; }; + 41ABB1422BFE00C30035D075 /* v3_pcons.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB12B2BFE00BA0035D075 /* v3_pcons.c */; }; + 41ABB1432BFE00C30035D075 /* v3_akey.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB12C2BFE00BA0035D075 /* v3_akey.c */; }; + 41ABB1442BFE00C30035D075 /* v3_prn.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB12D2BFE00BA0035D075 /* v3_prn.c */; }; + 41ABB1452BFE00C30035D075 /* v3_purp.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB12E2BFE00BB0035D075 /* v3_purp.c */; }; + 41ABB1462BFE00C30035D075 /* v3_genn.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB12F2BFE00BB0035D075 /* v3_genn.c */; }; + 41ABB1472BFE00C30035D075 /* v3_utl.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1302BFE00BC0035D075 /* v3_utl.c */; }; + 41ABB1482BFE00C30035D075 /* v3_akeya.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1312BFE00BC0035D075 /* v3_akeya.c */; }; + 41ABB1492BFE00C30035D075 /* v3_bcons.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1322BFE00BD0035D075 /* v3_bcons.c */; }; + 41ABB14A2BFE00C30035D075 /* v3_ia5.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1332BFE00BD0035D075 /* v3_ia5.c */; }; + 41ABB14B2BFE00C30035D075 /* v3_info.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1342BFE00BD0035D075 /* v3_info.c */; }; + 41ABB14C2BFE00C30035D075 /* v3_skey.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1352BFE00BE0035D075 /* v3_skey.c */; }; + 41ABB14D2BFE00C30035D075 /* v3_lib.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1362BFE00BE0035D075 /* v3_lib.c */; }; + 41ABB14E2BFE00C30035D075 /* v3_alt.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1372BFE00BF0035D075 /* v3_alt.c */; }; + 41ABB14F2BFE00C30035D075 /* v3_conf.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1382BFE00BF0035D075 /* v3_conf.c */; }; + 41ABB1502BFE00C30035D075 /* v3_enum.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1392BFE00BF0035D075 /* v3_enum.c */; }; + 41ABB1512BFE00C30035D075 /* v3_ncons.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB13A2BFE00C00035D075 /* v3_ncons.c */; }; + 41ABB1522BFE00C30035D075 /* v3_int.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB13B2BFE00C00035D075 /* v3_int.c */; }; + 41ABB1532BFE00C30035D075 /* v3_cpols.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB13C2BFE00C10035D075 /* v3_cpols.c */; }; + 41ABB1542BFE00C30035D075 /* v3_pmaps.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB13D2BFE00C10035D075 /* v3_pmaps.c */; }; + 41ABB1552BFE00C30035D075 /* v3_ocsp.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB13E2BFE00C20035D075 /* v3_ocsp.c */; }; + 41ABB1562BFE00C30035D075 /* v3_crld.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB13F2BFE00C20035D075 /* v3_crld.c */; }; + 41ABB1572BFE00C30035D075 /* v3_bitst.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1402BFE00C30035D075 /* v3_bitst.c */; }; + 41ABB1582BFE00C30035D075 /* v3_extku.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1412BFE00C30035D075 /* v3_extku.c */; }; + 41ABB15A2BFE015A0035D075 /* refcount.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1592BFE01590035D075 /* refcount.c */; }; + 41ABB15D2BFE01AD0035D075 /* getentropy.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB15B2BFE01AC0035D075 /* getentropy.c */; }; + 41ABB15E2BFE01AD0035D075 /* passive.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB15C2BFE01AC0035D075 /* passive.c */; }; + 41ABB1602BFE01EF0035D075 /* ssl_credential.cc in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB15F2BFE01EF0035D075 /* ssl_credential.cc */; }; + 41ABB1622BFE03020035D075 /* errno.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1612BFE03010035D075 /* errno.c */; }; + 41ABB1BA2BFE3FBF0035D075 /* ios.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1B82BFE3FB90035D075 /* ios.c */; }; + 41ABB1BE2BFE40190035D075 /* scrypt.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1BB2BFE40190035D075 /* scrypt.c */; }; + 41ABB1BF2BFE40190035D075 /* p_dh_asn1.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1BC2BFE40190035D075 /* p_dh_asn1.c */; }; + 41ABB1C02BFE40190035D075 /* p_dh.c in Sources */ = {isa = PBXBuildFile; fileRef = 41ABB1BD2BFE40190035D075 /* p_dh.c */; }; 41B675B7216599A80040A75D /* highbd_idct16x16_add_sse2.c in Sources */ = {isa = PBXBuildFile; fileRef = 41BAE3CF212E2D9100E22482 /* highbd_idct16x16_add_sse2.c */; }; 41B675B9216599A80040A75D /* highbd_idct32x32_add_sse2.c in Sources */ = {isa = PBXBuildFile; fileRef = 41C6290E212E2DE2002313D4 /* highbd_idct32x32_add_sse2.c */; }; 41B675BB216599A80040A75D /* highbd_idct4x4_add_sse2.c in Sources */ = {isa = PBXBuildFile; fileRef = 41C62910212E2DE3002313D4 /* highbd_idct4x4_add_sse2.c */; }; @@ -3421,35 +3454,10 @@ 5C4B463D1E42AA2C002651C8 /* ex_data.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B451F1E42A71B002651C8 /* ex_data.c */; }; 5C4B463E1E42AA2C002651C8 /* internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C4B45201E42A71B002651C8 /* internal.h */; }; 5C4B463F1E42AA2C002651C8 /* mem.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B45211E42A71B002651C8 /* mem.c */; }; - 5C4B46401E42AA2C002651C8 /* refcount_c11.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B45221E42A71B002651C8 /* refcount_c11.c */; }; - 5C4B46411E42AA2C002651C8 /* refcount_lock.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B45231E42A71B002651C8 /* refcount_lock.c */; }; 5C4B46431E42AA2C002651C8 /* thread_none.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B45251E42A71B002651C8 /* thread_none.c */; }; 5C4B46441E42AA2C002651C8 /* thread_pthread.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B45261E42A71B002651C8 /* thread_pthread.c */; }; 5C4B46461E42AA2C002651C8 /* thread_win.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B45281E42A71B002651C8 /* thread_win.c */; }; 5C4B46471E42AA2C002651C8 /* thread.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B45291E42A71B002651C8 /* thread.c */; }; - 5C4B46491E42AA34002651C8 /* ext_dat.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C4B44641E42A6E2002651C8 /* ext_dat.h */; }; - 5C4B46521E42AA34002651C8 /* v3_akey.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B446D1E42A6E2002651C8 /* v3_akey.c */; }; - 5C4B46531E42AA34002651C8 /* v3_akeya.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B446E1E42A6E2002651C8 /* v3_akeya.c */; }; - 5C4B46541E42AA34002651C8 /* v3_alt.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B446F1E42A6E2002651C8 /* v3_alt.c */; }; - 5C4B46551E42AA34002651C8 /* v3_bcons.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44701E42A6E2002651C8 /* v3_bcons.c */; }; - 5C4B46561E42AA34002651C8 /* v3_bitst.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44711E42A6E2002651C8 /* v3_bitst.c */; }; - 5C4B46571E42AA34002651C8 /* v3_conf.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44721E42A6E2002651C8 /* v3_conf.c */; }; - 5C4B46581E42AA34002651C8 /* v3_cpols.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44731E42A6E2002651C8 /* v3_cpols.c */; }; - 5C4B46591E42AA34002651C8 /* v3_crld.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44741E42A6E2002651C8 /* v3_crld.c */; }; - 5C4B465A1E42AA34002651C8 /* v3_enum.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44751E42A6E2002651C8 /* v3_enum.c */; }; - 5C4B465B1E42AA34002651C8 /* v3_extku.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44761E42A6E2002651C8 /* v3_extku.c */; }; - 5C4B465C1E42AA34002651C8 /* v3_genn.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44771E42A6E2002651C8 /* v3_genn.c */; }; - 5C4B465D1E42AA34002651C8 /* v3_ia5.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44781E42A6E2002651C8 /* v3_ia5.c */; }; - 5C4B465E1E42AA34002651C8 /* v3_info.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44791E42A6E2002651C8 /* v3_info.c */; }; - 5C4B465F1E42AA34002651C8 /* v3_int.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B447A1E42A6E2002651C8 /* v3_int.c */; }; - 5C4B46601E42AA34002651C8 /* v3_lib.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B447B1E42A6E2002651C8 /* v3_lib.c */; }; - 5C4B46611E42AA34002651C8 /* v3_ncons.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B447C1E42A6E2002651C8 /* v3_ncons.c */; }; - 5C4B46641E42AA34002651C8 /* v3_pcons.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B447F1E42A6E2002651C8 /* v3_pcons.c */; }; - 5C4B46661E42AA34002651C8 /* v3_pmaps.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44811E42A6E2002651C8 /* v3_pmaps.c */; }; - 5C4B46671E42AA34002651C8 /* v3_prn.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44821E42A6E2002651C8 /* v3_prn.c */; }; - 5C4B46681E42AA34002651C8 /* v3_purp.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44831E42A6E2002651C8 /* v3_purp.c */; }; - 5C4B46691E42AA34002651C8 /* v3_skey.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44841E42A6E2002651C8 /* v3_skey.c */; }; - 5C4B466B1E42AA34002651C8 /* v3_utl.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44861E42A6E2002651C8 /* v3_utl.c */; }; 5C4B466D1E42AA3C002651C8 /* a_digest.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44AC1E42A6F7002651C8 /* a_digest.c */; }; 5C4B466E1E42AA3C002651C8 /* a_sign.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44AD1E42A6F7002651C8 /* a_sign.c */; }; 5C4B46701E42AA3C002651C8 /* a_verify.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44AF1E42A6F7002651C8 /* a_verify.c */; }; @@ -3468,9 +3476,7 @@ 5C4B46821E42AA3C002651C8 /* x_attrib.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44C11E42A6F7002651C8 /* x_attrib.c */; }; 5C4B46831E42AA3C002651C8 /* x_crl.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44C21E42A6F7002651C8 /* x_crl.c */; }; 5C4B46841E42AA3C002651C8 /* x_exten.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44C31E42A6F7002651C8 /* x_exten.c */; }; - 5C4B46851E42AA3C002651C8 /* x_info.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44C41E42A6F7002651C8 /* x_info.c */; }; 5C4B46861E42AA3C002651C8 /* x_name.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44C51E42A6F7002651C8 /* x_name.c */; }; - 5C4B46871E42AA3C002651C8 /* x_pkey.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44C61E42A6F7002651C8 /* x_pkey.c */; }; 5C4B46881E42AA3C002651C8 /* x_pubkey.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44C71E42A6F7002651C8 /* x_pubkey.c */; }; 5C4B46891E42AA3C002651C8 /* x_req.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44C81E42A6F7002651C8 /* x_req.c */; }; 5C4B468A1E42AA3C002651C8 /* x_sig.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B44C91E42A6F7002651C8 /* x_sig.c */; }; @@ -6402,7 +6408,6 @@ 411927602A375B2A007C09F6 /* digestsign.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = digestsign.c; sourceTree = ""; }; 411927632A375B50007C09F6 /* hkdf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hkdf.c; sourceTree = ""; }; 411927662A375B8A007C09F6 /* kyber.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = kyber.c; sourceTree = ""; }; - 411927672A375B8B007C09F6 /* keccak.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = keccak.c; sourceTree = ""; }; 4119276A2A375BB0007C09F6 /* rsa_crypt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = rsa_crypt.c; sourceTree = ""; }; 4119276C2A375C47007C09F6 /* extensions.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = extensions.cc; sourceTree = ""; }; 411927702A375C93007C09F6 /* evp_do_all.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = evp_do_all.c; sourceTree = ""; }; @@ -7739,7 +7744,6 @@ 414FB86A216BB768001F5492 /* RTCRtpEncodingParameters.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RTCRtpEncodingParameters.mm; sourceTree = ""; }; 4154499F21CAC330001C0A55 /* ecdh.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ecdh.c; sourceTree = ""; }; 415449A121CAC34D001C0A55 /* unicode.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unicode.c; sourceTree = ""; }; - 415449A321CAC371001C0A55 /* v3_ocsp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_ocsp.c; sourceTree = ""; }; 415449A521CAC399001C0A55 /* div_extra.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = div_extra.c; sourceTree = ""; }; 415449A621CAC399001C0A55 /* gcd_extra.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gcd_extra.c; sourceTree = ""; }; 415449AA21CAC3CF001C0A55 /* kdf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = kdf.c; sourceTree = ""; }; @@ -8578,7 +8582,6 @@ 41A392071EFC4A7100C4516A /* p_ed25519_asn1.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = p_ed25519_asn1.c; sourceTree = ""; }; 41A3920B1EFC4AFE00C4516A /* deterministic.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = deterministic.c; sourceTree = ""; }; 41A3920C1EFC4AFE00C4516A /* forkunsafe.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = forkunsafe.c; sourceTree = ""; }; - 41A3920D1EFC4AFE00C4516A /* fuchsia.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fuchsia.c; sourceTree = ""; }; 41A3920E1EFC4AFE00C4516A /* rand_extra.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = rand_extra.c; sourceTree = ""; }; 41A3920F1EFC4AFE00C4516A /* windows.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = windows.c; sourceTree = ""; }; 41A392181EFC5AB800C4516A /* x25519-asm-arm.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "x25519-asm-arm.S"; sourceTree = ""; }; @@ -8658,6 +8661,41 @@ 41ABB1AE2BFE3E5C0035D075 /* highbd_sse_neon.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = highbd_sse_neon.c; sourceTree = ""; }; 41ABB1AF2BFE3E5D0035D075 /* sse_neon.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sse_neon.c; sourceTree = ""; }; 41ABB1B02BFE3E5D0035D075 /* sse_neon_dotprod.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sse_neon_dotprod.c; sourceTree = ""; }; + 41ABB1252BFE00210035D075 /* keccak.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = keccak.c; sourceTree = ""; }; + 41ABB1262BFE00210035D075 /* internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = internal.h; sourceTree = ""; }; + 41ABB1292BFE00810035D075 /* p256_64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = p256_64.h; sourceTree = ""; }; + 41ABB12B2BFE00BA0035D075 /* v3_pcons.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_pcons.c; sourceTree = ""; }; + 41ABB12C2BFE00BA0035D075 /* v3_akey.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_akey.c; sourceTree = ""; }; + 41ABB12D2BFE00BA0035D075 /* v3_prn.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_prn.c; sourceTree = ""; }; + 41ABB12E2BFE00BB0035D075 /* v3_purp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_purp.c; sourceTree = ""; }; + 41ABB12F2BFE00BB0035D075 /* v3_genn.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_genn.c; sourceTree = ""; }; + 41ABB1302BFE00BC0035D075 /* v3_utl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_utl.c; sourceTree = ""; }; + 41ABB1312BFE00BC0035D075 /* v3_akeya.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_akeya.c; sourceTree = ""; }; + 41ABB1322BFE00BD0035D075 /* v3_bcons.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_bcons.c; sourceTree = ""; }; + 41ABB1332BFE00BD0035D075 /* v3_ia5.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_ia5.c; sourceTree = ""; }; + 41ABB1342BFE00BD0035D075 /* v3_info.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_info.c; sourceTree = ""; }; + 41ABB1352BFE00BE0035D075 /* v3_skey.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_skey.c; sourceTree = ""; }; + 41ABB1362BFE00BE0035D075 /* v3_lib.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_lib.c; sourceTree = ""; }; + 41ABB1372BFE00BF0035D075 /* v3_alt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_alt.c; sourceTree = ""; }; + 41ABB1382BFE00BF0035D075 /* v3_conf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_conf.c; sourceTree = ""; }; + 41ABB1392BFE00BF0035D075 /* v3_enum.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_enum.c; sourceTree = ""; }; + 41ABB13A2BFE00C00035D075 /* v3_ncons.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_ncons.c; sourceTree = ""; }; + 41ABB13B2BFE00C00035D075 /* v3_int.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_int.c; sourceTree = ""; }; + 41ABB13C2BFE00C10035D075 /* v3_cpols.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_cpols.c; sourceTree = ""; }; + 41ABB13D2BFE00C10035D075 /* v3_pmaps.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_pmaps.c; sourceTree = ""; }; + 41ABB13E2BFE00C20035D075 /* v3_ocsp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_ocsp.c; sourceTree = ""; }; + 41ABB13F2BFE00C20035D075 /* v3_crld.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_crld.c; sourceTree = ""; }; + 41ABB1402BFE00C30035D075 /* v3_bitst.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_bitst.c; sourceTree = ""; }; + 41ABB1412BFE00C30035D075 /* v3_extku.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_extku.c; sourceTree = ""; }; + 41ABB1592BFE01590035D075 /* refcount.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = refcount.c; sourceTree = ""; }; + 41ABB15B2BFE01AC0035D075 /* getentropy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = getentropy.c; sourceTree = ""; }; + 41ABB15C2BFE01AC0035D075 /* passive.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = passive.c; sourceTree = ""; }; + 41ABB15F2BFE01EF0035D075 /* ssl_credential.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ssl_credential.cc; sourceTree = ""; }; + 41ABB1612BFE03010035D075 /* errno.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = errno.c; sourceTree = ""; }; + 41ABB1B82BFE3FB90035D075 /* ios.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ios.c; sourceTree = ""; }; + 41ABB1BB2BFE40190035D075 /* scrypt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = scrypt.c; sourceTree = ""; }; + 41ABB1BC2BFE40190035D075 /* p_dh_asn1.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = p_dh_asn1.c; sourceTree = ""; }; + 41ABB1BD2BFE40190035D075 /* p_dh.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = p_dh.c; sourceTree = ""; }; 41B8D75C28C8874D00E5FA37 /* i210_buffer.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = i210_buffer.cc; sourceTree = ""; }; 41B8D75D28C8874E00E5FA37 /* i422_buffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = i422_buffer.h; sourceTree = ""; }; 41B8D75E28C8874E00E5FA37 /* i422_buffer.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = i422_buffer.cc; sourceTree = ""; }; @@ -9636,29 +9674,6 @@ 5C4B44041E42A4F1002651C8 /* socket_helper.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = socket_helper.c; sourceTree = ""; }; 5C4B44051E42A4F1002651C8 /* socket.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = socket.c; sourceTree = ""; }; 5C4B443F1E42A549002651C8 /* buf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = buf.c; sourceTree = ""; }; - 5C4B44641E42A6E2002651C8 /* ext_dat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ext_dat.h; sourceTree = ""; }; - 5C4B446D1E42A6E2002651C8 /* v3_akey.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_akey.c; sourceTree = ""; }; - 5C4B446E1E42A6E2002651C8 /* v3_akeya.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_akeya.c; sourceTree = ""; }; - 5C4B446F1E42A6E2002651C8 /* v3_alt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_alt.c; sourceTree = ""; }; - 5C4B44701E42A6E2002651C8 /* v3_bcons.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_bcons.c; sourceTree = ""; }; - 5C4B44711E42A6E2002651C8 /* v3_bitst.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_bitst.c; sourceTree = ""; }; - 5C4B44721E42A6E2002651C8 /* v3_conf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_conf.c; sourceTree = ""; }; - 5C4B44731E42A6E2002651C8 /* v3_cpols.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_cpols.c; sourceTree = ""; }; - 5C4B44741E42A6E2002651C8 /* v3_crld.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_crld.c; sourceTree = ""; }; - 5C4B44751E42A6E2002651C8 /* v3_enum.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_enum.c; sourceTree = ""; }; - 5C4B44761E42A6E2002651C8 /* v3_extku.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_extku.c; sourceTree = ""; }; - 5C4B44771E42A6E2002651C8 /* v3_genn.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_genn.c; sourceTree = ""; }; - 5C4B44781E42A6E2002651C8 /* v3_ia5.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_ia5.c; sourceTree = ""; }; - 5C4B44791E42A6E2002651C8 /* v3_info.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_info.c; sourceTree = ""; }; - 5C4B447A1E42A6E2002651C8 /* v3_int.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_int.c; sourceTree = ""; }; - 5C4B447B1E42A6E2002651C8 /* v3_lib.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_lib.c; sourceTree = ""; }; - 5C4B447C1E42A6E2002651C8 /* v3_ncons.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_ncons.c; sourceTree = ""; }; - 5C4B447F1E42A6E2002651C8 /* v3_pcons.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_pcons.c; sourceTree = ""; }; - 5C4B44811E42A6E2002651C8 /* v3_pmaps.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_pmaps.c; sourceTree = ""; }; - 5C4B44821E42A6E2002651C8 /* v3_prn.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_prn.c; sourceTree = ""; }; - 5C4B44831E42A6E2002651C8 /* v3_purp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_purp.c; sourceTree = ""; }; - 5C4B44841E42A6E2002651C8 /* v3_skey.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_skey.c; sourceTree = ""; }; - 5C4B44861E42A6E2002651C8 /* v3_utl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = v3_utl.c; sourceTree = ""; }; 5C4B44AC1E42A6F7002651C8 /* a_digest.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = a_digest.c; sourceTree = ""; }; 5C4B44AD1E42A6F7002651C8 /* a_sign.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = a_sign.c; sourceTree = ""; }; 5C4B44AF1E42A6F7002651C8 /* a_verify.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = a_verify.c; sourceTree = ""; }; @@ -9678,9 +9693,7 @@ 5C4B44C11E42A6F7002651C8 /* x_attrib.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x_attrib.c; sourceTree = ""; }; 5C4B44C21E42A6F7002651C8 /* x_crl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x_crl.c; sourceTree = ""; }; 5C4B44C31E42A6F7002651C8 /* x_exten.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x_exten.c; sourceTree = ""; }; - 5C4B44C41E42A6F7002651C8 /* x_info.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x_info.c; sourceTree = ""; }; 5C4B44C51E42A6F7002651C8 /* x_name.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x_name.c; sourceTree = ""; }; - 5C4B44C61E42A6F7002651C8 /* x_pkey.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x_pkey.c; sourceTree = ""; }; 5C4B44C71E42A6F7002651C8 /* x_pubkey.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x_pubkey.c; sourceTree = ""; }; 5C4B44C81E42A6F7002651C8 /* x_req.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x_req.c; sourceTree = ""; }; 5C4B44C91E42A6F7002651C8 /* x_sig.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x_sig.c; sourceTree = ""; }; @@ -9711,8 +9724,6 @@ 5C4B451F1E42A71B002651C8 /* ex_data.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ex_data.c; sourceTree = ""; }; 5C4B45201E42A71B002651C8 /* internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = internal.h; sourceTree = ""; }; 5C4B45211E42A71B002651C8 /* mem.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mem.c; sourceTree = ""; }; - 5C4B45221E42A71B002651C8 /* refcount_c11.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = refcount_c11.c; sourceTree = ""; }; - 5C4B45231E42A71B002651C8 /* refcount_lock.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = refcount_lock.c; sourceTree = ""; }; 5C4B45251E42A71B002651C8 /* thread_none.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = thread_none.c; sourceTree = ""; }; 5C4B45261E42A71B002651C8 /* thread_pthread.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = thread_pthread.c; sourceTree = ""; }; 5C4B45281E42A71B002651C8 /* thread_win.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = thread_win.c; sourceTree = ""; }; @@ -12518,7 +12529,6 @@ 411927652A375B74007C09F6 /* kyber */ = { isa = PBXGroup; children = ( - 411927672A375B8B007C09F6 /* keccak.c */, 411927662A375B8A007C09F6 /* kyber.c */, ); path = kyber; @@ -14749,6 +14759,7 @@ 419C847B1FE256A80040C30F /* fiat */ = { isa = PBXGroup; children = ( + 41ABB1292BFE00810035D075 /* p256_64.h */, ); path = fiat; sourceTree = ""; @@ -15141,7 +15152,9 @@ children = ( 41A3920B1EFC4AFE00C4516A /* deterministic.c */, 41A3920C1EFC4AFE00C4516A /* forkunsafe.c */, - 41A3920D1EFC4AFE00C4516A /* fuchsia.c */, + 41ABB15B2BFE01AC0035D075 /* getentropy.c */, + 41ABB1B82BFE3FB90035D075 /* ios.c */, + 41ABB15C2BFE01AC0035D075 /* passive.c */, 41A3920E1EFC4AFE00C4516A /* rand_extra.c */, 41A3920F1EFC4AFE00C4516A /* windows.c */, ); @@ -15163,6 +15176,23 @@ name = "Recovered References"; sourceTree = ""; }; + 41AA85782B1F5F3400756EEB /* crc32 */ = { + isa = PBXGroup; + children = ( + 41AA85792B1F5F4900756EEB /* hash_arm_crc32.c */, + ); + path = crc32; + sourceTree = ""; + }; + 41ABB1242BFDFFE80035D075 /* keccak */ = { + isa = PBXGroup; + children = ( + 41ABB1262BFE00210035D075 /* internal.h */, + 41ABB1252BFE00210035D075 /* keccak.c */, + ); + path = keccak; + sourceTree = ""; + }; 41B8D77A28C8880B00E5FA37 /* timing */ = { isa = PBXGroup; children = ( @@ -16991,6 +17021,7 @@ 5C4B43FB1E42A4F1002651C8 /* bio.c */, 5C4B43FA1E42A4F1002651C8 /* bio_mem.c */, 5C4B43FD1E42A4F1002651C8 /* connect.c */, + 41ABB1612BFE03010035D075 /* errno.c */, 5C4B43FE1E42A4F1002651C8 /* fd.c */, 5C4B43FF1E42A4F1002651C8 /* file.c */, 5C4B44001E42A4F1002651C8 /* hexdump.c */, @@ -17064,6 +17095,8 @@ 5C4B45B91E42A87E002651C8 /* evp_asn1.c */, 5C4B45BA1E42A87E002651C8 /* evp_ctx.c */, 5C4B45BC1E42A87E002651C8 /* internal.h */, + 41ABB1BD2BFE40190035D075 /* p_dh.c */, + 41ABB1BC2BFE40190035D075 /* p_dh_asn1.c */, 5C4B45BD1E42A87E002651C8 /* p_dsa_asn1.c */, 5C4B45BF1E42A87E002651C8 /* p_ec.c */, 5C4B45BE1E42A87E002651C8 /* p_ec_asn1.c */, @@ -17076,6 +17109,7 @@ 4131BE76234B85A90028A615 /* p_x25519_asn1.c */, 5C4B45C31E42A87E002651C8 /* pbkdf.c */, 5C4B45C41E42A87E002651C8 /* print.c */, + 41ABB1BB2BFE40190035D075 /* scrypt.c */, 5C4B45C51E42A87E002651C8 /* sign.c */, ); path = evp; @@ -17169,6 +17203,29 @@ 5C4B44BB1E42A6F7002651C8 /* t_req.c */, 5C4B44BC1E42A6F7002651C8 /* t_x509.c */, 5C4B44BD1E42A6F7002651C8 /* t_x509a.c */, + 41ABB12C2BFE00BA0035D075 /* v3_akey.c */, + 41ABB1312BFE00BC0035D075 /* v3_akeya.c */, + 41ABB1372BFE00BF0035D075 /* v3_alt.c */, + 41ABB1322BFE00BD0035D075 /* v3_bcons.c */, + 41ABB1402BFE00C30035D075 /* v3_bitst.c */, + 41ABB1382BFE00BF0035D075 /* v3_conf.c */, + 41ABB13C2BFE00C10035D075 /* v3_cpols.c */, + 41ABB13F2BFE00C20035D075 /* v3_crld.c */, + 41ABB1392BFE00BF0035D075 /* v3_enum.c */, + 41ABB1412BFE00C30035D075 /* v3_extku.c */, + 41ABB12F2BFE00BB0035D075 /* v3_genn.c */, + 41ABB1332BFE00BD0035D075 /* v3_ia5.c */, + 41ABB1342BFE00BD0035D075 /* v3_info.c */, + 41ABB13B2BFE00C00035D075 /* v3_int.c */, + 41ABB1362BFE00BE0035D075 /* v3_lib.c */, + 41ABB13A2BFE00C00035D075 /* v3_ncons.c */, + 41ABB13E2BFE00C20035D075 /* v3_ocsp.c */, + 41ABB12B2BFE00BA0035D075 /* v3_pcons.c */, + 41ABB13D2BFE00C10035D075 /* v3_pmaps.c */, + 41ABB12D2BFE00BA0035D075 /* v3_prn.c */, + 41ABB12E2BFE00BB0035D075 /* v3_purp.c */, + 41ABB1352BFE00BE0035D075 /* v3_skey.c */, + 41ABB1302BFE00BC0035D075 /* v3_utl.c */, 5C4B44DE1E42A6F7002651C8 /* x509.c */, 5C4B44CE1E42A6F7002651C8 /* x509_att.c */, 5C4B44CF1E42A6F7002651C8 /* x509_cmp.c */, @@ -17194,9 +17251,7 @@ 5C4B44C11E42A6F7002651C8 /* x_attrib.c */, 5C4B44C21E42A6F7002651C8 /* x_crl.c */, 5C4B44C31E42A6F7002651C8 /* x_exten.c */, - 5C4B44C41E42A6F7002651C8 /* x_info.c */, 5C4B44C51E42A6F7002651C8 /* x_name.c */, - 5C4B44C61E42A6F7002651C8 /* x_pkey.c */, 5C4B44C71E42A6F7002651C8 /* x_pubkey.c */, 5C4B44C81E42A6F7002651C8 /* x_req.c */, 5C4B44C91E42A6F7002651C8 /* x_sig.c */, @@ -17208,37 +17263,6 @@ path = x509; sourceTree = ""; }; - 5C4B44631E42A6A7002651C8 /* x509v3 */ = { - isa = PBXGroup; - children = ( - 5C4B44641E42A6E2002651C8 /* ext_dat.h */, - 5C4B446D1E42A6E2002651C8 /* v3_akey.c */, - 5C4B446E1E42A6E2002651C8 /* v3_akeya.c */, - 5C4B446F1E42A6E2002651C8 /* v3_alt.c */, - 5C4B44701E42A6E2002651C8 /* v3_bcons.c */, - 5C4B44711E42A6E2002651C8 /* v3_bitst.c */, - 5C4B44721E42A6E2002651C8 /* v3_conf.c */, - 5C4B44731E42A6E2002651C8 /* v3_cpols.c */, - 5C4B44741E42A6E2002651C8 /* v3_crld.c */, - 5C4B44751E42A6E2002651C8 /* v3_enum.c */, - 5C4B44761E42A6E2002651C8 /* v3_extku.c */, - 5C4B44771E42A6E2002651C8 /* v3_genn.c */, - 5C4B44781E42A6E2002651C8 /* v3_ia5.c */, - 5C4B44791E42A6E2002651C8 /* v3_info.c */, - 5C4B447A1E42A6E2002651C8 /* v3_int.c */, - 5C4B447B1E42A6E2002651C8 /* v3_lib.c */, - 5C4B447C1E42A6E2002651C8 /* v3_ncons.c */, - 415449A321CAC371001C0A55 /* v3_ocsp.c */, - 5C4B447F1E42A6E2002651C8 /* v3_pcons.c */, - 5C4B44811E42A6E2002651C8 /* v3_pmaps.c */, - 5C4B44821E42A6E2002651C8 /* v3_prn.c */, - 5C4B44831E42A6E2002651C8 /* v3_purp.c */, - 5C4B44841E42A6E2002651C8 /* v3_skey.c */, - 5C4B44861E42A6E2002651C8 /* v3_utl.c */, - ); - path = x509v3; - sourceTree = ""; - }; 5C4B47D41E42C00F002651C8 /* conf */ = { isa = PBXGroup; children = ( @@ -17275,6 +17299,7 @@ 419C84431FE255F00040C30F /* ssl_buffer.cc */, 419C84581FE255F70040C30F /* ssl_cert.cc */, 419C84481FE255F10040C30F /* ssl_cipher.cc */, + 41ABB15F2BFE01EF0035D075 /* ssl_credential.cc */, 419C84551FE255F60040C30F /* ssl_file.cc */, 419C845B1FE255F90040C30F /* ssl_key_share.cc */, 419C84591FE255F80040C30F /* ssl_lib.cc */, @@ -18024,6 +18049,7 @@ 41EA53A21EFC2BAF002FF04C /* fipsmodule */, 41D728FD2665074200651A0B /* hpke */, 4131BE73234B85380028A615 /* hrss */, + 41ABB1242BFDFFE80035D075 /* keccak */, 411927652A375B74007C09F6 /* kyber */, 5C4B44521E42A61A002651C8 /* lhash */, 5C4B44571E42A645002651C8 /* obj */, @@ -18038,13 +18064,11 @@ 411927792A375E31007C09F6 /* siphash */, 5C4B44611E42A690002651C8 /* stack */, 5C4B44621E42A698002651C8 /* x509 */, - 5C4B44631E42A6A7002651C8 /* x509v3 */, 5C4B451E1E42A71B002651C8 /* crypto.c */, 5C4B451F1E42A71B002651C8 /* ex_data.c */, 5C4B45201E42A71B002651C8 /* internal.h */, 5C4B45211E42A71B002651C8 /* mem.c */, - 5C4B45221E42A71B002651C8 /* refcount_c11.c */, - 5C4B45231E42A71B002651C8 /* refcount_lock.c */, + 41ABB1592BFE01590035D075 /* refcount.c */, 5C4B45291E42A71B002651C8 /* thread.c */, 5C4B45251E42A71B002651C8 /* thread_none.c */, 5C4B45261E42A71B002651C8 /* thread_pthread.c */, @@ -20825,8 +20849,8 @@ files = ( 5C4B47DD1E42C029002651C8 /* conf_def.h in Headers */, 412FF947254B10A5001DF036 /* curve25519_tables.h in Headers */, - 5C4B46491E42AA34002651C8 /* ext_dat.h in Headers */, 412FF94B254B10EF001DF036 /* fork_detect.h in Headers */, + 41ABB1282BFE00220035D075 /* internal.h in Headers */, 412FF946254B10A5001DF036 /* internal.h in Headers */, 410091CF242CFD6500C5EDA2 /* internal.h in Headers */, 41A391B31EFC454F00C4516A /* internal.h in Headers */, @@ -20848,6 +20872,7 @@ 41A391851EFC447C00C4516A /* internal.h in Headers */, 41EA53AE1EFC2C4D002FF04C /* md32_common.h in Headers */, 5C4B46C91E42AA70002651C8 /* obj_dat.h in Headers */, + 41ABB12A2BFE00810035D075 /* p256_64.h in Headers */, 41EA540F1EFC2D1B002FF04C /* rsaz_exp.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -24823,6 +24848,7 @@ 5C4B46F21E42AAA1002651C8 /* engine.c in Sources */, 5C4B46F11E42AA9C002651C8 /* err.c in Sources */, 411927832A37618C007C09F6 /* err_data.c in Sources */, + 41ABB1622BFE03020035D075 /* errno.c in Sources */, 5C4B46E61E42AA97002651C8 /* evp.c in Sources */, 5C4B46E41E42AA97002651C8 /* evp_asn1.c in Sources */, 5C4B46E51E42AA97002651C8 /* evp_ctx.c in Sources */, @@ -24842,6 +24868,7 @@ 41A3917A1EFC447C00C4516A /* gcm.c in Sources */, 410091D2242CFF6F00C5EDA2 /* gcm_nohw.c in Sources */, 41EA54061EFC2D1B002FF04C /* generic.c in Sources */, + 41ABB15D2BFE01AD0035D075 /* getentropy.c in Sources */, 41D729032665079700651A0B /* handoff.cc in Sources */, 5CFACF3C226E970C0056C7D0 /* handshake.cc in Sources */, 5CFACF3D226E970F0056C7D0 /* handshake_client.cc in Sources */, @@ -24852,9 +24879,10 @@ 41D728FF2665075A00651A0B /* hpke.c in Sources */, 4131BE75234B855A0028A615 /* hrss.c in Sources */, 5C4B46761E42AA3C002651C8 /* i2d_pr.c in Sources */, + 41ABB1BA2BFE3FBF0035D075 /* ios.c in Sources */, 41EA54081EFC2D1B002FF04C /* jacobi.c in Sources */, 415449AB21CAC3CF001C0A55 /* kdf.c in Sources */, - 411927692A375B8B007C09F6 /* keccak.c in Sources */, + 41ABB1272BFE00220035D075 /* keccak.c in Sources */, 41A392001EFC493000C4516A /* key_wrap.c in Sources */, 411927682A375B8B007C09F6 /* kyber.c in Sources */, 5C4B46DF1E42AA89002651C8 /* lhash.c in Sources */, @@ -24873,6 +24901,8 @@ 41EA53CA1EFC2C8B002FF04C /* p224-64.c in Sources */, 412FF94D254B1109001DF036 /* p256.c in Sources */, 5C4B46BC1E42AA66002651C8 /* p5_pbev2.c in Sources */, + 41ABB1C02BFE40190035D075 /* p_dh.c in Sources */, + 41ABB1BF2BFE40190035D075 /* p_dh_asn1.c in Sources */, 5C4B46E81E42AA97002651C8 /* p_dsa_asn1.c in Sources */, 5C4B46EA1E42AA97002651C8 /* p_ec.c in Sources */, 5C4B46E91E42AA97002651C8 /* p_ec_asn1.c in Sources */, @@ -24886,6 +24916,7 @@ 41A391861EFC447C00C4516A /* padding.c in Sources */, 5C4B47421E42AAEA002651C8 /* pair.c in Sources */, 41D728F42665065300651A0B /* params.c in Sources */, + 41ABB15E2BFE01AD0035D075 /* passive.c in Sources */, 5C4B46EE1E42AA97002651C8 /* pbkdf.c in Sources */, 5C4B46C11E42AA6B002651C8 /* pem_all.c in Sources */, 5C4B46C21E42AA6B002651C8 /* pem_info.c in Sources */, @@ -24912,8 +24943,7 @@ 413111BB2552A0B2001B9D95 /* rand_extra.c in Sources */, 41EA540D1EFC2D1B002FF04C /* random.c in Sources */, 5C4B46B01E42AA51002651C8 /* rc4.c in Sources */, - 5C4B46401E42AA2C002651C8 /* refcount_c11.c in Sources */, - 5C4B46411E42AA2C002651C8 /* refcount_lock.c in Sources */, + 41ABB15A2BFE015A0035D075 /* refcount.c in Sources */, 41A391871EFC447C00C4516A /* rsa.c in Sources */, 41A391D31EFC484D00C4516A /* rsa_asn1.c in Sources */, 4119276B2A375BB0007C09F6 /* rsa_crypt.c in Sources */, @@ -24924,6 +24954,7 @@ 5CFACF40226E971C0056C7D0 /* s3_lib.cc in Sources */, 5CFACF41226E971F0056C7D0 /* s3_pkt.cc in Sources */, 415449B121CAC3F5001C0A55 /* scalar.c in Sources */, + 41ABB1BE2BFE40190035D075 /* scrypt.c in Sources */, 41A391741EFC447C00C4516A /* sha1.c in Sources */, 41A391751EFC447C00C4516A /* sha256.c in Sources */, 41A391761EFC447C00C4516A /* sha512.c in Sources */, @@ -24941,6 +24972,7 @@ 5CFACF45226E973C0056C7D0 /* ssl_buffer.cc in Sources */, 5CFACF46226E97400056C7D0 /* ssl_cert.cc in Sources */, 5CFACF47226E975D0056C7D0 /* ssl_cipher.cc in Sources */, + 41ABB1602BFE01EF0035D075 /* ssl_credential.cc in Sources */, 5CFACF48226E97610056C7D0 /* ssl_file.cc in Sources */, 5CFACF49226E97650056C7D0 /* ssl_key_share.cc in Sources */, 5CFACF33226E96BD0056C7D0 /* ssl_lib.cc in Sources */, @@ -24976,29 +25008,29 @@ 415449A221CAC34D001C0A55 /* unicode.c in Sources */, 41A391831EFC447C00C4516A /* urandom.c in Sources */, 415449B621CAC4CE001C0A55 /* util.c in Sources */, - 5C4B46521E42AA34002651C8 /* v3_akey.c in Sources */, - 5C4B46531E42AA34002651C8 /* v3_akeya.c in Sources */, - 5C4B46541E42AA34002651C8 /* v3_alt.c in Sources */, - 5C4B46551E42AA34002651C8 /* v3_bcons.c in Sources */, - 5C4B46561E42AA34002651C8 /* v3_bitst.c in Sources */, - 5C4B46571E42AA34002651C8 /* v3_conf.c in Sources */, - 5C4B46581E42AA34002651C8 /* v3_cpols.c in Sources */, - 5C4B46591E42AA34002651C8 /* v3_crld.c in Sources */, - 5C4B465A1E42AA34002651C8 /* v3_enum.c in Sources */, - 5C4B465B1E42AA34002651C8 /* v3_extku.c in Sources */, - 5C4B465C1E42AA34002651C8 /* v3_genn.c in Sources */, - 5C4B465D1E42AA34002651C8 /* v3_ia5.c in Sources */, - 5C4B465E1E42AA34002651C8 /* v3_info.c in Sources */, - 5C4B465F1E42AA34002651C8 /* v3_int.c in Sources */, - 5C4B46601E42AA34002651C8 /* v3_lib.c in Sources */, - 5C4B46611E42AA34002651C8 /* v3_ncons.c in Sources */, - 415449A421CAC372001C0A55 /* v3_ocsp.c in Sources */, - 5C4B46641E42AA34002651C8 /* v3_pcons.c in Sources */, - 5C4B46661E42AA34002651C8 /* v3_pmaps.c in Sources */, - 5C4B46671E42AA34002651C8 /* v3_prn.c in Sources */, - 5C4B46681E42AA34002651C8 /* v3_purp.c in Sources */, - 5C4B46691E42AA34002651C8 /* v3_skey.c in Sources */, - 5C4B466B1E42AA34002651C8 /* v3_utl.c in Sources */, + 41ABB1432BFE00C30035D075 /* v3_akey.c in Sources */, + 41ABB1482BFE00C30035D075 /* v3_akeya.c in Sources */, + 41ABB14E2BFE00C30035D075 /* v3_alt.c in Sources */, + 41ABB1492BFE00C30035D075 /* v3_bcons.c in Sources */, + 41ABB1572BFE00C30035D075 /* v3_bitst.c in Sources */, + 41ABB14F2BFE00C30035D075 /* v3_conf.c in Sources */, + 41ABB1532BFE00C30035D075 /* v3_cpols.c in Sources */, + 41ABB1562BFE00C30035D075 /* v3_crld.c in Sources */, + 41ABB1502BFE00C30035D075 /* v3_enum.c in Sources */, + 41ABB1582BFE00C30035D075 /* v3_extku.c in Sources */, + 41ABB1462BFE00C30035D075 /* v3_genn.c in Sources */, + 41ABB14A2BFE00C30035D075 /* v3_ia5.c in Sources */, + 41ABB14B2BFE00C30035D075 /* v3_info.c in Sources */, + 41ABB1522BFE00C30035D075 /* v3_int.c in Sources */, + 41ABB14D2BFE00C30035D075 /* v3_lib.c in Sources */, + 41ABB1512BFE00C30035D075 /* v3_ncons.c in Sources */, + 41ABB1552BFE00C30035D075 /* v3_ocsp.c in Sources */, + 41ABB1422BFE00C30035D075 /* v3_pcons.c in Sources */, + 41ABB1542BFE00C30035D075 /* v3_pmaps.c in Sources */, + 41ABB1442BFE00C30035D075 /* v3_prn.c in Sources */, + 41ABB1452BFE00C30035D075 /* v3_purp.c in Sources */, + 41ABB14C2BFE00C30035D075 /* v3_skey.c in Sources */, + 41ABB1472BFE00C30035D075 /* v3_utl.c in Sources */, 41EA53D21EFC2C8B002FF04C /* wnaf.c in Sources */, 41A3921A1EFC5AB800C4516A /* x25519-asm-arm.S in Sources */, 5C4B469F1E42AA3C002651C8 /* x509.c in Sources */, @@ -25026,9 +25058,7 @@ 5C4B46821E42AA3C002651C8 /* x_attrib.c in Sources */, 5C4B46831E42AA3C002651C8 /* x_crl.c in Sources */, 5C4B46841E42AA3C002651C8 /* x_exten.c in Sources */, - 5C4B46851E42AA3C002651C8 /* x_info.c in Sources */, 5C4B46861E42AA3C002651C8 /* x_name.c in Sources */, - 5C4B46871E42AA3C002651C8 /* x_pkey.c in Sources */, 5C4B46881E42AA3C002651C8 /* x_pubkey.c in Sources */, 5C4B46891E42AA3C002651C8 /* x_req.c in Sources */, 5C4B468A1E42AA3C002651C8 /* x_sig.c in Sources */, diff --git a/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml b/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml index 440b8ec464531..151f2cc1db173 100644 --- a/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml +++ b/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml @@ -856,7 +856,7 @@ BuiltInNotificationsEnabled: humanReadableName: "Built-In Web Notifications" humanReadableDescription: "Enable built-in WebKit managed notifications" webcoreBinding: DeprecatedGlobalSettings - condition: ENABLE(BUILT_IN_NOTIFICATIONS) + condition: ENABLE(WEB_PUSH_NOTIFICATIONS) defaultValue: WebCore: default: false diff --git a/Source/WTF/WTF.xcodeproj/project.pbxproj b/Source/WTF/WTF.xcodeproj/project.pbxproj index 350ec1e00fde9..ddd8479016a15 100644 --- a/Source/WTF/WTF.xcodeproj/project.pbxproj +++ b/Source/WTF/WTF.xcodeproj/project.pbxproj @@ -197,6 +197,7 @@ AD89B6B71E6415080090707F /* MemoryPressureHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AD89B6B51E6415080090707F /* MemoryPressureHandler.cpp */; }; AD89B6BA1E64150F0090707F /* MemoryPressureHandlerCocoa.mm in Sources */ = {isa = PBXBuildFile; fileRef = AD89B6B91E64150F0090707F /* MemoryPressureHandlerCocoa.mm */; }; ADF2CE671E39F106006889DB /* MemoryFootprintCocoa.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ADF2CE651E39F106006889DB /* MemoryFootprintCocoa.cpp */; }; + BC5267672C2726D600E422DD /* MakeString.h in Headers */ = {isa = PBXBuildFile; fileRef = BC5267662C2726D600E422DD /* MakeString.h */; settings = {ATTRIBUTES = (Private, ); }; }; C2BCFC401F61D13000C9222C /* Language.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2BCFC3E1F61D13000C9222C /* Language.cpp */; }; C2BCFC421F61D61600C9222C /* LanguageCF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2BCFC411F61D61600C9222C /* LanguageCF.cpp */; }; C2BCFC551F621F3F00C9222C /* LineEnding.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2BCFC531F621F3F00C9222C /* LineEnding.cpp */; }; @@ -595,7 +596,6 @@ DDF307D427C086DF006A526F /* TextBreakIteratorICU.h in Headers */ = {isa = PBXBuildFile; fileRef = 1CCDB14D1E566898006C73C0 /* TextBreakIteratorICU.h */; settings = {ATTRIBUTES = (Private, ); }; }; DDF307D527C086DF006A526F /* LineEnding.h in Headers */ = {isa = PBXBuildFile; fileRef = C2BCFC541F621F3F00C9222C /* LineEnding.h */; settings = {ATTRIBUTES = (Private, ); }; }; DDF307D627C086DF006A526F /* NullTextBreakIterator.h in Headers */ = {isa = PBXBuildFile; fileRef = DD4901ED27B474D900D7E50D /* NullTextBreakIterator.h */; settings = {ATTRIBUTES = (Private, ); }; }; - DDF307D727C086DF006A526F /* StringOperators.h in Headers */ = {isa = PBXBuildFile; fileRef = A8A4732A151A825B004123FF /* StringOperators.h */; settings = {ATTRIBUTES = (Private, ); }; }; DDF307D827C086DF006A526F /* StringBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = A8A47323151A825B004123FF /* StringBuffer.h */; settings = {ATTRIBUTES = (Private, ); }; }; DDF307D927C086DF006A526F /* StringBuilderInternals.h in Headers */ = {isa = PBXBuildFile; fileRef = A8A47325151A825B004123EE /* StringBuilderInternals.h */; settings = {ATTRIBUTES = (Private, ); }; }; DDF307DA27C086DF006A526F /* SymbolImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 70ECA60B1B02426800449739 /* SymbolImpl.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -1472,7 +1472,6 @@ A8A47327151A825B004123FF /* StringHash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringHash.h; sourceTree = ""; }; A8A47328151A825B004123FF /* StringImpl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StringImpl.cpp; sourceTree = ""; }; A8A47329151A825B004123FF /* StringImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringImpl.h; sourceTree = ""; }; - A8A4732A151A825B004123FF /* StringOperators.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringOperators.h; sourceTree = ""; }; A8A4732C151A825B004123FF /* TextPosition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextPosition.h; sourceTree = ""; }; A8A4732D151A825B004123FF /* WTFString.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WTFString.cpp; sourceTree = ""; }; A8A4732E151A825B004123FF /* WTFString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WTFString.h; sourceTree = ""; }; @@ -1503,6 +1502,7 @@ AD89B6B91E64150F0090707F /* MemoryPressureHandlerCocoa.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MemoryPressureHandlerCocoa.mm; sourceTree = ""; }; ADF2CE641E39F106006889DB /* MemoryFootprint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MemoryFootprint.h; sourceTree = ""; }; ADF2CE651E39F106006889DB /* MemoryFootprintCocoa.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFootprintCocoa.cpp; sourceTree = ""; }; + BC5267662C2726D600E422DD /* MakeString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MakeString.h; sourceTree = ""; }; C2BCFC3E1F61D13000C9222C /* Language.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Language.cpp; sourceTree = ""; }; C2BCFC3F1F61D13000C9222C /* Language.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Language.h; sourceTree = ""; }; C2BCFC411F61D61600C9222C /* LanguageCF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LanguageCF.cpp; sourceTree = ""; }; @@ -2589,6 +2589,7 @@ 93AC91A718942FC400244939 /* LChar.h */, C2BCFC531F621F3F00C9222C /* LineEnding.cpp */, C2BCFC541F621F3F00C9222C /* LineEnding.h */, + BC5267662C2726D600E422DD /* MakeString.h */, DD4901ED27B474D900D7E50D /* NullTextBreakIterator.h */, 14E785E71DFB330100209BD1 /* OrdinalNumber.h */, 0F95B63620CB5EFD00479635 /* StringBuffer.cpp */, @@ -2606,7 +2607,6 @@ FF8CB4022A88C125004AF498 /* StringHasherInlines.h */, A8A47328151A825B004123FF /* StringImpl.cpp */, A8A47329151A825B004123FF /* StringImpl.h */, - A8A4732A151A825B004123FF /* StringOperators.h */, 7C3B06E824A116E600FD26C7 /* StringParsingBuffer.h */, E324FAA228C9ADBA007089DF /* StringSearch.h */, CD00360D21501F7800F4ED4C /* StringToIntegerConversion.h */, @@ -3309,6 +3309,7 @@ DD3DC8B627A4BF8E007E5B61 /* MainThread.h in Headers */, DD4901EC27B4748A00D7E50D /* MainThreadData.h in Headers */, 5187AC962C08570600549EFE /* MainThreadDispatcher.h in Headers */, + BC5267672C2726D600E422DD /* MakeString.h in Headers */, DD3DC8D427A4BF8E007E5B61 /* MallocPtr.h in Headers */, DD3DC89727A4BF8E007E5B61 /* Markable.h in Headers */, DD3DC89427A4BF8E007E5B61 /* MathExtras.h in Headers */, @@ -3480,7 +3481,6 @@ DDF307C927C086DF006A526F /* StringHasher.h in Headers */, FF8CB4032A88C125004AF498 /* StringHasherInlines.h in Headers */, DDF307CB27C086DF006A526F /* StringImpl.h in Headers */, - DDF307D727C086DF006A526F /* StringOperators.h in Headers */, DDF307C727C086DF006A526F /* StringParsingBuffer.h in Headers */, DD3DC90427A4BF8E007E5B61 /* StringPrintStream.h in Headers */, E324FAA328C9ADBA007089DF /* StringSearch.h in Headers */, diff --git a/Source/WTF/wtf/Assertions.cpp b/Source/WTF/wtf/Assertions.cpp index c07bbd0946a20..cc169827a36d7 100644 --- a/Source/WTF/wtf/Assertions.cpp +++ b/Source/WTF/wtf/Assertions.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include diff --git a/Source/WTF/wtf/CMakeLists.txt b/Source/WTF/wtf/CMakeLists.txt index eac0294a6b5a8..72581e25594fc 100644 --- a/Source/WTF/wtf/CMakeLists.txt +++ b/Source/WTF/wtf/CMakeLists.txt @@ -420,6 +420,7 @@ set(WTF_PUBLIC_HEADERS text/IntegerToStringConversion.h text/LChar.h text/LineEnding.h + text/MakeString.h text/NullTextBreakIterator.h text/OrdinalNumber.h text/StringBuffer.h @@ -434,7 +435,6 @@ set(WTF_PUBLIC_HEADERS text/SuperFastHash.h text/WYHash.h text/StringImpl.h - text/StringOperators.h text/StringParsingBuffer.h text/StringSearch.h text/StringToIntegerConversion.h diff --git a/Source/WTF/wtf/HexNumber.h b/Source/WTF/wtf/HexNumber.h index 5d2d81eb81f74..86dca7098d55a 100644 --- a/Source/WTF/wtf/HexNumber.h +++ b/Source/WTF/wtf/HexNumber.h @@ -21,8 +21,7 @@ #pragma once #include -#include -#include +#include namespace WTF { diff --git a/Source/WTF/wtf/JSONValues.cpp b/Source/WTF/wtf/JSONValues.cpp index 7b2fe12d8402a..a22c907801b54 100644 --- a/Source/WTF/wtf/JSONValues.cpp +++ b/Source/WTF/wtf/JSONValues.cpp @@ -35,6 +35,7 @@ #include #include +#include #include namespace WTF { diff --git a/Source/WTF/wtf/Logger.h b/Source/WTF/wtf/Logger.h index 7bbd0934e736d..d50fe8180e251 100644 --- a/Source/WTF/wtf/Logger.h +++ b/Source/WTF/wtf/Logger.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #if ENABLE(JOURNALD_LOG) diff --git a/Source/WTF/wtf/MediaTime.cpp b/Source/WTF/wtf/MediaTime.cpp index b3eb6d7dd33bd..81878c310a45d 100644 --- a/Source/WTF/wtf/MediaTime.cpp +++ b/Source/WTF/wtf/MediaTime.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include namespace WTF { diff --git a/Source/WTF/wtf/NativePromise.h b/Source/WTF/wtf/NativePromise.h index 731bcbd8c2224..b6c8844ce6f95 100644 --- a/Source/WTF/wtf/NativePromise.h +++ b/Source/WTF/wtf/NativePromise.h @@ -50,6 +50,7 @@ #include #include #include +#include namespace WTF { class NativePromiseRequest; diff --git a/Source/WTF/wtf/PlatformEnableCocoa.h b/Source/WTF/wtf/PlatformEnableCocoa.h index 8e504e5b2c661..2139555cc7106 100644 --- a/Source/WTF/wtf/PlatformEnableCocoa.h +++ b/Source/WTF/wtf/PlatformEnableCocoa.h @@ -239,8 +239,8 @@ #define ENABLE_AX_THREAD_TEXT_APIS 0 #endif -#if !defined(ENABLE_BUILT_IN_NOTIFICATIONS) && (PLATFORM(MAC) || PLATFORM(IOS) || PLATFORM(VISION)) -#define ENABLE_BUILT_IN_NOTIFICATIONS 1 +#if !defined(ENABLE_WEB_PUSH_NOTIFICATIONS) && (PLATFORM(MAC) || PLATFORM(IOS) || PLATFORM(VISION)) +#define ENABLE_WEB_PUSH_NOTIFICATIONS 1 #endif #if !defined(ENABLE_CACHE_PARTITIONING) diff --git a/Source/WTF/wtf/PlatformGTK.cmake b/Source/WTF/wtf/PlatformGTK.cmake index df59263cd4d45..2a343a3458a9d 100644 --- a/Source/WTF/wtf/PlatformGTK.cmake +++ b/Source/WTF/wtf/PlatformGTK.cmake @@ -39,6 +39,7 @@ list(APPEND WTF_PUBLIC_HEADERS glib/RunLoopSourcePriority.h glib/Sandbox.h glib/SocketConnection.h + glib/SysprofAnnotator.h glib/WTFGType.h linux/CurrentProcessMemoryStatus.h diff --git a/Source/WTF/wtf/PlatformHave.h b/Source/WTF/wtf/PlatformHave.h index 5589d3f60d9d1..51171c53d7c0f 100644 --- a/Source/WTF/wtf/PlatformHave.h +++ b/Source/WTF/wtf/PlatformHave.h @@ -531,7 +531,7 @@ #endif #if PLATFORM(COCOA) -#define HAVE_AVPLAYER_SUPRESSES_AUDIO_RENDERING 1 +#define HAVE_AVPLAYER_SUPPRESSES_AUDIO_RENDERING 1 #endif #if PLATFORM(IOS) || PLATFORM(MAC) || PLATFORM(VISION) @@ -787,12 +787,6 @@ #endif #endif -#if PLATFORM(IOS) || PLATFORM(MACCATALYST) || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 130000) || PLATFORM(VISION) -#if !defined(HAVE_AVSPEECHSYNTHESIS_SYSTEMVOICE) -#define HAVE_AVSPEECHSYNTHESIS_SYSTEMVOICE 1 -#endif -#endif - #if PLATFORM(IOS) || PLATFORM(MAC) || PLATFORM(VISION) || PLATFORM(APPLETV) #if !defined(HAVE_TCC_IOS_14_BIG_SUR_SPI) #define HAVE_TCC_IOS_14_BIG_SUR_SPI 1 @@ -1631,7 +1625,8 @@ #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 140000) \ || (PLATFORM(MACCATALYST) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 170000) \ - || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 170000) + || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 170000) \ + || PLATFORM(VISION) #define HAVE_AUDIOFORMATPROPERTY_VARIABLEPACKET_SUPPORTED 1 #endif @@ -1812,3 +1807,17 @@ && (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 150000) #define HAVE_NS_TEXT_CHECKING_TYPE_MATH_COMPLETION 1 #endif + +#if !defined(HAVE_PASSKIT_MERCHANT_CATEGORY_CODE) \ + && ((PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 150000) \ + || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 180000) \ + || (PLATFORM(VISION) && __VISION_OS_VERSION_MIN_REQUIRED >= 20000)) +#define HAVE_PASSKIT_MERCHANT_CATEGORY_CODE 1 +#endif + +#if !defined(HAVE_PASSKIT_DISBURSEMENTS) \ + && ((PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 150000) \ + || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 170000) \ + || PLATFORM(VISION)) +#define HAVE_PASSKIT_DISBURSEMENTS 1 +#endif diff --git a/Source/WTF/wtf/PlatformUse.h b/Source/WTF/wtf/PlatformUse.h index b24f12b32f5c2..401e6a1b55cc6 100644 --- a/Source/WTF/wtf/PlatformUse.h +++ b/Source/WTF/wtf/PlatformUse.h @@ -369,11 +369,6 @@ #define USE_CONCATENATED_IMPULSE_RESPONSES 1 #endif -#if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 130000) \ - || PLATFORM(IOS_FAMILY) -#define USE_VARIABLE_OPTICAL_SIZING 1 -#endif - #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MAX_ALLOWED < 150000) \ || (PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MAX_ALLOWED < 180000) \ || (PLATFORM(APPLETV) && __TV_OS_VERSION_MAX_ALLOWED < 180000) \ diff --git a/Source/WTF/wtf/PlatformWPE.cmake b/Source/WTF/wtf/PlatformWPE.cmake index 8d0094e73d009..f052d3f042423 100644 --- a/Source/WTF/wtf/PlatformWPE.cmake +++ b/Source/WTF/wtf/PlatformWPE.cmake @@ -42,6 +42,7 @@ list(APPEND WTF_PUBLIC_HEADERS glib/RunLoopSourcePriority.h glib/Sandbox.h glib/SocketConnection.h + glib/SysprofAnnotator.h glib/WTFGType.h linux/CurrentProcessMemoryStatus.h diff --git a/Source/WTF/wtf/SIMDHelpers.h b/Source/WTF/wtf/SIMDHelpers.h index 0d47889132af8..20ce2123a3b77 100644 --- a/Source/WTF/wtf/SIMDHelpers.h +++ b/Source/WTF/wtf/SIMDHelpers.h @@ -499,6 +499,34 @@ ALWAYS_INLINE const CharacterType* find(std::span span, con return end; } +template * 2> +requires(sizeof(CharacterType) == 2) +ALWAYS_INLINE const CharacterType* findInterleaved(std::span span, const auto& vectorMatch, const auto& scalarMatch) +{ + constexpr size_t stride = SIMD::stride * 2; + static_assert(threshold >= stride); + const auto* cursor = span.data(); + const auto* end = span.data() + span.size(); + if (span.size() >= threshold) { + for (; cursor + (stride - 1) < end; cursor += stride) { + if (auto index = vectorMatch(simde_vld2q_u8(bitwise_cast(cursor)))) + return cursor + index.value(); + } + if (cursor < end) { + if (auto index = vectorMatch(simde_vld2q_u8(bitwise_cast(end - stride)))) + return end - stride + index.value(); + } + return end; + } + + for (; cursor != end; ++cursor) { + auto character = *cursor; + if (scalarMatch(character)) + return cursor; + } + return end; +} + } namespace SIMD = WTF::SIMD; diff --git a/Source/WTF/wtf/SystemTracing.h b/Source/WTF/wtf/SystemTracing.h index e5c33def03b49..5f2310fb8630b 100644 --- a/Source/WTF/wtf/SystemTracing.h +++ b/Source/WTF/wtf/SystemTracing.h @@ -171,12 +171,24 @@ enum TracePointCode { #ifdef __cplusplus +// This has to be included after the TracePointCode enum. +#if USE(SYSPROF_CAPTURE) +#include +#endif + namespace WTF { inline void tracePoint(TracePointCode code, uint64_t data1 = 0, uint64_t data2 = 0, uint64_t data3 = 0, uint64_t data4 = 0) { #if HAVE(KDEBUG_H) kdebug_trace(ARIADNEDBG_CODE(WEBKIT_COMPONENT, code), data1, data2, data3, data4); +#elif USE(SYSPROF_CAPTURE) + if (auto* annotator = SysprofAnnotator::singletonIfCreated()) + annotator->tracePoint(code); + UNUSED_PARAM(data1); + UNUSED_PARAM(data2); + UNUSED_PARAM(data3); + UNUSED_PARAM(data4); #else UNUSED_PARAM(code); UNUSED_PARAM(data1); @@ -328,6 +340,43 @@ enum WTFOSSignpostType { #define WTFEmitSignpostIndirectlyWithType(type, pointer, name, timeDelta, format, ...) \ os_log(WTFSignpostLogHandle(), "type=%d name=%d p=%" PRIuPTR " ts=%llu " format, type, WTFOSSignpostName ## name, reinterpret_cast(pointer), WTFCurrentContinuousTime(timeDelta), ##__VA_ARGS__) +#elif USE(SYSPROF_CAPTURE) + +#define WTFEmitSignpost(pointer, name, ...) \ + do { \ + if (auto* annotator = SysprofAnnotator::singletonIfCreated()) \ + annotator->instantMark(std::span(_STRINGIFY(name)), " " __VA_ARGS__); \ + } while (0) + +#define WTFBeginSignpost(pointer, name, ...) \ + do { \ + if (auto* annotator = SysprofAnnotator::singletonIfCreated()) \ + annotator->beginMark(pointer, std::span(_STRINGIFY(name)), " " __VA_ARGS__); \ + } while (0) + +#define WTFEndSignpost(pointer, name, ...) \ + do { \ + if (auto* annotator = SysprofAnnotator::singletonIfCreated()) \ + annotator->endMark(pointer, std::span(_STRINGIFY(name)), " " __VA_ARGS__); \ + } while (0) + +#define WTFEmitSignpostAlways(pointer, name, ...) WTFEmitSignpost((pointer), name, ##__VA_ARGS__) +#define WTFBeginSignpostAlways(pointer, name, ...) WTFBeginSignpost((pointer), name, ##__VA_ARGS__) +#define WTFEndSignpostAlways(pointer, name, ...) WTFEndSignpost((pointer), name, ##__VA_ARGS__) + +#define WTFEmitSignpostWithTimeDelta(pointer, name, timeDelta, ...) \ + do { \ + if (auto* annotator = SysprofAnnotator::singletonIfCreated()) \ + annotator->mark((timeDelta), std::span(_STRINGIFY(name)), " " __VA_ARGS__); \ + } while (0) + +#define WTFBeginSignpostWithTimeDelta(pointer, name, timeDelta, ...) WTFEmitSignpostWithTimeDelta((pointer), name, (timeDelta), ##__VA_ARGS__) +#define WTFEndSignpostWithTimeDelta(pointer, name, timeDelta, ...) WTFEmitSignpostWithTimeDelta((pointer), name, (timeDelta), ##__VA_ARGS__) + +#define WTFEmitSignpostAlwaysWithTimeDelta(pointer, name, timeDelta, ...) WTFEmitSignpostWithTimeDelta((pointer), name, (timeDelta), ##__VA_ARGS__) +#define WTFBeginSignpostAlwaysWithTimeDelta(pointer, name, timeDelta, ...) WTFBeginSignpostWithTimeDelta((pointer), name, (timeDelta), ##__VA_ARGS__) +#define WTFEndSignpostAlwaysWithTimeDelta(pointer, name, timeDelta, ...) WTFEndSignpostWithTimeDelta((pointer), name, (timeDelta), ##__VA_ARGS__) + #else #define WTFEmitSignpost(pointer, name, ...) do { } while (0) diff --git a/Source/WTF/wtf/URL.cpp b/Source/WTF/wtf/URL.cpp index 2531c752dc9f8..5b2ec76b1b707 100644 --- a/Source/WTF/wtf/URL.cpp +++ b/Source/WTF/wtf/URL.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include diff --git a/Source/WTF/wtf/URLParser.cpp b/Source/WTF/wtf/URLParser.cpp index 55a79f97c46a2..2feb3c036bba4 100644 --- a/Source/WTF/wtf/URLParser.cpp +++ b/Source/WTF/wtf/URLParser.cpp @@ -30,6 +30,7 @@ #include #include #include +#include namespace WTF { diff --git a/Source/WTF/wtf/UUID.cpp b/Source/WTF/wtf/UUID.cpp index 705e25bb7cd62..930d1ce18e014 100644 --- a/Source/WTF/wtf/UUID.cpp +++ b/Source/WTF/wtf/UUID.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #if OS(DARWIN) diff --git a/Source/WTF/wtf/UUID.h b/Source/WTF/wtf/UUID.h index ac931ddbcadac..aaa8f977dc3a7 100644 --- a/Source/WTF/wtf/UUID.h +++ b/Source/WTF/wtf/UUID.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #ifdef __OBJC__ diff --git a/Source/WTF/wtf/cf/TypeCastsCF.h b/Source/WTF/wtf/cf/TypeCastsCF.h index 836f57002d55f..4b36d6a095722 100644 --- a/Source/WTF/wtf/cf/TypeCastsCF.h +++ b/Source/WTF/wtf/cf/TypeCastsCF.h @@ -36,33 +36,6 @@ namespace WTF { template struct CFTypeTrait; -#define WTF_DECLARE_CF_TYPE_TRAIT(ClassName) \ -template <> \ -struct WTF::CFTypeTrait { \ - static inline CFTypeID typeID(void) { return ClassName##GetTypeID(); } \ -}; - -WTF_DECLARE_CF_TYPE_TRAIT(CFArray); -WTF_DECLARE_CF_TYPE_TRAIT(CFBoolean); -WTF_DECLARE_CF_TYPE_TRAIT(CFData); -WTF_DECLARE_CF_TYPE_TRAIT(CFDictionary); -WTF_DECLARE_CF_TYPE_TRAIT(CFNumber); -WTF_DECLARE_CF_TYPE_TRAIT(CFString); -WTF_DECLARE_CF_TYPE_TRAIT(CFURL); - -#define WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(ClassName, MutableClassName) \ -template <> \ -struct WTF::CFTypeTrait { \ - static inline CFTypeID typeID(void) { return ClassName##GetTypeID(); } \ -}; - -WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(CFArray, CFMutableArray); -WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(CFData, CFMutableData); -WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(CFDictionary, CFMutableDictionary); -WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(CFString, CFMutableString); - -#undef WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT - // Use dynamic_cf_cast<> instead of checked_cf_cast<> when actively checking CF types, // similar to dynamic_cast<> in C++. Be sure to include a nullptr check. @@ -102,5 +75,32 @@ template T checked_cf_cast(CFTypeRef object) } // namespace WTF +#define WTF_DECLARE_CF_TYPE_TRAIT(ClassName) \ +template <> \ +struct WTF::CFTypeTrait { \ + static inline CFTypeID typeID(void) { return ClassName##GetTypeID(); } \ +}; + +WTF_DECLARE_CF_TYPE_TRAIT(CFArray); +WTF_DECLARE_CF_TYPE_TRAIT(CFBoolean); +WTF_DECLARE_CF_TYPE_TRAIT(CFData); +WTF_DECLARE_CF_TYPE_TRAIT(CFDictionary); +WTF_DECLARE_CF_TYPE_TRAIT(CFNumber); +WTF_DECLARE_CF_TYPE_TRAIT(CFString); +WTF_DECLARE_CF_TYPE_TRAIT(CFURL); + +#define WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(ClassName, MutableClassName) \ +template <> \ +struct WTF::CFTypeTrait { \ + static inline CFTypeID typeID(void) { return ClassName##GetTypeID(); } \ +}; + +WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(CFArray, CFMutableArray); +WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(CFData, CFMutableData); +WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(CFDictionary, CFMutableDictionary); +WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(CFString, CFMutableString); + +#undef WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT + using WTF::checked_cf_cast; using WTF::dynamic_cf_cast; diff --git a/Source/WTF/wtf/cocoa/ResourceUsageCocoa.cpp b/Source/WTF/wtf/cocoa/ResourceUsageCocoa.cpp index 561839c37a610..b4a581ae0864c 100644 --- a/Source/WTF/wtf/cocoa/ResourceUsageCocoa.cpp +++ b/Source/WTF/wtf/cocoa/ResourceUsageCocoa.cpp @@ -30,7 +30,7 @@ #include #include #include -#include +#include namespace WTF { diff --git a/Source/WTF/wtf/glib/Application.cpp b/Source/WTF/wtf/glib/Application.cpp index 3038b4d1b52e4..349c1ccbf367b 100644 --- a/Source/WTF/wtf/glib/Application.cpp +++ b/Source/WTF/wtf/glib/Application.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace WTF { diff --git a/Source/WTF/wtf/glib/SysprofAnnotator.h b/Source/WTF/wtf/glib/SysprofAnnotator.h new file mode 100644 index 0000000000000..a477ff05c1153 --- /dev/null +++ b/Source/WTF/wtf/glib/SysprofAnnotator.h @@ -0,0 +1,456 @@ +/* + * Copyright (C) 2024 Igalia, S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace WTF { + +class SysprofAnnotator final { + WTF_MAKE_NONCOPYABLE(SysprofAnnotator); + + using RawPointerPair = std::pair; + using TimestampAndString = std::pair>; + +public: + + static SysprofAnnotator* singletonIfCreated() + { + return s_annotator; + } + + void instantMark(std::span name, const char* description, ...) WTF_ATTRIBUTE_PRINTF(3, 4) + { + va_list args; + va_start(args, description); + sysprof_collector_mark_vprintf(SYSPROF_CAPTURE_CURRENT_TIME, 0, m_processName, name.data(), description, args); + va_end(args); + } + + void mark(Seconds timeDelta, std::span name, const char* description, ...) WTF_ATTRIBUTE_PRINTF(4, 5) + { + va_list args; + va_start(args, description); + sysprof_collector_mark_vprintf(SYSPROF_CAPTURE_CURRENT_TIME, timeDelta.microsecondsAs(), m_processName, name.data(), description, args); + va_end(args); + } + + void beginMark(const void* pointer, std::span name, const char* description, ...) WTF_ATTRIBUTE_PRINTF(4, 5) + { + auto key = std::make_pair(pointer, static_cast(name.data())); + + Vector buffer; + va_list args; + va_start(args, description); + vsnprintf(buffer.data(), 1024, description, args); + va_end(args); + + auto value = std::make_pair(SYSPROF_CAPTURE_CURRENT_TIME, WTFMove(buffer)); + + Locker locker { m_lock }; + m_ongoingMarks.set(key, value); + } + + void endMark(const void* pointer, std::span name, const char* description, ...) WTF_ATTRIBUTE_PRINTF(4, 5) + { + auto key = std::make_pair(pointer, static_cast(name.data())); + std::optional value; + + { + Locker locker { m_lock }; + + TimestampAndString v = m_ongoingMarks.take(key); + if (v.first) + value = WTFMove(v); + } + + if (value) { + int64_t startTime = std::get<0>(*value); + const Vector& description = std::get<1>(*value); + sysprof_collector_mark(startTime, SYSPROF_CAPTURE_CURRENT_TIME - startTime, m_processName, name.data(), description.data()); + } else { + va_list args; + va_start(args, description); + sysprof_collector_mark_vprintf(SYSPROF_CAPTURE_CURRENT_TIME, 0, m_processName, name.data(), description, args); + va_end(args); + } + } + + void tracePoint(TracePointCode code) + { + switch (code) { + case VMEntryScopeStart: + case WebAssemblyCompileStart: + case WebAssemblyExecuteStart: + case DumpJITMemoryStart: + case FromJSStart: + case IncrementalSweepStart: + case MainResourceLoadDidStartProvisional: + case SubresourceLoadWillStart: + case FetchCookiesStart: + case StyleRecalcStart: + case RenderTreeBuildStart: + case PerformLayoutStart: + case PaintLayerStart: + case AsyncImageDecodeStart: + case RAFCallbackStart: + case MemoryPressureHandlerStart: + case UpdateTouchRegionsStart: + case DisplayListRecordStart: + case ComputeEventRegionsStart: + case RenderingUpdateStart: + case CompositingUpdateStart: + case DispatchTouchEventsStart: + case ParseHTMLStart: + case DisplayListReplayStart: + case ScrollingThreadRenderUpdateSyncStart: + case ScrollingThreadDisplayDidRefreshStart: + case RenderTreeLayoutStart: + case PerformOpportunisticallyScheduledTasksStart: + case WebXRLayerStartFrameStart: + case WebXRLayerEndFrameStart: + case WebXRSessionFrameCallbacksStart: + case WebHTMLViewPaintStart: + case BackingStoreFlushStart: + case BuildTransactionStart: + case SyncMessageStart: + case SyncTouchEventStart: + case InitializeWebProcessStart: + case RenderingUpdateRunLoopObserverStart: + case LayerTreeFreezeStart: + case FlushRemoteImageBufferStart: + case CreateInjectedBundleStart: + case PaintSnapshotStart: + case RenderServerSnapshotStart: + case TakeSnapshotStart: + case SyntheticMomentumStart: + case CommitLayerTreeStart: + case ProcessLaunchStart: + case InitializeSandboxStart: + case WebXRCPFrameWaitStart: + case WebXRCPFrameStartSubmissionStart: + case WebXRCPFrameEndSubmissionStart: + case WakeUpAndApplyDisplayListStart: + beginMark(nullptr, tracePointCodeName(code).spanIncludingNullTerminator(), "%s", ""); + break; + + case VMEntryScopeEnd: + case WebAssemblyCompileEnd: + case WebAssemblyExecuteEnd: + case DumpJITMemoryStop: + case FromJSStop: + case IncrementalSweepEnd: + case MainResourceLoadDidEnd: + case SubresourceLoadDidEnd: + case FetchCookiesEnd: + case StyleRecalcEnd: + case RenderTreeBuildEnd: + case PerformLayoutEnd: + case PaintLayerEnd: + case AsyncImageDecodeEnd: + case RAFCallbackEnd: + case MemoryPressureHandlerEnd: + case UpdateTouchRegionsEnd: + case DisplayListRecordEnd: + case ComputeEventRegionsEnd: + case RenderingUpdateEnd: + case CompositingUpdateEnd: + case DispatchTouchEventsEnd: + case ParseHTMLEnd: + case DisplayListReplayEnd: + case ScrollingThreadRenderUpdateSyncEnd: + case ScrollingThreadDisplayDidRefreshEnd: + case RenderTreeLayoutEnd: + case PerformOpportunisticallyScheduledTasksEnd: + case WebXRLayerStartFrameEnd: + case WebXRLayerEndFrameEnd: + case WebXRSessionFrameCallbacksEnd: + case WebHTMLViewPaintEnd: + case BackingStoreFlushEnd: + case BuildTransactionEnd: + case SyncMessageEnd: + case SyncTouchEventEnd: + case InitializeWebProcessEnd: + case RenderingUpdateRunLoopObserverEnd: + case LayerTreeFreezeEnd: + case FlushRemoteImageBufferEnd: + case CreateInjectedBundleEnd: + case PaintSnapshotEnd: + case RenderServerSnapshotEnd: + case TakeSnapshotEnd: + case SyntheticMomentumEnd: + case CommitLayerTreeEnd: + case ProcessLaunchEnd: + case InitializeSandboxEnd: + case WebXRCPFrameWaitEnd: + case WebXRCPFrameStartSubmissionEnd: + case WebXRCPFrameEndSubmissionEnd: + case WakeUpAndApplyDisplayListEnd: + endMark(nullptr, tracePointCodeName(code).spanIncludingNullTerminator(), "%s", ""); + break; + + case DisplayRefreshDispatchingToMainThread: + case ScheduleRenderingUpdate: + case TriggerRenderingUpdate: + case ScrollingTreeDisplayDidRefresh: + case SyntheticMomentumEvent: + case RemoteLayerTreeScheduleRenderingUpdate: + instantMark(tracePointCodeName(code).spanIncludingNullTerminator(), "%s", ""); + break; + + case WTFRange: + case JavaScriptRange: + case WebCoreRange: + case WebKitRange: + case WebKit2Range: + case UIProcessRange: + case GPUProcessRange: + break; + } + } + + static void createIfNeeded(ASCIILiteral processName) + { + if (!getenv("SYSPROF_CONTROL_FD")) + return; + + static LazyNeverDestroyed instance; + static std::once_flag onceFlag; + std::call_once(onceFlag, [&] { + instance.construct(processName); + }); + } + +private: + friend class LazyNeverDestroyed; + + static ASCIILiteral tracePointCodeName(TracePointCode code) + { + switch (code) { + case VMEntryScopeStart: + case VMEntryScopeEnd: + return "VMEntryScope"_s; + case WebAssemblyCompileStart: + case WebAssemblyCompileEnd: + return "WebAssemblyCompile"_s; + case WebAssemblyExecuteStart: + case WebAssemblyExecuteEnd: + return "WebAssemblyExecute"_s; + case DumpJITMemoryStart: + case DumpJITMemoryStop: + return "DumpJITMemory"_s; + case FromJSStart: + case FromJSStop: + return "FromJS"_s; + case IncrementalSweepStart: + case IncrementalSweepEnd: + return "IncrementalSweep"_s; + + case MainResourceLoadDidStartProvisional: + case MainResourceLoadDidEnd: + return "MainResourceLoad"_s; + case SubresourceLoadWillStart: + case SubresourceLoadDidEnd: + return "SubresourceLoad"_s; + case FetchCookiesStart: + case FetchCookiesEnd: + return "FetchCookies"_s; + case StyleRecalcStart: + case StyleRecalcEnd: + return "StyleRecalc"_s; + case RenderTreeBuildStart: + case RenderTreeBuildEnd: + return "RenderTreeBuild"_s; + case PerformLayoutStart: + case PerformLayoutEnd: + return "PerformLayout"_s; + case PaintLayerStart: + case PaintLayerEnd: + return "PaintLayer"_s; + case AsyncImageDecodeStart: + case AsyncImageDecodeEnd: + return "AsyncImageDecode"_s; + case RAFCallbackStart: + case RAFCallbackEnd: + return "RAFCallback"_s; + case MemoryPressureHandlerStart: + case MemoryPressureHandlerEnd: + return "MemoryPressureHandler"_s; + case UpdateTouchRegionsStart: + case UpdateTouchRegionsEnd: + return "UpdateTouchRegions"_s; + case DisplayListRecordStart: + case DisplayListRecordEnd: + return "DisplayListRecord"_s; + case DisplayRefreshDispatchingToMainThread: + return "DisplayRefreshDispatchingToMainThread"_s; + case ComputeEventRegionsStart: + case ComputeEventRegionsEnd: + return "ComputeEventRegions"_s; + case ScheduleRenderingUpdate: + return "ScheduleRenderingUpdate"_s; + case TriggerRenderingUpdate: + return "TriggerRenderingUpdate"_s; + case RenderingUpdateStart: + case RenderingUpdateEnd: + return "RenderingUpdate"_s; + case CompositingUpdateStart: + case CompositingUpdateEnd: + return "CompositingUpdate"_s; + case DispatchTouchEventsStart: + case DispatchTouchEventsEnd: + return "DispatchTouchEvents"_s; + case ParseHTMLStart: + case ParseHTMLEnd: + return "ParseHTML"_s; + case DisplayListReplayStart: + case DisplayListReplayEnd: + return "DisplayListReplay"_s; + case ScrollingThreadRenderUpdateSyncStart: + case ScrollingThreadRenderUpdateSyncEnd: + return "ScrollingThreadRenderUpdateSync"_s; + case ScrollingThreadDisplayDidRefreshStart: + case ScrollingThreadDisplayDidRefreshEnd: + return "ScrollingThreadDisplayDidRefresh"_s; + case ScrollingTreeDisplayDidRefresh: + return "ScrollingTreeDisplayDidRefresh"_s; + case RenderTreeLayoutStart: + case RenderTreeLayoutEnd: + return "RenderTreeLayout"_s; + case PerformOpportunisticallyScheduledTasksStart: + case PerformOpportunisticallyScheduledTasksEnd: + return "PerformOpportunisticallyScheduledTasks"_s; + case WebXRLayerStartFrameStart: + case WebXRLayerStartFrameEnd: + return "WebXRLayerStartFrame"_s; + case WebXRLayerEndFrameStart: + case WebXRLayerEndFrameEnd: + return "WebXRLayerEndFrame"_s; + case WebXRSessionFrameCallbacksStart: + case WebXRSessionFrameCallbacksEnd: + return "WebXRSessionFrameCallbacks"_s; + + case WebHTMLViewPaintStart: + case WebHTMLViewPaintEnd: + return "WebHTMLViewPaint"_s; + + case BackingStoreFlushStart: + case BackingStoreFlushEnd: + return "BackingStoreFlush"_s; + case BuildTransactionStart: + case BuildTransactionEnd: + return "BuildTransaction"_s; + case SyncMessageStart: + case SyncMessageEnd: + return "SyncMessage"_s; + case SyncTouchEventStart: + case SyncTouchEventEnd: + return "SyncTouchEvent"_s; + case InitializeWebProcessStart: + case InitializeWebProcessEnd: + return "InitializeWebProcess"_s; + case RenderingUpdateRunLoopObserverStart: + case RenderingUpdateRunLoopObserverEnd: + return "RenderingUpdateRunLoopObserver"_s; + case LayerTreeFreezeStart: + case LayerTreeFreezeEnd: + return "LayerTreeFreeze"_s; + case FlushRemoteImageBufferStart: + case FlushRemoteImageBufferEnd: + return "FlushRemoteImageBuffer"_s; + case CreateInjectedBundleStart: + case CreateInjectedBundleEnd: + return "CreateInjectedBundle"_s; + case PaintSnapshotStart: + case PaintSnapshotEnd: + return "PaintSnapshot"_s; + case RenderServerSnapshotStart: + case RenderServerSnapshotEnd: + return "RenderServerSnapshot"_s; + case TakeSnapshotStart: + case TakeSnapshotEnd: + return "TakeSnapshot"_s; + case SyntheticMomentumStart: + case SyntheticMomentumEnd: + return "SyntheticMomentum"_s; + case SyntheticMomentumEvent: + return "SyntheticMomentumEvent"_s; + case RemoteLayerTreeScheduleRenderingUpdate: + return "RemoteLayerTreeScheduleRenderingUpdate"_s; + + case CommitLayerTreeStart: + case CommitLayerTreeEnd: + return "CommitLayerTree"_s; + case ProcessLaunchStart: + case ProcessLaunchEnd: + return "ProcessLaunch"_s; + case InitializeSandboxStart: + case InitializeSandboxEnd: + return "InitializeSandbox"_s; + case WebXRCPFrameWaitStart: + case WebXRCPFrameWaitEnd: + return "WebXRCPFrameWait"_s; + case WebXRCPFrameStartSubmissionStart: + case WebXRCPFrameStartSubmissionEnd: + return "WebXRCPFrameStartSubmission"_s; + case WebXRCPFrameEndSubmissionStart: + case WebXRCPFrameEndSubmissionEnd: + return "WebXRCPFrameEndSubmission"_s; + + case WakeUpAndApplyDisplayListStart: + case WakeUpAndApplyDisplayListEnd: + return "WakeUpAndApplyDisplayList"_s; + + case WTFRange: + case JavaScriptRange: + case WebCoreRange: + case WebKitRange: + case WebKit2Range: + case UIProcessRange: + case GPUProcessRange: + return nullptr; + } + + RELEASE_ASSERT_NOT_REACHED(); + } + + explicit SysprofAnnotator(ASCIILiteral processName) + : m_processName(processName) + { + sysprof_collector_init(); + s_annotator = this; + } + + ASCIILiteral m_processName; + Lock m_lock; + HashMap m_ongoingMarks WTF_GUARDED_BY_LOCK(m_lock); + static SysprofAnnotator* s_annotator; +}; + +inline SysprofAnnotator* SysprofAnnotator::s_annotator; + +} // namespace WTF + +using WTF::SysprofAnnotator; diff --git a/Source/WTF/wtf/posix/FileSystemPOSIX.cpp b/Source/WTF/wtf/posix/FileSystemPOSIX.cpp index e809a5a8f2b7e..c6920fae4a369 100644 --- a/Source/WTF/wtf/posix/FileSystemPOSIX.cpp +++ b/Source/WTF/wtf/posix/FileSystemPOSIX.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include diff --git a/Source/WTF/wtf/text/Base64.cpp b/Source/WTF/wtf/text/Base64.cpp index 26c76f31d9e30..222b601b83893 100644 --- a/Source/WTF/wtf/text/Base64.cpp +++ b/Source/WTF/wtf/text/Base64.cpp @@ -26,6 +26,7 @@ #include #include +#include namespace WTF { @@ -94,12 +95,12 @@ static const char base64URLDecMap[decodeMapSize] = { 0x31, 0x32, 0x33, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet }; -template static void base64EncodeInternal(std::span inputDataBuffer, std::span destinationDataBuffer, Base64EncodeMode mode) +template static void base64EncodeInternal(std::span inputDataBuffer, std::span destinationDataBuffer, OptionSet options) { ASSERT(destinationDataBuffer.size() > 0); - ASSERT(calculateBase64EncodedSize(inputDataBuffer.size(), mode) == destinationDataBuffer.size()); + ASSERT(calculateBase64EncodedSize(inputDataBuffer.size(), options) == destinationDataBuffer.size()); - auto encodeMap = (mode == Base64EncodeMode::URL) ? base64URLEncMap : base64EncMap; + auto encodeMap = options.contains(Base64EncodeOption::URL) ? base64URLEncMap : base64EncMap; unsigned sidx = 0; unsigned didx = 0; @@ -123,55 +124,57 @@ template static void base64EncodeInternal(std::span static void base64EncodeInternal(std::span input, std::span destinationDataBuffer, Base64EncodeMode mode) +template static void base64EncodeInternal(std::span input, std::span destinationDataBuffer, OptionSet options) { - base64EncodeInternal(asBytes(input), destinationDataBuffer, mode); + base64EncodeInternal(asBytes(input), destinationDataBuffer, options); } -static Vector base64EncodeInternal(std::span input, Base64EncodeMode mode) +static Vector base64EncodeInternal(std::span input, OptionSet options) { - auto destinationLength = calculateBase64EncodedSize(input.size(), mode); + auto destinationLength = calculateBase64EncodedSize(input.size(), options); if (!destinationLength) return { }; Vector destinationVector(destinationLength); - base64EncodeInternal(input, std::span(destinationVector), mode); + base64EncodeInternal(input, std::span(destinationVector), options); return destinationVector; } -void base64Encode(std::span input, std::span destination, Base64EncodeMode mode) +void base64Encode(std::span input, std::span destination, OptionSet options) { if (!destination.size()) return; - base64EncodeInternal(input, destination, mode); + base64EncodeInternal(input, destination, options); } -void base64Encode(std::span input, std::span destination, Base64EncodeMode mode) +void base64Encode(std::span input, std::span destination, OptionSet options) { if (!destination.size()) return; - base64EncodeInternal(input, destination, mode); + base64EncodeInternal(input, destination, options); } -Vector base64EncodeToVector(std::span input, Base64EncodeMode mode) +Vector base64EncodeToVector(std::span input, OptionSet options) { - return base64EncodeInternal(input, mode); + return base64EncodeInternal(input, options); } -String base64EncodeToString(std::span input, Base64EncodeMode mode) +String base64EncodeToString(std::span input, OptionSet options) { - return makeString(base64Encoded(input, mode)); + return makeString(base64Encoded(input, options)); } -String base64EncodeToStringReturnNullIfOverflow(std::span input, Base64EncodeMode mode) +String base64EncodeToStringReturnNullIfOverflow(std::span input, OptionSet options) { - return tryMakeString(base64Encoded(input, mode)); + return tryMakeString(base64Encoded(input, options)); } template diff --git a/Source/WTF/wtf/text/Base64.h b/Source/WTF/wtf/text/Base64.h index 5dc2efde00bdd..43155e5c0a731 100644 --- a/Source/WTF/wtf/text/Base64.h +++ b/Source/WTF/wtf/text/Base64.h @@ -29,12 +29,14 @@ #include #include -#include #include namespace WTF { -enum class Base64EncodeMode : bool { Default, URL }; +enum class Base64EncodeOption { + URL = 1 << 0, + OmitPadding = 1 << 1, +}; enum class Base64DecodeOption { URL = 1 << 0, @@ -44,28 +46,28 @@ enum class Base64DecodeOption { struct Base64Specification { std::span input; - Base64EncodeMode mode; + OptionSet options; }; // If the input string is pathologically large, just return nothing. // Rather than being perfectly precise, this is a bit conservative. static constexpr unsigned maximumBase64EncoderInputBufferSize = std::numeric_limits::max() / 77 * 76 / 4 * 3 - 2; -unsigned calculateBase64EncodedSize(unsigned inputLength, Base64EncodeMode); +unsigned calculateBase64EncodedSize(unsigned inputLength, OptionSet options); template bool isBase64OrBase64URLCharacter(CharacterType); -WTF_EXPORT_PRIVATE void base64Encode(std::span, std::span, Base64EncodeMode = Base64EncodeMode::Default); -WTF_EXPORT_PRIVATE void base64Encode(std::span, std::span, Base64EncodeMode = Base64EncodeMode::Default); +WTF_EXPORT_PRIVATE void base64Encode(std::span, std::span, OptionSet = { }); +WTF_EXPORT_PRIVATE void base64Encode(std::span, std::span, OptionSet = { }); -WTF_EXPORT_PRIVATE Vector base64EncodeToVector(std::span, Base64EncodeMode = Base64EncodeMode::Default); -Vector base64EncodeToVector(std::span, Base64EncodeMode = Base64EncodeMode::Default); +WTF_EXPORT_PRIVATE Vector base64EncodeToVector(std::span, OptionSet = { }); +Vector base64EncodeToVector(std::span, OptionSet = { }); -WTF_EXPORT_PRIVATE String base64EncodeToString(std::span, Base64EncodeMode = Base64EncodeMode::Default); -String base64EncodeToString(std::span, Base64EncodeMode = Base64EncodeMode::Default); +WTF_EXPORT_PRIVATE String base64EncodeToString(std::span, OptionSet = { }); +String base64EncodeToString(std::span, OptionSet = { }); -WTF_EXPORT_PRIVATE String base64EncodeToStringReturnNullIfOverflow(std::span, Base64EncodeMode = Base64EncodeMode::Default); -String base64EncodeToStringReturnNullIfOverflow(const CString&, Base64EncodeMode = Base64EncodeMode::Default); +WTF_EXPORT_PRIVATE String base64EncodeToStringReturnNullIfOverflow(std::span, OptionSet = { }); +String base64EncodeToStringReturnNullIfOverflow(const CString&, OptionSet = { }); WTF_EXPORT_PRIVATE std::optional> base64Decode(std::span, OptionSet = { }); WTF_EXPORT_PRIVATE std::optional> base64Decode(StringView, OptionSet = { }); @@ -89,31 +91,30 @@ std::optional> base64URLDecode(std::span); // Versions for use with StringBuilder / makeString. -Base64Specification base64Encoded(std::span, Base64EncodeMode = Base64EncodeMode::Default); -Base64Specification base64Encoded(std::span, Base64EncodeMode = Base64EncodeMode::Default); +Base64Specification base64Encoded(std::span, OptionSet = { }); +Base64Specification base64Encoded(std::span, OptionSet = { }); Base64Specification base64URLEncoded(std::span); Base64Specification base64URLEncoded(std::span); - -inline Vector base64EncodeToVector(std::span input, Base64EncodeMode mode) +inline Vector base64EncodeToVector(std::span input, OptionSet options) { - return base64EncodeToVector(std::as_bytes(input), mode); + return base64EncodeToVector(std::as_bytes(input), options); } -inline String base64EncodeToString(std::span input, Base64EncodeMode mode) +inline String base64EncodeToString(std::span input, OptionSet options) { - return base64EncodeToString(std::as_bytes(input), mode); + return base64EncodeToString(std::as_bytes(input), options); } -inline String base64EncodeToStringReturnNullIfOverflow(std::span input, Base64EncodeMode mode) +inline String base64EncodeToStringReturnNullIfOverflow(std::span input, OptionSet options) { - return base64EncodeToStringReturnNullIfOverflow(std::as_bytes(input), mode); + return base64EncodeToStringReturnNullIfOverflow(std::as_bytes(input), options); } -inline String base64EncodeToStringReturnNullIfOverflow(const CString& input, Base64EncodeMode mode) +inline String base64EncodeToStringReturnNullIfOverflow(const CString& input, OptionSet options) { - return base64EncodeToStringReturnNullIfOverflow(input.span(), mode); + return base64EncodeToStringReturnNullIfOverflow(input.span(), options); } inline std::optional> base64Decode(std::span input, OptionSet options) @@ -123,22 +124,22 @@ inline std::optional> base64Decode(std::span inpu inline Vector base64URLEncodeToVector(std::span input) { - return base64EncodeToVector(input, Base64EncodeMode::URL); + return base64EncodeToVector(input, { Base64EncodeOption::URL, Base64EncodeOption::OmitPadding }); } inline Vector base64URLEncodeToVector(std::span input) { - return base64EncodeToVector(input, Base64EncodeMode::URL); + return base64EncodeToVector(input, { Base64EncodeOption::URL, Base64EncodeOption::OmitPadding }); } inline String base64URLEncodeToString(std::span input) { - return base64EncodeToString(input, Base64EncodeMode::URL); + return base64EncodeToString(input, { Base64EncodeOption::URL, Base64EncodeOption::OmitPadding }); } inline String base64URLEncodeToString(std::span input) { - return base64EncodeToString(input, Base64EncodeMode::URL); + return base64EncodeToString(input, { Base64EncodeOption::URL, Base64EncodeOption::OmitPadding }); } inline std::optional> base64URLDecode(StringView input) @@ -161,7 +162,7 @@ template bool isBase64OrBase64URLCharacter(CharacterType return isASCIIAlphanumeric(c) || c == '+' || c == '/' || c == '-' || c == '_'; } -inline unsigned calculateBase64EncodedSize(unsigned inputLength, Base64EncodeMode mode) +inline unsigned calculateBase64EncodedSize(unsigned inputLength, OptionSet options) { if (!inputLength) return 0; @@ -169,51 +170,37 @@ inline unsigned calculateBase64EncodedSize(unsigned inputLength, Base64EncodeMod if (inputLength > maximumBase64EncoderInputBufferSize) return 0; - auto basePaddedEncodedSize = [&] { - return ((inputLength + 2) / 3) * 4; - }; - - auto baseUnpaddedEncodedSize = [&] { + if (options.contains(Base64EncodeOption::OmitPadding)) return ((inputLength * 4) + 2) / 3; - }; - - switch (mode) { - case Base64EncodeMode::Default: - return basePaddedEncodedSize(); - - case Base64EncodeMode::URL: - return baseUnpaddedEncodedSize(); - } - ASSERT_NOT_REACHED(); - return 0; + return ((inputLength + 2) / 3) * 4; } -inline Base64Specification base64Encoded(std::span input, Base64EncodeMode mode) +inline Base64Specification base64Encoded(std::span input, OptionSet options) { - return { input, mode }; + return { input, options }; } -inline Base64Specification base64Encoded(std::span input, Base64EncodeMode mode) +inline Base64Specification base64Encoded(std::span input, OptionSet options) { - return base64Encoded(std::as_bytes(input), mode); + return base64Encoded(std::as_bytes(input), options); } inline Base64Specification base64URLEncoded(std::span input) { - return base64Encoded(input, Base64EncodeMode::URL); + return base64Encoded(input, { Base64EncodeOption::URL, Base64EncodeOption::OmitPadding }); } inline Base64Specification base64URLEncoded(std::span input) { - return base64Encoded(input, Base64EncodeMode::URL); + return base64Encoded(input, { Base64EncodeOption::URL, Base64EncodeOption::OmitPadding }); } template<> class StringTypeAdapter { public: StringTypeAdapter(const Base64Specification& base64) : m_base64 { base64 } - , m_encodedLength { calculateBase64EncodedSize(base64.input.size(), base64.mode) } + , m_encodedLength { calculateBase64EncodedSize(base64.input.size(), base64.options) } { } @@ -222,7 +209,7 @@ template<> class StringTypeAdapter { template void writeTo(CharacterType* destination) const { - base64Encode(m_base64.input, std::span(destination, m_encodedLength), m_base64.mode); + base64Encode(m_base64.input, std::span(destination, m_encodedLength), m_base64.options); } private: @@ -232,6 +219,7 @@ template<> class StringTypeAdapter { } // namespace WTF +using WTF::Base64EncodeOption; using WTF::Base64DecodeOption; using WTF::base64Decode; using WTF::base64EncodeToString; diff --git a/Source/WTF/wtf/text/MakeString.h b/Source/WTF/wtf/text/MakeString.h new file mode 100644 index 0000000000000..3fc47702690b7 --- /dev/null +++ b/Source/WTF/wtf/text/MakeString.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2010-2023 Apple Inc. All rights reserved. + * Copyright (C) 2024 Samuel Weinig + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace WTF { + +template constexpr bool makeStringSlowPathRequiredForAdapter = requires(const StringTypeAdapter& adapter) { + { adapter.writeUsing(std::declval) } -> std::same_as; +}; +template constexpr bool makeStringSlowPathRequired = (... || makeStringSlowPathRequiredForAdapter); + +template +RefPtr tryMakeStringImplFromAdaptersInternal(unsigned length, bool areAllAdapters8Bit, StringTypeAdapters... adapters) +{ + ASSERT(length <= String::MaxLength); + if (areAllAdapters8Bit) { + LChar* buffer; + RefPtr result = StringImpl::tryCreateUninitialized(length, buffer); + if (!result) + return nullptr; + + if (buffer) + stringTypeAdapterAccumulator(buffer, adapters...); + + return result; + } + + UChar* buffer; + RefPtr result = StringImpl::tryCreateUninitialized(length, buffer); + if (!result) + return nullptr; + + if (buffer) + stringTypeAdapterAccumulator(buffer, adapters...); + + return result; +} + +template +String tryMakeStringFromAdapters(StringTypeAdapters&&... adapters) +{ + static_assert(String::MaxLength == std::numeric_limits::max()); + + if constexpr (makeStringSlowPathRequired) { + StringBuilder builder; + builder.appendFromAdapters(std::forward(adapters)...); + return builder.toString(); + } else { + auto sum = checkedSum(adapters.length()...); + if (sum.hasOverflowed()) + return String(); + + bool areAllAdapters8Bit = are8Bit(adapters...); + return tryMakeStringImplFromAdaptersInternal(sum, areAllAdapters8Bit, adapters...); + } +} + +template +String tryMakeString(const StringTypes& ...strings) +{ + return tryMakeStringFromAdapters(StringTypeAdapter(strings)...); +} + +template +String makeString(StringTypes... strings) +{ + auto result = tryMakeString(strings...); + if (!result) + CRASH(); + return result; +} + +template +AtomString tryMakeAtomStringFromAdapters(StringTypeAdapters ...adapters) +{ + static_assert(String::MaxLength == std::numeric_limits::max()); + + if constexpr (makeStringSlowPathRequired) { + StringBuilder builder; + builder.appendFromAdapters(adapters...); + return builder.toAtomString(); + } else { + auto sum = checkedSum(adapters.length()...); + if (sum.hasOverflowed()) + return AtomString(); + + unsigned length = sum; + ASSERT(length <= String::MaxLength); + + bool areAllAdapters8Bit = are8Bit(adapters...); + constexpr size_t maxLengthToUseStackVariable = 64; + if (length < maxLengthToUseStackVariable) { + if (areAllAdapters8Bit) { + LChar buffer[maxLengthToUseStackVariable]; + stringTypeAdapterAccumulator(buffer, adapters...); + return std::span { buffer, length }; + } + UChar buffer[maxLengthToUseStackVariable]; + stringTypeAdapterAccumulator(buffer, adapters...); + return std::span { buffer, length }; + } + return tryMakeStringImplFromAdaptersInternal(length, areAllAdapters8Bit, adapters...).get(); + } +} + +template +AtomString tryMakeAtomString(StringTypes... strings) +{ + return tryMakeAtomStringFromAdapters(StringTypeAdapter(strings)...); +} + +template +AtomString makeAtomString(StringTypes... strings) +{ + auto result = tryMakeAtomString(strings...); + if (result.isNull()) + CRASH(); + return result; +} + +inline String WARN_UNUSED_RETURN makeStringByInserting(StringView originalString, StringView stringToInsert, unsigned position) +{ + return makeString(originalString.left(position), stringToInsert, originalString.substring(position)); +} + +// Helper functor useful in generic contexts where both makeString() and StringBuilder are being used. +struct SerializeUsingMakeString { + using Result = String; + template String operator()(T&&... args) + { + return makeString(std::forward(args)...); + } +}; + +} // namespace WTF + +using WTF::makeAtomString; +using WTF::makeString; +using WTF::makeStringByInserting; +using WTF::tryMakeAtomString; +using WTF::tryMakeString; +using WTF::SerializeUsingMakeString; diff --git a/Source/WTF/wtf/text/StringBuilder.h b/Source/WTF/wtf/text/StringBuilder.h index c0c7019acfa16..0de399a631d7f 100644 --- a/Source/WTF/wtf/text/StringBuilder.h +++ b/Source/WTF/wtf/text/StringBuilder.h @@ -65,6 +65,8 @@ class StringBuilder { void append(LChar); void append(char character) { append(byteCast(character)); } + template void appendFromAdapters(const StringTypeAdapters&...); + // FIXME: Add a StringTypeAdapter so we can append one string builder to another with variadic append. void append(const StringBuilder&); @@ -112,7 +114,6 @@ class StringBuilder { WTF_EXPORT_PRIVATE void reifyString() const; void appendFromAdapters() { /* empty base case */ } - template void appendFromAdapters(const StringTypeAdapters&...); template void appendFromAdaptersSlow(const StringTypeAdapter&, const StringTypeAdapters&...); template void appendFromAdapterSlow(const StringTypeAdapter&); @@ -349,6 +350,18 @@ template<> struct IntegerToStringConversionTrait { static void flush(std::span characters, StringBuilder* builder) { builder->append(characters); } }; +// Helper functor useful in generic contexts where both makeString() and StringBuilder are being used. +struct SerializeUsingStringBuilder { + StringBuilder& builder; + + using Result = void; + template void operator()(T&&... args) + { + return builder.append(std::forward(args)...); + } +}; + } // namespace WTF using WTF::StringBuilder; +using WTF::SerializeUsingStringBuilder; diff --git a/Source/WTF/wtf/text/StringConcatenate.h b/Source/WTF/wtf/text/StringConcatenate.h index 3d12c23104271..a016fbda84e01 100644 --- a/Source/WTF/wtf/text/StringConcatenate.h +++ b/Source/WTF/wtf/text/StringConcatenate.h @@ -478,6 +478,9 @@ template class Interleave { Interleave(const Interleave&) = delete; Interleave& operator=(const Interleave&) = delete; + Interleave(Interleave&&) = default; + Interleave& operator=(Interleave&&) = default; + template void writeUsing(Accumulator& accumulator) const { auto begin = std::begin(container); @@ -640,112 +643,12 @@ inline void stringTypeAdapterAccumulator(ResultType* result, StringTypeAdapter a stringTypeAdapterAccumulator(result + adapter.length(), adapters...); } -template -RefPtr tryMakeStringImplFromAdaptersInternal(unsigned length, bool areAllAdapters8Bit, StringTypeAdapters ...adapters) -{ - ASSERT(length <= String::MaxLength); - if (areAllAdapters8Bit) { - LChar* buffer; - RefPtr resultImpl = StringImpl::tryCreateUninitialized(length, buffer); - if (!resultImpl) - return nullptr; - - if (buffer) - stringTypeAdapterAccumulator(buffer, adapters...); - - return resultImpl; - } - - UChar* buffer; - RefPtr resultImpl = StringImpl::tryCreateUninitialized(length, buffer); - if (!resultImpl) - return nullptr; - - if (buffer) - stringTypeAdapterAccumulator(buffer, adapters...); - - return resultImpl; -} - template auto handleWithAdapters(Func&& func, StringTypes&& ...strings) -> decltype(auto) { return func(StringTypeAdapter(std::forward(strings))...); } -template -String tryMakeStringFromAdapters(StringTypeAdapters ...adapters) -{ - static_assert(String::MaxLength == std::numeric_limits::max()); - auto sum = checkedSum(adapters.length()...); - if (sum.hasOverflowed()) - return String(); - - bool areAllAdapters8Bit = are8Bit(adapters...); - return tryMakeStringImplFromAdaptersInternal(sum, areAllAdapters8Bit, adapters...); -} - -template -String tryMakeString(StringTypes ...strings) -{ - return tryMakeStringFromAdapters(StringTypeAdapter(strings)...); -} - -template -String makeString(StringTypes... strings) -{ - String result = tryMakeString(strings...); - if (!result) - CRASH(); - return result; -} - -template -AtomString tryMakeAtomStringFromAdapters(StringTypeAdapters ...adapters) -{ - static_assert(String::MaxLength == std::numeric_limits::max()); - auto sum = checkedSum(adapters.length()...); - if (sum.hasOverflowed()) - return AtomString(); - - unsigned length = sum; - ASSERT(length <= String::MaxLength); - - bool areAllAdapters8Bit = are8Bit(adapters...); - constexpr size_t maxLengthToUseStackVariable = 64; - if (length < maxLengthToUseStackVariable) { - if (areAllAdapters8Bit) { - LChar buffer[maxLengthToUseStackVariable]; - stringTypeAdapterAccumulator(buffer, adapters...); - return std::span { buffer, length }; - } - UChar buffer[maxLengthToUseStackVariable]; - stringTypeAdapterAccumulator(buffer, adapters...); - return std::span { buffer, length }; - } - return tryMakeStringImplFromAdaptersInternal(length, areAllAdapters8Bit, adapters...).get(); -} - -template -AtomString tryMakeAtomString(StringTypes ...strings) -{ - return tryMakeAtomStringFromAdapters(StringTypeAdapter(strings)...); -} - -template -AtomString makeAtomString(StringTypes... strings) -{ - AtomString result = tryMakeAtomString(strings...); - if (result.isNull()) - CRASH(); - return result; -} - -inline String WARN_UNUSED_RETURN makeStringByInserting(StringView originalString, StringView stringToInsert, unsigned position) -{ - return makeString(originalString.left(position), stringToInsert, originalString.substring(position)); -} - } // namespace WTF using WTF::Indentation; @@ -753,11 +656,4 @@ using WTF::IndentationScope; using WTF::asASCIILowercase; using WTF::asASCIIUppercase; using WTF::interleave; -using WTF::makeAtomString; -using WTF::makeString; -using WTF::makeStringByInserting; using WTF::pad; -using WTF::tryMakeAtomString; -using WTF::tryMakeString; - -#include diff --git a/Source/WTF/wtf/text/StringHash.h b/Source/WTF/wtf/text/StringHash.h index a89a288f073ba..bc85e0681c99f 100644 --- a/Source/WTF/wtf/text/StringHash.h +++ b/Source/WTF/wtf/text/StringHash.h @@ -25,6 +25,7 @@ #include #include #include +#include namespace WTF { diff --git a/Source/WTF/wtf/text/StringOperators.h b/Source/WTF/wtf/text/StringOperators.h deleted file mode 100644 index a5fb00d32b069..0000000000000 --- a/Source/WTF/wtf/text/StringOperators.h +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. - * Copyright (C) Research In Motion Limited 2011. All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - * - */ - -#pragma once - -#include - -namespace WTF { - -template class StringAppend { -public: - StringAppend(StringType1 string1, StringType2 string2) - : m_string1 { string1 } - , m_string2 { string2 } - { - } - - operator String() const - { - String result = tryMakeString(m_string1, m_string2); - if (!result) - CRASH(); - return result; - } - - operator AtomString() const - { - return AtomString(operator String()); - } - - bool is8Bit() const - { - StringTypeAdapter adapter1(m_string1); - StringTypeAdapter adapter2(m_string2); - return adapter1.is8Bit() && adapter2.is8Bit(); - } - - void writeTo(LChar* destination) const - { - ASSERT(is8Bit()); - StringTypeAdapter adapter1(m_string1); - StringTypeAdapter adapter2(m_string2); - adapter1.writeTo(destination); - adapter2.writeTo(destination + adapter1.length()); - } - - void writeTo(UChar* destination) const - { - StringTypeAdapter adapter1(m_string1); - StringTypeAdapter adapter2(m_string2); - adapter1.writeTo(destination); - adapter2.writeTo(destination + adapter1.length()); - } - - unsigned length() const - { - StringTypeAdapter adapter1(m_string1); - StringTypeAdapter adapter2(m_string2); - return adapter1.length() + adapter2.length(); - } - -private: - StringType1 m_string1; - StringType2 m_string2; -}; - -template -class StringTypeAdapter> { -public: - StringTypeAdapter(const StringAppend& buffer) - : m_buffer { buffer } - { - } - - unsigned length() const { return m_buffer.length(); } - bool is8Bit() const { return m_buffer.is8Bit(); } - template void writeTo(CharacterType* destination) const { m_buffer.writeTo(destination); } - -private: - const StringAppend& m_buffer; -}; - -inline StringAppend operator+(const char* string1, const String& string2) -{ - return StringAppend(string1, string2); -} - -inline StringAppend operator+(const char* string1, const AtomString& string2) -{ - return StringAppend(string1, string2); -} - -template, StringView>::value>> -inline StringAppend operator+(const char* string1, T string2) -{ - return StringAppend(string1, string2); -} - -template -inline StringAppend> operator+(const char* string1, const StringAppend& string2) -{ - return StringAppend>(string1, string2); -} - -inline StringAppend operator+(const UChar* string1, const String& string2) -{ - return StringAppend(string1, string2); -} - -inline StringAppend operator+(const UChar* string1, const AtomString& string2) -{ - return StringAppend(string1, string2); -} - -template, StringView>::value>> -inline StringAppend operator+(const UChar* string1, T string2) -{ - return StringAppend(string1, string2); -} - -template -inline StringAppend> operator+(const UChar* string1, const StringAppend& string2) -{ - return StringAppend>(string1, string2); -} - -inline StringAppend operator+(const ASCIILiteral& string1, const String& string2) -{ - return StringAppend(string1, string2); -} - -inline StringAppend operator+(const ASCIILiteral& string1, const AtomString& string2) -{ - return StringAppend(string1, string2); -} - -template, StringView>::value>> -inline StringAppend operator+(const ASCIILiteral& string1, T string2) -{ - return StringAppend(string1, string2); -} - -template -inline StringAppend> operator+(const ASCIILiteral& string1, const StringAppend& string2) -{ - return StringAppend>(string1, string2); -} - -template -StringAppend operator+(const String& string1, T string2) -{ - return StringAppend(string1, string2); -} - -template, StringView>::value>> -StringAppend operator+(T string1, U string2) -{ - return StringAppend(string1, string2); -} - -template -StringAppend, W> operator+(const StringAppend& string1, W string2) -{ - return StringAppend, W>(string1, string2); -} - -} // namespace WTF diff --git a/Source/WTF/wtf/text/StringView.cpp b/Source/WTF/wtf/text/StringView.cpp index 8516ccaa33e49..64924a479c279 100644 --- a/Source/WTF/wtf/text/StringView.cpp +++ b/Source/WTF/wtf/text/StringView.cpp @@ -33,6 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #include #include diff --git a/Source/WTF/wtf/text/TextStream.cpp b/Source/WTF/wtf/text/TextStream.cpp index e115c4bb4f8e0..d24053b5385aa 100644 --- a/Source/WTF/wtf/text/TextStream.cpp +++ b/Source/WTF/wtf/text/TextStream.cpp @@ -26,6 +26,7 @@ #include "config.h" #include +#include #include namespace WTF { diff --git a/Source/WTF/wtf/text/WTFString.cpp b/Source/WTF/wtf/text/WTFString.cpp index 17adca0c4a475..6f63dfd07be54 100644 --- a/Source/WTF/wtf/text/WTFString.cpp +++ b/Source/WTF/wtf/text/WTFString.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include diff --git a/Source/WTF/wtf/unix/LoggingUnix.cpp b/Source/WTF/wtf/unix/LoggingUnix.cpp index 4d86fed6c1544..d7a71472bc2af 100644 --- a/Source/WTF/wtf/unix/LoggingUnix.cpp +++ b/Source/WTF/wtf/unix/LoggingUnix.cpp @@ -26,6 +26,7 @@ #include "LogInitialization.h" #include +#include #include namespace WTF { diff --git a/Source/WTF/wtf/win/LanguageWin.cpp b/Source/WTF/wtf/win/LanguageWin.cpp index f08b652c0e9a3..3d4604949f181 100644 --- a/Source/WTF/wtf/win/LanguageWin.cpp +++ b/Source/WTF/wtf/win/LanguageWin.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -68,7 +69,7 @@ static String platformLanguage() if (countryName.isEmpty()) computedDefaultLanguage = languageName; else - computedDefaultLanguage = languageName + '-' + countryName; + computedDefaultLanguage = makeString(languageName, '-', countryName); return computedDefaultLanguage; } diff --git a/Source/WTF/wtf/win/PathWalker.cpp b/Source/WTF/wtf/win/PathWalker.cpp index 688fedd59fa8d..89af9ffab4c3c 100644 --- a/Source/WTF/wtf/win/PathWalker.cpp +++ b/Source/WTF/wtf/win/PathWalker.cpp @@ -26,6 +26,7 @@ #include "config.h" #include +#include #include namespace WTF { diff --git a/Source/WebCore/Headers.cmake b/Source/WebCore/Headers.cmake index 021e34e1d9fa1..4900b76a7802c 100644 --- a/Source/WebCore/Headers.cmake +++ b/Source/WebCore/Headers.cmake @@ -1800,6 +1800,7 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS platform/SleepDisabler.h platform/SleepDisablerClient.h platform/SleepDisablerIdentifier.h + platform/StyleAppearance.h platform/SuddenTermination.h platform/Supplementable.h platform/SystemSoundDelegate.h @@ -2502,7 +2503,6 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS style/AnchorPositionEvaluator.h style/PseudoElementIdentifier.h style/ScopedName.h - style/StyleAppearance.h style/StyleChange.h style/StyleScope.h style/StyleScopeOrdinal.h diff --git a/Source/WebCore/Modules/airplay/WebMediaSessionManager.cpp b/Source/WebCore/Modules/airplay/WebMediaSessionManager.cpp index 1d827f5b004d6..6f3808d56a795 100644 --- a/Source/WebCore/Modules/airplay/WebMediaSessionManager.cpp +++ b/Source/WebCore/Modules/airplay/WebMediaSessionManager.cpp @@ -34,6 +34,7 @@ #include "WebMediaSessionManagerClient.h" #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/Modules/applepay/ApplePayRequestBase.cpp b/Source/WebCore/Modules/applepay/ApplePayRequestBase.cpp index 25321279a55f8..d20c4221b6012 100644 --- a/Source/WebCore/Modules/applepay/ApplePayRequestBase.cpp +++ b/Source/WebCore/Modules/applepay/ApplePayRequestBase.cpp @@ -29,7 +29,7 @@ #if ENABLE(APPLE_PAY) #include "PaymentCoordinator.h" -#include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/applepay/ApplePaySession.cpp b/Source/WebCore/Modules/applepay/ApplePaySession.cpp index 9a4d4f0e0476d..63f22d0d83bbf 100644 --- a/Source/WebCore/Modules/applepay/ApplePaySession.cpp +++ b/Source/WebCore/Modules/applepay/ApplePaySession.cpp @@ -66,6 +66,7 @@ #include "UserGestureIndicator.h" #include #include +#include #if ENABLE(APPLE_PAY_DEFERRED_PAYMENTS) #include diff --git a/Source/WebCore/Modules/applepay/PaymentRequestValidator.mm b/Source/WebCore/Modules/applepay/PaymentRequestValidator.mm index 3699e5267ed7a..33d8b780bef89 100644 --- a/Source/WebCore/Modules/applepay/PaymentRequestValidator.mm +++ b/Source/WebCore/Modules/applepay/PaymentRequestValidator.mm @@ -32,6 +32,7 @@ #import "ApplePayShippingMethod.h" #import #import +#import #import namespace WebCore { @@ -105,8 +106,9 @@ if (amount < 0) return Exception { ExceptionCode::TypeError, "Total amount must not be negative."_s }; - if (amount > 100000000) - return Exception { ExceptionCode::TypeError, "Total amount is too big."_s }; + // We can safely defer a maximum amount check to the underlying payment system, instead. + // The downside is we lose an informative error mode and get an opaque payment sheet error for too large total amounts. + // FIXME: PaymentRequestValidator should adopt per-currency checks for total amounts. return { }; } diff --git a/Source/WebCore/Modules/applepay/paymentrequest/ApplePayPaymentHandler.cpp b/Source/WebCore/Modules/applepay/paymentrequest/ApplePayPaymentHandler.cpp index 438f09c68b949..78fc0a2bfed74 100644 --- a/Source/WebCore/Modules/applepay/paymentrequest/ApplePayPaymentHandler.cpp +++ b/Source/WebCore/Modules/applepay/paymentrequest/ApplePayPaymentHandler.cpp @@ -72,6 +72,7 @@ #include "PaymentValidationErrors.h" #include "Settings.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/applicationmanifest/ApplicationManifestParser.cpp b/Source/WebCore/Modules/applicationmanifest/ApplicationManifestParser.cpp index d4b7da30fb6a2..cf1aa4d1a5526 100644 --- a/Source/WebCore/Modules/applicationmanifest/ApplicationManifestParser.cpp +++ b/Source/WebCore/Modules/applicationmanifest/ApplicationManifestParser.cpp @@ -35,6 +35,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/beacon/NavigatorBeacon.cpp b/Source/WebCore/Modules/beacon/NavigatorBeacon.cpp index 2218bb201d76f..215bbd09dc333 100644 --- a/Source/WebCore/Modules/beacon/NavigatorBeacon.cpp +++ b/Source/WebCore/Modules/beacon/NavigatorBeacon.cpp @@ -37,6 +37,7 @@ #include "Navigator.h" #include "Page.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/cache/DOMCacheEngine.cpp b/Source/WebCore/Modules/cache/DOMCacheEngine.cpp index dc60d63159fd1..675b0fde7c5f5 100644 --- a/Source/WebCore/Modules/cache/DOMCacheEngine.cpp +++ b/Source/WebCore/Modules/cache/DOMCacheEngine.cpp @@ -32,6 +32,7 @@ #include "Exception.h" #include "HTTPParsers.h" #include "ScriptExecutionContext.h" +#include namespace WebCore { diff --git a/Source/WebCore/Modules/cache/DOMCacheStorage.cpp b/Source/WebCore/Modules/cache/DOMCacheStorage.cpp index 79df8afe3ada2..44d7351a7e033 100644 --- a/Source/WebCore/Modules/cache/DOMCacheStorage.cpp +++ b/Source/WebCore/Modules/cache/DOMCacheStorage.cpp @@ -35,6 +35,7 @@ #include "MultiCacheQueryOptions.h" #include "ScriptExecutionContext.h" #include "SecurityOrigin.h" +#include namespace WebCore { diff --git a/Source/WebCore/Modules/cookie-store/CookieStore.cpp b/Source/WebCore/Modules/cookie-store/CookieStore.cpp index b4b68807462dc..d128b3b04cefe 100644 --- a/Source/WebCore/Modules/cookie-store/CookieStore.cpp +++ b/Source/WebCore/Modules/cookie-store/CookieStore.cpp @@ -59,6 +59,7 @@ #include #include #include +#include #include namespace WebCore { @@ -396,7 +397,7 @@ void CookieStore::set(CookieInit&& options, Ref&& promise) } if (!cookie.path.endsWith('/')) - cookie.path = cookie.path + '/'; + cookie.path = makeString(cookie.path, '/'); // FIXME: Obtain the encoded length without allocating and encoding. if (cookie.path.utf8().length() > maximumAttributeValueSize) { diff --git a/Source/WebCore/Modules/entriesapi/DOMFileSystem.cpp b/Source/WebCore/Modules/entriesapi/DOMFileSystem.cpp index 165829313c011..5172b02bcf700 100644 --- a/Source/WebCore/Modules/entriesapi/DOMFileSystem.cpp +++ b/Source/WebCore/Modules/entriesapi/DOMFileSystem.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/Modules/entriesapi/ErrorCallback.idl b/Source/WebCore/Modules/entriesapi/ErrorCallback.idl index 4dd3901da244b..98609ac6cabcf 100644 --- a/Source/WebCore/Modules/entriesapi/ErrorCallback.idl +++ b/Source/WebCore/Modules/entriesapi/ErrorCallback.idl @@ -23,4 +23,4 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -callback ErrorCallback = undefined (DOMException error); +[ IsStrongCallback ] callback ErrorCallback = undefined (DOMException error); diff --git a/Source/WebCore/Modules/entriesapi/FileCallback.idl b/Source/WebCore/Modules/entriesapi/FileCallback.idl index feeb44b5c22b8..7f1938516a09a 100644 --- a/Source/WebCore/Modules/entriesapi/FileCallback.idl +++ b/Source/WebCore/Modules/entriesapi/FileCallback.idl @@ -23,4 +23,4 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -callback FileCallback = undefined (File file); +[ IsStrongCallback ] callback FileCallback = undefined (File file); diff --git a/Source/WebCore/Modules/entriesapi/FileSystemEntriesCallback.idl b/Source/WebCore/Modules/entriesapi/FileSystemEntriesCallback.idl index 798ee6a904558..77974d94fb82e 100644 --- a/Source/WebCore/Modules/entriesapi/FileSystemEntriesCallback.idl +++ b/Source/WebCore/Modules/entriesapi/FileSystemEntriesCallback.idl @@ -23,4 +23,4 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -callback FileSystemEntriesCallback = undefined (sequence entries); +[ IsStrongCallback ] callback FileSystemEntriesCallback = undefined (sequence entries); diff --git a/Source/WebCore/Modules/entriesapi/FileSystemEntryCallback.idl b/Source/WebCore/Modules/entriesapi/FileSystemEntryCallback.idl index 182bcd4f4a99c..9d7101e67ed82 100644 --- a/Source/WebCore/Modules/entriesapi/FileSystemEntryCallback.idl +++ b/Source/WebCore/Modules/entriesapi/FileSystemEntryCallback.idl @@ -23,4 +23,4 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -callback FileSystemEntryCallback = undefined (FileSystemEntry entry); +[ IsStrongCallback ] callback FileSystemEntryCallback = undefined (FileSystemEntry entry); diff --git a/Source/WebCore/Modules/fetch/FetchBody.cpp b/Source/WebCore/Modules/fetch/FetchBody.cpp index ea8acb4b55771..c04d32aa51e88 100644 --- a/Source/WebCore/Modules/fetch/FetchBody.cpp +++ b/Source/WebCore/Modules/fetch/FetchBody.cpp @@ -41,6 +41,7 @@ #include "ReadableStreamSource.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/fetch/FetchBodyConsumer.cpp b/Source/WebCore/Modules/fetch/FetchBodyConsumer.cpp index 218077c5e334c..5a722028705fc 100644 --- a/Source/WebCore/Modules/fetch/FetchBodyConsumer.cpp +++ b/Source/WebCore/Modules/fetch/FetchBodyConsumer.cpp @@ -42,6 +42,7 @@ #include "TextResourceDecoder.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/fetch/FetchHeaders.cpp b/Source/WebCore/Modules/fetch/FetchHeaders.cpp index 62ed9962a1c19..1fc1cb485429f 100644 --- a/Source/WebCore/Modules/fetch/FetchHeaders.cpp +++ b/Source/WebCore/Modules/fetch/FetchHeaders.cpp @@ -30,6 +30,7 @@ #include "FetchHeaders.h" #include "HTTPParsers.h" +#include namespace WebCore { @@ -211,13 +212,7 @@ ExceptionOr FetchHeaders::get(const String& name) const if (equalIgnoringASCIICase(name, "set-cookie"_s)) { if (m_setCookieValues.isEmpty()) return String(); - StringBuilder builder; - for (const auto& value : m_setCookieValues) { - if (!builder.isEmpty()) - builder.append(", "_s); - builder.append(value); - } - return builder.toString(); + return makeString(interleave(m_setCookieValues, ", "_s)); } return m_headers.get(name); } diff --git a/Source/WebCore/Modules/fetch/FetchRequest.cpp b/Source/WebCore/Modules/fetch/FetchRequest.cpp index 1322e41833145..89b455dadd580 100644 --- a/Source/WebCore/Modules/fetch/FetchRequest.cpp +++ b/Source/WebCore/Modules/fetch/FetchRequest.cpp @@ -40,6 +40,7 @@ #include "SecurityOrigin.h" #include "Settings.h" #include "WebCoreOpaqueRoot.h" +#include namespace WebCore { diff --git a/Source/WebCore/Modules/fetch/FetchResponse.cpp b/Source/WebCore/Modules/fetch/FetchResponse.cpp index 9467b3540fd81..4dfb141c7bcb9 100644 --- a/Source/WebCore/Modules/fetch/FetchResponse.cpp +++ b/Source/WebCore/Modules/fetch/FetchResponse.cpp @@ -40,7 +40,7 @@ #include "ResourceError.h" #include "ScriptExecutionContext.h" #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/geolocation/Geolocation.cpp b/Source/WebCore/Modules/geolocation/Geolocation.cpp index 6cfaedf99ffdc..be20114bdebd7 100644 --- a/Source/WebCore/Modules/geolocation/Geolocation.cpp +++ b/Source/WebCore/Modules/geolocation/Geolocation.cpp @@ -46,6 +46,7 @@ #include "SecurityOrigin.h" #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/Modules/geolocation/PositionCallback.idl b/Source/WebCore/Modules/geolocation/PositionCallback.idl index c200b31515c1f..8a21bed0ba1ae 100644 --- a/Source/WebCore/Modules/geolocation/PositionCallback.idl +++ b/Source/WebCore/Modules/geolocation/PositionCallback.idl @@ -24,4 +24,5 @@ [ Conditional=GEOLOCATION, + IsStrongCallback ] callback PositionCallback = undefined (GeolocationPosition? position); // FIXME: This should not be nullable. diff --git a/Source/WebCore/Modules/geolocation/PositionErrorCallback.idl b/Source/WebCore/Modules/geolocation/PositionErrorCallback.idl index 4ed634b182883..0054200b6de0d 100644 --- a/Source/WebCore/Modules/geolocation/PositionErrorCallback.idl +++ b/Source/WebCore/Modules/geolocation/PositionErrorCallback.idl @@ -24,4 +24,5 @@ [ Conditional=GEOLOCATION, + IsStrongCallback ] callback PositionErrorCallback = undefined (GeolocationPositionError error); diff --git a/Source/WebCore/Modules/indexeddb/IDBDatabaseIdentifier.cpp b/Source/WebCore/Modules/indexeddb/IDBDatabaseIdentifier.cpp index d530dc6deebd1..c0ba1cf0d949b 100644 --- a/Source/WebCore/Modules/indexeddb/IDBDatabaseIdentifier.cpp +++ b/Source/WebCore/Modules/indexeddb/IDBDatabaseIdentifier.cpp @@ -29,6 +29,7 @@ #include "SecurityOrigin.h" #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/Modules/indexeddb/IDBIndex.cpp b/Source/WebCore/Modules/indexeddb/IDBIndex.cpp index 66c2b704a830a..f4d9b9bab2c98 100644 --- a/Source/WebCore/Modules/indexeddb/IDBIndex.cpp +++ b/Source/WebCore/Modules/indexeddb/IDBIndex.cpp @@ -37,6 +37,7 @@ #include "WebCoreOpaqueRoot.h" #include #include +#include namespace WebCore { using namespace JSC; diff --git a/Source/WebCore/Modules/indexeddb/IDBKeyData.cpp b/Source/WebCore/Modules/indexeddb/IDBKeyData.cpp index be7887aec689e..1b13398333cb3 100644 --- a/Source/WebCore/Modules/indexeddb/IDBKeyData.cpp +++ b/Source/WebCore/Modules/indexeddb/IDBKeyData.cpp @@ -28,8 +28,8 @@ #include "KeyedCoding.h" #include +#include #include -#include namespace WebCore { @@ -320,19 +320,10 @@ String IDBKeyData::loggingString() const switch (type()) { case IndexedDB::KeyType::Invalid: return ""_s; - case IndexedDB::KeyType::Array: { - StringBuilder builder; - builder.append(" - { "_s); - auto& array = std::get>(m_value); - for (size_t i = 0; i < array.size(); ++i) { - builder.append(array[i].loggingString()); - if (i < array.size() - 1) - builder.append(", "_s); - } - builder.append(" }"_s); - result = builder.toString(); + case IndexedDB::KeyType::Array: + result = makeString(" - { "_s, interleave(std::get>(m_value), [](auto& builder, auto& item) { builder.append(item.loggingString()); }, ", "_s), " }"_s); break; - } + case IndexedDB::KeyType::Binary: { StringBuilder builder; builder.append(" - "_s); diff --git a/Source/WebCore/Modules/indexeddb/IDBKeyPath.cpp b/Source/WebCore/Modules/indexeddb/IDBKeyPath.cpp index 9a2d4ede88374..353389f10b28c 100644 --- a/Source/WebCore/Modules/indexeddb/IDBKeyPath.cpp +++ b/Source/WebCore/Modules/indexeddb/IDBKeyPath.cpp @@ -29,6 +29,7 @@ #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/Modules/indexeddb/IDBKeyRangeData.cpp b/Source/WebCore/Modules/indexeddb/IDBKeyRangeData.cpp index aa5ec8d4bc2b4..2db2203a8adb5 100644 --- a/Source/WebCore/Modules/indexeddb/IDBKeyRangeData.cpp +++ b/Source/WebCore/Modules/indexeddb/IDBKeyRangeData.cpp @@ -27,6 +27,7 @@ #include "IDBKeyRangeData.h" #include "IDBKey.h" +#include namespace WebCore { diff --git a/Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp b/Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp index 587992b1eb5a2..3eee5f9618ff1 100644 --- a/Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp +++ b/Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp @@ -49,6 +49,7 @@ #include #include #include +#include namespace WebCore { using namespace JSC; diff --git a/Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.cpp b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.cpp index a673c62542860..83f0797c1abfe 100644 --- a/Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.cpp +++ b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.cpp @@ -51,7 +51,7 @@ #include #include #include -#include +#include #include namespace WebCore { diff --git a/Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.cpp b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.cpp index a9b0d89f238bf..d133b47c447c0 100644 --- a/Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.cpp +++ b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.cpp @@ -35,6 +35,7 @@ #include "SQLiteStatement.h" #include "SQLiteTransaction.h" #include +#include namespace WebCore { namespace IDBServer { diff --git a/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp index bd5cda5e3e94e..956f60ffa0697 100644 --- a/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp +++ b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp @@ -46,6 +46,7 @@ #include "UniqueIDBDatabaseManager.h" #include #include +#include namespace WebCore { using namespace JSC; diff --git a/Source/WebCore/Modules/indexeddb/shared/IDBCursorInfo.cpp b/Source/WebCore/Modules/indexeddb/shared/IDBCursorInfo.cpp index ba10c9a6ff1e3..9d61c1eb932e8 100644 --- a/Source/WebCore/Modules/indexeddb/shared/IDBCursorInfo.cpp +++ b/Source/WebCore/Modules/indexeddb/shared/IDBCursorInfo.cpp @@ -29,7 +29,7 @@ #include "IDBDatabase.h" #include "IDBTransaction.h" #include "IndexedDB.h" -#include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/indexeddb/shared/IDBGetAllRecordsData.cpp b/Source/WebCore/Modules/indexeddb/shared/IDBGetAllRecordsData.cpp index 0e16edbf71441..fa269c63eea9f 100644 --- a/Source/WebCore/Modules/indexeddb/shared/IDBGetAllRecordsData.cpp +++ b/Source/WebCore/Modules/indexeddb/shared/IDBGetAllRecordsData.cpp @@ -27,7 +27,7 @@ #include "IDBGetAllRecordsData.h" #include "IDBKeyRangeData.h" -#include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/indexeddb/shared/IDBGetRecordData.cpp b/Source/WebCore/Modules/indexeddb/shared/IDBGetRecordData.cpp index c1ceffba9e479..6932775734769 100644 --- a/Source/WebCore/Modules/indexeddb/shared/IDBGetRecordData.cpp +++ b/Source/WebCore/Modules/indexeddb/shared/IDBGetRecordData.cpp @@ -27,6 +27,7 @@ #include "IDBGetRecordData.h" #include "IDBKeyRangeData.h" +#include namespace WebCore { diff --git a/Source/WebCore/Modules/indexeddb/shared/IDBIndexInfo.cpp b/Source/WebCore/Modules/indexeddb/shared/IDBIndexInfo.cpp index c875109675647..a25383b4469b2 100644 --- a/Source/WebCore/Modules/indexeddb/shared/IDBIndexInfo.cpp +++ b/Source/WebCore/Modules/indexeddb/shared/IDBIndexInfo.cpp @@ -27,7 +27,7 @@ #include "IDBIndexInfo.h" #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/indexeddb/shared/IDBIterateCursorData.cpp b/Source/WebCore/Modules/indexeddb/shared/IDBIterateCursorData.cpp index 5005030f7a5de..001fec0ad7ecd 100644 --- a/Source/WebCore/Modules/indexeddb/shared/IDBIterateCursorData.cpp +++ b/Source/WebCore/Modules/indexeddb/shared/IDBIterateCursorData.cpp @@ -25,7 +25,8 @@ #include "config.h" #include "IDBIterateCursorData.h" -#include + +#include namespace WebCore { diff --git a/Source/WebCore/Modules/indexeddb/shared/IDBObjectStoreInfo.cpp b/Source/WebCore/Modules/indexeddb/shared/IDBObjectStoreInfo.cpp index e236386043fbb..c6d0cb37cf723 100644 --- a/Source/WebCore/Modules/indexeddb/shared/IDBObjectStoreInfo.cpp +++ b/Source/WebCore/Modules/indexeddb/shared/IDBObjectStoreInfo.cpp @@ -27,6 +27,7 @@ #include "IDBObjectStoreInfo.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/Modules/indexeddb/shared/IDBResourceIdentifier.cpp b/Source/WebCore/Modules/indexeddb/shared/IDBResourceIdentifier.cpp index 593e1ce182a5d..0eb653cbcb868 100644 --- a/Source/WebCore/Modules/indexeddb/shared/IDBResourceIdentifier.cpp +++ b/Source/WebCore/Modules/indexeddb/shared/IDBResourceIdentifier.cpp @@ -30,7 +30,7 @@ #include "IDBConnectionToServer.h" #include "IDBRequest.h" #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/indexeddb/shared/IDBTransactionInfo.cpp b/Source/WebCore/Modules/indexeddb/shared/IDBTransactionInfo.cpp index 2bd2e1e4a2b58..2899fd75e2530 100644 --- a/Source/WebCore/Modules/indexeddb/shared/IDBTransactionInfo.cpp +++ b/Source/WebCore/Modules/indexeddb/shared/IDBTransactionInfo.cpp @@ -27,6 +27,7 @@ #include "IDBTransactionInfo.h" #include "IDBTransaction.h" +#include namespace WebCore { diff --git a/Source/WebCore/Modules/mediasession/MediaSessionActionHandler.idl b/Source/WebCore/Modules/mediasession/MediaSessionActionHandler.idl index 20f65892feb75..da829a9d60e92 100644 --- a/Source/WebCore/Modules/mediasession/MediaSessionActionHandler.idl +++ b/Source/WebCore/Modules/mediasession/MediaSessionActionHandler.idl @@ -23,7 +23,4 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -[ - Conditional=MEDIA_SESSION, - IsWeakCallback -] callback MediaSessionActionHandler = undefined (MediaSessionActionDetails details); +[ Conditional=MEDIA_SESSION ] callback MediaSessionActionHandler = undefined (MediaSessionActionDetails details); diff --git a/Source/WebCore/Modules/mediasession/MediaSessionCoordinator.cpp b/Source/WebCore/Modules/mediasession/MediaSessionCoordinator.cpp index 1b194e21d6004..0506caa77a9c3 100644 --- a/Source/WebCore/Modules/mediasession/MediaSessionCoordinator.cpp +++ b/Source/WebCore/Modules/mediasession/MediaSessionCoordinator.cpp @@ -42,6 +42,7 @@ #include #include #include +#include static const Seconds CommandTimeTolerance = 50_ms; diff --git a/Source/WebCore/Modules/mediasource/MediaSource.cpp b/Source/WebCore/Modules/mediasource/MediaSource.cpp index e1761150c825c..fb6a60539fdfe 100644 --- a/Source/WebCore/Modules/mediasource/MediaSource.cpp +++ b/Source/WebCore/Modules/mediasource/MediaSource.cpp @@ -71,6 +71,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/mediastream/MediaStreamTrack.cpp b/Source/WebCore/Modules/mediastream/MediaStreamTrack.cpp index d2f7e190304bf..837544011e643 100644 --- a/Source/WebCore/Modules/mediastream/MediaStreamTrack.cpp +++ b/Source/WebCore/Modules/mediastream/MediaStreamTrack.cpp @@ -294,6 +294,9 @@ MediaStreamTrack::TrackSettings MediaStreamTrack::getSettings() const if (settings.supportsBackgroundBlur()) result.backgroundBlur = settings.backgroundBlur(); + if (settings.supportsPowerEfficient()) + result.powerEfficient = settings.powerEfficient(); + return result; } diff --git a/Source/WebCore/Modules/mediastream/MediaStreamTrack.h b/Source/WebCore/Modules/mediastream/MediaStreamTrack.h index fb285c9980ad7..1aa7bf38364f3 100644 --- a/Source/WebCore/Modules/mediastream/MediaStreamTrack.h +++ b/Source/WebCore/Modules/mediastream/MediaStreamTrack.h @@ -123,6 +123,7 @@ class MediaStreamTrack std::optional zoom; std::optional torch; std::optional backgroundBlur; + std::optional powerEfficient; }; TrackSettings getSettings() const; diff --git a/Source/WebCore/Modules/mediastream/MediaStreamTrack.idl b/Source/WebCore/Modules/mediastream/MediaStreamTrack.idl index b58d3ecba61a1..a753881d68643 100644 --- a/Source/WebCore/Modules/mediastream/MediaStreamTrack.idl +++ b/Source/WebCore/Modules/mediastream/MediaStreamTrack.idl @@ -95,4 +95,5 @@ enum MediaStreamTrackState { "live", "ended" }; boolean torch; boolean backgroundBlur; + boolean powerEfficient; }; diff --git a/Source/WebCore/Modules/mediastream/MediaTrackCapabilities.cpp b/Source/WebCore/Modules/mediastream/MediaTrackCapabilities.cpp index 1b07b4c395754..ec094b1340776 100644 --- a/Source/WebCore/Modules/mediastream/MediaTrackCapabilities.cpp +++ b/Source/WebCore/Modules/mediastream/MediaTrackCapabilities.cpp @@ -96,6 +96,17 @@ static Vector capabilityBooleanVector(RealtimeMediaSourceCapabilities::Bac return result; } +static Vector powerEfficientCapabilityVector(bool powerEfficient) +{ + Vector result; + result.reserveInitialCapacity(2); + result.append(false); + if (powerEfficient) + result.append(true); + + return result; +} + MediaTrackCapabilities toMediaTrackCapabilities(const RealtimeMediaSourceCapabilities& capabilities, const String& groupId) { MediaTrackCapabilities result; @@ -132,6 +143,9 @@ MediaTrackCapabilities toMediaTrackCapabilities(const RealtimeMediaSourceCapabil if (capabilities.supportsBackgroundBlur()) result.backgroundBlur = capabilityBooleanVector(capabilities.backgroundBlur()); + if (capabilities.supportsPowerEfficient()) + result.powerEfficient = powerEfficientCapabilityVector(capabilities.powerEfficient()); + return result; } diff --git a/Source/WebCore/Modules/mediastream/MediaTrackCapabilities.h b/Source/WebCore/Modules/mediastream/MediaTrackCapabilities.h index a73cc02d79dab..5356d48064a70 100644 --- a/Source/WebCore/Modules/mediastream/MediaTrackCapabilities.h +++ b/Source/WebCore/Modules/mediastream/MediaTrackCapabilities.h @@ -53,6 +53,7 @@ struct MediaTrackCapabilities { std::optional zoom; std::optional torch; std::optional> backgroundBlur; + std::optional> powerEfficient; }; MediaTrackCapabilities toMediaTrackCapabilities(const RealtimeMediaSourceCapabilities&, const String& groupId); diff --git a/Source/WebCore/Modules/mediastream/MediaTrackCapabilities.idl b/Source/WebCore/Modules/mediastream/MediaTrackCapabilities.idl index 81dda6d512583..b3edfb40edf9a 100644 --- a/Source/WebCore/Modules/mediastream/MediaTrackCapabilities.idl +++ b/Source/WebCore/Modules/mediastream/MediaTrackCapabilities.idl @@ -64,4 +64,5 @@ boolean torch; sequence backgroundBlur; + sequence powerEfficient; }; diff --git a/Source/WebCore/Modules/mediastream/PeerConnectionBackend.cpp b/Source/WebCore/Modules/mediastream/PeerConnectionBackend.cpp index 443cfc378aac7..e19ef0f81161a 100644 --- a/Source/WebCore/Modules/mediastream/PeerConnectionBackend.cpp +++ b/Source/WebCore/Modules/mediastream/PeerConnectionBackend.cpp @@ -52,7 +52,6 @@ #include #include #include -#include #if USE(GSTREAMER_WEBRTC) #include "GStreamerWebRTCUtils.h" diff --git a/Source/WebCore/Modules/mediastream/RTCDataChannel.cpp b/Source/WebCore/Modules/mediastream/RTCDataChannel.cpp index 7913ef332203d..caf9b2905dff0 100644 --- a/Source/WebCore/Modules/mediastream/RTCDataChannel.cpp +++ b/Source/WebCore/Modules/mediastream/RTCDataChannel.cpp @@ -42,6 +42,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/modern-media-controls/controls/auto-hide-controller.js b/Source/WebCore/Modules/modern-media-controls/controls/auto-hide-controller.js index 05035a516a098..e97240bed1663 100644 --- a/Source/WebCore/Modules/modern-media-controls/controls/auto-hide-controller.js +++ b/Source/WebCore/Modules/modern-media-controls/controls/auto-hide-controller.js @@ -35,7 +35,6 @@ class AutoHideController this._pointerIdentifiersPreventingAutoHide = new Set; this._pointerIdentifiersPreventingAutoHideForHover = new Set; - this._mediaControls.element.addEventListener("mousemove", this); this._mediaControls.element.addEventListener("pointermove", this); this._mediaControls.element.addEventListener("pointerdown", this); this._mediaControls.element.addEventListener("pointerup", this); @@ -91,8 +90,6 @@ class AutoHideController return; switch (event.type) { - case "mousemove": - this._mediaControls.faded = false; case "pointermove": // If the pointer is a mouse (supports hover), immediately show the controls. if (event.pointerType === "mouse") diff --git a/Source/WebCore/Modules/modern-media-controls/controls/macos-fullscreen-media-controls.js b/Source/WebCore/Modules/modern-media-controls/controls/macos-fullscreen-media-controls.js index d93943b7db5b1..f094851eec031 100644 --- a/Source/WebCore/Modules/modern-media-controls/controls/macos-fullscreen-media-controls.js +++ b/Source/WebCore/Modules/modern-media-controls/controls/macos-fullscreen-media-controls.js @@ -77,6 +77,7 @@ class MacOSFullscreenMediaControls extends MediaControls this.bottomControlsBar.element.addEventListener("mousedown", this); this.bottomControlsBar.element.addEventListener("click", this); + this.element.addEventListener("mousemove", this); this._backgroundClickDelegateNotifier = new BackgroundClickDelegateNotifier(this); } @@ -181,6 +182,11 @@ class MacOSFullscreenMediaControls extends MediaControls _handleMousemove(event) { + if (!this._lastDragPoint) { + this.faded = false; + return; + } + event.preventDefault(); const currentDragPoint = this._pointForEvent(event); diff --git a/Source/WebCore/Modules/notifications/NotificationJSONParser.cpp b/Source/WebCore/Modules/notifications/NotificationJSONParser.cpp index 95fcc505f945a..1a25350ab677d 100644 --- a/Source/WebCore/Modules/notifications/NotificationJSONParser.cpp +++ b/Source/WebCore/Modules/notifications/NotificationJSONParser.cpp @@ -29,6 +29,7 @@ #include "JSDOMConvertEnumeration.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/Modules/notifications/NotificationPermissionCallback.idl b/Source/WebCore/Modules/notifications/NotificationPermissionCallback.idl index e512a60af7978..5aff48287d052 100644 --- a/Source/WebCore/Modules/notifications/NotificationPermissionCallback.idl +++ b/Source/WebCore/Modules/notifications/NotificationPermissionCallback.idl @@ -26,4 +26,5 @@ [ Conditional=NOTIFICATIONS, EnabledBySetting=NotificationsEnabled, + IsStrongCallback ] callback NotificationPermissionCallback = undefined (NotificationPermission permission); diff --git a/Source/WebCore/Modules/paymentrequest/MerchantValidationEvent.cpp b/Source/WebCore/Modules/paymentrequest/MerchantValidationEvent.cpp index f212018308c27..6292b36cde225 100644 --- a/Source/WebCore/Modules/paymentrequest/MerchantValidationEvent.cpp +++ b/Source/WebCore/Modules/paymentrequest/MerchantValidationEvent.cpp @@ -31,6 +31,7 @@ #include "Document.h" #include "PaymentRequest.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/paymentrequest/PaymentRequest.cpp b/Source/WebCore/Modules/paymentrequest/PaymentRequest.cpp index 7ce9942550670..a8dd0e47b9b10 100644 --- a/Source/WebCore/Modules/paymentrequest/PaymentRequest.cpp +++ b/Source/WebCore/Modules/paymentrequest/PaymentRequest.cpp @@ -56,6 +56,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/plugins/YouTubePluginReplacement.cpp b/Source/WebCore/Modules/plugins/YouTubePluginReplacement.cpp index 6f2188e45f1d8..3bc6d54133d1b 100644 --- a/Source/WebCore/Modules/plugins/YouTubePluginReplacement.cpp +++ b/Source/WebCore/Modules/plugins/YouTubePluginReplacement.cpp @@ -33,6 +33,7 @@ #include "Settings.h" #include "ShadowRoot.h" #include "YouTubeEmbedShadowElement.h" +#include #include namespace WebCore { diff --git a/Source/WebCore/Modules/push-api/PushDatabase.cpp b/Source/WebCore/Modules/push-api/PushDatabase.cpp index a0f1c6430d81c..5454dd3ac6ae1 100644 --- a/Source/WebCore/Modules/push-api/PushDatabase.cpp +++ b/Source/WebCore/Modules/push-api/PushDatabase.cpp @@ -38,7 +38,7 @@ #include #include #include -#include +#include #define PUSHDB_RELEASE_LOG(fmt, ...) RELEASE_LOG(Push, "%p - PushDatabase::" fmt, this, ##__VA_ARGS__) #define PUSHDB_RELEASE_LOG_ERROR(fmt, ...) RELEASE_LOG_ERROR(Push, "%p - PushDatabase::" fmt, this, ##__VA_ARGS__) diff --git a/Source/WebCore/Modules/remoteplayback/RemotePlaybackAvailabilityCallback.idl b/Source/WebCore/Modules/remoteplayback/RemotePlaybackAvailabilityCallback.idl index 54dc3157843e9..ffe42786fac38 100644 --- a/Source/WebCore/Modules/remoteplayback/RemotePlaybackAvailabilityCallback.idl +++ b/Source/WebCore/Modules/remoteplayback/RemotePlaybackAvailabilityCallback.idl @@ -25,4 +25,5 @@ [ Conditional=WIRELESS_PLAYBACK_TARGET, EnabledBySetting=RemotePlaybackEnabled, + IsStrongCallback ] callback RemotePlaybackAvailabilityCallback = boolean (boolean available); diff --git a/Source/WebCore/Modules/reporting/ReportingObserverCallback.idl b/Source/WebCore/Modules/reporting/ReportingObserverCallback.idl index 6b6165899a3f8..96be24ada2750 100644 --- a/Source/WebCore/Modules/reporting/ReportingObserverCallback.idl +++ b/Source/WebCore/Modules/reporting/ReportingObserverCallback.idl @@ -27,4 +27,4 @@ typedef sequence ReportList; -[ IsWeakCallback ] callback ReportingObserverCallback = undefined (ReportList reports, ReportingObserver observer); +callback ReportingObserverCallback = undefined (ReportList reports, ReportingObserver observer); diff --git a/Source/WebCore/Modules/web-locks/WebLockGrantedCallback.idl b/Source/WebCore/Modules/web-locks/WebLockGrantedCallback.idl index 4d26a726fa34b..6747dd931e246 100644 --- a/Source/WebCore/Modules/web-locks/WebLockGrantedCallback.idl +++ b/Source/WebCore/Modules/web-locks/WebLockGrantedCallback.idl @@ -23,5 +23,6 @@ */ [ - RethrowException + RethrowException, + IsStrongCallback ] callback WebLockGrantedCallback = Promise (WebLock? webLock); diff --git a/Source/WebCore/Modules/web-locks/WebLockManager.cpp b/Source/WebCore/Modules/web-locks/WebLockManager.cpp index dd5746a253012..a53a09cbf33c9 100644 --- a/Source/WebCore/Modules/web-locks/WebLockManager.cpp +++ b/Source/WebCore/Modules/web-locks/WebLockManager.cpp @@ -43,6 +43,7 @@ #include "WorkerThread.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/web-locks/WebLockRegistry.cpp b/Source/WebCore/Modules/web-locks/WebLockRegistry.cpp index 1af5e0c94f417..951f1b611c397 100644 --- a/Source/WebCore/Modules/web-locks/WebLockRegistry.cpp +++ b/Source/WebCore/Modules/web-locks/WebLockRegistry.cpp @@ -32,7 +32,6 @@ #include #include #include -#include namespace WebCore { diff --git a/Source/WebCore/Modules/webaudio/AudioBufferCallback.idl b/Source/WebCore/Modules/webaudio/AudioBufferCallback.idl index edac941becbb9..17be370a3fdf7 100644 --- a/Source/WebCore/Modules/webaudio/AudioBufferCallback.idl +++ b/Source/WebCore/Modules/webaudio/AudioBufferCallback.idl @@ -30,4 +30,5 @@ [ Conditional=WEB_AUDIO, JSGenerateToJSObject, + IsStrongCallback ] callback AudioBufferCallback = undefined (AudioBuffer? audioBuffer); diff --git a/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp b/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp index f86bffb9b507c..96edb67264830 100644 --- a/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp +++ b/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp @@ -423,11 +423,13 @@ ExceptionOr AudioBufferSourceNode::setBufferForBindings(RefPtr #include #include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/webaudio/AudioWorkletProcessorConstructor.idl b/Source/WebCore/Modules/webaudio/AudioWorkletProcessorConstructor.idl index 2b05ddb78b8c9..dae5bdc416e22 100644 --- a/Source/WebCore/Modules/webaudio/AudioWorkletProcessorConstructor.idl +++ b/Source/WebCore/Modules/webaudio/AudioWorkletProcessorConstructor.idl @@ -28,4 +28,5 @@ [ Conditional=WEB_AUDIO, + IsStrongCallback ] callback AudioWorkletProcessorConstructor = AudioWorkletProcessor (object options); diff --git a/Source/WebCore/Modules/webaudio/DelayNode.cpp b/Source/WebCore/Modules/webaudio/DelayNode.cpp index 8afc4f23547ec..b0b090a15c4a6 100644 --- a/Source/WebCore/Modules/webaudio/DelayNode.cpp +++ b/Source/WebCore/Modules/webaudio/DelayNode.cpp @@ -31,6 +31,7 @@ #include "DelayOptions.h" #include "DelayProcessor.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/webaudio/OfflineAudioContext.cpp b/Source/WebCore/Modules/webaudio/OfflineAudioContext.cpp index 6e9671838d8b8..14020bb0fbef6 100644 --- a/Source/WebCore/Modules/webaudio/OfflineAudioContext.cpp +++ b/Source/WebCore/Modules/webaudio/OfflineAudioContext.cpp @@ -38,6 +38,7 @@ #include "OfflineAudioContextOptions.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsAudioDataOutputCallback.idl b/Source/WebCore/Modules/webcodecs/WebCodecsAudioDataOutputCallback.idl index 1d0e8131fc7fa..a2c9805fd6e06 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsAudioDataOutputCallback.idl +++ b/Source/WebCore/Modules/webcodecs/WebCodecsAudioDataOutputCallback.idl @@ -26,4 +26,5 @@ [ Conditional=WEB_CODECS, + IsStrongCallback ] callback WebCodecsAudioDataOutputCallback = undefined(WebCodecsAudioData output); diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsAudioEncoder.cpp b/Source/WebCore/Modules/webcodecs/WebCodecsAudioEncoder.cpp index 16cc2b3f5723f..a8ea23695a5e9 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsAudioEncoder.cpp +++ b/Source/WebCore/Modules/webcodecs/WebCodecsAudioEncoder.cpp @@ -48,6 +48,7 @@ #include "WebCodecsErrorCallback.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsAudioEncoder.h b/Source/WebCore/Modules/webcodecs/WebCodecsAudioEncoder.h index bbc2aa8f323bc..9443aa06fd127 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsAudioEncoder.h +++ b/Source/WebCore/Modules/webcodecs/WebCodecsAudioEncoder.h @@ -75,6 +75,8 @@ class WebCodecsAudioEncoder void ref() const final { ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr::ref(); } void deref() const final { ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr::deref(); } + WebCodecsEncodedAudioChunkOutputCallback& outputCallbackConcurrently() { return m_output.get(); } + WebCodecsErrorCallback& errorCallbackConcurrently() { return m_error.get(); } private: WebCodecsAudioEncoder(ScriptExecutionContext&, Init&&); diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsAudioEncoder.idl b/Source/WebCore/Modules/webcodecs/WebCodecsAudioEncoder.idl index 677dd0efd602c..88ce54f5570b6 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsAudioEncoder.idl +++ b/Source/WebCore/Modules/webcodecs/WebCodecsAudioEncoder.idl @@ -31,6 +31,7 @@ EnabledBySetting=WebCodecsAudioEnabled, Exposed=(Window,DedicatedWorker), InterfaceName=AudioEncoder, + JSCustomMarkFunction ] interface WebCodecsAudioEncoder : EventTarget { [CallWith=CurrentScriptExecutionContext] constructor(WebCodecsAudioEncoderInit init); diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsEncodedAudioChunkOutputCallback.h b/Source/WebCore/Modules/webcodecs/WebCodecsEncodedAudioChunkOutputCallback.h index 75a44e803d04a..8dec05eb5fbcc 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsEncodedAudioChunkOutputCallback.h +++ b/Source/WebCore/Modules/webcodecs/WebCodecsEncodedAudioChunkOutputCallback.h @@ -43,6 +43,9 @@ class WebCodecsEncodedAudioChunkOutputCallback : public RefCounted handleEvent(WebCodecsEncodedAudioChunk&, const WebCodecsEncodedAudioChunkMetadata&) = 0; + +private: + virtual bool hasCallback() const = 0; }; } // namespace WebCore diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsEncodedAudioChunkOutputCallback.idl b/Source/WebCore/Modules/webcodecs/WebCodecsEncodedAudioChunkOutputCallback.idl index a49e92de8503d..caa6bbc2072ee 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsEncodedAudioChunkOutputCallback.idl +++ b/Source/WebCore/Modules/webcodecs/WebCodecsEncodedAudioChunkOutputCallback.idl @@ -24,6 +24,4 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -[ - Conditional=WEB_CODECS, -] callback WebCodecsEncodedAudioChunkOutputCallback = undefined (WebCodecsEncodedAudioChunk chunk, optional WebCodecsEncodedAudioChunkMetadata metadata = {}); +[ Conditional=WEB_CODECS ] callback WebCodecsEncodedAudioChunkOutputCallback = undefined (WebCodecsEncodedAudioChunk chunk, optional WebCodecsEncodedAudioChunkMetadata metadata = {}); diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsEncodedVideoChunkOutputCallback.h b/Source/WebCore/Modules/webcodecs/WebCodecsEncodedVideoChunkOutputCallback.h index 1fe13d714fe90..99865cd816ac9 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsEncodedVideoChunkOutputCallback.h +++ b/Source/WebCore/Modules/webcodecs/WebCodecsEncodedVideoChunkOutputCallback.h @@ -42,6 +42,9 @@ class WebCodecsEncodedVideoChunkOutputCallback : public RefCounted handleEvent(WebCodecsEncodedVideoChunk&, const WebCodecsEncodedVideoChunkMetadata&) = 0; + +private: + virtual bool hasCallback() const = 0; }; } // namespace WebCore diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsEncodedVideoChunkOutputCallback.idl b/Source/WebCore/Modules/webcodecs/WebCodecsEncodedVideoChunkOutputCallback.idl index 87503118aacc4..f4e7045a1450c 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsEncodedVideoChunkOutputCallback.idl +++ b/Source/WebCore/Modules/webcodecs/WebCodecsEncodedVideoChunkOutputCallback.idl @@ -23,6 +23,4 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -[ - Conditional=WEB_CODECS, -] callback WebCodecsEncodedVideoChunkOutputCallback = undefined (WebCodecsEncodedVideoChunk chunk, optional WebCodecsEncodedVideoChunkMetadata metadata = {}); +[ Conditional=WEB_CODECS ] callback WebCodecsEncodedVideoChunkOutputCallback = undefined (WebCodecsEncodedVideoChunk chunk, optional WebCodecsEncodedVideoChunkMetadata metadata = {}); diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsErrorCallback.h b/Source/WebCore/Modules/webcodecs/WebCodecsErrorCallback.h index e5b7116a9ada6..63d707124bfef 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsErrorCallback.h +++ b/Source/WebCore/Modules/webcodecs/WebCodecsErrorCallback.h @@ -41,6 +41,9 @@ class WebCodecsErrorCallback : public RefCounted, public using ActiveDOMCallback::ActiveDOMCallback; virtual CallbackResult handleEvent(DOMException&) = 0; + +private: + virtual bool hasCallback() const = 0; }; } // namespace WebCore diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsErrorCallback.idl b/Source/WebCore/Modules/webcodecs/WebCodecsErrorCallback.idl index 2e8b6c5e9ec00..1eb6e92b21ffe 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsErrorCallback.idl +++ b/Source/WebCore/Modules/webcodecs/WebCodecsErrorCallback.idl @@ -23,6 +23,4 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -[ - Conditional=WEB_CODECS, -] callback WebCodecsErrorCallback = undefined(DOMException error); +[ Conditional=WEB_CODECS ] callback WebCodecsErrorCallback = undefined(DOMException error); diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsVideoDecoder.h b/Source/WebCore/Modules/webcodecs/WebCodecsVideoDecoder.h index 7d34a4e0bc94a..a87882cfe2f87 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsVideoDecoder.h +++ b/Source/WebCore/Modules/webcodecs/WebCodecsVideoDecoder.h @@ -61,6 +61,9 @@ class WebCodecsVideoDecoder WebCodecsCodecState state() const { return m_state; } size_t decodeQueueSize() const { return m_decodeQueueSize; } + WebCodecsVideoFrameOutputCallback& outputCallbackConcurrently() { return m_output.get(); } + WebCodecsErrorCallback& errorCallbackConcurrently() { return m_error.get(); } + ExceptionOr configure(ScriptExecutionContext&, WebCodecsVideoDecoderConfig&&); ExceptionOr decode(Ref&&); ExceptionOr flush(Ref&&); diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsVideoDecoder.idl b/Source/WebCore/Modules/webcodecs/WebCodecsVideoDecoder.idl index ebe4c60ad4c8e..9eda6c017ff52 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsVideoDecoder.idl +++ b/Source/WebCore/Modules/webcodecs/WebCodecsVideoDecoder.idl @@ -30,6 +30,7 @@ EnabledBySetting=WebCodecsVideoEnabled, Exposed=(Window,DedicatedWorker), InterfaceName=VideoDecoder, + JSCustomMarkFunction, ] interface WebCodecsVideoDecoder : EventTarget { [CallWith=CurrentScriptExecutionContext] constructor(WebCodecsVideoDecoderInit init); diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoder.cpp b/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoder.cpp index a31cbd6333c1d..df8bc8c2e954b 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoder.cpp +++ b/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoder.cpp @@ -44,6 +44,7 @@ #include "WebCodecsVideoFrame.h" #include #include +#include namespace WebCore { @@ -112,6 +113,32 @@ static ExceptionOr createVideoEncoderConfig(const WebCodec return VideoEncoder::Config { config.width, config.height, useAnnexB, config.bitrate.value_or(0), config.framerate.value_or(0), config.latencyMode == LatencyMode::Realtime, scalabilityMode }; } +bool WebCodecsVideoEncoder::updateRates(const WebCodecsVideoEncoderConfig& config) +{ + auto bitrate = config.bitrate.value_or(0); + auto framerate = config.framerate.value_or(0); + + m_isMessageQueueBlocked = true; + bool isChangingRatesSupported = m_internalEncoder->setRates(bitrate, framerate, [this, weakThis = WeakPtr { *this }, bitrate, framerate]() mutable { + auto protectedThis = weakThis.get(); + if (!protectedThis) + return; + + if (m_state == WebCodecsCodecState::Closed || !scriptExecutionContext()) + return; + + if (bitrate) + m_baseConfiguration.bitrate = bitrate; + if (framerate) + m_baseConfiguration.framerate = framerate; + m_isMessageQueueBlocked = false; + processControlMessageQueue(); + }); + if (!isChangingRatesSupported) + m_isMessageQueueBlocked = false; + return isChangingRatesSupported; +} + ExceptionOr WebCodecsVideoEncoder::configure(ScriptExecutionContext& context, WebCodecsVideoEncoderConfig&& config) { if (!isValidEncoderConfig(config)) @@ -125,6 +152,9 @@ ExceptionOr WebCodecsVideoEncoder::configure(ScriptExecutionContext& conte if (m_internalEncoder) { queueControlMessageAndProcess([this, config]() mutable { + if (isSameConfigurationExceptBitrateAndFramerate(m_baseConfiguration, config) && updateRates(config)) + return; + m_isMessageQueueBlocked = true; m_internalEncoder->flush([weakThis = ThreadSafeWeakPtr { *this }, config = WTFMove(config)]() mutable { RefPtr protectedThis = weakThis.get(); @@ -142,6 +172,9 @@ ExceptionOr WebCodecsVideoEncoder::configure(ScriptExecutionContext& conte bool isSupportedCodec = isSupportedEncoderCodec(config.codec, context.settingsValues()); queueControlMessageAndProcess([this, config = WTFMove(config), isSupportedCodec, identifier = scriptExecutionContext()->identifier()]() mutable { + if (isSupportedCodec && isSameConfigurationExceptBitrateAndFramerate(m_baseConfiguration, config) && updateRates(config)) + return; + m_isMessageQueueBlocked = true; VideoEncoder::PostTaskCallback postTaskCallback = [weakThis = ThreadSafeWeakPtr { *this }, identifier](auto&& task) { ScriptExecutionContext::postTaskTo(identifier, [weakThis, task = WTFMove(task)](auto&) mutable { diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoder.h b/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoder.h index f5b97bc9e9a00..eb9d60e34f388 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoder.h +++ b/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoder.h @@ -74,6 +74,9 @@ class WebCodecsVideoEncoder void ref() const final { ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr::ref(); } void deref() const final { ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr::deref(); } + WebCodecsEncodedVideoChunkOutputCallback& outputCallbackConcurrently() { return m_output.get(); } + WebCodecsErrorCallback& errorCallbackConcurrently() { return m_error.get(); } + private: WebCodecsVideoEncoder(ScriptExecutionContext&, Init&&); @@ -96,6 +99,7 @@ class WebCodecsVideoEncoder void queueControlMessageAndProcess(Function&&); void processControlMessageQueue(); WebCodecsEncodedVideoChunkMetadata createEncodedChunkMetadata(std::optional); + bool updateRates(const WebCodecsVideoEncoderConfig&); WebCodecsCodecState m_state { WebCodecsCodecState::Unconfigured }; size_t m_encodeQueueSize { 0 }; diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoder.idl b/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoder.idl index 22b56f25608dc..3dfca5ec7b94c 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoder.idl +++ b/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoder.idl @@ -30,6 +30,7 @@ EnabledBySetting=WebCodecsVideoEnabled, Exposed=(Window,DedicatedWorker), InterfaceName=VideoEncoder, + JSCustomMarkFunction ] interface WebCodecsVideoEncoder : EventTarget { [CallWith=CurrentScriptExecutionContext] constructor(WebCodecsVideoEncoderInit init); diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoderConfig.h b/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoderConfig.h index 3481f1ccc648d..15e873bef0662 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoderConfig.h +++ b/Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoderConfig.h @@ -55,6 +55,22 @@ struct WebCodecsVideoEncoderConfig { WebCodecsVideoEncoderConfig isolatedCopy() const & { return { codec.isolatedCopy(), width, height, displayWidth, displayHeight, bitrate, framerate, hardwareAcceleration, alpha, scalabilityMode.isolatedCopy(), bitrateMode, latencyMode, avc }; } }; +inline bool isSameConfigurationExceptBitrateAndFramerate(const WebCodecsVideoEncoderConfig& a, const WebCodecsVideoEncoderConfig& b) +{ + return a.codec == b.codec + && a.width == b.width + && a.height == b.height + && a.displayWidth == b.displayWidth + && a.displayHeight == b.displayHeight + && a.hardwareAcceleration == b.hardwareAcceleration + && a.alpha == b.alpha + && a.scalabilityMode == b.scalabilityMode + && a.bitrateMode == b.bitrateMode + && a.latencyMode == b.latencyMode + && (!!a.avc == !!b.avc) + && (!a.avc || (a.avc->format == b.avc->format)); +} + } #endif // ENABLE(WEB_CODECS) diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrame.cpp b/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrame.cpp index b7a61da7d6afa..77b357899c28c 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrame.cpp +++ b/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrame.cpp @@ -46,6 +46,7 @@ #include "VideoColorSpace.h" #include "WebCodecsVideoFrameAlgorithms.h" #include +#include #if PLATFORM(COCOA) #include "VideoFrameCV.h" diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrameOutputCallback.h b/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrameOutputCallback.h index 8b6e704d114a1..f7ceb062c5d5d 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrameOutputCallback.h +++ b/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrameOutputCallback.h @@ -41,6 +41,9 @@ class WebCodecsVideoFrameOutputCallback : public RefCounted handleEvent(WebCodecsVideoFrame&) = 0; + +private: + virtual bool hasCallback() const = 0; }; } // namespace WebCore diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrameOutputCallback.idl b/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrameOutputCallback.idl index 76c31514bc2bc..12b600f3c66e4 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrameOutputCallback.idl +++ b/Source/WebCore/Modules/webcodecs/WebCodecsVideoFrameOutputCallback.idl @@ -23,6 +23,4 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -[ - Conditional=WEB_CODECS, -] callback WebCodecsVideoFrameOutputCallback = undefined(WebCodecsVideoFrame output); +[ Conditional=WEB_CODECS ] callback WebCodecsVideoFrameOutputCallback = undefined(WebCodecsVideoFrame output); diff --git a/Source/WebCore/Modules/webdatabase/Database.cpp b/Source/WebCore/Modules/webdatabase/Database.cpp index ac523c2b2d7f2..e194d3543e8f3 100644 --- a/Source/WebCore/Modules/webdatabase/Database.cpp +++ b/Source/WebCore/Modules/webdatabase/Database.cpp @@ -58,6 +58,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/webdatabase/DatabaseCallback.idl b/Source/WebCore/Modules/webdatabase/DatabaseCallback.idl index 2394f21b6b08c..d6d9cfd121180 100644 --- a/Source/WebCore/Modules/webdatabase/DatabaseCallback.idl +++ b/Source/WebCore/Modules/webdatabase/DatabaseCallback.idl @@ -26,4 +26,4 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -callback DatabaseCallback = undefined (Database database); +[ IsStrongCallback ] callback DatabaseCallback = undefined (Database database); diff --git a/Source/WebCore/Modules/webdatabase/DatabaseTracker.cpp b/Source/WebCore/Modules/webdatabase/DatabaseTracker.cpp index 368e6139eb48c..81e523bf4594c 100644 --- a/Source/WebCore/Modules/webdatabase/DatabaseTracker.cpp +++ b/Source/WebCore/Modules/webdatabase/DatabaseTracker.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #if PLATFORM(IOS_FAMILY) diff --git a/Source/WebCore/Modules/webdatabase/SQLError.h b/Source/WebCore/Modules/webdatabase/SQLError.h index 7a3a0f8347265..72f5053a0c842 100644 --- a/Source/WebCore/Modules/webdatabase/SQLError.h +++ b/Source/WebCore/Modules/webdatabase/SQLError.h @@ -29,7 +29,7 @@ #pragma once #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/webdatabase/SQLStatementCallback.idl b/Source/WebCore/Modules/webdatabase/SQLStatementCallback.idl index 7e6ace365922f..4ef95d6b1cefd 100644 --- a/Source/WebCore/Modules/webdatabase/SQLStatementCallback.idl +++ b/Source/WebCore/Modules/webdatabase/SQLStatementCallback.idl @@ -26,4 +26,4 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -callback SQLStatementCallback = undefined (SQLTransaction transaction, SQLResultSet resultSet); +[ IsStrongCallback ] callback SQLStatementCallback = undefined (SQLTransaction transaction, SQLResultSet resultSet); diff --git a/Source/WebCore/Modules/webdatabase/SQLStatementErrorCallback.idl b/Source/WebCore/Modules/webdatabase/SQLStatementErrorCallback.idl index c3fce7cce2a93..1cb649f7933ce 100644 --- a/Source/WebCore/Modules/webdatabase/SQLStatementErrorCallback.idl +++ b/Source/WebCore/Modules/webdatabase/SQLStatementErrorCallback.idl @@ -26,4 +26,4 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -callback SQLStatementErrorCallback = boolean (SQLTransaction transaction, SQLError error); +[ IsStrongCallback ] callback SQLStatementErrorCallback = boolean (SQLTransaction transaction, SQLError error); diff --git a/Source/WebCore/Modules/webdatabase/SQLTransactionCallback.idl b/Source/WebCore/Modules/webdatabase/SQLTransactionCallback.idl index f2e301e094d84..7fa612ead4a03 100644 --- a/Source/WebCore/Modules/webdatabase/SQLTransactionCallback.idl +++ b/Source/WebCore/Modules/webdatabase/SQLTransactionCallback.idl @@ -26,4 +26,4 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -callback SQLTransactionCallback = undefined (SQLTransaction transaction); +[ IsStrongCallback ] callback SQLTransactionCallback = undefined (SQLTransaction transaction); diff --git a/Source/WebCore/Modules/webdatabase/SQLTransactionErrorCallback.idl b/Source/WebCore/Modules/webdatabase/SQLTransactionErrorCallback.idl index f9340e4658327..e218c1f241273 100644 --- a/Source/WebCore/Modules/webdatabase/SQLTransactionErrorCallback.idl +++ b/Source/WebCore/Modules/webdatabase/SQLTransactionErrorCallback.idl @@ -26,4 +26,4 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -callback SQLTransactionErrorCallback = undefined (SQLError error); +[ IsStrongCallback ] callback SQLTransactionErrorCallback = undefined (SQLError error); diff --git a/Source/WebCore/Modules/websockets/ThreadableWebSocketChannel.cpp b/Source/WebCore/Modules/websockets/ThreadableWebSocketChannel.cpp index 05c53e1384101..2dc002fdc81cd 100644 --- a/Source/WebCore/Modules/websockets/ThreadableWebSocketChannel.cpp +++ b/Source/WebCore/Modules/websockets/ThreadableWebSocketChannel.cpp @@ -48,6 +48,7 @@ #include "WorkerRunLoop.h" #include "WorkerThread.h" #include "WorkerThreadableWebSocketChannel.h" +#include namespace WebCore { diff --git a/Source/WebCore/Modules/websockets/WebSocket.cpp b/Source/WebCore/Modules/websockets/WebSocket.cpp index cf62ecc5a069d..14b15c4366c64 100644 --- a/Source/WebCore/Modules/websockets/WebSocket.cpp +++ b/Source/WebCore/Modules/websockets/WebSocket.cpp @@ -69,6 +69,7 @@ #include #include #include +#include #include #if USE(WEB_THREAD) diff --git a/Source/WebCore/Modules/websockets/WebSocketExtensionDispatcher.cpp b/Source/WebCore/Modules/websockets/WebSocketExtensionDispatcher.cpp index 7edf9714bf2d8..ee5a5973611cd 100644 --- a/Source/WebCore/Modules/websockets/WebSocketExtensionDispatcher.cpp +++ b/Source/WebCore/Modules/websockets/WebSocketExtensionDispatcher.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include namespace WebCore { @@ -62,11 +63,7 @@ const String WebSocketExtensionDispatcher::createHeaderValue() const if (!numProcessors) return String(); - StringBuilder builder; - builder.append(m_processors[0]->handshakeString()); - for (size_t i = 1; i < numProcessors; ++i) - builder.append(", "_s, m_processors[i]->handshakeString()); - return builder.toString(); + return makeString(interleave(m_processors, [](auto& processor) { return processor->handshakeString(); }, ", "_s)); } void WebSocketExtensionDispatcher::appendAcceptedExtension(const String& extensionToken, HashMap& extensionParameters) @@ -121,7 +118,7 @@ bool WebSocketExtensionDispatcher::processHeaderValue(const String& headerValue) } // There is no extension which can process the response. if (index == m_processors.size()) { - fail(makeString("Received unexpected extension: "_s + extensionToken)); + fail(makeString("Received unexpected extension: "_s, extensionToken)); return false; } } diff --git a/Source/WebCore/Modules/websockets/WebSocketExtensionProcessor.h b/Source/WebCore/Modules/websockets/WebSocketExtensionProcessor.h index 947bd58e8b41c..741ae06c9ccd8 100644 --- a/Source/WebCore/Modules/websockets/WebSocketExtensionProcessor.h +++ b/Source/WebCore/Modules/websockets/WebSocketExtensionProcessor.h @@ -31,6 +31,7 @@ #pragma once #include +#include #include namespace WebCore { diff --git a/Source/WebCore/Modules/websockets/WebSocketFrame.cpp b/Source/WebCore/Modules/websockets/WebSocketFrame.cpp index 135fb906f5a0b..91663ffe2c7bb 100644 --- a/Source/WebCore/Modules/websockets/WebSocketFrame.cpp +++ b/Source/WebCore/Modules/websockets/WebSocketFrame.cpp @@ -25,7 +25,7 @@ #include "WebSocketFrame.h" #include #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/Modules/websockets/WebSocketHandshake.cpp b/Source/WebCore/Modules/websockets/WebSocketHandshake.cpp index af4cf62e004b8..60657b749d1b6 100644 --- a/Source/WebCore/Modules/websockets/WebSocketHandshake.cpp +++ b/Source/WebCore/Modules/websockets/WebSocketHandshake.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include diff --git a/Source/WebCore/Modules/websockets/WorkerThreadableWebSocketChannel.cpp b/Source/WebCore/Modules/websockets/WorkerThreadableWebSocketChannel.cpp index 45059699dd129..83ce81b3ed4e0 100644 --- a/Source/WebCore/Modules/websockets/WorkerThreadableWebSocketChannel.cpp +++ b/Source/WebCore/Modules/websockets/WorkerThreadableWebSocketChannel.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/Modules/webxr/WebXROpaqueFramebufferCocoa.cpp b/Source/WebCore/Modules/webxr/WebXROpaqueFramebufferCocoa.cpp index 8a5d8e17bb657..72de2d1619b53 100644 --- a/Source/WebCore/Modules/webxr/WebXROpaqueFramebufferCocoa.cpp +++ b/Source/WebCore/Modules/webxr/WebXROpaqueFramebufferCocoa.cpp @@ -117,6 +117,7 @@ WebXROpaqueFramebuffer::~WebXROpaqueFramebuffer() if (RefPtr gl = m_context.graphicsContextGL()) { m_drawAttachments.release(*gl); m_resolveAttachments.release(*gl); + m_displayFBO.release(*gl); m_resolvedFBO.release(*gl); m_context.deleteFramebuffer(m_drawFramebuffer.ptr()); } else { diff --git a/Source/WebCore/Modules/webxr/XRFrameRequestCallback.idl b/Source/WebCore/Modules/webxr/XRFrameRequestCallback.idl index 452e90f5c663f..a6e5a2829bb2b 100644 --- a/Source/WebCore/Modules/webxr/XRFrameRequestCallback.idl +++ b/Source/WebCore/Modules/webxr/XRFrameRequestCallback.idl @@ -28,5 +28,6 @@ typedef double DOMHighResTimeStamp; // https://immersive-web.github.io/webxr/#callbackdef-xrframerequestcallback [ Conditional=WEBXR, - EnabledBySetting=WebXREnabled + EnabledBySetting=WebXREnabled, + IsStrongCallback ] callback XRFrameRequestCallback = undefined (DOMHighResTimeStamp time, WebXRFrame frame); diff --git a/Source/WebCore/PAL/PAL.xcodeproj/project.pbxproj b/Source/WebCore/PAL/PAL.xcodeproj/project.pbxproj index 1ea7488624d3a..1a351ca8a2754 100644 --- a/Source/WebCore/PAL/PAL.xcodeproj/project.pbxproj +++ b/Source/WebCore/PAL/PAL.xcodeproj/project.pbxproj @@ -290,6 +290,7 @@ E34F26F62846D0D90076E549 /* PowerLogSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = E34F26F52846B7550076E549 /* PowerLogSPI.h */; settings = {ATTRIBUTES = (Private, ); }; }; E57B44B529AB45F4006069DE /* VisionSoftLink.h in Headers */ = {isa = PBXBuildFile; fileRef = E57B44B329AB45F4006069DE /* VisionSoftLink.h */; settings = {ATTRIBUTES = (Private, ); }; }; E57B44B729AB462E006069DE /* VisionSoftLink.mm in Sources */ = {isa = PBXBuildFile; fileRef = E57B44B429AB45F4006069DE /* VisionSoftLink.mm */; }; + E5C5B20B2C379AA000838733 /* UIFoundationSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = E5C5B20A2C379AA000838733 /* UIFoundationSPI.h */; settings = {ATTRIBUTES = (Private, ); }; }; EB3FE8E12A5DB94A00A20986 /* SQLite3SPI.h in Headers */ = {isa = PBXBuildFile; fileRef = EB3FE8E02A5DB94A00A20986 /* SQLite3SPI.h */; settings = {ATTRIBUTES = (Private, ); }; }; EBC13F3C2BD07DA500310E86 /* MobileKeyBagSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = EBC13F3B2BD07DA500310E86 /* MobileKeyBagSPI.h */; settings = {ATTRIBUTES = (Private, ); }; }; F410F1552ACA2EBA00A79859 /* LinkPresentationSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = F410F1542ACA2EBA00A79859 /* LinkPresentationSPI.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -696,6 +697,7 @@ E34F26F52846B7550076E549 /* PowerLogSPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PowerLogSPI.h; sourceTree = ""; }; E57B44B329AB45F4006069DE /* VisionSoftLink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VisionSoftLink.h; sourceTree = ""; }; E57B44B429AB45F4006069DE /* VisionSoftLink.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = VisionSoftLink.mm; sourceTree = ""; }; + E5C5B20A2C379AA000838733 /* UIFoundationSPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UIFoundationSPI.h; sourceTree = ""; }; E5D45D112106A07400D2B738 /* NSColorWellSPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSColorWellSPI.h; sourceTree = ""; }; E5D45D132106A18700D2B738 /* NSPopoverColorWellSPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSPopoverColorWellSPI.h; sourceTree = ""; }; EB3FE8E02A5DB94A00A20986 /* SQLite3SPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQLite3SPI.h; sourceTree = ""; }; @@ -865,6 +867,7 @@ EB3FE8E02A5DB94A00A20986 /* SQLite3SPI.h */, 4996C0F22717642B002C125D /* TCCSPI.h */, F44C007D29A06BC200211F33 /* TranslationUIServicesSPI.h */, + E5C5B20A2C379AA000838733 /* UIFoundationSPI.h */, 0C2DA12B1F3BEB4900DBC317 /* URLFormattingSPI.h */, F46B8C4E26740AD8007A6554 /* VisionKitCoreSPI.h */, 0C2DA13D1F3BEB4900DBC317 /* WebFilterEvaluatorSPI.h */, @@ -1532,6 +1535,7 @@ DD20DE6427BC90D80093D175 /* ThreadGlobalData.h in Headers */, F44C007B29A06B1C00211F33 /* TranslationUIServicesSoftLink.h in Headers */, F44C007E29A06BC200211F33 /* TranslationUIServicesSPI.h in Headers */, + E5C5B20B2C379AA000838733 /* UIFoundationSPI.h in Headers */, DD20DDC427BC90D70093D175 /* UIKitSoftLink.h in Headers */, DD20DE1B27BC90D80093D175 /* UIKitSPI.h in Headers */, DD20DE5D27BC90D80093D175 /* UnencodableHandling.h in Headers */, diff --git a/Source/WebCore/PAL/ThirdParty/libavif/ThirdParty/dav1d/src/decode.c b/Source/WebCore/PAL/ThirdParty/libavif/ThirdParty/dav1d/src/decode.c index eeb6e77bc08f4..839867c004d9d 100644 --- a/Source/WebCore/PAL/ThirdParty/libavif/ThirdParty/dav1d/src/decode.c +++ b/Source/WebCore/PAL/ThirdParty/libavif/ThirdParty/dav1d/src/decode.c @@ -2067,7 +2067,7 @@ static int decode_b(Dav1dTaskContext *const t, f->frame_hdr->loopfilter.level_y[1]) { const int is_globalmv = - b->inter_mode == (is_comp ? GLOBALMV_GLOBALMV : GLOBALMV); + is_comp ? (b->inter_mode == GLOBALMV_GLOBALMV) : (b->inter_mode == GLOBALMV); const uint8_t (*const lf_lvls)[8][2] = (const uint8_t (*)[8][2]) &ts->lflvl[b->seg_id][0][b->ref[0] + 1][!is_globalmv]; const uint16_t tx_split[2] = { b->tx_split0, b->tx_split1 }; diff --git a/Source/WebCore/PAL/pal/FileSizeFormatter.cpp b/Source/WebCore/PAL/pal/FileSizeFormatter.cpp index 29b90f9b9b973..e4ebde34d520a 100644 --- a/Source/WebCore/PAL/pal/FileSizeFormatter.cpp +++ b/Source/WebCore/PAL/pal/FileSizeFormatter.cpp @@ -26,7 +26,7 @@ #include "config.h" #include "FileSizeFormatter.h" -#include +#include namespace PAL { diff --git a/Source/WebCore/PAL/pal/cocoa/AVFoundationSoftLink.mm b/Source/WebCore/PAL/pal/cocoa/AVFoundationSoftLink.mm index 4120e745826c9..10a3a1da5ba67 100644 --- a/Source/WebCore/PAL/pal/cocoa/AVFoundationSoftLink.mm +++ b/Source/WebCore/PAL/pal/cocoa/AVFoundationSoftLink.mm @@ -33,7 +33,7 @@ #if !PLATFORM(WATCHOS) && !PLATFORM(APPLETV) SOFT_LINK_FRAMEWORK_FOR_SOURCE_WITH_EXPORT(PAL, AVFoundation, PAL_EXPORT) #else -@interface AVPlayerItem (DisableKVOOnSupressesVideoLayers) +@interface AVPlayerItem (DisableKVOOnSuppressesVideoLayers) + (BOOL)automaticallyNotifiesObserversOfSuppressesVideoLayers; @end diff --git a/Source/WebCore/PAL/pal/spi/cf/CFNetworkSPI.h b/Source/WebCore/PAL/pal/spi/cf/CFNetworkSPI.h index cca8df90a0435..74a39c1f1c98d 100644 --- a/Source/WebCore/PAL/pal/spi/cf/CFNetworkSPI.h +++ b/Source/WebCore/PAL/pal/spi/cf/CFNetworkSPI.h @@ -139,6 +139,7 @@ typedef enum { nw_resolver_t nw_resolver_create_with_endpoint(nw_endpoint_t, nw_parameters_t); typedef void (^nw_resolver_update_block_t) (nw_resolver_status_t, nw_array_t); bool nw_resolver_set_update_handler(nw_resolver_t, dispatch_queue_t, nw_resolver_update_block_t); +bool nw_resolver_cancel(nw_resolver_t); void nw_context_set_privacy_level(nw_context_t, nw_context_privacy_level_t); void nw_parameters_set_context(nw_parameters_t, nw_context_t); nw_context_t nw_context_create(const char *); diff --git a/Source/WebCore/PAL/pal/spi/cocoa/AVFoundationSPI.h b/Source/WebCore/PAL/pal/spi/cocoa/AVFoundationSPI.h index 4c172687cb7e4..4891b978b1c5b 100644 --- a/Source/WebCore/PAL/pal/spi/cocoa/AVFoundationSPI.h +++ b/Source/WebCore/PAL/pal/spi/cocoa/AVFoundationSPI.h @@ -90,8 +90,8 @@ typedef NSString * AVVideoRange NS_TYPED_ENUM; @end #endif -#if HAVE(AVPLAYER_SUPRESSES_AUDIO_RENDERING) -@interface AVPlayer (AVPlayerSupressesAudioRendering) +#if HAVE(AVPLAYER_SUPPRESSES_AUDIO_RENDERING) +@interface AVPlayer (AVPlayerSuppressesAudioRendering) @property (nonatomic, getter=_suppressesAudioRendering, setter=_setSuppressesAudioRendering:) BOOL suppressesAudioRendering; @end #endif diff --git a/Source/WebCore/PAL/pal/spi/cocoa/AccessibilitySupportSPI.h b/Source/WebCore/PAL/pal/spi/cocoa/AccessibilitySupportSPI.h index 8b17f1236a248..d06259ea1d458 100644 --- a/Source/WebCore/PAL/pal/spi/cocoa/AccessibilitySupportSPI.h +++ b/Source/WebCore/PAL/pal/spi/cocoa/AccessibilitySupportSPI.h @@ -29,7 +29,10 @@ #if USE(APPLE_INTERNAL_SDK) +// FIXME: Remove once rdar://131328679 is fixed and distributed. +IGNORE_WARNINGS_BEGIN("#warnings") #include +IGNORE_WARNINGS_END #else diff --git a/Source/WebCore/PAL/pal/spi/cocoa/UIFoundationSPI.h b/Source/WebCore/PAL/pal/spi/cocoa/UIFoundationSPI.h new file mode 100644 index 0000000000000..6a0d7f2db0054 --- /dev/null +++ b/Source/WebCore/PAL/pal/spi/cocoa/UIFoundationSPI.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. + */ + +#pragma once + +#if USE(APPLE_INTERNAL_SDK) + +#if ENABLE(MULTI_REPRESENTATION_HEIC) +#import +#import +#endif + +#else + +#if ENABLE(MULTI_REPRESENTATION_HEIC) + +#if USE(APPKIT) +#import +#else +#import +#endif + +@interface NSEmojiImageStrike : CTEmojiImageStrike +@end + +@interface NSAdaptiveImageGlyph () +@property (readonly) NSArray *strikes; +@end + +#endif // ENABLE(MULTI_REPRESENTATION_HEIC) + +#endif // USE(APPLE_INTERNAL_SDK) diff --git a/Source/WebCore/PAL/pal/spi/mac/NSMenuSPI.h b/Source/WebCore/PAL/pal/spi/mac/NSMenuSPI.h index 0e6470e0c1a69..5abba0c0dba79 100644 --- a/Source/WebCore/PAL/pal/spi/mac/NSMenuSPI.h +++ b/Source/WebCore/PAL/pal/spi/mac/NSMenuSPI.h @@ -31,6 +31,12 @@ #import +@interface NSMenuItem (Staging_129192954) + ++ (NSMenuItem *)standardWritingToolsMenuItem; + +@end + #elif USE(APPLE_INTERNAL_SDK) WTF_EXTERN_C_BEGIN @@ -64,6 +70,7 @@ enum { @interface NSMenuItem () + (QLPreviewMenuItem *)standardQuickLookMenuItem; + (NSMenuItem *)standardShareMenuItemForItems:(NSArray *)items; ++ (NSMenuItem *)standardWritingToolsMenuItem; @end #endif diff --git a/Source/WebCore/PAL/pal/text/TextCodecCJK.cpp b/Source/WebCore/PAL/pal/text/TextCodecCJK.cpp index 3846dfd4e8592..473aab0f8377b 100644 --- a/Source/WebCore/PAL/pal/text/TextCodecCJK.cpp +++ b/Source/WebCore/PAL/pal/text/TextCodecCJK.cpp @@ -29,6 +29,7 @@ #include "EncodingTables.h" #include #include +#include #include #include diff --git a/Source/WebCore/PAL/pal/text/win/TextCodecWin.cpp b/Source/WebCore/PAL/pal/text/win/TextCodecWin.cpp index 37a0872a36da4..f1ebcbc28466f 100644 --- a/Source/WebCore/PAL/pal/text/win/TextCodecWin.cpp +++ b/Source/WebCore/PAL/pal/text/win/TextCodecWin.cpp @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include diff --git a/Source/WebCore/PAL/pal/unix/LoggingUnix.cpp b/Source/WebCore/PAL/pal/unix/LoggingUnix.cpp index 5b3310265563f..fbde998eb89e7 100644 --- a/Source/WebCore/PAL/pal/unix/LoggingUnix.cpp +++ b/Source/WebCore/PAL/pal/unix/LoggingUnix.cpp @@ -26,10 +26,11 @@ #include "config.h" #include "Logging.h" -#include - #if !LOG_DISABLED || !RELEASE_LOG_DISABLED +#include +#include + namespace PAL { String logLevelString() diff --git a/Source/WebCore/SmartPointerExpectations/UncountedCallArgsCheckerExpectations b/Source/WebCore/SmartPointerExpectations/UncountedCallArgsCheckerExpectations index 8ccc7aab2a621..51177c967420d 100644 --- a/Source/WebCore/SmartPointerExpectations/UncountedCallArgsCheckerExpectations +++ b/Source/WebCore/SmartPointerExpectations/UncountedCallArgsCheckerExpectations @@ -333,7 +333,6 @@ JSStorageManager.cpp JSStyleMedia.h JSStyleSheetList.cpp JSSubtleCrypto.cpp -JSText.cpp JSTextTrack.cpp JSTextTrackCue.cpp JSTextTrackCueList.cpp @@ -349,7 +348,6 @@ JSVideoConfiguration.cpp JSVideoTrack.cpp JSViewTimeline.cpp JSVisualViewport.cpp -JSWakeLock.cpp JSWakeLockSentinel.cpp JSWaveShaperNode.cpp JSWebAnimation.cpp @@ -1521,7 +1519,6 @@ page/PageSerializer.cpp page/PartitionedSecurityOrigin.h page/Performance.cpp page/PerformanceMark.cpp -page/PerformanceMeasure.cpp page/PerformanceMonitor.cpp page/PerformanceObserver.cpp page/PerformanceUserTiming.cpp @@ -1708,7 +1705,6 @@ platform/graphics/cocoa/WebProcessGraphicsContextGLCocoa.mm platform/graphics/coreimage/FEComponentTransferCoreImageApplier.mm platform/graphics/coreimage/FilterImageCoreImage.mm platform/graphics/coretext/FontCascadeCoreText.cpp -platform/graphics/coretext/FontCoreText.cpp platform/graphics/coretext/FontCustomPlatformDataCoreText.cpp platform/graphics/coretext/FontPlatformDataCoreText.cpp platform/graphics/cv/GraphicsContextGLCVCocoa.cpp @@ -1760,7 +1756,6 @@ platform/mediastream/mac/AVCaptureDeviceManager.mm platform/mediastream/mac/AVVideoCaptureSource.mm platform/mediastream/mac/CGDisplayStreamCaptureSource.cpp platform/mediastream/mac/CGDisplayStreamScreenCaptureSource.mm -platform/mediastream/mac/CoreAudioCaptureSource.cpp platform/mediastream/mac/CoreAudioSharedUnit.cpp platform/mediastream/mac/MediaStreamTrackAudioSourceProviderCocoa.cpp platform/mediastream/mac/MockAudioSharedUnit.mm diff --git a/Source/WebCore/SmartPointerExpectations/UncountedLocalVarsCheckerExpectations b/Source/WebCore/SmartPointerExpectations/UncountedLocalVarsCheckerExpectations index 664f73df4e02d..5500a3e274b12 100644 --- a/Source/WebCore/SmartPointerExpectations/UncountedLocalVarsCheckerExpectations +++ b/Source/WebCore/SmartPointerExpectations/UncountedLocalVarsCheckerExpectations @@ -294,12 +294,8 @@ JSReadableStreamDefaultController.h JSRemotePlayback.cpp JSRemotePlaybackAvailabilityCallback.cpp JSReportingObserver.cpp -JSSQLResultSet.cpp -JSSQLResultSet.h JSSQLResultSetRowList.cpp JSSQLStatementCallback.cpp -JSSVGGradientElement.cpp -JSSVGGraphicsElement.cpp JSSVGLengthList.cpp JSSVGUseElement.cpp JSScreen.cpp @@ -333,6 +329,7 @@ JSTextTrackCueList.cpp JSTextTrackList.cpp JSTrustedHTML.h JSTrustedScript.h +JSTrustedTypePolicy.cpp JSTrustedTypePolicyFactory.cpp JSTypeConversions.cpp JSTypeConversions.h @@ -346,7 +343,6 @@ JSVideoTrackList.cpp JSViewTransition.cpp JSVisualViewport.cpp JSWakeLock.cpp -JSWakeLock.h JSWakeLockSentinel.cpp JSWaveShaperNode.cpp JSWebAnimation.cpp @@ -383,7 +379,6 @@ JSWebGLQuery.cpp JSWebGLRenderSharedExponent.cpp JSWebGLRenderbuffer.cpp JSWebGLRenderingContext.cpp -JSWebGLRenderingContext.h JSWebGLSampler.cpp JSWebGLShader.cpp JSWebGLStencilTexturing.cpp @@ -1167,14 +1162,11 @@ platform/graphics/filters/FilterImage.cpp platform/graphics/filters/FilterOperations.cpp platform/graphics/filters/software/FEBlendSoftwareApplier.cpp platform/graphics/filters/software/FEComponentTransferSoftwareApplier.cpp -platform/graphics/filters/software/FECompositeSoftwareApplier.cpp platform/graphics/filters/software/FECompositeSoftwareArithmeticApplier.cpp platform/graphics/filters/software/FEConvolveMatrixSoftwareApplier.cpp platform/graphics/filters/software/FEImageSoftwareApplier.cpp -platform/graphics/filters/software/FELightingSoftwareApplier.cpp platform/graphics/filters/software/FEMorphologySoftwareApplier.cpp platform/graphics/filters/software/FETurbulenceSoftwareApplier.cpp -platform/graphics/filters/software/SourceGraphicSoftwareApplier.cpp platform/graphics/mac/ComplexTextControllerCoreText.mm platform/graphics/mac/controls/MeterMac.mm platform/graphics/mac/controls/ProgressBarMac.mm diff --git a/Source/WebCore/Sources.txt b/Source/WebCore/Sources.txt index a7ad5d6ed0cf1..cb9ae7948c9a7 100644 --- a/Source/WebCore/Sources.txt +++ b/Source/WebCore/Sources.txt @@ -729,6 +729,9 @@ bindings/js/JSCSSStyleValueCustom.cpp bindings/js/JSCSSTransformComponentCustom.cpp bindings/js/JSUndoItemCustom.cpp bindings/js/JSWebAnimationCustom.cpp +bindings/js/JSWebCodecsVideoDecoderCustom.cpp +bindings/js/JSWebCodecsAudioEncoderCustom.cpp +bindings/js/JSWebCodecsVideoEncoderCustom.cpp bindings/js/JSWebGL2RenderingContextCustom.cpp bindings/js/JSWebGLRenderingContextCustom.cpp bindings/js/JSWebXRSessionCustom.cpp @@ -2207,6 +2210,7 @@ platform/SleepDisabler.cpp platform/SleepDisablerClient.cpp platform/SystemSoundManager.cpp platform/StaticPasteboard.cpp +platform/StyleAppearance.cpp platform/Theme.cpp platform/ThemeTypes.cpp platform/ThermalMitigationNotifier.cpp @@ -2961,7 +2965,6 @@ style/RuleFeature.cpp style/RuleSet.cpp style/RuleSetBuilder.cpp style/StyleAdjuster.cpp -style/StyleAppearance.cpp style/StyleBuilder.cpp style/StyleBuilderState.cpp style/StyleChange.cpp diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj index f2d01c55986a7..2055196c15f34 100644 --- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj +++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj @@ -6146,6 +6146,7 @@ E59DD4B821098287003C8B47 /* ListButtonArrow.png in Resources */ = {isa = PBXBuildFile; fileRef = E59DD4B721098285003C8B47 /* ListButtonArrow.png */; }; E5BA7D63151437CA00FE1E3F /* LengthFunctions.h in Headers */ = {isa = PBXBuildFile; fileRef = E5BA7D62151437CA00FE1E3F /* LengthFunctions.h */; settings = {ATTRIBUTES = (Private, ); }; }; E5C59A9A24D0E1AA003B86E1 /* DateTimeChooserParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = E5C59A9924D0E1AA003B86E1 /* DateTimeChooserParameters.h */; settings = {ATTRIBUTES = (Private, ); }; }; + E5C5B2092C3795EE00838733 /* PlatformNSAdaptiveImageGlyph.h in Headers */ = {isa = PBXBuildFile; fileRef = E5C5B2082C3795EE00838733 /* PlatformNSAdaptiveImageGlyph.h */; }; E5F06AE724D4847B00BBC4F8 /* DateTimeFieldElement.h in Headers */ = {isa = PBXBuildFile; fileRef = E5F06AE524D4847B00BBC4F8 /* DateTimeFieldElement.h */; }; E5F06AEB24D49BF700BBC4F8 /* DateTimeNumericFieldElement.h in Headers */ = {isa = PBXBuildFile; fileRef = E5F06AE924D49BF700BBC4F8 /* DateTimeNumericFieldElement.h */; }; E5F06AEF24D4A63700BBC4F8 /* DateTimeSymbolicFieldElement.h in Headers */ = {isa = PBXBuildFile; fileRef = E5F06AED24D4A63700BBC4F8 /* DateTimeSymbolicFieldElement.h */; }; @@ -10525,6 +10526,9 @@ 458FE4081589DF0B005609E6 /* RenderSearchField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderSearchField.h; sourceTree = ""; }; 45B5D85E2A968620004C85A0 /* LoaderMalloc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = LoaderMalloc.cpp; sourceTree = ""; }; 45B5D85F2A968620004C85A0 /* LoaderMalloc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LoaderMalloc.h; sourceTree = ""; }; + 45BA2FCA2C3C77870042DA4F /* JSWebCodecsAudioEncoderCustom.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = JSWebCodecsAudioEncoderCustom.cpp; sourceTree = ""; }; + 45BA2FCB2C3C77870042DA4F /* JSWebCodecsVideoEncoderCustom.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = JSWebCodecsVideoEncoderCustom.cpp; sourceTree = ""; }; + 45F4864B2C38CCE9008B9897 /* JSWebCodecsVideoDecoderCustom.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = JSWebCodecsVideoDecoderCustom.cpp; sourceTree = ""; }; 45FEA5CD156DDE8C00654101 /* Decimal.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Decimal.cpp; sourceTree = ""; }; 45FEA5CE156DDE8C00654101 /* Decimal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Decimal.h; sourceTree = ""; }; 46014ACB28333F52004C0B84 /* FrameDestructionObserverInlines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FrameDestructionObserverInlines.h; sourceTree = ""; }; @@ -20025,6 +20029,7 @@ E59DD4B721098285003C8B47 /* ListButtonArrow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ListButtonArrow.png; sourceTree = ""; }; E5BA7D62151437CA00FE1E3F /* LengthFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LengthFunctions.h; sourceTree = ""; }; E5C59A9924D0E1AA003B86E1 /* DateTimeChooserParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateTimeChooserParameters.h; sourceTree = ""; }; + E5C5B2082C3795EE00838733 /* PlatformNSAdaptiveImageGlyph.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlatformNSAdaptiveImageGlyph.h; sourceTree = ""; }; E5F06AE524D4847B00BBC4F8 /* DateTimeFieldElement.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DateTimeFieldElement.h; sourceTree = ""; }; E5F06AE624D4847B00BBC4F8 /* DateTimeFieldElement.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DateTimeFieldElement.cpp; sourceTree = ""; }; E5F06AE924D49BF700BBC4F8 /* DateTimeNumericFieldElement.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DateTimeNumericFieldElement.h; sourceTree = ""; }; @@ -28669,6 +28674,9 @@ A38ADF712B8E386D00579A4F /* JSTrustedTypePolicyFactoryCustom.cpp */, F4E1965A21F2395000285078 /* JSUndoItemCustom.cpp */, 715DA5D3201BB902002EF2B0 /* JSWebAnimationCustom.cpp */, + 45BA2FCA2C3C77870042DA4F /* JSWebCodecsAudioEncoderCustom.cpp */, + 45F4864B2C38CCE9008B9897 /* JSWebCodecsVideoDecoderCustom.cpp */, + 45BA2FCB2C3C77870042DA4F /* JSWebCodecsVideoEncoderCustom.cpp */, D3F3D3591A69A3B00059FC2B /* JSWebGL2RenderingContextCustom.cpp */, 49EED14C1051971A00099FAB /* JSWebGLRenderingContextCustom.cpp */, 11E6CF1B25F140E30013D3D5 /* JSWebXRReferenceSpaceCustom.cpp */, @@ -30712,6 +30720,7 @@ A18890AC1AA13F250026C301 /* ParentalControlsContentFilter.mm */, 9BED2CAF1F7CC06200666018 /* PasteboardCocoa.mm */, F4FB35002350C96200F0094A /* PasteboardCustomDataCocoa.mm */, + E5C5B2082C3795EE00838733 /* PlatformNSAdaptiveImageGlyph.h */, F4628A9E234D3BBF00BC884C /* PlatformPasteboardCocoa.mm */, AAE3755D17429BCC006200C2 /* PlatformSpeechSynthesizerCocoa.mm */, F4E90A3B2B52038E002DA469 /* PlatformTextAlternatives.h */, @@ -34203,6 +34212,8 @@ A3FF9B702921E6DC004F7A00 /* SourcesLibWebRTC.txt */, F433E9041DBBDBC200EF0D14 /* StaticPasteboard.cpp */, F433E9021DBBDBA200EF0D14 /* StaticPasteboard.h */, + 722728122947A4090048DD2E /* StyleAppearance.cpp */, + 722728112947A18A0048DD2E /* StyleAppearance.h */, 93B2D8150F9920D2006AE6B2 /* SuddenTermination.h */, 97627B9714FB5424002CDCA1 /* Supplementable.h */, E3C04131254CA6660021D0E6 /* SystemSoundDelegate.h */, @@ -35636,8 +35647,6 @@ 713922BC2518AB70005DB3C2 /* Styleable.h */, E45BA6B22376227E004DFC07 /* StyleAdjuster.cpp */, E45BA6B52376229F004DFC07 /* StyleAdjuster.h */, - 722728122947A4090048DD2E /* StyleAppearance.cpp */, - 722728112947A18A0048DD2E /* StyleAppearance.h */, E4ABAC07236B018100FA4345 /* StyleBuilder.cpp */, E4ABAC05236B016C00FA4345 /* StyleBuilder.h */, 83B9687919F8AB83004EF7AF /* StyleBuilderConverter.h */, @@ -41469,6 +41478,7 @@ F4CCD0592A993EA600536C6B /* PlatformMediaSession.serialization.in in Headers */, 07F944161864D046005D31CB /* PlatformMediaSessionManager.h in Headers */, 935C476909AC4D4300A6AAB4 /* PlatformMouseEvent.h in Headers */, + E5C5B2092C3795EE00838733 /* PlatformNSAdaptiveImageGlyph.h in Headers */, C598905814E9C29900E8D18B /* PlatformPasteboard.h in Headers */, 7246F0312A15519800FD74F1 /* PlatformPath.h in Headers */, A19759492A4F9DE000FEC5EA /* PlatformPlaybackSessionInterface.h in Headers */, diff --git a/Source/WebCore/accessibility/AXCoreObject.cpp b/Source/WebCore/accessibility/AXCoreObject.cpp index 3a20bc2ee843e..5fa7b6226333f 100644 --- a/Source/WebCore/accessibility/AXCoreObject.cpp +++ b/Source/WebCore/accessibility/AXCoreObject.cpp @@ -28,7 +28,9 @@ #include "config.h" #include "AXCoreObject.h" + #include "LocalFrameView.h" +#include namespace WebCore { diff --git a/Source/WebCore/accessibility/AXLogger.cpp b/Source/WebCore/accessibility/AXLogger.cpp index d9b546f9553c1..9ad1b2b7046c0 100644 --- a/Source/WebCore/accessibility/AXLogger.cpp +++ b/Source/WebCore/accessibility/AXLogger.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include namespace WebCore { @@ -657,14 +658,7 @@ TextStream& operator<<(TextStream& stream, AXObjectCache& axObjectCache) #if ENABLE(AX_THREAD_TEXT_APIS) static void streamTextRuns(TextStream& stream, const AXTextRuns& runs) { - StringBuilder result; - for (size_t i = 0; i < runs.size(); i++) { - result.append(makeString(runs[i].lineIndex, ":|", runs[i].text, "|(len: ", runs[i].text.length(), ")")); - if (i != runs.size() - 1) - result.append(", "); - } - - stream.dumpProperty("textRuns", result); + stream.dumpProperty("textRuns", makeString(interleave(runs, [](auto& builder, auto& run) { builder.append(run.lineIndex, ":|"_s, run.text, "|(len: "_s, run.text.length(), ')'); }, ", "_s))); } #endif // ENABLE(AX_THREAD_TEXT_APIS) diff --git a/Source/WebCore/accessibility/AXObjectCache.cpp b/Source/WebCore/accessibility/AXObjectCache.cpp index 3dfd3f84a49b6..730ebe28c2bf8 100644 --- a/Source/WebCore/accessibility/AXObjectCache.cpp +++ b/Source/WebCore/accessibility/AXObjectCache.cpp @@ -127,7 +127,7 @@ #include #include #include -#include +#include #if COMPILER(MSVC) // See https://msdn.microsoft.com/en-us/library/1wea5zwe.aspx @@ -1103,7 +1103,6 @@ void AXObjectCache::remove(Node& node) { AXTRACE(makeString("AXObjectCache::remove Node& 0x"_s, hex(reinterpret_cast(this)))); - removeNodeForUse(node); remove(m_nodeObjectMapping.take(&node)); remove(node.renderer()); @@ -2799,14 +2798,14 @@ Ref AXObjectCache::protectedDocument() const VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(const TextMarkerData& textMarkerData) { - if (!textMarkerData.node) + RefPtr node = nodeForID(textMarkerData.axObjectID()); + if (!node) return { }; - Ref node = *textMarkerData.node; - if (!isNodeInUse(node) || node->isPseudoElement()) + if (node->isPseudoElement()) return { }; - auto visiblePosition = VisiblePosition({ node.ptr(), textMarkerData.offset, textMarkerData.anchorType }, textMarkerData.affinity); + auto visiblePosition = VisiblePosition({ node.get(), textMarkerData.offset, textMarkerData.anchorType }, textMarkerData.affinity); auto deepPosition = visiblePosition.deepEquivalent(); if (deepPosition.isNull()) return { }; @@ -2816,7 +2815,7 @@ VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(const TextMarker return { }; auto* cache = renderer->document().axObjectCache(); - if (cache && !cache->m_idsInUse.contains(textMarkerData.objectID)) + if (cache && !cache->m_idsInUse.contains(textMarkerData.axObjectID())) return { }; return visiblePosition; @@ -2824,14 +2823,14 @@ VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(const TextMarker CharacterOffset AXObjectCache::characterOffsetForTextMarkerData(const TextMarkerData& textMarkerData) { - if (textMarkerData.ignored || !textMarkerData.node) + if (textMarkerData.ignored) return { }; - RefAllowingPartiallyDestroyed node = *textMarkerData.node; - if (!isNodeInUse(node)) + RefPtr node = nodeForID(textMarkerData.axObjectID()); + if (!node) return { }; - CharacterOffset result(node.ptr(), textMarkerData.characterStart, textMarkerData.characterOffset); + CharacterOffset result(node.get(), textMarkerData.characterStart, textMarkerData.characterOffset); // When we are at a line wrap and the VisiblePosition is upstream, it means the text marker is at the end of the previous line. // We use the previous CharacterOffset so that it will match the Range. if (textMarkerData.affinity == Affinity::Upstream) @@ -3103,7 +3102,6 @@ TextMarkerData AXObjectCache::textMarkerDataForCharacterOffset(const CharacterOf if (RefPtr input = dynamicDowncast(characterOffset.node.get()); input && input->isSecureField()) return { *this, { }, true }; - setNodeInUse(Ref { *characterOffset.node }); return { *this, characterOffset, false }; } @@ -3363,16 +3361,20 @@ CharacterOffset AXObjectCache::characterOffsetFromVisiblePosition(const VisibleP return result; } -AccessibilityObject* AXObjectCache::accessibilityObjectForTextMarkerData(const TextMarkerData& textMarkerData) +AccessibilityObject* AXObjectCache::objectForTextMarkerData(const TextMarkerData& textMarkerData) { if (textMarkerData.ignored) return nullptr; - RefPtr domNode = textMarkerData.node.get(); - if (!domNode || !isNodeInUse(*domNode)) + RefPtr object = m_objects.get(textMarkerData.axObjectID()); + if (!object) return nullptr; - return getOrCreate(*domNode); + Node* node = object->node(); + if (!node) + return nullptr; + ASSERT(object.get() == getOrCreate(*node)); + return getOrCreate(*node); } std::optional AXObjectCache::textMarkerDataForVisiblePosition(const VisiblePosition& visiblePosition) @@ -3398,24 +3400,10 @@ std::optional AXObjectCache::textMarkerDataForVisiblePosition(co CheckedPtr cache = node->document().axObjectCache(); if (!cache) return std::nullopt; - cache->setNodeInUse(*node); - return { { *cache, node.get(), visiblePosition, + return { { *cache, visiblePosition, characterOffset.startIndex, characterOffset.offset, false } }; } -// This function exists as a performance optimization to avoid a synchronous layout. -std::optional AXObjectCache::textMarkerDataForFirstPositionInTextControl(HTMLTextFormControlElement& textControl) -{ - if (RefPtr input = dynamicDowncast(textControl); input && input->isSecureField()) - return std::nullopt; - - CheckedPtr cache = textControl.document().axObjectCache(); - if (!cache) - return std::nullopt; - cache->setNodeInUse(textControl); - return { { *cache, &textControl, { }, false } }; -} - CharacterOffset AXObjectCache::nextCharacterOffset(const CharacterOffset& characterOffset, bool ignoreNextNodeStart) { if (characterOffset.isNull()) @@ -4031,7 +4019,6 @@ static void filterWeakHashMapForRemoval(WeakHashMapType& map, const Document& do void AXObjectCache::prepareForDocumentDestruction(const Document& document) { HashSet> nodesToRemove; - filterListForRemoval(m_textMarkerNodes, document, nodesToRemove); filterWeakListHashSetForRemoval(m_deferredTextChangedList, document, nodesToRemove); filterWeakListHashSetForRemoval(m_deferredNodeAddedOrRemovedList, document, nodesToRemove); filterWeakHashSetForRemoval(m_deferredRecomputeIsIgnoredList, document, nodesToRemove); diff --git a/Source/WebCore/accessibility/AXObjectCache.h b/Source/WebCore/accessibility/AXObjectCache.h index 4a46159dccf56..a0e52a4b16a5d 100644 --- a/Source/WebCore/accessibility/AXObjectCache.h +++ b/Source/WebCore/accessibility/AXObjectCache.h @@ -40,6 +40,7 @@ #include #include #include +#include OBJC_CLASS NSMutableArray; @@ -73,14 +74,14 @@ struct CharacterOffset { int startIndex; int offset; int remainingOffset; - + CharacterOffset(Node* n = nullptr, int startIndex = 0, int offset = 0, int remaining = 0) : node(n) , startIndex(startIndex) , offset(offset) , remainingOffset(remaining) { } - + int remaining() const { return remainingOffset; } bool isNull() const { return !node; } bool isEqual(const CharacterOffset& other) const @@ -433,6 +434,7 @@ class AXObjectCache final : public CanMakeWeakPtr, public CanMake AccessibilityObject* objectForID(const AXID id) const { return m_objects.get(id); } template Vector> objectsForIDs(const U&) const; + Node* nodeForID(const AXID) const; #if ENABLE(ACCESSIBILITY_ISOLATED_TREE) void onPaint(const RenderObject&, IntRect&&) const; @@ -444,7 +446,6 @@ class AXObjectCache final : public CanMakeWeakPtr, public CanMake // Text marker utilities. std::optional textMarkerDataForVisiblePosition(const VisiblePosition&); - std::optional textMarkerDataForFirstPositionInTextControl(HTMLTextFormControlElement&); TextMarkerData textMarkerDataForCharacterOffset(const CharacterOffset&); TextMarkerData textMarkerDataForNextCharacterOffset(const CharacterOffset&); AXTextMarker nextTextMarker(const AXTextMarker&); @@ -457,7 +458,7 @@ class AXObjectCache final : public CanMakeWeakPtr, public CanMake CharacterOffset previousCharacterOffset(const CharacterOffset&, bool ignorePreviousNodeEnd = true); TextMarkerData startOrEndTextMarkerDataForRange(const SimpleRange&, bool); CharacterOffset startOrEndCharacterOffsetForRange(const SimpleRange&, bool, bool enterTextControls = false); - AccessibilityObject* accessibilityObjectForTextMarkerData(const TextMarkerData&); + AccessibilityObject* objectForTextMarkerData(const TextMarkerData&); std::optional rangeForUnorderedCharacterOffsets(const CharacterOffset&, const CharacterOffset&); static SimpleRange rangeForNodeContents(Node&); static unsigned lengthForRange(const SimpleRange&); @@ -573,8 +574,6 @@ class AXObjectCache final : public CanMakeWeakPtr, public CanMake static bool clientIsInTestMode(); #endif - bool isNodeInUse(Node& node) const { return m_textMarkerNodes.contains(node); } - #if ENABLE(ACCESSIBILITY_ISOLATED_TREE) void scheduleObjectRegionsUpdate(bool scheduleImmediately = false) { m_geometryManager->scheduleObjectRegionsUpdate(scheduleImmediately); } void willUpdateObjectRegions() { m_geometryManager->willUpdateObjectRegions(); } @@ -625,10 +624,6 @@ class AXObjectCache final : public CanMakeWeakPtr, public CanMake void frameLoadingEventPlatformNotification(AccessibilityObject*, AXLoadingEvent); void handleLabelChanged(AccessibilityObject*); - // This is a weak reference cache for knowing if Nodes used by TextMarkers are valid. - void setNodeInUse(Node& node) { m_textMarkerNodes.add(node); } - void removeNodeForUse(Node& node) { m_textMarkerNodes.remove(node); } - // CharacterOffset functions. enum TraverseOption { TraverseOptionDefault = 1 << 0, TraverseOptionToNodeEnd = 1 << 1, TraverseOptionIncludeStart = 1 << 2, TraverseOptionValidateOffset = 1 << 3, TraverseOptionDoNotEnterTextControls = 1 << 4 }; Node* nextNode(Node*) const; @@ -761,8 +756,6 @@ class AXObjectCache final : public CanMakeWeakPtr, public CanMake HashMap, AXID> m_renderObjectMapping; HashMap, AXID> m_widgetObjectMapping; HashMap, AXID> m_nodeObjectMapping; - // The pointers in this HashSet should never be dereferenced. - ListHashSet> m_textMarkerNodes; std::unique_ptr m_computedObjectAttributeCache; @@ -861,6 +854,15 @@ inline Vector> AXObjectCache::objectsForIDs(const U& axIDs) }); } +inline Node* AXObjectCache::nodeForID(const AXID axID) const +{ + if (!axID.isValid()) + return nullptr; + + auto* object = m_objects.get(axID); + return object ? object->node() : nullptr; +} + inline bool AXObjectCache::accessibilityEnabled() { return gAccessibilityEnabled; diff --git a/Source/WebCore/accessibility/AXTextMarker.cpp b/Source/WebCore/accessibility/AXTextMarker.cpp index 87784c488b7ec..d842ed92242fe 100644 --- a/Source/WebCore/accessibility/AXTextMarker.cpp +++ b/Source/WebCore/accessibility/AXTextMarker.cpp @@ -32,6 +32,7 @@ #include "HTMLInputElement.h" #include "RenderObject.h" #include "TextIterator.h" +#include namespace WebCore { DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(AXTextMarker); @@ -43,33 +44,37 @@ static AXID nodeID(AXObjectCache& cache, Node* node) return { }; } -TextMarkerData::TextMarkerData(AXObjectCache& cache, Node* node, const VisiblePosition& visiblePosition, int charStart, int charOffset, bool ignoredParam) - : treeID(cache.treeID()) - , objectID(nodeID(cache, node)) - , node(node) - , affinity(visiblePosition.affinity()) - , characterStart(std::max(charStart, 0)) - , characterOffset(std::max(charOffset, 0)) - , ignored(ignoredParam) +TextMarkerData::TextMarkerData(AXObjectCache& cache, const VisiblePosition& visiblePosition, int charStart, int charOffset, bool ignoredParam) { + ASSERT(isMainThread()); + + memset(static_cast(this), 0, sizeof(*this)); + treeID = cache.treeID().toUInt64(); auto position = visiblePosition.deepEquivalent(); + objectID = nodeID(cache, position.anchorNode()).toUInt64(); offset = !visiblePosition.isNull() ? std::max(position.deprecatedEditingOffset(), 0) : 0; anchorType = position.anchorType(); + affinity = visiblePosition.affinity(); + characterStart = std::max(charStart, 0); + characterOffset = std::max(charOffset, 0); + ignored = ignoredParam; } -TextMarkerData::TextMarkerData(AXObjectCache& cache, const CharacterOffset& characterOffset, bool ignoredParam) - : treeID(cache.treeID()) - , objectID(nodeID(cache, characterOffset.node.get())) - , node(characterOffset.node.get()) - , anchorType(Position::PositionIsOffsetInAnchor) - , characterStart(std::max(characterOffset.startIndex, 0)) - , characterOffset(std::max(characterOffset.offset, 0)) - , ignored(ignoredParam) +TextMarkerData::TextMarkerData(AXObjectCache& cache, const CharacterOffset& characterOffsetParam, bool ignoredParam) { - auto visiblePosition = cache.visiblePositionFromCharacterOffset(characterOffset); + ASSERT(isMainThread()); + + memset(static_cast(this), 0, sizeof(*this)); + treeID = cache.treeID().toUInt64(); + objectID = nodeID(cache, characterOffsetParam.node.get()).toUInt64(); + auto visiblePosition = cache.visiblePositionFromCharacterOffset(characterOffsetParam); auto position = visiblePosition.deepEquivalent(); offset = !visiblePosition.isNull() ? std::max(position.deprecatedEditingOffset(), 0) : 0; + anchorType = Position::PositionIsOffsetInAnchor; affinity = visiblePosition.affinity(); + characterStart = std::max(characterOffsetParam.startIndex, 0); + characterOffset = std::max(characterOffsetParam.offset, 0); + ignored = ignoredParam; } AXTextMarker::AXTextMarker(const VisiblePosition& visiblePosition) @@ -103,28 +108,6 @@ AXTextMarker::AXTextMarker(const CharacterOffset& characterOffset) m_data = cache->textMarkerDataForCharacterOffset(characterOffset); } -void AXTextMarker::setNodeIfNeeded() const -{ - ASSERT(isMainThread()); - if (m_data.node) - return; - - WeakPtr cache = std::get>(axTreeForID(treeID())); - if (!cache) - return; - - auto* object = cache->objectForID(objectID()); - if (!object) - return; - - RefPtr node = object->node(); - if (!node) - return; - - const_cast(this)->m_data.node = *node; - cache->setNodeInUse(*node); -} - AXTextMarker::operator VisiblePosition() const { ASSERT(isMainThread()); @@ -133,7 +116,6 @@ AXTextMarker::operator VisiblePosition() const if (!cache) return { }; - setNodeIfNeeded(); return cache->visiblePositionForTextMarkerData(m_data); } @@ -144,18 +126,15 @@ AXTextMarker::operator CharacterOffset() const if (isIgnored() || isNull()) return { }; - WeakPtr cache = AXTreeStore::axObjectCacheForID(m_data.treeID); + WeakPtr cache = AXTreeStore::axObjectCacheForID(m_data.axTreeID()); if (!cache) return { }; - if (RefPtr node = m_data.node.get()) { - // Make sure that this node is still in cache->m_textMarkerNodes. Since this method can be called as a result of a dispatch from the AX thread, the Node may have gone away in a previous main loop cycle. - if (!cache->isNodeInUse(*node)) - return { }; - } else - setNodeIfNeeded(); + RefPtr object = cache->objectForID(m_data.axObjectID()); + if (!object) + return { }; - CharacterOffset result((RefPtr { m_data.node.get() }).get(), m_data.characterStart, m_data.characterOffset); + CharacterOffset result(object->node(), m_data.characterStart, m_data.characterOffset); // When we are at a line wrap and the VisiblePosition is upstream, it means the text marker is at the end of the previous line. // We use the previous CharacterOffset so that it will match the Range. if (m_data.affinity == Affinity::Upstream) @@ -226,7 +205,6 @@ String AXTextMarker::debugDescription() const , separator, "objectID "_s, objectID().loggingString() , separator, "role "_s, object ? accessibilityRoleToString(object->roleValue()) : "no object"_str , isIgnored() ? makeString(separator, "ignored"_s) : ""_s - , isMainThread() && node() ? makeString(separator, node()->debugDescription()) : ""_s , separator, "anchor "_s, m_data.anchorType , separator, "affinity "_s, m_data.affinity , separator, "offset "_s, m_data.offset @@ -275,8 +253,8 @@ AXTextMarkerRange::AXTextMarkerRange(AXID treeID, AXID objectID, unsigned start, { if (start > end) std::swap(start, end); - m_start = AXTextMarker({ treeID, objectID, nullptr, start, Position::PositionIsOffsetInAnchor, Affinity::Downstream, 0, start }); - m_end = AXTextMarker({ treeID, objectID, nullptr, end, Position::PositionIsOffsetInAnchor, Affinity::Downstream, 0, end }); + m_start = AXTextMarker({ treeID, objectID, start, Position::PositionIsOffsetInAnchor, Affinity::Downstream, 0, start }); + m_end = AXTextMarker({ treeID, objectID, end, Position::PositionIsOffsetInAnchor, Affinity::Downstream, 0, end }); } AXTextMarkerRange::operator VisiblePositionRange() const @@ -317,9 +295,8 @@ std::optional AXTextMarkerRange::intersectionWith(const AXTex { if (UNLIKELY(m_start.m_data.treeID != m_end.m_data.treeID || other.m_start.m_data.treeID != other.m_end.m_data.treeID - || m_start.m_data.treeID != other.m_start.m_data.treeID)) { + || m_start.m_data.treeID != other.m_start.m_data.treeID)) return std::nullopt; - } // Fast path: both ranges span one object if (m_start.m_data.objectID == m_end.m_data.objectID @@ -334,8 +311,8 @@ std::optional AXTextMarkerRange::intersectionWith(const AXTex return std::nullopt; return { { - AXTextMarker({ m_start.treeID(), m_start.objectID(), nullptr, startOffset, Position::PositionIsOffsetInAnchor, Affinity::Downstream, 0, startOffset }), - AXTextMarker({ m_start.treeID(), m_start.objectID(), nullptr, endOffset, Position::PositionIsOffsetInAnchor, Affinity::Downstream, 0, endOffset }) + AXTextMarker({ m_start.treeID(), m_start.objectID(), startOffset, Position::PositionIsOffsetInAnchor, Affinity::Downstream, 0, startOffset }), + AXTextMarker({ m_start.treeID(), m_start.objectID(), endOffset, Position::PositionIsOffsetInAnchor, Affinity::Downstream, 0, endOffset }) } }; } @@ -911,15 +888,4 @@ std::partial_ordering AXTextMarker::partialOrderByTraversal(const AXTextMarker& } #endif // ENABLE(AX_THREAD_TEXT_APIS) -TextMarkerData RawTextMarkerData::toTextMarkerData() const -{ - ObjectIdentifier axTreeID(treeID); - WeakPtr cache = AXTreeStore::axObjectCacheForID(axTreeID); - // Make sure the node is not stale before storing it in a WeakPtr. - Node* validNode = nullptr; - if (node && cache && cache->isNodeInUse(*node)) - validNode = node; - return { axTreeID, ObjectIdentifier(objectID), validNode, offset, anchorType, affinity, characterStart, characterOffset, ignored }; -} - } // namespace WebCore diff --git a/Source/WebCore/accessibility/AXTextMarker.h b/Source/WebCore/accessibility/AXTextMarker.h index f29a1c4fcb615..9e9d332745c79 100644 --- a/Source/WebCore/accessibility/AXTextMarker.h +++ b/Source/WebCore/accessibility/AXTextMarker.h @@ -48,13 +48,10 @@ enum class LineRangeType : uint8_t { Right, }; -struct TextMarkerData; - -struct RawTextMarkerData { +struct TextMarkerData { unsigned treeID; unsigned objectID; - Node* node; unsigned offset; Position::AnchorType anchorType; Affinity affinity; @@ -63,16 +60,16 @@ struct RawTextMarkerData { unsigned characterOffset; bool ignored; - // Constructors of RawTextMarkerData must zero the struct's block of memory because platform client code may rely on a byte-comparison to determine instances equality. + // Constructors of TextMarkerData must zero the struct's block of memory because platform client code may rely on a byte-comparison to determine instances equality. // Members initialization alone is not enough to guaranty that all bytes in the struct memeory are initialized, and may cause random inequalities when doing byte-comparisons. // For an example of such byte-comparison, see the TestRunner WTR::AccessibilityTextMarker::isEqual. - RawTextMarkerData() + TextMarkerData() { memset(static_cast(this), 0, sizeof(*this)); } - RawTextMarkerData(AXID axTreeID, AXID axObjectID, - Node* nodeParam = nullptr, unsigned offsetParam = 0, + TextMarkerData(AXID axTreeID, AXID axObjectID, + unsigned offsetParam = 0, Position::AnchorType anchorTypeParam = Position::PositionIsOffsetInAnchor, Affinity affinityParam = Affinity::Downstream, unsigned charStart = 0, unsigned charOffset = 0, bool ignoredParam = false) @@ -80,7 +77,6 @@ struct RawTextMarkerData { memset(static_cast(this), 0, sizeof(*this)); treeID = axTreeID.toUInt64(); objectID = axObjectID.toUInt64(); - node = nodeParam; offset = offsetParam; anchorType = anchorTypeParam; affinity = affinityParam; @@ -89,52 +85,24 @@ struct RawTextMarkerData { ignored = ignoredParam; } - TextMarkerData toTextMarkerData() const; -}; - -// Safer version of RawTextMarkerData with a WeakPtr for Node. -// RawTextMarkerData uses a raw pointer for Node because it is -// used with memset / memcmp. -struct TextMarkerData { - AXID treeID; - AXID objectID; - - WeakPtr node; - unsigned offset { 0 }; - Position::AnchorType anchorType { Position::AnchorType::PositionIsOffsetInAnchor }; - Affinity affinity { Affinity::Upstream }; - - unsigned characterStart { 0 }; - unsigned characterOffset { 0 }; - bool ignored { false }; - - TextMarkerData() = default; - TextMarkerData(AXObjectCache&, Node*, const VisiblePosition&, int charStart = 0, int charOffset = 0, bool ignoredParam = false); - TextMarkerData(AXID treeID, AXID objectID, Node* node, unsigned offset, Position::AnchorType anchorType = Position::AnchorType::PositionIsOffsetInAnchor, Affinity affinity = Affinity::Upstream, unsigned characterStart = 0, unsigned characterOffset = 0, bool ignored = false) - : treeID(treeID) - , objectID(objectID) - , node(node) - , offset(offset) - , anchorType(anchorType) - , affinity(affinity) - , characterStart(characterStart) - , characterOffset(characterOffset) - , ignored(ignored) - { } + TextMarkerData(AXObjectCache&, const VisiblePosition&, int charStart = 0, int charOffset = 0, bool ignoredParam = false); TextMarkerData(AXObjectCache&, const CharacterOffset&, bool ignoredParam = false); - RawTextMarkerData toRawTextMarkerData() const; -}; + AXID axTreeID() const + { + return ObjectIdentifier(treeID); + } -inline RawTextMarkerData TextMarkerData::toRawTextMarkerData() const -{ - return { treeID, objectID, node.get(), offset, anchorType, affinity, characterStart, characterOffset, ignored }; -} + AXID axObjectID() const + { + return ObjectIdentifier(objectID); + } +}; #if PLATFORM(MAC) using PlatformTextMarkerData = AXTextMarkerRef; #elif PLATFORM(IOS_FAMILY) -using PlatformTextMarkerData = NSData *;; +using PlatformTextMarkerData = NSData *; #endif DECLARE_ALLOCATOR_WITH_HEAP_IDENTIFIER(AXTextMarker); @@ -156,7 +124,7 @@ class AXTextMarker { AXTextMarker(PlatformTextMarkerData); #endif AXTextMarker(AXID treeID, AXID objectID, unsigned offset) - : m_data({ treeID, objectID, nullptr, offset, Position::PositionIsOffsetInAnchor, Affinity::Downstream, 0, offset }) + : m_data({ treeID, objectID, offset, Position::PositionIsOffsetInAnchor, Affinity::Downstream, 0, offset }) { } AXTextMarker() = default; @@ -171,8 +139,8 @@ class AXTextMarker { operator PlatformTextMarkerData() const { return platformData().autorelease(); } #endif - AXID treeID() const { return m_data.treeID; } - AXID objectID() const { return m_data.objectID; } + AXID treeID() const { return AXID { m_data.treeID }; } + AXID objectID() const { return AXID { m_data.objectID }; } unsigned offset() const { return m_data.offset; } bool isNull() const { return !treeID().isValid() || !objectID().isValid(); } #if ENABLE(ACCESSIBILITY_ISOLATED_TREE) @@ -181,15 +149,10 @@ class AXTextMarker { #endif RefPtr object() const; bool isValid() const { return object(); } - - Node* node() const; bool isIgnored() const { return m_data.ignored; } String debugDescription() const; - // Sets m_data.node when the marker was created with a PlatformTextMarkerData that lacks the node pointer because it was created off the main thread. - void setNodeIfNeeded() const; - #if ENABLE(AX_THREAD_TEXT_APIS) AXTextMarker toTextRunMarker(std::optional stopAtID = std::nullopt) const; // True if this marker points to an object with non-empty text runs. @@ -286,10 +249,4 @@ class AXTextMarkerRange { AXTextMarker m_end; }; -inline Node* AXTextMarker::node() const -{ - ASSERT(isMainThread()); - return m_data.node.get(); -} - } // namespace WebCore diff --git a/Source/WebCore/accessibility/AXTextRun.cpp b/Source/WebCore/accessibility/AXTextRun.cpp index 6c65a1b384c03..0849e7cc34828 100644 --- a/Source/WebCore/accessibility/AXTextRun.cpp +++ b/Source/WebCore/accessibility/AXTextRun.cpp @@ -27,19 +27,14 @@ #include "AXTextRun.h" #if ENABLE(AX_THREAD_TEXT_APIS) + +#include + namespace WebCore { String AXTextRuns::debugDescription() const { - StringBuilder result; - result.append('['); - for (unsigned i = 0; i < runs.size(); i++) { - result.append(runs[i].debugDescription(containingBlock)); - if (i != runs.size() - 1) - result.append(", "); - } - result.append(']'); - return result.toString(); + return makeString('[', interleave(runs, [&](auto& run) { return run.debugDescription(containingBlock); }, ", "_s), ']'); } size_t AXTextRuns::indexForOffset(unsigned textOffset) const diff --git a/Source/WebCore/accessibility/AXTextRun.h b/Source/WebCore/accessibility/AXTextRun.h index 668d9f4811856..92b686acbc312 100644 --- a/Source/WebCore/accessibility/AXTextRun.h +++ b/Source/WebCore/accessibility/AXTextRun.h @@ -26,6 +26,9 @@ #pragma once #if ENABLE(AX_THREAD_TEXT_APIS) + +#include + namespace WebCore { class RenderBlock; diff --git a/Source/WebCore/accessibility/AccessibilityObject.cpp b/Source/WebCore/accessibility/AccessibilityObject.cpp index d0b65c6ea5140..8791618023b7e 100644 --- a/Source/WebCore/accessibility/AccessibilityObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityObject.cpp @@ -95,6 +95,7 @@ #include "VisibleUnits.h" #include #include +#include #include #include #include diff --git a/Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp b/Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp index 814411a596b86..94580e9960e67 100644 --- a/Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp +++ b/Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp @@ -72,7 +72,7 @@ String AccessibilityProgressIndicator::valueDescription() const String gaugeRegionValue = gaugeRegionValueDescription(); if (!gaugeRegionValue.isEmpty()) - description = description.isEmpty() ? gaugeRegionValue : description + ", "_s + gaugeRegionValue; + description = description.isEmpty() ? gaugeRegionValue : makeString(description, ", "_s, gaugeRegionValue); return description; } diff --git a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp b/Source/WebCore/accessibility/AccessibilityRenderObject.cpp index 9865897e4b0ee..375e7930e8c8d 100644 --- a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityRenderObject.cpp @@ -116,6 +116,7 @@ #include #include #include +#include #include #if ENABLE(APPLE_PAY) @@ -813,7 +814,7 @@ String AccessibilityRenderObject::stringValue() const String value; Accessibility::enumerateDescendants(*const_cast(this), false, [&value] (const auto& object) { if (object.isStaticText()) - value = value + object.stringValue(); + value = makeString(value, object.stringValue()); }); return value; } diff --git a/Source/WebCore/accessibility/cocoa/AXTextMarkerCocoa.mm b/Source/WebCore/accessibility/cocoa/AXTextMarkerCocoa.mm index 7b143ceb5ab4a..140846e8542af 100644 --- a/Source/WebCore/accessibility/cocoa/AXTextMarkerCocoa.mm +++ b/Source/WebCore/accessibility/cocoa/AXTextMarkerCocoa.mm @@ -46,31 +46,23 @@ return; } - RawTextMarkerData rawTextMarkerData; - if (AXTextMarkerGetLength(platformData) != sizeof(rawTextMarkerData)) { + if (AXTextMarkerGetLength(platformData) != sizeof(m_data)) { ASSERT_NOT_REACHED(); return; } - memcpy(&rawTextMarkerData, AXTextMarkerGetBytePtr(platformData), sizeof(rawTextMarkerData)); - m_data = rawTextMarkerData.toTextMarkerData(); + memcpy(&m_data, AXTextMarkerGetBytePtr(platformData), sizeof(m_data)); #else // PLATFORM(IOS_FAMILY) - RawTextMarkerData rawTextMarkerData; - [platformData getBytes:&rawTextMarkerData length:sizeof(rawTextMarkerData)]; - m_data = rawTextMarkerData.toTextMarkerData(); + [platformData getBytes:&m_data length:sizeof(m_data)]; #endif - - if (isMainThread()) - setNodeIfNeeded(); } RetainPtr AXTextMarker::platformData() const { - auto rawTextMarkerData = m_data.toRawTextMarkerData(); #if PLATFORM(MAC) - return adoptCF(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&rawTextMarkerData, sizeof(rawTextMarkerData))); + return adoptCF(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&m_data, sizeof(m_data))); #else // PLATFORM(IOS_FAMILY) - return [NSData dataWithBytes:&rawTextMarkerData length:sizeof(rawTextMarkerData)]; + return [NSData dataWithBytes:&m_data length:sizeof(m_data)]; #endif } diff --git a/Source/WebCore/accessibility/ios/AccessibilityObjectIOS.mm b/Source/WebCore/accessibility/ios/AccessibilityObjectIOS.mm index e61be39bc5802..1620178837ff9 100644 --- a/Source/WebCore/accessibility/ios/AccessibilityObjectIOS.mm +++ b/Source/WebCore/accessibility/ios/AccessibilityObjectIOS.mm @@ -38,6 +38,7 @@ #import "WebAccessibilityObjectWrapperIOS.h" #import #import +#import SOFT_LINK_CONSTANT(AXRuntime, UIAccessibilityTokenBlockquoteLevel, NSString *); #define AccessibilityTokenBlockquoteLevel getUIAccessibilityTokenBlockquoteLevel() @@ -155,7 +156,7 @@ if (wordStart) previousCompositionNodeText = previousCompositionNodeText.substring(wordStart); - m_lastPresentedTextPredictionComplete = { previousCompositionNodeText + m_lastPresentedTextPrediction.text, wordStart }; + m_lastPresentedTextPredictionComplete = { makeString(previousCompositionNodeText, m_lastPresentedTextPrediction.text), wordStart }; // Reset last presented prediction since a candidate was accepted. m_lastPresentedTextPrediction.reset(); diff --git a/Source/WebCore/accessibility/ios/WebAccessibilityObjectWrapperIOS.mm b/Source/WebCore/accessibility/ios/WebAccessibilityObjectWrapperIOS.mm index 9f6e55737769a..fa1f0e86bc3a9 100644 --- a/Source/WebCore/accessibility/ios/WebAccessibilityObjectWrapperIOS.mm +++ b/Source/WebCore/accessibility/ios/WebAccessibilityObjectWrapperIOS.mm @@ -152,12 +152,10 @@ - (id)initWithData:(NSData *)data cache:(AXObjectCache*)cache { if (!(self = [super init])) return nil; - + _cache = cache; - RawTextMarkerData rawTextMarkerData; - [data getBytes:&rawTextMarkerData length:sizeof(rawTextMarkerData)]; - _textMarkerData = rawTextMarkerData.toTextMarkerData(); - + [data getBytes:&_textMarkerData length:sizeof(_textMarkerData)]; + return self; } @@ -203,8 +201,7 @@ + (WebAccessibilityTextMarker *)startOrEndTextMarkerForRange:(const std::optiona - (NSData *)dataRepresentation { - auto rawTextMarkerData = _textMarkerData.toRawTextMarkerData(); - return [NSData dataWithBytes:&rawTextMarkerData length:sizeof(rawTextMarkerData)]; + return [NSData dataWithBytes:&_textMarkerData length:sizeof(_textMarkerData)]; } - (VisiblePosition)visiblePosition @@ -224,12 +221,12 @@ - (BOOL)isIgnored - (AccessibilityObject*)accessibilityObject { - return _cache->accessibilityObjectForTextMarkerData(_textMarkerData); + return _cache->objectForTextMarkerData(_textMarkerData); } - (NSString *)description { - return [NSString stringWithFormat:@"[AXTextMarker %p] = node: %p offset: %d", self, _textMarkerData.node.get(), _textMarkerData.offset]; + return [NSString stringWithFormat:@"[AXTextMarker %p] = objectID: %d offset: %d", self, _textMarkerData.objectID, _textMarkerData.offset]; } - (TextMarkerData)textMarkerData diff --git a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp index 098b1c10c252a..06d67f4c1add2 100644 --- a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp +++ b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp @@ -36,6 +36,7 @@ #include "DateComponents.h" #include "HTMLNames.h" #include "RenderObject.h" +#include #if PLATFORM(MAC) #import diff --git a/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp b/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp index 6d226ceb3dd23..1702220b88a1a 100644 --- a/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp +++ b/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp @@ -38,6 +38,7 @@ #include #include #include +#include namespace WebCore { DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(AXIsolatedTree); diff --git a/Source/WebCore/accessibility/isolatedtree/mac/AXIsolatedObjectMac.mm b/Source/WebCore/accessibility/isolatedtree/mac/AXIsolatedObjectMac.mm index 41b678699b04c..6e42d06f4f0c1 100644 --- a/Source/WebCore/accessibility/isolatedtree/mac/AXIsolatedObjectMac.mm +++ b/Source/WebCore/accessibility/isolatedtree/mac/AXIsolatedObjectMac.mm @@ -230,12 +230,8 @@ auto attributedText = propertyValue>(AXPropertyName::AttributedText); if (!isConfined || !attributedText) { return Accessibility::retrieveValueFromMainThread>([markerRange = WTFMove(markerRange), &spellCheck, this] () mutable -> RetainPtr { - if (RefPtr axObject = associatedAXObject()) { - // Ensure that the TextMarkers have valid Node references, in case the range was created on the AX thread. - markerRange.start().setNodeIfNeeded(); - markerRange.end().setNodeIfNeeded(); + if (RefPtr axObject = associatedAXObject()) return axObject->attributedStringForTextMarkerRange(WTFMove(markerRange), spellCheck); - } return { }; }); } diff --git a/Source/WebCore/accessibility/mac/AXObjectCacheMac.mm b/Source/WebCore/accessibility/mac/AXObjectCacheMac.mm index ba6e956aa0242..94857ca88bdc8 100644 --- a/Source/WebCore/accessibility/mac/AXObjectCacheMac.mm +++ b/Source/WebCore/accessibility/mac/AXObjectCacheMac.mm @@ -605,9 +605,8 @@ static void addTextMarkerForVisiblePosition(NSMutableDictionary *change, AXObjec static void addFirstTextMarker(NSMutableDictionary *change, AXObjectCache& cache, AccessibilityObject& object) { - TextMarkerData textMarkerData { cache.treeID(), object.objectID(), nullptr, 0 }; - auto rawTextMarkerData = textMarkerData.toRawTextMarkerData(); - auto textMarkerDataRef = adoptCF(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&rawTextMarkerData, sizeof(rawTextMarkerData))); + TextMarkerData textMarkerData { cache.treeID(), object.objectID(), 0 }; + auto textMarkerDataRef = adoptCF(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&textMarkerData, sizeof(textMarkerData))); [change setObject:bridge_id_cast(textMarkerDataRef.get()) forKey:NSAccessibilityTextChangeValueStartMarker]; } @@ -859,13 +858,13 @@ static TextMarkerData getBytesFromAXTextMarker(AXTextMarkerRef textMarker) if (CFGetTypeID(textMarker) != AXTextMarkerGetTypeID()) return { }; - RawTextMarkerData rawTextMarkerData; - ASSERT(AXTextMarkerGetLength(textMarker) == sizeof(rawTextMarkerData)); - if (AXTextMarkerGetLength(textMarker) != sizeof(rawTextMarkerData)) + TextMarkerData textMarkerData; + ASSERT(AXTextMarkerGetLength(textMarker) == sizeof(textMarkerData)); + if (AXTextMarkerGetLength(textMarker) != sizeof(textMarkerData)) return { }; - memcpy(&rawTextMarkerData, AXTextMarkerGetBytePtr(textMarker), sizeof(rawTextMarkerData)); - return rawTextMarkerData.toTextMarkerData(); + memcpy(&textMarkerData, AXTextMarkerGetBytePtr(textMarker), sizeof(textMarkerData)); + return textMarkerData; } AccessibilityObject* accessibilityObjectForTextMarker(AXObjectCache* cache, AXTextMarkerRef textMarker) @@ -875,7 +874,7 @@ static TextMarkerData getBytesFromAXTextMarker(AXTextMarkerRef textMarker) return nullptr; auto textMarkerData = getBytesFromAXTextMarker(textMarker); - return cache->accessibilityObjectForTextMarkerData(textMarkerData); + return cache->objectForTextMarkerData(textMarkerData); } // TextMarker <-> VisiblePosition conversion. @@ -889,9 +888,7 @@ AXTextMarkerRef textMarkerForVisiblePosition(AXObjectCache* cache, const Visible auto textMarkerData = cache->textMarkerDataForVisiblePosition(visiblePos); if (!textMarkerData) return nil; - - auto rawTextMarkerData = textMarkerData->toRawTextMarkerData(); - return adoptCF(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&rawTextMarkerData, sizeof(rawTextMarkerData))).autorelease(); + return adoptCF(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&(*textMarkerData), sizeof(*textMarkerData))).autorelease(); } VisiblePosition visiblePositionForTextMarker(AXObjectCache* cache, AXTextMarkerRef textMarker) @@ -939,8 +936,7 @@ AXTextMarkerRef textMarkerForCharacterOffset(AXObjectCache* cache, const Charact auto textMarkerData = cache->textMarkerDataForCharacterOffset(characterOffset); if (!textMarkerData.objectID || textMarkerData.ignored) return nil; - auto rawTextMarkerData = textMarkerData.toRawTextMarkerData(); - return adoptCF(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&rawTextMarkerData, sizeof(rawTextMarkerData))).autorelease(); + return adoptCF(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&textMarkerData, sizeof(textMarkerData))).autorelease(); } CharacterOffset characterOffsetForTextMarker(AXObjectCache* cache, AXTextMarkerRef textMarker) @@ -964,8 +960,7 @@ AXTextMarkerRef startOrEndTextMarkerForRange(AXObjectCache* cache, const std::op auto textMarkerData = cache->startOrEndTextMarkerDataForRange(*range, isStart); if (!textMarkerData.objectID) return nil; - auto rawTextMarkerData = textMarkerData.toRawTextMarkerData(); - return adoptCF(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&rawTextMarkerData, sizeof(rawTextMarkerData))).autorelease(); + return adoptCF(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&textMarkerData, sizeof(textMarkerData))).autorelease(); } AXTextMarkerRangeRef textMarkerRangeFromRange(AXObjectCache* cache, const std::optional& range) diff --git a/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm b/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm index 5c397cc8e45f0..15d0b7b9ada76 100644 --- a/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm +++ b/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm @@ -82,6 +82,7 @@ #import #import #import +#import using namespace WebCore; diff --git a/Source/WebCore/accessibility/win/AccessibilityObjectWrapperWin.cpp b/Source/WebCore/accessibility/win/AccessibilityObjectWrapperWin.cpp index 379bc4dc5c459..e841166fb11e8 100644 --- a/Source/WebCore/accessibility/win/AccessibilityObjectWrapperWin.cpp +++ b/Source/WebCore/accessibility/win/AccessibilityObjectWrapperWin.cpp @@ -31,7 +31,7 @@ #include "BString.h" #include "HTMLNames.h" #include "QualifiedName.h" -#include +#include namespace WebCore { diff --git a/Source/WebCore/animation/CustomEffectCallback.idl b/Source/WebCore/animation/CustomEffectCallback.idl index 098a11526f1df..ebf66aee36792 100644 --- a/Source/WebCore/animation/CustomEffectCallback.idl +++ b/Source/WebCore/animation/CustomEffectCallback.idl @@ -23,4 +23,4 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -callback CustomEffectCallback = undefined (double progress); +[ IsStrongCallback ] callback CustomEffectCallback = undefined (double progress); diff --git a/Source/WebCore/animation/WebAnimationUtilities.cpp b/Source/WebCore/animation/WebAnimationUtilities.cpp index 981d163812219..8762e177981d0 100644 --- a/Source/WebCore/animation/WebAnimationUtilities.cpp +++ b/Source/WebCore/animation/WebAnimationUtilities.cpp @@ -43,6 +43,7 @@ #include "StyleOriginatedAnimation.h" #include "ViewTransition.h" #include "WebAnimation.h" +#include namespace WebCore { diff --git a/Source/WebCore/bindings/js/JSCallbackData.cpp b/Source/WebCore/bindings/js/JSCallbackData.cpp index 75fc96b26225b..698bf5382de5a 100644 --- a/Source/WebCore/bindings/js/JSCallbackData.cpp +++ b/Source/WebCore/bindings/js/JSCallbackData.cpp @@ -34,6 +34,7 @@ #include "JSExecState.h" #include "JSExecStateInstrumentation.h" #include +#include namespace WebCore { using namespace JSC; diff --git a/Source/WebCore/bindings/js/JSDOMBindingSecurity.cpp b/Source/WebCore/bindings/js/JSDOMBindingSecurity.cpp index 91c9f2b1345d6..f49badfc18285 100644 --- a/Source/WebCore/bindings/js/JSDOMBindingSecurity.cpp +++ b/Source/WebCore/bindings/js/JSDOMBindingSecurity.cpp @@ -30,9 +30,9 @@ #include "LocalDOMWindow.h" #include "LocalFrame.h" #include "SecurityOrigin.h" +#include #include - namespace WebCore { using namespace JSC; diff --git a/Source/WebCore/bindings/js/JSDOMConvertNumbers.cpp b/Source/WebCore/bindings/js/JSDOMConvertNumbers.cpp index 81e2847072000..8d8bb58d9ef93 100644 --- a/Source/WebCore/bindings/js/JSDOMConvertNumbers.cpp +++ b/Source/WebCore/bindings/js/JSDOMConvertNumbers.cpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include namespace WebCore { diff --git a/Source/WebCore/bindings/js/JSDOMExceptionHandling.cpp b/Source/WebCore/bindings/js/JSDOMExceptionHandling.cpp index 98a63d9d97f81..06abf198aa081 100644 --- a/Source/WebCore/bindings/js/JSDOMExceptionHandling.cpp +++ b/Source/WebCore/bindings/js/JSDOMExceptionHandling.cpp @@ -34,6 +34,7 @@ #include #include #include +#include namespace WebCore { using namespace JSC; diff --git a/Source/WebCore/bindings/js/JSDOMGlobalObject.cpp b/Source/WebCore/bindings/js/JSDOMGlobalObject.cpp index 2f9595f35e335..c8ff1e3eaf68c 100644 --- a/Source/WebCore/bindings/js/JSDOMGlobalObject.cpp +++ b/Source/WebCore/bindings/js/JSDOMGlobalObject.cpp @@ -79,7 +79,7 @@ #include #include #include -#include +#include #if ENABLE(REMOTE_INSPECTOR) #include diff --git a/Source/WebCore/bindings/js/JSDOMMapLike.cpp b/Source/WebCore/bindings/js/JSDOMMapLike.cpp index e4ba08c4fb3e6..0533f217e2156 100644 --- a/Source/WebCore/bindings/js/JSDOMMapLike.cpp +++ b/Source/WebCore/bindings/js/JSDOMMapLike.cpp @@ -29,7 +29,6 @@ #include "WebCoreJSBuiltinInternals.h" #include "WebCoreJSClientData.h" #include -#include #include #include diff --git a/Source/WebCore/bindings/js/JSLazyEventListener.cpp b/Source/WebCore/bindings/js/JSLazyEventListener.cpp index fa3974d6af304..4a029e5bcd7ce 100644 --- a/Source/WebCore/bindings/js/JSLazyEventListener.cpp +++ b/Source/WebCore/bindings/js/JSLazyEventListener.cpp @@ -39,6 +39,7 @@ #include #include #include +#include namespace WebCore { using namespace JSC; diff --git a/Source/WebCore/bindings/js/JSWebCodecsAudioEncoderCustom.cpp b/Source/WebCore/bindings/js/JSWebCodecsAudioEncoderCustom.cpp new file mode 100644 index 0000000000000..4940ad32c5c5e --- /dev/null +++ b/Source/WebCore/bindings/js/JSWebCodecsAudioEncoderCustom.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 Apple, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +#include "config.h" + +#if ENABLE(WEB_CODECS) +#include "JSWebCodecsAudioEncoder.h" + +#include "WebCodecsEncodedAudioChunkOutputCallback.h" +#include "WebCodecsErrorCallback.h" + +#include + +namespace WebCore { + +template +void JSWebCodecsAudioEncoder::visitAdditionalChildren(Visitor& visitor) +{ + wrapped().outputCallbackConcurrently().visitJSFunction(visitor); + wrapped().errorCallbackConcurrently().visitJSFunction(visitor); +} + +DEFINE_VISIT_ADDITIONAL_CHILDREN(JSWebCodecsAudioEncoder); + +} + +#endif diff --git a/Source/WebCore/bindings/js/JSWebCodecsVideoDecoderCustom.cpp b/Source/WebCore/bindings/js/JSWebCodecsVideoDecoderCustom.cpp new file mode 100644 index 0000000000000..3147fcedc7168 --- /dev/null +++ b/Source/WebCore/bindings/js/JSWebCodecsVideoDecoderCustom.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 Apple, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +#include "config.h" + +#if ENABLE(WEB_CODECS) +#include "JSWebCodecsVideoDecoder.h" + +#include "WebCodecsErrorCallback.h" +#include "WebCodecsVideoFrameOutputCallback.h" + +#include + +namespace WebCore { + +template +void JSWebCodecsVideoDecoder::visitAdditionalChildren(Visitor& visitor) +{ + wrapped().outputCallbackConcurrently().visitJSFunction(visitor); + wrapped().errorCallbackConcurrently().visitJSFunction(visitor); +} + +DEFINE_VISIT_ADDITIONAL_CHILDREN(JSWebCodecsVideoDecoder); + +} + +#endif diff --git a/Source/WebCore/bindings/js/JSWebCodecsVideoEncoderCustom.cpp b/Source/WebCore/bindings/js/JSWebCodecsVideoEncoderCustom.cpp new file mode 100644 index 0000000000000..807aa76f385e5 --- /dev/null +++ b/Source/WebCore/bindings/js/JSWebCodecsVideoEncoderCustom.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 Apple, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +#include "config.h" + +#if ENABLE(WEB_CODECS) +#include "JSWebCodecsVideoEncoder.h" + +#include "WebCodecsEncodedVideoChunkOutputCallback.h" +#include "WebCodecsErrorCallback.h" + +#include + +namespace WebCore { + +template +void JSWebCodecsVideoEncoder::visitAdditionalChildren(Visitor& visitor) +{ + wrapped().outputCallbackConcurrently().visitJSFunction(visitor); + wrapped().errorCallbackConcurrently().visitJSFunction(visitor); +} + +DEFINE_VISIT_ADDITIONAL_CHILDREN(JSWebCodecsVideoEncoder); + +} + +#endif diff --git a/Source/WebCore/bindings/js/JSWindowProxy.cpp b/Source/WebCore/bindings/js/JSWindowProxy.cpp index d449ea61d8d61..3ab856d1d1536 100644 --- a/Source/WebCore/bindings/js/JSWindowProxy.cpp +++ b/Source/WebCore/bindings/js/JSWindowProxy.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #if PLATFORM(COCOA) #include diff --git a/Source/WebCore/bindings/js/ScriptController.cpp b/Source/WebCore/bindings/js/ScriptController.cpp index 584d05f90e0d9..b360d5415293a 100644 --- a/Source/WebCore/bindings/js/ScriptController.cpp +++ b/Source/WebCore/bindings/js/ScriptController.cpp @@ -78,6 +78,7 @@ #include #include #include +#include #include #define SCRIPTCONTROLLER_RELEASE_LOG_ERROR(channel, fmt, ...) RELEASE_LOG_ERROR(channel, "%p - ScriptController::" fmt, this, ##__VA_ARGS__) diff --git a/Source/WebCore/bindings/js/ScriptModuleLoader.cpp b/Source/WebCore/bindings/js/ScriptModuleLoader.cpp index 05514aeaca1ea..8dace6a8e312f 100644 --- a/Source/WebCore/bindings/js/ScriptModuleLoader.cpp +++ b/Source/WebCore/bindings/js/ScriptModuleLoader.cpp @@ -61,6 +61,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/bindings/js/SerializedScriptValue.cpp b/Source/WebCore/bindings/js/SerializedScriptValue.cpp index 8e461b894ddc6..f6b909a6d40a6 100644 --- a/Source/WebCore/bindings/js/SerializedScriptValue.cpp +++ b/Source/WebCore/bindings/js/SerializedScriptValue.cpp @@ -2862,7 +2862,9 @@ SerializationReturnCode CloneSerializer::serialize(JSValue in) if (!addToObjectPoolIfNotDupe(inMap)) break; write(MapObjectTag); - JSMapIterator* iterator = JSMapIterator::create(vm, m_lexicalGlobalObject->mapIteratorStructure(), inMap, IterationKind::Entries); + JSMapIterator* iterator = JSMapIterator::create(m_lexicalGlobalObject, m_lexicalGlobalObject->mapIteratorStructure(), inMap, IterationKind::Entries); + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; m_keepAliveBuffer.appendWithCrashOnOverflow(iterator); mapIteratorStack.append(iterator); inputObjectStack.append(inMap); @@ -2908,7 +2910,9 @@ SerializationReturnCode CloneSerializer::serialize(JSValue in) if (!addToObjectPoolIfNotDupe(inSet)) break; write(SetObjectTag); - JSSetIterator* iterator = JSSetIterator::create(vm, m_lexicalGlobalObject->setIteratorStructure(), inSet, IterationKind::Keys); + JSSetIterator* iterator = JSSetIterator::create(m_lexicalGlobalObject, m_lexicalGlobalObject->setIteratorStructure(), inSet, IterationKind::Keys); + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; m_keepAliveBuffer.appendWithCrashOnOverflow(iterator); setIteratorStack.append(iterator); inputObjectStack.append(inSet); diff --git a/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm b/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm index 7a9bf5efd220b..dd9a7b0af4d24 100644 --- a/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm +++ b/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm @@ -5267,12 +5267,13 @@ sub GenerateImplementation AddToImplIncludes(""); AddToImplIncludes("ScriptExecutionContext.h"); AddToImplIncludes(""); + AddToImplIncludes(""); push(@implContent, "void ${className}::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)\n"); push(@implContent, "{\n"); push(@implContent, " auto* thisObject = jsCast<${className}*>(cell);\n"); push(@implContent, " analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped());\n"); push(@implContent, " if (thisObject->scriptExecutionContext())\n"); - push(@implContent, " analyzer.setLabelForCell(cell, \"url \"_s + thisObject->scriptExecutionContext()->url().string());\n"); + push(@implContent, " analyzer.setLabelForCell(cell, makeString(\"url \"_s, thisObject->scriptExecutionContext()->url().string()));\n"); push(@implContent, " Base::analyzeHeap(cell, analyzer);\n"); push(@implContent, "}\n\n"); } @@ -6700,7 +6701,7 @@ sub GenerateCallbackHeaderContent my ($object, $interfaceOrCallback, $operations, $constants, $contentRef, $includesRef) = @_; my $name = $interfaceOrCallback->type->name; - my $callbackDataType = $interfaceOrCallback->extendedAttributes->{IsWeakCallback} ? "JSCallbackDataWeak" : "JSCallbackDataStrong"; + my $callbackDataType = $interfaceOrCallback->extendedAttributes->{IsStrongCallback} ? "JSCallbackDataStrong" : "JSCallbackDataWeak"; my $className = GetCallbackClassName($name); $includesRef->{"IDLTypes.h"} = 1; @@ -6765,12 +6766,12 @@ sub GenerateCallbackHeaderContent push(@$contentRef, " ${className}(JSC::JSObject*, JSDOMGlobalObject*);\n\n"); - if ($interfaceOrCallback->extendedAttributes->{IsWeakCallback}) { + if (!$interfaceOrCallback->extendedAttributes->{IsStrongCallback}) { push(@$contentRef, " bool hasCallback() const final { return m_data && m_data->callback(); }\n\n"); } - push(@$contentRef, " void visitJSFunction(JSC::AbstractSlotVisitor&) override;\n\n") if $interfaceOrCallback->extendedAttributes->{IsWeakCallback}; - push(@$contentRef, " void visitJSFunction(JSC::SlotVisitor&) override;\n\n") if $interfaceOrCallback->extendedAttributes->{IsWeakCallback}; + push(@$contentRef, " void visitJSFunction(JSC::AbstractSlotVisitor&) override;\n\n") if !$interfaceOrCallback->extendedAttributes->{IsStrongCallback}; + push(@$contentRef, " void visitJSFunction(JSC::SlotVisitor&) override;\n\n") if !$interfaceOrCallback->extendedAttributes->{IsStrongCallback}; push(@$contentRef, " ${callbackDataType}* m_data;\n"); push(@$contentRef, "};\n\n"); @@ -6790,7 +6791,7 @@ sub GenerateCallbackImplementationContent my ($object, $interfaceOrCallback, $operations, $constants, $contentRef, $includesRef) = @_; my $name = $interfaceOrCallback->type->name; - my $callbackDataType = $interfaceOrCallback->extendedAttributes->{IsWeakCallback} ? "JSCallbackDataWeak" : "JSCallbackDataStrong"; + my $callbackDataType = $interfaceOrCallback->extendedAttributes->{IsStrongCallback} ? "JSCallbackDataStrong" : "JSCallbackDataWeak"; my $visibleName = $codeGenerator->GetVisibleInterfaceName($interfaceOrCallback); my $className = "JS${name}"; @@ -6982,7 +6983,7 @@ sub GenerateCallbackImplementationContent } } - if ($interfaceOrCallback->extendedAttributes->{IsWeakCallback}) { + if (!$interfaceOrCallback->extendedAttributes->{IsStrongCallback}) { push(@$contentRef, "void ${className}::visitJSFunction(JSC::AbstractSlotVisitor& visitor)\n"); push(@$contentRef, "{\n"); push(@$contentRef, " m_data->visitJSFunction(visitor);\n"); diff --git a/Source/WebCore/bindings/scripts/IDLAttributes.json b/Source/WebCore/bindings/scripts/IDLAttributes.json index 2b070fc2dca8b..27846dd35682d 100644 --- a/Source/WebCore/bindings/scripts/IDLAttributes.json +++ b/Source/WebCore/bindings/scripts/IDLAttributes.json @@ -269,7 +269,7 @@ "IsImmutablePrototypeExoticObjectOnPrototype": { "contextsAllowed": ["interface"] }, - "IsWeakCallback": { + "IsStrongCallback": { "contextsAllowed": ["callback-function", "interface"] }, "JSBuiltin": { diff --git a/Source/WebCore/bindings/scripts/test/JS/JSDOMWindow.cpp b/Source/WebCore/bindings/scripts/test/JS/JSDOMWindow.cpp index bd46f75abea25..57a295f3ab1b8 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSDOMWindow.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSDOMWindow.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #if ENABLE(Condition1) || ENABLE(Condition2) #include "JSTestInterface.h" @@ -387,7 +388,7 @@ void JSDOMWindow::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSDedicatedWorkerGlobalScope.cpp b/Source/WebCore/bindings/scripts/test/JS/JSDedicatedWorkerGlobalScope.cpp index 8fa4792577a06..2ec91a0d11b77 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSDedicatedWorkerGlobalScope.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSDedicatedWorkerGlobalScope.cpp @@ -43,6 +43,7 @@ #include #include #include +#include namespace WebCore { @@ -188,7 +189,7 @@ void JSDedicatedWorkerGlobalScope::analyzeHeap(JSCell* cell, HeapAnalyzer& analy auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSExposedStar.cpp b/Source/WebCore/bindings/scripts/test/JS/JSExposedStar.cpp index 568a8fab762a4..cdacdd95818a8 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSExposedStar.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSExposedStar.cpp @@ -44,6 +44,7 @@ #include #include #include +#include namespace WebCore { @@ -246,7 +247,7 @@ void JSExposedStar::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSExposedToWorkerAndWindow.cpp b/Source/WebCore/bindings/scripts/test/JS/JSExposedToWorkerAndWindow.cpp index c4031b53bdaa0..da7cda5e05b47 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSExposedToWorkerAndWindow.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSExposedToWorkerAndWindow.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace WebCore { @@ -259,7 +260,7 @@ void JSExposedToWorkerAndWindow::analyzeHeap(JSCell* cell, HeapAnalyzer& analyze auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSPaintWorkletGlobalScope.cpp b/Source/WebCore/bindings/scripts/test/JS/JSPaintWorkletGlobalScope.cpp index 7a6782102c214..050be6ef36995 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSPaintWorkletGlobalScope.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSPaintWorkletGlobalScope.cpp @@ -43,6 +43,7 @@ #include #include #include +#include namespace WebCore { @@ -187,7 +188,7 @@ void JSPaintWorkletGlobalScope::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSServiceWorkerGlobalScope.cpp b/Source/WebCore/bindings/scripts/test/JS/JSServiceWorkerGlobalScope.cpp index b109ea52d5023..e08f9eb127814 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSServiceWorkerGlobalScope.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSServiceWorkerGlobalScope.cpp @@ -43,6 +43,7 @@ #include #include #include +#include namespace WebCore { @@ -187,7 +188,7 @@ void JSServiceWorkerGlobalScope::analyzeHeap(JSCell* cell, HeapAnalyzer& analyze auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSShadowRealmGlobalScope.cpp b/Source/WebCore/bindings/scripts/test/JS/JSShadowRealmGlobalScope.cpp index 7cf8d9104fe99..9749f2f6d2086 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSShadowRealmGlobalScope.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSShadowRealmGlobalScope.cpp @@ -44,6 +44,7 @@ #include #include #include +#include namespace WebCore { @@ -164,7 +165,7 @@ void JSShadowRealmGlobalScope::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSSharedWorkerGlobalScope.cpp b/Source/WebCore/bindings/scripts/test/JS/JSSharedWorkerGlobalScope.cpp index 3828ba508d641..06c3194ea22f1 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSSharedWorkerGlobalScope.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSSharedWorkerGlobalScope.cpp @@ -43,6 +43,7 @@ #include #include #include +#include namespace WebCore { @@ -188,7 +189,7 @@ void JSSharedWorkerGlobalScope::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestAsyncIterable.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestAsyncIterable.cpp index 6b33c7e46eea5..c6a8a296994cf 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestAsyncIterable.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestAsyncIterable.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace WebCore { @@ -277,7 +278,7 @@ void JSTestAsyncIterable::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestAsyncKeyValueIterable.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestAsyncKeyValueIterable.cpp index 7160d9cb0db6d..9d97e2176da71 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestAsyncKeyValueIterable.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestAsyncKeyValueIterable.cpp @@ -46,6 +46,7 @@ #include #include #include +#include namespace WebCore { @@ -278,7 +279,7 @@ void JSTestAsyncKeyValueIterable::analyzeHeap(JSCell* cell, HeapAnalyzer& analyz auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCEReactions.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestCEReactions.cpp index 875691dcddd87..fc8d0aa6fd1e3 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestCEReactions.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCEReactions.cpp @@ -51,6 +51,7 @@ #include #include #include +#include namespace WebCore { @@ -451,7 +452,7 @@ void JSTestCEReactions::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCEReactionsStringifier.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestCEReactionsStringifier.cpp index c17dbcfffab8e..213a7a1172029 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestCEReactionsStringifier.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCEReactionsStringifier.cpp @@ -44,6 +44,7 @@ #include #include #include +#include namespace WebCore { @@ -271,7 +272,7 @@ void JSTestCEReactionsStringifier::analyzeHeap(JSCell* cell, HeapAnalyzer& analy auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallTracer.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestCallTracer.cpp index cde830db4cc8c..b261a724c833f 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestCallTracer.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCallTracer.cpp @@ -57,6 +57,7 @@ #include #include #include +#include namespace WebCore { @@ -531,7 +532,7 @@ void JSTestCallTracer::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunction.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunction.cpp index cc994f66a2cf3..a2515391ccd5c 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunction.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunction.cpp @@ -33,7 +33,7 @@ using namespace JSC; JSTestCallbackFunction::JSTestCallbackFunction(JSObject* callback, JSDOMGlobalObject* globalObject) : TestCallbackFunction(globalObject->scriptExecutionContext()) - , m_data(new JSCallbackDataStrong(callback, globalObject, this)) + , m_data(new JSCallbackDataWeak(callback, globalObject, this)) { } @@ -83,6 +83,16 @@ CallbackResult JSTestCallbackFunction return { returnValue.releaseReturnValue() }; } +void JSTestCallbackFunction::visitJSFunction(JSC::AbstractSlotVisitor& visitor) +{ + m_data->visitJSFunction(visitor); +} + +void JSTestCallbackFunction::visitJSFunction(JSC::SlotVisitor& visitor) +{ + m_data->visitJSFunction(visitor); +} + JSC::JSValue toJS(TestCallbackFunction& impl) { if (!static_cast(impl).callbackData()) diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunction.h b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunction.h index 6e1364e4d159a..04d0bcb7a9830 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunction.h +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunction.h @@ -37,7 +37,7 @@ class JSTestCallbackFunction final : public TestCallbackFunction { ScriptExecutionContext* scriptExecutionContext() const { return ContextDestructionObserver::scriptExecutionContext(); } ~JSTestCallbackFunction() final; - JSCallbackDataStrong* callbackData() { return m_data; } + JSCallbackDataWeak* callbackData() { return m_data; } // Functions CallbackResult handleEvent(typename IDLLong::ParameterType argument) override; @@ -45,7 +45,13 @@ class JSTestCallbackFunction final : public TestCallbackFunction { private: JSTestCallbackFunction(JSC::JSObject*, JSDOMGlobalObject*); - JSCallbackDataStrong* m_data; + bool hasCallback() const final { return m_data && m_data->callback(); } + + void visitJSFunction(JSC::AbstractSlotVisitor&) override; + + void visitJSFunction(JSC::SlotVisitor&) override; + + JSCallbackDataWeak* m_data; }; JSC::JSValue toJS(TestCallbackFunction&); diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionRethrow.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionRethrow.cpp index fa2e2d9a5f644..9dca892efa36a 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionRethrow.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionRethrow.cpp @@ -36,7 +36,7 @@ using namespace JSC; JSTestCallbackFunctionRethrow::JSTestCallbackFunctionRethrow(JSObject* callback, JSDOMGlobalObject* globalObject) : TestCallbackFunctionRethrow(globalObject->scriptExecutionContext()) - , m_data(new JSCallbackDataStrong(callback, globalObject, this)) + , m_data(new JSCallbackDataWeak(callback, globalObject, this)) { } @@ -86,6 +86,16 @@ CallbackResult JSTestCallbackFunction return { returnValue.releaseReturnValue() }; } +void JSTestCallbackFunctionRethrow::visitJSFunction(JSC::AbstractSlotVisitor& visitor) +{ + m_data->visitJSFunction(visitor); +} + +void JSTestCallbackFunctionRethrow::visitJSFunction(JSC::SlotVisitor& visitor) +{ + m_data->visitJSFunction(visitor); +} + JSC::JSValue toJS(TestCallbackFunctionRethrow& impl) { if (!static_cast(impl).callbackData()) diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionRethrow.h b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionRethrow.h index 86ecacc3f78b0..e9a591f3faf05 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionRethrow.h +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionRethrow.h @@ -37,7 +37,7 @@ class JSTestCallbackFunctionRethrow final : public TestCallbackFunctionRethrow { ScriptExecutionContext* scriptExecutionContext() const { return ContextDestructionObserver::scriptExecutionContext(); } ~JSTestCallbackFunctionRethrow() final; - JSCallbackDataStrong* callbackData() { return m_data; } + JSCallbackDataWeak* callbackData() { return m_data; } // Functions CallbackResult handleEvent(typename IDLSequence::ParameterType argument) override; @@ -45,7 +45,13 @@ class JSTestCallbackFunctionRethrow final : public TestCallbackFunctionRethrow { private: JSTestCallbackFunctionRethrow(JSC::JSObject*, JSDOMGlobalObject*); - JSCallbackDataStrong* m_data; + bool hasCallback() const final { return m_data && m_data->callback(); } + + void visitJSFunction(JSC::AbstractSlotVisitor&) override; + + void visitJSFunction(JSC::SlotVisitor&) override; + + JSCallbackDataWeak* m_data; }; JSC::JSValue toJS(TestCallbackFunctionRethrow&); diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionStrong.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionStrong.cpp new file mode 100644 index 0000000000000..30e45789d33a6 --- /dev/null +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionStrong.cpp @@ -0,0 +1,94 @@ +/* + This file is part of the WebKit open source project. + This file has been generated by generate-bindings.pl. DO NOT MODIFY! + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#include "JSTestCallbackFunctionStrong.h" + +#include "ContextDestructionObserverInlines.h" +#include "JSDOMConvertNumbers.h" +#include "JSDOMConvertStrings.h" +#include "JSDOMExceptionHandling.h" +#include "ScriptExecutionContext.h" + + +namespace WebCore { +using namespace JSC; + +JSTestCallbackFunctionStrong::JSTestCallbackFunctionStrong(JSObject* callback, JSDOMGlobalObject* globalObject) + : TestCallbackFunctionStrong(globalObject->scriptExecutionContext()) + , m_data(new JSCallbackDataStrong(callback, globalObject, this)) +{ +} + +JSTestCallbackFunctionStrong::~JSTestCallbackFunctionStrong() +{ + ScriptExecutionContext* context = scriptExecutionContext(); + // When the context is destroyed, all tasks with a reference to a callback + // should be deleted. So if the context is 0, we are on the context thread. + if (!context || context->isContextThread()) + delete m_data; + else + context->postTask(DeleteCallbackDataTask(m_data)); +#ifndef NDEBUG + m_data = nullptr; +#endif +} + +CallbackResult JSTestCallbackFunctionStrong::handleEvent(typename IDLLong::ParameterType argument) +{ + if (!canInvokeCallback()) + return CallbackResultType::UnableToExecute; + + Ref protectedThis(*this); + + auto& globalObject = *m_data->globalObject(); + auto& vm = globalObject.vm(); + + JSLockHolder lock(vm); + auto& lexicalGlobalObject = globalObject; + JSValue thisValue = jsUndefined(); + MarkedArgumentBuffer args; + args.append(toJS(argument)); + ASSERT(!args.hasOverflowed()); + + NakedPtr returnedException; + auto jsResult = m_data->invokeCallback(thisValue, args, JSCallbackData::CallbackType::Function, Identifier(), returnedException); + if (returnedException) { + UNUSED_PARAM(lexicalGlobalObject); + reportException(m_data->callback()->globalObject(), returnedException); + return CallbackResultType::ExceptionThrown; + } + + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto returnValue = convert(lexicalGlobalObject, jsResult); + if (UNLIKELY(returnValue.hasException(throwScope))) + return CallbackResultType::ExceptionThrown; + return { returnValue.releaseReturnValue() }; +} + +JSC::JSValue toJS(TestCallbackFunctionStrong& impl) +{ + if (!static_cast(impl).callbackData()) + return jsNull(); + + return static_cast(impl).callbackData()->callback(); +} + +} // namespace WebCore diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionStrong.h b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionStrong.h new file mode 100644 index 0000000000000..64195ca255965 --- /dev/null +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionStrong.h @@ -0,0 +1,57 @@ +/* + This file is part of the WebKit open source project. + This file has been generated by generate-bindings.pl. DO NOT MODIFY! + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include "IDLTypes.h" +#include "JSCallbackData.h" +#include "TestCallbackFunctionStrong.h" +#include + +namespace WebCore { + +class JSTestCallbackFunctionStrong final : public TestCallbackFunctionStrong { +public: + static Ref create(JSC::JSObject* callback, JSDOMGlobalObject* globalObject) + { + return adoptRef(*new JSTestCallbackFunctionStrong(callback, globalObject)); + } + + ScriptExecutionContext* scriptExecutionContext() const { return ContextDestructionObserver::scriptExecutionContext(); } + + ~JSTestCallbackFunctionStrong() final; + JSCallbackDataStrong* callbackData() { return m_data; } + + // Functions + CallbackResult handleEvent(typename IDLLong::ParameterType argument) override; + +private: + JSTestCallbackFunctionStrong(JSC::JSObject*, JSDOMGlobalObject*); + + JSCallbackDataStrong* m_data; +}; + +JSC::JSValue toJS(TestCallbackFunctionStrong&); +inline JSC::JSValue toJS(TestCallbackFunctionStrong* impl) { return impl ? toJS(*impl) : JSC::jsNull(); } + +template<> struct JSDOMCallbackConverterTraits { + using Base = TestCallbackFunctionStrong; +}; +} // namespace WebCore diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithThisObject.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithThisObject.cpp index a726c709053c9..d83a5ca96450d 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithThisObject.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithThisObject.cpp @@ -37,7 +37,7 @@ using namespace JSC; JSTestCallbackFunctionWithThisObject::JSTestCallbackFunctionWithThisObject(JSObject* callback, JSDOMGlobalObject* globalObject) : TestCallbackFunctionWithThisObject(globalObject->scriptExecutionContext()) - , m_data(new JSCallbackDataStrong(callback, globalObject, this)) + , m_data(new JSCallbackDataWeak(callback, globalObject, this)) { } @@ -83,6 +83,16 @@ CallbackResult JSTestCallbackFunction return { }; } +void JSTestCallbackFunctionWithThisObject::visitJSFunction(JSC::AbstractSlotVisitor& visitor) +{ + m_data->visitJSFunction(visitor); +} + +void JSTestCallbackFunctionWithThisObject::visitJSFunction(JSC::SlotVisitor& visitor) +{ + m_data->visitJSFunction(visitor); +} + JSC::JSValue toJS(TestCallbackFunctionWithThisObject& impl) { if (!static_cast(impl).callbackData()) diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithThisObject.h b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithThisObject.h index 246ea6dce2421..896a554a2b0a9 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithThisObject.h +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithThisObject.h @@ -37,7 +37,7 @@ class JSTestCallbackFunctionWithThisObject final : public TestCallbackFunctionWi ScriptExecutionContext* scriptExecutionContext() const { return ContextDestructionObserver::scriptExecutionContext(); } ~JSTestCallbackFunctionWithThisObject() final; - JSCallbackDataStrong* callbackData() { return m_data; } + JSCallbackDataWeak* callbackData() { return m_data; } // Functions CallbackResult handleEvent(typename IDLInterface::ParameterType thisObject, typename IDLSequence>::ParameterType parameter) override; @@ -45,7 +45,13 @@ class JSTestCallbackFunctionWithThisObject final : public TestCallbackFunctionWi private: JSTestCallbackFunctionWithThisObject(JSC::JSObject*, JSDOMGlobalObject*); - JSCallbackDataStrong* m_data; + bool hasCallback() const final { return m_data && m_data->callback(); } + + void visitJSFunction(JSC::AbstractSlotVisitor&) override; + + void visitJSFunction(JSC::SlotVisitor&) override; + + JSCallbackDataWeak* m_data; }; JSC::JSValue toJS(TestCallbackFunctionWithThisObject&); diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithTypedefs.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithTypedefs.cpp index 49bbd3e7a45b3..d6f9d2d4e74e7 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithTypedefs.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithTypedefs.cpp @@ -37,7 +37,7 @@ using namespace JSC; JSTestCallbackFunctionWithTypedefs::JSTestCallbackFunctionWithTypedefs(JSObject* callback, JSDOMGlobalObject* globalObject) : TestCallbackFunctionWithTypedefs(globalObject->scriptExecutionContext()) - , m_data(new JSCallbackDataStrong(callback, globalObject, this)) + , m_data(new JSCallbackDataWeak(callback, globalObject, this)) { } @@ -84,6 +84,16 @@ CallbackResult JSTestCallbackFunction return { }; } +void JSTestCallbackFunctionWithTypedefs::visitJSFunction(JSC::AbstractSlotVisitor& visitor) +{ + m_data->visitJSFunction(visitor); +} + +void JSTestCallbackFunctionWithTypedefs::visitJSFunction(JSC::SlotVisitor& visitor) +{ + m_data->visitJSFunction(visitor); +} + JSC::JSValue toJS(TestCallbackFunctionWithTypedefs& impl) { if (!static_cast(impl).callbackData()) diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithTypedefs.h b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithTypedefs.h index 86672e9094357..673a3fdf09b15 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithTypedefs.h +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithTypedefs.h @@ -37,7 +37,7 @@ class JSTestCallbackFunctionWithTypedefs final : public TestCallbackFunctionWith ScriptExecutionContext* scriptExecutionContext() const { return ContextDestructionObserver::scriptExecutionContext(); } ~JSTestCallbackFunctionWithTypedefs() final; - JSCallbackDataStrong* callbackData() { return m_data; } + JSCallbackDataWeak* callbackData() { return m_data; } // Functions CallbackResult handleEvent(typename IDLSequence>::ParameterType sequenceArg, typename IDLLong::ParameterType longArg) override; @@ -45,7 +45,13 @@ class JSTestCallbackFunctionWithTypedefs final : public TestCallbackFunctionWith private: JSTestCallbackFunctionWithTypedefs(JSC::JSObject*, JSDOMGlobalObject*); - JSCallbackDataStrong* m_data; + bool hasCallback() const final { return m_data && m_data->callback(); } + + void visitJSFunction(JSC::AbstractSlotVisitor&) override; + + void visitJSFunction(JSC::SlotVisitor&) override; + + JSCallbackDataWeak* m_data; }; JSC::JSValue toJS(TestCallbackFunctionWithTypedefs&); diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithVariadic.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithVariadic.cpp index 2bbdc0b67b3bd..94a13116251fd 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithVariadic.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithVariadic.cpp @@ -32,7 +32,7 @@ using namespace JSC; JSTestCallbackFunctionWithVariadic::JSTestCallbackFunctionWithVariadic(JSObject* callback, JSDOMGlobalObject* globalObject) : TestCallbackFunctionWithVariadic(globalObject->scriptExecutionContext()) - , m_data(new JSCallbackDataStrong(callback, globalObject, this)) + , m_data(new JSCallbackDataWeak(callback, globalObject, this)) { } @@ -84,6 +84,16 @@ CallbackResult JSTestCallbackFunction return { returnValue.releaseReturnValue() }; } +void JSTestCallbackFunctionWithVariadic::visitJSFunction(JSC::AbstractSlotVisitor& visitor) +{ + m_data->visitJSFunction(visitor); +} + +void JSTestCallbackFunctionWithVariadic::visitJSFunction(JSC::SlotVisitor& visitor) +{ + m_data->visitJSFunction(visitor); +} + JSC::JSValue toJS(TestCallbackFunctionWithVariadic& impl) { if (!static_cast(impl).callbackData()) diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithVariadic.h b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithVariadic.h index 661f330bb0169..530d6cf3bbb1c 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithVariadic.h +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackFunctionWithVariadic.h @@ -39,7 +39,7 @@ class JSTestCallbackFunctionWithVariadic final : public TestCallbackFunctionWith ScriptExecutionContext* scriptExecutionContext() const { return ContextDestructionObserver::scriptExecutionContext(); } ~JSTestCallbackFunctionWithVariadic() final; - JSCallbackDataStrong* callbackData() { return m_data; } + JSCallbackDataWeak* callbackData() { return m_data; } // Functions CallbackResult handleEvent(VariadicArguments&& arguments) override; @@ -47,7 +47,13 @@ class JSTestCallbackFunctionWithVariadic final : public TestCallbackFunctionWith private: JSTestCallbackFunctionWithVariadic(JSC::JSObject*, JSDOMGlobalObject*); - JSCallbackDataStrong* m_data; + bool hasCallback() const final { return m_data && m_data->callback(); } + + void visitJSFunction(JSC::AbstractSlotVisitor&) override; + + void visitJSFunction(JSC::SlotVisitor&) override; + + JSCallbackDataWeak* m_data; }; JSC::JSValue toJS(TestCallbackFunctionWithVariadic&); diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackInterface.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackInterface.cpp index 41bc25e02242a..7cea012966d17 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackInterface.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackInterface.cpp @@ -135,7 +135,7 @@ template<> ConversionResult> co JSTestCallbackInterface::JSTestCallbackInterface(JSObject* callback, JSDOMGlobalObject* globalObject) : TestCallbackInterface(globalObject->scriptExecutionContext()) - , m_data(new JSCallbackDataStrong(callback, globalObject, this)) + , m_data(new JSCallbackDataWeak(callback, globalObject, this)) { } @@ -453,6 +453,16 @@ CallbackResult JSTestCallbackInterfac return { returnValue.releaseReturnValue() }; } +void JSTestCallbackInterface::visitJSFunction(JSC::AbstractSlotVisitor& visitor) +{ + m_data->visitJSFunction(visitor); +} + +void JSTestCallbackInterface::visitJSFunction(JSC::SlotVisitor& visitor) +{ + m_data->visitJSFunction(visitor); +} + JSC::JSValue toJS(TestCallbackInterface& impl) { if (!static_cast(impl).callbackData()) diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackInterface.h b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackInterface.h index 9d3a4a42f7bb2..ef980fa8f6fd6 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackInterface.h +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestCallbackInterface.h @@ -41,7 +41,7 @@ class JSTestCallbackInterface final : public TestCallbackInterface { ScriptExecutionContext* scriptExecutionContext() const { return ContextDestructionObserver::scriptExecutionContext(); } ~JSTestCallbackInterface() final; - JSCallbackDataStrong* callbackData() { return m_data; } + JSCallbackDataWeak* callbackData() { return m_data; } static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*); // Functions @@ -59,7 +59,13 @@ class JSTestCallbackInterface final : public TestCallbackInterface { private: JSTestCallbackInterface(JSC::JSObject*, JSDOMGlobalObject*); - JSCallbackDataStrong* m_data; + bool hasCallback() const final { return m_data && m_data->callback(); } + + void visitJSFunction(JSC::AbstractSlotVisitor&) override; + + void visitJSFunction(JSC::SlotVisitor&) override; + + JSCallbackDataWeak* m_data; }; JSC::JSValue toJS(TestCallbackInterface&); diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestClassWithJSBuiltinConstructor.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestClassWithJSBuiltinConstructor.cpp index 224bc620f39c1..ce06754bd61b0 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestClassWithJSBuiltinConstructor.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestClassWithJSBuiltinConstructor.cpp @@ -41,6 +41,7 @@ #include #include #include +#include namespace WebCore { @@ -179,7 +180,7 @@ void JSTestClassWithJSBuiltinConstructor::analyzeHeap(JSCell* cell, HeapAnalyzer auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestConditionalIncludes.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestConditionalIncludes.cpp index ea02e418b88d8..b8b67b660c795 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestConditionalIncludes.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestConditionalIncludes.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #if (ENABLE(Condition11) && ENABLE(Condition12) && ENABLE(Condition22)) || (ENABLE(Condition12) && ENABLE(Condition22)) || (ENABLE(Condition12) && ENABLE(Condition22) && ENABLE(Condition33)) || ENABLE(Condition23) #include "IDLTypes.h" @@ -800,7 +801,7 @@ void JSTestConditionalIncludes::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestConditionallyReadWrite.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestConditionallyReadWrite.cpp index 95a416389826e..5957404833350 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestConditionallyReadWrite.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestConditionallyReadWrite.cpp @@ -48,6 +48,7 @@ #include #include #include +#include namespace WebCore { @@ -472,7 +473,7 @@ void JSTestConditionallyReadWrite::analyzeHeap(JSCell* cell, HeapAnalyzer& analy auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestDOMJIT.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestDOMJIT.cpp index 93a16dce084dc..cdebc1ea7d5ee 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestDOMJIT.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestDOMJIT.cpp @@ -55,6 +55,7 @@ #include #include #include +#include namespace WebCore { @@ -1265,7 +1266,7 @@ void JSTestDOMJIT::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSON.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSON.cpp index 7194201124055..2b972b866d694 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSON.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSON.cpp @@ -60,6 +60,7 @@ #include #include #include +#include #if ENABLE(TEST_CONDITIONAL) #include "JSDOMConvertEnumeration.h" @@ -839,7 +840,7 @@ void JSTestDefaultToJSON::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONFilteredByExposed.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONFilteredByExposed.cpp index bf452c430d464..a841f5d038340 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONFilteredByExposed.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONFilteredByExposed.cpp @@ -47,6 +47,7 @@ #include #include #include +#include namespace WebCore { @@ -275,7 +276,7 @@ void JSTestDefaultToJSONFilteredByExposed::analyzeHeap(JSCell* cell, HeapAnalyze auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONIndirectInheritance.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONIndirectInheritance.cpp index 7f96d97531bf8..6d8a008162405 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONIndirectInheritance.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONIndirectInheritance.cpp @@ -39,6 +39,7 @@ #include #include #include +#include namespace WebCore { @@ -170,7 +171,7 @@ void JSTestDefaultToJSONIndirectInheritance::analyzeHeap(JSCell* cell, HeapAnaly auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONInherit.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONInherit.cpp index 0cde4f97d3283..730cc3eef9733 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONInherit.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONInherit.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #if ENABLE(TEST_CONDITIONAL) #include "JSDOMConvertEnumeration.h" @@ -303,7 +304,7 @@ void JSTestDefaultToJSONInherit::analyzeHeap(JSCell* cell, HeapAnalyzer& analyze auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONInheritFinal.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONInheritFinal.cpp index 65a97ef7c77b5..e80a4ea957655 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONInheritFinal.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestDefaultToJSONInheritFinal.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #if ENABLE(TEST_CONDITIONAL) #include "JSDOMConvertEnumeration.h" @@ -345,7 +346,7 @@ void JSTestDefaultToJSONInheritFinal::analyzeHeap(JSCell* cell, HeapAnalyzer& an auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestDelegateToSharedSyntheticAttribute.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestDelegateToSharedSyntheticAttribute.cpp index 9f082bd43d8c4..41ed7ba79265c 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestDelegateToSharedSyntheticAttribute.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestDelegateToSharedSyntheticAttribute.cpp @@ -43,6 +43,7 @@ #include #include #include +#include namespace WebCore { @@ -253,7 +254,7 @@ void JSTestDelegateToSharedSyntheticAttribute::analyzeHeap(JSCell* cell, HeapAna auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestDomainSecurity.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestDomainSecurity.cpp index 3dc7d5ff6ba23..a5038fdf9b985 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestDomainSecurity.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestDomainSecurity.cpp @@ -50,6 +50,7 @@ #include #include #include +#include namespace WebCore { @@ -328,7 +329,7 @@ void JSTestDomainSecurity::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestEnabledBySetting.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestEnabledBySetting.cpp index b2d25fcb6021c..40b1cb61ab735 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestEnabledBySetting.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestEnabledBySetting.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #if ENABLE(TEST_FEATURE) #include "IDLTypes.h" @@ -477,7 +478,7 @@ void JSTestEnabledBySetting::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestEnabledForContext.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestEnabledForContext.cpp index 111ceb842bce7..7b8b4f8ee14a2 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestEnabledForContext.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestEnabledForContext.cpp @@ -43,6 +43,7 @@ #include #include #include +#include namespace WebCore { @@ -197,7 +198,7 @@ void JSTestEnabledForContext::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestEventConstructor.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestEventConstructor.cpp index 4bd764633eef7..ff7b073e4d947 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestEventConstructor.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestEventConstructor.cpp @@ -44,6 +44,7 @@ #include #include #include +#include namespace WebCore { @@ -329,7 +330,7 @@ void JSTestEventConstructor::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestEventTarget.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestEventTarget.cpp index daa30b6367a53..1e0394afddfef 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestEventTarget.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestEventTarget.cpp @@ -49,6 +49,7 @@ #include #include #include +#include namespace WebCore { @@ -394,7 +395,7 @@ void JSTestEventTarget::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestException.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestException.cpp index be84cc0853060..bda6ca3d29d02 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestException.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestException.cpp @@ -42,6 +42,7 @@ #include #include #include +#include namespace WebCore { @@ -190,7 +191,7 @@ void JSTestException::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestGenerateAddOpaqueRoot.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestGenerateAddOpaqueRoot.cpp index f372f06aeb837..b63202ac39e15 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestGenerateAddOpaqueRoot.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestGenerateAddOpaqueRoot.cpp @@ -43,6 +43,7 @@ #include #include #include +#include namespace WebCore { @@ -202,7 +203,7 @@ void JSTestGenerateAddOpaqueRoot::analyzeHeap(JSCell* cell, HeapAnalyzer& analyz auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestGenerateIsReachable.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestGenerateIsReachable.cpp index 3260e925e9ca2..65d588e14202d 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestGenerateIsReachable.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestGenerateIsReachable.cpp @@ -43,6 +43,7 @@ #include #include #include +#include namespace WebCore { @@ -201,7 +202,7 @@ void JSTestGenerateIsReachable::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestGlobalObject.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestGlobalObject.cpp index 67e9da0653102..420f0211c8ad8 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestGlobalObject.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestGlobalObject.cpp @@ -117,6 +117,7 @@ #include #include #include +#include #if ENABLE(Condition1) || ENABLE(Condition2) #include "JSTestSerializedScriptValueInterface.h" @@ -2280,7 +2281,7 @@ void JSTestGlobalObject::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestIndexedSetterNoIdentifier.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestIndexedSetterNoIdentifier.cpp index 1fc336a264f2c..5411cf59ffc04 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestIndexedSetterNoIdentifier.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestIndexedSetterNoIdentifier.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace WebCore { @@ -324,7 +325,7 @@ void JSTestIndexedSetterNoIdentifier::analyzeHeap(JSCell* cell, HeapAnalyzer& an auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestIndexedSetterThrowingException.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestIndexedSetterThrowingException.cpp index 56280a5251b4e..6d50815bc4fbe 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestIndexedSetterThrowingException.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestIndexedSetterThrowingException.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace WebCore { @@ -324,7 +325,7 @@ void JSTestIndexedSetterThrowingException::analyzeHeap(JSCell* cell, HeapAnalyze auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestIndexedSetterWithIdentifier.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestIndexedSetterWithIdentifier.cpp index e23ec7eda1b69..18fef29c5c115 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestIndexedSetterWithIdentifier.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestIndexedSetterWithIdentifier.cpp @@ -49,6 +49,7 @@ #include #include #include +#include namespace WebCore { @@ -358,7 +359,7 @@ void JSTestIndexedSetterWithIdentifier::analyzeHeap(JSCell* cell, HeapAnalyzer& auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestInterface.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestInterface.cpp index 39dc9645e5ca9..a13d9cfb53192 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestInterface.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestInterface.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #if ENABLE(Condition11) || (ENABLE(Condition11) && ENABLE(Condition22)) || ENABLE(Condition12) || ENABLE(Condition22) || (ENABLE(Condition22) && ENABLE(Condition33)) || ENABLE(Condition23) #include "IDLTypes.h" @@ -1209,7 +1210,7 @@ void JSTestInterface::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestInterfaceLeadingUnderscore.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestInterfaceLeadingUnderscore.cpp index 109fe0dee90c5..deca8629507ca 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestInterfaceLeadingUnderscore.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestInterfaceLeadingUnderscore.cpp @@ -42,6 +42,7 @@ #include #include #include +#include namespace WebCore { @@ -190,7 +191,7 @@ void JSTestInterfaceLeadingUnderscore::analyzeHeap(JSCell* cell, HeapAnalyzer& a auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestIterable.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestIterable.cpp index 28eedb93b18db..cf592ffe06606 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestIterable.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestIterable.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace WebCore { @@ -282,7 +283,7 @@ void JSTestIterable::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestLegacyFactoryFunction.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestLegacyFactoryFunction.cpp index 49720db1b93d3..1f24ce11b5ac1 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestLegacyFactoryFunction.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestLegacyFactoryFunction.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace WebCore { @@ -238,7 +239,7 @@ void JSTestLegacyFactoryFunction::analyzeHeap(JSCell* cell, HeapAnalyzer& analyz auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestLegacyNoInterfaceObject.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestLegacyNoInterfaceObject.cpp index 2be8301353cef..ecf9e65dd8443 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestLegacyNoInterfaceObject.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestLegacyNoInterfaceObject.cpp @@ -48,6 +48,7 @@ #include #include #include +#include namespace WebCore { @@ -345,7 +346,7 @@ void JSTestLegacyNoInterfaceObject::analyzeHeap(JSCell* cell, HeapAnalyzer& anal auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestLegacyOverrideBuiltIns.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestLegacyOverrideBuiltIns.cpp index c25ec062cd74d..ad63440ad530d 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestLegacyOverrideBuiltIns.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestLegacyOverrideBuiltIns.cpp @@ -49,6 +49,7 @@ #include #include #include +#include namespace WebCore { @@ -372,7 +373,7 @@ void JSTestLegacyOverrideBuiltIns::analyzeHeap(JSCell* cell, HeapAnalyzer& analy auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestMapLike.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestMapLike.cpp index ae20eef07dbf4..05fab65be50fd 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestMapLike.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestMapLike.cpp @@ -46,6 +46,7 @@ #include #include #include +#include namespace WebCore { @@ -375,7 +376,7 @@ void JSTestMapLike::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestMapLikeWithOverriddenOperations.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestMapLikeWithOverriddenOperations.cpp index c90b5045df2e2..d8ecbe5b00d3c 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestMapLikeWithOverriddenOperations.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestMapLikeWithOverriddenOperations.cpp @@ -49,6 +49,7 @@ #include #include #include +#include namespace WebCore { @@ -389,7 +390,7 @@ void JSTestMapLikeWithOverriddenOperations::analyzeHeap(JSCell* cell, HeapAnalyz auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedAndIndexedSetterNoIdentifier.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedAndIndexedSetterNoIdentifier.cpp index 9e46220b77ea6..e3a94106174f6 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedAndIndexedSetterNoIdentifier.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedAndIndexedSetterNoIdentifier.cpp @@ -46,6 +46,7 @@ #include #include #include +#include namespace WebCore { @@ -409,7 +410,7 @@ void JSTestNamedAndIndexedSetterNoIdentifier::analyzeHeap(JSCell* cell, HeapAnal auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedAndIndexedSetterThrowingException.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedAndIndexedSetterThrowingException.cpp index 2f9b9dfac6f5b..0af112fb29ef4 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedAndIndexedSetterThrowingException.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedAndIndexedSetterThrowingException.cpp @@ -46,6 +46,7 @@ #include #include #include +#include namespace WebCore { @@ -409,7 +410,7 @@ void JSTestNamedAndIndexedSetterThrowingException::analyzeHeap(JSCell* cell, Hea auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedAndIndexedSetterWithIdentifier.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedAndIndexedSetterWithIdentifier.cpp index 1c82014c1f5e9..67f91ce105d01 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedAndIndexedSetterWithIdentifier.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedAndIndexedSetterWithIdentifier.cpp @@ -50,6 +50,7 @@ #include #include #include +#include namespace WebCore { @@ -470,7 +471,7 @@ void JSTestNamedAndIndexedSetterWithIdentifier::analyzeHeap(JSCell* cell, HeapAn auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterNoIdentifier.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterNoIdentifier.cpp index cc6a653baa54e..eadcdb32f4644 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterNoIdentifier.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterNoIdentifier.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace WebCore { @@ -338,7 +339,7 @@ void JSTestNamedDeleterNoIdentifier::analyzeHeap(JSCell* cell, HeapAnalyzer& ana auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterThrowingException.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterThrowingException.cpp index e0c44f3b6548a..511ec23d68dfd 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterThrowingException.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterThrowingException.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace WebCore { @@ -338,7 +339,7 @@ void JSTestNamedDeleterThrowingException::analyzeHeap(JSCell* cell, HeapAnalyzer auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterWithIdentifier.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterWithIdentifier.cpp index a39ac78cb56a3..49b9db8d2d337 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterWithIdentifier.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterWithIdentifier.cpp @@ -48,6 +48,7 @@ #include #include #include +#include namespace WebCore { @@ -363,7 +364,7 @@ void JSTestNamedDeleterWithIdentifier::analyzeHeap(JSCell* cell, HeapAnalyzer& a auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterWithIndexedGetter.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterWithIndexedGetter.cpp index 4adc64ac44416..52d378e6f2221 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterWithIndexedGetter.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedDeleterWithIndexedGetter.cpp @@ -46,6 +46,7 @@ #include #include #include +#include namespace WebCore { @@ -355,7 +356,7 @@ void JSTestNamedDeleterWithIndexedGetter::analyzeHeap(JSCell* cell, HeapAnalyzer auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedGetterCallWith.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedGetterCallWith.cpp index 3f2a808b53851..47dfa60f5c5fa 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedGetterCallWith.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedGetterCallWith.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace WebCore { @@ -348,7 +349,7 @@ void JSTestNamedGetterCallWith::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedGetterNoIdentifier.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedGetterNoIdentifier.cpp index 1ad2eb90fb8f7..0097d8620b9c7 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedGetterNoIdentifier.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedGetterNoIdentifier.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace WebCore { @@ -348,7 +349,7 @@ void JSTestNamedGetterNoIdentifier::analyzeHeap(JSCell* cell, HeapAnalyzer& anal auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedGetterWithIdentifier.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedGetterWithIdentifier.cpp index 5ea643b6eb34d..890ca08368382 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedGetterWithIdentifier.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedGetterWithIdentifier.cpp @@ -46,6 +46,7 @@ #include #include #include +#include namespace WebCore { @@ -375,7 +376,7 @@ void JSTestNamedGetterWithIdentifier::analyzeHeap(JSCell* cell, HeapAnalyzer& an auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterNoIdentifier.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterNoIdentifier.cpp index cbfb05e6f96e5..547573a87e868 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterNoIdentifier.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterNoIdentifier.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace WebCore { @@ -367,7 +368,7 @@ void JSTestNamedSetterNoIdentifier::analyzeHeap(JSCell* cell, HeapAnalyzer& anal auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterThrowingException.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterThrowingException.cpp index 8df9bc9e67c97..5e6d1d3d99b78 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterThrowingException.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterThrowingException.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace WebCore { @@ -367,7 +368,7 @@ void JSTestNamedSetterThrowingException::analyzeHeap(JSCell* cell, HeapAnalyzer& auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithIdentifier.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithIdentifier.cpp index 91cc990be8582..a5dc0faa18fc7 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithIdentifier.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithIdentifier.cpp @@ -48,6 +48,7 @@ #include #include #include +#include namespace WebCore { @@ -400,7 +401,7 @@ void JSTestNamedSetterWithIdentifier::analyzeHeap(JSCell* cell, HeapAnalyzer& an auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithIndexedGetter.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithIndexedGetter.cpp index 70d76fd405071..35bebe1eef78d 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithIndexedGetter.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithIndexedGetter.cpp @@ -50,6 +50,7 @@ #include #include #include +#include namespace WebCore { @@ -443,7 +444,7 @@ void JSTestNamedSetterWithIndexedGetter::analyzeHeap(JSCell* cell, HeapAnalyzer& auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithIndexedGetterAndSetter.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithIndexedGetterAndSetter.cpp index c26606ae9e708..1e7c9e40e2c17 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithIndexedGetterAndSetter.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithIndexedGetterAndSetter.cpp @@ -50,6 +50,7 @@ #include #include #include +#include namespace WebCore { @@ -498,7 +499,7 @@ void JSTestNamedSetterWithIndexedGetterAndSetter::analyzeHeap(JSCell* cell, Heap auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithLegacyOverrideBuiltIns.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithLegacyOverrideBuiltIns.cpp index ec168be55920c..6ee85eed6e3c7 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithLegacyOverrideBuiltIns.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithLegacyOverrideBuiltIns.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace WebCore { @@ -343,7 +344,7 @@ void JSTestNamedSetterWithLegacyOverrideBuiltIns::analyzeHeap(JSCell* cell, Heap auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithLegacyUnforgeableProperties.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithLegacyUnforgeableProperties.cpp index 78ff93e6aea0b..afc6baefc1de8 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithLegacyUnforgeableProperties.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithLegacyUnforgeableProperties.cpp @@ -49,6 +49,7 @@ #include #include #include +#include namespace WebCore { @@ -428,7 +429,7 @@ void JSTestNamedSetterWithLegacyUnforgeableProperties::analyzeHeap(JSCell* cell, auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithLegacyUnforgeablePropertiesAndLegacyOverrideBuiltIns.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithLegacyUnforgeablePropertiesAndLegacyOverrideBuiltIns.cpp index 382ad0bfabe57..8c564fbf3cca1 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithLegacyUnforgeablePropertiesAndLegacyOverrideBuiltIns.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNamedSetterWithLegacyUnforgeablePropertiesAndLegacyOverrideBuiltIns.cpp @@ -49,6 +49,7 @@ #include #include #include +#include namespace WebCore { @@ -404,7 +405,7 @@ void JSTestNamedSetterWithLegacyUnforgeablePropertiesAndLegacyOverrideBuiltIns:: auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestNode.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestNode.cpp index 609727de7b4d4..b77ae72fb3813 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestNode.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestNode.cpp @@ -56,6 +56,7 @@ #include #include #include +#include namespace WebCore { @@ -495,7 +496,7 @@ void JSTestNode::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestObj.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestObj.cpp index 32b8d39800eaf..ab503bb2db5b3 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestObj.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestObj.cpp @@ -110,6 +110,7 @@ #include #include #include +#include #if ENABLE(Condition1) #include "JSTestObjectA.h" @@ -9958,7 +9959,7 @@ void JSTestObj::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestOperationConditional.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestOperationConditional.cpp index 796b8c329eb36..cefc5481b360c 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestOperationConditional.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestOperationConditional.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #if ENABLE(ConditionBase) || (ENABLE(ConditionBase) && ENABLE(ConditionOperation)) #include "IDLTypes.h" @@ -237,7 +238,7 @@ void JSTestOperationConditional::analyzeHeap(JSCell* cell, HeapAnalyzer& analyze auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestOverloadedConstructors.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestOverloadedConstructors.cpp index eef4b10e64330..9e79389943b8e 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestOverloadedConstructors.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestOverloadedConstructors.cpp @@ -47,6 +47,7 @@ #include #include #include +#include namespace WebCore { @@ -313,7 +314,7 @@ void JSTestOverloadedConstructors::analyzeHeap(JSCell* cell, HeapAnalyzer& analy auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestOverloadedConstructorsWithSequence.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestOverloadedConstructorsWithSequence.cpp index 3720001148c8f..39af490db5a40 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestOverloadedConstructorsWithSequence.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestOverloadedConstructorsWithSequence.cpp @@ -46,6 +46,7 @@ #include #include #include +#include namespace WebCore { @@ -248,7 +249,7 @@ void JSTestOverloadedConstructorsWithSequence::analyzeHeap(JSCell* cell, HeapAna auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestPluginInterface.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestPluginInterface.cpp index a7d8beff1e9ac..e9e4551e73406 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestPluginInterface.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestPluginInterface.cpp @@ -44,6 +44,7 @@ #include #include #include +#include namespace WebCore { @@ -281,7 +282,7 @@ void JSTestPluginInterface::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestPromiseRejectionEvent.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestPromiseRejectionEvent.cpp index f42b1120976e2..77953a6c0e37b 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestPromiseRejectionEvent.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestPromiseRejectionEvent.cpp @@ -48,6 +48,7 @@ #include #include #include +#include namespace WebCore { @@ -311,7 +312,7 @@ void JSTestPromiseRejectionEvent::analyzeHeap(JSCell* cell, HeapAnalyzer& analyz auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestReadOnlyMapLike.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestReadOnlyMapLike.cpp index ff4fc75b1b789..9c8ede200bc80 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestReadOnlyMapLike.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestReadOnlyMapLike.cpp @@ -46,6 +46,7 @@ #include #include #include +#include namespace WebCore { @@ -311,7 +312,7 @@ void JSTestReadOnlyMapLike::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestReadOnlySetLike.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestReadOnlySetLike.cpp index fd3cead9a1dde..c6497518be63f 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestReadOnlySetLike.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestReadOnlySetLike.cpp @@ -46,6 +46,7 @@ #include #include #include +#include namespace WebCore { @@ -289,7 +290,7 @@ void JSTestReadOnlySetLike::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestReportExtraMemoryCost.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestReportExtraMemoryCost.cpp index 6cee9d5aa86f3..d5a9bebfd4150 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestReportExtraMemoryCost.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestReportExtraMemoryCost.cpp @@ -40,6 +40,7 @@ #include #include #include +#include namespace WebCore { @@ -198,7 +199,7 @@ void JSTestReportExtraMemoryCost::analyzeHeap(JSCell* cell, HeapAnalyzer& analyz auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestScheduledAction.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestScheduledAction.cpp index d7185c3214316..adc45c72d204c 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestScheduledAction.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestScheduledAction.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace WebCore { @@ -204,7 +205,7 @@ void JSTestScheduledAction::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestSerializedScriptValueInterface.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestSerializedScriptValueInterface.cpp index 83230ab99da56..ded1947047516 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestSerializedScriptValueInterface.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestSerializedScriptValueInterface.cpp @@ -54,6 +54,7 @@ #include #include #include +#include namespace WebCore { @@ -369,7 +370,7 @@ void JSTestSerializedScriptValueInterface::analyzeHeap(JSCell* cell, HeapAnalyze auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestSetLike.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestSetLike.cpp index c2a767eac27f6..9d9764c1de07f 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestSetLike.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestSetLike.cpp @@ -46,6 +46,7 @@ #include #include #include +#include namespace WebCore { @@ -349,7 +350,7 @@ void JSTestSetLike::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestSetLikeWithOverriddenOperations.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestSetLikeWithOverriddenOperations.cpp index 7b5b1aab10cf2..b49d3e67573a3 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestSetLikeWithOverriddenOperations.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestSetLikeWithOverriddenOperations.cpp @@ -49,6 +49,7 @@ #include #include #include +#include namespace WebCore { @@ -361,7 +362,7 @@ void JSTestSetLikeWithOverriddenOperations::analyzeHeap(JSCell* cell, HeapAnalyz auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestStringifier.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestStringifier.cpp index d6df8725a441a..ed5dba84b348f 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestStringifier.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestStringifier.cpp @@ -42,6 +42,7 @@ #include #include #include +#include namespace WebCore { @@ -195,7 +196,7 @@ void JSTestStringifier::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierAnonymousOperation.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierAnonymousOperation.cpp index 65a4064a93d2c..58021f7d42408 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierAnonymousOperation.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierAnonymousOperation.cpp @@ -42,6 +42,7 @@ #include #include #include +#include namespace WebCore { @@ -195,7 +196,7 @@ void JSTestStringifierAnonymousOperation::analyzeHeap(JSCell* cell, HeapAnalyzer auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierNamedOperation.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierNamedOperation.cpp index ad3f5efac2158..404f15acbb802 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierNamedOperation.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierNamedOperation.cpp @@ -42,6 +42,7 @@ #include #include #include +#include namespace WebCore { @@ -212,7 +213,7 @@ void JSTestStringifierNamedOperation::analyzeHeap(JSCell* cell, HeapAnalyzer& an auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierOperationImplementedAs.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierOperationImplementedAs.cpp index e5f25f3839dac..3f8ceb094b810 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierOperationImplementedAs.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierOperationImplementedAs.cpp @@ -42,6 +42,7 @@ #include #include #include +#include namespace WebCore { @@ -212,7 +213,7 @@ void JSTestStringifierOperationImplementedAs::analyzeHeap(JSCell* cell, HeapAnal auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierOperationNamedToString.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierOperationNamedToString.cpp index e951ec26149a1..74874651c4c77 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierOperationNamedToString.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierOperationNamedToString.cpp @@ -42,6 +42,7 @@ #include #include #include +#include namespace WebCore { @@ -195,7 +196,7 @@ void JSTestStringifierOperationNamedToString::analyzeHeap(JSCell* cell, HeapAnal auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierReadOnlyAttribute.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierReadOnlyAttribute.cpp index cc68c4f1a1eca..19cba8c3d3225 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierReadOnlyAttribute.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierReadOnlyAttribute.cpp @@ -43,6 +43,7 @@ #include #include #include +#include namespace WebCore { @@ -211,7 +212,7 @@ void JSTestStringifierReadOnlyAttribute::analyzeHeap(JSCell* cell, HeapAnalyzer& auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierReadWriteAttribute.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierReadWriteAttribute.cpp index 0ca53514b7942..031f00b1a998a 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierReadWriteAttribute.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestStringifierReadWriteAttribute.cpp @@ -43,6 +43,7 @@ #include #include #include +#include namespace WebCore { @@ -232,7 +233,7 @@ void JSTestStringifierReadWriteAttribute::analyzeHeap(JSCell* cell, HeapAnalyzer auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestTaggedWrapper.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestTaggedWrapper.cpp index d4faabd708b74..e113609da5ee4 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestTaggedWrapper.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestTaggedWrapper.cpp @@ -40,6 +40,7 @@ #include #include #include +#include namespace WebCore { @@ -173,7 +174,7 @@ void JSTestTaggedWrapper::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestTypedefs.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestTypedefs.cpp index 95f1705293b0f..dc6022da33a8f 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestTypedefs.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestTypedefs.cpp @@ -64,6 +64,7 @@ #include #include #include +#include namespace WebCore { @@ -817,7 +818,7 @@ void JSTestTypedefs::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestVoidCallbackFunction.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestVoidCallbackFunction.cpp index ee6ecac4237fc..f0eeb1737cd52 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestVoidCallbackFunction.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestVoidCallbackFunction.cpp @@ -45,7 +45,7 @@ using namespace JSC; JSTestVoidCallbackFunction::JSTestVoidCallbackFunction(JSObject* callback, JSDOMGlobalObject* globalObject) : TestVoidCallbackFunction(globalObject->scriptExecutionContext()) - , m_data(new JSCallbackDataStrong(callback, globalObject, this)) + , m_data(new JSCallbackDataWeak(callback, globalObject, this)) { } @@ -96,6 +96,16 @@ CallbackResult JSTestVoidCallbackFunc return { }; } +void JSTestVoidCallbackFunction::visitJSFunction(JSC::AbstractSlotVisitor& visitor) +{ + m_data->visitJSFunction(visitor); +} + +void JSTestVoidCallbackFunction::visitJSFunction(JSC::SlotVisitor& visitor) +{ + m_data->visitJSFunction(visitor); +} + JSC::JSValue toJS(TestVoidCallbackFunction& impl) { if (!static_cast(impl).callbackData()) diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestVoidCallbackFunction.h b/Source/WebCore/bindings/scripts/test/JS/JSTestVoidCallbackFunction.h index 9bacd6d9942b4..3ae9fe77c58e4 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestVoidCallbackFunction.h +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestVoidCallbackFunction.h @@ -39,7 +39,7 @@ class JSTestVoidCallbackFunction final : public TestVoidCallbackFunction { ScriptExecutionContext* scriptExecutionContext() const { return ContextDestructionObserver::scriptExecutionContext(); } ~JSTestVoidCallbackFunction() final; - JSCallbackDataStrong* callbackData() { return m_data; } + JSCallbackDataWeak* callbackData() { return m_data; } // Functions CallbackResult handleEvent(typename IDLFloat32Array::ParameterType arrayParam, typename IDLSerializedScriptValue::ParameterType srzParam, typename IDLDOMString::ParameterType strArg, typename IDLBoolean::ParameterType boolParam, typename IDLLong::ParameterType longParam, typename IDLInterface::ParameterType testNodeParam) override; @@ -47,7 +47,13 @@ class JSTestVoidCallbackFunction final : public TestVoidCallbackFunction { private: JSTestVoidCallbackFunction(JSC::JSObject*, JSDOMGlobalObject*); - JSCallbackDataStrong* m_data; + bool hasCallback() const final { return m_data && m_data->callback(); } + + void visitJSFunction(JSC::AbstractSlotVisitor&) override; + + void visitJSFunction(JSC::SlotVisitor&) override; + + JSCallbackDataWeak* m_data; }; JSC::JSValue toJS(TestVoidCallbackFunction&); diff --git a/Source/WebCore/bindings/scripts/test/JS/JSWorkerGlobalScope.cpp b/Source/WebCore/bindings/scripts/test/JS/JSWorkerGlobalScope.cpp index 67944a8426fef..8e19e1e21601e 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSWorkerGlobalScope.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSWorkerGlobalScope.cpp @@ -47,6 +47,7 @@ #include #include #include +#include namespace WebCore { @@ -269,7 +270,7 @@ void JSWorkerGlobalScope::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/JS/JSWorkletGlobalScope.cpp b/Source/WebCore/bindings/scripts/test/JS/JSWorkletGlobalScope.cpp index 1cf1fda2af41b..138487486d00f 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSWorkletGlobalScope.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSWorkletGlobalScope.cpp @@ -42,6 +42,7 @@ #include #include #include +#include namespace WebCore { @@ -183,7 +184,7 @@ void JSWorkletGlobalScope::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, makeString("url "_s, thisObject->scriptExecutionContext()->url().string())); Base::analyzeHeap(cell, analyzer); } diff --git a/Source/WebCore/bindings/scripts/test/SupplementalDependencies.dep b/Source/WebCore/bindings/scripts/test/SupplementalDependencies.dep index 396a5c18c2bc0..afe3999c0cfff 100644 --- a/Source/WebCore/bindings/scripts/test/SupplementalDependencies.dep +++ b/Source/WebCore/bindings/scripts/test/SupplementalDependencies.dep @@ -20,6 +20,7 @@ JSTestCEReactionsStringifier.h: JSTestCallTracer.h: JSTestCallbackFunction.h: JSTestCallbackFunctionRethrow.h: +JSTestCallbackFunctionStrong.h: JSTestCallbackFunctionWithThisObject.h: JSTestCallbackFunctionWithTypedefs.h: JSTestCallbackFunctionWithVariadic.h: diff --git a/Source/WebCore/bindings/scripts/test/TestCallbackFunctionStrong.idl b/Source/WebCore/bindings/scripts/test/TestCallbackFunctionStrong.idl new file mode 100644 index 0000000000000..9c14fbc425e92 --- /dev/null +++ b/Source/WebCore/bindings/scripts/test/TestCallbackFunctionStrong.idl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +[ IsStrongCallback ] callback TestCallbackFunctionStrong = DOMString (long argument); diff --git a/Source/WebCore/contentextensions/ContentExtensionActions.cpp b/Source/WebCore/contentextensions/ContentExtensionActions.cpp index 6e087a10ba8a4..a1d0d021c2f53 100644 --- a/Source/WebCore/contentextensions/ContentExtensionActions.cpp +++ b/Source/WebCore/contentextensions/ContentExtensionActions.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include namespace WebCore::ContentExtensions { diff --git a/Source/WebCore/contentextensions/ContentExtensionParser.cpp b/Source/WebCore/contentextensions/ContentExtensionParser.cpp index a9c2179af0993..d5ef3a852506e 100644 --- a/Source/WebCore/contentextensions/ContentExtensionParser.cpp +++ b/Source/WebCore/contentextensions/ContentExtensionParser.cpp @@ -38,6 +38,7 @@ #include "ProcessWarming.h" #include #include +#include #include namespace WebCore::ContentExtensions { diff --git a/Source/WebCore/contentextensions/ContentExtensionsBackend.cpp b/Source/WebCore/contentextensions/ContentExtensionsBackend.cpp index 018410c2f7b93..21d567a8b6023 100644 --- a/Source/WebCore/contentextensions/ContentExtensionsBackend.cpp +++ b/Source/WebCore/contentextensions/ContentExtensionsBackend.cpp @@ -50,6 +50,7 @@ #include "UserContentController.h" #include #include +#include namespace WebCore::ContentExtensions { diff --git a/Source/WebCore/css/CSSAnchorValue.cpp b/Source/WebCore/css/CSSAnchorValue.cpp index cbbec3d0f3c5b..14b8649cd5ff7 100644 --- a/Source/WebCore/css/CSSAnchorValue.cpp +++ b/Source/WebCore/css/CSSAnchorValue.cpp @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2024 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -27,6 +27,7 @@ #include "CSSAnchorValue.h" #include "CSSPrimitiveValue.h" +#include namespace WebCore { diff --git a/Source/WebCore/css/CSSAnchorValue.h b/Source/WebCore/css/CSSAnchorValue.h index e87ddd93d722e..b9e02810e97a2 100644 --- a/Source/WebCore/css/CSSAnchorValue.h +++ b/Source/WebCore/css/CSSAnchorValue.h @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2024 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/WebCore/css/CSSAspectRatioValue.cpp b/Source/WebCore/css/CSSAspectRatioValue.cpp index cfe856c014386..078bd5a2c41e4 100644 --- a/Source/WebCore/css/CSSAspectRatioValue.cpp +++ b/Source/WebCore/css/CSSAspectRatioValue.cpp @@ -29,7 +29,7 @@ #include "config.h" #include "CSSAspectRatioValue.h" -#include +#include namespace WebCore { diff --git a/Source/WebCore/css/CSSBackgroundRepeatValue.cpp b/Source/WebCore/css/CSSBackgroundRepeatValue.cpp index 1935b4faf56ab..668d2e375afc2 100644 --- a/Source/WebCore/css/CSSBackgroundRepeatValue.cpp +++ b/Source/WebCore/css/CSSBackgroundRepeatValue.cpp @@ -27,6 +27,7 @@ #include "CSSBackgroundRepeatValue.h" #include "CSSValueKeywords.h" +#include #include namespace WebCore { diff --git a/Source/WebCore/css/CSSBasicShapes.cpp b/Source/WebCore/css/CSSBasicShapes.cpp index f722fe56b8eae..60b957f905655 100644 --- a/Source/WebCore/css/CSSBasicShapes.cpp +++ b/Source/WebCore/css/CSSBasicShapes.cpp @@ -36,6 +36,7 @@ #include "CSSValuePool.h" #include "SVGPathByteStream.h" #include "SVGPathUtilities.h" +#include #include namespace WebCore { diff --git a/Source/WebCore/css/CSSBorderImageSliceValue.cpp b/Source/WebCore/css/CSSBorderImageSliceValue.cpp index defdcb8799b4a..f959e42c3d6d2 100644 --- a/Source/WebCore/css/CSSBorderImageSliceValue.cpp +++ b/Source/WebCore/css/CSSBorderImageSliceValue.cpp @@ -26,6 +26,7 @@ #include "config.h" #include "CSSBorderImageSliceValue.h" +#include #include namespace WebCore { diff --git a/Source/WebCore/css/CSSCanvasValue.cpp b/Source/WebCore/css/CSSCanvasValue.cpp index c0f84be7d7ff9..d0eb9bebf27d8 100644 --- a/Source/WebCore/css/CSSCanvasValue.cpp +++ b/Source/WebCore/css/CSSCanvasValue.cpp @@ -27,6 +27,7 @@ #include "CSSCanvasValue.h" #include "StyleCanvasImage.h" +#include namespace WebCore { diff --git a/Source/WebCore/css/CSSContentDistributionValue.cpp b/Source/WebCore/css/CSSContentDistributionValue.cpp index ea49bd7b7ca5b..7b364d393c94e 100644 --- a/Source/WebCore/css/CSSContentDistributionValue.cpp +++ b/Source/WebCore/css/CSSContentDistributionValue.cpp @@ -27,6 +27,7 @@ #include "CSSContentDistributionValue.h" #include "CSSValueKeywords.h" +#include #include namespace WebCore { diff --git a/Source/WebCore/css/CSSCounterStyle.cpp b/Source/WebCore/css/CSSCounterStyle.cpp index bb93d6b9feeef..8f55cb05eb1cd 100644 --- a/Source/WebCore/css/CSSCounterStyle.cpp +++ b/Source/WebCore/css/CSSCounterStyle.cpp @@ -30,6 +30,7 @@ #include "CSSCounterStyleRegistry.h" #include #include +#include #include #include #include diff --git a/Source/WebCore/css/CSSCounterStyleDescriptors.cpp b/Source/WebCore/css/CSSCounterStyleDescriptors.cpp index 6619a51638675..0d561f4aa980e 100644 --- a/Source/WebCore/css/CSSCounterStyleDescriptors.cpp +++ b/Source/WebCore/css/CSSCounterStyleDescriptors.cpp @@ -31,9 +31,9 @@ #include "CSSPrimitiveValue.h" #include "CSSValueList.h" #include "CSSValuePair.h" -#include - #include +#include +#include namespace WebCore { diff --git a/Source/WebCore/css/CSSCounterStyleRule.cpp b/Source/WebCore/css/CSSCounterStyleRule.cpp index eea1418557dc5..6bd5a0fd8b473 100644 --- a/Source/WebCore/css/CSSCounterStyleRule.cpp +++ b/Source/WebCore/css/CSSCounterStyleRule.cpp @@ -34,6 +34,7 @@ #include "MutableStyleProperties.h" #include "StyleProperties.h" #include "StylePropertiesInlines.h" +#include #include namespace WebCore { diff --git a/Source/WebCore/css/CSSCounterValue.cpp b/Source/WebCore/css/CSSCounterValue.cpp index 3a0d9744ccd59..e9a31e1851fcb 100644 --- a/Source/WebCore/css/CSSCounterValue.cpp +++ b/Source/WebCore/css/CSSCounterValue.cpp @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2023 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,6 +29,7 @@ #include "CSSMarkup.h" #include "CSSPrimitiveValue.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/css/CSSCounterValue.h b/Source/WebCore/css/CSSCounterValue.h index 95121851ca79f..acbf6241b5972 100644 --- a/Source/WebCore/css/CSSCounterValue.h +++ b/Source/WebCore/css/CSSCounterValue.h @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2023 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/Source/WebCore/css/CSSCrossfadeValue.cpp b/Source/WebCore/css/CSSCrossfadeValue.cpp index 5a7dc13390bd9..16824775b56d1 100644 --- a/Source/WebCore/css/CSSCrossfadeValue.cpp +++ b/Source/WebCore/css/CSSCrossfadeValue.cpp @@ -29,6 +29,7 @@ #include "StyleBuilderState.h" #include "StyleCrossfadeImage.h" +#include namespace WebCore { diff --git a/Source/WebCore/css/CSSCursorImageValue.cpp b/Source/WebCore/css/CSSCursorImageValue.cpp index 41ee1461ff038..9e57471d0349c 100644 --- a/Source/WebCore/css/CSSCursorImageValue.cpp +++ b/Source/WebCore/css/CSSCursorImageValue.cpp @@ -31,6 +31,7 @@ #include "StyleBuilderState.h" #include "StyleCursorImage.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/css/CSSFilterImageValue.cpp b/Source/WebCore/css/CSSFilterImageValue.cpp index f35e451812c78..ccf767d71fcd6 100644 --- a/Source/WebCore/css/CSSFilterImageValue.cpp +++ b/Source/WebCore/css/CSSFilterImageValue.cpp @@ -30,6 +30,7 @@ #include "StyleBuilderState.h" #include "StyleFilterImage.h" +#include namespace WebCore { diff --git a/Source/WebCore/css/CSSFontFace.h b/Source/WebCore/css/CSSFontFace.h index 7ab696987f5e2..4366947016482 100644 --- a/Source/WebCore/css/CSSFontFace.h +++ b/Source/WebCore/css/CSSFontFace.h @@ -64,7 +64,7 @@ class ScriptExecutionContext; class StyleProperties; class StyleRuleFontFace; -enum class ExternalResourceDownloadPolicy; +enum class ExternalResourceDownloadPolicy : bool; DECLARE_ALLOCATOR_WITH_HEAP_IDENTIFIER(CSSFontFace); class CSSFontFace final : public RefCounted { diff --git a/Source/WebCore/css/CSSFontFaceRule.cpp b/Source/WebCore/css/CSSFontFaceRule.cpp index 10d4c30c6e542..a49fb049e1c90 100644 --- a/Source/WebCore/css/CSSFontFaceRule.cpp +++ b/Source/WebCore/css/CSSFontFaceRule.cpp @@ -26,6 +26,7 @@ #include "PropertySetCSSStyleDeclaration.h" #include "StyleProperties.h" #include "StyleRule.h" +#include #include namespace WebCore { diff --git a/Source/WebCore/css/CSSFontFaceSrcValue.cpp b/Source/WebCore/css/CSSFontFaceSrcValue.cpp index be359f9b1a0a9..abedd43e07523 100644 --- a/Source/WebCore/css/CSSFontFaceSrcValue.cpp +++ b/Source/WebCore/css/CSSFontFaceSrcValue.cpp @@ -35,6 +35,7 @@ #include "FontCustomPlatformData.h" #include "SVGFontFaceElement.h" #include "ScriptExecutionContext.h" +#include #include namespace WebCore { diff --git a/Source/WebCore/css/CSSFontPaletteValuesOverrideColorsValue.cpp b/Source/WebCore/css/CSSFontPaletteValuesOverrideColorsValue.cpp index 9e564891b976d..f2910b69fbb17 100644 --- a/Source/WebCore/css/CSSFontPaletteValuesOverrideColorsValue.cpp +++ b/Source/WebCore/css/CSSFontPaletteValuesOverrideColorsValue.cpp @@ -26,6 +26,8 @@ #include "config.h" #include "CSSFontPaletteValuesOverrideColorsValue.h" +#include + namespace WebCore { String CSSFontPaletteValuesOverrideColorsValue::customCSSText() const diff --git a/Source/WebCore/css/CSSFontPaletteValuesRule.cpp b/Source/WebCore/css/CSSFontPaletteValuesRule.cpp index 9779c36ed6ba3..c7da9df8626da 100644 --- a/Source/WebCore/css/CSSFontPaletteValuesRule.cpp +++ b/Source/WebCore/css/CSSFontPaletteValuesRule.cpp @@ -32,6 +32,7 @@ #include "PropertySetCSSStyleDeclaration.h" #include "StyleProperties.h" #include "StyleRule.h" +#include #include #include diff --git a/Source/WebCore/css/CSSFontSelector.cpp b/Source/WebCore/css/CSSFontSelector.cpp index d77c86b12c223..05328f983f645 100644 --- a/Source/WebCore/css/CSSFontSelector.cpp +++ b/Source/WebCore/css/CSSFontSelector.cpp @@ -382,12 +382,12 @@ FontRanges CSSFontSelector::fontRangesForFamily(const FontDescription& fontDescr bool resolveGenericFamilyFirst = familyName == m_fontFamilyNames.at(FamilyNamesIndex::StandardFamily); AtomString familyForLookup = familyName; - bool isGeneric = false; + auto isGenericFontFamily = IsGenericFontFamily::No; const FontDescription* fontDescriptionForLookup = &fontDescription; auto resolveAndAssignGenericFamily = [&] { if (auto genericFamilyOptional = resolveGenericFamily(fontDescription, familyName)) { familyForLookup = *genericFamilyOptional; - isGeneric = true; + isGenericFontFamily = IsGenericFontFamily::Yes; } }; @@ -401,7 +401,7 @@ FontRanges CSSFontSelector::fontRangesForFamily(const FontDescription& fontDescr if (face) { if (document && document->settings().webAPIStatisticsEnabled()) ResourceLoadObserver::shared().logFontLoad(*document, familyForLookup.string(), true); - return { face->fontRanges(*fontDescriptionForLookup, fontPaletteValues, fontFeatureValues), isGeneric }; + return { face->fontRanges(*fontDescriptionForLookup, fontPaletteValues, fontFeatureValues), isGenericFontFamily }; } if (!resolveGenericFamilyFirst) @@ -410,7 +410,7 @@ FontRanges CSSFontSelector::fontRangesForFamily(const FontDescription& fontDescr auto font = FontCache::forCurrentThread().fontForFamily(*fontDescriptionForLookup, familyForLookup, { { }, { }, fontPaletteValues, fontFeatureValues, 1.0 }); if (document && document->settings().webAPIStatisticsEnabled()) ResourceLoadObserver::shared().logFontLoad(*document, familyForLookup.string(), !!font); - return { FontRanges { WTFMove(font) }, isGeneric }; + return { FontRanges { WTFMove(font) }, isGenericFontFamily }; } void CSSFontSelector::clearFonts() diff --git a/Source/WebCore/css/CSSFontStyleWithAngleValue.cpp b/Source/WebCore/css/CSSFontStyleWithAngleValue.cpp index 5085aad436d71..e0d3b3469565a 100644 --- a/Source/WebCore/css/CSSFontStyleWithAngleValue.cpp +++ b/Source/WebCore/css/CSSFontStyleWithAngleValue.cpp @@ -26,6 +26,8 @@ #include "config.h" #include "CSSFontStyleWithAngleValue.h" +#include + namespace WebCore { CSSFontStyleWithAngleValue::CSSFontStyleWithAngleValue(Ref&& obliqueAngle) diff --git a/Source/WebCore/css/CSSFontVariationValue.cpp b/Source/WebCore/css/CSSFontVariationValue.cpp index 18f85740bda3f..9efbf55f33e3e 100644 --- a/Source/WebCore/css/CSSFontVariationValue.cpp +++ b/Source/WebCore/css/CSSFontVariationValue.cpp @@ -26,7 +26,7 @@ #include "config.h" #include "CSSFontVariationValue.h" -#include +#include namespace WebCore { diff --git a/Source/WebCore/css/CSSKeyframeRule.cpp b/Source/WebCore/css/CSSKeyframeRule.cpp index 26f0aee948a33..5d8a2a2401195 100644 --- a/Source/WebCore/css/CSSKeyframeRule.cpp +++ b/Source/WebCore/css/CSSKeyframeRule.cpp @@ -32,6 +32,7 @@ #include "PropertySetCSSStyleDeclaration.h" #include "StyleProperties.h" #include "StylePropertiesInlines.h" +#include #include namespace WebCore { diff --git a/Source/WebCore/css/CSSNamedImageValue.cpp b/Source/WebCore/css/CSSNamedImageValue.cpp index addad237946d5..9f614c0068612 100644 --- a/Source/WebCore/css/CSSNamedImageValue.cpp +++ b/Source/WebCore/css/CSSNamedImageValue.cpp @@ -27,6 +27,7 @@ #include "CSSNamedImageValue.h" #include "StyleNamedImage.h" +#include namespace WebCore { diff --git a/Source/WebCore/css/CSSPageRule.cpp b/Source/WebCore/css/CSSPageRule.cpp index 31926b9b8a669..84e9ee33837c9 100644 --- a/Source/WebCore/css/CSSPageRule.cpp +++ b/Source/WebCore/css/CSSPageRule.cpp @@ -30,6 +30,7 @@ #include "PropertySetCSSStyleDeclaration.h" #include "StyleProperties.h" #include "StyleRule.h" +#include namespace WebCore { diff --git a/Source/WebCore/css/CSSPaintCallback.idl b/Source/WebCore/css/CSSPaintCallback.idl index aa0fe619f064c..c43896b156ad5 100644 --- a/Source/WebCore/css/CSSPaintCallback.idl +++ b/Source/WebCore/css/CSSPaintCallback.idl @@ -25,5 +25,6 @@ [ EnabledBySetting=CSSPaintingAPIEnabled, - CallbackThisObject=any + CallbackThisObject=any, + IsStrongCallback ] callback CSSPaintCallback = undefined (PaintRenderingContext2D context, CSSPaintSize size, StylePropertyMapReadOnly styleMap, sequence arguments); diff --git a/Source/WebCore/css/CSSPaintImageValue.cpp b/Source/WebCore/css/CSSPaintImageValue.cpp index 7a41fea165654..c503f728f1188 100644 --- a/Source/WebCore/css/CSSPaintImageValue.cpp +++ b/Source/WebCore/css/CSSPaintImageValue.cpp @@ -29,6 +29,7 @@ #include "CSSVariableData.h" #include "StylePaintImage.h" +#include #include namespace WebCore { diff --git a/Source/WebCore/css/CSSPrimitiveValue.cpp b/Source/WebCore/css/CSSPrimitiveValue.cpp index 6b2204cc56e70..12f8393464c75 100644 --- a/Source/WebCore/css/CSSPrimitiveValue.cpp +++ b/Source/WebCore/css/CSSPrimitiveValue.cpp @@ -47,8 +47,8 @@ #include #include #include +#include #include -#include namespace WebCore { @@ -699,7 +699,7 @@ static double lengthOfViewportPhysicalAxisForLogicalAxis(LogicalBoxAxis logicalA if (!style) return 0; - switch (mapLogicalAxisToPhysicalAxis({ style->blockFlowDirection(), style->direction() }, logicalAxis)) { + switch (mapLogicalAxisToPhysicalAxis(makeTextFlow(style->writingMode(), style->direction()), logicalAxis)) { case BoxAxis::Horizontal: return size.width(); diff --git a/Source/WebCore/css/CSSProperties.json b/Source/WebCore/css/CSSProperties.json index 3acf191561484..c58d369d6a6d4 100644 --- a/Source/WebCore/css/CSSProperties.json +++ b/Source/WebCore/css/CSSProperties.json @@ -661,6 +661,7 @@ } }, "text-box-edge": { + "inherited": true, "values": [ "leading", "text", diff --git a/Source/WebCore/css/CSSPropertySourceData.cpp b/Source/WebCore/css/CSSPropertySourceData.cpp index 83d31a5e03690..01c549999d2d9 100644 --- a/Source/WebCore/css/CSSPropertySourceData.cpp +++ b/Source/WebCore/css/CSSPropertySourceData.cpp @@ -32,6 +32,7 @@ #include "config.h" #include "CSSPropertySourceData.h" +#include #include #include diff --git a/Source/WebCore/css/CSSRayValue.cpp b/Source/WebCore/css/CSSRayValue.cpp index 089bd9f9b90b6..29ba2f7396392 100644 --- a/Source/WebCore/css/CSSRayValue.cpp +++ b/Source/WebCore/css/CSSRayValue.cpp @@ -30,6 +30,7 @@ #include "CSSRayValue.h" #include "CSSPrimitiveValueMappings.h" +#include namespace WebCore { diff --git a/Source/WebCore/css/CSSReflectValue.cpp b/Source/WebCore/css/CSSReflectValue.cpp index 6049d4af7d2d3..9254ee7ac5730 100644 --- a/Source/WebCore/css/CSSReflectValue.cpp +++ b/Source/WebCore/css/CSSReflectValue.cpp @@ -26,6 +26,8 @@ #include "config.h" #include "CSSReflectValue.h" +#include + namespace WebCore { CSSReflectValue::CSSReflectValue(CSSValueID direction, Ref offset, RefPtr mask) diff --git a/Source/WebCore/css/CSSScrollValue.cpp b/Source/WebCore/css/CSSScrollValue.cpp index c3dadb8c3b6e5..82a8f6f1252bd 100644 --- a/Source/WebCore/css/CSSScrollValue.cpp +++ b/Source/WebCore/css/CSSScrollValue.cpp @@ -30,6 +30,7 @@ #include "CSSScrollValue.h" #include "CSSPrimitiveValueMappings.h" +#include namespace WebCore { diff --git a/Source/WebCore/css/CSSSelector.cpp b/Source/WebCore/css/CSSSelector.cpp index fbc05d8e3c194..3e56f92819c15 100644 --- a/Source/WebCore/css/CSSSelector.cpp +++ b/Source/WebCore/css/CSSSelector.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -659,7 +660,7 @@ String CSSSelector::selectorText(StringView separator, StringView rightSide) con return previousSelector->selectorText(separator, builder); } else if (auto separatorText = separatorTextForNestingRelative(); !separatorText.isNull()) { // We have a separator but no tag history which can happen with implicit relative nesting selector - return separatorText + builder.toString(); + return makeString(separatorText, builder.toString()); } return builder.toString(); diff --git a/Source/WebCore/css/CSSStyleSheet.cpp b/Source/WebCore/css/CSSStyleSheet.cpp index ce37eb1f8151c..fa9954043c151 100644 --- a/Source/WebCore/css/CSSStyleSheet.cpp +++ b/Source/WebCore/css/CSSStyleSheet.cpp @@ -44,8 +44,8 @@ #include "StyleScope.h" #include "StyleSheetContents.h" #include "StyleSheetContentsCache.h" - #include +#include #include namespace WebCore { diff --git a/Source/WebCore/css/CSSTimingFunctionValue.cpp b/Source/WebCore/css/CSSTimingFunctionValue.cpp index c4dc6d8dcb439..3aeb943fcf5b0 100644 --- a/Source/WebCore/css/CSSTimingFunctionValue.cpp +++ b/Source/WebCore/css/CSSTimingFunctionValue.cpp @@ -26,8 +26,8 @@ #include "config.h" #include "CSSTimingFunctionValue.h" +#include #include -#include namespace WebCore { diff --git a/Source/WebCore/css/CSSUnicodeRangeValue.cpp b/Source/WebCore/css/CSSUnicodeRangeValue.cpp index ad596b8d3efc8..ba4270aa4349c 100644 --- a/Source/WebCore/css/CSSUnicodeRangeValue.cpp +++ b/Source/WebCore/css/CSSUnicodeRangeValue.cpp @@ -27,6 +27,7 @@ #include "CSSUnicodeRangeValue.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/css/CSSValuePair.cpp b/Source/WebCore/css/CSSValuePair.cpp index 87fe61625b07f..94d608683049f 100644 --- a/Source/WebCore/css/CSSValuePair.cpp +++ b/Source/WebCore/css/CSSValuePair.cpp @@ -27,6 +27,7 @@ #include "CSSValuePair.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/css/CSSValuePool.cpp b/Source/WebCore/css/CSSValuePool.cpp index 372e9de6f0517..eb69633fb9e89 100644 --- a/Source/WebCore/css/CSSValuePool.cpp +++ b/Source/WebCore/css/CSSValuePool.cpp @@ -30,7 +30,6 @@ #include "CSSPrimitiveValueMappings.h" #include "CSSValueKeywords.h" #include "CSSValueList.h" -#include namespace WebCore { DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(CSSValuePool); diff --git a/Source/WebCore/css/CSSViewValue.cpp b/Source/WebCore/css/CSSViewValue.cpp index f25d6df1e843e..59f14729ff0d7 100644 --- a/Source/WebCore/css/CSSViewValue.cpp +++ b/Source/WebCore/css/CSSViewValue.cpp @@ -30,6 +30,7 @@ #include "CSSViewValue.h" #include "CSSPrimitiveValueMappings.h" +#include namespace WebCore { diff --git a/Source/WebCore/css/DOMMatrixReadOnly.cpp b/Source/WebCore/css/DOMMatrixReadOnly.cpp index 876c01bf07d11..6a3e366a0c021 100644 --- a/Source/WebCore/css/DOMMatrixReadOnly.cpp +++ b/Source/WebCore/css/DOMMatrixReadOnly.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/css/Quad.h b/Source/WebCore/css/Quad.h index 163acd4899b19..0874846fec6a0 100644 --- a/Source/WebCore/css/Quad.h +++ b/Source/WebCore/css/Quad.h @@ -21,7 +21,7 @@ #pragma once #include "RectBase.h" -#include +#include namespace WebCore { diff --git a/Source/WebCore/css/Rect.h b/Source/WebCore/css/Rect.h index 5f576a536a69c..13b1487e77110 100644 --- a/Source/WebCore/css/Rect.h +++ b/Source/WebCore/css/Rect.h @@ -21,6 +21,7 @@ #pragma once #include "RectBase.h" +#include namespace WebCore { diff --git a/Source/WebCore/css/ShorthandSerializer.cpp b/Source/WebCore/css/ShorthandSerializer.cpp index 4820bab2de4ce..c6c50031de81c 100644 --- a/Source/WebCore/css/ShorthandSerializer.cpp +++ b/Source/WebCore/css/ShorthandSerializer.cpp @@ -40,6 +40,7 @@ #include "Quad.h" #include "StylePropertiesInlines.h" #include "StylePropertyShorthand.h" +#include namespace WebCore { diff --git a/Source/WebCore/css/StyleMedia.cpp b/Source/WebCore/css/StyleMedia.cpp index 46a5cc0939173..45b55a3f3d5bb 100644 --- a/Source/WebCore/css/StyleMedia.cpp +++ b/Source/WebCore/css/StyleMedia.cpp @@ -35,7 +35,7 @@ StyleMedia::StyleMedia(LocalDOMWindow& window) { if (window.document()) { window.document()->addConsoleMessage(makeUnique(MessageSource::JS, MessageType::Log, MessageLevel::Warning, - "window.styleMedia is deprecated draft version of window.matchMedia API that is not implemented in Firefox and will be removed from the web platform in future."_s)); + "window.styleMedia is a deprecated draft version of window.matchMedia API, and it will be removed in the future."_s)); } } diff --git a/Source/WebCore/css/StyleProperties.cpp b/Source/WebCore/css/StyleProperties.cpp index b4d371e97e5fc..f11bd466f43a6 100644 --- a/Source/WebCore/css/StyleProperties.cpp +++ b/Source/WebCore/css/StyleProperties.cpp @@ -37,6 +37,7 @@ #include "StylePropertiesInlines.h" #include "StylePropertyShorthand.h" #include +#include #include #ifndef NDEBUG diff --git a/Source/WebCore/css/StyleRule.cpp b/Source/WebCore/css/StyleRule.cpp index 75d618e7c9852..ff027adcc8c91 100644 --- a/Source/WebCore/css/StyleRule.cpp +++ b/Source/WebCore/css/StyleRule.cpp @@ -323,6 +323,13 @@ Vector> StyleRule::splitIntoMultipleRulesWithMaximumSelectorCompo return rules; } +String StyleRule::debugDescription() const +{ + StringBuilder builder; + builder.append("StyleRule ["_s, m_properties->asText(), ']'); + return builder.toString(); +} + StyleRuleWithNesting::~StyleRuleWithNesting() = default; Ref StyleRuleWithNesting::copy() const @@ -330,6 +337,16 @@ Ref StyleRuleWithNesting::copy() const return adoptRef(*new StyleRuleWithNesting(*this)); } +String StyleRuleWithNesting::debugDescription() const +{ + StringBuilder builder; + builder.append("StyleRuleWithNesting ["_s, properties().asText(), " "_s); + for (const auto& rule : m_nestedRules) + builder.append(rule->debugDescription()); + builder.append(']'); + return builder.toString(); +} + StyleRuleWithNesting::StyleRuleWithNesting(const StyleRuleWithNesting& other) : StyleRule(other) , m_nestedRules(other.m_nestedRules.map([](auto& rule) { return rule->copy(); })) @@ -477,6 +494,16 @@ void StyleRuleGroup::wrapperRemoveRule(unsigned index) m_childRules.remove(index); } +String StyleRuleGroup::debugDescription() const +{ + StringBuilder builder; + builder.append("StyleRuleGroup ["_s); + for (const auto& rule : m_childRules) + builder.append(rule->debugDescription()); + builder.append(']'); + return builder.toString(); +} + StyleRuleMedia::StyleRuleMedia(MQ::MediaQueryList&& mediaQueries, Vector>&& rules) : StyleRuleGroup(StyleRuleType::Media, WTFMove(rules)) , m_mediaQueries(WTFMove(mediaQueries)) @@ -499,6 +526,13 @@ Ref StyleRuleMedia::copy() const return adoptRef(*new StyleRuleMedia(*this)); } +String StyleRuleMedia::debugDescription() const +{ + StringBuilder builder; + builder.append("StyleRuleMedia ["_s, StyleRuleGroup::debugDescription(), ']'); + return builder.toString(); +} + StyleRuleSupports::StyleRuleSupports(const String& conditionText, bool conditionIsSupported, Vector>&& rules) : StyleRuleGroup(StyleRuleType::Supports, WTFMove(rules)) , m_conditionText(conditionText) @@ -613,4 +647,20 @@ Ref StyleRuleNamespace::create(const AtomString& prefix, con return adoptRef(*new StyleRuleNamespace(prefix, uri)); } +String StyleRuleBase::debugDescription() const +{ + return visitDerived([] (const RuleType& rule) -> String { + // FIXME: implement debugDescription() for all classes which inherit StyleRuleBase. + if constexpr (std::is_same_v) + return "StyleRuleBase"_s; + return rule.debugDescription(); + }); +} + +WTF::TextStream& operator<<(WTF::TextStream& ts, const StyleRuleBase& rule) +{ + ts << rule.debugDescription(); + return ts; +} + } // namespace WebCore diff --git a/Source/WebCore/css/StyleRule.h b/Source/WebCore/css/StyleRule.h index e23cf38c649b5..39aecbd8b3426 100644 --- a/Source/WebCore/css/StyleRule.h +++ b/Source/WebCore/css/StyleRule.h @@ -36,6 +36,7 @@ #include #include #include +#include namespace WebCore { @@ -89,6 +90,8 @@ class StyleRuleBase : public RefCounted { WEBCORE_EXPORT void operator delete(StyleRuleBase*, std::destroying_delete_t); + String debugDescription() const; + protected: explicit StyleRuleBase(StyleRuleType, bool hasDocumentSecurityOrigin = false); StyleRuleBase(const StyleRuleBase&); @@ -140,6 +143,7 @@ class StyleRule : public StyleRuleBase { static unsigned averageSizeInBytes(); void setProperties(Ref&&); + String debugDescription() const; protected: StyleRule(Ref&&, bool hasDocumentSecurityOrigin, CSSSelectorList&&); StyleRule(const StyleRule&); @@ -171,6 +175,7 @@ class StyleRuleWithNesting final : public StyleRule { const CSSSelectorList& originalSelectorList() const { return m_originalSelectorList; } void wrapperAdoptOriginalSelectorList(CSSSelectorList&&); + String debugDescription() const; protected: StyleRuleWithNesting(const StyleRuleWithNesting&); @@ -294,6 +299,7 @@ class StyleRuleGroup : public StyleRuleBase { friend class CSSGroupingRule; friend class CSSStyleSheet; + String debugDescription() const; protected: StyleRuleGroup(StyleRuleType, Vector>&&); StyleRuleGroup(const StyleRuleGroup&); @@ -310,6 +316,7 @@ class StyleRuleMedia final : public StyleRuleGroup { const MQ::MediaQueryList& mediaQueries() const { return m_mediaQueries; } void setMediaQueries(MQ::MediaQueryList&& queries) { m_mediaQueries = WTFMove(queries); } + String debugDescription() const; private: StyleRuleMedia(MQ::MediaQueryList&&, Vector>&&); StyleRuleMedia(const StyleRuleMedia&); @@ -496,6 +503,8 @@ inline CompiledSelector& StyleRule::compiledSelectorForListIndex(unsigned index) #endif +WTF::TextStream& operator<<(WTF::TextStream&, const StyleRuleBase&); + } // namespace WebCore SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::StyleRule) diff --git a/Source/WebCore/css/StyleSheetContents.cpp b/Source/WebCore/css/StyleSheetContents.cpp index 7127c5d72aaa9..019b23b93089b 100644 --- a/Source/WebCore/css/StyleSheetContents.cpp +++ b/Source/WebCore/css/StyleSheetContents.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #if ENABLE(CONTENT_EXTENSIONS) #include "ContentRuleListResults.h" diff --git a/Source/WebCore/css/TransformFunctions.cpp b/Source/WebCore/css/TransformFunctions.cpp index 40fa0015bee91..a252cb953acd8 100644 --- a/Source/WebCore/css/TransformFunctions.cpp +++ b/Source/WebCore/css/TransformFunctions.cpp @@ -44,7 +44,6 @@ #include "ScaleTransformOperation.h" #include "SkewTransformOperation.h" #include "TranslateTransformOperation.h" -#include namespace WebCore { diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Color.cpp b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Color.cpp index 55b9db51feca4..581bf0f4ed25e 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Color.cpp +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Color.cpp @@ -54,6 +54,7 @@ #include "Color.h" #include "ColorLuminance.h" #include +#include namespace WebCore { namespace CSSPropertyParserHelpers { diff --git a/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp b/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp index a1fa9e8487182..f203f774c9ad3 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp +++ b/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp @@ -108,7 +108,6 @@ #include "TimingFunction.h" #include "WebKitFontFamilyNames.h" #include -#include #include namespace WebCore { diff --git a/Source/WebCore/css/process-css-properties.py b/Source/WebCore/css/process-css-properties.py index 1719ecd366be4..aa4353c82df6a 100755 --- a/Source/WebCore/css/process-css-properties.py +++ b/Source/WebCore/css/process-css-properties.py @@ -2614,7 +2614,7 @@ def _generate_physical_logical_conversion_function(self, *, to, signature, sourc to.write(f"{signature}") to.write(f"{{") with to.indent(): - to.write(f"TextFlow flow {{ writingModeToBlockFlowDirection(writingMode), direction }};") + to.write(f"auto textflow = makeTextFlow(writingMode, direction);") to.write(f"switch (id) {{") for group_name, property_group in sorted(self.properties_and_descriptors.style_properties.logical_property_groups.items(), key=lambda x: x[0]): @@ -2631,7 +2631,7 @@ def _generate_physical_logical_conversion_function(self, *, to, signature, sourc to.write(f"case {property.id}: {{") with to.indent(): to.write(f"static constexpr CSSPropertyID properties[{len(properties)}] = {{ {', '.join(properties)} }};") - to.write(f"return properties[static_cast(map{source_as_id}{kind_as_id}To{destination_as_id}{kind_as_id}(flow, {resolver_enum}))];") + to.write(f"return properties[static_cast(map{source_as_id}{kind_as_id}To{destination_as_id}{kind_as_id}(textflow, {resolver_enum}))];") to.write(f"}}") to.write(f"default:") diff --git a/Source/WebCore/css/query/GenericMediaQueryParser.cpp b/Source/WebCore/css/query/GenericMediaQueryParser.cpp index e277b6202cecc..04569108919d4 100644 --- a/Source/WebCore/css/query/GenericMediaQueryParser.cpp +++ b/Source/WebCore/css/query/GenericMediaQueryParser.cpp @@ -38,6 +38,7 @@ #include "CSSValue.h" #include "CSSVariableParser.h" #include "MediaQueryParserContext.h" +#include namespace WebCore { namespace MQ { @@ -89,9 +90,9 @@ std::optional FeatureParser::consumeBooleanOrPlainFeature(CSSParserToke if (name.startsWith("max-"_s)) return { StringView(name).substring(4).toAtomString(), ComparisonOperator::LessThanOrEqual }; if (name.startsWith("-webkit-min-"_s)) - return { "-webkit-"_s + StringView(name).substring(12), ComparisonOperator::GreaterThanOrEqual }; + return { makeAtomString("-webkit-"_s, StringView(name).substring(12)), ComparisonOperator::GreaterThanOrEqual }; if (name.startsWith("-webkit-max-"_s)) - return { "-webkit-"_s + StringView(name).substring(12), ComparisonOperator::LessThanOrEqual }; + return { makeAtomString("-webkit-"_s, StringView(name).substring(12)), ComparisonOperator::LessThanOrEqual }; return { name, ComparisonOperator::Equal }; }; diff --git a/Source/WebCore/css/typedom/CSSStyleImageValue.h b/Source/WebCore/css/typedom/CSSStyleImageValue.h index 47ab9b4b154dc..8736f20cf87a3 100644 --- a/Source/WebCore/css/typedom/CSSStyleImageValue.h +++ b/Source/WebCore/css/typedom/CSSStyleImageValue.h @@ -29,7 +29,6 @@ #include "CSSStyleValue.h" #include #include -#include #include namespace WebCore { diff --git a/Source/WebCore/css/typedom/CSSStyleValue.cpp b/Source/WebCore/css/typedom/CSSStyleValue.cpp index 1ddfca14427a0..bff17e2f52a2a 100644 --- a/Source/WebCore/css/typedom/CSSStyleValue.cpp +++ b/Source/WebCore/css/typedom/CSSStyleValue.cpp @@ -35,6 +35,7 @@ #include "CSSStyleValueFactory.h" #include "CSSUnitValue.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/css/typedom/CSSStyleValueFactory.cpp b/Source/WebCore/css/typedom/CSSStyleValueFactory.cpp index 2ffe75d280d65..55075eb063547 100644 --- a/Source/WebCore/css/typedom/CSSStyleValueFactory.cpp +++ b/Source/WebCore/css/typedom/CSSStyleValueFactory.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include diff --git a/Source/WebCore/css/typedom/CSSUnitValue.h b/Source/WebCore/css/typedom/CSSUnitValue.h index 8f87793855144..86de96707b31a 100644 --- a/Source/WebCore/css/typedom/CSSUnitValue.h +++ b/Source/WebCore/css/typedom/CSSUnitValue.h @@ -27,7 +27,6 @@ #include "CSSNumericValue.h" #include -#include #include namespace WebCore { diff --git a/Source/WebCore/css/typedom/CSSUnparsedValue.cpp b/Source/WebCore/css/typedom/CSSUnparsedValue.cpp index a4610e0ec0dfc..a00e90c4d036d 100644 --- a/Source/WebCore/css/typedom/CSSUnparsedValue.cpp +++ b/Source/WebCore/css/typedom/CSSUnparsedValue.cpp @@ -38,6 +38,7 @@ #include "ExceptionOr.h" #include #include +#include #include #include #include diff --git a/Source/WebCore/css/typedom/MainThreadStylePropertyMapReadOnly.cpp b/Source/WebCore/css/typedom/MainThreadStylePropertyMapReadOnly.cpp index 3d999ae79215c..adb5b649de896 100644 --- a/Source/WebCore/css/typedom/MainThreadStylePropertyMapReadOnly.cpp +++ b/Source/WebCore/css/typedom/MainThreadStylePropertyMapReadOnly.cpp @@ -38,6 +38,7 @@ #include "Document.h" #include "PaintWorkletGlobalScope.h" #include "StylePropertyShorthand.h" +#include namespace WebCore { diff --git a/Source/WebCore/css/typedom/StylePropertyMap.cpp b/Source/WebCore/css/typedom/StylePropertyMap.cpp index 9c6a499cd3b1d..e7ebc4b138557 100644 --- a/Source/WebCore/css/typedom/StylePropertyMap.cpp +++ b/Source/WebCore/css/typedom/StylePropertyMap.cpp @@ -36,6 +36,7 @@ #include "Document.h" #include "StylePropertyShorthand.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/css/typedom/numeric/CSSNumericType.cpp b/Source/WebCore/css/typedom/numeric/CSSNumericType.cpp index 1d290d5a02eab..7b99e28eb15ef 100644 --- a/Source/WebCore/css/typedom/numeric/CSSNumericType.cpp +++ b/Source/WebCore/css/typedom/numeric/CSSNumericType.cpp @@ -25,8 +25,10 @@ #include "config.h" #include "CSSNumericType.h" + #include "CSSNumericValue.h" #include "CSSUnits.h" +#include namespace WebCore { diff --git a/Source/WebCore/css/typedom/numeric/CSSNumericType.h b/Source/WebCore/css/typedom/numeric/CSSNumericType.h index 604b3708df8bb..f02d65de9ae8e 100644 --- a/Source/WebCore/css/typedom/numeric/CSSNumericType.h +++ b/Source/WebCore/css/typedom/numeric/CSSNumericType.h @@ -28,7 +28,6 @@ #include "CSSNumericBaseType.h" #include #include -#include namespace WebCore { diff --git a/Source/WebCore/css/typedom/transform/CSSMatrixComponent.cpp b/Source/WebCore/css/typedom/transform/CSSMatrixComponent.cpp index 5efa1b01367e3..af5e54cad3aff 100644 --- a/Source/WebCore/css/typedom/transform/CSSMatrixComponent.cpp +++ b/Source/WebCore/css/typedom/transform/CSSMatrixComponent.cpp @@ -39,7 +39,7 @@ #include "DOMMatrixInit.h" #include "ExceptionOr.h" #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/css/typedom/transform/CSSTransformValue.cpp b/Source/WebCore/css/typedom/transform/CSSTransformValue.cpp index f6a7db2ca32f3..3d2d4bb2a2cae 100644 --- a/Source/WebCore/css/typedom/transform/CSSTransformValue.cpp +++ b/Source/WebCore/css/typedom/transform/CSSTransformValue.cpp @@ -46,6 +46,7 @@ #include "ExceptionOr.h" #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/dom/AbortAlgorithm.idl b/Source/WebCore/dom/AbortAlgorithm.idl index ece75bc513963..0ae52458a28c4 100644 --- a/Source/WebCore/dom/AbortAlgorithm.idl +++ b/Source/WebCore/dom/AbortAlgorithm.idl @@ -23,4 +23,4 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -callback AbortAlgorithm = undefined (any value); +[ IsStrongCallback ] callback AbortAlgorithm = undefined (any value); diff --git a/Source/WebCore/dom/CharacterData.cpp b/Source/WebCore/dom/CharacterData.cpp index 68e91c48d1dcf..5e3b9d39f9132 100644 --- a/Source/WebCore/dom/CharacterData.cpp +++ b/Source/WebCore/dom/CharacterData.cpp @@ -37,6 +37,7 @@ #include "StyleInheritedData.h" #include #include +#include namespace WebCore { @@ -116,7 +117,7 @@ void CharacterData::parserAppendData(StringView string) void CharacterData::appendData(const String& data) { - setDataAndUpdate(m_data + data, m_data.length(), 0, data.length(), UpdateLiveRanges::No); + setDataAndUpdate(makeString(m_data, data), m_data.length(), 0, data.length(), UpdateLiveRanges::No); } ExceptionOr CharacterData::insertData(unsigned offset, const String& data) diff --git a/Source/WebCore/dom/CreateHTMLCallback.idl b/Source/WebCore/dom/CreateHTMLCallback.idl index 58a9bfffb78d7..c203d718e2556 100644 --- a/Source/WebCore/dom/CreateHTMLCallback.idl +++ b/Source/WebCore/dom/CreateHTMLCallback.idl @@ -23,7 +23,4 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -[ - RethrowException, - IsWeakCallback -] callback CreateHTMLCallback = DOMString? (DOMString input, any... arguments); +[ RethrowException ] callback CreateHTMLCallback = DOMString? (DOMString input, any... arguments); diff --git a/Source/WebCore/dom/CreateScriptCallback.idl b/Source/WebCore/dom/CreateScriptCallback.idl index deda8fbf36a67..5aee70e967569 100644 --- a/Source/WebCore/dom/CreateScriptCallback.idl +++ b/Source/WebCore/dom/CreateScriptCallback.idl @@ -23,7 +23,4 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -[ - RethrowException, - IsWeakCallback -] callback CreateScriptCallback = DOMString? (DOMString input, any... arguments); +[ RethrowException ] callback CreateScriptCallback = DOMString? (DOMString input, any... arguments); diff --git a/Source/WebCore/dom/CreateScriptURLCallback.idl b/Source/WebCore/dom/CreateScriptURLCallback.idl index f3e9d902d0b78..704fb96fa404b 100644 --- a/Source/WebCore/dom/CreateScriptURLCallback.idl +++ b/Source/WebCore/dom/CreateScriptURLCallback.idl @@ -23,7 +23,4 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -[ - RethrowException, - IsWeakCallback -] callback CreateScriptURLCallback = USVString? (DOMString input, any... arguments); +[ RethrowException ] callback CreateScriptURLCallback = USVString? (DOMString input, any... arguments); diff --git a/Source/WebCore/dom/DeviceMotionEvent.cpp b/Source/WebCore/dom/DeviceMotionEvent.cpp index e306e149a7876..5f5e1d773e400 100644 --- a/Source/WebCore/dom/DeviceMotionEvent.cpp +++ b/Source/WebCore/dom/DeviceMotionEvent.cpp @@ -30,6 +30,7 @@ #include "DeviceOrientationAndMotionAccessController.h" #include "JSDOMPromiseDeferred.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/dom/DeviceOrientationEvent.cpp b/Source/WebCore/dom/DeviceOrientationEvent.cpp index 8c63ff52e239b..c673f2da23710 100644 --- a/Source/WebCore/dom/DeviceOrientationEvent.cpp +++ b/Source/WebCore/dom/DeviceOrientationEvent.cpp @@ -32,6 +32,7 @@ #include "JSDOMPromiseDeferred.h" #include "LocalDOMWindow.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/dom/Document.cpp b/Source/WebCore/dom/Document.cpp index ebd5b98b6e15a..0a333b3319cb1 100644 --- a/Source/WebCore/dom/Document.cpp +++ b/Source/WebCore/dom/Document.cpp @@ -314,6 +314,7 @@ #include #include #include +#include #include #include @@ -1824,7 +1825,7 @@ void Document::setReadyState(ReadyState readyState) eventTiming->domLoading = now; // We do this here instead of in the Document constructor because monotonicTimestamp() is 0 when the Document constructor is running. if (!url().isEmpty()) - WTFBeginSignpostWithTimeDelta(this, NavigationAndPaintTiming, -Seconds(monotonicTimestamp()), "Loading %{public}s | isMainFrame: %d", url().string().utf8().data(), frame() && frame()->isMainFrame()); + WTFBeginSignpostWithTimeDelta(this, NavigationAndPaintTiming, -Seconds(monotonicTimestamp()), "Loading %" PUBLIC_LOG_STRING " | isMainFrame: %d", url().string().utf8().data(), frame() && frame()->isMainFrame()); WTFEmitSignpost(this, NavigationAndPaintTiming, "domLoading"); } break; diff --git a/Source/WebCore/dom/Document.h b/Source/WebCore/dom/Document.h index d4f7174477856..b5196fe49fac1 100644 --- a/Source/WebCore/dom/Document.h +++ b/Source/WebCore/dom/Document.h @@ -1802,7 +1802,7 @@ class Document WEBCORE_EXPORT void navigateFromServiceWorker(const URL&, CompletionHandler&&); #if ENABLE(VIDEO) - void forEachMediaElement(const Function&); + WEBCORE_EXPORT void forEachMediaElement(const Function&); #endif #if ENABLE(IOS_TOUCH_EVENTS) diff --git a/Source/WebCore/dom/DocumentMarker.h b/Source/WebCore/dom/DocumentMarker.h index c2dd1c947b3f3..84aefe745ac9e 100644 --- a/Source/WebCore/dom/DocumentMarker.h +++ b/Source/WebCore/dom/DocumentMarker.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #if PLATFORM(IOS_FAMILY) diff --git a/Source/WebCore/dom/Element.cpp b/Source/WebCore/dom/Element.cpp index 704952be016dd..7f89e600e8c25 100644 --- a/Source/WebCore/dom/Element.cpp +++ b/Source/WebCore/dom/Element.cpp @@ -161,6 +161,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/dom/Event.cpp b/Source/WebCore/dom/Event.cpp index b8eb2b5598454..4aca9ab28a788 100644 --- a/Source/WebCore/dom/Event.cpp +++ b/Source/WebCore/dom/Event.cpp @@ -34,6 +34,7 @@ #include "WorkerGlobalScope.h" #include #include +#include #include #include diff --git a/Source/WebCore/dom/FragmentDirectiveGenerator.cpp b/Source/WebCore/dom/FragmentDirectiveGenerator.cpp index c4aa66828a86a..989a66a0fb078 100644 --- a/Source/WebCore/dom/FragmentDirectiveGenerator.cpp +++ b/Source/WebCore/dom/FragmentDirectiveGenerator.cpp @@ -35,9 +35,9 @@ #include #include #include +#include #include - namespace WebCore { constexpr int maxCharacters = 300; diff --git a/Source/WebCore/dom/IdleRequestCallback.idl b/Source/WebCore/dom/IdleRequestCallback.idl index c5863cabf0cf6..ad73def9ddab4 100644 --- a/Source/WebCore/dom/IdleRequestCallback.idl +++ b/Source/WebCore/dom/IdleRequestCallback.idl @@ -23,4 +23,4 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -callback IdleRequestCallback = undefined (IdleDeadline deadline); +[ IsStrongCallback ] callback IdleRequestCallback = undefined (IdleDeadline deadline); diff --git a/Source/WebCore/dom/ImageOverlay.cpp b/Source/WebCore/dom/ImageOverlay.cpp index e103f72aea924..45e0725cb592d 100644 --- a/Source/WebCore/dom/ImageOverlay.cpp +++ b/Source/WebCore/dom/ImageOverlay.cpp @@ -64,6 +64,7 @@ #include #include #include +#include #if ENABLE(DATA_DETECTION) #include "DataDetection.h" diff --git a/Source/WebCore/dom/LoadableClassicScript.cpp b/Source/WebCore/dom/LoadableClassicScript.cpp index a4622e0473bcf..da6017e220413 100644 --- a/Source/WebCore/dom/LoadableClassicScript.cpp +++ b/Source/WebCore/dom/LoadableClassicScript.cpp @@ -35,6 +35,7 @@ #include "ScriptSourceCode.h" #include "SubresourceIntegrity.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/dom/MessagePortIdentifier.h b/Source/WebCore/dom/MessagePortIdentifier.h index fe799783d4b5b..8fff45c00668f 100644 --- a/Source/WebCore/dom/MessagePortIdentifier.h +++ b/Source/WebCore/dom/MessagePortIdentifier.h @@ -28,7 +28,7 @@ #include "PortIdentifier.h" #include "ProcessIdentifier.h" #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/dom/MutationCallback.idl b/Source/WebCore/dom/MutationCallback.idl index ecbb83b4d9b6f..a12a2256add98 100644 --- a/Source/WebCore/dom/MutationCallback.idl +++ b/Source/WebCore/dom/MutationCallback.idl @@ -23,7 +23,4 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -[ - CallbackThisObject=MutationObserver, - IsWeakCallback, -] callback MutationCallback = undefined (sequence mutations, MutationObserver observer); +[ CallbackThisObject=MutationObserver ] callback MutationCallback = undefined (sequence mutations, MutationObserver observer); diff --git a/Source/WebCore/dom/Node.cpp b/Source/WebCore/dom/Node.cpp index 751bf3b428f21..b5eaa9ae8ea13 100644 --- a/Source/WebCore/dom/Node.cpp +++ b/Source/WebCore/dom/Node.cpp @@ -96,6 +96,7 @@ #include #include #include +#include #include #include diff --git a/Source/WebCore/dom/Node.h b/Source/WebCore/dom/Node.h index 4eb022a8b6368..db52e11ea6893 100644 --- a/Source/WebCore/dom/Node.h +++ b/Source/WebCore/dom/Node.h @@ -740,7 +740,7 @@ class Node : public EventTarget { void clearDescendantsNeedStyleResolution() { m_flags = (flags() - NodeStyleFlag::DescendantNeedsStyleResolution - NodeStyleFlag::DirectChildNeedsStyleResolution).toRaw(); } private: - uint16_t m_styleValidity : 2; + uint16_t m_styleValidity : 3; uint16_t m_flags : 13; }; diff --git a/Source/WebCore/dom/NodeFilter.idl b/Source/WebCore/dom/NodeFilter.idl index 2b39d6301902c..9505e3b54c2d7 100644 --- a/Source/WebCore/dom/NodeFilter.idl +++ b/Source/WebCore/dom/NodeFilter.idl @@ -18,10 +18,7 @@ * Boston, MA 02110-1301, USA. */ -[ - IsWeakCallback, - Exposed=Window -] callback interface NodeFilter { +[ Exposed=Window ] callback interface NodeFilter { // Constants for acceptNode(). const unsigned short FILTER_ACCEPT = 1; const unsigned short FILTER_REJECT = 2; diff --git a/Source/WebCore/dom/Position.cpp b/Source/WebCore/dom/Position.cpp index b03646f4f8211..1657418a32bf2 100644 --- a/Source/WebCore/dom/Position.cpp +++ b/Source/WebCore/dom/Position.cpp @@ -60,6 +60,7 @@ #include "VisibleUnits.h" #include #include +#include #include #include diff --git a/Source/WebCore/dom/QualifiedName.h b/Source/WebCore/dom/QualifiedName.h index 11c2bdd8221c0..755df782443cf 100644 --- a/Source/WebCore/dom/QualifiedName.h +++ b/Source/WebCore/dom/QualifiedName.h @@ -25,6 +25,7 @@ #include #include #include +#include namespace WebCore { @@ -160,7 +161,7 @@ inline String QualifiedName::toString() const if (!hasPrefix()) return localName(); - return prefix().string() + ':' + localName().string(); + return makeString(prefix().string(), ':', localName().string()); } inline AtomString QualifiedName::toAtomString() const diff --git a/Source/WebCore/dom/Range.cpp b/Source/WebCore/dom/Range.cpp index 28418f70214ae..f9699a6bb0225 100644 --- a/Source/WebCore/dom/Range.cpp +++ b/Source/WebCore/dom/Range.cpp @@ -55,6 +55,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/dom/RequestAnimationFrameCallback.idl b/Source/WebCore/dom/RequestAnimationFrameCallback.idl index 9c978ead36552..b63b306f0368a 100644 --- a/Source/WebCore/dom/RequestAnimationFrameCallback.idl +++ b/Source/WebCore/dom/RequestAnimationFrameCallback.idl @@ -31,4 +31,4 @@ // highResTime is passed as high resolution timestamp, see // http://www.w3.org/TR/hr-time/ for details. -callback RequestAnimationFrameCallback = undefined (unrestricted double highResTime); +[ IsStrongCallback ] callback RequestAnimationFrameCallback = undefined (unrestricted double highResTime); diff --git a/Source/WebCore/dom/ScriptElement.cpp b/Source/WebCore/dom/ScriptElement.cpp index d110ca782b56e..2535842d3a71a 100644 --- a/Source/WebCore/dom/ScriptElement.cpp +++ b/Source/WebCore/dom/ScriptElement.cpp @@ -62,6 +62,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/dom/ScriptExecutionContext.cpp b/Source/WebCore/dom/ScriptExecutionContext.cpp index 0cd97a86d611c..91330bb44ad37 100644 --- a/Source/WebCore/dom/ScriptExecutionContext.cpp +++ b/Source/WebCore/dom/ScriptExecutionContext.cpp @@ -85,6 +85,7 @@ #include #include #include +#include namespace WebCore { using namespace Inspector; diff --git a/Source/WebCore/dom/StringCallback.idl b/Source/WebCore/dom/StringCallback.idl index 1a61f75053228..3f328c6c5e7df 100644 --- a/Source/WebCore/dom/StringCallback.idl +++ b/Source/WebCore/dom/StringCallback.idl @@ -29,5 +29,6 @@ */ [ - ExportMacro=WEBCORE_EXPORT + ExportMacro=WEBCORE_EXPORT, + IsStrongCallback ] callback StringCallback = undefined (DOMString data); diff --git a/Source/WebCore/dom/StyledElement.cpp b/Source/WebCore/dom/StyledElement.cpp index 8b2dcfb51e886..cb095ab545cea 100644 --- a/Source/WebCore/dom/StyledElement.cpp +++ b/Source/WebCore/dom/StyledElement.cpp @@ -158,7 +158,7 @@ void StyledElement::styleAttributeChanged(const AtomString& newStyleString, Attr elementData()->setStyleAttributeIsDirty(false); - invalidateStyleInternal(); + Node::invalidateStyle(Style::Validity::InlineStyleInvalid); InspectorInstrumentation::didInvalidateStyleAttr(*this); } @@ -184,7 +184,8 @@ void StyledElement::invalidateStyleAttribute() } elementData()->setStyleAttributeIsDirty(true); - invalidateStyleInternal(); + + Node::invalidateStyle(Style::Validity::InlineStyleInvalid); // In the rare case of selectors like "[style] ~ div" we need to synchronize immediately to invalidate. if (styleResolver().ruleSets().hasComplexSelectorsForStyleAttribute()) { @@ -382,4 +383,9 @@ void StyledElement::addPropertyToPresentationalHintStyle(MutableStyleProperties& style.setProperty(propertyID, value, false, CSSParserContext(document())); } +void StyledElement::addPropertyToPresentationalHintStyle(MutableStyleProperties& style, CSSPropertyID propertyID, RefPtr&& value) +{ + style.setProperty(propertyID, WTFMove(value), false); +} + } diff --git a/Source/WebCore/dom/StyledElement.h b/Source/WebCore/dom/StyledElement.h index 7b6ce33558c37..ac76005297686 100644 --- a/Source/WebCore/dom/StyledElement.h +++ b/Source/WebCore/dom/StyledElement.h @@ -84,6 +84,7 @@ class StyledElement : public Element { void addPropertyToPresentationalHintStyle(MutableStyleProperties&, CSSPropertyID, CSSValueID identifier); void addPropertyToPresentationalHintStyle(MutableStyleProperties&, CSSPropertyID, double value, CSSUnitType); void addPropertyToPresentationalHintStyle(MutableStyleProperties&, CSSPropertyID, const String& value); + void addPropertyToPresentationalHintStyle(MutableStyleProperties&, CSSPropertyID, RefPtr&&); void addSubresourceAttributeURLs(ListHashSet&) const override; Attribute replaceURLsInAttributeValue(const Attribute&, const HashMap&) const override; diff --git a/Source/WebCore/dom/TextDecoder.cpp b/Source/WebCore/dom/TextDecoder.cpp index 1aa453e53ff0c..5d570d313f9a5 100644 --- a/Source/WebCore/dom/TextDecoder.cpp +++ b/Source/WebCore/dom/TextDecoder.cpp @@ -27,6 +27,7 @@ #include #include +#include namespace WebCore { diff --git a/Source/WebCore/dom/TrustedType.cpp b/Source/WebCore/dom/TrustedType.cpp index ee2d7b99df715..37c95fa097eb3 100644 --- a/Source/WebCore/dom/TrustedType.cpp +++ b/Source/WebCore/dom/TrustedType.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace WebCore { using namespace JSC; @@ -242,7 +243,7 @@ AttributeTypeAndSink trustedTypeForAttribute(const String& elementName, const St if (attributeNS.isNull() && !attributeName.isNull()) { auto& eventName = HTMLElement::eventNameForEventHandlerAttribute(attribute); if (!eventName.isNull()) { - returnValues.sink = "Element "_s + attributeName; + returnValues.sink = makeString("Element "_s, attributeName); returnValues.attributeType = trustedTypeToString(TrustedType::TrustedScript); return returnValues; } diff --git a/Source/WebCore/dom/TrustedTypePolicy.cpp b/Source/WebCore/dom/TrustedTypePolicy.cpp index ed563176e5ae5..73415520e7ff8 100644 --- a/Source/WebCore/dom/TrustedTypePolicy.cpp +++ b/Source/WebCore/dom/TrustedTypePolicy.cpp @@ -33,6 +33,7 @@ #include "TrustedTypePolicyOptions.h" #include "WebCoreOpaqueRoot.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/dom/TrustedTypePolicy.idl b/Source/WebCore/dom/TrustedTypePolicy.idl index 2dbef8ecc8f34..a471429f6acc3 100644 --- a/Source/WebCore/dom/TrustedTypePolicy.idl +++ b/Source/WebCore/dom/TrustedTypePolicy.idl @@ -27,6 +27,7 @@ EnabledBySetting=TrustedTypesEnabled, Exposed=(Window,Worker), JSCustomMarkFunction, + GenerateIsReachable=Impl, ] interface TrustedTypePolicy { readonly attribute DOMString name; diff --git a/Source/WebCore/dom/TrustedTypePolicyFactory.cpp b/Source/WebCore/dom/TrustedTypePolicyFactory.cpp index 9fc1a13cce074..796d88ad728d2 100644 --- a/Source/WebCore/dom/TrustedTypePolicyFactory.cpp +++ b/Source/WebCore/dom/TrustedTypePolicyFactory.cpp @@ -39,6 +39,7 @@ #include "TrustedTypePolicyOptions.h" #include "XLinkNames.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/dom/ViewTransition.cpp b/Source/WebCore/dom/ViewTransition.cpp index a2d82088bdf98..70aac3bf950d6 100644 --- a/Source/WebCore/dom/ViewTransition.cpp +++ b/Source/WebCore/dom/ViewTransition.cpp @@ -50,6 +50,7 @@ #include "StyleScope.h" #include "Styleable.h" #include "WebAnimation.h" +#include namespace WebCore { @@ -244,13 +245,16 @@ void ViewTransition::setupViewTransition() }); } -static AtomString effectiveViewTransitionName(RenderLayerModelObject& renderer) +static AtomString effectiveViewTransitionName(RenderLayerModelObject& renderer, Element& originatingElement, Style::Scope& documentScope) { if (renderer.isSkippedContent()) return nullAtom(); auto transitionName = renderer.style().viewTransitionName(); if (!transitionName) return nullAtom(); + auto scope = Style::Scope::forOrdinal(originatingElement, transitionName->scopeOrdinal); + if (!scope || scope != &documentScope) + return nullAtom(); return transitionName->name; } @@ -369,10 +373,10 @@ ExceptionOr ViewTransition::captureOldState() auto result = forEachRendererInPaintOrder([&](RenderLayerModelObject& renderer) -> ExceptionOr { auto styleable = Styleable::fromRenderer(renderer); - if (!styleable || &styleable->element.treeScope() != document()) + if (!styleable) return { }; - if (auto name = effectiveViewTransitionName(renderer); !name.isNull()) { + if (auto name = effectiveViewTransitionName(renderer, styleable->element, document()->styleScope()); !name.isNull()) { if (auto check = checkDuplicateViewTransitionName(name, usedTransitionNames); check.hasException()) return check.releaseException(); @@ -400,9 +404,8 @@ ExceptionOr ViewTransition::captureOldState() m_namedElements.add(transitionName->name, capture); } - for (auto& renderer : captureRenderers) { + for (auto& renderer : captureRenderers) renderer->setCapturedInViewTransition(false); - } return { }; } @@ -416,10 +419,10 @@ ExceptionOr ViewTransition::captureNewState() if (CheckedPtr view = document()->renderView()) { auto result = forEachRendererInPaintOrder([&](RenderLayerModelObject& renderer) -> ExceptionOr { auto styleable = Styleable::fromRenderer(renderer); - if (!styleable || &styleable->element.treeScope() != document()) + if (!styleable) return { }; - if (auto name = effectiveViewTransitionName(renderer); !name.isNull()) { + if (auto name = effectiveViewTransitionName(renderer, styleable->element, document()->styleScope()); !name.isNull()) { if (auto check = checkDuplicateViewTransitionName(name, usedTransitionNames); check.hasException()) return check.releaseException(); @@ -787,11 +790,11 @@ void ViewTransition::stop() bool ViewTransition::documentElementIsCaptured() const { - RefPtr doc = document(); - if (!doc) + RefPtr document = this->document(); + if (!document) return false; - RefPtr documentElement = doc->documentElement(); + RefPtr documentElement = document->documentElement(); if (!documentElement) return false; diff --git a/Source/WebCore/dom/ViewTransitionUpdateCallback.idl b/Source/WebCore/dom/ViewTransitionUpdateCallback.idl index b93e722d7cedf..23162449eb993 100644 --- a/Source/WebCore/dom/ViewTransitionUpdateCallback.idl +++ b/Source/WebCore/dom/ViewTransitionUpdateCallback.idl @@ -24,5 +24,6 @@ */ [ - EnabledBySetting=ViewTransitionsEnabled + EnabledBySetting=ViewTransitionsEnabled, + IsStrongCallback ] callback ViewTransitionUpdateCallback = Promise (); diff --git a/Source/WebCore/dom/ViewportArguments.cpp b/Source/WebCore/dom/ViewportArguments.cpp index 4d4689589378d..13afe13cfbc45 100644 --- a/Source/WebCore/dom/ViewportArguments.cpp +++ b/Source/WebCore/dom/ViewportArguments.cpp @@ -34,6 +34,7 @@ #include "LocalFrame.h" #include "ScriptableDocumentParser.h" #include "Settings.h" +#include #include namespace WebCore { diff --git a/Source/WebCore/dom/WindowEventLoop.cpp b/Source/WebCore/dom/WindowEventLoop.cpp index 3e7e5213272ed..4e1998a80ad5d 100644 --- a/Source/WebCore/dom/WindowEventLoop.cpp +++ b/Source/WebCore/dom/WindowEventLoop.cpp @@ -41,6 +41,7 @@ #include "ThreadTimers.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/dom/messageports/MessagePortChannel.h b/Source/WebCore/dom/messageports/MessagePortChannel.h index 7d98b451bd0c6..996a6b71fcbf4 100644 --- a/Source/WebCore/dom/messageports/MessagePortChannel.h +++ b/Source/WebCore/dom/messageports/MessagePortChannel.h @@ -32,6 +32,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/editing/CharacterRange.h b/Source/WebCore/editing/CharacterRange.h index 27b44089ebb19..73d7f9710bbff 100644 --- a/Source/WebCore/editing/CharacterRange.h +++ b/Source/WebCore/editing/CharacterRange.h @@ -46,6 +46,8 @@ struct CharacterRange { CharacterRange() = default; constexpr CharacterRange(uint64_t location, uint64_t length); + bool operator==(const CharacterRange&) const = default; + #if USE(CF) constexpr CharacterRange(CFRange); constexpr operator CFRange() const; diff --git a/Source/WebCore/editing/CompositeEditCommand.cpp b/Source/WebCore/editing/CompositeEditCommand.cpp index cb0d487453475..e365c7e01feba 100644 --- a/Source/WebCore/editing/CompositeEditCommand.cpp +++ b/Source/WebCore/editing/CompositeEditCommand.cpp @@ -77,6 +77,7 @@ #include "VisibleUnits.h" #include "WrapContentsInDummySpanCommand.h" #include "markup.h" +#include namespace WebCore { diff --git a/Source/WebCore/editing/EditingStyle.cpp b/Source/WebCore/editing/EditingStyle.cpp index e2a41a41da124..2f62353c3f1ec 100644 --- a/Source/WebCore/editing/EditingStyle.cpp +++ b/Source/WebCore/editing/EditingStyle.cpp @@ -62,6 +62,7 @@ #include "StyleRule.h" #include "StyledElement.h" #include "VisibleUnits.h" +#include namespace WebCore { diff --git a/Source/WebCore/editing/Editor.cpp b/Source/WebCore/editing/Editor.cpp index d1a55fc3c8b76..9794db7ae9cc5 100644 --- a/Source/WebCore/editing/Editor.cpp +++ b/Source/WebCore/editing/Editor.cpp @@ -131,6 +131,7 @@ #include #include #include +#include #include #if PLATFORM(MAC) @@ -1192,6 +1193,13 @@ static void dispatchInputEvents(RefPtr startRoot, RefPtr endRo bool Editor::willApplyEditing(CompositeEditCommand& command, Vector>&& targetRanges) { +#if ENABLE(WRITING_TOOLS) + if (suppressEditingForWritingTools()) { + RELEASE_LOG(Editing, "Editor %p suppressed editing for Writing Tools", this); + return false; + } +#endif + m_hasHandledAnyEditing = true; if (!command.shouldDispatchInputEvents()) @@ -2267,6 +2275,11 @@ void Editor::setComposition(const String& text, SetCompositionMode mode) } } +void Editor::closeTyping() +{ + TypingCommand::closeTyping(m_document); +} + RenderInline* Editor::writingSuggestionRenderer() const { return m_writingSuggestionRenderer.get(); diff --git a/Source/WebCore/editing/Editor.h b/Source/WebCore/editing/Editor.h index ec9b355d39ace..3e6a41e702c2c 100644 --- a/Source/WebCore/editing/Editor.h +++ b/Source/WebCore/editing/Editor.h @@ -284,6 +284,11 @@ class Editor final : public CanMakeCheckedPtr { WEBCORE_EXPORT void applyStyleToSelection(Ref&&, EditAction, ColorFilterMode); void applyParagraphStyleToSelection(StyleProperties*, EditAction); +#if ENABLE(WRITING_TOOLS) + bool suppressEditingForWritingTools() const { return m_suppressEditingForWritingTools; } + void setSuppressEditingForWritingTools(bool suppress) { m_suppressEditingForWritingTools = suppress; } +#endif + // Returns whether or not we should proceed with editing. bool willApplyEditing(CompositeEditCommand&, Vector>&&); bool willUnapplyEditing(const EditCommandComposition&) const; @@ -619,6 +624,8 @@ class Editor final : public CanMakeCheckedPtr { RenderInline* writingSuggestionRenderer() const; void setWritingSuggestionRenderer(RenderInline&); + WEBCORE_EXPORT void closeTyping(); + private: Document& document() const { return m_document.get(); } Ref protectedDocument() const { return m_document.get(); } @@ -694,6 +701,10 @@ class Editor final : public CanMakeCheckedPtr { EditorParagraphSeparator m_defaultParagraphSeparator { EditorParagraphSeparator::div }; bool m_overwriteModeEnabled { false }; +#if ENABLE(WRITING_TOOLS) + bool m_suppressEditingForWritingTools { false }; +#endif + #if ENABLE(ATTACHMENT_ELEMENT) MemoryCompactRobinHoodHashSet m_insertedAttachmentIdentifiers; MemoryCompactRobinHoodHashSet m_removedAttachmentIdentifiers; diff --git a/Source/WebCore/editing/HTMLInterchange.cpp b/Source/WebCore/editing/HTMLInterchange.cpp index 5bb33aaf38906..787020d82ee05 100644 --- a/Source/WebCore/editing/HTMLInterchange.cpp +++ b/Source/WebCore/editing/HTMLInterchange.cpp @@ -30,6 +30,7 @@ #include "RenderStyleInlines.h" #include "RenderText.h" #include "Text.h" +#include #include #include diff --git a/Source/WebCore/editing/TextIterator.cpp b/Source/WebCore/editing/TextIterator.cpp index 618afd1250782..86cbea5b88815 100644 --- a/Source/WebCore/editing/TextIterator.cpp +++ b/Source/WebCore/editing/TextIterator.cpp @@ -68,6 +68,7 @@ #include #include #include +#include #include #include #include diff --git a/Source/WebCore/editing/VisiblePosition.cpp b/Source/WebCore/editing/VisiblePosition.cpp index f22be66568285..527ecd4eddc31 100644 --- a/Source/WebCore/editing/VisiblePosition.cpp +++ b/Source/WebCore/editing/VisiblePosition.cpp @@ -49,6 +49,7 @@ #include "VisibleUnits.h" #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/editing/VisibleSelection.cpp b/Source/WebCore/editing/VisibleSelection.cpp index 672db56ab5474..bc015f21dc480 100644 --- a/Source/WebCore/editing/VisibleSelection.cpp +++ b/Source/WebCore/editing/VisibleSelection.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include diff --git a/Source/WebCore/editing/VisibleUnits.cpp b/Source/WebCore/editing/VisibleUnits.cpp index 33a51c90c00de..8ca41e9a3c7de 100644 --- a/Source/WebCore/editing/VisibleUnits.cpp +++ b/Source/WebCore/editing/VisibleUnits.cpp @@ -48,6 +48,7 @@ #include "TextIterator.h" #include "VisibleSelection.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/editing/WritingToolsCompositionCommand.cpp b/Source/WebCore/editing/WritingToolsCompositionCommand.cpp index 6bfac7156c0f0..925f482de0f24 100644 --- a/Source/WebCore/editing/WritingToolsCompositionCommand.cpp +++ b/Source/WebCore/editing/WritingToolsCompositionCommand.cpp @@ -40,9 +40,6 @@ WritingToolsCompositionCommand::WritingToolsCompositionCommand(Ref&& d void WritingToolsCompositionCommand::replaceContentsOfRangeWithFragment(RefPtr&& fragment, const SimpleRange& range, MatchStyle matchStyle, State state) { - if (endingSelection().isNoneOrOrphaned() || !endingSelection().isContentEditable()) - return; - auto contextRange = m_endingContextRange; auto contextRangeCount = characterCount(contextRange); diff --git a/Source/WebCore/editing/WritingToolsCompositionCommand.h b/Source/WebCore/editing/WritingToolsCompositionCommand.h index b05021cb3c575..8376b68c63de9 100644 --- a/Source/WebCore/editing/WritingToolsCompositionCommand.h +++ b/Source/WebCore/editing/WritingToolsCompositionCommand.h @@ -56,6 +56,8 @@ class WritingToolsCompositionCommand : public CompositeEditCommand { SimpleRange endingContextRange() const { return m_endingContextRange; } + void setEndingContextRange(const SimpleRange& range) { m_endingContextRange = range; } + private: WritingToolsCompositionCommand(Ref&&, const SimpleRange&); diff --git a/Source/WebCore/editing/cocoa/AttributedString.mm b/Source/WebCore/editing/cocoa/AttributedString.mm index 87fd0c09e508c..574cd8ad849fc 100644 --- a/Source/WebCore/editing/cocoa/AttributedString.mm +++ b/Source/WebCore/editing/cocoa/AttributedString.mm @@ -31,8 +31,10 @@ #import "Font.h" #import "LoaderNSURLExtras.h" #import "Logging.h" +#import "PlatformNSAdaptiveImageGlyph.h" #import "WebCoreTextAttachment.h" #import +#import #import #import #if PLATFORM(MAC) @@ -43,10 +45,6 @@ #import "UIFoundationSoftLink.h" #endif -#if USE(APPLE_INTERNAL_SDK) -#include -#endif - namespace WebCore { using IdentifierToTableMap = HashMap; @@ -163,8 +161,57 @@ return mutableStyle; } -#if USE(APPLE_INTERNAL_SDK) -#include +#if ENABLE(MULTI_REPRESENTATION_HEIC) + +static MultiRepresentationHEICAttachmentData toMultiRepresentationHEICAttachmentData(NSAdaptiveImageGlyph *attachment) +{ + MultiRepresentationHEICAttachmentData attachmentData; + attachmentData.identifier = attachment.contentIdentifier; + attachmentData.description = attachment.contentDescription; + + for (NSEmojiImageStrike *strike in attachment.strikes) { + MultiRepresentationHEICAttachmentSingleImage image; + RefPtr nativeImage = NativeImage::create(strike.cgImage); + image.image = BitmapImage::create(WTFMove(nativeImage)); + image.size = FloatSize { strike.alignmentInset }; + attachmentData.images.append(image); + } + + if (auto data = bridge_cast([attachment imageContent])) + attachmentData.data = data; + + return attachmentData; +} + +static RetainPtr toWebMultiRepresentationHEICAttachment(const MultiRepresentationHEICAttachmentData& attachmentData) +{ + if (RetainPtr data = attachmentData.data ? bridge_cast((attachmentData.data).get()) : nil) { + RetainPtr attachment = adoptNS([[PlatformNSAdaptiveImageGlyph alloc] initWithImageContent:data.get()]); + if (attachment) + return attachment; + } + + NSString *identifier = attachmentData.identifier; + NSString *description = attachmentData.description; + if (!description.length) + description = @"Apple Emoji"; + + NSMutableArray *images = [NSMutableArray arrayWithCapacity:attachmentData.images.size()]; + for (auto& singleImage : attachmentData.images) { + RetainPtr strike = adoptNS([[CTEmojiImageStrike alloc] initWithImage:singleImage.image->nativeImage()->platformImage().get() alignmentInset:singleImage.size]); + [images addObject:strike.get()]; + } + + if (![images count]) + return nil; + + RetainPtr asset = adoptNS([[CTEmojiImageAsset alloc] initWithContentIdentifier:identifier shortDescription:description strikeImages:images]); + if (![asset imageData]) + return nil; + + return adoptNS([[PlatformNSAdaptiveImageGlyph alloc] initWithImageContent:[asset imageData]]); +} + #endif static RetainPtr toNSObject(const AttributedString::AttributeValue& value, IdentifierToTableMap& tables, IdentifierToTableBlockMap& tableBlocks, IdentifierToListMap& lists) @@ -354,8 +401,8 @@ if ([value isKindOfClass:PlatformNSPresentationIntent]) return { { { RetainPtr { (NSPresentationIntent *)value } } } }; #if ENABLE(MULTI_REPRESENTATION_HEIC) - if ([value isKindOfClass:PlatformWebMultiRepresentationHEICAttachment]) { - auto attachment = static_cast(value); + if ([value isKindOfClass:PlatformNSAdaptiveImageGlyph]) { + auto attachment = static_cast(value); return { { toMultiRepresentationHEICAttachmentData(attachment) } }; } #endif diff --git a/Source/WebCore/editing/cocoa/DataDetection.mm b/Source/WebCore/editing/cocoa/DataDetection.mm index 4f565d76391e0..a00833a14417c 100644 --- a/Source/WebCore/editing/cocoa/DataDetection.mm +++ b/Source/WebCore/editing/cocoa/DataDetection.mm @@ -60,6 +60,7 @@ #import "VisibleUnits.h" #import #import +#import #import #import diff --git a/Source/WebCore/editing/cocoa/EditorCocoa.mm b/Source/WebCore/editing/cocoa/EditorCocoa.mm index ffe23ee469476..1f97be9109e44 100644 --- a/Source/WebCore/editing/cocoa/EditorCocoa.mm +++ b/Source/WebCore/editing/cocoa/EditorCocoa.mm @@ -72,10 +72,7 @@ #import #import #import - -#if USE(APPLE_INTERNAL_SDK) -#include -#endif +#import namespace WebCore { @@ -401,7 +398,7 @@ static void maybeCopyNodeAttributesToFragment(const Node& node, DocumentFragment { auto document = protectedDocument(); - String primaryType = MULTI_REPRESENTATION_HEIC_MIME_TYPE_STRING; + String primaryType = "image/x-apple-adaptive-glyph"_s; auto primaryBuffer = FragmentedSharedBuffer::create(data); String fallbackType = "image/png"_s; @@ -424,7 +421,7 @@ static void maybeCopyNodeAttributesToFragment(const Node& node, DocumentFragment auto fragment = document->createDocumentFragment(); fragment->appendChild(WTFMove(picture)); - ReplaceSelectionCommand::create(document.get(), WTFMove(fragment), ReplaceSelectionCommand::PreventNesting, EditAction::Insert)->apply(); + ReplaceSelectionCommand::create(document.get(), WTFMove(fragment), { ReplaceSelectionCommand::MatchStyle, ReplaceSelectionCommand::PreventNesting }, EditAction::Insert)->apply(); #if ENABLE(ATTACHMENT_ELEMENT) if (DeprecatedGlobalSettings::attachmentElementEnabled()) { diff --git a/Source/WebCore/editing/cocoa/HTMLConverter.mm b/Source/WebCore/editing/cocoa/HTMLConverter.mm index 06da2143b0891..f85dbae4991c8 100644 --- a/Source/WebCore/editing/cocoa/HTMLConverter.mm +++ b/Source/WebCore/editing/cocoa/HTMLConverter.mm @@ -74,6 +74,7 @@ #import #import #import +#import #import #import @@ -81,6 +82,10 @@ #import "DataDetection.h" #endif +#if ENABLE(MULTI_REPRESENTATION_HEIC) +#import "PlatformNSAdaptiveImageGlyph.h" +#endif + #if PLATFORM(IOS_FAMILY) #import "UIFoundationSoftLink.h" #import "WAKAppKitStubs.h" @@ -88,11 +93,6 @@ #import #endif -#if USE(APPLE_INTERNAL_SDK) -#include -#include -#endif - using namespace WebCore; using namespace HTMLNames; @@ -1218,7 +1218,7 @@ static Class _WebMessageDocumentClass() if (!image) return NO; - WebMultiRepresentationHEICAttachment *attachment = image->adapter().multiRepresentationHEIC(); + NSAdaptiveImageGlyph *attachment = image->adapter().multiRepresentationHEIC(); if (!attachment) return NO; @@ -1232,7 +1232,7 @@ static Class _WebMessageDocumentClass() if (rangeToReplace.location < _domRangeStartIndex) _domRangeStartIndex += rangeToReplace.length; - [_attrStr addAttribute:WebMultiRepresentationHEICAttachmentAttributeName value:attachment range:rangeToReplace]; + [_attrStr addAttribute:NSAdaptiveImageGlyphAttributeName value:attachment range:rangeToReplace]; _flags.isSoft = NO; return YES; @@ -1304,9 +1304,9 @@ static Class _WebMessageDocumentClass() if (RetainPtr data = [fileWrapper regularFileContents]) { RefPtr imageElement = dynamicDowncast(element); if (imageElement && imageElement->isMultiRepresentationHEIC()) - attachment = adoptNS([[PlatformWebMultiRepresentationHEICAttachment alloc] initWithImageContent:data.get()]); + attachment = adoptNS([[PlatformNSAdaptiveImageGlyph alloc] initWithImageContent:data.get()]); if (attachment) - attributeName = WebMultiRepresentationHEICAttachmentAttributeName; + attributeName = NSAdaptiveImageGlyphAttributeName; } #endif @@ -2411,15 +2411,24 @@ static String preferredFilenameForElement(const HTMLImageElement& element) return wrapper; } +static RetainPtr attributedStringWithAttachmentForFileWrapper(NSFileWrapper *fileWrapper) +{ + if (!fileWrapper) + return adoptNS([[NSAttributedString alloc] initWithString:@" "]).autorelease(); + + RetainPtr attachment = adoptNS([[PlatformNSTextAttachment alloc] initWithFileWrapper:fileWrapper]); + return [NSAttributedString attributedStringWithAttachment:attachment.get()]; +} + static RetainPtr attributedStringWithAttachmentForElement(const HTMLImageElement& element) { #if ENABLE(MULTI_REPRESENTATION_HEIC) if (element.isMultiRepresentationHEIC()) { if (RefPtr image = element.image()) { - if (WebMultiRepresentationHEICAttachment *attachment = image->adapter().multiRepresentationHEIC()) { + if (NSAdaptiveImageGlyph *attachment = image->adapter().multiRepresentationHEIC()) { RetainPtr attachmentString = adoptNS([[NSString alloc] initWithFormat:@"%C", static_cast(NSAttachmentCharacter)]); RetainPtr attributedString = adoptNS([[NSMutableAttributedString alloc] initWithString:attachmentString.get()]); - [attributedString addAttribute:WebMultiRepresentationHEICAttachmentAttributeName value:attachment range:NSMakeRange(0, 1)]; + [attributedString addAttribute:NSAdaptiveImageGlyphAttributeName value:attachment range:NSMakeRange(0, 1)]; return attributedString; } } @@ -2427,15 +2436,13 @@ static String preferredFilenameForElement(const HTMLImageElement& element) #endif RetainPtr fileWrapper = fileWrapperForElement(element); - RetainPtr attachment = adoptNS([[PlatformNSTextAttachment alloc] initWithFileWrapper:fileWrapper.get()]); - return [NSAttributedString attributedStringWithAttachment:attachment.get()]; + return attributedStringWithAttachmentForFileWrapper(fileWrapper.get()); } static RetainPtr attributedStringWithAttachmentForElement(const HTMLAttachmentElement& element) { RetainPtr fileWrapper = fileWrapperForElement(element); - RetainPtr attachment = adoptNS([[PlatformNSTextAttachment alloc] initWithFileWrapper:fileWrapper.get()]); - return [NSAttributedString attributedStringWithAttachment:attachment.get()]; + return attributedStringWithAttachmentForFileWrapper(fileWrapper.get()); } #if ENABLE(WRITING_TOOLS) diff --git a/Source/WebCore/editing/markup.cpp b/Source/WebCore/editing/markup.cpp index eb12f4e769755..4d40dc2ef4070 100644 --- a/Source/WebCore/editing/markup.cpp +++ b/Source/WebCore/editing/markup.cpp @@ -91,6 +91,7 @@ #include #include #include +#include #include #if ENABLE(DATA_DETECTION) diff --git a/Source/WebCore/en.lproj/Localizable.strings b/Source/WebCore/en.lproj/Localizable.strings index 9868c54fc88d8..aea5df387a5d6 100644 --- a/Source/WebCore/en.lproj/Localizable.strings +++ b/Source/WebCore/en.lproj/Localizable.strings @@ -175,8 +175,8 @@ /* Allow button title in speech recognition prompt */ "Allow (speechrecognition)" = "Allow"; -/* Display USDZ file */ -"Allow (usdz QuickLook Preview)" = "Allow (usdz QuickLook Preview)"; +/* Allow displaying QuickLook Preview of 3D model */ +"Allow (usdz QuickLook Preview)" = "Allow"; /* Allow button title in user media prompt */ "Allow (usermedia)" = "Allow"; @@ -289,8 +289,8 @@ /* Button title in Device Orientation Permission API prompt */ "Cancel (device motion and orientation access)" = "Cancel"; -/* Cancel USDZ file */ -"Cancel (usdz QuickLook Preview)" = "Cancel (usdz QuickLook Preview)"; +/* Cancel displaying QuickLook Preview of 3D model */ +"Cancel (usdz QuickLook Preview)" = "Cancel"; /* Title for Cancel button label in button bar */ "Cancel button label in button bar" = "Cancel"; @@ -1102,7 +1102,7 @@ /* Title for Open in External Application Link action button */ "Open in “%@”" = "Open in “%@”"; -/* Open 3D object in a new window? */ +/* Open this 3D model? */ "Open this 3D model?" = "Open this 3D model?"; /* context menu item for PDF */ diff --git a/Source/WebCore/fileapi/BlobCallback.idl b/Source/WebCore/fileapi/BlobCallback.idl index d920727393fb7..6ebd806d541c5 100644 --- a/Source/WebCore/fileapi/BlobCallback.idl +++ b/Source/WebCore/fileapi/BlobCallback.idl @@ -23,4 +23,4 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -callback BlobCallback = undefined (Blob? blob); +[ IsStrongCallback ] callback BlobCallback = undefined (Blob? blob); diff --git a/Source/WebCore/fileapi/BlobURL.cpp b/Source/WebCore/fileapi/BlobURL.cpp index eb5d663f2ce34..67ae61a3e2eb6 100644 --- a/Source/WebCore/fileapi/BlobURL.cpp +++ b/Source/WebCore/fileapi/BlobURL.cpp @@ -29,14 +29,14 @@ */ #include "config.h" - #include "BlobURL.h" + #include "Document.h" #include "SecurityOrigin.h" #include "ThreadableBlobRegistry.h" - #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/fileapi/FileCocoa.mm b/Source/WebCore/fileapi/FileCocoa.mm index 517710eed478b..8f63757f89b2a 100644 --- a/Source/WebCore/fileapi/FileCocoa.mm +++ b/Source/WebCore/fileapi/FileCocoa.mm @@ -30,6 +30,7 @@ #import #import +#include #if PLATFORM(IOS_FAMILY) #import diff --git a/Source/WebCore/fileapi/FileReaderLoader.cpp b/Source/WebCore/fileapi/FileReaderLoader.cpp index 5311c45aa99d8..3c062f76b05a8 100644 --- a/Source/WebCore/fileapi/FileReaderLoader.cpp +++ b/Source/WebCore/fileapi/FileReaderLoader.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/history/BackForwardCache.cpp b/Source/WebCore/history/BackForwardCache.cpp index 4e501a6e95e84..fac069f7b3940 100644 --- a/Source/WebCore/history/BackForwardCache.cpp +++ b/Source/WebCore/history/BackForwardCache.cpp @@ -59,7 +59,7 @@ #include #include #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/history/HistoryItem.cpp b/Source/WebCore/history/HistoryItem.cpp index d4de59e34db75..a2ddd40038e67 100644 --- a/Source/WebCore/history/HistoryItem.cpp +++ b/Source/WebCore/history/HistoryItem.cpp @@ -39,6 +39,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/html/Autofill.cpp b/Source/WebCore/html/Autofill.cpp index 491324d56b727..8d1799bf5cfce 100644 --- a/Source/WebCore/html/Autofill.cpp +++ b/Source/WebCore/html/Autofill.cpp @@ -33,6 +33,7 @@ #include "HTMLNames.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/html/BaseTextInputType.cpp b/Source/WebCore/html/BaseTextInputType.cpp index eb2d255a5a8e6..35322c091e23a 100644 --- a/Source/WebCore/html/BaseTextInputType.cpp +++ b/Source/WebCore/html/BaseTextInputType.cpp @@ -29,6 +29,7 @@ #include "HTMLInputElement.h" #include "HTMLNames.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/html/CanvasBase.cpp b/Source/WebCore/html/CanvasBase.cpp index 778fd2c19b8fc..af1ff3fa071eb 100644 --- a/Source/WebCore/html/CanvasBase.cpp +++ b/Source/WebCore/html/CanvasBase.cpp @@ -47,6 +47,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/html/DOMURL.cpp b/Source/WebCore/html/DOMURL.cpp index ad1396eb7c09a..0f941d44c547d 100644 --- a/Source/WebCore/html/DOMURL.cpp +++ b/Source/WebCore/html/DOMURL.cpp @@ -36,6 +36,7 @@ #include "SecurityOrigin.h" #include "URLSearchParams.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/html/DateInputType.cpp b/Source/WebCore/html/DateInputType.cpp index a5ab94269d37e..aa9326884af25 100644 --- a/Source/WebCore/html/DateInputType.cpp +++ b/Source/WebCore/html/DateInputType.cpp @@ -40,6 +40,7 @@ #include "InputTypeNames.h" #include "PlatformLocale.h" #include "StepRange.h" +#include namespace WebCore { diff --git a/Source/WebCore/html/DateTimeLocalInputType.cpp b/Source/WebCore/html/DateTimeLocalInputType.cpp index 46d1db8abfa56..b5513b0db3155 100644 --- a/Source/WebCore/html/DateTimeLocalInputType.cpp +++ b/Source/WebCore/html/DateTimeLocalInputType.cpp @@ -42,6 +42,7 @@ #include "InputTypeNames.h" #include "PlatformLocale.h" #include "StepRange.h" +#include namespace WebCore { diff --git a/Source/WebCore/html/DirectoryFileListCreator.cpp b/Source/WebCore/html/DirectoryFileListCreator.cpp index ddeb7ce666166..a27893a924e6c 100644 --- a/Source/WebCore/html/DirectoryFileListCreator.cpp +++ b/Source/WebCore/html/DirectoryFileListCreator.cpp @@ -31,6 +31,7 @@ #include "FileList.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/html/FTPDirectoryDocument.cpp b/Source/WebCore/html/FTPDirectoryDocument.cpp index 1db74657be9e4..490695f26f3e7 100644 --- a/Source/WebCore/html/FTPDirectoryDocument.cpp +++ b/Source/WebCore/html/FTPDirectoryDocument.cpp @@ -42,7 +42,7 @@ #include #include #include -#include +#include #include #include @@ -245,14 +245,9 @@ static String processFileDateString(const FTPTime& fileTime) if (month < 0 || month > 11) month = 12; - String dateString; - if (fileTime.tm_year > -1) - dateString = makeString(months[month], ' ', fileTime.tm_mday, ", "_s, fileTime.tm_year); - else - dateString = makeString(months[month], ' ', fileTime.tm_mday, ", "_s, now.year()); - - return dateString + timeOfDay; + return makeString(months[month], ' ', fileTime.tm_mday, ", "_s, fileTime.tm_year, timeOfDay); + return makeString(months[month], ' ', fileTime.tm_mday, ", "_s, now.year(), timeOfDay); } void FTPDirectoryDocumentParser::parseAndAppendOneLine(const String& inputLine) diff --git a/Source/WebCore/html/FileInputType.cpp b/Source/WebCore/html/FileInputType.cpp index 67feb6c9b89bb..7a26cfbbaf3d9 100644 --- a/Source/WebCore/html/FileInputType.cpp +++ b/Source/WebCore/html/FileInputType.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #if PLATFORM(MAC) diff --git a/Source/WebCore/html/FormController.cpp b/Source/WebCore/html/FormController.cpp index bf0cebde4f7ff..439f47d6ad4c8 100644 --- a/Source/WebCore/html/FormController.cpp +++ b/Source/WebCore/html/FormController.cpp @@ -29,8 +29,8 @@ #include "TypedElementDescendantIteratorInlines.h" #include #include +#include #include -#include #include namespace WebCore { diff --git a/Source/WebCore/html/HTMLAnchorElement.cpp b/Source/WebCore/html/HTMLAnchorElement.cpp index a65eb5131cbba..a064efa2aad9c 100644 --- a/Source/WebCore/html/HTMLAnchorElement.cpp +++ b/Source/WebCore/html/HTMLAnchorElement.cpp @@ -61,8 +61,8 @@ #include "UserGestureIndicator.h" #include #include +#include #include -#include #if PLATFORM(COCOA) #include "DataDetection.h" diff --git a/Source/WebCore/html/HTMLAttachmentElement.cpp b/Source/WebCore/html/HTMLAttachmentElement.cpp index 2804ecd34bf65..796629e7045fd 100644 --- a/Source/WebCore/html/HTMLAttachmentElement.cpp +++ b/Source/WebCore/html/HTMLAttachmentElement.cpp @@ -60,6 +60,7 @@ #include #include #include +#include #include #if ENABLE(SERVICE_CONTROLS) diff --git a/Source/WebCore/html/HTMLIFrameElement.cpp b/Source/WebCore/html/HTMLIFrameElement.cpp index 3133805dd038e..2ca3bb0f0c831 100644 --- a/Source/WebCore/html/HTMLIFrameElement.cpp +++ b/Source/WebCore/html/HTMLIFrameElement.cpp @@ -43,6 +43,7 @@ #include "Settings.h" #include "TrustedType.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/html/HTMLImageElement.cpp b/Source/WebCore/html/HTMLImageElement.cpp index 168bf3f2170e4..e1de06dd8f1bb 100644 --- a/Source/WebCore/html/HTMLImageElement.cpp +++ b/Source/WebCore/html/HTMLImageElement.cpp @@ -70,10 +70,6 @@ #include "ImageControlsMac.h" #endif -#if USE(APPLE_INTERNAL_SDK) -#include -#endif - namespace WebCore { WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLImageElement); @@ -956,7 +952,7 @@ bool HTMLImageElement::isMultiRepresentationHEIC() const return false; auto& typeAttribute = m_sourceElement->attributeWithoutSynchronization(typeAttr); - return typeAttribute == MULTI_REPRESENTATION_HEIC_MIME_TYPE_STRING; + return typeAttribute == "image/x-apple-adaptive-glyph"_s; } #endif diff --git a/Source/WebCore/html/HTMLInputElement.cpp b/Source/WebCore/html/HTMLInputElement.cpp index ae242ccb3f4dc..a33abf3bdf23c 100644 --- a/Source/WebCore/html/HTMLInputElement.cpp +++ b/Source/WebCore/html/HTMLInputElement.cpp @@ -84,6 +84,7 @@ #include #include #include +#include #include #if ENABLE(TOUCH_EVENTS) diff --git a/Source/WebCore/html/HTMLLIElement.cpp b/Source/WebCore/html/HTMLLIElement.cpp index 74004fba22a1d..36debf0fc8c05 100644 --- a/Source/WebCore/html/HTMLLIElement.cpp +++ b/Source/WebCore/html/HTMLLIElement.cpp @@ -33,6 +33,7 @@ #include "HTMLUListElement.h" #include "RenderListItem.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/html/HTMLLinkElement.cpp b/Source/WebCore/html/HTMLLinkElement.cpp index bcae9f13b90c7..2c57da0cd25d0 100644 --- a/Source/WebCore/html/HTMLLinkElement.cpp +++ b/Source/WebCore/html/HTMLLinkElement.cpp @@ -70,6 +70,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/html/HTMLMediaElement.cpp b/Source/WebCore/html/HTMLMediaElement.cpp index c174e09707b0c..48b31c87ef3a5 100644 --- a/Source/WebCore/html/HTMLMediaElement.cpp +++ b/Source/WebCore/html/HTMLMediaElement.cpp @@ -147,6 +147,7 @@ #include #include #include +#include #if USE(AUDIO_SESSION) #include "AudioSession.h" diff --git a/Source/WebCore/html/HTMLMediaElement.h b/Source/WebCore/html/HTMLMediaElement.h index 77ecadf771738..5dac87fc32e97 100644 --- a/Source/WebCore/html/HTMLMediaElement.h +++ b/Source/WebCore/html/HTMLMediaElement.h @@ -183,7 +183,7 @@ class HTMLMediaElement virtual bool isVideo() const { return false; } bool hasVideo() const override { return false; } - bool hasAudio() const override; + WEBCORE_EXPORT bool hasAudio() const override; bool hasRenderer() const { return static_cast(renderer()); } WEBCORE_EXPORT static HashSet>& allMediaElements(); @@ -572,7 +572,7 @@ class HTMLMediaElement RenderMedia* renderer() const; void resetPlaybackSessionState(); - bool isVisibleInViewport() const; + WEBCORE_EXPORT bool isVisibleInViewport() const; bool hasEverNotifiedAboutPlaying() const; void setShouldDelayLoadEvent(bool); @@ -755,6 +755,10 @@ class HTMLMediaElement bool videoFullscreenStandby() const { return m_videoFullscreenStandby; } void setVideoFullscreenStandbyInternal(bool videoFullscreenStandby) { m_videoFullscreenStandby = videoFullscreenStandby; } +protected: + // ActiveDOMObject + void stop() override; + private: friend class Internals; @@ -777,7 +781,6 @@ class HTMLMediaElement // ActiveDOMObject. void suspend(ReasonForSuspension) override; void resume() override; - void stop() override; bool virtualHasPendingActivity() const override; void stopWithoutDestroyingMediaPlayer(); diff --git a/Source/WebCore/html/HTMLOptionElement.cpp b/Source/WebCore/html/HTMLOptionElement.cpp index dc8e5f125e52a..0625341ddec04 100644 --- a/Source/WebCore/html/HTMLOptionElement.cpp +++ b/Source/WebCore/html/HTMLOptionElement.cpp @@ -46,6 +46,7 @@ #include "Text.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/html/HTMLSelectElement.cpp b/Source/WebCore/html/HTMLSelectElement.cpp index 45b64bf488318..41dcd15cdf7ba 100644 --- a/Source/WebCore/html/HTMLSelectElement.cpp +++ b/Source/WebCore/html/HTMLSelectElement.cpp @@ -60,7 +60,7 @@ #include "Settings.h" #include "SpatialNavigation.h" #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/html/HTMLTextFormControlElement.cpp b/Source/WebCore/html/HTMLTextFormControlElement.cpp index 991adf128912f..ace96c4c25f55 100644 --- a/Source/WebCore/html/HTMLTextFormControlElement.cpp +++ b/Source/WebCore/html/HTMLTextFormControlElement.cpp @@ -63,6 +63,7 @@ #include "Text.h" #include "TextControlInnerElements.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/html/HTMLTrackElement.cpp b/Source/WebCore/html/HTMLTrackElement.cpp index 62d000dfb7222..152abceffc3d5 100644 --- a/Source/WebCore/html/HTMLTrackElement.cpp +++ b/Source/WebCore/html/HTMLTrackElement.cpp @@ -41,6 +41,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/html/HTMLVideoElement.cpp b/Source/WebCore/html/HTMLVideoElement.cpp index 3b98156af7658..bb2a7d6771e33 100644 --- a/Source/WebCore/html/HTMLVideoElement.cpp +++ b/Source/WebCore/html/HTMLVideoElement.cpp @@ -156,6 +156,14 @@ void HTMLVideoElement::computeAcceleratedRenderingStateAndUpdateMediaPlayer() player->acceleratedRenderingStateChanged(); // This call will trigger a call back to `mediaPlayerRenderingCanBeAccelerated()` from the MediaPlayer. } +#if PLATFORM(IOS_FAMILY) +bool HTMLVideoElement::canShowWhileLocked() const +{ + RefPtr page = document().page(); + return page && page->canShowWhileLocked(); +} +#endif + void HTMLVideoElement::collectPresentationalHintsForAttribute(const QualifiedName& name, const AtomString& value, MutableStyleProperties& style) { if (name == widthAttr) { @@ -686,6 +694,13 @@ void HTMLVideoElement::cancelVideoFrameCallback(unsigned identifier) } } +void HTMLVideoElement::stop() +{ + m_videoFrameRequests.clear(); + m_servicedVideoFrameRequests.clear(); + HTMLMediaElement::stop(); +} + static void processVideoFrameMetadataTimestamps(VideoFrameMetadata& metadata, Performance& performance) { metadata.presentationTime = performance.relativeTimeFromTimeOriginInReducedResolution(MonotonicTime::fromRawSeconds(metadata.presentationTime)); diff --git a/Source/WebCore/html/HTMLVideoElement.h b/Source/WebCore/html/HTMLVideoElement.h index c64895eab0385..139ff00711322 100644 --- a/Source/WebCore/html/HTMLVideoElement.h +++ b/Source/WebCore/html/HTMLVideoElement.h @@ -129,6 +129,9 @@ class HTMLVideoElement final : public HTMLMediaElement, public Supplementable m_imageLoader; diff --git a/Source/WebCore/html/ImageDocument.cpp b/Source/WebCore/html/ImageDocument.cpp index 33c2a7d1b80a0..476d8948bebc8 100644 --- a/Source/WebCore/html/ImageDocument.cpp +++ b/Source/WebCore/html/ImageDocument.cpp @@ -50,7 +50,7 @@ #include "RenderElement.h" #include "Settings.h" #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/html/ImageInputType.cpp b/Source/WebCore/html/ImageInputType.cpp index bdbb37312a445..1ea78a13fd2fe 100644 --- a/Source/WebCore/html/ImageInputType.cpp +++ b/Source/WebCore/html/ImageInputType.cpp @@ -37,6 +37,7 @@ #include "RenderElementInlines.h" #include "RenderImage.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/html/MonthInputType.cpp b/Source/WebCore/html/MonthInputType.cpp index 3b02b5a802391..5b62560bf09b8 100644 --- a/Source/WebCore/html/MonthInputType.cpp +++ b/Source/WebCore/html/MonthInputType.cpp @@ -44,6 +44,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/html/PDFDocument.cpp b/Source/WebCore/html/PDFDocument.cpp index 67867fd8f77ae..ded1978ab97a2 100644 --- a/Source/WebCore/html/PDFDocument.cpp +++ b/Source/WebCore/html/PDFDocument.cpp @@ -47,7 +47,6 @@ #include "Settings.h" #include #include -#include namespace WebCore { diff --git a/Source/WebCore/html/PermissionsPolicy.cpp b/Source/WebCore/html/PermissionsPolicy.cpp index 56647fa986eb1..8ede68f80fa8b 100644 --- a/Source/WebCore/html/PermissionsPolicy.cpp +++ b/Source/WebCore/html/PermissionsPolicy.cpp @@ -34,6 +34,7 @@ #include "LocalDOMWindow.h" #include "Quirks.h" #include "SecurityOrigin.h" +#include namespace WebCore { diff --git a/Source/WebCore/html/PluginDocument.cpp b/Source/WebCore/html/PluginDocument.cpp index 70ed852eff21a..d3ca6b4fd6336 100644 --- a/Source/WebCore/html/PluginDocument.cpp +++ b/Source/WebCore/html/PluginDocument.cpp @@ -42,6 +42,7 @@ #include "RenderEmbeddedObject.h" #include "StyleSheetContents.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/html/TimeInputType.cpp b/Source/WebCore/html/TimeInputType.cpp index 4d606d6e302de..de4bdf17dd5fe 100644 --- a/Source/WebCore/html/TimeInputType.cpp +++ b/Source/WebCore/html/TimeInputType.cpp @@ -43,6 +43,7 @@ #include "StepRange.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/html/URLDecomposition.cpp b/Source/WebCore/html/URLDecomposition.cpp index 6e38df99bf728..7aa3e4ebeef0c 100644 --- a/Source/WebCore/html/URLDecomposition.cpp +++ b/Source/WebCore/html/URLDecomposition.cpp @@ -27,6 +27,7 @@ #include "URLDecomposition.h" #include "SecurityOrigin.h" +#include #include namespace WebCore { diff --git a/Source/WebCore/html/ValidatedFormListedElement.cpp b/Source/WebCore/html/ValidatedFormListedElement.cpp index 7b3fa97505a33..a57c93edcb545 100644 --- a/Source/WebCore/html/ValidatedFormListedElement.cpp +++ b/Source/WebCore/html/ValidatedFormListedElement.cpp @@ -51,6 +51,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/html/ValidationMessage.cpp b/Source/WebCore/html/ValidationMessage.cpp index f2aba9433d91e..d9b5e44ac55ba 100644 --- a/Source/WebCore/html/ValidationMessage.cpp +++ b/Source/WebCore/html/ValidationMessage.cpp @@ -49,6 +49,7 @@ #include "Text.h" #include "UserAgentParts.h" #include "ValidationMessageClient.h" +#include namespace WebCore { @@ -95,7 +96,7 @@ void ValidationMessage::updateValidationMessage(HTMLElement& element, const Stri if (!updatedMessage.isEmpty()) { const AtomString& title = m_element->attributeWithoutSynchronization(titleAttr); if (!title.isEmpty()) - updatedMessage = updatedMessage + '\n' + title; + updatedMessage = makeString(updatedMessage, '\n', title); } } diff --git a/Source/WebCore/html/VideoFrameRequestCallback.idl b/Source/WebCore/html/VideoFrameRequestCallback.idl index 63f7e1b1b910d..1ba840c1da5eb 100644 --- a/Source/WebCore/html/VideoFrameRequestCallback.idl +++ b/Source/WebCore/html/VideoFrameRequestCallback.idl @@ -27,4 +27,5 @@ typedef double DOMHighResTimeStamp; [ Conditional=VIDEO, + IsStrongCallback ] callback VideoFrameRequestCallback = undefined(DOMHighResTimeStamp now, VideoFrameMetadata metadata); diff --git a/Source/WebCore/html/VoidCallback.idl b/Source/WebCore/html/VoidCallback.idl index 182de28808f7a..bee2ca5e40e43 100644 --- a/Source/WebCore/html/VoidCallback.idl +++ b/Source/WebCore/html/VoidCallback.idl @@ -24,5 +24,6 @@ */ [ - ExportMacro=WEBCORE_EXPORT + ExportMacro=WEBCORE_EXPORT, + IsStrongCallback ] callback VoidCallback = undefined (); diff --git a/Source/WebCore/html/canvas/CanvasPath.cpp b/Source/WebCore/html/canvas/CanvasPath.cpp index c91e16e3d6740..7528040739ef0 100644 --- a/Source/WebCore/html/canvas/CanvasPath.cpp +++ b/Source/WebCore/html/canvas/CanvasPath.cpp @@ -42,6 +42,7 @@ #include "FloatSize.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp b/Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp index c0bd019e783c2..4f50b87a8cea4 100644 --- a/Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp +++ b/Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp @@ -78,6 +78,7 @@ #include #include #include +#include #include #include diff --git a/Source/WebCore/html/canvas/WebGLRenderingContextBase.cpp b/Source/WebCore/html/canvas/WebGLRenderingContextBase.cpp index 7e5be4832d3bd..efcca9980388f 100644 --- a/Source/WebCore/html/canvas/WebGLRenderingContextBase.cpp +++ b/Source/WebCore/html/canvas/WebGLRenderingContextBase.cpp @@ -162,6 +162,7 @@ #include #include #include +#include #include #if ENABLE(MEDIA_STREAM) diff --git a/Source/WebCore/html/parser/HTMLDocumentParserFastPath.cpp b/Source/WebCore/html/parser/HTMLDocumentParserFastPath.cpp index 6852985dd60d3..21bbdfff9e09f 100644 --- a/Source/WebCore/html/parser/HTMLDocumentParserFastPath.cpp +++ b/Source/WebCore/html/parser/HTMLDocumentParserFastPath.cpp @@ -476,27 +476,38 @@ class HTMLFastPathParser { // `LChar` parser. String scanText() { - using UnsignedType = std::make_unsigned_t; - constexpr auto quoteMask = SIMD::splat('<'); - constexpr auto escapeMask = SIMD::splat('&'); - constexpr auto newlineMask = SIMD::splat('\r'); - constexpr auto zeroMask = SIMD::splat(0); - auto vectorMatch = [&](auto input) ALWAYS_INLINE_LAMBDA { - auto quotes = SIMD::equal(input, quoteMask); - auto escapes = SIMD::equal(input, escapeMask); - auto newlines = SIMD::equal(input, newlineMask); - auto zeros = SIMD::equal(input, zeroMask); - auto mask = SIMD::bitOr(zeros, quotes, escapes, newlines); - return SIMD::findFirstNonZeroIndex(mask); - }; + auto* start = m_parsingBuffer.position(); + const auto* end = start + m_parsingBuffer.lengthRemaining(); auto scalarMatch = [&](auto character) ALWAYS_INLINE_LAMBDA { return character == '<' || character == '&' || character == '\r' || character == '\0'; }; - auto* start = m_parsingBuffer.position(); - const auto* end = start + m_parsingBuffer.lengthRemaining(); - auto* cursor = SIMD::find(std::span { start, end }, vectorMatch, scalarMatch); + auto vectorEquals8Bit = [&](auto input) ALWAYS_INLINE_LAMBDA { + // https://lemire.me/blog/2024/06/08/scan-html-faster-with-simd-instructions-chrome-edition/ + // By looking up the table via lower 4bit, we can identify the category. + // '\0' => 0000 0000 + // '&' => 0010 0110 + // '<' => 0011 1100 + // '\r' => 0000 1101 + constexpr simde_uint8x16_t lowNibbleMask { '\0', 0, 0, 0, 0, 0, '&', 0, 0, 0, 0, 0, '<', '\r', 0, 0 }; + constexpr simde_uint8x16_t v0f = SIMD::splat(0x0f); + return SIMD::equal(simde_vqtbl1q_u8(lowNibbleMask, SIMD::bitAnd(input, v0f)), input); + }; + + const CharacterType* cursor = nullptr; + if constexpr (sizeof(CharacterType) == 1) { + auto vectorMatch = [&](auto input) ALWAYS_INLINE_LAMBDA { + return SIMD::findFirstNonZeroIndex(vectorEquals8Bit(input)); + }; + cursor = SIMD::find(std::span { start, end }, vectorMatch, scalarMatch); + } else { + auto vectorMatch = [&](auto input) ALWAYS_INLINE_LAMBDA { + constexpr simde_uint8x16_t zeros = SIMD::splat(0); + return SIMD::findFirstNonZeroIndex(SIMD::bitAnd(vectorEquals8Bit(input.val[0]), SIMD::equal(input.val[1], zeros))); + }; + cursor = SIMD::findInterleaved(std::span { start, end }, vectorMatch, scalarMatch); + } m_parsingBuffer.setPosition(cursor); if (cursor != end) { @@ -617,25 +628,47 @@ class HTMLFastPathParser { if (m_parsingBuffer.hasCharactersRemaining() && isQuoteCharacter(*m_parsingBuffer)) { auto quoteChar = m_parsingBuffer.consume(); - using UnsignedType = std::make_unsigned_t; - const auto quoteMask = SIMD::splat(quoteChar); - constexpr auto escapeMask = SIMD::splat('&'); - constexpr auto newlineMask = SIMD::splat('\r'); - auto vectorMatch = [&](auto input) ALWAYS_INLINE_LAMBDA { - auto quotes = SIMD::equal(input, quoteMask); - auto escapes = SIMD::equal(input, escapeMask); - auto newlines = SIMD::equal(input, newlineMask); - auto mask = SIMD::bitOr(quotes, escapes, newlines); - return SIMD::findFirstNonZeroIndex(mask); - }; - - auto scalarMatch = [&](auto character) ALWAYS_INLINE_LAMBDA { - return character == quoteChar || character == '&' || character == '\r'; + auto find = [&](std::span span) ALWAYS_INLINE_LAMBDA { + auto scalarMatch = [&](auto character) ALWAYS_INLINE_LAMBDA { + return character == quoteChar || character == '&' || character == '\r' || character == '\0'; + }; + + auto vectorEquals8Bit = [&](auto input) ALWAYS_INLINE_LAMBDA { + // https://lemire.me/blog/2024/06/08/scan-html-faster-with-simd-instructions-chrome-edition/ + // By looking up the table via lower 4bit, we can identify the category. + // '\0' => 0000 0000 + // '&' => 0010 0110 + // '\'' => 0010 0111 + // '\r' => 0000 1101 + // + // OR + // + // '\0' => 0000 0000 + // '"' => 0010 0010 + // '&' => 0010 0110 + // '\r' => 0000 1101 + constexpr auto lowNibbleMask = quoteChar == '\'' ? simde_uint8x16_t { '\0', 0, 0, 0, 0, 0, '&', '\'', 0, 0, 0, 0, 0, '\r', 0, 0 } : simde_uint8x16_t { '\0', 0, '"', 0, 0, 0, '&', 0, 0, 0, 0, 0, 0, '\r', 0, 0 }; + constexpr auto v0f = SIMD::splat(0x0f); + return SIMD::equal(simde_vqtbl1q_u8(lowNibbleMask, SIMD::bitAnd(input, v0f)), input); + }; + + if constexpr (sizeof(CharacterType) == 1) { + auto vectorMatch = [&](auto input) ALWAYS_INLINE_LAMBDA { + return SIMD::findFirstNonZeroIndex(vectorEquals8Bit(input)); + }; + return SIMD::find(span, vectorMatch, scalarMatch); + } else { + auto vectorMatch = [&](auto input) ALWAYS_INLINE_LAMBDA { + constexpr simde_uint8x16_t zeros = SIMD::splat(0); + return SIMD::findFirstNonZeroIndex(SIMD::bitAnd(vectorEquals8Bit(input.val[0]), SIMD::equal(input.val[1], zeros))); + }; + return SIMD::findInterleaved(span, vectorMatch, scalarMatch); + } }; start = m_parsingBuffer.position(); const auto* end = start + m_parsingBuffer.lengthRemaining(); - auto* cursor = SIMD::find(std::span { start, end }, vectorMatch, scalarMatch); + const auto* cursor = quoteChar == '\'' ? find.template operator()<'\''>(std::span { start, end }) : find.template operator()<'"'>(std::span { start, end }); if (UNLIKELY(cursor == end)) return didFail(HTMLFastPathResult::FailedParsingQuotedAttributeValue, emptyAtom()); diff --git a/Source/WebCore/html/parser/HTMLTokenizer.cpp b/Source/WebCore/html/parser/HTMLTokenizer.cpp index ae405577a582e..e7979441f63da 100644 --- a/Source/WebCore/html/parser/HTMLTokenizer.cpp +++ b/Source/WebCore/html/parser/HTMLTokenizer.cpp @@ -31,9 +31,9 @@ #include "HTMLEntityParser.h" #include "HTMLNames.h" #include "MarkupTokenizerInlines.h" +#include #include - namespace WebCore { using namespace HTMLNames; diff --git a/Source/WebCore/html/parser/HTMLTreeBuilder.cpp b/Source/WebCore/html/parser/HTMLTreeBuilder.cpp index 9b26462f17b75..efbe41be037df 100644 --- a/Source/WebCore/html/parser/HTMLTreeBuilder.cpp +++ b/Source/WebCore/html/parser/HTMLTreeBuilder.cpp @@ -51,6 +51,7 @@ #include "XMLNames.h" #include #include +#include #include #if ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(IOS_FAMILY) diff --git a/Source/WebCore/html/shadow/DateTimeNumericFieldElement.cpp b/Source/WebCore/html/shadow/DateTimeNumericFieldElement.cpp index 3088ef42129bd..261552791ea3e 100644 --- a/Source/WebCore/html/shadow/DateTimeNumericFieldElement.cpp +++ b/Source/WebCore/html/shadow/DateTimeNumericFieldElement.cpp @@ -37,6 +37,7 @@ #include "RenderBlock.h" #include "RenderStyleSetters.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/html/track/TextTrackCue.cpp b/Source/WebCore/html/track/TextTrackCue.cpp index 922b2972ac6fc..a7da0788e8a4a 100644 --- a/Source/WebCore/html/track/TextTrackCue.cpp +++ b/Source/WebCore/html/track/TextTrackCue.cpp @@ -58,7 +58,7 @@ #include #include #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/html/track/TrackBase.cpp b/Source/WebCore/html/track/TrackBase.cpp index 96d9d160b0346..26ac3227c5711 100644 --- a/Source/WebCore/html/track/TrackBase.cpp +++ b/Source/WebCore/html/track/TrackBase.cpp @@ -33,6 +33,7 @@ #include "TrackPrivateBase.h" #include "TrackPrivateBaseClient.h" #include +#include #include #include diff --git a/Source/WebCore/html/track/VTTCue.cpp b/Source/WebCore/html/track/VTTCue.cpp index 66add1e21d2b0..e30c1553cfc78 100644 --- a/Source/WebCore/html/track/VTTCue.cpp +++ b/Source/WebCore/html/track/VTTCue.cpp @@ -63,7 +63,6 @@ #include #include #include -#include namespace WebCore { diff --git a/Source/WebCore/inspector/InspectorAuditResourcesObject.cpp b/Source/WebCore/inspector/InspectorAuditResourcesObject.cpp index 3917dbbd97002..df8ed1c4fd559 100644 --- a/Source/WebCore/inspector/InspectorAuditResourcesObject.cpp +++ b/Source/WebCore/inspector/InspectorAuditResourcesObject.cpp @@ -36,6 +36,7 @@ #include "FrameDestructionObserverInlines.h" #include "InspectorPageAgent.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/inspector/InspectorCanvas.cpp b/Source/WebCore/inspector/InspectorCanvas.cpp index 335f42704069b..266b6e6557032 100644 --- a/Source/WebCore/inspector/InspectorCanvas.cpp +++ b/Source/WebCore/inspector/InspectorCanvas.cpp @@ -94,6 +94,7 @@ #include #include #include +#include #include #if ENABLE(OFFSCREEN_CANVAS) diff --git a/Source/WebCore/inspector/InspectorFrontendAPIDispatcher.cpp b/Source/WebCore/inspector/InspectorFrontendAPIDispatcher.cpp index c72d3978ed4a3..ecebbb32ca34c 100644 --- a/Source/WebCore/inspector/InspectorFrontendAPIDispatcher.cpp +++ b/Source/WebCore/inspector/InspectorFrontendAPIDispatcher.cpp @@ -37,6 +37,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/inspector/InspectorFrontendHost.cpp b/Source/WebCore/inspector/InspectorFrontendHost.cpp index 73b77eeb21deb..0ba9300faaa70 100644 --- a/Source/WebCore/inspector/InspectorFrontendHost.cpp +++ b/Source/WebCore/inspector/InspectorFrontendHost.cpp @@ -77,6 +77,7 @@ #include #include #include +#include #if PLATFORM(COCOA) #include diff --git a/Source/WebCore/inspector/InspectorOverlay.cpp b/Source/WebCore/inspector/InspectorOverlay.cpp index 101f23a77c5da..3b53f758a833d 100644 --- a/Source/WebCore/inspector/InspectorOverlay.cpp +++ b/Source/WebCore/inspector/InspectorOverlay.cpp @@ -75,6 +75,7 @@ #include "StyleResolver.h" #include "TextDirection.h" #include +#include #include #include diff --git a/Source/WebCore/inspector/InspectorOverlayLabel.cpp b/Source/WebCore/inspector/InspectorOverlayLabel.cpp index 056f9df58c94d..47180f260491f 100644 --- a/Source/WebCore/inspector/InspectorOverlayLabel.cpp +++ b/Source/WebCore/inspector/InspectorOverlayLabel.cpp @@ -35,6 +35,7 @@ #include "FontCascadeDescription.h" #include "GraphicsContext.h" #include "Path.h" +#include namespace WebCore { diff --git a/Source/WebCore/inspector/InspectorShaderProgram.cpp b/Source/WebCore/inspector/InspectorShaderProgram.cpp index b339d1f6e0afa..7a11eed23806f 100644 --- a/Source/WebCore/inspector/InspectorShaderProgram.cpp +++ b/Source/WebCore/inspector/InspectorShaderProgram.cpp @@ -39,6 +39,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/inspector/InspectorStyleSheet.cpp b/Source/WebCore/inspector/InspectorStyleSheet.cpp index 1bc02e5d2684d..d5a98f13c3c11 100644 --- a/Source/WebCore/inspector/InspectorStyleSheet.cpp +++ b/Source/WebCore/inspector/InspectorStyleSheet.cpp @@ -69,6 +69,7 @@ #include #include #include +#include #include using JSON::ArrayOf; @@ -1130,7 +1131,7 @@ ExceptionOr InspectorStyleSheet::setRuleHeaderText(const InspectorCSSId& i // include the space between the `@whatever` and the query/name/etc.. However, not all rules must contain a // space between those, for example `@media(...)`. We need to add the space if the new header text does not // start with an opening parenthesis, otherwise we will create an invalid declaration (e.g. `@mediascreen`). - correctedHeaderText = " "_s + correctedHeaderText; + correctedHeaderText = makeString(' ', correctedHeaderText); } sheetText = makeStringByReplacing(sheetText, sourceData->ruleHeaderRange.start, sourceData->ruleHeaderRange.length(), correctedHeaderText); diff --git a/Source/WebCore/inspector/RTCLogsCallback.idl b/Source/WebCore/inspector/RTCLogsCallback.idl index 011da0b3bad58..180392fbc07b1 100644 --- a/Source/WebCore/inspector/RTCLogsCallback.idl +++ b/Source/WebCore/inspector/RTCLogsCallback.idl @@ -35,4 +35,5 @@ [ Conditional=WEB_RTC, + IsStrongCallback ] callback RTCLogsCallback = undefined (RTCLogs logs); diff --git a/Source/WebCore/inspector/agents/InspectorAnimationAgent.cpp b/Source/WebCore/inspector/agents/InspectorAnimationAgent.cpp index 5f7b40e68f6e3..775f7c37b1aa7 100644 --- a/Source/WebCore/inspector/agents/InspectorAnimationAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorAnimationAgent.cpp @@ -61,6 +61,7 @@ #include #include #include +#include #include #include diff --git a/Source/WebCore/inspector/agents/InspectorApplicationCacheAgent.cpp b/Source/WebCore/inspector/agents/InspectorApplicationCacheAgent.cpp index 5e7425246a8bc..c5dd0c471727e 100644 --- a/Source/WebCore/inspector/agents/InspectorApplicationCacheAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorApplicationCacheAgent.cpp @@ -35,6 +35,7 @@ #include "LocalFrame.h" #include "Page.h" #include "PlatformStrategies.h" +#include namespace WebCore { diff --git a/Source/WebCore/inspector/agents/InspectorCSSAgent.cpp b/Source/WebCore/inspector/agents/InspectorCSSAgent.cpp index 3c4507527ee94..ca59d1f9b3654 100644 --- a/Source/WebCore/inspector/agents/InspectorCSSAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorCSSAgent.cpp @@ -77,7 +77,7 @@ #include #include #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp b/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp index 537ba581f7be9..ec5000cb553f2 100644 --- a/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp @@ -132,6 +132,7 @@ #include #include #include +#include #include #include #include diff --git a/Source/WebCore/inspector/agents/InspectorIndexedDBAgent.cpp b/Source/WebCore/inspector/agents/InspectorIndexedDBAgent.cpp index 87a309a53d4dd..e69eed28246a1 100644 --- a/Source/WebCore/inspector/agents/InspectorIndexedDBAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorIndexedDBAgent.cpp @@ -67,7 +67,7 @@ #include #include #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp b/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp index 23ca77bbd2d24..f8a58175bdaa1 100644 --- a/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp @@ -90,6 +90,7 @@ #include #include #include +#include #include #include diff --git a/Source/WebCore/inspector/agents/InspectorTimelineAgent.cpp b/Source/WebCore/inspector/agents/InspectorTimelineAgent.cpp index 09e85a0538fbb..224fc6684c929 100644 --- a/Source/WebCore/inspector/agents/InspectorTimelineAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorTimelineAgent.cpp @@ -57,6 +57,7 @@ #include #include #include +#include #if PLATFORM(IOS_FAMILY) #include "RuntimeApplicationChecks.h" diff --git a/Source/WebCore/inspector/agents/WebConsoleAgent.cpp b/Source/WebCore/inspector/agents/WebConsoleAgent.cpp index 24c3a866d3fca..5f54432aeac3e 100644 --- a/Source/WebCore/inspector/agents/WebConsoleAgent.cpp +++ b/Source/WebCore/inspector/agents/WebConsoleAgent.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.cpp b/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.cpp index a808ea712817e..6af4ad1ca22c8 100644 --- a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.cpp +++ b/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.cpp @@ -37,6 +37,7 @@ #include "TextUtil.h" #include #include +#include using WTF::Range; diff --git a/Source/WebCore/layout/integration/flex/LayoutIntegrationFlexLayout.cpp b/Source/WebCore/layout/integration/flex/LayoutIntegrationFlexLayout.cpp index af9b0c353f097..93eba8cfab857 100644 --- a/Source/WebCore/layout/integration/flex/LayoutIntegrationFlexLayout.cpp +++ b/Source/WebCore/layout/integration/flex/LayoutIntegrationFlexLayout.cpp @@ -70,12 +70,8 @@ static inline Layout::BoxGeometry::Edges flexBoxLogicalPadding(const RenderBoxMo UNUSED_PARAM(isLeftToRightInlineDirection); UNUSED_PARAM(blockFlowDirection); - auto paddingLeft = renderer.paddingLeft(); - auto paddingRight = renderer.paddingRight(); - auto paddingTop = renderer.paddingTop(); - auto paddingBottom = renderer.paddingBottom(); - - return { { paddingLeft, paddingRight }, { paddingTop, paddingBottom } }; + auto padding = renderer.padding(); + return { { padding.left(), padding.right() }, { padding.top(), padding.bottom() } }; } void FlexLayout::updateFormattingRootGeometryAndInvalidate() diff --git a/Source/WebCore/layout/layouttree/LayoutTreeBuilder.cpp b/Source/WebCore/layout/layouttree/LayoutTreeBuilder.cpp index bacdfae8bae8a..8cbd7f89dbfc6 100644 --- a/Source/WebCore/layout/layouttree/LayoutTreeBuilder.cpp +++ b/Source/WebCore/layout/layouttree/LayoutTreeBuilder.cpp @@ -58,6 +58,7 @@ #include "TextUtil.h" #include "WidthIterator.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/loader/CrossOriginAccessControl.cpp b/Source/WebCore/loader/CrossOriginAccessControl.cpp index f7b5e9bbd9543..230fcf024bb91 100644 --- a/Source/WebCore/loader/CrossOriginAccessControl.cpp +++ b/Source/WebCore/loader/CrossOriginAccessControl.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/loader/CrossOriginEmbedderPolicy.cpp b/Source/WebCore/loader/CrossOriginEmbedderPolicy.cpp index 8945497e835f2..ca0b237042bc5 100644 --- a/Source/WebCore/loader/CrossOriginEmbedderPolicy.cpp +++ b/Source/WebCore/loader/CrossOriginEmbedderPolicy.cpp @@ -41,6 +41,7 @@ #include "SecurityOrigin.h" #include "ViolationReportType.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/loader/CrossOriginOpenerPolicy.cpp b/Source/WebCore/loader/CrossOriginOpenerPolicy.cpp index ec9e384f1f3a6..25cba9daca5f8 100644 --- a/Source/WebCore/loader/CrossOriginOpenerPolicy.cpp +++ b/Source/WebCore/loader/CrossOriginOpenerPolicy.cpp @@ -41,6 +41,7 @@ #include "ScriptExecutionContext.h" #include "SecurityPolicy.h" #include "ViolationReportType.h" +#include namespace WebCore { diff --git a/Source/WebCore/loader/CrossOriginPreflightChecker.cpp b/Source/WebCore/loader/CrossOriginPreflightChecker.cpp index 3deb9b52e39ad..a26164cabb1d8 100644 --- a/Source/WebCore/loader/CrossOriginPreflightChecker.cpp +++ b/Source/WebCore/loader/CrossOriginPreflightChecker.cpp @@ -46,6 +46,7 @@ #include "NetworkLoadMetrics.h" #include "Quirks.h" #include "SharedBuffer.h" +#include namespace WebCore { diff --git a/Source/WebCore/loader/CrossOriginPreflightResultCache.cpp b/Source/WebCore/loader/CrossOriginPreflightResultCache.cpp index add24d0586adf..18a35b714b7c8 100644 --- a/Source/WebCore/loader/CrossOriginPreflightResultCache.cpp +++ b/Source/WebCore/loader/CrossOriginPreflightResultCache.cpp @@ -33,6 +33,7 @@ #include "ResourceResponse.h" #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/loader/DocumentLoader.cpp b/Source/WebCore/loader/DocumentLoader.cpp index 483cda4ccec32..8be71b98ce576 100644 --- a/Source/WebCore/loader/DocumentLoader.cpp +++ b/Source/WebCore/loader/DocumentLoader.cpp @@ -103,6 +103,7 @@ #include #include #include +#include #include #if ENABLE(APPLICATION_MANIFEST) diff --git a/Source/WebCore/loader/DocumentThreadableLoader.cpp b/Source/WebCore/loader/DocumentThreadableLoader.cpp index 27cc56673c4ca..d529d3cf1e756 100644 --- a/Source/WebCore/loader/DocumentThreadableLoader.cpp +++ b/Source/WebCore/loader/DocumentThreadableLoader.cpp @@ -65,6 +65,7 @@ #include "ThreadableLoaderClient.h" #include #include +#include #if PLATFORM(IOS_FAMILY) #include diff --git a/Source/WebCore/loader/EmptyClients.cpp b/Source/WebCore/loader/EmptyClients.cpp index 5789dc063d8b5..2ef2d248c9492 100644 --- a/Source/WebCore/loader/EmptyClients.cpp +++ b/Source/WebCore/loader/EmptyClients.cpp @@ -130,7 +130,7 @@ class EmptyContextMenuClient final : public ContextMenuClient { #endif #if ENABLE(WRITING_TOOLS) - void handleWritingTools(IntRect) final { }; + void handleWritingToolsDeprecated(IntRect) final { }; #endif #if PLATFORM(GTK) diff --git a/Source/WebCore/loader/FormSubmission.cpp b/Source/WebCore/loader/FormSubmission.cpp index 3c7e1d06ae697..f47cbc47d836d 100644 --- a/Source/WebCore/loader/FormSubmission.cpp +++ b/Source/WebCore/loader/FormSubmission.cpp @@ -51,6 +51,7 @@ #include "ScriptDisallowedScope.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/loader/FrameLoader.cpp b/Source/WebCore/loader/FrameLoader.cpp index de5816b1f30ca..aa59f7f53baf3 100644 --- a/Source/WebCore/loader/FrameLoader.cpp +++ b/Source/WebCore/loader/FrameLoader.cpp @@ -145,6 +145,7 @@ #include #include #include +#include #include #if ENABLE(WEB_ARCHIVE) || ENABLE(MHTML) @@ -1783,9 +1784,6 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t if (shouldPerformFragmentNavigation(isFormSubmission, httpMethod, policyChecker().loadType(), newURL)) { - if (!dispatchNavigateEvent(newURL, type, loader->triggeringAction(), NavigationHistoryBehavior::Auto, true, formState.get())) - return; - RefPtr oldDocumentLoader = m_documentLoader; NavigationAction action { frame->protectedDocument().releaseNonNull(), loader->request(), InitiatedByMainFrame::Unknown, loader->isRequestFromClientOrUserInput(), policyChecker().loadType(), isFormSubmission }; action.setNavigationAPIType(determineNavigationType(type, NavigationHistoryBehavior::Auto)); @@ -1802,12 +1800,6 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t return; } - auto& action = loader->triggeringAction(); - if (m_frame->document() && action.requester() && m_frame->document()->securityOrigin().isSameOriginDomain(action.requester()->securityOrigin)) { - if (!dispatchNavigateEvent(newURL, type, action, NavigationHistoryBehavior::Auto, false, formState.get())) - return; - } - if (RefPtr parent = dynamicDowncast(frame->tree().parent())) loader->setOverrideEncoding(parent->loader().documentLoader()->overrideEncoding()); diff --git a/Source/WebCore/loader/HistoryController.cpp b/Source/WebCore/loader/HistoryController.cpp index c161f59f3706d..5cfe601b34128 100644 --- a/Source/WebCore/loader/HistoryController.cpp +++ b/Source/WebCore/loader/HistoryController.cpp @@ -227,7 +227,7 @@ void HistoryController::saveDocumentAndScrollState() continue; CheckedRef history = localFrame->history(); history->saveDocumentState(); - history->saveScrollPositionAndViewStateToItem(history->currentItem()); + history->saveScrollPositionAndViewStateToItem(history->protectedCurrentItem().get()); } } @@ -414,7 +414,7 @@ void HistoryController::updateForStandardLoad(HistoryUpdateType updateType) CheckedRef frameLoader = frame->loader(); bool usesEphemeralSession = m_frame->page() ? m_frame->page()->usesEphemeralSession() : true; - const URL& historyURL = frameLoader->documentLoader()->urlForHistory(); + const URL& historyURL = frameLoader->protectedDocumentLoader()->urlForHistory(); RefPtr documentLoader = frameLoader->documentLoader(); if (!frameLoader->documentLoader()->isClientRedirect()) { @@ -501,7 +501,7 @@ void HistoryController::updateForClientRedirect() } bool usesEphemeralSession = frame->page() ? frame->page()->usesEphemeralSession() : true; - const URL& historyURL = frame->loader().documentLoader()->urlForHistory(); + const URL& historyURL = frame->loader().protectedDocumentLoader()->urlForHistory(); if (!historyURL.isEmpty() && !usesEphemeralSession) { if (RefPtr page = frame->page()) @@ -571,7 +571,7 @@ void HistoryController::recursiveUpdateForCommit() saveDocumentState(); saveScrollPositionAndViewStateToItem(protectedCurrentItem().get()); - if (auto* view = dynamicDowncast(m_frame->virtualView())) + if (RefPtr view = dynamicDowncast(m_frame->virtualView())) view->setWasScrolledByUser(false); // Now commit the provisional item @@ -859,7 +859,7 @@ void HistoryController::updateBackForwardListClippedAtTarget(bool doClip) if (!page) return; - if (frame->loader().documentLoader()->urlForHistory().isEmpty()) + if (frame->loader().protectedDocumentLoader()->urlForHistory().isEmpty()) return; Ref mainFrame = frame->mainFrame(); @@ -914,11 +914,8 @@ void HistoryController::pushState(RefPtr&& stateObject, c bool shouldRestoreScrollPosition = m_currentItem->shouldRestoreScrollPosition(); // Get a HistoryItem tree for the current frame tree. - RefPtr localFrame = dynamicDowncast(frame->mainFrame()); - if (!localFrame) - return; - - Ref topItem = localFrame->checkedHistory()->createItemTree(page->historyItemClient(), *frame, false); + Ref mainFrame = frame->mainFrame(); + Ref topItem = mainFrame->checkedHistory()->createItemTree(page->historyItemClient(), *frame, false); RefPtr document = frame->document(); if (document && !document->hasRecentUserInteractionForNavigationFromJS()) @@ -942,7 +939,7 @@ void HistoryController::pushState(RefPtr&& stateObject, c frame->checkedLoader()->client().updateGlobalHistory(); if (document && document->settings().navigationAPIEnabled()) - document->protectedWindow()->navigation().updateForNavigation(*currentItem, NavigationNavigationType::Push); + document->protectedWindow()->protectedNavigation()->updateForNavigation(*currentItem, NavigationNavigationType::Push); } void HistoryController::replaceState(RefPtr&& stateObject, const String& urlString) @@ -973,7 +970,7 @@ void HistoryController::replaceState(RefPtr&& stateObject if (RefPtr document = frame->document(); document && document->settings().navigationAPIEnabled()) { currentItem->setNavigationAPIStateObject(nullptr); - document->protectedWindow()->navigation().updateForNavigation(*currentItem, NavigationNavigationType::Replace); + document->protectedWindow()->protectedNavigation()->updateForNavigation(*currentItem, NavigationNavigationType::Replace); } } diff --git a/Source/WebCore/loader/ImageLoader.cpp b/Source/WebCore/loader/ImageLoader.cpp index d31f512663433..59a2fd8971060 100644 --- a/Source/WebCore/loader/ImageLoader.cpp +++ b/Source/WebCore/loader/ImageLoader.cpp @@ -52,6 +52,7 @@ #include "Settings.h" #include #include +#include #include #if ENABLE(VIDEO) diff --git a/Source/WebCore/loader/LinkLoader.cpp b/Source/WebCore/loader/LinkLoader.cpp index 98bdaf4f34184..e5a10ce755bf6 100644 --- a/Source/WebCore/loader/LinkLoader.cpp +++ b/Source/WebCore/loader/LinkLoader.cpp @@ -62,6 +62,7 @@ #include "Settings.h" #include "SizesAttributeParser.h" #include "StyleResolver.h" +#include namespace WebCore { diff --git a/Source/WebCore/loader/MixedContentChecker.cpp b/Source/WebCore/loader/MixedContentChecker.cpp index 159e4d39f615a..dc59323ade7aa 100644 --- a/Source/WebCore/loader/MixedContentChecker.cpp +++ b/Source/WebCore/loader/MixedContentChecker.cpp @@ -40,6 +40,7 @@ #include "LocalFrameLoaderClient.h" #include "Quirks.h" #include "SecurityOrigin.h" +#include #if PLATFORM(IOS_FAMILY) #include diff --git a/Source/WebCore/loader/PrivateClickMeasurement.cpp b/Source/WebCore/loader/PrivateClickMeasurement.cpp index a42b39fc10e26..d8921dd1fc204 100644 --- a/Source/WebCore/loader/PrivateClickMeasurement.cpp +++ b/Source/WebCore/loader/PrivateClickMeasurement.cpp @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include diff --git a/Source/WebCore/loader/ResourceLoader.cpp b/Source/WebCore/loader/ResourceLoader.cpp index 3c91699457e55..a8271e0b736f1 100644 --- a/Source/WebCore/loader/ResourceLoader.cpp +++ b/Source/WebCore/loader/ResourceLoader.cpp @@ -61,6 +61,7 @@ #include "SubresourceLoader.h" #include #include +#include #if ENABLE(CONTENT_EXTENSIONS) #include "UserContentController.h" diff --git a/Source/WebCore/loader/SubresourceIntegrity.cpp b/Source/WebCore/loader/SubresourceIntegrity.cpp index ee20271611bcf..e7a218a8bbcf6 100644 --- a/Source/WebCore/loader/SubresourceIntegrity.cpp +++ b/Source/WebCore/loader/SubresourceIntegrity.cpp @@ -30,6 +30,7 @@ #include "ParsingUtilities.h" #include "ResourceCryptographicDigest.h" #include "SharedBuffer.h" +#include #include namespace WebCore { @@ -202,7 +203,7 @@ bool matchIntegrityMetadataSlow(const CachedResource& resource, const String& in String integrityMismatchDescription(const CachedResource& resource, const String& integrityMetadata) { auto resourceURL = resource.url().stringCenterEllipsizedToLength(); - if (auto resourceBuffer = resource.resourceBuffer()) { + if (RefPtr resourceBuffer = resource.resourceBuffer()) { return makeString(resourceURL, ". Failed integrity metadata check. Content length: "_s, resourceBuffer->size(), ", Expected content length: "_s, resource.response().expectedContentLength(), ", Expected metadata: "_s, integrityMetadata); } diff --git a/Source/WebCore/loader/SubresourceLoader.cpp b/Source/WebCore/loader/SubresourceLoader.cpp index fe7a07ad03994..9284724baba41 100644 --- a/Source/WebCore/loader/SubresourceLoader.cpp +++ b/Source/WebCore/loader/SubresourceLoader.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #if PLATFORM(IOS_FAMILY) #include diff --git a/Source/WebCore/loader/TextResourceDecoder.cpp b/Source/WebCore/loader/TextResourceDecoder.cpp index 45d2338804f69..b38a0a659dd03 100644 --- a/Source/WebCore/loader/TextResourceDecoder.cpp +++ b/Source/WebCore/loader/TextResourceDecoder.cpp @@ -31,6 +31,7 @@ #include #include #include +#include namespace WebCore { @@ -662,8 +663,8 @@ String TextResourceDecoder::flush() String TextResourceDecoder::decodeAndFlush(std::span data) { - String decoded = decode(data); - return decoded + flush(); + auto decoded = decode(data); + return makeString(decoded, flush()); } const PAL::TextEncoding* TextResourceDecoder::encodingForURLParsing() diff --git a/Source/WebCore/loader/ThreadableLoader.cpp b/Source/WebCore/loader/ThreadableLoader.cpp index 5925c63df5e95..5d2b18cf39d18 100644 --- a/Source/WebCore/loader/ThreadableLoader.cpp +++ b/Source/WebCore/loader/ThreadableLoader.cpp @@ -42,6 +42,7 @@ #include "WorkerRunLoop.h" #include "WorkerThreadableLoader.h" #include "WorkletGlobalScope.h" +#include namespace WebCore { diff --git a/Source/WebCore/loader/WorkerThreadableLoader.cpp b/Source/WebCore/loader/WorkerThreadableLoader.cpp index 8ee579837f33f..0033c5e212cdc 100644 --- a/Source/WebCore/loader/WorkerThreadableLoader.cpp +++ b/Source/WebCore/loader/WorkerThreadableLoader.cpp @@ -52,6 +52,7 @@ #include "WorkerThread.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/loader/appcache/ApplicationCacheGroup.cpp b/Source/WebCore/loader/appcache/ApplicationCacheGroup.cpp index 0cb3723b1d70e..7c4a8c07efa87 100644 --- a/Source/WebCore/loader/appcache/ApplicationCacheGroup.cpp +++ b/Source/WebCore/loader/appcache/ApplicationCacheGroup.cpp @@ -51,6 +51,7 @@ #include "Settings.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp b/Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp index 437275ef55fdf..10e51f2346916 100644 --- a/Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp +++ b/Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp @@ -40,8 +40,8 @@ #include #include #include +#include #include -#include namespace WebCore { diff --git a/Source/WebCore/loader/archive/cf/LegacyWebArchive.cpp b/Source/WebCore/loader/archive/cf/LegacyWebArchive.cpp index f641eeb46ac09..a7b3d2e092d4b 100644 --- a/Source/WebCore/loader/archive/cf/LegacyWebArchive.cpp +++ b/Source/WebCore/loader/archive/cf/LegacyWebArchive.cpp @@ -61,8 +61,9 @@ #include #include #include -#include #include +#include +#include namespace WebCore { @@ -506,7 +507,7 @@ RefPtr LegacyWebArchive::create(Node& node, Function LegacyWebArchive::create(const SimpleRange& range) // FIXME: This is always "for interchange". Is that right? Vector> nodeList; - String markupString = documentTypeString(document) + serializePreservingVisualAppearance(range, &nodeList, AnnotateForInterchange::Yes); + auto markupString = makeString(documentTypeString(document), serializePreservingVisualAppearance(range, &nodeList, AnnotateForInterchange::Yes)); return create(markupString, *frame, WTFMove(nodeList), nullptr); } diff --git a/Source/WebCore/loader/cache/CachedFontLoadRequest.h b/Source/WebCore/loader/cache/CachedFontLoadRequest.h index 305b364eaf63f..7f12e8f898a04 100644 --- a/Source/WebCore/loader/cache/CachedFontLoadRequest.h +++ b/Source/WebCore/loader/cache/CachedFontLoadRequest.h @@ -32,6 +32,7 @@ #include "FontLoadRequest.h" #include "FontSelectionAlgorithm.h" #include "ScriptExecutionContext.h" +#include namespace WebCore { diff --git a/Source/WebCore/loader/cache/CachedResourceLoader.cpp b/Source/WebCore/loader/cache/CachedResourceLoader.cpp index 5b1876ae33c30..b3a62148b02e0 100644 --- a/Source/WebCore/loader/cache/CachedResourceLoader.cpp +++ b/Source/WebCore/loader/cache/CachedResourceLoader.cpp @@ -86,6 +86,7 @@ #include "UserStyleSheet.h" #include #include +#include #include #if ENABLE(APPLICATION_MANIFEST) diff --git a/Source/WebCore/loader/cache/TrustedFonts.cpp b/Source/WebCore/loader/cache/TrustedFonts.cpp index 17400834b503b..5c749c09f6599 100644 --- a/Source/WebCore/loader/cache/TrustedFonts.cpp +++ b/Source/WebCore/loader/cache/TrustedFonts.cpp @@ -27,13 +27,13 @@ #include "TrustedFonts.h" #include "Logging.h" - #include #include #include #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/loader/cocoa/BundleResourceLoader.mm b/Source/WebCore/loader/cocoa/BundleResourceLoader.mm index 99faa2b3938e4..a4b3030cdc2b5 100644 --- a/Source/WebCore/loader/cocoa/BundleResourceLoader.mm +++ b/Source/WebCore/loader/cocoa/BundleResourceLoader.mm @@ -42,8 +42,8 @@ static WorkQueue& loadQueue() { - static auto& queue = WorkQueue::create("org.WebKit.BundleResourceLoader"_s, WorkQueue::QOS::Utility).leakRef(); - return queue; + static NeverDestroyed> queue(WorkQueue::create("org.WebKit.BundleResourceLoader"_s, WorkQueue::QOS::Utility)); + return queue.get(); } void loadResourceFromBundle(ResourceLoader& loader, const String& subdirectory) diff --git a/Source/WebCore/loader/cocoa/PrivateClickMeasurementCocoa.mm b/Source/WebCore/loader/cocoa/PrivateClickMeasurementCocoa.mm index 2076172d62db4..f56596b03aa1c 100644 --- a/Source/WebCore/loader/cocoa/PrivateClickMeasurementCocoa.mm +++ b/Source/WebCore/loader/cocoa/PrivateClickMeasurementCocoa.mm @@ -27,6 +27,7 @@ #import "PrivateClickMeasurement.h" #import +#import #import diff --git a/Source/WebCore/mathml/MathMLElement.cpp b/Source/WebCore/mathml/MathMLElement.cpp index 55b85d73a0bab..42be9247bc79b 100644 --- a/Source/WebCore/mathml/MathMLElement.cpp +++ b/Source/WebCore/mathml/MathMLElement.cpp @@ -44,7 +44,7 @@ #include "RenderTableCell.h" #include "Settings.h" #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/page/CaptionUserPreferencesMediaAF.cpp b/Source/WebCore/page/CaptionUserPreferencesMediaAF.cpp index 2bd337f1e59bb..636ccdff9344c 100644 --- a/Source/WebCore/page/CaptionUserPreferencesMediaAF.cpp +++ b/Source/WebCore/page/CaptionUserPreferencesMediaAF.cpp @@ -46,8 +46,8 @@ #include #include #include +#include #include -#include #include #include diff --git a/Source/WebCore/page/ChromeClient.h b/Source/WebCore/page/ChromeClient.h index 8b21089b3e0bf..1712a19473ab6 100644 --- a/Source/WebCore/page/ChromeClient.h +++ b/Source/WebCore/page/ChromeClient.h @@ -678,9 +678,11 @@ class ChromeClient { virtual void addInitialTextAnimation(const WritingTools::SessionID&) { } - virtual void addSourceTextAnimation(const WritingTools::SessionID&, const CharacterRange&) { } + virtual void addSourceTextAnimation(const WritingTools::SessionID&, const CharacterRange&, const String, WTF::CompletionHandler&&) { } - virtual void addDestinationTextAnimation(const WritingTools::SessionID&, const CharacterRange&) { } + virtual void addDestinationTextAnimation(const WritingTools::SessionID&, const CharacterRange&, const String) { } + + virtual void clearAnimationsForSessionID(const WritingTools::SessionID&) { }; #endif virtual void hasActiveNowPlayingSessionChanged(bool) { } diff --git a/Source/WebCore/page/ClientOrigin.h b/Source/WebCore/page/ClientOrigin.h index 71edefb3f4fb1..bf13283cd7957 100644 --- a/Source/WebCore/page/ClientOrigin.h +++ b/Source/WebCore/page/ClientOrigin.h @@ -30,6 +30,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/page/ContextMenuClient.h b/Source/WebCore/page/ContextMenuClient.h index 2d0009978806b..ea24140f4af58 100644 --- a/Source/WebCore/page/ContextMenuClient.h +++ b/Source/WebCore/page/ContextMenuClient.h @@ -63,7 +63,7 @@ class ContextMenuClient { #endif #if ENABLE(WRITING_TOOLS) - virtual void handleWritingTools(IntRect selectionBoundsInRootView) = 0; + virtual void handleWritingToolsDeprecated(IntRect selectionBoundsInRootView) = 0; #endif #if PLATFORM(GTK) diff --git a/Source/WebCore/page/ContextMenuController.cpp b/Source/WebCore/page/ContextMenuController.cpp index dd82e850fc630..fa1121deb38cd 100644 --- a/Source/WebCore/page/ContextMenuController.cpp +++ b/Source/WebCore/page/ContextMenuController.cpp @@ -650,7 +650,7 @@ void ContextMenuController::contextMenuItemSelected(ContextMenuAction action, co case ContextMenuItemTagWritingTools: #if ENABLE(WRITING_TOOLS) if (RefPtr view = frame->view()) - m_client->handleWritingTools(view->contentsToRootView(enclosingIntRect(frame->selection().selectionBounds()))); + m_client->handleWritingToolsDeprecated(view->contentsToRootView(enclosingIntRect(frame->selection().selectionBounds()))); #endif break; @@ -1044,11 +1044,6 @@ void ContextMenuController::populate() appendItem(translateItem, m_contextMenu.get()); #endif -#if ENABLE(WRITING_TOOLS) - ContextMenuItem writingToolsItem(ContextMenuItemType::Action, ContextMenuItemTagWritingTools, contextMenuItemTagWritingTools()); - appendItem(writingToolsItem, m_contextMenu.get()); -#endif - #if !PLATFORM(GTK) appendItem(SearchWebItem, m_contextMenu.get()); appendItem(*separatorItem(), m_contextMenu.get()); @@ -1190,6 +1185,12 @@ void ContextMenuController::populate() appendItem(ShareMenuItem, m_contextMenu.get()); appendItem(*separatorItem(), m_contextMenu.get()); +#if ENABLE(WRITING_TOOLS) + appendItem(*separatorItem(), m_contextMenu.get()); + ContextMenuItem writingToolsItem(ContextMenuItemType::Action, ContextMenuItemTagWritingTools, contextMenuItemTagWritingTools()); + appendItem(writingToolsItem, m_contextMenu.get()); +#endif + ContextMenuItem SpeechMenuItem(ContextMenuItemType::Submenu, ContextMenuItemTagSpeechMenu, contextMenuItemTagSpeechMenu()); createAndAppendSpeechSubMenu(SpeechMenuItem); appendItem(SpeechMenuItem, m_contextMenu.get()); @@ -1341,8 +1342,16 @@ void ContextMenuController::populate() #endif if (!inPasswordField) { +#if ENABLE(WRITING_TOOLS) + appendItem(*separatorItem(), m_contextMenu.get()); + ContextMenuItem writingToolsItem(ContextMenuItemType::Action, ContextMenuItemTagWritingTools, contextMenuItemTagWritingTools()); + appendItem(writingToolsItem, m_contextMenu.get()); +#endif + #if !PLATFORM(GTK) +#if !ENABLE(WRITING_TOOLS) appendItem(*separatorItem(), m_contextMenu.get()); +#endif ContextMenuItem SpellingAndGrammarMenuItem(ContextMenuItemType::Submenu, ContextMenuItemTagSpellingMenu, contextMenuItemTagSpellingMenu()); createAndAppendSpellingAndGrammarSubMenu(SpellingAndGrammarMenuItem); diff --git a/Source/WebCore/page/DebugPageOverlays.cpp b/Source/WebCore/page/DebugPageOverlays.cpp index f3a3f4e51a133..014b135edcd0a 100644 --- a/Source/WebCore/page/DebugPageOverlays.cpp +++ b/Source/WebCore/page/DebugPageOverlays.cpp @@ -48,6 +48,7 @@ #include "ScrollingCoordinator.h" #include "Settings.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/page/DeprecatedGlobalSettings.h b/Source/WebCore/page/DeprecatedGlobalSettings.h index c7f7ecf3befd1..878d5279148e5 100644 --- a/Source/WebCore/page/DeprecatedGlobalSettings.h +++ b/Source/WebCore/page/DeprecatedGlobalSettings.h @@ -144,7 +144,7 @@ class DeprecatedGlobalSettings { static bool mediaSourceInlinePaintingEnabled() { return shared().m_mediaSourceInlinePaintingEnabled; } #endif -#if ENABLE(BUILT_IN_NOTIFICATIONS) +#if ENABLE(WEB_PUSH_NOTIFICATIONS) static void setBuiltInNotificationsEnabled(bool isEnabled) { shared().m_builtInNotificationsEnabled = isEnabled; } static bool builtInNotificationsEnabled() { return shared().m_builtInNotificationsEnabled; } #endif @@ -226,7 +226,7 @@ class DeprecatedGlobalSettings { bool m_mediaSourceInlinePaintingEnabled { false }; #endif -#if ENABLE(BUILT_IN_NOTIFICATIONS) +#if ENABLE(WEB_PUSH_NOTIFICATIONS) bool m_builtInNotificationsEnabled { false }; #endif diff --git a/Source/WebCore/page/DragController.cpp b/Source/WebCore/page/DragController.cpp index d2e660fb1ab2b..28bd5b342d9e9 100644 --- a/Source/WebCore/page/DragController.cpp +++ b/Source/WebCore/page/DragController.cpp @@ -98,6 +98,7 @@ #include "WebContentReader.h" #include "markup.h" #include +#include #endif #if ENABLE(DATA_DETECTION) diff --git a/Source/WebCore/page/ElementTargetingController.cpp b/Source/WebCore/page/ElementTargetingController.cpp index 85aa39df5e7bd..886b68ce9db39 100644 --- a/Source/WebCore/page/ElementTargetingController.cpp +++ b/Source/WebCore/page/ElementTargetingController.cpp @@ -67,6 +67,7 @@ #include "TypedElementDescendantIteratorInlines.h" #include "VisibilityAdjustment.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/page/EventSource.cpp b/Source/WebCore/page/EventSource.cpp index 26c5d680a5431..438dda439beb7 100644 --- a/Source/WebCore/page/EventSource.cpp +++ b/Source/WebCore/page/EventSource.cpp @@ -49,6 +49,7 @@ #include "ThreadableLoader.h" #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/page/FrameTree.cpp b/Source/WebCore/page/FrameTree.cpp index 7087a29e3cf20..57e9fee5897a0 100644 --- a/Source/WebCore/page/FrameTree.cpp +++ b/Source/WebCore/page/FrameTree.cpp @@ -32,7 +32,6 @@ #include #include #include -#include namespace WebCore { diff --git a/Source/WebCore/page/History.cpp b/Source/WebCore/page/History.cpp index f294330324f99..22a7e6a441577 100644 --- a/Source/WebCore/page/History.cpp +++ b/Source/WebCore/page/History.cpp @@ -46,7 +46,7 @@ #include #include #include -#include +#include #if PLATFORM(COCOA) #include @@ -254,54 +254,54 @@ ExceptionOr History::stateObjectAdded(RefPtr&& data if (!allowSandboxException && !documentSecurityOrigin->canRequest(fullURL, OriginAccessPatternsForWebProcess::singleton()) && (fullURL.path() != documentURL.path() || fullURL.query() != documentURL.query())) return createBlockedURLSecurityErrorWithMessageSuffix("Paths and fragments must match for a sandboxed document."_s); - RefPtr localMainFrame = dynamicDowncast(frame->page()->mainFrame()); - RefPtr mainWindow = localMainFrame ? localMainFrame->window() : nullptr; - if (!mainWindow) - return { }; - - Ref mainHistory = mainWindow->history(); - - WallTime currentTimestamp = WallTime::now(); - if (currentTimestamp - mainHistory->m_currentStateObjectTimeSpanStart > stateObjectTimeSpan) { - mainHistory->m_currentStateObjectTimeSpanStart = currentTimestamp; - mainHistory->m_currentStateObjectTimeSpanObjectsAdded = 0; - } - - if (mainHistory->m_currentStateObjectTimeSpanObjectsAdded >= perStateObjectTimeSpanLimit) { - if (stateObjectType == StateObjectType::Replace) - return Exception { ExceptionCode::SecurityError, makeString("Attempt to use history.replaceState() more than "_s, perStateObjectTimeSpanLimit, " times per "_s, stateObjectTimeSpan.seconds(), " seconds"_s) }; - return Exception { ExceptionCode::SecurityError, makeString("Attempt to use history.pushState() more than "_s, perStateObjectTimeSpanLimit, " times per "_s, stateObjectTimeSpan.seconds(), " seconds"_s) }; - } - - if (RefPtr document = frame->document(); document && document->settings().navigationAPIEnabled()) { - auto& navigation = document->domWindow()->navigation(); - if (!navigation.dispatchPushReplaceReloadNavigateEvent(fullURL, stateObjectType == StateObjectType::Push ? NavigationNavigationType::Push : NavigationNavigationType::Replace, true, nullptr, data.get())) - return { }; - } - Checked urlSize = fullURL.string().length(); urlSize *= 2; Checked payloadSize = urlSize; payloadSize += data ? data->wireBytes().size() : 0; - Checked newTotalUsage = mainHistory->m_totalStateObjectUsage; + if (RefPtr localMainFrame = dynamicDowncast(frame->page()->mainFrame())) { + RefPtr mainWindow = localMainFrame->window(); + if (!mainWindow) + return { }; + Ref mainHistory = mainWindow->history(); + + WallTime currentTimestamp = WallTime::now(); + if (currentTimestamp - mainHistory->m_currentStateObjectTimeSpanStart > stateObjectTimeSpan) { + mainHistory->m_currentStateObjectTimeSpanStart = currentTimestamp; + mainHistory->m_currentStateObjectTimeSpanObjectsAdded = 0; + } + + if (mainHistory->m_currentStateObjectTimeSpanObjectsAdded >= perStateObjectTimeSpanLimit) { + if (stateObjectType == StateObjectType::Replace) + return Exception { ExceptionCode::SecurityError, makeString("Attempt to use history.replaceState() more than "_s, perStateObjectTimeSpanLimit, " times per "_s, stateObjectTimeSpan.seconds(), " seconds"_s) }; + return Exception { ExceptionCode::SecurityError, makeString("Attempt to use history.pushState() more than "_s, perStateObjectTimeSpanLimit, " times per "_s, stateObjectTimeSpan.seconds(), " seconds"_s) }; + } - if (stateObjectType == StateObjectType::Replace) - newTotalUsage -= m_mostRecentStateObjectUsage; - newTotalUsage += payloadSize; + if (RefPtr document = frame->document(); document && document->settings().navigationAPIEnabled()) { + Ref navigation = document->domWindow()->navigation(); + if (!navigation->dispatchPushReplaceReloadNavigateEvent(fullURL, stateObjectType == StateObjectType::Push ? NavigationNavigationType::Push : NavigationNavigationType::Replace, true, nullptr, data.get())) + return { }; + } + + Checked newTotalUsage = mainHistory->m_totalStateObjectUsage; - if (newTotalUsage > mainHistory->totalStateObjectPayloadLimit()) { if (stateObjectType == StateObjectType::Replace) - return Exception { ExceptionCode::QuotaExceededError, "Attempt to store more data than allowed using history.replaceState()"_s }; - return Exception { ExceptionCode::QuotaExceededError, "Attempt to store more data than allowed using history.pushState()"_s }; + newTotalUsage -= m_mostRecentStateObjectUsage; + newTotalUsage += payloadSize; + + if (newTotalUsage > mainHistory->totalStateObjectPayloadLimit()) { + if (stateObjectType == StateObjectType::Replace) + return Exception { ExceptionCode::QuotaExceededError, "Attempt to store more data than allowed using history.replaceState()"_s }; + return Exception { ExceptionCode::QuotaExceededError, "Attempt to store more data than allowed using history.pushState()"_s }; + } + + mainHistory->m_totalStateObjectUsage = newTotalUsage; + ++mainHistory->m_currentStateObjectTimeSpanObjectsAdded; } m_mostRecentStateObjectUsage = payloadSize; - mainHistory->m_totalStateObjectUsage = newTotalUsage; - ++mainHistory->m_currentStateObjectTimeSpanObjectsAdded; - if (!urlString.isEmpty()) frame->protectedDocument()->updateURLForPushOrReplaceState(fullURL); diff --git a/Source/WebCore/page/InteractionRegion.cpp b/Source/WebCore/page/InteractionRegion.cpp index c57c4f77c85e1..a20f2a400896e 100644 --- a/Source/WebCore/page/InteractionRegion.cpp +++ b/Source/WebCore/page/InteractionRegion.cpp @@ -60,6 +60,7 @@ #include "StyleResolver.h" #include "TextIterator.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/page/IntersectionObserverCallback.idl b/Source/WebCore/page/IntersectionObserverCallback.idl index fb2c49e7e3696..881bf219765dd 100644 --- a/Source/WebCore/page/IntersectionObserverCallback.idl +++ b/Source/WebCore/page/IntersectionObserverCallback.idl @@ -25,7 +25,4 @@ // https://wicg.github.io/IntersectionObserver/ -[ - CallbackThisObject=IntersectionObserver, - IsWeakCallback, -] callback IntersectionObserverCallback = undefined (sequence entries, IntersectionObserver observer); +[ CallbackThisObject=IntersectionObserver ] callback IntersectionObserverCallback = undefined (sequence entries, IntersectionObserver observer); diff --git a/Source/WebCore/page/LocalDOMWindow.cpp b/Source/WebCore/page/LocalDOMWindow.cpp index 39b1b3cfbcac0..0885fae907ac3 100644 --- a/Source/WebCore/page/LocalDOMWindow.cpp +++ b/Source/WebCore/page/LocalDOMWindow.cpp @@ -140,6 +140,7 @@ #include #include #include +#include #include #if ENABLE(USER_MESSAGE_HANDLERS) @@ -2524,11 +2525,11 @@ String LocalDOMWindow::crossDomainAccessErrorMessage(const LocalDOMWindow& activ // 'document.domain' errors. if (targetOrigin->domainWasSetInDOM() && activeOrigin->domainWasSetInDOM()) - return makeString(message + "The frame requesting access set \"document.domain\" to \""_s, activeOrigin->domain(), "\", the frame being accessed set it to \""_s, targetOrigin->domain(), "\". Both must set \"document.domain\" to the same value to allow access."_s); + return makeString(message, "The frame requesting access set \"document.domain\" to \""_s, activeOrigin->domain(), "\", the frame being accessed set it to \""_s, targetOrigin->domain(), "\". Both must set \"document.domain\" to the same value to allow access."_s); if (activeOrigin->domainWasSetInDOM()) - return makeString(message + "The frame requesting access set \"document.domain\" to \""_s, activeOrigin->domain(), "\", but the frame being accessed did not. Both must set \"document.domain\" to the same value to allow access."_s); + return makeString(message, "The frame requesting access set \"document.domain\" to \""_s, activeOrigin->domain(), "\", but the frame being accessed did not. Both must set \"document.domain\" to the same value to allow access."_s); if (targetOrigin->domainWasSetInDOM()) - return makeString(message + "The frame being accessed set \"document.domain\" to \""_s, targetOrigin->domain(), "\", but the frame requesting access did not. Both must set \"document.domain\" to the same value to allow access."_s); + return makeString(message, "The frame being accessed set \"document.domain\" to \""_s, targetOrigin->domain(), "\", but the frame requesting access did not. Both must set \"document.domain\" to the same value to allow access."_s); } // Default. diff --git a/Source/WebCore/page/LocalFrameView.cpp b/Source/WebCore/page/LocalFrameView.cpp index c9ba3755a89f4..1c590ffd2a2b2 100644 --- a/Source/WebCore/page/LocalFrameView.cpp +++ b/Source/WebCore/page/LocalFrameView.cpp @@ -130,6 +130,7 @@ #include #include #include +#include #include #if USE(COORDINATED_GRAPHICS) diff --git a/Source/WebCore/page/Location.cpp b/Source/WebCore/page/Location.cpp index 096d22bfb8562..2798e2a37a9be 100644 --- a/Source/WebCore/page/Location.cpp +++ b/Source/WebCore/page/Location.cpp @@ -38,6 +38,7 @@ #include "SecurityOrigin.h" #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/page/LoginStatus.cpp b/Source/WebCore/page/LoginStatus.cpp index be378b8b49346..7641ece450ef1 100644 --- a/Source/WebCore/page/LoginStatus.cpp +++ b/Source/WebCore/page/LoginStatus.cpp @@ -27,8 +27,8 @@ #include "LoginStatus.h" #include +#include #include -#include namespace WebCore { diff --git a/Source/WebCore/page/MemoryRelease.cpp b/Source/WebCore/page/MemoryRelease.cpp index 07f06d2e9d816..0051f4bdfbc50 100644 --- a/Source/WebCore/page/MemoryRelease.cpp +++ b/Source/WebCore/page/MemoryRelease.cpp @@ -67,6 +67,7 @@ #include #include #include +#include #if PLATFORM(COCOA) #include "ResourceUsageThread.h" diff --git a/Source/WebCore/page/Navigation.cpp b/Source/WebCore/page/Navigation.cpp index 0e1b19c8e9116..1fa2e868ad5ce 100644 --- a/Source/WebCore/page/Navigation.cpp +++ b/Source/WebCore/page/Navigation.cpp @@ -310,7 +310,7 @@ Navigation::Result Navigation::navigate(const String& url, NavigateOptions&& opt // If the load() call never made it to the point that NavigateEvent was emitted, thus promoteUpcomingAPIMethodTracker() called, this will be true. if (m_upcomingNonTraverseMethodTracker == apiMethodTracker) { m_upcomingNonTraverseMethodTracker = nullptr; - // FIXME: This should return an early error. + return createErrorResult(WTFMove(apiMethodTracker->committedPromise), WTFMove(apiMethodTracker->finishedPromise), ExceptionCode::AbortError, "Navigation aborted"_s); } return apiMethodTrackerDerivedResult(*apiMethodTracker); diff --git a/Source/WebCore/page/NavigationInterceptHandler.idl b/Source/WebCore/page/NavigationInterceptHandler.idl index 342e866073321..1c8523281f593 100644 --- a/Source/WebCore/page/NavigationInterceptHandler.idl +++ b/Source/WebCore/page/NavigationInterceptHandler.idl @@ -23,5 +23,8 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -[EnabledBySetting=NavigationAPIEnabled] -callback NavigationInterceptHandler = Promise (); +[ + EnabledBySetting=NavigationAPIEnabled, + IsStrongCallback, + SkipCallbackInvokeCheck +] callback NavigationInterceptHandler = Promise (); diff --git a/Source/WebCore/page/NavigatorBase.cpp b/Source/WebCore/page/NavigatorBase.cpp index 67d4497c0d8b2..71cb38b1cf266 100644 --- a/Source/WebCore/page/NavigatorBase.cpp +++ b/Source/WebCore/page/NavigatorBase.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #if OS(LINUX) diff --git a/Source/WebCore/page/Page.cpp b/Source/WebCore/page/Page.cpp index cabfd52618329..72ffbbfd28edb 100644 --- a/Source/WebCore/page/Page.cpp +++ b/Source/WebCore/page/Page.cpp @@ -190,6 +190,7 @@ #include #include #include +#include #include #include @@ -352,6 +353,9 @@ Page::Page(PageConfiguration&& pageConfiguration) , m_mediaRecorderProvider((WTFMove(pageConfiguration.mediaRecorderProvider))) , m_webRTCProvider(WTFMove(pageConfiguration.webRTCProvider)) , m_rtcController(RTCController::create()) +#if PLATFORM(IOS_FAMILY) + , m_canShowWhileLocked(pageConfiguration.canShowWhileLocked) +#endif , m_domTimerAlignmentInterval(DOMTimer::defaultAlignmentInterval()) , m_domTimerAlignmentIntervalIncreaseTimer(*this, &Page::domTimerAlignmentIntervalIncreaseTimerFired) , m_activityState(pageInitialActivityState()) diff --git a/Source/WebCore/page/Page.h b/Source/WebCore/page/Page.h index b8e82a50b108f..67a128b27cdd4 100644 --- a/Source/WebCore/page/Page.h +++ b/Source/WebCore/page/Page.h @@ -1176,6 +1176,10 @@ class Page : public RefCounted, public Supplementable, public CanMak void hasActiveNowPlayingSessionChanged(); void activeNowPlayingSessionUpdateTimerFired(); +#if PLATFORM(IOS_FAMILY) + bool canShowWhileLocked() const { return m_canShowWhileLocked; } +#endif + private: explicit Page(PageConfiguration&&); @@ -1316,6 +1320,7 @@ class Page : public RefCounted, public Supplementable, public CanMak #if PLATFORM(IOS_FAMILY) bool m_enclosedInScrollableAncestorView { false }; + bool m_canShowWhileLocked { false }; #endif bool m_useSystemAppearance { false }; diff --git a/Source/WebCore/page/PageConfiguration.h b/Source/WebCore/page/PageConfiguration.h index e7c30d6ab4cbb..7112b35be6ab1 100644 --- a/Source/WebCore/page/PageConfiguration.h +++ b/Source/WebCore/page/PageConfiguration.h @@ -194,6 +194,10 @@ class PageConfiguration { bool httpsUpgradeEnabled { true }; std::optional> portsForUpgradingInsecureSchemeForTesting; +#if PLATFORM(IOS_FAMILY) + bool canShowWhileLocked { false }; +#endif + UniqueRef storageProvider; UniqueRef modelPlayerProvider; diff --git a/Source/WebCore/page/PageSerializer.cpp b/Source/WebCore/page/PageSerializer.cpp index a8ab9c14b8148..15b3795c20f69 100644 --- a/Source/WebCore/page/PageSerializer.cpp +++ b/Source/WebCore/page/PageSerializer.cpp @@ -63,6 +63,7 @@ #include "Text.h" #include #include +#include #include #include diff --git a/Source/WebCore/page/PerformanceLoggingClient.cpp b/Source/WebCore/page/PerformanceLoggingClient.cpp index 6d3d37a521d31..70b516e341a97 100644 --- a/Source/WebCore/page/PerformanceLoggingClient.cpp +++ b/Source/WebCore/page/PerformanceLoggingClient.cpp @@ -26,6 +26,8 @@ #include "config.h" #include "PerformanceLoggingClient.h" +#include + namespace WebCore { String PerformanceLoggingClient::synchronousScrollingReasonsAsString(OptionSet reasons) diff --git a/Source/WebCore/page/PerformanceObserverCallback.idl b/Source/WebCore/page/PerformanceObserverCallback.idl index 1b49c0a70bc70..8351f5516a6b5 100644 --- a/Source/WebCore/page/PerformanceObserverCallback.idl +++ b/Source/WebCore/page/PerformanceObserverCallback.idl @@ -25,7 +25,4 @@ // https://w3c.github.io/performance-timeline/ -[ - IsWeakCallback, - CallbackThisObject=PerformanceObserver, -] callback PerformanceObserverCallback = undefined (PerformanceObserverEntryList entries, PerformanceObserver observer); +[ CallbackThisObject=PerformanceObserver ] callback PerformanceObserverCallback = undefined (PerformanceObserverEntryList entries, PerformanceObserver observer); diff --git a/Source/WebCore/page/PerformanceUserTiming.cpp b/Source/WebCore/page/PerformanceUserTiming.cpp index d2cb864065f1c..82bf29d8c736c 100644 --- a/Source/WebCore/page/PerformanceUserTiming.cpp +++ b/Source/WebCore/page/PerformanceUserTiming.cpp @@ -38,6 +38,7 @@ #include "WorkerOrWorkletGlobalScope.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/page/PrintContext.cpp b/Source/WebCore/page/PrintContext.cpp index 439a0a0f194f8..6f902020b9ac7 100644 --- a/Source/WebCore/page/PrintContext.cpp +++ b/Source/WebCore/page/PrintContext.cpp @@ -33,7 +33,7 @@ #include "StyleInheritedData.h" #include "StyleResolver.h" #include "StyleScope.h" -#include +#include namespace WebCore { diff --git a/Source/WebCore/page/Quirks.cpp b/Source/WebCore/page/Quirks.cpp index e25bd927e00b4..b10d48c5e17c4 100644 --- a/Source/WebCore/page/Quirks.cpp +++ b/Source/WebCore/page/Quirks.cpp @@ -70,6 +70,7 @@ #include #include #include +#include #if PLATFORM(IOS_FAMILY) #include @@ -255,7 +256,7 @@ bool Quirks::shouldHideSearchFieldResultsButton() const } // docs.google.com https://bugs.webkit.org/show_bug.cgi?id=161984 -bool Quirks::isTouchBarUpdateSupressedForHiddenContentEditable() const +bool Quirks::isTouchBarUpdateSuppressedForHiddenContentEditable() const { #if PLATFORM(MAC) if (!needsQuirks()) diff --git a/Source/WebCore/page/Quirks.h b/Source/WebCore/page/Quirks.h index 8270835350e93..cf01c33a63ac9 100644 --- a/Source/WebCore/page/Quirks.h +++ b/Source/WebCore/page/Quirks.h @@ -85,7 +85,7 @@ class Quirks { WEBCORE_EXPORT bool shouldDispatchSyntheticMouseEventsWhenModifyingSelection() const; WEBCORE_EXPORT bool shouldSuppressAutocorrectionAndAutocapitalizationInHiddenEditableAreas() const; - WEBCORE_EXPORT bool isTouchBarUpdateSupressedForHiddenContentEditable() const; + WEBCORE_EXPORT bool isTouchBarUpdateSuppressedForHiddenContentEditable() const; WEBCORE_EXPORT bool isNeverRichlyEditableForTouchBar() const; WEBCORE_EXPORT bool shouldAvoidResizingWhenInputViewBoundsChange() const; WEBCORE_EXPORT bool shouldAvoidScrollingWhenFocusedContentIsVisible() const; diff --git a/Source/WebCore/page/ResizeObserverCallback.idl b/Source/WebCore/page/ResizeObserverCallback.idl index 4a40799af6ee0..16244da5ab1ce 100644 --- a/Source/WebCore/page/ResizeObserverCallback.idl +++ b/Source/WebCore/page/ResizeObserverCallback.idl @@ -24,7 +24,4 @@ */ // https://drafts.csswg.org/resize-observer/#resize-observer-callback -[ - CallbackThisObject=ResizeObserver, - IsWeakCallback, -] callback ResizeObserverCallback = undefined (sequence entries, ResizeObserver observer); +[ CallbackThisObject=ResizeObserver ] callback ResizeObserverCallback = undefined (sequence entries, ResizeObserver observer); diff --git a/Source/WebCore/page/SecurityOrigin.cpp b/Source/WebCore/page/SecurityOrigin.cpp index b563c3191e89b..5e3232d15406b 100644 --- a/Source/WebCore/page/SecurityOrigin.cpp +++ b/Source/WebCore/page/SecurityOrigin.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #if PLATFORM(COCOA) diff --git a/Source/WebCore/page/SecurityOriginData.cpp b/Source/WebCore/page/SecurityOriginData.cpp index a46595d6cfd34..fd60d8eb9b722 100644 --- a/Source/WebCore/page/SecurityOriginData.cpp +++ b/Source/WebCore/page/SecurityOriginData.cpp @@ -33,7 +33,7 @@ #include "SecurityOrigin.h" #include #include -#include +#include #include namespace WebCore { diff --git a/Source/WebCore/page/SecurityPolicy.cpp b/Source/WebCore/page/SecurityPolicy.cpp index 5127c3606dea2..6bc6b8eb0ace9 100644 --- a/Source/WebCore/page/SecurityPolicy.cpp +++ b/Source/WebCore/page/SecurityPolicy.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/page/UserContentURLPattern.cpp b/Source/WebCore/page/UserContentURLPattern.cpp index 5a6d1653e8343..fc8dc068a9af2 100644 --- a/Source/WebCore/page/UserContentURLPattern.cpp +++ b/Source/WebCore/page/UserContentURLPattern.cpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/page/UserScript.cpp b/Source/WebCore/page/UserScript.cpp index 45758538b15c1..f11bf6dcce9e1 100644 --- a/Source/WebCore/page/UserScript.cpp +++ b/Source/WebCore/page/UserScript.cpp @@ -26,7 +26,7 @@ #include "config.h" #include "UserScript.h" -#include +#include namespace WebCore { diff --git a/Source/WebCore/page/UserStyleSheet.cpp b/Source/WebCore/page/UserStyleSheet.cpp index a8b22caed0fd5..b26de670f68f1 100644 --- a/Source/WebCore/page/UserStyleSheet.cpp +++ b/Source/WebCore/page/UserStyleSheet.cpp @@ -26,7 +26,7 @@ #include "config.h" #include "UserStyleSheet.h" -#include +#include namespace WebCore { diff --git a/Source/WebCore/page/cocoa/ResourceUsageOverlayCocoa.mm b/Source/WebCore/page/cocoa/ResourceUsageOverlayCocoa.mm index 8dc1c433a64d9..000a98efc1eb0 100644 --- a/Source/WebCore/page/cocoa/ResourceUsageOverlayCocoa.mm +++ b/Source/WebCore/page/cocoa/ResourceUsageOverlayCocoa.mm @@ -41,7 +41,8 @@ #import #import #import -#import +#import +#import using WebCore::ResourceUsageOverlay; @@ -472,15 +473,16 @@ static String gcTimerString(MonotonicTime timerFireDate, MonotonicTime now) size_t dirty = category.dirtySize.last(); size_t reclaimable = category.reclaimableSize.last(); size_t external = category.externalSize.last(); - - auto label = makeString(pad(' ', 11, category.name), ": "_s, formatByteNumber(dirty)); + + StringBuilder labelBuilder; + labelBuilder.append(pad(' ', 11, category.name), ": "_s, formatByteNumber(dirty)); if (external) - label = label + makeString(" + "_s, formatByteNumber(external)); + labelBuilder.append(" + "_s, formatByteNumber(external)); if (reclaimable) - label = label + makeString(" ["_s, formatByteNumber(reclaimable), ']'); + labelBuilder.append(" ["_s, formatByteNumber(reclaimable), ']'); // FIXME: Show size/capacity of GC heap. - showText(context, 10, y, category.color.get(), label); + showText(context, 10, y, category.color.get(), labelBuilder.toString()); y += 10; } y -= 5; diff --git a/Source/WebCore/page/cocoa/ResourceUsageThreadCocoa.mm b/Source/WebCore/page/cocoa/ResourceUsageThreadCocoa.mm index c4a9b0a82a729..fb1cd3df7c990 100644 --- a/Source/WebCore/page/cocoa/ResourceUsageThreadCocoa.mm +++ b/Source/WebCore/page/cocoa/ResourceUsageThreadCocoa.mm @@ -38,7 +38,6 @@ #import #import #import -#import namespace WebCore { diff --git a/Source/WebCore/page/csp/ContentSecurityPolicy.cpp b/Source/WebCore/page/csp/ContentSecurityPolicy.cpp index 2b3cffe02206b..7501c94c9a279 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicy.cpp +++ b/Source/WebCore/page/csp/ContentSecurityPolicy.cpp @@ -64,6 +64,7 @@ #include #include #include +#include #include #include #include @@ -795,7 +796,7 @@ bool ContentSecurityPolicy::requireTrustedTypesForSinkGroup(const String& sinkGr } // https://w3c.github.io/trusted-types/dist/spec/#should-block-sink-type-mismatch -bool ContentSecurityPolicy::allowMissingTrustedTypesForSinkGroup(const String& stringContext, const String& sink, const String& sinkGroup, const String& source) const +bool ContentSecurityPolicy::allowMissingTrustedTypesForSinkGroup(const String& stringContext, const String& sink, const String& sinkGroup, StringView source) const { return allOf(m_policies, [&](auto& policy) { bool isAllowed = true; @@ -806,9 +807,8 @@ bool ContentSecurityPolicy::allowMissingTrustedTypesForSinkGroup(const String& s String consoleMessage = makeString(policy->isReportOnly() ? "[Report Only] "_s : ""_s, "This requires a "_s, stringContext, " value else it violates the following Content Security Policy directive: \"require-trusted-types-for 'script'\""_s); - TextPosition sourcePosition(OrdinalNumber::beforeFirst(), OrdinalNumber()); - String sample = makeString(sink, '|', source); - reportViolation("require-trusted-types-for"_s, *policy, "trusted-types-sink"_s, consoleMessage, nullString(), sample, sourcePosition, nullptr); + String sample = makeString(sink, '|', source.left(40)); + reportViolation("require-trusted-types-for"_s, *policy, "trusted-types-sink"_s, consoleMessage, nullString(), sample, TextPosition(OrdinalNumber::beforeFirst(), OrdinalNumber()), nullptr); } return isAllowed; }); @@ -849,7 +849,7 @@ String ContentSecurityPolicy::createURLForReporting(const URL& url, const String void ContentSecurityPolicy::reportViolation(const ContentSecurityPolicyDirective& violatedDirective, const String& blockedURL, const String& consoleMessage, JSC::JSGlobalObject* state, StringView sourceContent) const { // FIXME: Extract source file, and position from JSC::ExecState. - return reportViolation(violatedDirective.nameForReporting().convertToASCIILowercase(), violatedDirective.directiveList(), blockedURL, consoleMessage, String(), sourceContent, TextPosition(OrdinalNumber::beforeFirst(), OrdinalNumber::beforeFirst()), state); + return reportViolation(violatedDirective.nameForReporting().convertToASCIILowercase(), violatedDirective.directiveList(), blockedURL, consoleMessage, String(), sourceContent.left(40), TextPosition(OrdinalNumber::beforeFirst(), OrdinalNumber::beforeFirst()), state); } void ContentSecurityPolicy::reportViolation(const String& violatedDirective, const ContentSecurityPolicyDirectiveList& violatedDirectiveList, const String& blockedURL, const String& consoleMessage, JSC::JSGlobalObject* state) const @@ -858,12 +858,12 @@ void ContentSecurityPolicy::reportViolation(const String& violatedDirective, con return reportViolation(violatedDirective, violatedDirectiveList, blockedURL, consoleMessage, String(), StringView(), TextPosition(OrdinalNumber::beforeFirst(), OrdinalNumber::beforeFirst()), state); } -void ContentSecurityPolicy::reportViolation(const ContentSecurityPolicyDirective& violatedDirective, const String& blockedURL, const String& consoleMessage, const String& sourceURL, const StringView& sourceContent, const TextPosition& sourcePosition, const URL& preRedirectURL, JSC::JSGlobalObject* state, Element* element) const +void ContentSecurityPolicy::reportViolation(const ContentSecurityPolicyDirective& violatedDirective, const String& blockedURL, const String& consoleMessage, const String& sourceURL, StringView sourceContent, const TextPosition& sourcePosition, const URL& preRedirectURL, JSC::JSGlobalObject* state, Element* element) const { - return reportViolation(violatedDirective.nameForReporting().convertToASCIILowercase(), violatedDirective.directiveList(), blockedURL, consoleMessage, sourceURL, sourceContent, sourcePosition, state, preRedirectURL, element); + return reportViolation(violatedDirective.nameForReporting().convertToASCIILowercase(), violatedDirective.directiveList(), blockedURL, consoleMessage, sourceURL, sourceContent.left(40), sourcePosition, state, preRedirectURL, element); } -void ContentSecurityPolicy::reportViolation(const String& effectiveViolatedDirective, const ContentSecurityPolicyDirectiveList& violatedDirectiveList, const String& blockedURLString, const String& consoleMessage, const String& sourceURL, const StringView& sourceContent, const TextPosition& sourcePosition, JSC::JSGlobalObject* state, const URL& preRedirectURL, Element* element) const +void ContentSecurityPolicy::reportViolation(const String& effectiveViolatedDirective, const ContentSecurityPolicyDirectiveList& violatedDirectiveList, const String& blockedURLString, const String& consoleMessage, const String& sourceURL, StringView sourceContent, const TextPosition& sourcePosition, JSC::JSGlobalObject* state, const URL& preRedirectURL, Element* element) const { logToConsole(consoleMessage, sourceURL, sourcePosition.m_line, sourcePosition.m_column, state); @@ -886,7 +886,7 @@ void ContentSecurityPolicy::reportViolation(const String& effectiveViolatedDirec info.documentURI = m_documentURL ? m_documentURL.value().strippedForUseAsReferrer().string : blockedURI; info.lineNumber = sourcePosition.m_line.oneBasedInt(); info.columnNumber = sourcePosition.m_column.oneBasedInt(); - info.sample = violatedDirectiveList.shouldReportSample(effectiveViolatedDirective) ? sourceContent.left(40).toString() : emptyString(); + info.sample = violatedDirectiveList.shouldReportSample(effectiveViolatedDirective) ? sourceContent.toString() : emptyString(); if (!m_client) { RefPtrAllowingPartiallyDestroyed document = dynamicDowncast(m_scriptExecutionContext.get()); diff --git a/Source/WebCore/page/csp/ContentSecurityPolicy.h b/Source/WebCore/page/csp/ContentSecurityPolicy.h index 8abbd3ac67837..91c3c202aa673 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicy.h +++ b/Source/WebCore/page/csp/ContentSecurityPolicy.h @@ -153,7 +153,7 @@ class ContentSecurityPolicy final : public CanMakeThreadSafeCheckedPtr #include namespace WebCore { @@ -37,7 +38,7 @@ class ContentSecurityPolicyDirective { public: ContentSecurityPolicyDirective(const ContentSecurityPolicyDirectiveList& directiveList, const String& name, const String& value) : m_name(name) - , m_text(name + ' ' + value) + , m_text(makeString(name, ' ', value)) , m_directiveList(directiveList) { } diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp index 8ceb34b339452..32a9b84f0b400 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp +++ b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp @@ -33,6 +33,7 @@ #include "LocalFrame.h" #include "ParsingUtilities.h" #include "SecurityContext.h" +#include #include namespace WebCore { diff --git a/Source/WebCore/page/linux/ResourceUsageOverlayLinux.cpp b/Source/WebCore/page/linux/ResourceUsageOverlayLinux.cpp index f97e769183493..d5dd151cead01 100644 --- a/Source/WebCore/page/linux/ResourceUsageOverlayLinux.cpp +++ b/Source/WebCore/page/linux/ResourceUsageOverlayLinux.cpp @@ -36,7 +36,7 @@ #include "ResourceUsageThread.h" #include "SystemFontDatabase.h" #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp b/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp index 0dd4356faf06f..4979aac45ef07 100644 --- a/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp +++ b/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp @@ -56,6 +56,7 @@ #include "WheelEventTestMonitor.h" #include "pal/HysteresisActivity.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.cpp b/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.cpp index e00f26735f63f..b8570290bbe8e 100644 --- a/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.cpp +++ b/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.cpp @@ -36,7 +36,6 @@ #include "RenderView.h" #include "ScrollableArea.h" #include "StyleScrollSnapPoints.h" -#include namespace WebCore { diff --git a/Source/WebCore/page/scrolling/ScrollingCoordinator.cpp b/Source/WebCore/page/scrolling/ScrollingCoordinator.cpp index 5b6f675930aa8..79c7377c0d530 100644 --- a/Source/WebCore/page/scrolling/ScrollingCoordinator.cpp +++ b/Source/WebCore/page/scrolling/ScrollingCoordinator.cpp @@ -42,6 +42,7 @@ #include "ScrollAnimator.h" #include "Settings.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/page/text-extraction/TextExtraction.cpp b/Source/WebCore/page/text-extraction/TextExtraction.cpp index fcb3df0ab0f3e..d58728ff1bf8a 100644 --- a/Source/WebCore/page/text-extraction/TextExtraction.cpp +++ b/Source/WebCore/page/text-extraction/TextExtraction.cpp @@ -52,6 +52,7 @@ #include "TextIterator.h" #include "WritingMode.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/page/writing-tools/WritingToolsController.h b/Source/WebCore/page/writing-tools/WritingToolsController.h index 3d807e6997ec1..22c104fb1d9a8 100644 --- a/Source/WebCore/page/writing-tools/WritingToolsController.h +++ b/Source/WebCore/page/writing-tools/WritingToolsController.h @@ -30,6 +30,9 @@ #import "Range.h" #import "WritingToolsCompositionCommand.h" #import "WritingToolsTypes.h" +#import +#import +#import namespace WebCore { @@ -43,9 +46,10 @@ class Page; struct SimpleRange; -class WritingToolsController final { +class WritingToolsController final : public CanMakeWeakPtr, public CanMakeCheckedPtr { WTF_MAKE_FAST_ALLOCATED; WTF_MAKE_NONCOPYABLE(WritingToolsController); + WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(WritingToolsController); public: explicit WritingToolsController(Page&); @@ -87,6 +91,7 @@ class WritingToolsController final { // These two vectors should never have the same command in both of them. Vector> unappliedCommands; Vector> reappliedCommands; + bool hasReceivedText { false }; }; struct ProofreadingState : CanMakeCheckedPtr { @@ -116,6 +121,17 @@ class WritingToolsController final { using Value = CompositionState; }; + class EditingScope { + WTF_MAKE_NONCOPYABLE(EditingScope); WTF_MAKE_FAST_ALLOCATED; + public: + EditingScope(Document&); + ~EditingScope(); + + private: + RefPtr m_document; + bool m_editingWasSuppressed; + }; + static CharacterRange characterRange(const SimpleRange& scope, const SimpleRange&); static SimpleRange resolveCharacterRange(const SimpleRange& scope, CharacterRange); static uint64_t characterCount(const SimpleRange&); diff --git a/Source/WebCore/page/writing-tools/WritingToolsController.mm b/Source/WebCore/page/writing-tools/WritingToolsController.mm index 65db800c9f681..942017fb0e567 100644 --- a/Source/WebCore/page/writing-tools/WritingToolsController.mm +++ b/Source/WebCore/page/writing-tools/WritingToolsController.mm @@ -48,6 +48,20 @@ namespace WebCore { +#pragma mark - EditingScope + +WritingToolsController::EditingScope::EditingScope(Document& document) + : m_document(&document) + , m_editingWasSuppressed(document.editor().suppressEditingForWritingTools()) +{ + document.editor().setSuppressEditingForWritingTools(false); +} + +WritingToolsController::EditingScope::~EditingScope() +{ + m_document->editor().setSuppressEditingForWritingTools(m_editingWasSuppressed); +} + #pragma mark - Overloaded TextIterator-based static functions. // To maintain consistency between the traversals of `TextIterator` and `HTMLConverter` within the controller, @@ -202,16 +216,14 @@ return; } + document->editor().setSuppressEditingForWritingTools(true); + completionHandler({ { WTF::UUID { 0 }, attributedStringFromRange, selectedTextCharacterRange } }); } void WritingToolsController::didBeginWritingToolsSession(const WritingTools::Session& session, const Vector& contexts) { RELEASE_LOG(WritingTools, "WritingToolsController::didBeginWritingToolsSession (%s) [received contexts: %zu]", session.identifier.toString().utf8().data(), contexts.size()); - - // Don't animate smart replies, they are animated by UIKit/AppKit. - if (session.compositionType != WebCore::WritingTools::Session::CompositionType::SmartReply) - m_page->chrome().client().addInitialTextAnimation(session.identifier); } void WritingToolsController::proofreadingSessionDidReceiveSuggestions(const WritingTools::Session& session, const Vector& suggestions, const WritingTools::Context& context, bool finished) @@ -267,8 +279,10 @@ state->replacementLocationOffset += static_cast(suggestion.replacement.length()) - static_cast(suggestion.originalRange.length); } - if (finished) + if (finished) { + document->editor().setSuppressEditingForWritingTools(false); document->selection().setSelection({ sessionRange }); + } } void WritingToolsController::proofreadingSessionDidUpdateStateForSuggestion(const WritingTools::Session& session, WritingTools::TextSuggestion::State newTextSuggestionState, const WritingTools::TextSuggestion& textSuggestion, const WritingTools::Context&) @@ -366,6 +380,19 @@ return; } +#if PLATFORM(IOS_FAMILY) + if (!state->hasReceivedText && session.compositionType == WritingTools::Session::CompositionType::SmartReply) { + // UIKit inserts a space prior to `willBegin` and before `didReceiveText`, so the initial session range + // becomes invalid and must be re-computed. + // + // FIXME: (rdar://131197624) Remove this special-case logic if/when UIKit no longer injects a space. + if (auto selectedRange = document->selection().selection().firstRange()) + state->reappliedCommands.last()->setEndingContextRange(*selectedRange); + } + + state->hasReceivedText = true; +#endif + m_page->chrome().client().removeInitialTextAnimation(session.identifier); document->selection().clear(); @@ -382,13 +409,10 @@ // The character count delta is `sessionRangeCharacterCount - contextTextCharacterCount`; // the above check ensures that the full range length expression will never underflow. - auto characterCountDelta = sessionRangeCharacterCount - contextTextCharacterCount; auto adjustedCharacterRange = CharacterRange { range.location, range.length + characterCountDelta }; auto resolvedRange = resolveCharacterRange(sessionRange, adjustedCharacterRange); - m_page->chrome().client().addSourceTextAnimation(session.identifier, range); - // Prefer using any attributes that `attributedText` may have; however, if it has none, // just conduct the replacement so that it matches the style of its surrounding text. // @@ -397,9 +421,38 @@ auto commandState = finished ? WritingToolsCompositionCommand::State::Complete : WritingToolsCompositionCommand::State::InProgress; - replaceContentsOfRangeInSession(*state, resolvedRange, attributedText, commandState); + auto addDestinationTextAnimation = [weakThis = WeakPtr { *this }, state, resolvedRange, attributedText, commandState, identifier = session.identifier, sessionRange]() mutable { + if (!weakThis) + return; + + RefPtr document = weakThis->document(); + if (!document) { + ASSERT_NOT_REACHED(); + return; + } + + weakThis->replaceContentsOfRangeInSession(*state, resolvedRange, attributedText, commandState); + + // FIXME: We won't be setting the selection after every replace, we need a different way to + // caluculate this range. + auto selectionRange = document->selection().selection().firstRange(); + if (!selectionRange) + return; + + + auto rangeAfterReplace = characterRange(sessionRange, *selectionRange); + + if (!weakThis->m_page) + return; + + weakThis->m_page->chrome().client().addDestinationTextAnimation(identifier, rangeAfterReplace, attributedText.string); + }; - m_page->chrome().client().addDestinationTextAnimation(session.identifier, adjustedCharacterRange); +#if PLATFORM(MAC) + m_page->chrome().client().addSourceTextAnimation(session.identifier, range, attributedText.string, WTFMove(addDestinationTextAnimation)); +#else + addDestinationTextAnimation(); +#endif } template<> @@ -556,6 +609,8 @@ return; } + document->editor().setSuppressEditingForWritingTools(false); + switch (session.type) { case WritingTools::Session::Type::Proofreading: didEndWritingToolsSession(session, accepted); @@ -573,12 +628,6 @@ m_page->chrome().client().removeInitialTextAnimation(session.identifier); - // At this point, the selection will be the replaced text, which is the desired behavior for - // Smart Reply sessions. However, for others, the entire session context range should be selected. - - if (session.compositionType != WritingTools::Session::CompositionType::SmartReply) - document->selection().setSelection({ *sessionRange }); - m_page->chrome().client().removeTransparentMarkersForSessionID(session.identifier); m_states.remove(session.identifier); @@ -759,6 +808,11 @@ static bool appliedCommandIsWritingToolsCommand(const Vectorchrome().client().clearAnimationsForSessionID(session.identifier); + // Don't animate smart replies, they are animated by UIKit/AppKit. + if (session.compositionType != WebCore::WritingTools::Session::CompositionType::SmartReply) + m_page->chrome().client().addInitialTextAnimation(session.identifier); + // The stack will never be empty as the sentinel command always exists. auto currentContextRange = state->reappliedCommands.last()->endingContextRange(); state->reappliedCommands.append(WritingToolsCompositionCommand::create(Ref { *document }, currentContextRange)); @@ -833,7 +887,10 @@ static bool appliedCommandIsWritingToolsCommand(const Vectorselection().setSelection({ range }); - document->editor().replaceSelectionWithText(replacementText, Editor::SelectReplacement::Yes, Editor::SmartReplace::No, EditAction::InsertReplacement); + { + EditingScope editingScope { *document }; + document->editor().replaceSelectionWithText(replacementText, Editor::SelectReplacement::Yes, Editor::SmartReplace::No, EditAction::InsertReplacement); + } auto selection = document->selection().selection(); @@ -859,6 +916,7 @@ static bool appliedCommandIsWritingToolsCommand(const VectorreplaceContentsOfRangeWithFragment(WTFMove(fragment), range, matchStyle, commandState); } diff --git a/Source/WebCore/page/writing-tools/WritingToolsTypes.h b/Source/WebCore/page/writing-tools/WritingToolsTypes.h index eb10ec2af9546..a4a5044e56d0a 100644 --- a/Source/WebCore/page/writing-tools/WritingToolsTypes.h +++ b/Source/WebCore/page/writing-tools/WritingToolsTypes.h @@ -47,6 +47,10 @@ enum class Action : uint8_t { Restart, }; +enum class RequestedTool : uint16_t { + // Opaque type to transitively convert to/from WTRequestedTool. +}; + #pragma mark - Session enum class SessionType : uint8_t { diff --git a/Source/WebCore/platform/DateComponents.cpp b/Source/WebCore/platform/DateComponents.cpp index a516c0be8f93a..f1ffb4e2d03e8 100644 --- a/Source/WebCore/platform/DateComponents.cpp +++ b/Source/WebCore/platform/DateComponents.cpp @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include namespace WebCore { diff --git a/Source/WebCore/platform/LocalizedStrings.cpp b/Source/WebCore/platform/LocalizedStrings.cpp index b4b77aa518c63..17363f1f29e19 100644 --- a/Source/WebCore/platform/LocalizedStrings.cpp +++ b/Source/WebCore/platform/LocalizedStrings.cpp @@ -30,6 +30,7 @@ #include "IntSize.h" #include "NotImplemented.h" #include +#include #include #include diff --git a/Source/WebCore/platform/MIMETypeRegistry.cpp b/Source/WebCore/platform/MIMETypeRegistry.cpp index a4e3064903c7b..092649a26549e 100644 --- a/Source/WebCore/platform/MIMETypeRegistry.cpp +++ b/Source/WebCore/platform/MIMETypeRegistry.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #if USE(CG) #include "ImageBufferUtilitiesCG.h" @@ -63,10 +64,6 @@ #include "ImageDecoderGStreamer.h" #endif -#if USE(APPLE_INTERNAL_SDK) -#include -#endif - namespace WebCore { static String normalizedImageMIMEType(const String&); @@ -135,7 +132,7 @@ constexpr ComparableCaseFoldingASCIILiteral supportedImageMIMETypeArray[] = { #endif "image/webp", #if ENABLE(MULTI_REPRESENTATION_HEIC) - MULTI_REPRESENTATION_HEIC_MIME_TYPE, + "image/x-apple-adaptive-glyph", #endif #if PLATFORM(IOS_FAMILY) "image/x-bmp", diff --git a/Source/WebCore/platform/ProcessQualified.h b/Source/WebCore/platform/ProcessQualified.h index 929c88549ac00..7ccbfc96beb51 100644 --- a/Source/WebCore/platform/ProcessQualified.h +++ b/Source/WebCore/platform/ProcessQualified.h @@ -28,7 +28,7 @@ #include "ProcessIdentifier.h" #include #include -#include +#include #include namespace WebCore { diff --git a/Source/WebCore/platform/RectEdges.h b/Source/WebCore/platform/RectEdges.h index 0ef16c0faa6b8..c4124a9ab6277 100644 --- a/Source/WebCore/platform/RectEdges.h +++ b/Source/WebCore/platform/RectEdges.h @@ -95,13 +95,13 @@ template class RectEdges { T& before(WritingMode writingMode) { return at(mapLogicalSideToPhysicalSide(writingMode, LogicalBoxSide::BlockStart)); } T& after(WritingMode writingMode) { return at(mapLogicalSideToPhysicalSide(writingMode, LogicalBoxSide::BlockEnd)); } - T& start(WritingMode writingMode, TextDirection direction = TextDirection::LTR) { return at(mapLogicalSideToPhysicalSide({ writingModeToBlockFlowDirection(writingMode), direction }, LogicalBoxSide::InlineStart)); } - T& end(WritingMode writingMode, TextDirection direction = TextDirection::LTR) { return at(mapLogicalSideToPhysicalSide({ writingModeToBlockFlowDirection(writingMode), direction }, LogicalBoxSide::InlineEnd)); } + T& start(WritingMode writingMode, TextDirection direction = TextDirection::LTR) { return at(mapLogicalSideToPhysicalSide(makeTextFlow(writingMode, direction), LogicalBoxSide::InlineStart)); } + T& end(WritingMode writingMode, TextDirection direction = TextDirection::LTR) { return at(mapLogicalSideToPhysicalSide(makeTextFlow(writingMode, direction), LogicalBoxSide::InlineEnd)); } const T& before(WritingMode writingMode) const { return at(mapLogicalSideToPhysicalSide(writingMode, LogicalBoxSide::BlockStart)); } const T& after(WritingMode writingMode) const { return at(mapLogicalSideToPhysicalSide(writingMode, LogicalBoxSide::BlockEnd)); } - const T& start(WritingMode writingMode, TextDirection direction = TextDirection::LTR) const { return at(mapLogicalSideToPhysicalSide({ writingModeToBlockFlowDirection(writingMode), direction }, LogicalBoxSide::InlineStart)); } - const T& end(WritingMode writingMode, TextDirection direction = TextDirection::LTR) const { return at(mapLogicalSideToPhysicalSide({ writingModeToBlockFlowDirection(writingMode), direction }, LogicalBoxSide::InlineEnd)); } + const T& start(WritingMode writingMode, TextDirection direction = TextDirection::LTR) const { return at(mapLogicalSideToPhysicalSide(makeTextFlow(writingMode, direction), LogicalBoxSide::InlineStart)); } + const T& end(WritingMode writingMode, TextDirection direction = TextDirection::LTR) const { return at(mapLogicalSideToPhysicalSide(makeTextFlow(writingMode, direction), LogicalBoxSide::InlineEnd)); } void setBefore(const T& before, WritingMode writingMode) { this->before(writingMode) = before; } void setAfter(const T& after, WritingMode writingMode) { this->after(writingMode) = after; } diff --git a/Source/WebCore/platform/ScrollView.cpp b/Source/WebCore/platform/ScrollView.cpp index 58a9bdee81c45..39094b5371f23 100644 --- a/Source/WebCore/platform/ScrollView.cpp +++ b/Source/WebCore/platform/ScrollView.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/style/StyleAppearance.cpp b/Source/WebCore/platform/StyleAppearance.cpp similarity index 100% rename from Source/WebCore/style/StyleAppearance.cpp rename to Source/WebCore/platform/StyleAppearance.cpp diff --git a/Source/WebCore/style/StyleAppearance.h b/Source/WebCore/platform/StyleAppearance.h similarity index 100% rename from Source/WebCore/style/StyleAppearance.h rename to Source/WebCore/platform/StyleAppearance.h diff --git a/Source/WebCore/platform/VideoEncoder.h b/Source/WebCore/platform/VideoEncoder.h index 8682cbb57e110..91c86e856cbb3 100644 --- a/Source/WebCore/platform/VideoEncoder.h +++ b/Source/WebCore/platform/VideoEncoder.h @@ -80,6 +80,8 @@ class VideoEncoder { using EncodeCallback = Function; virtual void encode(RawFrame&&, bool shouldGenerateKeyFrame, EncodeCallback&&) = 0; + // FIXME: Evaluate whether we can make it virtual pure and not return a boolean. + virtual bool setRates(uint64_t /* bitRate */, double /* frameRate */, Function&&) { return false; } virtual void flush(Function&&) = 0; virtual void reset() = 0; virtual void close() = 0; diff --git a/Source/WebCore/platform/animation/TimingFunction.cpp b/Source/WebCore/platform/animation/TimingFunction.cpp index 379b2fa94fee5..c79a6c26a336b 100644 --- a/Source/WebCore/platform/animation/TimingFunction.cpp +++ b/Source/WebCore/platform/animation/TimingFunction.cpp @@ -33,7 +33,7 @@ #include "MutableStyleProperties.h" #include "SpringSolver.h" #include "UnitBezier.h" -#include +#include #include namespace WebCore { diff --git a/Source/WebCore/platform/audio/HRTFElevation.cpp b/Source/WebCore/platform/audio/HRTFElevation.cpp index fb0a042e6a86d..4e30ecbde85d2 100644 --- a/Source/WebCore/platform/audio/HRTFElevation.cpp +++ b/Source/WebCore/platform/audio/HRTFElevation.cpp @@ -41,6 +41,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/audio/PlatformMediaSession.cpp b/Source/WebCore/platform/audio/PlatformMediaSession.cpp index eab03bf06c1b8..5ec39e861f5ef 100644 --- a/Source/WebCore/platform/audio/PlatformMediaSession.cpp +++ b/Source/WebCore/platform/audio/PlatformMediaSession.cpp @@ -35,6 +35,7 @@ #include "PlatformMediaSessionManager.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/audio/glib/MediaSessionGLib.cpp b/Source/WebCore/platform/audio/glib/MediaSessionGLib.cpp index d0b01aa95d104..a36dec38642a1 100644 --- a/Source/WebCore/platform/audio/glib/MediaSessionGLib.cpp +++ b/Source/WebCore/platform/audio/glib/MediaSessionGLib.cpp @@ -26,6 +26,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/audio/gstreamer/AudioDecoderGStreamer.cpp b/Source/WebCore/platform/audio/gstreamer/AudioDecoderGStreamer.cpp index 45680108d4bf9..9b79e961dc642 100644 --- a/Source/WebCore/platform/audio/gstreamer/AudioDecoderGStreamer.cpp +++ b/Source/WebCore/platform/audio/gstreamer/AudioDecoderGStreamer.cpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/audio/gstreamer/AudioDestinationGStreamer.cpp b/Source/WebCore/platform/audio/gstreamer/AudioDestinationGStreamer.cpp index a7e033c46cb63..b82091667b923 100644 --- a/Source/WebCore/platform/audio/gstreamer/AudioDestinationGStreamer.cpp +++ b/Source/WebCore/platform/audio/gstreamer/AudioDestinationGStreamer.cpp @@ -33,7 +33,7 @@ #include #include #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/platform/audio/gstreamer/AudioEncoderGStreamer.cpp b/Source/WebCore/platform/audio/gstreamer/AudioEncoderGStreamer.cpp index 97a9a631b34f1..34c412c170079 100644 --- a/Source/WebCore/platform/audio/gstreamer/AudioEncoderGStreamer.cpp +++ b/Source/WebCore/platform/audio/gstreamer/AudioEncoderGStreamer.cpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/audio/gstreamer/AudioFileReaderGStreamer.cpp b/Source/WebCore/platform/audio/gstreamer/AudioFileReaderGStreamer.cpp index 6ba19adc28f55..cbee6c5281fa0 100644 --- a/Source/WebCore/platform/audio/gstreamer/AudioFileReaderGStreamer.cpp +++ b/Source/WebCore/platform/audio/gstreamer/AudioFileReaderGStreamer.cpp @@ -35,7 +35,7 @@ #include #include #include -#include +#include namespace WebCore { class AudioFileReader; diff --git a/Source/WebCore/platform/audio/gstreamer/AudioSourceProviderGStreamer.cpp b/Source/WebCore/platform/audio/gstreamer/AudioSourceProviderGStreamer.cpp index d7704db18864e..51547b0226c00 100644 --- a/Source/WebCore/platform/audio/gstreamer/AudioSourceProviderGStreamer.cpp +++ b/Source/WebCore/platform/audio/gstreamer/AudioSourceProviderGStreamer.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #if ENABLE(MEDIA_STREAM) #include "GStreamerAudioData.h" diff --git a/Source/WebCore/platform/audio/ios/AudioSessionIOS.mm b/Source/WebCore/platform/audio/ios/AudioSessionIOS.mm index 876cb70c70d06..31a9ca82b8513 100644 --- a/Source/WebCore/platform/audio/ios/AudioSessionIOS.mm +++ b/Source/WebCore/platform/audio/ios/AudioSessionIOS.mm @@ -155,10 +155,6 @@ - (void)interruption:(NSNotification *)notification ALWAYS_LOG(LOGIDENTIFIER); AVAudioSession *session = [PAL::getAVAudioSessionClass() sharedInstance]; - if (![session respondsToSelector:@selector(setAuditTokensForProcessAssertion:error:)]) - return; - - ALWAYS_LOG(LOGIDENTIFIER); auto nsAuditTokens = adoptNS([[NSMutableArray alloc] init]); for (auto& token : auditTokens) { auto nsToken = adoptNS([[NSData alloc] initWithBytes:token.val length:sizeof(token.val)]); diff --git a/Source/WebCore/platform/cocoa/DragDataCocoa.mm b/Source/WebCore/platform/cocoa/DragDataCocoa.mm index 1b4de0f06ce07..41fd23035138b 100644 --- a/Source/WebCore/platform/cocoa/DragDataCocoa.mm +++ b/Source/WebCore/platform/cocoa/DragDataCocoa.mm @@ -44,10 +44,6 @@ #import #endif -#if USE(APPLE_INTERNAL_SDK) -#include -#endif - namespace WebCore { static inline String rtfPasteboardType() @@ -181,7 +177,7 @@ static inline String tiffPasteboardType() Vector types; auto context = createPasteboardContext(); platformStrategies()->pasteboardStrategy()->getTypes(types, m_pasteboardName, context.get()); - return types.contains(MULTI_REPRESENTATION_HEIC_PASTEBOARD_TYPE_STRING); + return types.contains("com.apple.sticker"_s); #else return false; #endif diff --git a/Source/WebCore/platform/cocoa/KeyEventCocoa.mm b/Source/WebCore/platform/cocoa/KeyEventCocoa.mm index 5e9c59ed1fb6b..233831fe1b324 100644 --- a/Source/WebCore/platform/cocoa/KeyEventCocoa.mm +++ b/Source/WebCore/platform/cocoa/KeyEventCocoa.mm @@ -32,6 +32,7 @@ #import #import #import +#import #if PLATFORM(IOS_FAMILY) #import "KeyEventCodesIOS.h" diff --git a/Source/WebCore/platform/cocoa/MIMETypeRegistryCocoa.mm b/Source/WebCore/platform/cocoa/MIMETypeRegistryCocoa.mm index 6d26d559db656..de62bcdb0ad79 100644 --- a/Source/WebCore/platform/cocoa/MIMETypeRegistryCocoa.mm +++ b/Source/WebCore/platform/cocoa/MIMETypeRegistryCocoa.mm @@ -32,6 +32,7 @@ #import #import #import +#import namespace WebCore { diff --git a/Source/WebCore/platform/cocoa/PlatformNSAdaptiveImageGlyph.h b/Source/WebCore/platform/cocoa/PlatformNSAdaptiveImageGlyph.h new file mode 100644 index 0000000000000..e948778971f72 --- /dev/null +++ b/Source/WebCore/platform/cocoa/PlatformNSAdaptiveImageGlyph.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. + */ + +#pragma once + +#if ENABLE(MULTI_REPRESENTATION_HEIC) + +#if USE(APPKIT) + +#import + +#define PlatformNSAdaptiveImageGlyph NSAdaptiveImageGlyph.class + +#else + +#import + +#define PlatformNSAdaptiveImageGlyph getNSAdaptiveImageGlyphClass() + +#endif + +#endif // ENABLE(MULTI_REPRESENTATION_HEIC) diff --git a/Source/WebCore/platform/cocoa/PlatformSpeechSynthesizerCocoa.mm b/Source/WebCore/platform/cocoa/PlatformSpeechSynthesizerCocoa.mm index 588981cdf7151..f0d31de023a2a 100644 --- a/Source/WebCore/platform/cocoa/PlatformSpeechSynthesizerCocoa.mm +++ b/Source/WebCore/platform/cocoa/PlatformSpeechSynthesizerCocoa.mm @@ -296,31 +296,16 @@ - (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer willSpeakRangeOfSpe NSArray *voices = nil; // SpeechSynthesis replaces on-device compact with higher quality compact voices. These // are not available to WebKit so we're losing these default voices for WebSpeech. + // FIXME: Remove respondsToSelector check when is available on all SDKs. if ([PAL::getAVSpeechSynthesisVoiceClass() respondsToSelector:@selector(speechVoicesIncludingSuperCompact)]) voices = [PAL::getAVSpeechSynthesisVoiceClass() speechVoicesIncludingSuperCompact]; else voices = [PAL::getAVSpeechSynthesisVoiceClass() speechVoices]; + // Only show built-in voices when requesting through WebKit to reduce fingerprinting surface area. for (AVSpeechSynthesisVoice *voice in voices) { - NSString *language = [voice language]; - bool isDefault = true; - NSString *voiceURI = [voice identifier]; - NSString *name = [voice name]; - // Only show built-in voices when requesting through WebKit to reduce fingerprinting surface area. -#if HAVE(AVSPEECHSYNTHESIS_SYSTEMVOICE) - // FIXME: Remove respondsToSelector check when is available on all SDKs. - BOOL includeVoice = NO; - if ([voice respondsToSelector:@selector(isSystemVoice)]) - includeVoice = voice.isSystemVoice; - else - includeVoice = voice.quality == AVSpeechSynthesisVoiceQualityDefault; - if (includeVoice) -#else - // AVSpeechSynthesis on macOS does not support quality property correctly. - if (voice.quality == AVSpeechSynthesisVoiceQualityDefault - || (TARGET_OS_OSX && ![voiceURI hasSuffix:@"premium"])) -#endif - m_voiceList.append(PlatformSpeechSynthesisVoice::create(voiceURI, name, language, true, isDefault)); + if (voice.isSystemVoice) + m_voiceList.append(PlatformSpeechSynthesisVoice::create(voice.identifier, voice.name, voice.language, true, true)); } END_BLOCK_OBJC_EXCEPTIONS } diff --git a/Source/WebCore/platform/encryptedmedia/CDMProxy.cpp b/Source/WebCore/platform/encryptedmedia/CDMProxy.cpp index 440bffbef6c9a..4cc68f3d88b38 100644 --- a/Source/WebCore/platform/encryptedmedia/CDMProxy.cpp +++ b/Source/WebCore/platform/encryptedmedia/CDMProxy.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/platform/gamepad/cocoa/GameControllerGamepad.mm b/Source/WebCore/platform/gamepad/cocoa/GameControllerGamepad.mm index ea6ff819a49fd..8900f1f3631cf 100644 --- a/Source/WebCore/platform/gamepad/cocoa/GameControllerGamepad.mm +++ b/Source/WebCore/platform/gamepad/cocoa/GameControllerGamepad.mm @@ -33,6 +33,7 @@ #import #import #import +#import #import "GameControllerSoftLink.h" diff --git a/Source/WebCore/platform/gamepad/mac/HIDGamepad.cpp b/Source/WebCore/platform/gamepad/mac/HIDGamepad.cpp index 8f2dcbf6d1430..ffa891b031a68 100644 --- a/Source/WebCore/platform/gamepad/mac/HIDGamepad.cpp +++ b/Source/WebCore/platform/gamepad/mac/HIDGamepad.cpp @@ -40,6 +40,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/glib/UserAgentGLib.cpp b/Source/WebCore/platform/glib/UserAgentGLib.cpp index a2fa34c242b63..163b8daae2229 100644 --- a/Source/WebCore/platform/glib/UserAgentGLib.cpp +++ b/Source/WebCore/platform/glib/UserAgentGLib.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #if OS(UNIX) @@ -137,7 +138,7 @@ String standardUserAgent(const String& applicationName, const String& applicatio String finalApplicationVersion = applicationVersion; if (finalApplicationVersion.isEmpty()) finalApplicationVersion = "605.1.15"_s; - userAgent = standardUserAgentStatic() + ' ' + applicationName + '/' + finalApplicationVersion; + userAgent = makeString(standardUserAgentStatic(), ' ', applicationName, '/', finalApplicationVersion); } static bool checked = false; diff --git a/Source/WebCore/platform/graphics/ColorSerialization.cpp b/Source/WebCore/platform/graphics/ColorSerialization.cpp index 71cc626aafac5..894d36cb8fa7b 100644 --- a/Source/WebCore/platform/graphics/ColorSerialization.cpp +++ b/Source/WebCore/platform/graphics/ColorSerialization.cpp @@ -31,8 +31,8 @@ #include #include #include +#include #include -#include namespace WebCore { diff --git a/Source/WebCore/platform/graphics/DisplayRefreshMonitorManager.cpp b/Source/WebCore/platform/graphics/DisplayRefreshMonitorManager.cpp index a0861e208e547..683f78c0abaf5 100644 --- a/Source/WebCore/platform/graphics/DisplayRefreshMonitorManager.cpp +++ b/Source/WebCore/platform/graphics/DisplayRefreshMonitorManager.cpp @@ -72,13 +72,13 @@ void DisplayRefreshMonitorManager::unregisterClient(DisplayRefreshMonitorClient& void DisplayRefreshMonitorManager::clientPreferredFramesPerSecondChanged(DisplayRefreshMonitorClient& client) { - if (auto* monitor = monitorForClient(client)) + if (RefPtr monitor = monitorForClient(client)) monitor->clientPreferredFramesPerSecondChanged(client); } bool DisplayRefreshMonitorManager::scheduleAnimation(DisplayRefreshMonitorClient& client) { - if (auto* monitor = monitorForClient(client)) { + if (RefPtr monitor = monitorForClient(client)) { client.setIsScheduled(true); return monitor->requestRefreshCallback(); } @@ -103,8 +103,7 @@ void DisplayRefreshMonitorManager::windowScreenDidChange(PlatformDisplayID displ std::optional DisplayRefreshMonitorManager::nominalFramesPerSecondForDisplay(PlatformDisplayID displayID, DisplayRefreshMonitorFactory* factory) { - auto* monitor = ensureMonitorForDisplayID(displayID, factory); - if (monitor) + if (RefPtr monitor = ensureMonitorForDisplayID(displayID, factory)) return monitor->displayNominalFramesPerSecond(); return std::nullopt; @@ -112,8 +111,7 @@ std::optional DisplayRefreshMonitorManager::nominalFramesPerSec void DisplayRefreshMonitorManager::displayDidRefresh(PlatformDisplayID displayID, const DisplayUpdate& displayUpdate) { - auto* monitor = monitorForDisplayID(displayID); - if (monitor) + if (RefPtr monitor = monitorForDisplayID(displayID)) monitor->displayLinkFired(displayUpdate); } @@ -129,11 +127,11 @@ DisplayRefreshMonitor* DisplayRefreshMonitorManager::monitorForClient(DisplayRef if (!client.hasDisplayID()) return nullptr; - auto* monitor = ensureMonitorForDisplayID(client.displayID(), client.displayRefreshMonitorFactory()); + RefPtr monitor = ensureMonitorForDisplayID(client.displayID(), client.displayRefreshMonitorFactory()); if (monitor) monitor->addClient(client); - return monitor; + return monitor.get(); } DisplayRefreshMonitor* DisplayRefreshMonitorManager::monitorForDisplayID(PlatformDisplayID displayID) const diff --git a/Source/WebCore/platform/graphics/FloatPolygon.cpp b/Source/WebCore/platform/graphics/FloatPolygon.cpp index 0a62c3f34c146..a28dfcb7ed8e6 100644 --- a/Source/WebCore/platform/graphics/FloatPolygon.cpp +++ b/Source/WebCore/platform/graphics/FloatPolygon.cpp @@ -32,7 +32,6 @@ #include #include -#include namespace WebCore { diff --git a/Source/WebCore/platform/graphics/FontCascadeFonts.cpp b/Source/WebCore/platform/graphics/FontCascadeFonts.cpp index d0cbcd6a8f0fd..a75d8b1e72c15 100644 --- a/Source/WebCore/platform/graphics/FontCascadeFonts.cpp +++ b/Source/WebCore/platform/graphics/FontCascadeFonts.cpp @@ -179,7 +179,7 @@ static FontRanges realizeNextFallback(const FontCascadeDescription& description, return FontRanges(WTFMove(font)); return FontRanges(); }, [&](const FontFamilyPlatformSpecification& fontFamilySpecification) -> FontRanges { - return { fontFamilySpecification.fontRanges(description), true }; + return { fontFamilySpecification.fontRanges(description), IsGenericFontFamily::Yes }; }); const auto& currentFamily = description.effectiveFamilyAt(index++); auto ranges = std::visit(visitor, currentFamily); diff --git a/Source/WebCore/platform/graphics/FontRanges.cpp b/Source/WebCore/platform/graphics/FontRanges.cpp index 26d7d3fd130d0..b5aac6f5a792c 100644 --- a/Source/WebCore/platform/graphics/FontRanges.cpp +++ b/Source/WebCore/platform/graphics/FontRanges.cpp @@ -39,9 +39,9 @@ const Font* FontRanges::Range::font(ExternalResourceDownloadPolicy policy) const return m_fontAccessor->font(policy); } -FontRanges::FontRanges(FontRanges&& other, bool isGeneric) +FontRanges::FontRanges(FontRanges&& other, IsGenericFontFamily isGenericFontFamily) : m_ranges { WTFMove(other.m_ranges) } -, m_isGeneric { isGeneric } +, m_isGenericFontFamily { isGenericFontFamily } { } @@ -82,7 +82,7 @@ FontRanges::~FontRanges() = default; GlyphData FontRanges::glyphDataForCharacter(char32_t character, ExternalResourceDownloadPolicy policy) const { const Font* resultFont = nullptr; - if (isGeneric() && isPrivateUseAreaCharacter(character)) + if (isGenericFontFamily() && isPrivateUseAreaCharacter(character)) return GlyphData(); for (auto& range : m_ranges) { diff --git a/Source/WebCore/platform/graphics/FontRanges.h b/Source/WebCore/platform/graphics/FontRanges.h index 03dfa8ea717f8..55e8fef65912c 100644 --- a/Source/WebCore/platform/graphics/FontRanges.h +++ b/Source/WebCore/platform/graphics/FontRanges.h @@ -33,11 +33,16 @@ namespace WebCore { class FontAccessor; -enum class ExternalResourceDownloadPolicy { +enum class ExternalResourceDownloadPolicy : bool { Forbid, Allow }; +enum class IsGenericFontFamily : bool { + No, + Yes +}; + class FontRanges { public: struct Range { @@ -75,7 +80,7 @@ class FontRanges { ~FontRanges(); FontRanges(const FontRanges&) = default; - FontRanges(FontRanges&& other, bool isGeneric); + FontRanges(FontRanges&& other, IsGenericFontFamily); FontRanges& operator=(FontRanges&&) = default; bool isNull() const { return m_ranges.isEmpty(); } @@ -90,11 +95,11 @@ class FontRanges { WEBCORE_EXPORT const Font* fontForCharacter(char32_t) const; WEBCORE_EXPORT const Font& fontForFirstRange() const; bool isLoading() const; - bool isGeneric() const { return m_isGeneric; } + bool isGenericFontFamily() const { return m_isGenericFontFamily == IsGenericFontFamily::Yes; } private: Vector m_ranges; - bool m_isGeneric { false }; + IsGenericFontFamily m_isGenericFontFamily { IsGenericFontFamily::No }; }; } diff --git a/Source/WebCore/platform/graphics/GraphicsLayer.cpp b/Source/WebCore/platform/graphics/GraphicsLayer.cpp index e8186a0075983..f1f5435b6a97d 100644 --- a/Source/WebCore/platform/graphics/GraphicsLayer.cpp +++ b/Source/WebCore/platform/graphics/GraphicsLayer.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include diff --git a/Source/WebCore/platform/graphics/HEVCUtilities.cpp b/Source/WebCore/platform/graphics/HEVCUtilities.cpp index 505d2bab8128e..6809dae452f96 100644 --- a/Source/WebCore/platform/graphics/HEVCUtilities.cpp +++ b/Source/WebCore/platform/graphics/HEVCUtilities.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/platform/graphics/ImageAdapter.h b/Source/WebCore/platform/graphics/ImageAdapter.h index 3e89f40d7ffea..fda6e0437a399 100644 --- a/Source/WebCore/platform/graphics/ImageAdapter.h +++ b/Source/WebCore/platform/graphics/ImageAdapter.h @@ -25,16 +25,12 @@ #pragma once -#if USE(APPLE_INTERNAL_SDK) -#include -#endif - #if USE(APPKIT) OBJC_CLASS NSImage; #endif #if ENABLE(MULTI_REPRESENTATION_HEIC) -OBJC_CLASS WebMultiRepresentationHEICAttachment; +OBJC_CLASS NSAdaptiveImageGlyph; #endif #if USE(CG) @@ -94,7 +90,7 @@ class ImageAdapter { #endif #if ENABLE(MULTI_REPRESENTATION_HEIC) - WebMultiRepresentationHEICAttachment *multiRepresentationHEIC(); + NSAdaptiveImageGlyph *multiRepresentationHEIC(); #endif #if PLATFORM(GTK) @@ -129,7 +125,7 @@ class ImageAdapter { mutable RetainPtr m_tiffRep; // Cached TIFF rep for all the frames. Only built lazily if someone queries for one. #endif #if ENABLE(MULTI_REPRESENTATION_HEIC) - mutable RetainPtr m_multiRepHEIC; + mutable RetainPtr m_multiRepHEIC; #endif }; diff --git a/Source/WebCore/platform/graphics/ImageBuffer.cpp b/Source/WebCore/platform/graphics/ImageBuffer.cpp index 407d1cae73298..78af4e2208bca 100644 --- a/Source/WebCore/platform/graphics/ImageBuffer.cpp +++ b/Source/WebCore/platform/graphics/ImageBuffer.cpp @@ -39,6 +39,7 @@ #include "ProcessCapabilities.h" #include "TransparencyLayerContextSwitcher.h" #include +#include #if USE(CG) #include "ImageBufferUtilitiesCG.h" diff --git a/Source/WebCore/platform/graphics/MediaPlayer.cpp b/Source/WebCore/platform/graphics/MediaPlayer.cpp index e1e9c5b2cbcfc..c6a4ee6d8436b 100644 --- a/Source/WebCore/platform/graphics/MediaPlayer.cpp +++ b/Source/WebCore/platform/graphics/MediaPlayer.cpp @@ -33,6 +33,7 @@ #include "DeprecatedGlobalSettings.h" #include "FourCC.h" #include "GraphicsContext.h" +#include "InbandTextTrackPrivate.h" #include "IntRect.h" #include "LegacyCDMSession.h" #include "Logging.h" @@ -54,7 +55,7 @@ #include #include #include -#include "InbandTextTrackPrivate.h" +#include #if ENABLE(MEDIA_SOURCE) #include "MediaSourcePrivateClient.h" @@ -928,6 +929,13 @@ FloatSize MediaPlayer::videoLayerSize() const return client().mediaPlayerVideoLayerSize(); } +#if PLATFORM(IOS_FAMILY) +bool MediaPlayer::canShowWhileLocked() const +{ + return client().canShowWhileLocked(); +} +#endif + void MediaPlayer::videoLayerSizeDidChange(const FloatSize& size) { client().mediaPlayerVideoLayerSizeDidChange(size); diff --git a/Source/WebCore/platform/graphics/MediaPlayer.h b/Source/WebCore/platform/graphics/MediaPlayer.h index 8047b6e5bc45c..c14be7d57babe 100644 --- a/Source/WebCore/platform/graphics/MediaPlayer.h +++ b/Source/WebCore/platform/graphics/MediaPlayer.h @@ -325,6 +325,10 @@ class MediaPlayerClient : public CanMakeWeakPtr { virtual const void* mediaPlayerLogIdentifier() { return nullptr; } virtual const Logger& mediaPlayerLogger() = 0; #endif + +#if PLATFORM(IOS_FAMILY) + virtual bool canShowWhileLocked() const { return false; } +#endif }; class WEBCORE_EXPORT MediaPlayer : public MediaPlayerEnums, public ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr { @@ -773,6 +777,10 @@ class WEBCORE_EXPORT MediaPlayer : public MediaPlayerEnums, public ThreadSafeRef bool supportsLinearMediaPlayer() const; #endif +#if PLATFORM(IOS_FAMILY) + bool canShowWhileLocked() const; +#endif + private: MediaPlayer(MediaPlayerClient&); MediaPlayer(MediaPlayerClient&, MediaPlayerEnums::MediaEngineIdentifier); diff --git a/Source/WebCore/platform/graphics/MediaResourceSniffer.cpp b/Source/WebCore/platform/graphics/MediaResourceSniffer.cpp index b9632ad7fe08a..6a69aa67f659a 100644 --- a/Source/WebCore/platform/graphics/MediaResourceSniffer.cpp +++ b/Source/WebCore/platform/graphics/MediaResourceSniffer.cpp @@ -31,6 +31,7 @@ #include "MIMESniffer.h" #include "ResourceRequest.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/graphics/SourceBufferPrivateClient.h b/Source/WebCore/platform/graphics/SourceBufferPrivateClient.h index 1f90927093a13..fcb649cdd42ce 100644 --- a/Source/WebCore/platform/graphics/SourceBufferPrivateClient.h +++ b/Source/WebCore/platform/graphics/SourceBufferPrivateClient.h @@ -33,6 +33,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/graphics/VP9Utilities.cpp b/Source/WebCore/platform/graphics/VP9Utilities.cpp index 29998ccc7b0c1..7a794d1cf3c28 100644 --- a/Source/WebCore/platform/graphics/VP9Utilities.cpp +++ b/Source/WebCore/platform/graphics/VP9Utilities.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include namespace WebCore { diff --git a/Source/WebCore/platform/graphics/avfoundation/InbandTextTrackPrivateAVF.cpp b/Source/WebCore/platform/graphics/avfoundation/InbandTextTrackPrivateAVF.cpp index b922ea16de4ec..da9f49aa04df9 100644 --- a/Source/WebCore/platform/graphics/avfoundation/InbandTextTrackPrivateAVF.cpp +++ b/Source/WebCore/platform/graphics/avfoundation/InbandTextTrackPrivateAVF.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include diff --git a/Source/WebCore/platform/graphics/avfoundation/SampleBufferDisplayLayer.h b/Source/WebCore/platform/graphics/avfoundation/SampleBufferDisplayLayer.h index 46642af8e0915..f4654a2806f08 100644 --- a/Source/WebCore/platform/graphics/avfoundation/SampleBufferDisplayLayer.h +++ b/Source/WebCore/platform/graphics/avfoundation/SampleBufferDisplayLayer.h @@ -31,15 +31,6 @@ #include #include -namespace WebCore { -class SampleBufferDisplayLayerClient; -} - -namespace WTF { -template struct IsDeprecatedWeakRefSmartPointerException; -template<> struct IsDeprecatedWeakRefSmartPointerException : std::true_type { }; -} - namespace WTF { class MediaTime; } @@ -56,6 +47,11 @@ class SampleBufferDisplayLayerClient : public CanMakeWeakPtr { @@ -97,6 +93,7 @@ class SampleBufferDisplayLayer : public ThreadSafeRefCountedAndCanMakeThreadSafe protected: explicit SampleBufferDisplayLayer(SampleBufferDisplayLayerClient&); + bool canShowWhileLocked(); WeakPtr m_client; private: @@ -108,4 +105,13 @@ inline SampleBufferDisplayLayer::SampleBufferDisplayLayer(SampleBufferDisplayLay { } +inline bool SampleBufferDisplayLayer::canShowWhileLocked() +{ +#if PLATFORM(IOS_FAMILY) + return m_client && m_client->canShowWhileLocked(); +#else + return false; +#endif +} + } diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/AVAssetMIMETypeCache.mm b/Source/WebCore/platform/graphics/avfoundation/objc/AVAssetMIMETypeCache.mm index 20359def40382..a2506f5ed849a 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/AVAssetMIMETypeCache.mm +++ b/Source/WebCore/platform/graphics/avfoundation/objc/AVAssetMIMETypeCache.mm @@ -34,6 +34,7 @@ #import #import #import +#import #import #import diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/ImageDecoderAVFObjC.h b/Source/WebCore/platform/graphics/avfoundation/objc/ImageDecoderAVFObjC.h index b38867750a06d..06aca7a64e5ea 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/ImageDecoderAVFObjC.h +++ b/Source/WebCore/platform/graphics/avfoundation/objc/ImageDecoderAVFObjC.h @@ -87,7 +87,7 @@ class ImageDecoderAVFObjC : public ImageDecoder { WEBCORE_EXPORT void setExpectedContentSize(long long) final; WEBCORE_EXPORT void setData(const FragmentedSharedBuffer&, bool allDataReceived) final; bool isAllDataReceived() const final { return m_isAllDataReceived; } - void clearFrameBufferCache(size_t) final { } + WEBCORE_EXPORT void clearFrameBufferCache(size_t) final; bool hasTrack() const { return !!m_track; } WEBCORE_EXPORT Vector frameInfos() const; @@ -98,7 +98,7 @@ class ImageDecoderAVFObjC : public ImageDecoder { AVAssetTrack *firstEnabledTrack(); void readSamples(); void readTrackMetadata(); - bool createFrameImageFromSampleBuffer(CMSampleBufferRef, CGImageRef *imageOut); + bool storeSampleBuffer(CMSampleBufferRef); void advanceCursor(); void setTrack(AVAssetTrack *); diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/ImageDecoderAVFObjC.mm b/Source/WebCore/platform/graphics/avfoundation/objc/ImageDecoderAVFObjC.mm index 754e1252784ac..658adc5225b0b 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/ImageDecoderAVFObjC.mm +++ b/Source/WebCore/platform/graphics/avfoundation/objc/ImageDecoderAVFObjC.mm @@ -240,9 +240,17 @@ - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingR return adoptRef(*new ImageDecoderAVFObjCSample(WTFMove(sampleBuffer))); } - void setAlpha(bool hasAlpha) + CGImageRef image() const { return m_image.get(); } + void setImage(RetainPtr&& image) { - m_hasAlpha = hasAlpha; + m_image = WTFMove(image); + if (!m_image) { + m_hasAlpha = false; + return; + } + + auto alphaInfo = CGImageGetAlphaInfo(m_image.get()); + m_hasAlpha = alphaInfo != kCGImageAlphaNone && alphaInfo != kCGImageAlphaNoneSkipLast && alphaInfo != kCGImageAlphaNoneSkipFirst; } std::optional byteRange() const final @@ -286,9 +294,15 @@ SampleFlags flags() const override return { { CheckedSize(byteOffset), singleSizeEntry } }; } + RetainPtr m_image; bool m_hasAlpha { false }; }; +static ImageDecoderAVFObjCSample* toSample(const PresentationOrderSampleMap::value_type& pair) +{ + return (ImageDecoderAVFObjCSample*)pair.second.ptr(); +} + template ImageDecoderAVFObjCSample* toSample(Iterator iter) { @@ -406,18 +420,8 @@ SampleFlags flags() const override m_size = expandedIntSize(m_imageRotationSession->rotatedSize()); } -bool ImageDecoderAVFObjC::createFrameImageFromSampleBuffer(CMSampleBufferRef sampleBuffer, CGImageRef *imageOut) +bool ImageDecoderAVFObjC::storeSampleBuffer(CMSampleBufferRef sampleBuffer) { - if (!sampleBuffer) { - RELEASE_LOG_ERROR(Images, "ImageDecoderAVFObjC::storeSampleBuffer(%p) - sampleBuffer is null", this); - return false; - } - - if (!imageOut) { - RELEASE_LOG_ERROR(Images, "ImageDecoderAVFObjC::storeSampleBuffer(%p) - imageOut is null", this); - return false; - } - auto pixelBuffer = m_decompressionSession->decodeSampleSync(sampleBuffer); if (!pixelBuffer) { RELEASE_LOG_ERROR(Images, "ImageDecoderAVFObjC::storeSampleBuffer(%p) - could not decode sampleBuffer", this); @@ -441,24 +445,18 @@ SampleFlags flags() const override setOwnershipIdentityForCVPixelBuffer(pixelBuffer.get(), m_resourceOwner); } - if (noErr != VTCreateCGImageFromCVPixelBuffer(pixelBuffer.get(), nullptr, imageOut)) { + CGImageRef rawImage = nullptr; + if (noErr != VTCreateCGImageFromCVPixelBuffer(pixelBuffer.get(), nullptr, &rawImage)) { RELEASE_LOG_ERROR(Images, "ImageDecoderAVFObjC::storeSampleBuffer(%p) - could not create CGImage from pixelBuffer", this); return false; } - auto alphaFromFrameImage = [](CGImageRef image) -> bool { - if (!image) - return false; - auto alphaInfo = CGImageGetAlphaInfo(image); - return alphaInfo != kCGImageAlphaNone && alphaInfo != kCGImageAlphaNoneSkipLast && alphaInfo != kCGImageAlphaNoneSkipFirst; - }; - // FIXME(rdar://115887662): The pixel buffer being passed to the VTCreateCGImageFromCVPixelBuffer may // not be the pixel buffer that will be used. It is unclear if the request to // obtain RGBA IOSurface-backed CVPixelBuffer from the decoding session is enough // to ensure the pixel buffer is not replaced in VTCreateCGImageFromCVPixelBuffer. - toSample(iter)->setAlpha(alphaFromFrameImage(*imageOut)); + toSample(iter)->setImage(adoptCF(rawImage)); return true; } @@ -592,6 +590,9 @@ SampleFlags flags() const override if (!sampleData) return nullptr; + if (auto image = sampleData->image()) + return image; + if (m_cursor == m_sampleData.decodeOrder().end()) m_cursor = m_sampleData.decodeOrder().begin(); @@ -606,9 +607,7 @@ SampleFlags flags() const override } while (--m_cursor != m_sampleData.decodeOrder().begin()); } - CGImageRef image = nullptr; - - for (bool succeeded = true; succeeded && !image; advanceCursor()) { + while (true) { if (decodeTime < m_cursor->second->decodeTime()) return nullptr; @@ -646,10 +645,19 @@ SampleFlags flags() const override } auto cursorSampleBuffer = cursorSample->sampleBuffer(); - succeeded = createFrameImageFromSampleBuffer(cursorSampleBuffer, &image); + if (!cursorSampleBuffer) + break; + + if (!storeSampleBuffer(cursorSampleBuffer)) + break; + + advanceCursor(); + if (auto image = sampleData->image()) + return image; } - return adoptCF(image); + advanceCursor(); + return nullptr; } void ImageDecoderAVFObjC::setExpectedContentSize(long long expectedContentSize) @@ -675,6 +683,16 @@ SampleFlags flags() const override } } +void ImageDecoderAVFObjC::clearFrameBufferCache(size_t index) +{ + size_t i = 0; + for (auto& samplePair : m_sampleData.presentationOrder()) { + toSample(samplePair)->setImage(nullptr); + if (++i > index) + break; + } +} + const ImageDecoderAVFObjCSample* ImageDecoderAVFObjC::sampleAtIndex(size_t index) const { if (index >= m_sampleData.presentationOrder().size()) diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm index 753bd77ca4354..91a9281dc7ccf 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm +++ b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm @@ -1154,7 +1154,7 @@ static URL conformFragmentIdentifierForURL(const URL& url) if (m_muted) { [m_avPlayer setMuted:m_muted]; -#if HAVE(AVPLAYER_SUPRESSES_AUDIO_RENDERING) +#if HAVE(AVPLAYER_SUPPRESSES_AUDIO_RENDERING) if (player->isVideoPlayer()) m_avPlayer.get().suppressesAudioRendering = YES; #endif @@ -1685,7 +1685,7 @@ static URL conformFragmentIdentifierForURL(const URL& url) return; [m_avPlayer setMuted:m_muted]; -#if HAVE(AVPLAYER_SUPRESSES_AUDIO_RENDERING) +#if HAVE(AVPLAYER_SUPPRESSES_AUDIO_RENDERING) if (!m_muted) m_avPlayer.get().suppressesAudioRendering = NO; #endif diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.h b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.h index 601962a0785b0..a696f2f1e7b48 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.h +++ b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.h @@ -289,6 +289,9 @@ class MediaPlayerPrivateMediaStreamAVFObjC final // SampleBufferDisplayLayerClient void sampleBufferDisplayLayerStatusDidFail() final; +#if PLATFORM(IOS_FAMILY) + bool canShowWhileLocked() const final; +#endif RetainPtr m_boundsChangeListener; diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.mm b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.mm index 55c560190e438..4fbccddec5f0e 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.mm +++ b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.mm @@ -47,6 +47,7 @@ #import #import #import +#include #import "CoreVideoSoftLink.h" #import @@ -351,6 +352,14 @@ void getSupportedTypes(HashSet& types) const final updateLayersAsNeeded(); } +#if PLATFORM(IOS_FAMILY) +bool MediaPlayerPrivateMediaStreamAVFObjC::canShowWhileLocked() const +{ + auto player = m_player.get(); + return player && player->canShowWhileLocked(); +} +#endif + void MediaPlayerPrivateMediaStreamAVFObjC::applicationDidBecomeActive() { if (m_sampleBufferDisplayLayer && m_sampleBufferDisplayLayer->didFail()) { diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/QueuedVideoOutput.mm b/Source/WebCore/platform/graphics/avfoundation/objc/QueuedVideoOutput.mm index 375c9af246c25..5d29ebdafbcdf 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/QueuedVideoOutput.mm +++ b/Source/WebCore/platform/graphics/avfoundation/objc/QueuedVideoOutput.mm @@ -31,7 +31,6 @@ #include #include #include -#include #include #include diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferParserAVFObjC.mm b/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferParserAVFObjC.mm index 70ab40816b23f..cd8c98e955eba 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferParserAVFObjC.mm +++ b/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferParserAVFObjC.mm @@ -50,6 +50,7 @@ #import #import #import +#import #import #import diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/WebCoreAVFResourceLoader.mm b/Source/WebCore/platform/graphics/avfoundation/objc/WebCoreAVFResourceLoader.mm index a2bcd39c4803f..fbc58027c82ac 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/WebCoreAVFResourceLoader.mm +++ b/Source/WebCore/platform/graphics/avfoundation/objc/WebCoreAVFResourceLoader.mm @@ -44,6 +44,7 @@ #import #import #import +#import @interface AVAssetResourceLoadingContentInformationRequest (WebKitExtensions) @property (nonatomic, getter=isEntireLengthAvailableOnDemand) BOOL entireLengthAvailableOnDemand; diff --git a/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.cpp b/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.cpp index e971f8c2331c2..346c3ae6bc774 100644 --- a/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.cpp +++ b/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.cpp @@ -65,7 +65,7 @@ #include #include #include -#include +#include #include #if PLATFORM(IOS_FAMILY) diff --git a/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.h b/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.h index 3eb22bfe6f902..660ff25265cd6 100644 --- a/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.h +++ b/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.h @@ -32,6 +32,7 @@ #include "PlatformCALayerClient.h" #include #include +#include #include // Enable this to add a light red wash over the visible portion of Tiled Layers, as computed diff --git a/Source/WebCore/platform/graphics/ca/PlatformCAAnimation.cpp b/Source/WebCore/platform/graphics/ca/PlatformCAAnimation.cpp index ae1c3f8205dea..112b718c87f84 100644 --- a/Source/WebCore/platform/graphics/ca/PlatformCAAnimation.cpp +++ b/Source/WebCore/platform/graphics/ca/PlatformCAAnimation.cpp @@ -26,6 +26,7 @@ #include "config.h" #include "PlatformCAAnimation.h" +#include #include #include diff --git a/Source/WebCore/platform/graphics/ca/TileController.cpp b/Source/WebCore/platform/graphics/ca/TileController.cpp index dcc3a3432d1a8..d229f5c6659f4 100644 --- a/Source/WebCore/platform/graphics/ca/TileController.cpp +++ b/Source/WebCore/platform/graphics/ca/TileController.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #if HAVE(IOSURFACE) diff --git a/Source/WebCore/platform/graphics/ca/cocoa/PlatformCAFiltersCocoa.mm b/Source/WebCore/platform/graphics/ca/cocoa/PlatformCAFiltersCocoa.mm index cb6500ae65c47..d9d9dab833e48 100644 --- a/Source/WebCore/platform/graphics/ca/cocoa/PlatformCAFiltersCocoa.mm +++ b/Source/WebCore/platform/graphics/ca/cocoa/PlatformCAFiltersCocoa.mm @@ -33,7 +33,7 @@ #import #import #import -#import +#import namespace WebCore { diff --git a/Source/WebCore/platform/graphics/cg/ImageBufferUtilitiesCG.cpp b/Source/WebCore/platform/graphics/cg/ImageBufferUtilitiesCG.cpp index ec2f0b5adc313..a9a81af35ae3b 100644 --- a/Source/WebCore/platform/graphics/cg/ImageBufferUtilitiesCG.cpp +++ b/Source/WebCore/platform/graphics/cg/ImageBufferUtilitiesCG.cpp @@ -36,6 +36,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/graphics/cg/ImageUtilitiesCG.cpp b/Source/WebCore/platform/graphics/cg/ImageUtilitiesCG.cpp index caf4bb0b646a6..bba0d64d87746 100644 --- a/Source/WebCore/platform/graphics/cg/ImageUtilitiesCG.cpp +++ b/Source/WebCore/platform/graphics/cg/ImageUtilitiesCG.cpp @@ -32,6 +32,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/graphics/cocoa/FontPlatformDataCocoa.mm b/Source/WebCore/platform/graphics/cocoa/FontPlatformDataCocoa.mm index 89f50438c7992..6da05aabb09f7 100644 --- a/Source/WebCore/platform/graphics/cocoa/FontPlatformDataCocoa.mm +++ b/Source/WebCore/platform/graphics/cocoa/FontPlatformDataCocoa.mm @@ -28,7 +28,6 @@ #import "SharedBuffer.h" #import #import -#import #if PLATFORM(IOS_FAMILY) #import diff --git a/Source/WebCore/platform/graphics/cocoa/GraphicsContextCocoa.mm b/Source/WebCore/platform/graphics/cocoa/GraphicsContextCocoa.mm index 6e2158a890fc5..8ce4946cf19b6 100644 --- a/Source/WebCore/platform/graphics/cocoa/GraphicsContextCocoa.mm +++ b/Source/WebCore/platform/graphics/cocoa/GraphicsContextCocoa.mm @@ -63,15 +63,39 @@ // NSColor, NSBezierPath, and NSGraphicsContext calls do not raise exceptions // so we don't block exceptions. -#if USE(APPLE_INTERNAL_SDK) -#import -#else #if ENABLE(MULTI_REPRESENTATION_HEIC) -ImageDrawResult GraphicsContext::drawMultiRepresentationHEIC(Image&, const Font&, const FloatRect&, ImagePaintingOptions) + +ImageDrawResult GraphicsContext::drawMultiRepresentationHEIC(Image& image, const Font& font, const FloatRect& destination, ImagePaintingOptions options) { - return ImageDrawResult::DidNothing; + RetainPtr multiRepresentationHEIC = image.adapter().multiRepresentationHEIC(); + if (!multiRepresentationHEIC) + return ImageDrawResult::DidNothing; + + RefPtr imageBuffer = createScaledImageBuffer(destination.size(), scaleFactor(), DestinationColorSpace::SRGB(), RenderingMode::Unaccelerated, RenderingMethod::Local); + if (!imageBuffer) + return ImageDrawResult::DidNothing; + + CGContextRef cgContext = imageBuffer->context().platformContext(); + + CGContextScaleCTM(cgContext, 1, -1); + CGContextTranslateCTM(cgContext, 0, -destination.height()); + + // FIXME (rdar://123044459): This needs to account for vertical writing modes. + CGContextSetTextPosition(cgContext, 0, font.metricsForMultiRepresentationHEIC().descent); + +ALLOW_DEPRECATED_DECLARATIONS_BEGIN + CTFontDrawImageFromEmojiImageProviderAtPoint(font.getCTFont(), multiRepresentationHEIC.get(), CGContextGetTextPosition(cgContext), cgContext); +ALLOW_DEPRECATED_DECLARATIONS_END + + auto orientation = options.orientation(); + if (orientation == ImageOrientation::Orientation::FromImage) + orientation = image.orientation(); + + drawImageBuffer(*imageBuffer, destination, { options, orientation }); + + return ImageDrawResult::DidDraw; } -#endif + #endif void GraphicsContextCG::drawFocusRing(const Path& path, float, const Color& color) diff --git a/Source/WebCore/platform/graphics/cocoa/MediaPlayerPrivateWebM.mm b/Source/WebCore/platform/graphics/cocoa/MediaPlayerPrivateWebM.mm index aac67ce17ec6d..1d6669e2061a4 100644 --- a/Source/WebCore/platform/graphics/cocoa/MediaPlayerPrivateWebM.mm +++ b/Source/WebCore/platform/graphics/cocoa/MediaPlayerPrivateWebM.mm @@ -368,7 +368,6 @@ static bool isCopyDisplayedPixelBufferAvailable() auto pendingSeek = std::exchange(m_pendingSeek, { }).value(); m_lastSeekTime = pendingSeek.time; - [m_synchronizer setRate:0 time:PAL::toCMTime(m_lastSeekTime)]; m_seekState = Seeking; @@ -386,6 +385,7 @@ static bool isCopyDisplayedPixelBufferAvailable() } flush(); + [m_synchronizer setRate:0 time:PAL::toCMTime(m_lastSeekTime)]; for (auto& trackBufferPair : m_trackBufferMap) { TrackBuffer& trackBuffer = trackBufferPair.second; diff --git a/Source/WebCore/platform/graphics/cocoa/UnrealizedCoreTextFont.cpp b/Source/WebCore/platform/graphics/cocoa/UnrealizedCoreTextFont.cpp index bd1e4b4adbae1..6ac241d1ed774 100644 --- a/Source/WebCore/platform/graphics/cocoa/UnrealizedCoreTextFont.cpp +++ b/Source/WebCore/platform/graphics/cocoa/UnrealizedCoreTextFont.cpp @@ -82,14 +82,9 @@ void UnrealizedCoreTextFont::addAttributesForOpticalSizing(CFMutableDictionaryRe WTF::switchOn(opticalSizingType, [&](OpticalSizingTypes::None) { CFDictionarySetValue(attributes, kCTFontOpticalSizeAttribute, CFSTR("none")); }, [&](OpticalSizingTypes::JustVariation) { -#if USE(VARIABLE_OPTICAL_SIZING) // FIXME: https://bugs.webkit.org/show_bug.cgi?id=252592 We should never be enabling just the opsz variation without also enabling trak. // We should delete this and use the OpticalSizingType::Everything path instead. variationsToBeApplied.set({ { 'o', 'p', 's', 'z' } }, size); -#else - UNUSED_PARAM(variationsToBeApplied); - UNUSED_PARAM(size); -#endif }, [&](const OpticalSizingTypes::Everything& everything) { if (everything.opticalSizingValue) { auto number = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &everything.opticalSizingValue.value())); diff --git a/Source/WebCore/platform/graphics/coretext/FontCascadeCoreText.cpp b/Source/WebCore/platform/graphics/coretext/FontCascadeCoreText.cpp index 154fe2e3e0911..d58a2da2396e0 100644 --- a/Source/WebCore/platform/graphics/coretext/FontCascadeCoreText.cpp +++ b/Source/WebCore/platform/graphics/coretext/FontCascadeCoreText.cpp @@ -422,7 +422,7 @@ const Font* FontCascade::fontForCombiningCharacterSequence(StringView stringView for (unsigned i = 0; !fallbackRangesAt(i).isNull(); ++i) { auto& fontRanges = fallbackRangesAt(i); - if (fontRanges.isGeneric() && isPrivateUseAreaCharacter(baseCharacter)) + if (fontRanges.isGenericFontFamily() && isPrivateUseAreaCharacter(baseCharacter)) continue; const Font* font = fontRanges.fontForCharacter(baseCharacter); if (!font) diff --git a/Source/WebCore/platform/graphics/coretext/FontCoreText.cpp b/Source/WebCore/platform/graphics/coretext/FontCoreText.cpp index ff250b86b6a5d..8f1c1af0ce0f4 100644 --- a/Source/WebCore/platform/graphics/coretext/FontCoreText.cpp +++ b/Source/WebCore/platform/graphics/coretext/FontCoreText.cpp @@ -913,15 +913,23 @@ bool Font::hasAnyComplexColorFormatGlyphs(const GlyphBufferGlyph* glyphs, unsign return false; } -#if USE(APPLE_INTERNAL_SDK) -#include -#else #if ENABLE(MULTI_REPRESENTATION_HEIC) + MultiRepresentationHEICMetrics Font::metricsForMultiRepresentationHEIC() const { - return { }; + CGFloat ascent; + CGFloat descent; +ALLOW_DEPRECATED_DECLARATIONS_BEGIN + CGFloat width = CTFontGetTypographicBoundsForEmojiImageProvider(getCTFont(), nullptr, &ascent, &descent); +ALLOW_DEPRECATED_DECLARATIONS_END + + MultiRepresentationHEICMetrics metrics; + metrics.ascent = ascent; + metrics.descent = descent; + metrics.width = width; + return metrics; } -#endif + #endif } // namespace WebCore diff --git a/Source/WebCore/platform/graphics/coretext/FontPlatformDataCoreText.cpp b/Source/WebCore/platform/graphics/coretext/FontPlatformDataCoreText.cpp index e3b685a63063d..c79497b746895 100644 --- a/Source/WebCore/platform/graphics/coretext/FontPlatformDataCoreText.cpp +++ b/Source/WebCore/platform/graphics/coretext/FontPlatformDataCoreText.cpp @@ -28,7 +28,7 @@ #include "SharedBuffer.h" #include #include -#include +#include #include namespace WebCore { diff --git a/Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp b/Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp index d043de3e8234e..7ea05916a9f3e 100644 --- a/Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp @@ -50,6 +50,7 @@ #include #include #include +#include #include #if USE(GSTREAMER_MPEGTS) @@ -971,6 +972,44 @@ GstElement* makeGStreamerBin(const char* description, bool ghostUnlinkedPads) return bin; } +#if USE(GSTREAMER_WEBRTC) +static ASCIILiteral webrtcStatsTypeName(int value) +{ + switch (value) { + case GST_WEBRTC_STATS_CODEC: + return "codec"_s; + case GST_WEBRTC_STATS_INBOUND_RTP: + return "inbound-rtp"_s; + case GST_WEBRTC_STATS_OUTBOUND_RTP: + return "outbound-rtp"_s; + case GST_WEBRTC_STATS_REMOTE_INBOUND_RTP: + return "remote-inbound-rtp"_s; + case GST_WEBRTC_STATS_REMOTE_OUTBOUND_RTP: + return "remote-outbound-rtp"_s; + case GST_WEBRTC_STATS_CSRC: + return "csrc"_s; + case GST_WEBRTC_STATS_PEER_CONNECTION: + return "peer-connection"_s; + case GST_WEBRTC_STATS_TRANSPORT: + return "transport"_s; + case GST_WEBRTC_STATS_STREAM: + return "stream"_s; + case GST_WEBRTC_STATS_DATA_CHANNEL: + return "data-channel"_s; + case GST_WEBRTC_STATS_LOCAL_CANDIDATE: + return "local-candidate"_s; + case GST_WEBRTC_STATS_REMOTE_CANDIDATE: + return "remote-candidate"_s; + case GST_WEBRTC_STATS_CANDIDATE_PAIR: + return "candidate-pair"_s; + case GST_WEBRTC_STATS_CERTIFICATE: + return "certificate"_s; + } + ASSERT_NOT_REACHED(); + return nullptr; +} +#endif + static RefPtr gstStructureToJSON(const GstStructure*); static std::optional> gstStructureValueToJSON(const GValue* value) @@ -1004,7 +1043,7 @@ static std::optional> gstStructureValueToJSON(const GValue* return JSON::Value::create(g_value_get_int(value))->asValue(); if (valueType == G_TYPE_UINT) - return JSON::Value::create(static_cast(g_value_get_uint(value)))->asValue(); + return JSON::Value::create(static_cast(g_value_get_uint(value)))->asValue(); if (valueType == G_TYPE_DOUBLE) return JSON::Value::create(g_value_get_double(value))->asValue(); @@ -1029,8 +1068,9 @@ static std::optional> gstStructureValueToJSON(const GValue* #if USE(GSTREAMER_WEBRTC) if (valueType == GST_TYPE_WEBRTC_STATS_TYPE) { - GUniquePtr statsType(g_enum_to_string(GST_TYPE_WEBRTC_STATS_TYPE, g_value_get_enum(value))); - return JSON::Value::create(makeString(span(statsType.get())))->asValue(); + auto name = webrtcStatsTypeName(g_value_get_enum(value)); + if (LIKELY(name.isEmpty())) + return JSON::Value::create(makeString(name))->asValue(); } #endif diff --git a/Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.h b/Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.h index e9909b4140066..7d738ed9b18e4 100644 --- a/Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.h +++ b/Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.h @@ -279,19 +279,19 @@ inline std::optional gstStructureGet(const GstStructure* structure, ASCIILite T value; if constexpr(std::is_same_v) { - if (gst_structure_get_int(structure, key.characters(), &value)) + if (UNLIKELY(gst_structure_get_int(structure, key.characters(), &value))) return value; } else if constexpr(std::is_same_v) { - if (gst_structure_get_int64(structure, key.characters(), &value)) + if (UNLIKELY(gst_structure_get_int64(structure, key.characters(), &value))) return value; } else if constexpr(std::is_same_v) { - if (gst_structure_get_uint(structure, key.characters(), &value)) + if (UNLIKELY(gst_structure_get_uint(structure, key.characters(), &value))) return value; } else if constexpr(std::is_same_v) { - if (gst_structure_get_uint64(structure, key.characters(), &value)) + if (UNLIKELY(gst_structure_get_uint64(structure, key.characters(), &value))) return value; } else if constexpr(std::is_same_v) { - if (gst_structure_get_double(structure, key.characters(), &value)) + if (UNLIKELY(gst_structure_get_double(structure, key.characters(), &value))) return value; } return std::nullopt; diff --git a/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.cpp b/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.cpp index 823cb4ab782d1..831cd214504d6 100644 --- a/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #if USE(GSTREAMER_WEBRTC) @@ -1153,6 +1154,21 @@ Vector GStreamerRegistryScanner:: return *m_videoRtpExtensions; } +GStreamerRegistryScanner::RegistryLookupResult GStreamerRegistryScanner::isRtpPacketizerSupported(const String& encoding) +{ + static HashMap mapping = { { "h264"_s, "video/x-h264"_s }, { "vp8"_s, "video/x-vp8"_s }, + { "vp9"_s, "video/x-vp9"_s }, { "av1"_s, "video/x-av1"_s }, { "h265"_s, "video/x-h265"_s }, { "opus"_s, "audio/x-opus"_s }, + { "g722"_s, "audio/G722"_s }, { "pcma"_s, "audio/x-alaw"_s }, { "pcmu"_s, "audio/x-mulaw"_s } }; + auto gstCapsName = mapping.getOptional(encoding); + if (!gstCapsName) { + GST_WARNING("Unhandled RTP encoding-name: %s", encoding.ascii().data()); + return { }; + } + + ElementFactories factories(ElementFactories::Type::RtpPayloader); + return factories.hasElementForMediaType(ElementFactories::Type::RtpPayloader, *gstCapsName); +} + #endif // USE(GSTREAMER_WEBRTC) #undef GST_CAT_DEFAULT diff --git a/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.h b/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.h index e8978f6de641c..1451c29ee20b1 100644 --- a/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.h +++ b/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.h @@ -113,6 +113,7 @@ class GStreamerRegistryScanner { RTCRtpCapabilities videoRtpCapabilities(Configuration); Vector audioRtpExtensions(); Vector videoRtpExtensions(); + RegistryLookupResult isRtpPacketizerSupported(const String& encoding); #endif protected: diff --git a/Source/WebCore/platform/graphics/gstreamer/ImageDecoderGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/ImageDecoderGStreamer.cpp index 3cdbbbcfab3ad..5ae702a055a44 100644 --- a/Source/WebCore/platform/graphics/gstreamer/ImageDecoderGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/ImageDecoderGStreamer.cpp @@ -31,6 +31,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp index 1887533682b78..c7b1f16ebe70f 100644 --- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp @@ -99,7 +99,7 @@ #include #include #include -#include +#include #include #if USE(GSTREAMER_MPEGTS) @@ -3213,6 +3213,10 @@ void MediaPlayerPrivateGStreamer::configureVideoDecoder(GstElement* decoder) configureMediaStreamVideoDecoder(decoder); auto pad = adoptGRef(gst_element_get_static_pad(decoder, "src")); + if (!pad) { + GST_INFO_OBJECT(pipeline(), "the decoder %s does not have a src pad, probably because it's a hardware decoder sink, can't get decoder stats", name.get()); + return; + } gst_pad_add_probe(pad.get(), static_cast(GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM | GST_PAD_PROBE_TYPE_BUFFER), [](GstPad*, GstPadProbeInfo* info, gpointer userData) -> GstPadProbeReturn { auto* player = static_cast(userData); if (GST_PAD_PROBE_INFO_TYPE(info) & GST_PAD_PROBE_TYPE_BUFFER) { diff --git a/Source/WebCore/platform/graphics/gstreamer/VideoDecoderGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/VideoDecoderGStreamer.cpp index fbfc46c0c9617..bf9e60f4b7b9e 100644 --- a/Source/WebCore/platform/graphics/gstreamer/VideoDecoderGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/VideoDecoderGStreamer.cpp @@ -27,6 +27,7 @@ #include "GStreamerRegistryScanner.h" #include "VideoFrameGStreamer.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/graphics/gstreamer/VideoEncoderGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/VideoEncoderGStreamer.cpp index 7d40f6c91235f..ef4c1e5129173 100644 --- a/Source/WebCore/platform/graphics/gstreamer/VideoEncoderGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/VideoEncoderGStreamer.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #if USE(GSTREAMER_GL) #include diff --git a/Source/WebCore/platform/graphics/gstreamer/mse/AppendPipeline.cpp b/Source/WebCore/platform/graphics/gstreamer/mse/AppendPipeline.cpp index 80e03815a216a..54dcebbeaac33 100644 --- a/Source/WebCore/platform/graphics/gstreamer/mse/AppendPipeline.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/mse/AppendPipeline.cpp @@ -43,7 +43,7 @@ #include #include #include -#include +#include GST_DEBUG_CATEGORY_EXTERN(webkit_mse_debug); #define GST_CAT_DEFAULT webkit_mse_debug diff --git a/Source/WebCore/platform/graphics/gstreamer/mse/GStreamerMediaDescription.cpp b/Source/WebCore/platform/graphics/gstreamer/mse/GStreamerMediaDescription.cpp index d6c37da93605a..edd5a0b1d7de3 100644 --- a/Source/WebCore/platform/graphics/gstreamer/mse/GStreamerMediaDescription.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/mse/GStreamerMediaDescription.cpp @@ -23,6 +23,7 @@ #include "GStreamerCommon.h" #include +#include #if ENABLE(VIDEO) && USE(GSTREAMER) && ENABLE(MEDIA_SOURCE) diff --git a/Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp b/Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp index dec2e7bbb2dc7..ced22af6f17b4 100644 --- a/Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include static const char* dumpReadyState(WebCore::MediaPlayer::ReadyState readyState) diff --git a/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.cpp index c842e1d690f02..fc324175f18db 100644 --- a/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.cpp @@ -41,6 +41,7 @@ #include #include #include +#include using namespace WTF; using namespace WebCore; diff --git a/Source/WebCore/platform/graphics/mac/ImageAdapterMac.mm b/Source/WebCore/platform/graphics/mac/ImageAdapterMac.mm index 8e820a4ea9cd7..6cb4ec892e5bf 100644 --- a/Source/WebCore/platform/graphics/mac/ImageAdapterMac.mm +++ b/Source/WebCore/platform/graphics/mac/ImageAdapterMac.mm @@ -33,6 +33,10 @@ #import #import +#if ENABLE(MULTI_REPRESENTATION_HEIC) +#import "PlatformNSAdaptiveImageGlyph.h" +#endif + #if PLATFORM(IOS_FAMILY) #import "UIFoundationSoftLink.h" #import @@ -40,10 +44,6 @@ #import #endif -#if USE(APPLE_INTERNAL_SDK) -#include -#endif - @interface WebCoreBundleFinder : NSObject @end @@ -95,7 +95,7 @@ @implementation WebCoreBundleFinder } #if ENABLE(MULTI_REPRESENTATION_HEIC) -WebMultiRepresentationHEICAttachment *ImageAdapter::multiRepresentationHEIC() +NSAdaptiveImageGlyph *ImageAdapter::multiRepresentationHEIC() { if (m_multiRepHEIC) return m_multiRepHEIC.get(); @@ -107,7 +107,7 @@ @implementation WebCoreBundleFinder Vector data = buffer->copyData(); RetainPtr nsData = toNSData(data.span()); - m_multiRepHEIC = adoptNS([[PlatformWebMultiRepresentationHEICAttachment alloc] initWithImageContent:nsData.get()]); + m_multiRepHEIC = adoptNS([[PlatformNSAdaptiveImageGlyph alloc] initWithImageContent:nsData.get()]); return m_multiRepHEIC.get(); } diff --git a/Source/WebCore/platform/graphics/skia/FontCascadeSkia.cpp b/Source/WebCore/platform/graphics/skia/FontCascadeSkia.cpp index 7673630537159..b70211ac70597 100644 --- a/Source/WebCore/platform/graphics/skia/FontCascadeSkia.cpp +++ b/Source/WebCore/platform/graphics/skia/FontCascadeSkia.cpp @@ -138,7 +138,7 @@ const Font* FontCascade::fontForCombiningCharacterSequence(StringView stringView bool triedBaseCharacterFont = false; for (unsigned i = 0; !fallbackRangesAt(i).isNull(); ++i) { auto& fontRanges = fallbackRangesAt(i); - if (fontRanges.isGeneric() && isPrivateUseAreaCharacter(baseCharacter)) + if (fontRanges.isGenericFontFamily() && isPrivateUseAreaCharacter(baseCharacter)) continue; const Font* font = fontRanges.fontForCharacter(baseCharacter); diff --git a/Source/WebCore/platform/gstreamer/GStreamerCodecUtilities.cpp b/Source/WebCore/platform/gstreamer/GStreamerCodecUtilities.cpp index 89dbdfca3b065..2dee3eb580104 100644 --- a/Source/WebCore/platform/gstreamer/GStreamerCodecUtilities.cpp +++ b/Source/WebCore/platform/gstreamer/GStreamerCodecUtilities.cpp @@ -27,6 +27,7 @@ #include "VP9Utilities.h" #include #include +#include #include #include #include diff --git a/Source/WebCore/platform/gstreamer/GStreamerElementHarness.cpp b/Source/WebCore/platform/gstreamer/GStreamerElementHarness.cpp index 12c4d61a68b56..c8247479faf05 100644 --- a/Source/WebCore/platform/gstreamer/GStreamerElementHarness.cpp +++ b/Source/WebCore/platform/gstreamer/GStreamerElementHarness.cpp @@ -27,9 +27,8 @@ #include #include #include +#include #include -#include -#include namespace WebCore { diff --git a/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkBcmNexus.cpp b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkBcmNexus.cpp index 37e3a70c4621c..aef32a25d07ef 100644 --- a/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkBcmNexus.cpp +++ b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkBcmNexus.cpp @@ -22,6 +22,7 @@ #include "GStreamerHolePunchQuirkBcmNexus.h" #include "GStreamerCommon.h" +#include #if USE(GSTREAMER) diff --git a/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkRialto.cpp b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkRialto.cpp index 8b73d800f4885..903f5bf85acf3 100644 --- a/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkRialto.cpp +++ b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkRialto.cpp @@ -30,6 +30,7 @@ #include "GStreamerCommon.h" #include "MediaPlayerPrivateGStreamer.h" +#include namespace WebCore { diff --git a/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkWesteros.cpp b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkWesteros.cpp index 0fd94427142e3..4d7a6f4a95d05 100644 --- a/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkWesteros.cpp +++ b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkWesteros.cpp @@ -25,6 +25,7 @@ #if USE(GSTREAMER) #include "GStreamerCommon.h" +#include namespace WebCore { diff --git a/Source/WebCore/platform/gstreamer/GStreamerQuirkBroadcom.cpp b/Source/WebCore/platform/gstreamer/GStreamerQuirkBroadcom.cpp index 01738d0e220e1..8bf195f4ab7a0 100644 --- a/Source/WebCore/platform/gstreamer/GStreamerQuirkBroadcom.cpp +++ b/Source/WebCore/platform/gstreamer/GStreamerQuirkBroadcom.cpp @@ -39,7 +39,7 @@ GStreamerQuirkBroadcom::GStreamerQuirkBroadcom() void GStreamerQuirkBroadcom::configureElement(GstElement* element, const OptionSet& characteristics) { - if (g_str_has_prefix(GST_ELEMENT_NAME(element), "brcmaudiosink")) + if (!g_strcmp0(G_OBJECT_TYPE_NAME(element), "Gstbrcmaudiosink")) g_object_set(G_OBJECT(element), "async", TRUE, nullptr); else if (g_str_has_prefix(GST_ELEMENT_NAME(element), "brcmaudiodecoder")) { // Limit BCM audio decoder buffering to 1sec so live progressive playback can start faster. diff --git a/Source/WebCore/platform/gstreamer/PlatformSpeechSynthesizerGStreamer.cpp b/Source/WebCore/platform/gstreamer/PlatformSpeechSynthesizerGStreamer.cpp index cd557a76f1a7d..d5cb6f6d5236a 100644 --- a/Source/WebCore/platform/gstreamer/PlatformSpeechSynthesizerGStreamer.cpp +++ b/Source/WebCore/platform/gstreamer/PlatformSpeechSynthesizerGStreamer.cpp @@ -34,7 +34,7 @@ #include "PlatformSpeechSynthesisVoice.h" #include "WebKitAudioSinkGStreamer.h" #include "WebKitFliteSourceGStreamer.h" -#include +#include namespace WebCore { diff --git a/Source/WebCore/platform/gstreamer/VideoEncoderPrivateGStreamer.cpp b/Source/WebCore/platform/gstreamer/VideoEncoderPrivateGStreamer.cpp index 22ab71868d615..152d2b43ee10f 100644 --- a/Source/WebCore/platform/gstreamer/VideoEncoderPrivateGStreamer.cpp +++ b/Source/WebCore/platform/gstreamer/VideoEncoderPrivateGStreamer.cpp @@ -28,7 +28,7 @@ #include "NotImplemented.h" #include #include -#include +#include #include #include @@ -958,16 +958,15 @@ static void webkit_video_encoder_class_init(WebKitVideoEncoderClass* klass) return; setBitrateKbitPerSec(object, propertyName, bitrate); auto bitrateMode = GPOINTER_TO_INT(g_object_get_qdata(object, x265BitrateQuark)); - StringBuilder builder; + String options; switch (bitrateMode) { case CONSTANT_BITRATE_MODE: - builder.append("vbv-maxrate="_s, bitrate, ":vbv-bufsize="_s, bitrate / 2); + options = makeString("vbv-maxrate="_s, bitrate, ":vbv-bufsize="_s, bitrate / 2); break; case VARIABLE_BITRATE_MODE: - builder.append("vbv-maxrate=0:vbvbufsize=0"_s); + options = "vbv-maxrate=0:vbv-bufsize=0"_s; break; }; - auto options = builder.toString(); g_object_set(object, "option-string", options.ascii().data(), nullptr); }, "key-int-max", [](GstElement* encoder, BitrateMode mode) { g_object_set_qdata(G_OBJECT(encoder), x265BitrateQuark, GINT_TO_POINTER(mode)); diff --git a/Source/WebCore/platform/gtk/PlatformKeyboardEventGtk.cpp b/Source/WebCore/platform/gtk/PlatformKeyboardEventGtk.cpp index 8185125ac6aa0..979ec06ecd698 100644 --- a/Source/WebCore/platform/gtk/PlatformKeyboardEventGtk.cpp +++ b/Source/WebCore/platform/gtk/PlatformKeyboardEventGtk.cpp @@ -38,6 +38,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/gtk/SelectionData.cpp b/Source/WebCore/platform/gtk/SelectionData.cpp index 39af3e5674b1e..1461dffb552b5 100644 --- a/Source/WebCore/platform/gtk/SelectionData.cpp +++ b/Source/WebCore/platform/gtk/SelectionData.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include diff --git a/Source/WebCore/platform/ios/UIFoundationSoftLink.h b/Source/WebCore/platform/ios/UIFoundationSoftLink.h index 7a3c86db78e9c..567e3d81b18ec 100644 --- a/Source/WebCore/platform/ios/UIFoundationSoftLink.h +++ b/Source/WebCore/platform/ios/UIFoundationSoftLink.h @@ -41,8 +41,13 @@ SOFT_LINK_CLASS_FOR_HEADER(WebCore, NSTextTableBlock) SOFT_LINK_CLASS_FOR_HEADER(WebCore, NSTextTable) SOFT_LINK_CLASS_FOR_HEADER(WebCore, NSTextTab) -#if USE(APPLE_INTERNAL_SDK) -#import +#if ENABLE(MULTI_REPRESENTATION_HEIC) + +SOFT_LINK_CLASS_FOR_HEADER(WebCore, NSAdaptiveImageGlyph) + +SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, UIFoundation, NSAdaptiveImageGlyphAttributeName, NSString *) +#define NSAdaptiveImageGlyphAttributeName WebCore::get_UIFoundation_NSAdaptiveImageGlyphAttributeName() + #endif #endif diff --git a/Source/WebCore/platform/ios/UIFoundationSoftLink.mm b/Source/WebCore/platform/ios/UIFoundationSoftLink.mm index 272323de57db2..da3564f96cca6 100644 --- a/Source/WebCore/platform/ios/UIFoundationSoftLink.mm +++ b/Source/WebCore/platform/ios/UIFoundationSoftLink.mm @@ -42,8 +42,12 @@ SOFT_LINK_CLASS_FOR_SOURCE(WebCore, UIFoundation, NSTextTable) SOFT_LINK_CLASS_FOR_SOURCE(WebCore, UIFoundation, NSTextTab) -#if USE(APPLE_INTERNAL_SDK) -#import +#if ENABLE(MULTI_REPRESENTATION_HEIC) + +SOFT_LINK_CLASS_FOR_SOURCE(WebCore, UIFoundation, NSAdaptiveImageGlyph) + +SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, UIFoundation, NSAdaptiveImageGlyphAttributeName, NSString *) + #endif #endif diff --git a/Source/WebCore/platform/ios/UserAgentIOS.mm b/Source/WebCore/platform/ios/UserAgentIOS.mm index 560e945148821..5318af672fba5 100644 --- a/Source/WebCore/platform/ios/UserAgentIOS.mm +++ b/Source/WebCore/platform/ios/UserAgentIOS.mm @@ -36,6 +36,7 @@ #import #import #import +#import #import diff --git a/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoDecoder.cpp b/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoDecoder.cpp index 88ea62f1528bc..a5026d5a0c2d6 100644 --- a/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoDecoder.cpp +++ b/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoDecoder.cpp @@ -37,7 +37,7 @@ #include #include #include -#include +#include ALLOW_UNUSED_PARAMETERS_BEGIN ALLOW_COMMA_BEGIN diff --git a/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoEncoder.cpp b/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoEncoder.cpp index 0b46647f88afa..c988a0dd07781 100644 --- a/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoEncoder.cpp +++ b/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoEncoder.cpp @@ -33,6 +33,7 @@ #include #include #include +#include ALLOW_UNUSED_PARAMETERS_BEGIN ALLOW_COMMA_BEGIN @@ -48,6 +49,8 @@ ALLOW_UNUSED_PARAMETERS_END namespace WebCore { +static constexpr double defaultFrameRate = 30.0; + static WorkQueue& vpxEncoderQueue() { static NeverDestroyed> queue(WorkQueue::create("VPX VideoEncoder Queue"_s)); @@ -64,6 +67,8 @@ class LibWebRTCVPXInternalVideoEncoder : public ThreadSafeRefCounted&& task) { m_postTaskCallback(WTFMove(task)); } void encode(VideoEncoder::RawFrame&&, bool shouldGenerateKeyFrame, VideoEncoder::EncodeCallback&&); void close() { m_isClosed = true; } + void setRates(uint64_t bitRate, double frameRate); + private: LibWebRTCVPXInternalVideoEncoder(LibWebRTCVPXVideoEncoder::Type, VideoEncoder::OutputCallback&&, VideoEncoder::PostTaskCallback&&); webrtc::EncodedImageCallback::Result OnEncodedImage(const webrtc::EncodedImage&, const webrtc::CodecSpecificInfo*) final; @@ -76,8 +81,7 @@ class LibWebRTCVPXInternalVideoEncoder : public ThreadSafeRefCounted m_duration; bool m_isClosed { false }; - uint64_t m_width { 0 }; - uint64_t m_height { 0 }; + VideoEncoder::Config m_config; bool m_isInitialized { false }; bool m_hasEncoded { false }; bool m_hasMultipleTemporalLayers { false }; @@ -141,6 +145,15 @@ void LibWebRTCVPXVideoEncoder::close() m_internalEncoder->close(); } +bool LibWebRTCVPXVideoEncoder::setRates(uint64_t bitRate, double frameRate, Function&& callback) +{ + vpxEncoderQueue().dispatch([encoder = m_internalEncoder, bitRate, frameRate, callback = WTFMove(callback)]() mutable { + encoder->setRates(bitRate, frameRate); + encoder->postTask(WTFMove(callback)); + }); + return true; +} + static UniqueRef createInternalEncoder(LibWebRTCVPXVideoEncoder::Type type) { switch (type) { @@ -164,10 +177,31 @@ LibWebRTCVPXInternalVideoEncoder::LibWebRTCVPXInternalVideoEncoder(LibWebRTCVPXV { } +static webrtc::VideoBitrateAllocation computeAllocation(const VideoEncoder::Config& config) +{ + auto totalBitRate = config.bitRate ? config.bitRate : 3 * config.width * config.height; + + webrtc::VideoBitrateAllocation allocation; + switch (config.scalabilityMode) { + case VideoEncoder::ScalabilityMode::L1T1: + allocation.SetBitrate(0, 0, totalBitRate); + break; + case VideoEncoder::ScalabilityMode::L1T2: + allocation.SetBitrate(0, 0, totalBitRate * 0.6); + allocation.SetBitrate(0, 1, totalBitRate * 0.4); + break; + case VideoEncoder::ScalabilityMode::L1T3: + allocation.SetBitrate(0, 0, totalBitRate * 0.5); + allocation.SetBitrate(0, 1, totalBitRate * 0.2); + allocation.SetBitrate(0, 2, totalBitRate * 0.3); + break; + } + return allocation; +} + int LibWebRTCVPXInternalVideoEncoder::initialize(LibWebRTCVPXVideoEncoder::Type type, const VideoEncoder::Config& config) { - m_width = config.width; - m_height = config.height; + m_config = config; const int defaultPayloadSize = 1440; webrtc::VideoCodec videoCodec; @@ -175,25 +209,17 @@ int LibWebRTCVPXInternalVideoEncoder::initialize(LibWebRTCVPXVideoEncoder::Type videoCodec.height = config.height; videoCodec.maxFramerate = 100; - webrtc::VideoBitrateAllocation allocation; - auto totalBitRate = config.bitRate ? config.bitRate : 3 * config.width * config.height; switch (config.scalabilityMode) { case VideoEncoder::ScalabilityMode::L1T1: videoCodec.SetScalabilityMode(webrtc::ScalabilityMode::kL1T1); - allocation.SetBitrate(0, 0, totalBitRate); break; case VideoEncoder::ScalabilityMode::L1T2: m_hasMultipleTemporalLayers = true; videoCodec.SetScalabilityMode(webrtc::ScalabilityMode::kL1T2); - allocation.SetBitrate(0, 0, totalBitRate * 0.6); - allocation.SetBitrate(0, 1, totalBitRate * 0.4); break; case VideoEncoder::ScalabilityMode::L1T3: m_hasMultipleTemporalLayers = true; videoCodec.SetScalabilityMode(webrtc::ScalabilityMode::kL1T3); - allocation.SetBitrate(0, 0, totalBitRate * 0.5); - allocation.SetBitrate(0, 1, totalBitRate * 0.2); - allocation.SetBitrate(0, 2, totalBitRate * 0.3); break; } @@ -229,7 +255,7 @@ int LibWebRTCVPXInternalVideoEncoder::initialize(LibWebRTCVPXVideoEncoder::Type return error; m_isInitialized = true; - m_internalEncoder->SetRates({ allocation, config.frameRate ? config.frameRate : 30.0 }); + m_internalEncoder->SetRates({ computeAllocation(config), config.frameRate ? config.frameRate : defaultFrameRate }); m_internalEncoder->RegisterEncodeCompleteCallback(this); return 0; @@ -250,8 +276,8 @@ void LibWebRTCVPXInternalVideoEncoder::encode(VideoEncoder::RawFrame&& rawFrame, auto frameBuffer = webrtc::pixelBufferToFrame(rawFrame.frame->pixelBuffer()); - if (m_width != static_cast(frameBuffer->width()) || m_height != static_cast(frameBuffer->height())) - frameBuffer = frameBuffer->Scale(m_width, m_height); + if (m_config.width != static_cast(frameBuffer->width()) || m_config.height != static_cast(frameBuffer->height())) + frameBuffer = frameBuffer->Scale(m_config.width, m_config.height); webrtc::VideoFrame frame { frameBuffer, webrtc::kVideoRotation_0, rawFrame.timestamp + m_timestampOffset }; auto error = m_internalEncoder->Encode(frame, &frameTypes); @@ -270,6 +296,15 @@ void LibWebRTCVPXInternalVideoEncoder::encode(VideoEncoder::RawFrame&& rawFrame, }); } +void LibWebRTCVPXInternalVideoEncoder::setRates(uint64_t bitRate, double frameRate) +{ + if (bitRate) + m_config.bitRate = bitRate; + if (frameRate) + m_config.frameRate = frameRate; + m_internalEncoder->SetRates({ computeAllocation(m_config), m_config.frameRate ? m_config.frameRate : defaultFrameRate }); +} + webrtc::EncodedImageCallback::Result LibWebRTCVPXInternalVideoEncoder::OnEncodedImage(const webrtc::EncodedImage& encodedImage, const webrtc::CodecSpecificInfo*) { std::optional frameTemporalIndex; diff --git a/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoEncoder.h b/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoEncoder.h index e8148c1c02f16..fd9a26dfbf033 100644 --- a/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoEncoder.h +++ b/Source/WebCore/platform/libwebrtc/LibWebRTCVPXVideoEncoder.h @@ -57,6 +57,7 @@ class LibWebRTCVPXVideoEncoder : public VideoEncoder { void flush(Function&&) final; void reset() final; void close() final; + bool setRates(uint64_t bitRate, double frameRate, Function&&) final; Ref m_internalEncoder; }; diff --git a/Source/WebCore/platform/libwpe/PlatformKeyboardEventLibWPE.cpp b/Source/WebCore/platform/libwpe/PlatformKeyboardEventLibWPE.cpp index 76f1c37bb0295..a867491642990 100644 --- a/Source/WebCore/platform/libwpe/PlatformKeyboardEventLibWPE.cpp +++ b/Source/WebCore/platform/libwpe/PlatformKeyboardEventLibWPE.cpp @@ -31,6 +31,7 @@ #include "WindowsKeyboardCodes.h" #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/platform/mac/PlatformScreenMac.mm b/Source/WebCore/platform/mac/PlatformScreenMac.mm index b8250473e2683..d529ec2100b02 100644 --- a/Source/WebCore/platform/mac/PlatformScreenMac.mm +++ b/Source/WebCore/platform/mac/PlatformScreenMac.mm @@ -128,7 +128,7 @@ ScreenProperties collectScreenProperties() auto screenSupportsHighDynamicRange = [](PlatformDisplayID displayID, DynamicRangeMode& dynamicRangeMode) { bool supportsHighDynamicRange = false; #if HAVE(AVPLAYER_VIDEORANGEOVERRIDE) - if (PAL::isAVFoundationFrameworkAvailable() && [PAL::getAVPlayerClass() respondsToSelector:@selector(preferredVideoRangeForDisplays:)]) { + if (PAL::isAVFoundationFrameworkAvailable()) { dynamicRangeMode = convertAVVideoRangeToEnum([PAL::getAVPlayerClass() preferredVideoRangeForDisplays:@[ @(displayID) ]]); supportsHighDynamicRange = dynamicRangeMode > DynamicRangeMode::Standard; } @@ -389,7 +389,7 @@ DynamicRangeMode preferredDynamicRangeMode(Widget* widget) return data->preferredDynamicRangeMode; ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer)); - if (PAL::isAVFoundationFrameworkAvailable() && [PAL::getAVPlayerClass() respondsToSelector:@selector(preferredVideoRangeForDisplays:)]) { + if (PAL::isAVFoundationFrameworkAvailable()) { auto displayID = WebCore::displayID(screen(widget)); return convertAVVideoRangeToEnum([PAL::getAVPlayerClass() preferredVideoRangeForDisplays:@[ @(displayID) ]]); } diff --git a/Source/WebCore/platform/mac/UserAgentMac.mm b/Source/WebCore/platform/mac/UserAgentMac.mm index 7ba9652539484..583eba2961cf0 100644 --- a/Source/WebCore/platform/mac/UserAgentMac.mm +++ b/Source/WebCore/platform/mac/UserAgentMac.mm @@ -29,6 +29,7 @@ #if PLATFORM(MAC) #import "SystemVersion.h" +#import namespace WebCore { diff --git a/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.cpp b/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.cpp index 1b296e05817d2..325277f7722d1 100644 --- a/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.cpp +++ b/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.cpp @@ -62,7 +62,7 @@ static RefPtr audioModuleFromSource(RealtimeMediaSource& s std::unique_ptr AudioTrackPrivateMediaStream::createRenderer(AudioTrackPrivateMediaStream& stream) { #if !RELEASE_LOG_DISABLED - auto& track = stream.m_streamTrack.get(); + auto& track = stream.m_streamTrack; #endif return AudioMediaStreamTrackRenderer::create(AudioMediaStreamTrackRenderer::Init { [stream = WeakPtr { stream }] { @@ -73,8 +73,8 @@ std::unique_ptr AudioTrackPrivateMediaStream::cre , audioModuleFromSource(stream.m_audioSource.get()) #endif #if !RELEASE_LOG_DISABLED - , track.logger() - , track.logIdentifier() + , track->logger() + , track->logIdentifier() #endif }); } diff --git a/Source/WebCore/platform/mediastream/RealtimeMediaSourceCapabilities.h b/Source/WebCore/platform/mediastream/RealtimeMediaSourceCapabilities.h index 43aa76027bd40..e6d0a0562b0a3 100644 --- a/Source/WebCore/platform/mediastream/RealtimeMediaSourceCapabilities.h +++ b/Source/WebCore/platform/mediastream/RealtimeMediaSourceCapabilities.h @@ -92,7 +92,7 @@ class RealtimeMediaSourceCapabilities { OnOff, }; - RealtimeMediaSourceCapabilities(LongCapabilityRange width, LongCapabilityRange height, DoubleCapabilityRange aspectRatio, DoubleCapabilityRange frameRate, Vector&& facingMode, DoubleCapabilityRange volume, LongCapabilityRange sampleRate, LongCapabilityRange sampleSize, EchoCancellation echoCancellation, String&& deviceId, String&& groupId, DoubleCapabilityRange focusDistance, Vector&& whiteBalanceModes, DoubleCapabilityRange zoom, bool torch, BackgroundBlur backgroundBlur, RealtimeMediaSourceSupportedConstraints&& supportedConstraints) + RealtimeMediaSourceCapabilities(LongCapabilityRange width, LongCapabilityRange height, DoubleCapabilityRange aspectRatio, DoubleCapabilityRange frameRate, Vector&& facingMode, DoubleCapabilityRange volume, LongCapabilityRange sampleRate, LongCapabilityRange sampleSize, EchoCancellation echoCancellation, String&& deviceId, String&& groupId, DoubleCapabilityRange focusDistance, Vector&& whiteBalanceModes, DoubleCapabilityRange zoom, bool torch, BackgroundBlur backgroundBlur, bool powerEfficient, RealtimeMediaSourceSupportedConstraints&& supportedConstraints) : m_width(WTFMove(width)) , m_height(WTFMove(height)) , m_aspectRatio(WTFMove(aspectRatio)) @@ -109,6 +109,7 @@ class RealtimeMediaSourceCapabilities { , m_zoom(WTFMove(zoom)) , m_torch(torch) , m_backgroundBlur(backgroundBlur) + , m_powerEfficient(powerEfficient) , m_supportedConstraints(WTFMove(supportedConstraints)) { } @@ -185,10 +186,14 @@ class RealtimeMediaSourceCapabilities { BackgroundBlur backgroundBlur() const { return m_backgroundBlur; } void setBackgroundBlur(BackgroundBlur backgroundBlur) { m_backgroundBlur = backgroundBlur; } + bool supportsPowerEfficient() const { return m_supportedConstraints.supportsPowerEfficient(); } + bool powerEfficient() const { return m_powerEfficient; } + void setPowerEfficient(bool value) { m_powerEfficient = value; } + const RealtimeMediaSourceSupportedConstraints& supportedConstraints() const { return m_supportedConstraints; } void setSupportedConstraints(const RealtimeMediaSourceSupportedConstraints& constraints) { m_supportedConstraints = constraints; } - RealtimeMediaSourceCapabilities isolatedCopy() const { return { m_width, m_height, m_aspectRatio, m_frameRate, Vector { m_facingMode }, m_volume, m_sampleRate, m_sampleSize, m_echoCancellation, m_deviceId.isolatedCopy(), m_groupId.isolatedCopy(), m_focusDistance, Vector { m_whiteBalanceModes }, m_zoom, m_torch, m_backgroundBlur, RealtimeMediaSourceSupportedConstraints { m_supportedConstraints } }; } + RealtimeMediaSourceCapabilities isolatedCopy() const { return { m_width, m_height, m_aspectRatio, m_frameRate, Vector { m_facingMode }, m_volume, m_sampleRate, m_sampleSize, m_echoCancellation, m_deviceId.isolatedCopy(), m_groupId.isolatedCopy(), m_focusDistance, Vector { m_whiteBalanceModes }, m_zoom, m_torch, m_backgroundBlur, m_powerEfficient, RealtimeMediaSourceSupportedConstraints { m_supportedConstraints } }; } private: LongCapabilityRange m_width; @@ -209,6 +214,7 @@ class RealtimeMediaSourceCapabilities { bool m_torch { false }; BackgroundBlur m_backgroundBlur { BackgroundBlur::Off }; + bool m_powerEfficient { false }; RealtimeMediaSourceSupportedConstraints m_supportedConstraints; }; diff --git a/Source/WebCore/platform/mediastream/RealtimeMediaSourceSettings.cpp b/Source/WebCore/platform/mediastream/RealtimeMediaSourceSettings.cpp index dd865650305a9..6e0a2ce079b16 100644 --- a/Source/WebCore/platform/mediastream/RealtimeMediaSourceSettings.cpp +++ b/Source/WebCore/platform/mediastream/RealtimeMediaSourceSettings.cpp @@ -40,7 +40,7 @@ namespace WebCore { RealtimeMediaSourceSettings RealtimeMediaSourceSettings::isolatedCopy() const { - return { m_width, m_height , m_frameRate, m_facingMode, m_volume , m_sampleRate, m_sampleSize, m_echoCancellation, m_deviceId.isolatedCopy(), m_groupId.isolatedCopy(), m_label.isolatedCopy(), m_displaySurface, m_logicalSurface, m_whiteBalanceMode, m_zoom, m_torch, m_backgroundBlur, RealtimeMediaSourceSupportedConstraints { m_supportedConstraints } }; + return { m_width, m_height , m_frameRate, m_facingMode, m_volume , m_sampleRate, m_sampleSize, m_echoCancellation, m_deviceId.isolatedCopy(), m_groupId.isolatedCopy(), m_label.isolatedCopy(), m_displaySurface, m_logicalSurface, m_whiteBalanceMode, m_zoom, m_torch, m_backgroundBlur, m_powerEfficient, RealtimeMediaSourceSupportedConstraints { m_supportedConstraints } }; } VideoFacingMode RealtimeMediaSourceSettings::videoFacingModeEnum(const String& mode) @@ -118,6 +118,9 @@ String RealtimeMediaSourceSettings::convertFlagsToString(const OptionSet RealtimeMediaSourceSettings::differ difference.add(RealtimeMediaSourceSettings::Torch); if (backgroundBlur() != that.backgroundBlur()) difference.add(RealtimeMediaSourceSettings::BackgroundBlur); + if (powerEfficient() != that.powerEfficient()) + difference.add(RealtimeMediaSourceSettings::PowerEfficient); return difference; } diff --git a/Source/WebCore/platform/mediastream/RealtimeMediaSourceSettings.h b/Source/WebCore/platform/mediastream/RealtimeMediaSourceSettings.h index 4fce213b37ad6..9a3c19dcffaff 100644 --- a/Source/WebCore/platform/mediastream/RealtimeMediaSourceSettings.h +++ b/Source/WebCore/platform/mediastream/RealtimeMediaSourceSettings.h @@ -75,14 +75,15 @@ class RealtimeMediaSourceSettings { Zoom = 1 << 14, Torch = 1 << 15, BackgroundBlur = 1 << 16, + PowerEfficient = 1 << 17, }; - static constexpr OptionSet allFlags() { return { Width, Height, FrameRate, FacingMode, Volume, SampleRate, SampleSize, EchoCancellation, DeviceId, GroupId, Label, DisplaySurface, LogicalSurface, WhiteBalanceMode, Zoom, Torch, BackgroundBlur }; } + static constexpr OptionSet allFlags() { return { Width, Height, FrameRate, FacingMode, Volume, SampleRate, SampleSize, EchoCancellation, DeviceId, GroupId, Label, DisplaySurface, LogicalSurface, WhiteBalanceMode, Zoom, Torch, BackgroundBlur, PowerEfficient }; } WEBCORE_EXPORT OptionSet difference(const RealtimeMediaSourceSettings&) const; RealtimeMediaSourceSettings() = default; - RealtimeMediaSourceSettings(uint32_t width, uint32_t height, float frameRate, VideoFacingMode facingMode, double volume, uint32_t sampleRate, uint32_t sampleSize, bool echoCancellation, String&& deviceId, String&& groupId, String&& label, DisplaySurfaceType displaySurface, bool logicalSurface, MeteringMode whiteBalanceMode, double zoom, bool torch, bool backgroundBlur, RealtimeMediaSourceSupportedConstraints&& supportedConstraints) + RealtimeMediaSourceSettings(uint32_t width, uint32_t height, float frameRate, VideoFacingMode facingMode, double volume, uint32_t sampleRate, uint32_t sampleSize, bool echoCancellation, String&& deviceId, String&& groupId, String&& label, DisplaySurfaceType displaySurface, bool logicalSurface, MeteringMode whiteBalanceMode, double zoom, bool torch, bool backgroundBlur, bool powerEfficient, RealtimeMediaSourceSupportedConstraints&& supportedConstraints) : m_width(width) , m_height(height) , m_frameRate(frameRate) @@ -100,6 +101,7 @@ class RealtimeMediaSourceSettings { , m_zoom(zoom) , m_torch(torch) , m_backgroundBlur(backgroundBlur) + , m_powerEfficient(powerEfficient) , m_supportedConstraints(WTFMove(supportedConstraints)) { } @@ -170,6 +172,10 @@ class RealtimeMediaSourceSettings { bool backgroundBlur() const { return m_backgroundBlur; } void setBackgroundBlur(bool backgroundBlur) { m_backgroundBlur = backgroundBlur; } + bool supportsPowerEfficient() const { return m_supportedConstraints.supportsPowerEfficient(); } + bool powerEfficient() const { return m_powerEfficient; } + void setPowerEfficient(bool value) { m_powerEfficient = value; } + const RealtimeMediaSourceSupportedConstraints& supportedConstraints() const { return m_supportedConstraints; } void setSupportedConstraints(const RealtimeMediaSourceSupportedConstraints& supportedConstraints) { m_supportedConstraints = supportedConstraints; } @@ -201,6 +207,7 @@ class RealtimeMediaSourceSettings { double m_zoom { 1.0 }; bool m_torch { false }; bool m_backgroundBlur { false }; + bool m_powerEfficient { false }; RealtimeMediaSourceSupportedConstraints m_supportedConstraints; }; diff --git a/Source/WebCore/platform/mediastream/RealtimeMediaSourceSupportedConstraints.h b/Source/WebCore/platform/mediastream/RealtimeMediaSourceSupportedConstraints.h index 25911c7e31bab..fd9ccfb58c232 100644 --- a/Source/WebCore/platform/mediastream/RealtimeMediaSourceSupportedConstraints.h +++ b/Source/WebCore/platform/mediastream/RealtimeMediaSourceSupportedConstraints.h @@ -42,7 +42,7 @@ class RealtimeMediaSourceSupportedConstraints { { } - RealtimeMediaSourceSupportedConstraints(bool supportsWidth, bool supportsHeight, bool supportsAspectRatio, bool supportsFrameRate, bool supportsFacingMode, bool supportsVolume, bool supportsSampleRate, bool supportsSampleSize, bool supportsEchoCancellation, bool supportsDeviceId, bool supportsGroupId, bool supportsDisplaySurface, bool supportsLogicalSurface, bool supportsFocusDistance, bool supportsWhiteBalanceMode, bool supportsZoom, bool supportsTorch, bool supportsBackgroundBlur) + RealtimeMediaSourceSupportedConstraints(bool supportsWidth, bool supportsHeight, bool supportsAspectRatio, bool supportsFrameRate, bool supportsFacingMode, bool supportsVolume, bool supportsSampleRate, bool supportsSampleSize, bool supportsEchoCancellation, bool supportsDeviceId, bool supportsGroupId, bool supportsDisplaySurface, bool supportsLogicalSurface, bool supportsFocusDistance, bool supportsWhiteBalanceMode, bool supportsZoom, bool supportsTorch, bool supportsBackgroundBlur, bool supportsPowerEfficient) : m_supportsWidth(supportsWidth) , m_supportsHeight(supportsHeight) , m_supportsAspectRatio(supportsAspectRatio) @@ -61,6 +61,7 @@ class RealtimeMediaSourceSupportedConstraints { , m_supportsZoom(supportsZoom) , m_supportsTorch(supportsTorch) , m_supportsBackgroundBlur(supportsBackgroundBlur) + , m_supportsPowerEfficient(supportsPowerEfficient) { } @@ -120,6 +121,9 @@ class RealtimeMediaSourceSupportedConstraints { bool supportsBackgroundBlur() const { return m_supportsBackgroundBlur; } void setSupportsBackgroundBlur(bool value) { m_supportsBackgroundBlur = value; } + bool supportsPowerEfficient() const { return m_supportsPowerEfficient; } + void setSupportsPowerEfficient(bool value) { m_supportsPowerEfficient = value; } + private: bool m_supportsWidth { false }; bool m_supportsHeight { false }; @@ -139,6 +143,7 @@ class RealtimeMediaSourceSupportedConstraints { bool m_supportsZoom { false }; bool m_supportsTorch { false }; bool m_supportsBackgroundBlur { false }; + bool m_supportsPowerEfficient { false }; }; } // namespace WebCore diff --git a/Source/WebCore/platform/mediastream/RealtimeVideoCaptureSource.cpp b/Source/WebCore/platform/mediastream/RealtimeVideoCaptureSource.cpp index e965edafbd368..88ccf2b9fbb1e 100644 --- a/Source/WebCore/platform/mediastream/RealtimeVideoCaptureSource.cpp +++ b/Source/WebCore/platform/mediastream/RealtimeVideoCaptureSource.cpp @@ -594,6 +594,12 @@ String SizeFrameRateAndZoom::toJSONString() const } #endif +bool RealtimeVideoCaptureSource::canBePowerEfficient() +{ + return anyOf(presets(), [] (auto& preset) { return preset.isEfficient(); }) && anyOf(presets(), [] (auto& preset) { return !preset.isEfficient(); }); +} + + } // namespace WebCore #endif // ENABLE(MEDIA_STREAM) diff --git a/Source/WebCore/platform/mediastream/RealtimeVideoCaptureSource.h b/Source/WebCore/platform/mediastream/RealtimeVideoCaptureSource.h index 16bdd1365b2cb..10e7617f252ce 100644 --- a/Source/WebCore/platform/mediastream/RealtimeVideoCaptureSource.h +++ b/Source/WebCore/platform/mediastream/RealtimeVideoCaptureSource.h @@ -80,6 +80,8 @@ class WEBCORE_EXPORT RealtimeVideoCaptureSource : public RealtimeMediaSource, pu virtual Ref takePhotoInternal(PhotoSettings&&); bool mutedForPhotoCapture() const { return m_mutedForPhotoCapture; } + bool canBePowerEfficient(); + private: struct CaptureSizeFrameRateAndZoom { std::optional encodingPreset; diff --git a/Source/WebCore/platform/mediastream/gstreamer/GStreamerAudioCaptureSource.cpp b/Source/WebCore/platform/mediastream/gstreamer/GStreamerAudioCaptureSource.cpp index 77cc7aa99cd0e..8320b4215ca87 100644 --- a/Source/WebCore/platform/mediastream/gstreamer/GStreamerAudioCaptureSource.cpp +++ b/Source/WebCore/platform/mediastream/gstreamer/GStreamerAudioCaptureSource.cpp @@ -32,6 +32,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/mediastream/gstreamer/GStreamerCaptureDeviceManager.cpp b/Source/WebCore/platform/mediastream/gstreamer/GStreamerCaptureDeviceManager.cpp index 955d58ff0ca24..e0b2df8c9aa85 100644 --- a/Source/WebCore/platform/mediastream/gstreamer/GStreamerCaptureDeviceManager.cpp +++ b/Source/WebCore/platform/mediastream/gstreamer/GStreamerCaptureDeviceManager.cpp @@ -26,6 +26,7 @@ #include "GStreamerCommon.h" #include "GStreamerMockDeviceProvider.h" +#include namespace WebCore { diff --git a/Source/WebCore/platform/mediastream/gstreamer/GStreamerCapturer.cpp b/Source/WebCore/platform/mediastream/gstreamer/GStreamerCapturer.cpp index 349d4c3bc820b..4328d83189c42 100644 --- a/Source/WebCore/platform/mediastream/gstreamer/GStreamerCapturer.cpp +++ b/Source/WebCore/platform/mediastream/gstreamer/GStreamerCapturer.cpp @@ -33,6 +33,7 @@ #include #include #include +#include GST_DEBUG_CATEGORY(webkit_capturer_debug); #define GST_CAT_DEFAULT webkit_capturer_debug diff --git a/Source/WebCore/platform/mediastream/gstreamer/GStreamerDisplayCaptureDeviceManager.cpp b/Source/WebCore/platform/mediastream/gstreamer/GStreamerDisplayCaptureDeviceManager.cpp index a3f24d9d39486..b5c4354e5ed0e 100644 --- a/Source/WebCore/platform/mediastream/gstreamer/GStreamerDisplayCaptureDeviceManager.cpp +++ b/Source/WebCore/platform/mediastream/gstreamer/GStreamerDisplayCaptureDeviceManager.cpp @@ -28,6 +28,7 @@ #include "GStreamerVideoCaptureSource.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/mediastream/gstreamer/GStreamerIncomingTrackProcessor.cpp b/Source/WebCore/platform/mediastream/gstreamer/GStreamerIncomingTrackProcessor.cpp index 2f00a62e1df27..fc2bda02754a0 100644 --- a/Source/WebCore/platform/mediastream/gstreamer/GStreamerIncomingTrackProcessor.cpp +++ b/Source/WebCore/platform/mediastream/gstreamer/GStreamerIncomingTrackProcessor.cpp @@ -23,6 +23,7 @@ #include "GStreamerCommon.h" #include "GStreamerRegistryScanner.h" +#include GST_DEBUG_CATEGORY(webkit_webrtc_incoming_track_processor_debug); #define GST_CAT_DEFAULT webkit_webrtc_incoming_track_processor_debug diff --git a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp b/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp index 9575eb82cefaf..d30254d597d60 100644 --- a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp +++ b/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp @@ -44,6 +44,7 @@ #include #include #include +#include using namespace WebCore; diff --git a/Source/WebCore/platform/mediastream/gstreamer/GStreamerVideoCaptureSource.cpp b/Source/WebCore/platform/mediastream/gstreamer/GStreamerVideoCaptureSource.cpp index d6a078df0b252..58a3ed1908267 100644 --- a/Source/WebCore/platform/mediastream/gstreamer/GStreamerVideoCaptureSource.cpp +++ b/Source/WebCore/platform/mediastream/gstreamer/GStreamerVideoCaptureSource.cpp @@ -27,8 +27,8 @@ #include "DisplayCaptureManager.h" #include "GStreamerCaptureDeviceManager.h" - #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/mediastream/gstreamer/RealtimeIncomingAudioSourceGStreamer.cpp b/Source/WebCore/platform/mediastream/gstreamer/RealtimeIncomingAudioSourceGStreamer.cpp index ed27d81b9be26..ee07ae297e422 100644 --- a/Source/WebCore/platform/mediastream/gstreamer/RealtimeIncomingAudioSourceGStreamer.cpp +++ b/Source/WebCore/platform/mediastream/gstreamer/RealtimeIncomingAudioSourceGStreamer.cpp @@ -24,6 +24,7 @@ #include "GStreamerAudioData.h" #include "GStreamerAudioStreamDescription.h" +#include GST_DEBUG_CATEGORY(webkit_webrtc_incoming_audio_debug); #define GST_CAT_DEFAULT webkit_webrtc_incoming_audio_debug diff --git a/Source/WebCore/platform/mediastream/gstreamer/RealtimeIncomingSourceGStreamer.cpp b/Source/WebCore/platform/mediastream/gstreamer/RealtimeIncomingSourceGStreamer.cpp index 0633d6ea21bad..28216fe72f410 100644 --- a/Source/WebCore/platform/mediastream/gstreamer/RealtimeIncomingSourceGStreamer.cpp +++ b/Source/WebCore/platform/mediastream/gstreamer/RealtimeIncomingSourceGStreamer.cpp @@ -25,6 +25,7 @@ #include "GStreamerCommon.h" #include "NotImplemented.h" #include +#include #include GST_DEBUG_CATEGORY(webkit_webrtc_incoming_media_debug); diff --git a/Source/WebCore/platform/mediastream/gstreamer/RealtimeIncomingVideoSourceGStreamer.cpp b/Source/WebCore/platform/mediastream/gstreamer/RealtimeIncomingVideoSourceGStreamer.cpp index a4adc1488666b..d660fa9025a10 100644 --- a/Source/WebCore/platform/mediastream/gstreamer/RealtimeIncomingVideoSourceGStreamer.cpp +++ b/Source/WebCore/platform/mediastream/gstreamer/RealtimeIncomingVideoSourceGStreamer.cpp @@ -26,6 +26,7 @@ #include "GStreamerWebRTCUtils.h" #include "VideoFrameGStreamer.h" #include "VideoFrameMetadataGStreamer.h" +#include GST_DEBUG_CATEGORY(webkit_webrtc_incoming_video_debug); #define GST_CAT_DEFAULT webkit_webrtc_incoming_video_debug diff --git a/Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingAudioSourceGStreamer.cpp b/Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingAudioSourceGStreamer.cpp index 2d7ad47969512..f2568e6beb2ec 100644 --- a/Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingAudioSourceGStreamer.cpp +++ b/Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingAudioSourceGStreamer.cpp @@ -25,7 +25,7 @@ #include "GStreamerCommon.h" #include "GStreamerRegistryScanner.h" #include "MediaStreamTrack.h" - +#include #include GST_DEBUG_CATEGORY(webkit_webrtc_outgoing_audio_debug); @@ -61,18 +61,20 @@ bool RealtimeOutgoingAudioSourceGStreamer::setPayloadType(const GRefPtr // FIXME: We use only the first structure of the caps. This not be the right approach specially // we don't have a payloader or encoder for that format. GUniquePtr structure(gst_structure_copy(gst_caps_get_structure(caps.get(), 0))); - const char* encodingName = gst_structure_get_string(structure.get(), "encoding-name"); - if (!encodingName) { + auto encoding = StringView::fromLatin1(gst_structure_get_string(structure.get(), "encoding-name")).convertToASCIILowercase(); + if (encoding.isNull()) { GST_ERROR_OBJECT(m_bin.get(), "encoding-name not found"); return false; } - auto encoding = String(WTF::span(encodingName)).convertToASCIILowercase(); - m_payloader = makeGStreamerElement(makeString("rtp"_s, encoding, "pay"_s).ascii().data(), nullptr); - if (UNLIKELY(!m_payloader)) { - GST_ERROR_OBJECT(m_bin.get(), "RTP payloader not found for encoding %s", encodingName); + auto& registryScanner = GStreamerRegistryScanner::singleton(); + auto lookupResult = registryScanner.isRtpPacketizerSupported(encoding); + if (!lookupResult) { + GST_ERROR_OBJECT(m_bin.get(), "RTP payloader not found for encoding %s", encoding.ascii().data()); return false; } + m_payloader = gst_element_factory_create(lookupResult.factory.get(), nullptr); + GST_DEBUG_OBJECT(m_bin.get(), "Using %" GST_PTR_FORMAT " for %s RTP packetizing", m_payloader.get(), encoding.ascii().data()); m_inputCaps = adoptGRef(gst_caps_new_any()); @@ -112,12 +114,12 @@ bool RealtimeOutgoingAudioSourceGStreamer::setPayloadType(const GRefPtr else if (encoding == "pcmu"_s) m_encoder = makeGStreamerElement("mulawenc", nullptr); else { - GST_ERROR_OBJECT(m_bin.get(), "Unsupported outgoing audio encoding: %s", encodingName); + GST_ERROR_OBJECT(m_bin.get(), "Unsupported outgoing audio encoding: %s", encoding.ascii().data()); return false; } if (!m_encoder) { - GST_ERROR_OBJECT(m_bin.get(), "Encoder not found for encoding %s", encodingName); + GST_ERROR_OBJECT(m_bin.get(), "Encoder not found for encoding %s", encoding.ascii().data()); return false; } @@ -126,8 +128,12 @@ bool RealtimeOutgoingAudioSourceGStreamer::setPayloadType(const GRefPtr if (const char* minPTime = gst_structure_get_string(structure.get(), "minptime")) { auto time = String::fromLatin1(minPTime); - if (auto value = parseIntegerAllowingTrailingJunk(time)) - g_object_set(m_payloader.get(), "min-ptime", *value * GST_MSECOND, nullptr); + if (auto value = parseIntegerAllowingTrailingJunk(time)) { + if (gstObjectHasProperty(m_payloader.get(), "min-ptime")) + g_object_set(m_payloader.get(), "min-ptime", *value * GST_MSECOND, nullptr); + else + GST_WARNING_OBJECT(m_payloader.get(), "min-ptime property not supported"); + } gst_structure_remove_field(structure.get(), "minptime"); } diff --git a/Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingMediaSourceGStreamer.cpp b/Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingMediaSourceGStreamer.cpp index fdf60a728dec5..2d4a1e5d79dae 100644 --- a/Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingMediaSourceGStreamer.cpp +++ b/Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingMediaSourceGStreamer.cpp @@ -31,6 +31,7 @@ #undef GST_USE_UNSTABLE_API #include +#include GST_DEBUG_CATEGORY(webkit_webrtc_outgoing_media_debug); #define GST_CAT_DEFAULT webkit_webrtc_outgoing_media_debug diff --git a/Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingVideoSourceGStreamer.cpp b/Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingVideoSourceGStreamer.cpp index ae41d61308c95..e4b8d2f3d15cb 100644 --- a/Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingVideoSourceGStreamer.cpp +++ b/Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingVideoSourceGStreamer.cpp @@ -30,6 +30,7 @@ #include "VP9Utilities.h" #include "VideoEncoderPrivateGStreamer.h" #include +#include #include GST_DEBUG_CATEGORY(webkit_webrtc_outgoing_video_debug); @@ -111,18 +112,20 @@ bool RealtimeOutgoingVideoSourceGStreamer::setPayloadType(const GRefPtr // FIXME: We use only the first structure of the caps. This not be the right approach specially // we don't have a payloader or encoder for that format. GUniquePtr structure(gst_structure_copy(gst_caps_get_structure(caps.get(), 0))); - const char* encodingName = gst_structure_get_string(structure.get(), "encoding-name"); - if (!encodingName) { + auto encoding = StringView::fromLatin1(gst_structure_get_string(structure.get(), "encoding-name")).convertToASCIILowercase(); + if (encoding.isNull()) { GST_ERROR_OBJECT(m_bin.get(), "encoding-name not found"); return false; } - auto encoding = String(WTF::span(encodingName)).convertToASCIILowercase(); - m_payloader = makeGStreamerElement(makeString("rtp"_s, encoding, "pay"_s).ascii().data(), nullptr); - if (UNLIKELY(!m_payloader)) { - GST_ERROR_OBJECT(m_bin.get(), "RTP payloader not found for encoding %s", encodingName); + auto& registryScanner = GStreamerRegistryScanner::singleton(); + auto lookupResult = registryScanner.isRtpPacketizerSupported(encoding); + if (!lookupResult) { + GST_ERROR_OBJECT(m_bin.get(), "RTP payloader not found for encoding %s", encoding.ascii().data()); return false; } + m_payloader = gst_element_factory_create(lookupResult.factory.get(), nullptr); + GST_DEBUG_OBJECT(m_bin.get(), "Using %" GST_PTR_FORMAT " for %s RTP packetizing", m_payloader.get(), encoding.ascii().data()); auto codec = emptyString(); if (encoding == "vp8"_s) { @@ -166,7 +169,7 @@ bool RealtimeOutgoingVideoSourceGStreamer::setPayloadType(const GRefPtr } else if (encoding == "av1"_s) codec = createAV1CodecParametersString({ }); else { - GST_ERROR_OBJECT(m_bin.get(), "Unsupported outgoing video encoding: %s", encodingName); + GST_ERROR_OBJECT(m_bin.get(), "Unsupported outgoing video encoding: %s", encoding.ascii().data()); return false; } diff --git a/Source/WebCore/platform/mediastream/libwebrtc/LibWebRTCProvider.cpp b/Source/WebCore/platform/mediastream/libwebrtc/LibWebRTCProvider.cpp index 2e731267387e4..2081d256017d8 100644 --- a/Source/WebCore/platform/mediastream/libwebrtc/LibWebRTCProvider.cpp +++ b/Source/WebCore/platform/mediastream/libwebrtc/LibWebRTCProvider.cpp @@ -30,6 +30,7 @@ #include "ContentType.h" #include "LibWebRTCAudioModule.h" +#include "LibWebRTCLogSink.h" #include "LibWebRTCUtils.h" #include "Logging.h" #include "MediaCapabilitiesDecodingInfo.h" diff --git a/Source/WebCore/platform/mediastream/libwebrtc/gstreamer/GStreamerVideoEncoderFactory.cpp b/Source/WebCore/platform/mediastream/libwebrtc/gstreamer/GStreamerVideoEncoderFactory.cpp index 839ed6fb6b730..992f0cbeec2c7 100644 --- a/Source/WebCore/platform/mediastream/libwebrtc/gstreamer/GStreamerVideoEncoderFactory.cpp +++ b/Source/WebCore/platform/mediastream/libwebrtc/gstreamer/GStreamerVideoEncoderFactory.cpp @@ -47,7 +47,7 @@ #include #include #include -#include +#include GST_DEBUG_CATEGORY(webkit_webrtcenc_debug); #define GST_CAT_DEFAULT webkit_webrtcenc_debug diff --git a/Source/WebCore/platform/mediastream/mac/AVVideoCaptureSource.mm b/Source/WebCore/platform/mediastream/mac/AVVideoCaptureSource.mm index 6842c9d7ae7bb..b56c85c835767 100644 --- a/Source/WebCore/platform/mediastream/mac/AVVideoCaptureSource.mm +++ b/Source/WebCore/platform/mediastream/mac/AVVideoCaptureSource.mm @@ -486,6 +486,11 @@ static bool isZoomSupported(const Vector& presets) settings.setTorch([device() torchMode] == AVCaptureTorchModeOn); } +#if PLATFORM(IOS_FAMILY) + supportedConstraints.setSupportsPowerEfficient(true); + if (canBePowerEfficient()) + settings.setPowerEfficient(m_currentPreset ? m_currentPreset->isEfficient() : false); +#endif settings.setSupportedConstraints(supportedConstraints); m_currentSettings = WTFMove(settings); @@ -530,7 +535,11 @@ static bool isZoomSupported(const Vector& presets) } capabilities.setBackgroundBlur(device().portraitEffectActive ? RealtimeMediaSourceCapabilities::BackgroundBlur::On : RealtimeMediaSourceCapabilities::BackgroundBlur::Off); - supportedConstraints.setSupportsBackgroundBlur(true); + +#if PLATFORM(IOS_FAMILY) + supportedConstraints.setSupportsPowerEfficient(true); + capabilities.setPowerEfficient(canBePowerEfficient()); +#endif capabilities.setSupportedConstraints(supportedConstraints); updateCapabilities(capabilities); diff --git a/Source/WebCore/platform/mediastream/mac/CGDisplayStreamScreenCaptureSource.mm b/Source/WebCore/platform/mediastream/mac/CGDisplayStreamScreenCaptureSource.mm index d3a01206e94ad..5a80949d96c83 100644 --- a/Source/WebCore/platform/mediastream/mac/CGDisplayStreamScreenCaptureSource.mm +++ b/Source/WebCore/platform/mediastream/mac/CGDisplayStreamScreenCaptureSource.mm @@ -39,6 +39,7 @@ #import "RealtimeMediaSourceSettings.h" #import "RealtimeVideoUtilities.h" #import +#import #import #import "CoreVideoSoftLink.h" diff --git a/Source/WebCore/platform/mock/MockRealtimeVideoSource.cpp b/Source/WebCore/platform/mock/MockRealtimeVideoSource.cpp index fcb4232ac514d..fcad22e964e5d 100644 --- a/Source/WebCore/platform/mock/MockRealtimeVideoSource.cpp +++ b/Source/WebCore/platform/mock/MockRealtimeVideoSource.cpp @@ -47,16 +47,12 @@ #include #include #include -#include +#include #if ENABLE(EXTENSION_CAPABILITIES) #include #endif -#if USE(GLIB_EVENT_LOOP) -#include -#endif - namespace WebCore { #if !PLATFORM(MAC) && !PLATFORM(IOS_FAMILY) && !USE(GSTREAMER) @@ -85,12 +81,6 @@ static ThreadSafeWeakHashSet& allMockRealtimeVideoSourc return videoSources; } -static RunLoop& takePhotoRunLoop() -{ - static NeverDestroyed> runLoop = RunLoop::create("WebKit::MockRealtimeVideoSource takePhoto runloop"_s); - return runLoop.get(); -} - FontCascadeDescription& MockRealtimeVideoSource::DrawingState::fontDescription() { if (!m_fontDescription) { @@ -147,14 +137,10 @@ const FontCascade& MockRealtimeVideoSource::DrawingState::statsFont() MockRealtimeVideoSource::MockRealtimeVideoSource(String&& deviceID, AtomString&& name, MediaDeviceHashSalts&& hashSalts, PageIdentifier pageIdentifier) : RealtimeVideoCaptureSource(CaptureDevice { WTFMove(deviceID), CaptureDevice::DeviceType::Camera, WTFMove(name) }, WTFMove(hashSalts), pageIdentifier) - , m_emitFrameTimer(RunLoop::current(), this, &MockRealtimeVideoSource::generateFrame) + , m_runLoop(RunLoop::create("WebKit::MockRealtimeVideoSource generateFrame runloop"_s)) + , m_emitFrameTimer(m_runLoop, [protectedThis = Ref { *this }] { protectedThis->generateFrame(); }) , m_deviceOrientation { VideoFrameRotation::None } { -#if USE(GLIB_EVENT_LOOP) - // Make sure run loop dispatcher sources are higher priority. - m_emitFrameTimer.setPriority(RunLoopSourcePriority::RunLoopDispatcher + 1); - m_emitFrameTimer.setName("[MockRealtimeVideoSource] Generate frame"_s); -#endif allMockRealtimeVideoSource().add(*this); @@ -244,6 +230,9 @@ const RealtimeMediaSourceCapabilities& MockRealtimeVideoSource::capabilities() capabilities.setBackgroundBlur(std::get(m_device.properties).hasBackgroundBlur ? RealtimeMediaSourceCapabilities::BackgroundBlur::On : RealtimeMediaSourceCapabilities::BackgroundBlur::Off); supportedConstraints.setSupportsBackgroundBlur(true); + capabilities.setPowerEfficient(canBePowerEfficient()); + supportedConstraints.setSupportsPowerEfficient(true); + capabilities.setSupportedConstraints(supportedConstraints); } else if (mockDisplay()) { capabilities.setWidth({ 72, std::get(m_device.properties).defaultSize.width() }); @@ -267,7 +256,7 @@ auto MockRealtimeVideoSource::takePhotoInternal(PhotoSettings&&) -> RefisEfficient() : false); + supportedConstraints.setSupportsPowerEfficient(true); + supportedConstraints.setSupportsBackgroundBlur(true); settings.setBackgroundBlur(std::get(m_device.properties).hasBackgroundBlur); } else { @@ -664,6 +657,8 @@ RefPtr MockRealtimeVideoSource::generateFrameInternal() void MockRealtimeVideoSource::generateFrame() { + ASSERT(!isMainThread()); + if (m_delayUntil) { if (m_delayUntil < MonotonicTime::now()) return; diff --git a/Source/WebCore/platform/mock/MockRealtimeVideoSource.h b/Source/WebCore/platform/mock/MockRealtimeVideoSource.h index e581b00ba17f7..ab01535161a9b 100644 --- a/Source/WebCore/platform/mock/MockRealtimeVideoSource.h +++ b/Source/WebCore/platform/mock/MockRealtimeVideoSource.h @@ -157,6 +157,7 @@ class MockRealtimeVideoSource : public RealtimeVideoCaptureSource, private Orien MonotonicTime m_delayUntil; unsigned m_frameNumber { 0 }; + Ref m_runLoop; RunLoop::Timer m_emitFrameTimer; std::optional m_capabilities; std::optional m_currentSettings; diff --git a/Source/WebCore/platform/mock/ScrollbarsControllerMock.cpp b/Source/WebCore/platform/mock/ScrollbarsControllerMock.cpp index 676f81cfaacf3..23b9ba4e53cd4 100644 --- a/Source/WebCore/platform/mock/ScrollbarsControllerMock.cpp +++ b/Source/WebCore/platform/mock/ScrollbarsControllerMock.cpp @@ -33,6 +33,7 @@ #include "ScrollbarsControllerMock.h" #include "ScrollableArea.h" +#include namespace WebCore { diff --git a/Source/WebCore/platform/network/BlobRegistryImpl.cpp b/Source/WebCore/platform/network/BlobRegistryImpl.cpp index 58e0c7c116428..40d0755622f4b 100644 --- a/Source/WebCore/platform/network/BlobRegistryImpl.cpp +++ b/Source/WebCore/platform/network/BlobRegistryImpl.cpp @@ -50,6 +50,7 @@ #include #include #include +#include namespace WebCore { @@ -325,8 +326,8 @@ unsigned long long BlobRegistryImpl::blobSize(const URL& url) static WorkQueue& blobUtilityQueue() { - static auto& queue = WorkQueue::create("org.webkit.BlobUtility"_s, WorkQueue::QOS::Utility).leakRef(); - return queue; + static NeverDestroyed> queue(WorkQueue::create("org.webkit.BlobUtility"_s, WorkQueue::QOS::Utility)); + return queue.get(); } bool BlobRegistryImpl::populateBlobsForFileWriting(const Vector& blobURLs, Vector& blobsForWriting) diff --git a/Source/WebCore/platform/network/CredentialBase.cpp b/Source/WebCore/platform/network/CredentialBase.cpp index 8a21d6f36758e..542c080f396f0 100644 --- a/Source/WebCore/platform/network/CredentialBase.cpp +++ b/Source/WebCore/platform/network/CredentialBase.cpp @@ -28,6 +28,7 @@ #include "Credential.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/network/CredentialStorage.cpp b/Source/WebCore/platform/network/CredentialStorage.cpp index 7e9a766463746..dab0afbca3fd2 100644 --- a/Source/WebCore/platform/network/CredentialStorage.cpp +++ b/Source/WebCore/platform/network/CredentialStorage.cpp @@ -28,6 +28,7 @@ #include "NetworkStorageSession.h" #include +#include #if PLATFORM(IOS_FAMILY) #include "WebCoreThread.h" diff --git a/Source/WebCore/platform/network/DataURLDecoder.cpp b/Source/WebCore/platform/network/DataURLDecoder.cpp index 22322da4d8fad..0b1bdb6a10469 100644 --- a/Source/WebCore/platform/network/DataURLDecoder.cpp +++ b/Source/WebCore/platform/network/DataURLDecoder.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #if PLATFORM(COCOA) #include @@ -62,8 +63,8 @@ static bool shouldRemoveFragmentIdentifier(const String& mediaType) static WorkQueue& decodeQueue() { - static auto& queue = WorkQueue::create("org.webkit.DataURLDecoder"_s, WorkQueue::QOS::UserInitiated).leakRef(); - return queue; + static NeverDestroyed> queue(WorkQueue::create("org.webkit.DataURLDecoder"_s, WorkQueue::QOS::UserInitiated)); + return queue.get(); } static Result parseMediaType(const String& mediaType) diff --git a/Source/WebCore/platform/network/HTTPHeaderMap.cpp b/Source/WebCore/platform/network/HTTPHeaderMap.cpp index 80b35d678bb9f..65679251a5c66 100644 --- a/Source/WebCore/platform/network/HTTPHeaderMap.cpp +++ b/Source/WebCore/platform/network/HTTPHeaderMap.cpp @@ -34,6 +34,7 @@ #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/platform/network/HTTPParsers.cpp b/Source/WebCore/platform/network/HTTPParsers.cpp index 7eb4b74c9a0ee..a9b0312f468b9 100644 --- a/Source/WebCore/platform/network/HTTPParsers.cpp +++ b/Source/WebCore/platform/network/HTTPParsers.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include diff --git a/Source/WebCore/platform/network/MIMEHeader.cpp b/Source/WebCore/platform/network/MIMEHeader.cpp index 6e88101c6c85b..971c277da8db0 100644 --- a/Source/WebCore/platform/network/MIMEHeader.cpp +++ b/Source/WebCore/platform/network/MIMEHeader.cpp @@ -37,8 +37,8 @@ #include "SharedBufferChunkReader.h" #include #include +#include #include -#include #include namespace WebCore { diff --git a/Source/WebCore/platform/network/ParsedContentRange.cpp b/Source/WebCore/platform/network/ParsedContentRange.cpp index c7ed878b75408..27772ad8dcb7e 100644 --- a/Source/WebCore/platform/network/ParsedContentRange.cpp +++ b/Source/WebCore/platform/network/ParsedContentRange.cpp @@ -27,7 +27,7 @@ #include "ParsedContentRange.h" #include -#include +#include #include namespace WebCore { diff --git a/Source/WebCore/platform/network/ResourceHandle.cpp b/Source/WebCore/platform/network/ResourceHandle.cpp index c0cf012c7dc65..49ce9f0ef17c1 100644 --- a/Source/WebCore/platform/network/ResourceHandle.cpp +++ b/Source/WebCore/platform/network/ResourceHandle.cpp @@ -41,6 +41,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/network/ResourceResponseBase.cpp b/Source/WebCore/platform/network/ResourceResponseBase.cpp index e1bf41ea3b643..97e5f6f38b9c0 100644 --- a/Source/WebCore/platform/network/ResourceResponseBase.cpp +++ b/Source/WebCore/platform/network/ResourceResponseBase.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/platform/network/cf/DNSResolveQueueCFNet.cpp b/Source/WebCore/platform/network/cf/DNSResolveQueueCFNet.cpp index 84599f47e6c91..db357a208df47 100644 --- a/Source/WebCore/platform/network/cf/DNSResolveQueueCFNet.cpp +++ b/Source/WebCore/platform/network/cf/DNSResolveQueueCFNet.cpp @@ -110,18 +110,24 @@ static std::optional extractIPAddress(const struct sockaddr* address) void DNSResolveQueueCFNet::performDNSLookup(const String& hostname, Ref&& completionHandler) { - auto hostEndpoint = adoptCF(nw_endpoint_create_host(hostname.utf8().data(), "0")); - auto context = adoptCF(nw_context_create("WebKit DNS Lookup")); - auto parameters = adoptCF(nw_parameters_create()); + RetainPtr hostEndpoint = adoptCF(nw_endpoint_create_host(hostname.utf8().data(), "0")); + RetainPtr context = adoptCF(nw_context_create("WebKit DNS Lookup")); + RetainPtr parameters = adoptCF(nw_parameters_create()); nw_context_set_privacy_level(context.get(), nw_context_privacy_level_silent); nw_parameters_set_context(parameters.get(), context.get()); - auto resolver = adoptCF(nw_resolver_create_with_endpoint(hostEndpoint.get(), parameters.get())); + RetainPtr resolver = adoptCF(nw_resolver_create_with_endpoint(hostEndpoint.get(), parameters.get())); - nw_resolver_set_update_handler(resolver.get(), dispatch_get_main_queue(), makeBlockPtr([completionHandler = WTFMove(completionHandler)] (nw_resolver_status_t status, nw_array_t resolvedEndpoints) mutable { + nw_resolver_set_update_handler(resolver.get(), dispatch_get_main_queue(), makeBlockPtr([resolver, completionHandler = WTFMove(completionHandler)] (nw_resolver_status_t status, nw_array_t resolvedEndpoints) mutable { if (status == nw_resolver_status_in_progress) return; + auto callCompletionHandler = [resolver = WTFMove(resolver), completionHandler = WTFMove(completionHandler)](DNSAddressesOrError&& result) mutable { + completionHandler->complete(WTFMove(result)); + // We need to call nw_resolver_cancel to release the reference taken by nw_resolver_set_update_handler on the resolver. + nw_resolver_cancel(resolver.get()); + }; + if (!resolvedEndpoints) - return completionHandler->complete(makeUnexpected(DNSError::CannotResolve)); + return callCompletionHandler(makeUnexpected(DNSError::CannotResolve)); size_t count = nw_array_get_count(resolvedEndpoints); Vector result; @@ -134,9 +140,9 @@ void DNSResolveQueueCFNet::performDNSLookup(const String& hostname, Refcomplete(makeUnexpected(DNSError::CannotResolve)); + callCompletionHandler(makeUnexpected(DNSError::CannotResolve)); else - completionHandler->complete(WTFMove(result)); + callCompletionHandler(WTFMove(result)); }).get()); } diff --git a/Source/WebCore/platform/network/cf/NetworkStorageSessionCFNet.cpp b/Source/WebCore/platform/network/cf/NetworkStorageSessionCFNet.cpp index becc15194e7b6..02474d172504c 100644 --- a/Source/WebCore/platform/network/cf/NetworkStorageSessionCFNet.cpp +++ b/Source/WebCore/platform/network/cf/NetworkStorageSessionCFNet.cpp @@ -32,7 +32,6 @@ #include #include #include -#include namespace WebCore { diff --git a/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm b/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm index 55ebb19af79c9..eeea738bc83ef 100644 --- a/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm +++ b/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm @@ -42,6 +42,7 @@ #import #import #import +#import #import #import diff --git a/Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm b/Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm index e65547d3813a7..0627c4503c12b 100644 --- a/Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm +++ b/Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm @@ -36,6 +36,7 @@ #import #import #import +#import #import namespace WebCore { diff --git a/Source/WebCore/platform/network/curl/CookieJarDB.cpp b/Source/WebCore/platform/network/curl/CookieJarDB.cpp index a25ca7b35676b..1b1a87207751c 100644 --- a/Source/WebCore/platform/network/curl/CookieJarDB.cpp +++ b/Source/WebCore/platform/network/curl/CookieJarDB.cpp @@ -37,7 +37,7 @@ #include #include #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/platform/network/curl/CookieUtil.cpp b/Source/WebCore/platform/network/curl/CookieUtil.cpp index 52a3cf3f59c47..4a4fc1f086cf5 100644 --- a/Source/WebCore/platform/network/curl/CookieUtil.cpp +++ b/Source/WebCore/platform/network/curl/CookieUtil.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include diff --git a/Source/WebCore/platform/network/curl/CurlContext.cpp b/Source/WebCore/platform/network/curl/CurlContext.cpp index b5842d07b9f36..f0104345c9708 100644 --- a/Source/WebCore/platform/network/curl/CurlContext.cpp +++ b/Source/WebCore/platform/network/curl/CurlContext.cpp @@ -40,7 +40,7 @@ #include #include #include -#include +#include #if OS(WINDOWS) #include "WebCoreBundleWin.h" diff --git a/Source/WebCore/platform/network/curl/CurlMultipartHandle.cpp b/Source/WebCore/platform/network/curl/CurlMultipartHandle.cpp index aa609010e4c0f..b19f41a1d5cd8 100644 --- a/Source/WebCore/platform/network/curl/CurlMultipartHandle.cpp +++ b/Source/WebCore/platform/network/curl/CurlMultipartHandle.cpp @@ -36,6 +36,7 @@ #include "ParsedContentType.h" #include "SharedBuffer.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/network/curl/CurlProxySettings.cpp b/Source/WebCore/platform/network/curl/CurlProxySettings.cpp index 88701a750a022..ebfaeb6c45035 100644 --- a/Source/WebCore/platform/network/curl/CurlProxySettings.cpp +++ b/Source/WebCore/platform/network/curl/CurlProxySettings.cpp @@ -33,7 +33,7 @@ #endif #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/platform/network/curl/CurlRequest.cpp b/Source/WebCore/platform/network/curl/CurlRequest.cpp index 1abe48e491e41..615698a3aaa6a 100644 --- a/Source/WebCore/platform/network/curl/CurlRequest.cpp +++ b/Source/WebCore/platform/network/curl/CurlRequest.cpp @@ -39,6 +39,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/network/curl/NetworkStorageSessionCurl.cpp b/Source/WebCore/platform/network/curl/NetworkStorageSessionCurl.cpp index 798c95d09029d..7567442a6bdcc 100644 --- a/Source/WebCore/platform/network/curl/NetworkStorageSessionCurl.cpp +++ b/Source/WebCore/platform/network/curl/NetworkStorageSessionCurl.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/platform/network/curl/OpenSSLHelper.cpp b/Source/WebCore/platform/network/curl/OpenSSLHelper.cpp index bd9603d578689..c9eb331084dd2 100644 --- a/Source/WebCore/platform/network/curl/OpenSSLHelper.cpp +++ b/Source/WebCore/platform/network/curl/OpenSSLHelper.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include namespace OpenSSL { diff --git a/Source/WebCore/platform/network/ios/WebCoreURLResponseIOS.mm b/Source/WebCore/platform/network/ios/WebCoreURLResponseIOS.mm index c4db0fb5c3d62..8822e84e8a7ce 100644 --- a/Source/WebCore/platform/network/ios/WebCoreURLResponseIOS.mm +++ b/Source/WebCore/platform/network/ios/WebCoreURLResponseIOS.mm @@ -43,7 +43,7 @@ static inline bool shouldPreferTextPlainMIMEType(const String& mimeType, const S return ("text/plain"_s == mimeType) && ((proposedMIMEType == "text/xml"_s) || (proposedMIMEType == "application/xml"_s) || (proposedMIMEType == "image/svg+xml"_s)); } -void adjustMIMETypeIfNecessary(CFURLResponseRef response, IsMainResourceLoad isMainResourceLoad, IsNoSniffSet isNoSniffSet) +void adjustMIMETypeIfNecessary(CFURLResponseRef response, bool isMainResourceLoad) { auto type = CFURLResponseGetMIMEType(response); if (!type) { @@ -58,11 +58,10 @@ void adjustMIMETypeIfNecessary(CFURLResponseRef response, IsMainResourceLoad isM #if !USE(QUICK_LOOK) UNUSED_PARAM(isMainResourceLoad); - UNUSED_PARAM(isNoSniffSet); #else // Ensure that the MIME type is correct so that QuickLook's web plug-in is called when needed. // The shouldUseQuickLookForMIMEType function filters out the common MIME types so we don't do unnecessary work in those cases. - if (isMainResourceLoad == IsMainResourceLoad::Yes && isNoSniffSet == IsNoSniffSet::No && shouldUseQuickLookForMIMEType((__bridge NSString *)type)) { + if (isMainResourceLoad && shouldUseQuickLookForMIMEType((__bridge NSString *)type)) { RetainPtr updatedType; auto suggestedFilename = adoptCF(CFURLResponseCopySuggestedFilename(response)); if (auto quickLookType = adoptNS(PAL::softLink_QuickLook_QLTypeCopyBestMimeTypeForFileNameAndMimeType((__bridge NSString *)suggestedFilename.get(), (__bridge NSString *)type))) diff --git a/Source/WebCore/platform/network/mac/WebCoreResourceHandleAsOperationQueueDelegate.mm b/Source/WebCore/platform/network/mac/WebCoreResourceHandleAsOperationQueueDelegate.mm index c6d782c55673f..334735d05eb0d 100644 --- a/Source/WebCore/platform/network/mac/WebCoreResourceHandleAsOperationQueueDelegate.mm +++ b/Source/WebCore/platform/network/mac/WebCoreResourceHandleAsOperationQueueDelegate.mm @@ -256,7 +256,7 @@ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLRespon int statusCode = [r respondsToSelector:@selector(statusCode)] ? [(id)r statusCode] : 0; if (statusCode != 304) { bool isMainResourceLoad = m_handle->firstRequest().requester() == ResourceRequestRequester::Main; - adjustMIMETypeIfNecessary([r _CFURLResponse], isMainResourceLoad ? IsMainResourceLoad::Yes : IsMainResourceLoad::No, IsNoSniffSet::No); + adjustMIMETypeIfNecessary([r _CFURLResponse], isMainResourceLoad); } if ([m_handle->firstRequest().nsURLRequest(HTTPBodyUpdatePolicy::DoNotUpdateHTTPBody) _propertyForKey:@"ForceHTMLMIMEType"]) diff --git a/Source/WebCore/platform/network/mac/WebCoreURLResponse.h b/Source/WebCore/platform/network/mac/WebCoreURLResponse.h index f1adee874ccb8..d2b4d5e252f44 100644 --- a/Source/WebCore/platform/network/mac/WebCoreURLResponse.h +++ b/Source/WebCore/platform/network/mac/WebCoreURLResponse.h @@ -36,10 +36,7 @@ namespace WebCore { WEBCORE_EXPORT NSURLResponse *synthesizeRedirectResponseIfNecessary(NSURLRequest *currentRequest, NSURLRequest *newRequest, NSURLResponse *redirectResponse); #endif -enum class IsMainResourceLoad : bool { No, Yes }; -enum class IsNoSniffSet : bool { No, Yes }; - -WEBCORE_EXPORT void adjustMIMETypeIfNecessary(CFURLResponseRef, IsMainResourceLoad, IsNoSniffSet); +WEBCORE_EXPORT void adjustMIMETypeIfNecessary(CFURLResponseRef, bool isMainResourceLoad); RetainPtr filePathExtension(CFURLResponseRef); RetainPtr preferredMIMETypeForFileExtensionFromUTType(CFStringRef extension); diff --git a/Source/WebCore/platform/network/mac/WebCoreURLResponse.mm b/Source/WebCore/platform/network/mac/WebCoreURLResponse.mm index d538d7ebc9104..ca650a54e43e8 100644 --- a/Source/WebCore/platform/network/mac/WebCoreURLResponse.mm +++ b/Source/WebCore/platform/network/mac/WebCoreURLResponse.mm @@ -41,14 +41,14 @@ #if PLATFORM(MAC) -void adjustMIMETypeIfNecessary(CFURLResponseRef response, IsMainResourceLoad, IsNoSniffSet isNoSniffSet) +void adjustMIMETypeIfNecessary(CFURLResponseRef response, bool /*isMainResourceLoad*/) { if (CFURLResponseGetMIMEType(response)) return; RetainPtr type; - if (auto extension = filePathExtension(response); extension && isNoSniffSet == IsNoSniffSet::No) { + if (auto extension = filePathExtension(response)) { // CoreTypes UTI map is missing 100+ file extensions that GateKeeper knew about // Once UTType matches one of these mappings on all versions of macOS we support, we can remove that pair. // Alternatively, we could remove any pairs that we determine we no longer need. diff --git a/Source/WebCore/platform/network/soup/AuthenticationChallengeSoup.cpp b/Source/WebCore/platform/network/soup/AuthenticationChallengeSoup.cpp index f29a8c430e14b..9fc226996e485 100644 --- a/Source/WebCore/platform/network/soup/AuthenticationChallengeSoup.cpp +++ b/Source/WebCore/platform/network/soup/AuthenticationChallengeSoup.cpp @@ -32,6 +32,7 @@ #include "ResourceError.h" #include "URLSoup.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/playstation/UserAgentPlayStation.cpp b/Source/WebCore/platform/playstation/UserAgentPlayStation.cpp index 219c4dd2f05f5..74f133472cefe 100644 --- a/Source/WebCore/platform/playstation/UserAgentPlayStation.cpp +++ b/Source/WebCore/platform/playstation/UserAgentPlayStation.cpp @@ -27,7 +27,7 @@ #include "UserAgent.h" #include -#include +#include // WARNING! WARNING! WARNING! // diff --git a/Source/WebCore/platform/sql/SQLiteDatabase.cpp b/Source/WebCore/platform/sql/SQLiteDatabase.cpp index 14a6a49d670cf..3f2ccb01f7998 100644 --- a/Source/WebCore/platform/sql/SQLiteDatabase.cpp +++ b/Source/WebCore/platform/sql/SQLiteDatabase.cpp @@ -42,7 +42,7 @@ #include #include #include -#include +#include #if !USE(SYSTEM_MALLOC) #include diff --git a/Source/WebCore/platform/sql/SQLiteFileSystem.cpp b/Source/WebCore/platform/sql/SQLiteFileSystem.cpp index 083e57c785c6b..2b68cddc6cbbe 100644 --- a/Source/WebCore/platform/sql/SQLiteFileSystem.cpp +++ b/Source/WebCore/platform/sql/SQLiteFileSystem.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #if PLATFORM(COCOA) #include diff --git a/Source/WebCore/platform/text/WritingMode.h b/Source/WebCore/platform/text/WritingMode.h index c05619284e666..ad445b503ae98 100644 --- a/Source/WebCore/platform/text/WritingMode.h +++ b/Source/WebCore/platform/text/WritingMode.h @@ -104,16 +104,27 @@ struct TextFlow { } }; +constexpr inline TextFlow makeTextFlow(WritingMode writingMode, TextDirection direction) +{ + auto textDirection = direction; + + // FIXME: Remove this erronous logic and remove `makeTextFlow` helper (webkit.org/b/276028). + if (writingMode == WritingMode::SidewaysLr) + textDirection = direction == TextDirection::RTL ? TextDirection::LTR : TextDirection::RTL; + + return { writingModeToBlockFlowDirection(writingMode), textDirection }; +} + // Lines have vertical orientation; modes vertical-lr or vertical-rl. constexpr inline bool isVerticalWritingMode(WritingMode writingMode) { - return TextFlow { writingModeToBlockFlowDirection(writingMode), TextDirection::LTR }.isVertical(); + return makeTextFlow(writingMode, TextDirection::LTR).isVertical(); } // Block progression increases in the opposite direction to normal; modes vertical-rl or horizontal-bt. constexpr inline bool isFlippedWritingMode(WritingMode writingMode) { - return TextFlow { writingModeToBlockFlowDirection(writingMode), TextDirection::LTR }.isFlipped(); + return makeTextFlow(writingMode, TextDirection::LTR).isFlipped(); } // Lines have horizontal orientation; modes horizontal-tb or horizontal-bt. @@ -125,7 +136,7 @@ constexpr inline bool isHorizontalWritingMode(WritingMode writingMode) // Bottom of the line occurs earlier in the block; modes vertical-lr or horizontal-bt. constexpr inline bool isFlippedLinesWritingMode(WritingMode writingMode) { - return TextFlow { writingModeToBlockFlowDirection(writingMode), TextDirection::LTR }.isFlippedLines(); + return makeTextFlow(writingMode, TextDirection::LTR).isFlippedLines(); } enum class LogicalBoxSide : uint8_t { @@ -186,7 +197,7 @@ constexpr BoxSide mapLogicalSideToPhysicalSide(WritingMode writingMode, LogicalB { // Set the direction such that side is mirrored if isFlippedWritingMode() is true auto direction = isFlippedWritingMode(writingMode) ? TextDirection::RTL : TextDirection::LTR; - return mapLogicalSideToPhysicalSide({ writingModeToBlockFlowDirection(writingMode), direction }, logicalSide); + return mapLogicalSideToPhysicalSide(makeTextFlow(writingMode, direction), logicalSide); } constexpr LogicalBoxSide mapPhysicalSideToLogicalSide(TextFlow flow, BoxSide side) diff --git a/Source/WebCore/platform/text/win/LocaleWin.cpp b/Source/WebCore/platform/text/win/LocaleWin.cpp index ea84684f5f2e1..1ad346d4479bd 100644 --- a/Source/WebCore/platform/text/win/LocaleWin.cpp +++ b/Source/WebCore/platform/text/win/LocaleWin.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include diff --git a/Source/WebCore/platform/unix/LoggingUnix.cpp b/Source/WebCore/platform/unix/LoggingUnix.cpp index 6a8388170ad36..92034f605196e 100644 --- a/Source/WebCore/platform/unix/LoggingUnix.cpp +++ b/Source/WebCore/platform/unix/LoggingUnix.cpp @@ -25,6 +25,7 @@ #if !LOG_DISABLED || !RELEASE_LOG_DISABLED #include +#include #include namespace WebCore { diff --git a/Source/WebCore/platform/unix/SharedMemoryUnix.cpp b/Source/WebCore/platform/unix/SharedMemoryUnix.cpp index a713f95101140..e4a63f02f3da4 100644 --- a/Source/WebCore/platform/unix/SharedMemoryUnix.cpp +++ b/Source/WebCore/platform/unix/SharedMemoryUnix.cpp @@ -42,7 +42,7 @@ #include #include #include -#include +#include #include #if HAVE(LINUX_MEMFD_H) diff --git a/Source/WebCore/platform/win/ClipboardUtilitiesWin.cpp b/Source/WebCore/platform/win/ClipboardUtilitiesWin.cpp index 602348fdd5c5d..71c773f4532b6 100644 --- a/Source/WebCore/platform/win/ClipboardUtilitiesWin.cpp +++ b/Source/WebCore/platform/win/ClipboardUtilitiesWin.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include diff --git a/Source/WebCore/platform/win/KeyEventWin.cpp b/Source/WebCore/platform/win/KeyEventWin.cpp index d948c806e68a0..d450bf9d0fd1f 100644 --- a/Source/WebCore/platform/win/KeyEventWin.cpp +++ b/Source/WebCore/platform/win/KeyEventWin.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #ifndef MAPVK_VSC_TO_VK_EX #define MAPVK_VSC_TO_VK_EX 3 diff --git a/Source/WebCore/platform/win/MIMETypeRegistryWin.cpp b/Source/WebCore/platform/win/MIMETypeRegistryWin.cpp index ad709ef3e2060..6e8ed108c0b57 100644 --- a/Source/WebCore/platform/win/MIMETypeRegistryWin.cpp +++ b/Source/WebCore/platform/win/MIMETypeRegistryWin.cpp @@ -30,6 +30,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/win/PasteboardWin.cpp b/Source/WebCore/platform/win/PasteboardWin.cpp index 6ead5f4dea594..03e5dc0b6ff09 100644 --- a/Source/WebCore/platform/win/PasteboardWin.cpp +++ b/Source/WebCore/platform/win/PasteboardWin.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include #include diff --git a/Source/WebCore/platform/win/SearchPopupMenuDB.cpp b/Source/WebCore/platform/win/SearchPopupMenuDB.cpp index 58f4cccfdc179..08edfeae5f414 100644 --- a/Source/WebCore/platform/win/SearchPopupMenuDB.cpp +++ b/Source/WebCore/platform/win/SearchPopupMenuDB.cpp @@ -29,7 +29,7 @@ #include "SQLiteTransaction.h" #include #include -#include +#include namespace WebCore { diff --git a/Source/WebCore/platform/win/SystemInfo.cpp b/Source/WebCore/platform/win/SystemInfo.cpp index c4d2ab3a32b25..b233e7ade6fbd 100644 --- a/Source/WebCore/platform/win/SystemInfo.cpp +++ b/Source/WebCore/platform/win/SystemInfo.cpp @@ -27,7 +27,7 @@ #include "SystemInfo.h" #include -#include +#include #include namespace WebCore { @@ -163,7 +163,7 @@ static String architectureTokenForUAString() String windowsVersionForUAString() { - return osVersionForUAString() + architectureTokenForUAString(); + return makeString(osVersionForUAString(), architectureTokenForUAString()); } } // namespace WebCore diff --git a/Source/WebCore/platform/win/UserAgentWin.cpp b/Source/WebCore/platform/win/UserAgentWin.cpp index 07d9db251a493..7daf80a856064 100644 --- a/Source/WebCore/platform/win/UserAgentWin.cpp +++ b/Source/WebCore/platform/win/UserAgentWin.cpp @@ -27,7 +27,7 @@ #include "UserAgent.h" #include "SystemInfo.h" -#include +#include namespace WebCore { diff --git a/Source/WebCore/platform/win/WebCoreBundleWin.cpp b/Source/WebCore/platform/win/WebCoreBundleWin.cpp index fb1695736e51e..f2248fbe56eab 100644 --- a/Source/WebCore/platform/win/WebCoreBundleWin.cpp +++ b/Source/WebCore/platform/win/WebCoreBundleWin.cpp @@ -31,6 +31,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/platform/win/WindowsKeyNames.cpp b/Source/WebCore/platform/win/WindowsKeyNames.cpp index c33ae4b6aac91..e0f434cd88c0d 100644 --- a/Source/WebCore/platform/win/WindowsKeyNames.cpp +++ b/Source/WebCore/platform/win/WindowsKeyNames.cpp @@ -30,6 +30,8 @@ #include "config.h" #include "WindowsKeyNames.h" +#include + namespace WebCore { enum class WindowsKeyNames::KeyModifier : uint8_t { diff --git a/Source/WebCore/platform/xdg/MIMETypeRegistryXdg.cpp b/Source/WebCore/platform/xdg/MIMETypeRegistryXdg.cpp index 4c3ed8d7c3bf6..caf8ef981379f 100644 --- a/Source/WebCore/platform/xdg/MIMETypeRegistryXdg.cpp +++ b/Source/WebCore/platform/xdg/MIMETypeRegistryXdg.cpp @@ -26,6 +26,8 @@ #include "config.h" #include "MIMETypeRegistry.h" +#include + #define XDG_PREFIX _wk_xdg #include "xdgmime.h" @@ -38,7 +40,7 @@ String MIMETypeRegistry::mimeTypeForExtension(StringView string) return String(); // Build any filename with the given extension. - String filename = makeString("a."_s, string); + auto filename = makeString("a."_s, string); if (const char* mimeType = xdg_mime_get_mime_type_from_file_name(filename.utf8().data())) { if (mimeType != XDG_MIME_TYPE_UNKNOWN) return String::fromUTF8(mimeType); diff --git a/Source/WebCore/platform/xr/PlatformXR.h b/Source/WebCore/platform/xr/PlatformXR.h index 575e6dbd1d24b..f209dca53759c 100644 --- a/Source/WebCore/platform/xr/PlatformXR.h +++ b/Source/WebCore/platform/xr/PlatformXR.h @@ -260,7 +260,7 @@ struct FrameData { #if PLATFORM(COCOA) struct RateMapDescription { - WebCore::IntSize screenSize; + WebCore::IntSize screenSize = { 0, 0 }; Vector horizontalSamplesLeft; Vector horizontalSamplesRight; // Vertical samples is shared by both horizontalSamples diff --git a/Source/WebCore/platform/xr/openxr/OpenXRInputSource.cpp b/Source/WebCore/platform/xr/openxr/OpenXRInputSource.cpp index 857d1d7d518bd..1c10b62d39579 100644 --- a/Source/WebCore/platform/xr/openxr/OpenXRInputSource.cpp +++ b/Source/WebCore/platform/xr/openxr/OpenXRInputSource.cpp @@ -22,6 +22,8 @@ #if ENABLE(WEBXR) && USE(OPENXR) +#include + constexpr auto OPENXR_INPUT_HAND_PATH { "/user/hand/"_s }; constexpr auto OPENXR_INPUT_GRIP_PATH { "/input/grip/pose"_s }; constexpr auto OPENXR_INPUT_AIM_PATH { "/input/aim/pose"_s }; diff --git a/Source/WebCore/platform/xr/openxr/OpenXRUtils.h b/Source/WebCore/platform/xr/openxr/OpenXRUtils.h index 0487d02071c41..ae43f770f85d1 100644 --- a/Source/WebCore/platform/xr/openxr/OpenXRUtils.h +++ b/Source/WebCore/platform/xr/openxr/OpenXRUtils.h @@ -33,8 +33,7 @@ #include "PlatformXR.h" #include #include - -#include +#include #include namespace PlatformXR { diff --git a/Source/WebCore/rendering/BackgroundPainter.cpp b/Source/WebCore/rendering/BackgroundPainter.cpp index de5de8c469f9c..3c94063e98f18 100644 --- a/Source/WebCore/rendering/BackgroundPainter.cpp +++ b/Source/WebCore/rendering/BackgroundPainter.cpp @@ -270,12 +270,9 @@ void BackgroundPainter::paintFillLayer(const Color& color, const FillLayer& bgLa RoundedRect border = isBorderFill ? backgroundRoundedRectAdjustedForBleedAvoidance(rect, bleedAvoidance, box, includeLeftEdge, includeRightEdge) : backgroundRoundedRect(rect, box, includeLeftEdge, includeRightEdge); // Clip to the padding or content boxes as necessary. - if (bgLayer.clip() == FillBox::Content) { - border = style.getRoundedInnerBorderFor(border.rect(), - m_renderer.paddingTop() + m_renderer.borderTop(), m_renderer.paddingBottom() + m_renderer.borderBottom(), - m_renderer.paddingLeft() + m_renderer.borderLeft(), m_renderer.paddingRight() + m_renderer.borderRight(), - includeLeftEdge, includeRightEdge); - } else if (bgLayer.clip() == FillBox::Padding) + if (bgLayer.clip() == FillBox::Content) + border = m_renderer.roundedContentBoxRect(border.rect(), includeLeftEdge, includeRightEdge); + else if (bgLayer.clip() == FillBox::Padding) border = style.getRoundedInnerBorderFor(border.rect(), includeLeftEdge, includeRightEdge); clipRoundedInnerRect(context, pixelSnappedRect, border.pixelSnappedRoundedRectForPainting(deviceScaleFactor)); diff --git a/Source/WebCore/rendering/FloatingObjects.cpp b/Source/WebCore/rendering/FloatingObjects.cpp index 9f85072af8d77..c20685a3f7e1e 100644 --- a/Source/WebCore/rendering/FloatingObjects.cpp +++ b/Source/WebCore/rendering/FloatingObjects.cpp @@ -29,7 +29,6 @@ #include "RenderBox.h" #include "RenderView.h" #include -#include namespace WebCore { diff --git a/Source/WebCore/rendering/RenderBlock.cpp b/Source/WebCore/rendering/RenderBlock.cpp index 9fd3d905d643a..923650c2413dc 100644 --- a/Source/WebCore/rendering/RenderBlock.cpp +++ b/Source/WebCore/rendering/RenderBlock.cpp @@ -325,17 +325,6 @@ RenderBlock::~RenderBlock() // Do not add any more code here. Add it to willBeDestroyed() instead. } -// Note that this is not called for RenderBlockFlows. -void RenderBlock::willBeDestroyed() -{ - if (!renderTreeBeingDestroyed()) { - if (parent()) - parent()->dirtyLinesFromChangedChild(*this); - } - - RenderBox::willBeDestroyed(); -} - void RenderBlock::removePositionedObjectsIfNeeded(const RenderStyle& oldStyle, const RenderStyle& newStyle) { bool hadTransform = oldStyle.hasTransformRelatedProperty(); diff --git a/Source/WebCore/rendering/RenderBlock.h b/Source/WebCore/rendering/RenderBlock.h index 12ccb5c399ef8..2f552d1671920 100644 --- a/Source/WebCore/rendering/RenderBlock.h +++ b/Source/WebCore/rendering/RenderBlock.h @@ -300,7 +300,6 @@ class RenderBlock : public RenderBox { protected: RenderFragmentedFlow* locateEnclosingFragmentedFlow() const override; - void willBeDestroyed() override; void layout() override; diff --git a/Source/WebCore/rendering/RenderBlockFlow.cpp b/Source/WebCore/rendering/RenderBlockFlow.cpp index 4a4e01e83afd8..d1ea21d019e16 100644 --- a/Source/WebCore/rendering/RenderBlockFlow.cpp +++ b/Source/WebCore/rendering/RenderBlockFlow.cpp @@ -168,8 +168,8 @@ void RenderBlockFlow::willBeDestroyed() childBox->removeFromParent(); } } - } else if (parent()) - parent()->dirtyLinesFromChangedChild(*this); + } else if (auto* parent = this->parent(); parent && parent->isSVGRenderer()) + parent->dirtyLinesFromChangedChild(*this); } if (legacyLineLayout()) diff --git a/Source/WebCore/rendering/RenderBox.cpp b/Source/WebCore/rendering/RenderBox.cpp index f2d75ecfdd8de..e2e42a38133c9 100644 --- a/Source/WebCore/rendering/RenderBox.cpp +++ b/Source/WebCore/rendering/RenderBox.cpp @@ -4307,18 +4307,40 @@ void RenderBox::computePositionedLogicalWidthUsing(SizeType widthType, Length lo computeLogicalLeftPositionedOffset(computedValues.m_position, this, computedValues.m_extent + bordersPlusPadding, containerBlock, containerLogicalWidth, style().logicalLeft().isAuto(), style().logicalRight().isAuto()); } +static bool shouldFlipStaticPositionInParent(const RenderBox& outOfFlowBox, const RenderBoxModelObject& containerBlock) +{ + ASSERT(outOfFlowBox.isOutOfFlowPositioned()); + + auto* parent = outOfFlowBox.parent(); + if (!parent || parent == &containerBlock || !is(*parent)) + return false; + if (is(parent)) { + // FIXME: Out-of-flow grid item's static position computation is non-existent and enabling proper flipping + // without implementing the logic in grid layout makes us fail a couple of WPT tests -we pass them now accidentally. + return false; + } + // FIXME: While this ensures flipping when parent is a writing root, computeBlockStaticDistance still does not + // properly flip when the parent itself is not a writing root but an ancestor between this parent and out-of-flow's containing block. + return parent->style().isFlippedBlocksWritingMode() && parent->isWritingModeRoot(); +} + static void computeBlockStaticDistance(Length& logicalTop, Length& logicalBottom, const RenderBox* child, const RenderBoxModelObject& containerBlock) { if (!logicalTop.isAuto() || !logicalBottom.isAuto()) return; auto* parent = child->parent(); - + bool haveOrthogonalWritingModes = isOrthogonal(*child, *parent); // The static positions from the child's layer are relative to the container block's coordinate space (which is determined // by the writing mode and text direction), meaning that for orthogonal flows the logical top of the child (which depends on // the child's writing mode) is retrieved from the static inline position instead of the static block position. - bool haveOrthogonalWritingModes = isOrthogonal(*child, *parent); - LayoutUnit staticLogicalTop = haveOrthogonalWritingModes ? child->layer()->staticInlinePosition() - containerBlock.borderLogicalLeft() : child->layer()->staticBlockPosition() - containerBlock.borderBefore(); + auto staticLogicalTop = haveOrthogonalWritingModes ? child->layer()->staticInlinePosition() : child->layer()->staticBlockPosition(); + if (shouldFlipStaticPositionInParent(*child, containerBlock)) { + // Note that at this point we can't resolve static top position completely in flipped case as at this point the height of the child box has not been computed yet. + // What we can compute here is essentially the "bottom position". + staticLogicalTop = downcast(*parent).flipForWritingMode(staticLogicalTop); + } + staticLogicalTop -= haveOrthogonalWritingModes ? containerBlock.borderLogicalLeft() : containerBlock.borderBefore(); for (RenderElement* container = child->parent(); container && container != &containerBlock; container = container->container()) { auto* renderBox = dynamicDowncast(*container); if (!renderBox) @@ -4462,6 +4484,11 @@ static void computeLogicalTopPositionedOffset(LayoutUnit& logicalTopPos, const R // Deal with differing writing modes here. Our offset needs to be in the containing block's coordinate space. If the containing block is flipped // along this axis, then we need to flip the coordinate. This can only happen if the containing block is both a flipped mode and perpendicular to us. if (!isOverconstrained) { + if (logicalTopIsAuto && logicalBottomIsAuto && shouldFlipStaticPositionInParent(*child, containerBlock)) { + // Let's finish computing static top postion inside parents with flipped writing mode now that we've got final height value. + // see details in computeBlockStaticDistance. + logicalTopPos -= logicalHeightValue; + } if ((haveOrthogonalWritingModes && !logicalTopAndBottomAreAuto && child->style().isFlippedBlocksWritingMode()) || (haveFlippedBlockAxis && !haveOrthogonalWritingModes)) logicalTopPos = containerLogicalHeightForPositioned - logicalHeightValue - logicalTopPos; diff --git a/Source/WebCore/rendering/RenderBoxInlines.h b/Source/WebCore/rendering/RenderBoxInlines.h index 4f46107a13781..af2caf7e46970 100644 --- a/Source/WebCore/rendering/RenderBoxInlines.h +++ b/Source/WebCore/rendering/RenderBoxInlines.h @@ -96,20 +96,15 @@ inline LayoutRect RenderBox::contentBoxRect() const topScrollbarSpace = horizontalScrollbarHeight; } - auto paddingLeft = this->paddingLeft(); - auto paddingTop = this->paddingTop(); - - auto paddingRight = this->paddingRight(); - auto paddingBottom = this->paddingBottom(); - + auto padding = this->padding(); auto borderWidths = this->borderWidths(); - auto location = LayoutPoint { borderWidths.left() + paddingLeft + leftScrollbarSpace, borderWidths.top() + paddingTop + topScrollbarSpace }; + auto location = LayoutPoint { borderWidths.left() + padding.left() + leftScrollbarSpace, borderWidths.top() + padding.top() + topScrollbarSpace }; auto paddingBoxWidth = std::max(0_lu, width() - borderWidths.left() - borderWidths.right() - verticalScrollbarWidth); auto paddingBoxHeight = std::max(0_lu, height() - borderWidths.top() - borderWidths.bottom() - horizontalScrollbarHeight); - auto width = std::max(0_lu, paddingBoxWidth - paddingLeft - paddingRight - leftScrollbarSpace); - auto height = std::max(0_lu, paddingBoxHeight - paddingTop - paddingBottom - topScrollbarSpace); + auto width = std::max(0_lu, paddingBoxWidth - padding.left() - padding.right() - leftScrollbarSpace); + auto height = std::max(0_lu, paddingBoxHeight - padding.top() - padding.bottom() - topScrollbarSpace); auto size = LayoutSize { width, height }; diff --git a/Source/WebCore/rendering/RenderBoxModelObject.cpp b/Source/WebCore/rendering/RenderBoxModelObject.cpp index 7159759e589ae..37f6583e8b901 100644 --- a/Source/WebCore/rendering/RenderBoxModelObject.cpp +++ b/Source/WebCore/rendering/RenderBoxModelObject.cpp @@ -469,6 +469,21 @@ LayoutPoint RenderBoxModelObject::adjustedPositionRelativeToOffsetParent(const L if (const RenderBoxModelObject* offsetParent = this->offsetParent()) { if (auto* renderBox = dynamicDowncast(*offsetParent); renderBox && !offsetParent->isBody() && !is(*offsetParent)) referencePoint.move(-renderBox->borderLeft(), -renderBox->borderTop()); + else if (auto* renderInline = dynamicDowncast(*offsetParent)) { + // Inside inline formatting context both inflow and statically positioned out-of-flow boxes are positioned relative to the root block container. + auto topLeft = renderInline->firstInlineBoxTopLeft(); + if (isOutOfFlowPositioned()) { + auto& outOfFlowStyle = style(); + ASSERT(containingBlock()); + auto isHorizontalWritingMode = containingBlock() ? containingBlock()->style().isHorizontalWritingMode() : true; + if (!outOfFlowStyle.hasStaticInlinePosition(isHorizontalWritingMode)) + topLeft.setX(LayoutUnit { }); + if (!outOfFlowStyle.hasStaticBlockPosition(isHorizontalWritingMode)) + topLeft.setY(LayoutUnit { }); + } + referencePoint.move(-topLeft.x(), -topLeft.y()); + } + if (!isOutOfFlowPositioned() || enclosingFragmentedFlow()) { if (isRelativelyPositioned()) referencePoint.move(relativePositionOffset()); @@ -852,6 +867,16 @@ bool RenderBoxModelObject::borderObscuresBackground() const return true; } +RoundedRect RenderBoxModelObject::roundedContentBoxRect(const LayoutRect& borderBoxRect, bool includeLeftEdge, bool includeRightEdge) const +{ + auto borderWidths = this->borderWidths(); + auto padding = this->padding(); + return style().getRoundedInnerBorderFor(borderBoxRect, + borderWidths.top() + padding.top(), borderWidths.bottom() + padding.bottom(), + borderWidths.left() + padding.left(), borderWidths.right() + padding.right(), + includeLeftEdge, includeRightEdge); +} + LayoutUnit RenderBoxModelObject::containingBlockLogicalWidthForContent() const { if (auto* containingBlock = this->containingBlock()) diff --git a/Source/WebCore/rendering/RenderBoxModelObject.h b/Source/WebCore/rendering/RenderBoxModelObject.h index 88b1ff0b3e3b3..16d4d26231f21 100644 --- a/Source/WebCore/rendering/RenderBoxModelObject.h +++ b/Source/WebCore/rendering/RenderBoxModelObject.h @@ -118,6 +118,7 @@ class RenderBoxModelObject : public RenderLayerModelObject { // These functions are used during layout. Table cells and the MathML // code override them to include some extra intrinsic padding. + virtual inline RectEdges padding() const; virtual inline LayoutUnit paddingTop() const; virtual inline LayoutUnit paddingBottom() const; virtual inline LayoutUnit paddingLeft() const; @@ -181,6 +182,8 @@ class RenderBoxModelObject : public RenderLayerModelObject { LayoutUnit marginLogicalHeight() const { return marginBefore() + marginAfter(); } LayoutUnit marginLogicalWidth() const { return marginStart() + marginEnd(); } + RoundedRect roundedContentBoxRect(const LayoutRect& borderBoxRect, bool includeLeftEdge = true, bool includeRightEdge = true) const; + inline bool hasInlineDirectionBordersPaddingOrMargin() const; inline bool hasInlineDirectionBordersOrPadding() const; diff --git a/Source/WebCore/rendering/RenderBoxModelObjectInlines.h b/Source/WebCore/rendering/RenderBoxModelObjectInlines.h index abe866c075e91..6a58cac65aa6a 100644 --- a/Source/WebCore/rendering/RenderBoxModelObjectInlines.h +++ b/Source/WebCore/rendering/RenderBoxModelObjectInlines.h @@ -88,6 +88,16 @@ inline RectEdges RenderBoxModelObject::borderWidths() const }; } +RectEdges RenderBoxModelObject::padding() const +{ + return { + computedCSSPaddingTop(), + computedCSSPaddingRight(), + computedCSSPaddingBottom(), + computedCSSPaddingLeft() + }; +} + inline LayoutUnit RenderBoxModelObject::resolveLengthPercentageUsingContainerLogicalWidth(const Length& value) const { LayoutUnit containerWidth; diff --git a/Source/WebCore/rendering/RenderCounter.cpp b/Source/WebCore/rendering/RenderCounter.cpp index 8af48035409e7..b6eca7413c036 100644 --- a/Source/WebCore/rendering/RenderCounter.cpp +++ b/Source/WebCore/rendering/RenderCounter.cpp @@ -472,7 +472,7 @@ String RenderCounter::originalText() const if (m_counter.listStyleType().type == ListStyleType::Type::CounterStyle) { ASSERT(counterStyle()); - return counterStyle()->text(value, { style().blockFlowDirection(), style().direction() }); + return counterStyle()->text(value, makeTextFlow(style().writingMode(), style().direction())); } ASSERT_NOT_REACHED(); @@ -483,8 +483,7 @@ String RenderCounter::originalText() const if (!child->actsAsReset()) child = child->parent(); while (CounterNode* parent = child->parent()) { - text = counterText(child->countInParent()) - + m_counter.separator() + text; + text = makeString(counterText(child->countInParent()), m_counter.separator(), text); child = parent; } } diff --git a/Source/WebCore/rendering/RenderElement.cpp b/Source/WebCore/rendering/RenderElement.cpp index 41bc47ca4f935..b62b754cbe47c 100644 --- a/Source/WebCore/rendering/RenderElement.cpp +++ b/Source/WebCore/rendering/RenderElement.cpp @@ -1095,9 +1095,6 @@ void RenderElement::willBeRemovedFromTree() if (firstChild() || hasLayer()) removeLayers(); - if (isOutOfFlowPositioned() && parent()->childrenInline()) - checkedParent()->dirtyLinesFromChangedChild(*this); - RenderObject::willBeRemovedFromTree(); } diff --git a/Source/WebCore/rendering/RenderFileUploadControl.cpp b/Source/WebCore/rendering/RenderFileUploadControl.cpp index c6e8a712fef85..d63930843a9cb 100644 --- a/Source/WebCore/rendering/RenderFileUploadControl.cpp +++ b/Source/WebCore/rendering/RenderFileUploadControl.cpp @@ -279,7 +279,10 @@ void RenderFileUploadControl::computeIntrinsicLogicalWidths(LayoutUnit& minLogic defaultLabelWidth += buttonRenderer->maxPreferredLogicalWidth() + afterButtonSpacing; maxLogicalWidth = static_cast(ceilf(std::max(minDefaultLabelWidth, defaultLabelWidth))); - if (!style().logicalWidth().isPercentOrCalculated()) + auto& logicalWidth = style().logicalWidth(); + if (logicalWidth.isCalculated()) + minLogicalWidth = std::max(0_lu, valueForLength(logicalWidth, 0_lu)); + else if (!logicalWidth.isPercent()) minLogicalWidth = maxLogicalWidth; } diff --git a/Source/WebCore/rendering/RenderFlexibleBox.cpp b/Source/WebCore/rendering/RenderFlexibleBox.cpp index 85373c8de72dd..6a2e93f99edaf 100644 --- a/Source/WebCore/rendering/RenderFlexibleBox.cpp +++ b/Source/WebCore/rendering/RenderFlexibleBox.cpp @@ -756,10 +756,9 @@ std::optional RenderFlexibleBox::computeMainAxisExtentForChild(Rende return child.maxPreferredLogicalWidth() - child.borderAndPaddingLogicalWidth(); } } - - // FIXME: Figure out how this should work for regions and pass in the appropriate values. - RenderFragmentContainer* fragment = nullptr; - return child.computeLogicalWidthInFragmentUsing(sizeType, size, contentLogicalWidth(), *this, fragment) - child.borderAndPaddingLogicalWidth(); + + auto mainAxisWidth = isColumnFlow() ? availableLogicalHeight(AvailableLogicalHeightType::ExcludeMarginBorderPadding) : contentLogicalWidth(); + return child.computeLogicalWidthInFragmentUsing(sizeType, size, mainAxisWidth, *this, { }) - child.borderAndPaddingLogicalWidth(); } BlockFlowDirection RenderFlexibleBox::transformedBlockFlowDirection() const diff --git a/Source/WebCore/rendering/RenderInline.cpp b/Source/WebCore/rendering/RenderInline.cpp index bc0eb4833dcc1..ec37a422d382f 100644 --- a/Source/WebCore/rendering/RenderInline.cpp +++ b/Source/WebCore/rendering/RenderInline.cpp @@ -109,8 +109,8 @@ void RenderInline::willBeDestroyed() for (auto* box = firstLineBox(); box; box = box->nextLineBox()) box->removeFromParent(); } - } else if (parent()) - parent()->dirtyLinesFromChangedChild(*this); + } else if (auto* parent = this->parent(); parent && parent->isSVGRenderer()) + parent->dirtyLinesFromChangedChild(*this); } m_lineBoxes.deleteLineBoxes(); diff --git a/Source/WebCore/rendering/RenderInline.h b/Source/WebCore/rendering/RenderInline.h index 90d6665a26189..4be1a11faf0bc 100644 --- a/Source/WebCore/rendering/RenderInline.h +++ b/Source/WebCore/rendering/RenderInline.h @@ -87,6 +87,8 @@ class RenderInline : public RenderBoxModelObject { bool requiresLayer() const override; + LayoutPoint firstInlineBoxTopLeft() const; + protected: void willBeDestroyed() override; @@ -115,7 +117,6 @@ class RenderInline : public RenderBoxModelObject { LayoutUnit offsetTop() const final; LayoutUnit offsetWidth() const final { return linesBoundingBox().width(); } LayoutUnit offsetHeight() const final { return linesBoundingBox().height(); } - LayoutPoint firstInlineBoxTopLeft() const; protected: LayoutRect clippedOverflowRect(const RenderLayerModelObject* repaintContainer, VisibleRectContext) const override; diff --git a/Source/WebCore/rendering/RenderLayer.cpp b/Source/WebCore/rendering/RenderLayer.cpp index 653201d979e78..130a2fa302cf6 100644 --- a/Source/WebCore/rendering/RenderLayer.cpp +++ b/Source/WebCore/rendering/RenderLayer.cpp @@ -154,6 +154,7 @@ #include #include #include +#include #include namespace WebCore { @@ -1731,7 +1732,9 @@ bool RenderLayer::updateLayerPosition(OptionSet* flags auto layerRect = computeLayerPositionAndIntegralSize(renderer()); auto localPoint = layerRect.location(); + bool geometryChanged = false; if (IntSize newSize(layerRect.width().toInt(), layerRect.height().toInt()); newSize != size()) { + geometryChanged = true; setSize(newSize); if (flags && renderer().hasNonVisibleOverflow()) @@ -1792,29 +1795,30 @@ bool RenderLayer::updateLayerPosition(OptionSet* flags } else if (!m_contentsScrollingScope || m_contentsScrollingScope != m_boxScrollingScope) m_contentsScrollingScope = m_boxScrollingScope; - bool positionOrOffsetChanged = false; if (renderer().isInFlowPositioned()) { if (auto* boxModelObject = dynamicDowncast(renderer())) { auto newOffset = boxModelObject->offsetForInFlowPosition(); - positionOrOffsetChanged = newOffset != m_offsetForPosition; + geometryChanged |= newOffset != m_offsetForPosition; m_offsetForPosition = newOffset; localPoint.move(m_offsetForPosition); } } - positionOrOffsetChanged |= location() != localPoint; + geometryChanged |= location() != localPoint; setLocation(localPoint); - if (positionOrOffsetChanged && compositor().hasContentCompositingLayers()) { + if (geometryChanged && compositor().hasContentCompositingLayers()) { if (isComposited()) setNeedsCompositingGeometryUpdate(); - // This layer's position can affect the location of a composited descendant (which may be a sibling in z-order), - // so trigger a descendant walk from the paint-order parent. - if (auto* paintParent = paintOrderParent()) - paintParent->setDescendantsNeedUpdateBackingAndHierarchyTraversal(); + // This layer's footprint can affect the location of a composited descendant (which may be a sibling in z-order), + // so trigger a descendant walk from the enclosing stacking context. + if (auto* sc = stackingContext()) { + sc->setDescendantsNeedCompositingRequirementsTraversal(); + sc->setDescendantsNeedUpdateBackingAndHierarchyTraversal(); + } } - return positionOrOffsetChanged; + return geometryChanged; } TransformationMatrix RenderLayer::perspectiveTransform() const diff --git a/Source/WebCore/rendering/RenderLayerBacking.cpp b/Source/WebCore/rendering/RenderLayerBacking.cpp index 4e996e753949a..80433dcedf694 100644 --- a/Source/WebCore/rendering/RenderLayerBacking.cpp +++ b/Source/WebCore/rendering/RenderLayerBacking.cpp @@ -83,6 +83,7 @@ #include "TiledBacking.h" #include "ViewTransition.h" #include +#include #include #if ENABLE(FULLSCREEN_API) @@ -1868,7 +1869,7 @@ void RenderLayerBacking::updateContentsRects() m_graphicsLayer->setContentsRect(snapRectToDevicePixelsIfNeeded(contentsBox(), renderer())); if (CheckedPtr renderReplaced = dynamicDowncast(renderer())) { - FloatRoundedRect contentsClippingRect = renderReplaced->roundedContentBoxRect().pixelSnappedRoundedRectForPainting(deviceScaleFactor()); + FloatRoundedRect contentsClippingRect = renderReplaced->roundedContentBoxRect(renderReplaced->borderBoxRect()).pixelSnappedRoundedRectForPainting(deviceScaleFactor()); contentsClippingRect.move(contentOffsetInCompositingLayer()); m_graphicsLayer->setContentsClippingRect(contentsClippingRect); } diff --git a/Source/WebCore/rendering/RenderLayerCompositor.cpp b/Source/WebCore/rendering/RenderLayerCompositor.cpp index 0fa4e448aba00..53d92851ae952 100644 --- a/Source/WebCore/rendering/RenderLayerCompositor.cpp +++ b/Source/WebCore/rendering/RenderLayerCompositor.cpp @@ -82,8 +82,8 @@ #include #include #include +#include #include -#include #include #if PLATFORM(IOS_FAMILY) @@ -2729,7 +2729,7 @@ String RenderLayerCompositor::layerTreeAsText(OptionSet // The true root layer is not included in the dump, so if we want to report // its repaint rects, they must be included here. if (options & LayerTreeAsTextOptions::IncludeRepaintRects) - return m_renderView.frameView().trackedRepaintRectsAsText() + layerTreeText; + return makeString(m_renderView.frameView().trackedRepaintRectsAsText(), layerTreeText); return layerTreeText; } diff --git a/Source/WebCore/rendering/RenderLayerScrollableArea.cpp b/Source/WebCore/rendering/RenderLayerScrollableArea.cpp index f8b8d4cd2071a..ba6bbf782b9b4 100644 --- a/Source/WebCore/rendering/RenderLayerScrollableArea.cpp +++ b/Source/WebCore/rendering/RenderLayerScrollableArea.cpp @@ -78,6 +78,7 @@ #include "ScrollingCoordinator.h" #include "ShadowRoot.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/rendering/RenderLineBoxList.cpp b/Source/WebCore/rendering/RenderLineBoxList.cpp index a1b3507ea8ac6..faf822db93afd 100644 --- a/Source/WebCore/rendering/RenderLineBoxList.cpp +++ b/Source/WebCore/rendering/RenderLineBoxList.cpp @@ -36,6 +36,7 @@ #include "RenderBlockFlow.h" #include "RenderInline.h" #include "RenderLineBreak.h" +#include "RenderSVGInline.h" #include "RenderStyleInlines.h" #include "RenderView.h" @@ -310,67 +311,24 @@ bool RenderLineBoxList::hitTest(RenderBoxModelObject* renderer, const HitTestReq return false; } -void RenderLineBoxList::dirtyLinesFromChangedChild(RenderBoxModelObject& container, RenderObject& child) +void RenderLineBoxList::dirtyLinesFromChangedChild(RenderBoxModelObject& container, RenderObject&) { ASSERT(is(container) || is(container)); - if (!container.parent() || (is(container) && container.selfNeedsLayout())) + if (!container.isSVGRenderer()) return; - auto* inlineContainer = dynamicDowncast(container); - LegacyInlineBox* firstBox = inlineContainer ? inlineContainer->firstLineBox() : firstLineBox(); - - // If we have no first line box, then just bail early. - if (!firstBox) { - // For an empty inline, propagate the check up to our parent, unless the parent is already dirty. - if (container.isInline() && !container.ancestorLineBoxDirty()) { - container.parent()->dirtyLinesFromChangedChild(container); - container.setAncestorLineBoxDirty(); // Mark the container to avoid dirtying the same lines again across multiple destroy() calls of the same subtree. - } + if (!container.parent() || (is(container) && container.selfNeedsLayout())) return; - } - - // Try to figure out which line box we belong in. First try to find a previous - // line box by examining our siblings. If we didn't find a line box, then use our - // parent's first line box. - LegacyRootInlineBox* box = nullptr; - RenderObject* current; - for (current = child.previousSibling(); current; current = current->previousSibling()) { - if (current->isFloatingOrOutOfFlowPositioned()) - continue; - - if (auto* textRenderer = dynamicDowncast(*current)) { - if (auto* textBox = textRenderer->lastTextBox()) - box = &textBox->root(); - } else if (auto* renderInline = dynamicDowncast(*current)) { - auto* lastSiblingBox = renderInline->lastLineBox(); - if (lastSiblingBox) - box = &lastSiblingBox->root(); - } - if (box) - break; + auto* inlineContainer = dynamicDowncast(container); + if (auto* firstBox = inlineContainer ? inlineContainer->firstLineBox() : firstLineBox()) { + firstBox->root().markDirty(); + return; } - if (!box) - box = &firstBox->root(); - - // If we found a line box, then dirty it. - if (box) { - box->markDirty(); - - // Dirty the adjacent lines that might be affected. - // NOTE: we dirty the previous line because RootInlineBox objects cache - // the address of the first object on the next line after a BR, which we may be - // invalidating here. For more info, see how RenderBlock::layoutInlineChildren - // calls setLineBreakInfo with the result of findNextLineBreak. findNextLineBreak, - // despite the name, actually returns the first RenderObject after the BR. - // "Typing after pasting line does not appear until after window resize." - if (LegacyRootInlineBox* prevBox = box->prevRootBox()) - prevBox->markDirty(); - - // FIXME: We shouldn't need to always dirty the next line. This is only strictly - // necessary some of the time, in situations involving BRs. - if (LegacyRootInlineBox* nextBox = box->nextRootBox()) - nextBox->markDirty(); + // For an empty inline, propagate the check up to our parent, unless the parent is already dirty. + if (container.isInline() && !container.ancestorLineBoxDirty()) { + container.parent()->dirtyLinesFromChangedChild(container); + container.setAncestorLineBoxDirty(); // Mark the container to avoid dirtying the same lines again across multiple destroy() calls of the same subtree. } } diff --git a/Source/WebCore/rendering/RenderListBox.cpp b/Source/WebCore/rendering/RenderListBox.cpp index 8fa6ff8c3891d..4143d485a1902 100644 --- a/Source/WebCore/rendering/RenderListBox.cpp +++ b/Source/WebCore/rendering/RenderListBox.cpp @@ -70,6 +70,7 @@ #include #include #include +#include namespace WebCore { @@ -236,7 +237,10 @@ void RenderListBox::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, L if (m_scrollbar) maxLogicalWidth += m_scrollbar->orientation() == ScrollbarOrientation::Vertical ? m_scrollbar->width() : m_scrollbar->height(); - if (!style().logicalWidth().isPercentOrCalculated()) + auto& logicalWidth = style().logicalWidth(); + if (logicalWidth.isCalculated()) + minLogicalWidth = std::max(0_lu, valueForLength(logicalWidth, 0_lu)); + else if (!logicalWidth.isPercent()) minLogicalWidth = maxLogicalWidth; } diff --git a/Source/WebCore/rendering/RenderListMarker.cpp b/Source/WebCore/rendering/RenderListMarker.cpp index 3f6b734ef833d..a0fb1cbe5b737 100644 --- a/Source/WebCore/rendering/RenderListMarker.cpp +++ b/Source/WebCore/rendering/RenderListMarker.cpp @@ -39,7 +39,7 @@ #include "StyleScope.h" #include #include -#include +#include #include namespace WebCore { @@ -313,7 +313,7 @@ void RenderListMarker::updateContent() case ListStyleType::Type::CounterStyle: { auto counter = counterStyle(); ASSERT(counter); - auto text = makeString(counter->prefix().text, counter->text(m_listItem->value(), { style().blockFlowDirection(), style().direction() })); + auto text = makeString(counter->prefix().text, counter->text(m_listItem->value(), makeTextFlow(style().writingMode(), style().direction()))); m_textWithSuffix = makeString(text, counter->suffix().text); m_textWithoutSuffixLength = text.length(); m_textIsLeftToRightDirection = u_charDirection(text[0]) != U_RIGHT_TO_LEFT; diff --git a/Source/WebCore/rendering/RenderMenuList.cpp b/Source/WebCore/rendering/RenderMenuList.cpp index 3ef4b4bfcbcee..4197ece1be9af 100644 --- a/Source/WebCore/rendering/RenderMenuList.cpp +++ b/Source/WebCore/rendering/RenderMenuList.cpp @@ -350,7 +350,10 @@ void RenderMenuList::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, if (auto logicalWidth = explicitIntrinsicInnerLogicalWidth()) maxLogicalWidth = logicalWidth.value(); } - if (!style().logicalWidth().isPercentOrCalculated()) + auto& logicalWidth = style().logicalWidth(); + if (logicalWidth.isCalculated()) + minLogicalWidth = std::max(0_lu, valueForLength(logicalWidth, 0_lu)); + else if (!logicalWidth.isPercent()) minLogicalWidth = maxLogicalWidth; } diff --git a/Source/WebCore/rendering/RenderObject.cpp b/Source/WebCore/rendering/RenderObject.cpp index 342fcfc9b4e99..330360fb8a4de 100644 --- a/Source/WebCore/rendering/RenderObject.cpp +++ b/Source/WebCore/rendering/RenderObject.cpp @@ -1845,7 +1845,7 @@ void RenderObject::willBeDestroyed() void RenderObject::insertedIntoTree() { // FIXME: We should ASSERT(isRooted()) here but generated content makes some out-of-order insertion. - if (!isFloating() && parent()->childrenInline()) + if (!isFloating() && parent()->isSVGRenderer() && parent()->childrenInline()) checkedParent()->dirtyLinesFromChangedChild(*this); } diff --git a/Source/WebCore/rendering/RenderReplaced.cpp b/Source/WebCore/rendering/RenderReplaced.cpp index c6ff58733dc99..8022a7ff4668f 100644 --- a/Source/WebCore/rendering/RenderReplaced.cpp +++ b/Source/WebCore/rendering/RenderReplaced.cpp @@ -302,8 +302,7 @@ void RenderReplaced::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) if (!completelyClippedOut) { // Push a clip if we have a border radius, since we want to round the foreground content that gets painted. paintInfo.context().save(); - auto pixelSnappedRoundedRect = style().getRoundedInnerBorderFor(paintRect, - paddingTop() + borderTop(), paddingBottom() + borderBottom(), paddingLeft() + borderLeft(), paddingRight() + borderRight(), true, true).pixelSnappedRoundedRectForPainting(document().deviceScaleFactor()); + auto pixelSnappedRoundedRect = roundedContentBoxRect(paintRect).pixelSnappedRoundedRectForPainting(document().deviceScaleFactor()); BackgroundPainter::clipRoundedInnerRect(paintInfo.context(), paintRect, pixelSnappedRoundedRect); } } @@ -527,13 +526,6 @@ double RenderReplaced::computeIntrinsicAspectRatio() const return intrinsicRatio.aspectRatioDouble(); } -RoundedRect RenderReplaced::roundedContentBoxRect() const -{ - return style().getRoundedInnerBorderFor(borderBoxRect(), - borderTop() + paddingTop(), borderBottom() + paddingBottom(), - borderLeft() + paddingLeft(), borderRight() + paddingRight()); -} - void RenderReplaced::computeIntrinsicRatioInformation(FloatSize& intrinsicSize, FloatSize& intrinsicRatio) const { // If there's an embeddedContentBox() of a remote, referenced document available, this code-path should never be used. diff --git a/Source/WebCore/rendering/RenderReplaced.h b/Source/WebCore/rendering/RenderReplaced.h index 76c114891ddef..6cd81f1aab098 100644 --- a/Source/WebCore/rendering/RenderReplaced.h +++ b/Source/WebCore/rendering/RenderReplaced.h @@ -41,8 +41,6 @@ class RenderReplaced : public RenderBox { LayoutSize intrinsicSize() const final; - RoundedRect roundedContentBoxRect() const; - bool isContentLikelyVisibleInViewport(); bool needsPreferredWidthsRecalculation() const override; diff --git a/Source/WebCore/rendering/RenderSlider.cpp b/Source/WebCore/rendering/RenderSlider.cpp index aab3c79ade820..f7ab6ed4c55f4 100644 --- a/Source/WebCore/rendering/RenderSlider.cpp +++ b/Source/WebCore/rendering/RenderSlider.cpp @@ -81,7 +81,10 @@ void RenderSlider::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, La return; } maxLogicalWidth = defaultTrackLength * style().usedZoom(); - if (!style().width().isPercentOrCalculated()) + auto& logicalWidth = style().logicalWidth(); + if (logicalWidth.isCalculated()) + minLogicalWidth = std::max(0_lu, valueForLength(logicalWidth, 0_lu)); + else if (!logicalWidth.isPercent()) minLogicalWidth = maxLogicalWidth; } diff --git a/Source/WebCore/rendering/RenderTableCell.cpp b/Source/WebCore/rendering/RenderTableCell.cpp index f2560019b8663..2976e3edb200d 100644 --- a/Source/WebCore/rendering/RenderTableCell.cpp +++ b/Source/WebCore/rendering/RenderTableCell.cpp @@ -324,6 +324,31 @@ void RenderTableCell::layout() setCellWidthChanged(false); } +RectEdges RenderTableCell::padding() const +{ + auto top = computedCSSPaddingTop(); + auto right = computedCSSPaddingRight(); + auto bottom = computedCSSPaddingBottom(); + auto left = computedCSSPaddingLeft(); + + if (isHorizontalWritingMode()) { + bool isTopToBottom = style().blockFlowDirection() == BlockFlowDirection::TopToBottom; + top += isTopToBottom ? intrinsicPaddingBefore() : intrinsicPaddingAfter(); + bottom += isTopToBottom ? intrinsicPaddingAfter() : intrinsicPaddingBefore(); + } else { + bool isLeftToRight = style().blockFlowDirection() == BlockFlowDirection::LeftToRight; + left += isLeftToRight ? intrinsicPaddingBefore() : intrinsicPaddingAfter(); + right += isLeftToRight ? intrinsicPaddingAfter() : intrinsicPaddingBefore(); + } + + return { + top, + right, + bottom, + left + }; +} + LayoutUnit RenderTableCell::paddingTop() const { LayoutUnit result = computedCSSPaddingTop(); diff --git a/Source/WebCore/rendering/RenderTableCell.h b/Source/WebCore/rendering/RenderTableCell.h index 6f3144c2ca582..6bb1e060e9820 100644 --- a/Source/WebCore/rendering/RenderTableCell.h +++ b/Source/WebCore/rendering/RenderTableCell.h @@ -94,6 +94,7 @@ class RenderTableCell final : public RenderBlockFlow { LayoutUnit intrinsicPaddingBefore() const { return m_intrinsicPaddingBefore; } LayoutUnit intrinsicPaddingAfter() const { return m_intrinsicPaddingAfter; } + RectEdges padding() const override; LayoutUnit paddingTop() const override; LayoutUnit paddingBottom() const override; LayoutUnit paddingLeft() const override; diff --git a/Source/WebCore/rendering/RenderTableRow.cpp b/Source/WebCore/rendering/RenderTableRow.cpp index 74b9698bf6983..e79d93cf394c8 100644 --- a/Source/WebCore/rendering/RenderTableRow.cpp +++ b/Source/WebCore/rendering/RenderTableRow.cpp @@ -202,13 +202,17 @@ bool RenderTableRow::nodeAtPoint(const HitTestRequest& request, HitTestResult& r { // Table rows cannot ever be hit tested. Effectively they do not exist. // Just forward to our children always. + auto* section = this->section(); + if (!section) + return false; + for (RenderTableCell* cell = lastCell(); cell; cell = cell->previousCell()) { // FIXME: We have to skip over inline flows, since they can show up inside table rows // at the moment (a demoted inline
        for example). If we ever implement a // table-specific hit-test method (which we should do for performance reasons anyway), // then we can remove this check. if (!cell->hasSelfPaintingLayer()) { - LayoutPoint cellPoint = flipForWritingModeForChild(*cell, accumulatedOffset); + auto cellPoint = section->flipForWritingModeForChild(*cell, accumulatedOffset); if (cell->nodeAtPoint(request, result, locationInContainer, cellPoint, action)) { updateHitTestResult(result, locationInContainer.point() - toLayoutSize(cellPoint)); return true; diff --git a/Source/WebCore/rendering/RenderTableSection.cpp b/Source/WebCore/rendering/RenderTableSection.cpp index 02219b0367636..d71d2507a3bab 100644 --- a/Source/WebCore/rendering/RenderTableSection.cpp +++ b/Source/WebCore/rendering/RenderTableSection.cpp @@ -1498,11 +1498,8 @@ bool RenderTableSection::nodeAtPoint(const HitTestRequest& request, HitTestResul // table-specific hit-test method (which we should do for performance reasons anyway), // then we can remove this check. if (!row->hasSelfPaintingLayer()) { - LayoutPoint childPoint = flipForWritingModeForChild(*row, adjustedLocation); - if (row->nodeAtPoint(request, result, locationInContainer, childPoint, action)) { - updateHitTestResult(result, toLayoutPoint(locationInContainer.point() - childPoint)); + if (row->nodeAtPoint(request, result, locationInContainer, adjustedLocation, action)) return true; - } } } return false; diff --git a/Source/WebCore/rendering/RenderTextControl.cpp b/Source/WebCore/rendering/RenderTextControl.cpp index 58574b4d9fb50..5d81c5942027e 100644 --- a/Source/WebCore/rendering/RenderTextControl.cpp +++ b/Source/WebCore/rendering/RenderTextControl.cpp @@ -173,7 +173,10 @@ void RenderTextControl::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidt maxLogicalWidth = preferredContentLogicalWidth(const_cast(this)->getAverageCharWidth()); if (RenderBox* innerTextRenderBox = innerTextElement() ? innerTextElement()->renderBox() : nullptr) maxLogicalWidth += innerTextRenderBox->paddingStart() + innerTextRenderBox->paddingEnd(); - if (!style().logicalWidth().isPercentOrCalculated()) + auto& logicalWidth = style().logicalWidth(); + if (logicalWidth.isCalculated()) + minLogicalWidth = std::max(0_lu, valueForLength(logicalWidth, 0_lu)); + else if (!logicalWidth.isPercent()) minLogicalWidth = maxLogicalWidth; } diff --git a/Source/WebCore/rendering/RenderTheme.cpp b/Source/WebCore/rendering/RenderTheme.cpp index 0c6134beebba1..9204af5296d2c 100644 --- a/Source/WebCore/rendering/RenderTheme.cpp +++ b/Source/WebCore/rendering/RenderTheme.cpp @@ -78,7 +78,6 @@ #include #include #include -#include #if ENABLE(SERVICE_CONTROLS) #include "ImageControlsMac.h" @@ -743,7 +742,7 @@ ControlStyle RenderTheme::extractControlStyleForRenderer(const RenderObject& ren extractControlStyleStatesForRendererInternal(*renderer), renderer->style().computedFontSize(), renderer->style().usedZoom(), - renderer->style().usedAccentColor(), + renderer->style().usedAccentColor(renderObject.styleColorOptions()), renderer->style().visitedDependentColorWithColorFilter(CSSPropertyColor), renderer->style().borderWidth() }; @@ -810,7 +809,7 @@ bool RenderTheme::paint(const RenderBox& box, const PaintInfo& paintInfo, const case StyleAppearance::Button: case StyleAppearance::InnerSpinButton: { auto states = extractControlStyleStatesForRenderer(box); - Theme::singleton().paint(appearance, states, paintInfo.context(), devicePixelSnappedRect, box.useDarkAppearance(), box.style().usedAccentColor()); + Theme::singleton().paint(appearance, states, paintInfo.context(), devicePixelSnappedRect, box.useDarkAppearance(), box.style().usedAccentColor(box.styleColorOptions())); return false; } #else // !USE(THEME_ADWAITA) diff --git a/Source/WebCore/rendering/RenderTreeAsText.cpp b/Source/WebCore/rendering/RenderTreeAsText.cpp index d805accc7c5c7..bed7136ac81f4 100644 --- a/Source/WebCore/rendering/RenderTreeAsText.cpp +++ b/Source/WebCore/rendering/RenderTreeAsText.cpp @@ -178,43 +178,6 @@ String quoteAndEscapeNonPrintables(StringView s) return result.toString(); } -static inline bool isRenderInlineEmpty(const RenderInline& inlineRenderer) -{ - if (isEmptyInline(inlineRenderer)) - return true; - - for (auto& child : childrenOfType(inlineRenderer)) { - if (child.isFloatingOrOutOfFlowPositioned()) - continue; - auto isChildEmpty = false; - if (auto* renderInline = dynamicDowncast(child)) - isChildEmpty = isRenderInlineEmpty(*renderInline); - else if (auto* text = dynamicDowncast(child)) - isChildEmpty = !text->linesBoundingBox().height(); - if (!isChildEmpty) - return false; - } - return true; -} - -static inline bool hasNonEmptySibling(const RenderInline& inlineRenderer) -{ - auto* parent = inlineRenderer.parent(); - if (!parent) - return false; - - for (auto& sibling : childrenOfType(*parent)) { - if (&sibling == &inlineRenderer || sibling.isFloatingOrOutOfFlowPositioned()) - continue; - auto* siblingRendererInline = dynamicDowncast(sibling); - if (!siblingRendererInline) - return true; - if (siblingRendererInline->mayAffectLayout() || !isRenderInlineEmpty(*siblingRendererInline)) - return true; - } - return false; -} - inline bool shouldEnableSubpixelPrecisionForTextDump(const Document& document) { // If LBSE is activated and the document contains outermost elements, generate the text diff --git a/Source/WebCore/rendering/RenderWidget.cpp b/Source/WebCore/rendering/RenderWidget.cpp index 7ac575afcb518..b1726cb12da71 100644 --- a/Source/WebCore/rendering/RenderWidget.cpp +++ b/Source/WebCore/rendering/RenderWidget.cpp @@ -327,8 +327,7 @@ void RenderWidget::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) // Push a clip if we have a border radius, since we want to round the foreground content that gets painted. paintInfo.context().save(); - FloatRoundedRect roundedInnerRect = FloatRoundedRect(style().getRoundedInnerBorderFor(borderRect, - paddingTop() + borderTop(), paddingBottom() + borderBottom(), paddingLeft() + borderLeft(), paddingRight() + borderRight(), true, true)); + auto roundedInnerRect = FloatRoundedRect(roundedContentBoxRect(borderRect)); BackgroundPainter::clipRoundedInnerRect(paintInfo.context(), borderRect, roundedInnerRect); } diff --git a/Source/WebCore/rendering/adwaita/RenderThemeAdwaita.cpp b/Source/WebCore/rendering/adwaita/RenderThemeAdwaita.cpp index 904b35d47101e..560d499ae6738 100644 --- a/Source/WebCore/rendering/adwaita/RenderThemeAdwaita.cpp +++ b/Source/WebCore/rendering/adwaita/RenderThemeAdwaita.cpp @@ -107,7 +107,7 @@ static inline Color getSystemAccentColor() static inline Color getAccentColor(const RenderObject& renderObject) { if (!renderObject.style().hasAutoAccentColor()) - return renderObject.style().usedAccentColor(); + return renderObject.style().usedAccentColor(renderObject.styleColorOptions()); return getSystemAccentColor(); } @@ -418,7 +418,7 @@ bool RenderThemeAdwaita::paintMenuList(const RenderObject& renderObject, const P states.add(ControlStyle::State::Pressed); if (isHovered(renderObject)) states.add(ControlStyle::State::Hovered); - Theme::singleton().paint(StyleAppearance::Button, states, graphicsContext, rect, renderObject.useDarkAppearance(), renderObject.style().usedAccentColor()); + Theme::singleton().paint(StyleAppearance::Button, states, graphicsContext, rect, renderObject.useDarkAppearance(), renderObject.style().usedAccentColor(renderObject.styleColorOptions())); auto zoomedArrowSize = menuListButtonArrowSize * renderObject.style().usedZoom(); FloatRect fieldRect = rect; diff --git a/Source/WebCore/rendering/ios/RenderThemeIOS.mm b/Source/WebCore/rendering/ios/RenderThemeIOS.mm index 473001bfbae6b..c0981f5745e47 100644 --- a/Source/WebCore/rendering/ios/RenderThemeIOS.mm +++ b/Source/WebCore/rendering/ios/RenderThemeIOS.mm @@ -897,7 +897,7 @@ static bool renderThemePaintSwitchTrack(OptionSet, const Re return; if (!style.hasAutoAccentColor()) { - auto tintColor = style.usedAccentColor(); + auto tintColor = style.usedAccentColor(element.document().styleColorOptions(&style)); if (isSubmitStyleButton(element)) style.setBackgroundColor(tintColor); else @@ -1234,7 +1234,7 @@ static bool renderThemePaintSwitchTrack(OptionSet, const Re Color RenderThemeIOS::controlTintColor(const RenderStyle& style, OptionSet options) const { if (!style.hasAutoAccentColor()) - return style.usedAccentColor(); + return style.usedAccentColor(options); return systemColor(CSSValueAppleSystemBlue, options); } diff --git a/Source/WebCore/rendering/mathml/RenderMathMLBlock.cpp b/Source/WebCore/rendering/mathml/RenderMathMLBlock.cpp index 38a8711b45269..d4ac222f09c02 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLBlock.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLBlock.cpp @@ -279,6 +279,12 @@ void RenderMathMLBlock::layoutInvalidMarkup(bool relayoutChildren) clearNeedsLayout(); } +void RenderMathMLBlock::computeAndSetBlockDirectionMarginsOfChildren() +{ + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) + child->computeAndSetBlockDirectionMargins(*this); +} + void RenderMathMLBlock::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) { RenderBlock::styleDidChange(diff, oldStyle); diff --git a/Source/WebCore/rendering/mathml/RenderMathMLBlock.h b/Source/WebCore/rendering/mathml/RenderMathMLBlock.h index eea6ebd9f1a11..4e290617e4d46 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLBlock.h +++ b/Source/WebCore/rendering/mathml/RenderMathMLBlock.h @@ -78,6 +78,7 @@ class RenderMathMLBlock : public RenderBlock { void layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight = 0_lu) override; void layoutInvalidMarkup(bool relayoutChildren); + void computeAndSetBlockDirectionMarginsOfChildren(); private: bool isRenderMathMLBlock() const final { return true; } diff --git a/Source/WebCore/rendering/mathml/RenderMathMLFraction.cpp b/Source/WebCore/rendering/mathml/RenderMathMLFraction.cpp index 677c5f8f4fa3c..dcecf76d95593 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLFraction.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLFraction.cpp @@ -119,9 +119,9 @@ RenderMathMLFraction::FractionParameters RenderMathMLFraction::fractionParameter } // Adjust fraction shifts to satisfy min gaps. - LayoutUnit numeratorAscent = ascentForChild(numerator()); - LayoutUnit numeratorDescent = numerator().logicalHeight() - numeratorAscent; - LayoutUnit denominatorAscent = ascentForChild(denominator()); + LayoutUnit numeratorAscent = ascentForChild(numerator()) + numerator().marginBefore(); + LayoutUnit numeratorDescent = numerator().logicalHeight() + numerator().marginLogicalHeight() - numeratorAscent; + LayoutUnit denominatorAscent = ascentForChild(denominator()) + denominator().marginBefore(); LayoutUnit thickness = lineThickness(); parameters.numeratorShiftUp = std::max(numeratorMinShiftUp, mathAxisHeight() + thickness / 2 + numeratorGapMin + numeratorDescent); parameters.denominatorShiftDown = std::max(denominatorMinShiftDown, thickness / 2 + denominatorGapMin + denominatorAscent - mathAxisHeight()); @@ -155,9 +155,9 @@ RenderMathMLFraction::FractionParameters RenderMathMLFraction::stackParameters() } // Adjust fraction shifts to satisfy min gaps. - LayoutUnit numeratorAscent = ascentForChild(numerator()); - LayoutUnit numeratorDescent = numerator().logicalHeight() - numeratorAscent; - LayoutUnit denominatorAscent = ascentForChild(denominator()); + LayoutUnit numeratorAscent = ascentForChild(numerator()) + numerator().marginBefore(); + LayoutUnit numeratorDescent = numerator().logicalHeight() + numerator().marginLogicalHeight() - numeratorAscent; + LayoutUnit denominatorAscent = ascentForChild(denominator()) + denominator().marginBefore(); LayoutUnit gap = parameters.numeratorShiftUp - numeratorDescent + parameters.denominatorShiftDown - denominatorAscent; if (gap < gapMin) { LayoutUnit delta = (gapMin - gap) / 2; @@ -185,9 +185,9 @@ void RenderMathMLFraction::computePreferredLogicalWidths() m_maxPreferredLogicalWidth = 0; if (isValid()) { - LayoutUnit numeratorWidth = numerator().maxPreferredLogicalWidth(); - LayoutUnit denominatorWidth = denominator().maxPreferredLogicalWidth(); - m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = std::max(numeratorWidth, denominatorWidth); + LayoutUnit numeratorWidth = numerator().maxPreferredLogicalWidth() + numerator().marginLogicalWidth(); + LayoutUnit denominatorWidth = denominator().maxPreferredLogicalWidth() + denominator().marginLogicalWidth(); + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = std::max(numeratorWidth, denominatorWidth) + borderAndPaddingLogicalWidth(); } setPreferredLogicalWidthsDirty(false); @@ -195,11 +195,13 @@ void RenderMathMLFraction::computePreferredLogicalWidths() LayoutUnit RenderMathMLFraction::horizontalOffset(RenderBox& child, MathMLFractionElement::FractionAlignment align) const { + LayoutUnit contentBoxInlineSize = logicalWidth() - borderAndPaddingLogicalWidth(); + LayoutUnit childMarginBoxInlineSize = child.marginStart() + child.logicalWidth() + child.marginEnd(); switch (align) { case MathMLFractionElement::FractionAlignmentRight: - return LayoutUnit(logicalWidth() - child.logicalWidth()); + return LayoutUnit(contentBoxInlineSize - childMarginBoxInlineSize); case MathMLFractionElement::FractionAlignmentCenter: - return LayoutUnit((logicalWidth() - child.logicalWidth()) / 2); + return LayoutUnit((contentBoxInlineSize - childMarginBoxInlineSize) / 2); case MathMLFractionElement::FractionAlignmentLeft: return 0_lu; } @@ -212,11 +214,11 @@ LayoutUnit RenderMathMLFraction::fractionAscent() const { ASSERT(isValid()); - LayoutUnit numeratorAscent = ascentForChild(numerator()); + LayoutUnit numeratorAscent = ascentForChild(numerator()) + numerator().marginBefore(); if (LayoutUnit thickness = lineThickness()) - return std::max(mathAxisHeight() + thickness / 2, numeratorAscent + fractionParameters().numeratorShiftUp); + return borderAndPaddingBefore() + std::max(mathAxisHeight() + thickness / 2, numeratorAscent + fractionParameters().numeratorShiftUp); - return numeratorAscent + stackParameters().numeratorShiftUp; + return borderAndPaddingBefore() + numeratorAscent + stackParameters().numeratorShiftUp; } void RenderMathMLFraction::layoutBlock(bool relayoutChildren, LayoutUnit) @@ -231,21 +233,30 @@ void RenderMathMLFraction::layoutBlock(bool relayoutChildren, LayoutUnit) return; } + recomputeLogicalWidth(); + numerator().layoutIfNeeded(); denominator().layoutIfNeeded(); + computeAndSetBlockDirectionMarginsOfChildren(); + + LayoutUnit numeratorMarginBoxInlineSize = numerator().marginStart() + numerator().logicalWidth() + numerator().marginEnd(); + LayoutUnit denominatorMarginBoxInlineSize = denominator().marginStart() + denominator().logicalWidth() + denominator().marginEnd(); + setLogicalWidth(std::max(numeratorMarginBoxInlineSize, denominatorMarginBoxInlineSize) + borderAndPaddingLogicalWidth()); - setLogicalWidth(std::max(numerator().logicalWidth(), denominator().logicalWidth())); + LayoutUnit borderAndPaddingLeft = style().isLeftToRightDirection() ? borderAndPaddingStart() : borderAndPaddingEnd(); - LayoutUnit verticalOffset; // This is the top of the renderer. - LayoutPoint numeratorLocation(horizontalOffset(numerator(), element().numeratorAlignment()), verticalOffset); + LayoutUnit verticalOffset = borderAndPaddingBefore(); // This is the top of the renderer. + verticalOffset += numerator().marginBefore(); + LayoutPoint numeratorLocation(borderAndPaddingLeft + numerator().marginLeft() + horizontalOffset(numerator(), element().numeratorAlignment()), verticalOffset); numerator().setLocation(numeratorLocation); - LayoutUnit denominatorAscent = ascentForChild(denominator()); + LayoutUnit denominatorAscent = ascentForChild(denominator()) + denominator().marginBefore(); verticalOffset = fractionAscent(); FractionParameters parameters = lineThickness() ? fractionParameters() : stackParameters(); verticalOffset += parameters.denominatorShiftDown - denominatorAscent; - LayoutPoint denominatorLocation(horizontalOffset(denominator(), element().denominatorAlignment()), verticalOffset); + verticalOffset += denominator().marginBefore(); + LayoutPoint denominatorLocation(borderAndPaddingLeft + denominator().marginLeft() + horizontalOffset(denominator(), element().denominatorAlignment()), verticalOffset); denominator().setLocation(denominatorLocation); if (numerator().isOutOfFlowPositioned()) @@ -253,7 +264,7 @@ void RenderMathMLFraction::layoutBlock(bool relayoutChildren, LayoutUnit) if (denominator().isOutOfFlowPositioned()) denominator().containingBlock()->insertPositionedObject(denominator()); - verticalOffset += denominator().logicalHeight(); // This is the bottom of our renderer. + verticalOffset += denominator().logicalHeight() + denominator().marginAfter() + borderAndPaddingAfter(); // This is the bottom of our renderer. setLogicalHeight(verticalOffset); layoutPositionedObjects(relayoutChildren); @@ -270,14 +281,15 @@ void RenderMathMLFraction::paint(PaintInfo& info, const LayoutPoint& paintOffset if (info.context().paintingDisabled() || info.phase != PaintPhase::Foreground || style().usedVisibility() != Visibility::Visible || !isValid() || !thickness) return; - IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location() + LayoutPoint(0_lu, fractionAscent() - mathAxisHeight())); + LayoutUnit borderAndPaddingLeft = style().isLeftToRightDirection() ? borderAndPaddingStart() : borderAndPaddingEnd(); + IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location() + LayoutPoint(borderAndPaddingLeft, fractionAscent() - mathAxisHeight())); GraphicsContextStateSaver stateSaver(info.context()); info.context().setStrokeThickness(thickness); info.context().setStrokeStyle(StrokeStyle::SolidStroke); info.context().setStrokeColor(style().visitedDependentColorWithColorFilter(CSSPropertyColor)); - info.context().drawLine(adjustedPaintOffset, roundedIntPoint(LayoutPoint(adjustedPaintOffset.x() + logicalWidth(), LayoutUnit(adjustedPaintOffset.y())))); + info.context().drawLine(adjustedPaintOffset, roundedIntPoint(LayoutPoint(adjustedPaintOffset.x() + logicalWidth() - borderAndPaddingLogicalWidth(), LayoutUnit(adjustedPaintOffset.y())))); } std::optional RenderMathMLFraction::firstLineBaseline() const diff --git a/Source/WebCore/rendering/mathml/RenderMathMLMath.cpp b/Source/WebCore/rendering/mathml/RenderMathMLMath.cpp index 95436b1d582f9..e18936a628207 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLMath.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLMath.cpp @@ -82,6 +82,7 @@ void RenderMathMLMath::layoutBlock(bool relayoutChildren, LayoutUnit pageLogical return; recomputeLogicalWidth(); + computeAndSetBlockDirectionMarginsOfChildren(); setLogicalHeight(borderAndPaddingLogicalHeight() + scrollbarLogicalHeight()); @@ -96,8 +97,9 @@ void RenderMathMLMath::layoutBlock(bool relayoutChildren, LayoutUnit pageLogical centerChildren(width); else setLogicalWidth(width); + shiftRowItems(0_lu, borderAndPaddingBefore()); - setLogicalHeight(borderTop() + paddingTop() + ascent + descent + borderBottom() + paddingBottom() + horizontalScrollbarHeight()); + setLogicalHeight(ascent + descent + borderAndPaddingLogicalHeight() + horizontalScrollbarHeight()); updateLogicalHeight(); layoutPositionedObjects(relayoutChildren); diff --git a/Source/WebCore/rendering/mathml/RenderMathMLMenclose.cpp b/Source/WebCore/rendering/mathml/RenderMathMLMenclose.cpp index 538778612a11f..a90b59b2f1b20 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLMenclose.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLMenclose.cpp @@ -162,12 +162,10 @@ void RenderMathMLMenclose::computePreferredLogicalWidths() { ASSERT(preferredLogicalWidthsDirty()); - RenderMathMLRow::computePreferredLogicalWidths(); - - LayoutUnit preferredWidth = m_maxPreferredLogicalWidth; + LayoutUnit preferredWidth = preferredLogicalWidthOfRowItems(); SpaceAroundContent space = spaceAroundContent(preferredWidth, 0); - m_maxPreferredLogicalWidth = space.left + preferredWidth + space.right; - m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth; + preferredWidth += space.left + space.right + borderAndPaddingLogicalWidth(); + m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth = preferredWidth; setPreferredLogicalWidthsDirty(false); } @@ -179,18 +177,22 @@ void RenderMathMLMenclose::layoutBlock(bool relayoutChildren, LayoutUnit) if (!relayoutChildren && simplifiedLayout()) return; + recomputeLogicalWidth(); + computeAndSetBlockDirectionMarginsOfChildren(); + LayoutUnit contentWidth, contentAscent, contentDescent; stretchVerticalOperatorsAndLayoutChildren(); getContentBoundingBox(contentWidth, contentAscent, contentDescent); layoutRowItems(contentWidth, contentAscent); SpaceAroundContent space = spaceAroundContent(contentWidth, contentAscent + contentDescent); + space.left += borderLeft() + paddingLeft(); + space.right += borderRight() + paddingRight(); + space.top += borderAndPaddingBefore(); + space.bottom += borderAndPaddingAfter(); setLogicalWidth(space.left + contentWidth + space.right); setLogicalHeight(space.top + contentAscent + contentDescent + space.bottom); - - LayoutPoint contentLocation(space.left, space.top); - for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) - child->setLocation(child->location() + contentLocation); + shiftRowItems(space.left, space.top); m_contentRect = LayoutRect(space.left, space.top, contentWidth, contentAscent + contentDescent); diff --git a/Source/WebCore/rendering/mathml/RenderMathMLPadded.cpp b/Source/WebCore/rendering/mathml/RenderMathMLPadded.cpp index 087855979fff5..9b600ed0f7981 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLPadded.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLPadded.cpp @@ -75,13 +75,11 @@ void RenderMathMLPadded::computePreferredLogicalWidths() { ASSERT(preferredLogicalWidthsDirty()); - // Determine the intrinsic width of the content. - RenderMathMLRow::computePreferredLogicalWidths(); - // Only the width attribute should modify the width. // We parse it using the preferred width of the content as its default value. - m_maxPreferredLogicalWidth = mpaddedWidth(m_maxPreferredLogicalWidth); - m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth; + LayoutUnit preferredWidth = preferredLogicalWidthOfRowItems(); + preferredWidth = mpaddedWidth(preferredWidth) + borderAndPaddingLogicalWidth(); + m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth = preferredWidth; setPreferredLogicalWidthsDirty(false); } @@ -93,6 +91,9 @@ void RenderMathMLPadded::layoutBlock(bool relayoutChildren, LayoutUnit) if (!relayoutChildren && simplifiedLayout()) return; + recomputeLogicalWidth(); + computeAndSetBlockDirectionMarginsOfChildren(); + // We first layout our children as a normal element. LayoutUnit contentWidth, contentAscent, contentDescent; stretchVerticalOperatorsAndLayoutChildren(); @@ -100,14 +101,12 @@ void RenderMathMLPadded::layoutBlock(bool relayoutChildren, LayoutUnit) layoutRowItems(contentWidth, contentAscent); // We parse the mpadded attributes using the content metrics as the default value. - LayoutUnit width = mpaddedWidth(contentWidth); - LayoutUnit ascent = mpaddedHeight(contentAscent); - LayoutUnit descent = mpaddedDepth(contentDescent); + LayoutUnit width = mpaddedWidth(contentWidth) + borderAndPaddingLogicalWidth(); + LayoutUnit ascent = mpaddedHeight(contentAscent) + borderAndPaddingBefore(); + LayoutUnit descent = mpaddedDepth(contentDescent) + borderAndPaddingAfter(); // Align children on the new baseline and shift them by (lspace, -voffset) - LayoutPoint contentLocation(lspace(), ascent - contentAscent - voffset()); - for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) - child->setLocation(child->location() + contentLocation); + shiftRowItems(borderLeft() + paddingLeft() + lspace(), ascent - contentAscent - voffset()); // Set the final metrics. setLogicalWidth(width); diff --git a/Source/WebCore/rendering/mathml/RenderMathMLRoot.cpp b/Source/WebCore/rendering/mathml/RenderMathMLRoot.cpp index 0b01167a2fd55..844cc0f3442bb 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLRoot.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLRoot.cpp @@ -165,12 +165,10 @@ void RenderMathMLRoot::computePreferredLogicalWidths() return; } - LayoutUnit preferredWidth; + LayoutUnit preferredWidth = 0; if (rootType() == RootType::SquareRoot) { preferredWidth += m_radicalOperator.maxPreferredWidth(); - setPreferredLogicalWidthsDirty(true); - RenderMathMLRow::computePreferredLogicalWidths(); - preferredWidth += m_maxPreferredLogicalWidth; + preferredWidth += preferredLogicalWidthOfRowItems(); } else { ASSERT(rootType() == RootType::RootWithIndex); auto horizontal = horizontalParameters(); @@ -180,6 +178,7 @@ void RenderMathMLRoot::computePreferredLogicalWidths() preferredWidth += m_radicalOperator.maxPreferredWidth(); preferredWidth += getBase().maxPreferredLogicalWidth(); } + preferredWidth += borderAndPaddingLogicalWidth(); m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = preferredWidth; setPreferredLogicalWidthsDirty(false); @@ -204,15 +203,16 @@ void RenderMathMLRoot::layoutBlock(bool relayoutChildren, LayoutUnit) // Note: Per the MathML specification, the children of are wrapped in an inferred , which is the desired base. LayoutUnit baseAscent, baseDescent; recomputeLogicalWidth(); + computeAndSetBlockDirectionMarginsOfChildren(); if (rootType() == RootType::SquareRoot) { stretchVerticalOperatorsAndLayoutChildren(); getContentBoundingBox(m_baseWidth, baseAscent, baseDescent); layoutRowItems(m_baseWidth, baseAscent); } else { getBase().layoutIfNeeded(); - m_baseWidth = getBase().logicalWidth(); - baseAscent = ascentForChild(getBase()); - baseDescent = getBase().logicalHeight() - baseAscent; + m_baseWidth = getBase().logicalWidth() + getBase().marginLogicalWidth(); + baseAscent = ascentForChild(getBase()) + getBase().marginBefore(); + baseDescent = getBase().logicalHeight() + getBase().marginLogicalHeight() - baseAscent; getIndex().layoutIfNeeded(); } @@ -231,25 +231,27 @@ void RenderMathMLRoot::layoutBlock(bool relayoutChildren, LayoutUnit) // We set the logical width. if (rootType() == RootType::SquareRoot) - setLogicalWidth(m_radicalOperator.width() + m_baseWidth); + setLogicalWidth(m_radicalOperator.width() + m_baseWidth + borderAndPaddingLogicalWidth()); else { ASSERT(rootType() == RootType::RootWithIndex); - setLogicalWidth(horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree + m_radicalOperator.width() + m_baseWidth); + setLogicalWidth(horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree + m_radicalOperator.width() + m_baseWidth + borderAndPaddingLogicalWidth()); } // For , we update the metrics to take into account the index. LayoutUnit indexAscent, indexDescent; if (rootType() == RootType::RootWithIndex) { - indexAscent = ascentForChild(getIndex()); - indexDescent = getIndex().logicalHeight() - indexAscent; + indexAscent = ascentForChild(getIndex()) + getIndex().marginBefore(); + indexDescent = getIndex().logicalHeight() + getIndex().marginLogicalHeight() - indexAscent; ascent = std::max(radicalAscent, indexBottomRaise + indexDescent + indexAscent - descent); } + ascent += borderAndPaddingBefore(); + descent += borderAndPaddingAfter(); // We set the final position of children. m_radicalOperatorTop = ascent - radicalAscent + vertical.extraAscender; - LayoutUnit horizontalOffset = m_radicalOperator.width(); + LayoutUnit horizontalOffset = borderAndPaddingStart() + m_radicalOperator.width(); if (rootType() == RootType::RootWithIndex) - horizontalOffset += horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree; + horizontalOffset += horizontal.kernBeforeDegree + getIndex().logicalWidth() + getIndex().marginLogicalWidth() + horizontal.kernAfterDegree; LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, m_baseWidth), ascent - baseAscent); if (rootType() == RootType::SquareRoot) { for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) @@ -257,7 +259,7 @@ void RenderMathMLRoot::layoutBlock(bool relayoutChildren, LayoutUnit) } else { ASSERT(rootType() == RootType::RootWithIndex); getBase().setLocation(baseLocation); - LayoutPoint indexLocation(mirrorIfNeeded(horizontal.kernBeforeDegree, getIndex()), ascent + descent - indexBottomRaise - indexDescent - indexAscent); + LayoutPoint indexLocation(mirrorIfNeeded(borderAndPaddingStart() + horizontal.kernBeforeDegree, getIndex()), ascent + descent - indexBottomRaise - indexDescent - indexAscent); getIndex().setLocation(indexLocation); } @@ -279,10 +281,10 @@ void RenderMathMLRoot::paint(PaintInfo& info, const LayoutPoint& paintOffset) // We draw the radical operator. LayoutPoint radicalOperatorTopLeft = paintOffset + location(); - LayoutUnit horizontalOffset; + LayoutUnit horizontalOffset = borderAndPaddingStart(); if (rootType() == RootType::RootWithIndex) { auto horizontal = horizontalParameters(); - horizontalOffset = horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree; + horizontalOffset += horizontal.kernBeforeDegree + getIndex().logicalWidth() + getIndex().marginLogicalWidth() + horizontal.kernAfterDegree; } radicalOperatorTopLeft.move(mirrorIfNeeded(horizontalOffset, m_radicalOperator.width()), m_radicalOperatorTop); m_radicalOperator.paint(style(), info, radicalOperatorTopLeft); diff --git a/Source/WebCore/rendering/mathml/RenderMathMLRow.cpp b/Source/WebCore/rendering/mathml/RenderMathMLRow.cpp index 9447c794c2e59..9a86ecb1e4a0a 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLRow.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLRow.cpp @@ -63,7 +63,7 @@ std::optional RenderMathMLRow::firstLineBaseline() const if (!baselineChild) return std::optional(); - return LayoutUnit { static_cast(lroundf(ascentForChild(*baselineChild) + baselineChild->logicalTop())) }; + return LayoutUnit { static_cast(lroundf(ascentForChild(*baselineChild) + baselineChild->marginBefore() + baselineChild->logicalTop())) }; } static RenderMathMLOperator* toVerticalStretchyOperator(RenderBox* box) @@ -88,8 +88,8 @@ void RenderMathMLRow::stretchVerticalOperatorsAndLayoutChildren() if (toVerticalStretchyOperator(child)) continue; child->layoutIfNeeded(); - LayoutUnit childAscent = ascentForChild(*child); - LayoutUnit childDescent = child->logicalHeight() - childAscent; + LayoutUnit childAscent = ascentForChild(*child) + child->marginBefore(); + LayoutUnit childDescent = child->logicalHeight() + child->marginLogicalHeight() - childAscent; stretchAscent = std::max(stretchAscent, childAscent); stretchDescent = std::max(stretchDescent, childDescent); } @@ -115,47 +115,47 @@ void RenderMathMLRow::getContentBoundingBox(LayoutUnit& width, LayoutUnit& ascen { ascent = 0; descent = 0; - width = borderAndPaddingStart(); + width = 0; for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) { if (child->isOutOfFlowPositioned()) continue; width += child->marginStart() + child->logicalWidth() + child->marginEnd(); - LayoutUnit childAscent = ascentForChild(*child); - LayoutUnit childDescent = child->logicalHeight() - childAscent; - ascent = std::max(ascent, childAscent + child->marginTop()); - descent = std::max(descent, childDescent + child->marginBottom()); + LayoutUnit childAscent = ascentForChild(*child) + child->marginBefore(); + LayoutUnit childDescent = child->logicalHeight() + child->marginLogicalHeight() - childAscent; + ascent = std::max(ascent, childAscent); + descent = std::max(descent, childDescent); } - width += borderEnd() + paddingEnd(); } -void RenderMathMLRow::computePreferredLogicalWidths() +LayoutUnit RenderMathMLRow::preferredLogicalWidthOfRowItems() { - ASSERT(preferredLogicalWidthsDirty()); - - m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = 0; - - LayoutUnit preferredWidth; + LayoutUnit preferredWidth = 0; for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) { if (child->isOutOfFlowPositioned()) continue; preferredWidth += child->maxPreferredLogicalWidth() + child->marginLogicalWidth(); } + return preferredWidth; +} + +void RenderMathMLRow::computePreferredLogicalWidths() +{ + ASSERT(preferredLogicalWidthsDirty()); - m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = preferredWidth + borderAndPaddingLogicalWidth(); + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = preferredLogicalWidthOfRowItems() + borderAndPaddingLogicalWidth(); setPreferredLogicalWidthsDirty(false); } void RenderMathMLRow::layoutRowItems(LayoutUnit width, LayoutUnit ascent) { - LayoutUnit horizontalOffset = borderAndPaddingStart(); + LayoutUnit horizontalOffset = 0; for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) { if (child->isOutOfFlowPositioned()) continue; horizontalOffset += child->marginStart(); - LayoutUnit childAscent = ascentForChild(*child); - LayoutUnit childVerticalOffset = borderTop() + paddingTop() + child->marginTop() + ascent - childAscent; + LayoutUnit childVerticalOffset = ascent - ascentForChild(*child); LayoutUnit childWidth = child->logicalWidth(); LayoutUnit childHorizontalOffset = style().isLeftToRightDirection() ? horizontalOffset : width - horizontalOffset - childWidth; auto repaintRect = child->checkForRepaintDuringLayout() ? std::make_optional(child->frameRect()) : std::nullopt; @@ -168,6 +168,13 @@ void RenderMathMLRow::layoutRowItems(LayoutUnit width, LayoutUnit ascent) } } +void RenderMathMLRow::shiftRowItems(LayoutUnit left, LayoutUnit top) +{ + LayoutPoint shift(left, top); + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) + child->setLocation(child->location() + shift); +} + void RenderMathMLRow::layoutBlock(bool relayoutChildren, LayoutUnit) { ASSERT(needsLayout()); @@ -176,6 +183,7 @@ void RenderMathMLRow::layoutBlock(bool relayoutChildren, LayoutUnit) return; recomputeLogicalWidth(); + computeAndSetBlockDirectionMarginsOfChildren(); setLogicalHeight(borderAndPaddingLogicalHeight() + scrollbarLogicalHeight()); @@ -183,8 +191,9 @@ void RenderMathMLRow::layoutBlock(bool relayoutChildren, LayoutUnit) stretchVerticalOperatorsAndLayoutChildren(); getContentBoundingBox(width, ascent, descent); layoutRowItems(width, ascent); - setLogicalWidth(width); - setLogicalHeight(borderTop() + paddingTop() + ascent + descent + borderBottom() + paddingBottom() + horizontalScrollbarHeight()); + setLogicalWidth(width + borderAndPaddingLogicalWidth()); + setLogicalHeight(ascent + descent + borderAndPaddingLogicalHeight() + scrollbarLogicalHeight()); + shiftRowItems(borderLeft() + paddingLeft(), borderAndPaddingBefore()); updateLogicalHeight(); layoutPositionedObjects(relayoutChildren); diff --git a/Source/WebCore/rendering/mathml/RenderMathMLRow.h b/Source/WebCore/rendering/mathml/RenderMathMLRow.h index fcfb354a962ec..0cdb38a7ac6a0 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLRow.h +++ b/Source/WebCore/rendering/mathml/RenderMathMLRow.h @@ -49,6 +49,8 @@ class RenderMathMLRow : public RenderMathMLBlock { void stretchVerticalOperatorsAndLayoutChildren(); void getContentBoundingBox(LayoutUnit& width, LayoutUnit& ascent, LayoutUnit& descent) const; void layoutRowItems(LayoutUnit width, LayoutUnit ascent); + void shiftRowItems(LayoutUnit left, LayoutUnit top); + LayoutUnit preferredLogicalWidthOfRowItems(); void computePreferredLogicalWidths() override; private: diff --git a/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp b/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp index a14d0b6e160f3..36f90265b0e3b 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp @@ -186,19 +186,19 @@ void RenderMathMLScripts::computePreferredLogicalWidths() } auto& reference = possibleReference.value(); - LayoutUnit baseItalicCorrection = std::min(reference.base->maxPreferredLogicalWidth(), italicCorrection(reference)); + LayoutUnit baseItalicCorrection = std::min(reference.base->maxPreferredLogicalWidth() + reference.base->marginLogicalWidth(), italicCorrection(reference)); LayoutUnit space = spaceAfterScript(); switch (scriptType()) { case MathMLScriptsElement::ScriptType::Sub: case MathMLScriptsElement::ScriptType::Under: - m_maxPreferredLogicalWidth += reference.base->maxPreferredLogicalWidth(); - m_maxPreferredLogicalWidth += std::max(0_lu, reference.firstPostScript->maxPreferredLogicalWidth() - baseItalicCorrection + space); + m_maxPreferredLogicalWidth += reference.base->maxPreferredLogicalWidth() + reference.base->marginLogicalWidth(); + m_maxPreferredLogicalWidth += std::max(0_lu, reference.firstPostScript->maxPreferredLogicalWidth() + reference.firstPostScript->marginLogicalWidth() - baseItalicCorrection + space); break; case MathMLScriptsElement::ScriptType::Super: case MathMLScriptsElement::ScriptType::Over: m_maxPreferredLogicalWidth += reference.base->maxPreferredLogicalWidth(); - m_maxPreferredLogicalWidth += std::max(0_lu, reference.firstPostScript->maxPreferredLogicalWidth() + space); + m_maxPreferredLogicalWidth += std::max(0_lu, reference.firstPostScript->maxPreferredLogicalWidth() + reference.firstPostScript->marginLogicalWidth() + space); break; case MathMLScriptsElement::ScriptType::SubSup: case MathMLScriptsElement::ScriptType::UnderOver: @@ -207,22 +207,23 @@ void RenderMathMLScripts::computePreferredLogicalWidths() while (subScript) { auto supScript = subScript->nextSiblingBox(); ASSERT(supScript); - LayoutUnit subSupPairWidth = std::max(subScript->maxPreferredLogicalWidth(), supScript->maxPreferredLogicalWidth()); + LayoutUnit subSupPairWidth = std::max(subScript->maxPreferredLogicalWidth() + subScript->marginLogicalWidth(), supScript->maxPreferredLogicalWidth() + supScript->marginLogicalWidth()); m_maxPreferredLogicalWidth += subSupPairWidth + space; subScript = supScript->nextSiblingBox(); } - m_maxPreferredLogicalWidth += reference.base->maxPreferredLogicalWidth(); + m_maxPreferredLogicalWidth += reference.base->maxPreferredLogicalWidth() + reference.base->marginLogicalWidth(); subScript = reference.firstPostScript; while (subScript && subScript != reference.prescriptDelimiter) { auto supScript = subScript->nextSiblingBox(); ASSERT(supScript); - LayoutUnit subSupPairWidth = std::max(std::max(0_lu, subScript->maxPreferredLogicalWidth() - baseItalicCorrection), supScript->maxPreferredLogicalWidth()); + LayoutUnit subSupPairWidth = std::max(std::max(0_lu, subScript->maxPreferredLogicalWidth() + subScript->marginLogicalWidth() - baseItalicCorrection), supScript->maxPreferredLogicalWidth() + supScript->marginLogicalWidth()); m_maxPreferredLogicalWidth += subSupPairWidth + space; subScript = supScript->nextSiblingBox(); } } } + m_maxPreferredLogicalWidth += borderAndPaddingLogicalWidth(); m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth; setPreferredLogicalWidthsDirty(false); @@ -261,8 +262,8 @@ RenderMathMLScripts::VerticalMetrics RenderMathMLScripts::verticalMetrics(const VerticalParameters parameters = verticalParameters(); VerticalMetrics metrics = { 0, 0, 0, 0 }; - LayoutUnit baseAscent = ascentForChild(*reference.base); - LayoutUnit baseDescent = reference.base->logicalHeight() - baseAscent; + LayoutUnit baseAscent = ascentForChild(*reference.base) + reference.base->marginBefore(); + LayoutUnit baseDescent = reference.base->logicalHeight() + reference.base->marginLogicalHeight() - baseAscent; if (scriptType() == MathMLScriptsElement::ScriptType::Sub || scriptType() == MathMLScriptsElement::ScriptType::SubSup || scriptType() == MathMLScriptsElement::ScriptType::Multiscripts || scriptType() == MathMLScriptsElement::ScriptType::Under || scriptType() == MathMLScriptsElement::ScriptType::UnderOver) { metrics.subShift = std::max(parameters.subscriptShiftDown, baseDescent + parameters.subscriptBaselineDropMin); if (!isRenderMathMLUnderOver()) { @@ -285,16 +286,16 @@ RenderMathMLScripts::VerticalMetrics RenderMathMLScripts::verticalMetrics(const switch (scriptType()) { case MathMLScriptsElement::ScriptType::Sub: case MathMLScriptsElement::ScriptType::Under: { - LayoutUnit subAscent = ascentForChild(*reference.firstPostScript); - LayoutUnit subDescent = reference.firstPostScript->logicalHeight() - subAscent; + LayoutUnit subAscent = ascentForChild(*reference.firstPostScript) + reference.firstPostScript->marginBefore(); + LayoutUnit subDescent = reference.firstPostScript->logicalHeight() + reference.firstPostScript->marginLogicalHeight() - subAscent; metrics.descent = subDescent; metrics.subShift = std::max(metrics.subShift, subAscent - parameters.subscriptTopMax); } break; case MathMLScriptsElement::ScriptType::Super: case MathMLScriptsElement::ScriptType::Over: { - LayoutUnit supAscent = ascentForChild(*reference.firstPostScript); - LayoutUnit supDescent = reference.firstPostScript->logicalHeight() - supAscent; + LayoutUnit supAscent = ascentForChild(*reference.firstPostScript) + reference.firstPostScript->marginBefore(); + LayoutUnit supDescent = reference.firstPostScript->logicalHeight() + reference.firstPostScript->marginLogicalHeight() - supAscent; metrics.ascent = supAscent; metrics.supShift = std::max(metrics.supShift, parameters.superscriptBottomMin + supDescent); } @@ -309,10 +310,10 @@ RenderMathMLScripts::VerticalMetrics RenderMathMLScripts::verticalMetrics(const while (subScript) { auto supScript = subScript->nextSiblingBox(); ASSERT(supScript); - LayoutUnit subAscent = ascentForChild(*subScript); - LayoutUnit subDescent = subScript->logicalHeight() - subAscent; - LayoutUnit supAscent = ascentForChild(*supScript); - LayoutUnit supDescent = supScript->logicalHeight() - supAscent; + LayoutUnit subAscent = ascentForChild(*subScript) + subScript->marginBefore(); + LayoutUnit subDescent = subScript->logicalHeight() + subScript->marginLogicalHeight() - subAscent; + LayoutUnit supAscent = ascentForChild(*supScript) + supScript->marginBefore(); + LayoutUnit supDescent = supScript->logicalHeight() + supScript->marginLogicalHeight() - supAscent; metrics.ascent = std::max(metrics.ascent, supAscent); metrics.descent = std::max(metrics.descent, subDescent); LayoutUnit subScriptShift = std::max(parameters.subscriptShiftDown, baseDescent + parameters.subscriptBaselineDropMin); @@ -362,6 +363,7 @@ void RenderMathMLScripts::layoutBlock(bool relayoutChildren, LayoutUnit) auto& reference = possibleReference.value(); recomputeLogicalWidth(); + computeAndSetBlockDirectionMarginsOfChildren(); for (auto child = firstChildBox(); child; child = child->nextSiblingBox()) { if (child->isOutOfFlowPositioned()) { child->containingBlock()->insertPositionedObject(*child); @@ -375,35 +377,39 @@ void RenderMathMLScripts::layoutBlock(bool relayoutChildren, LayoutUnit) // We determine the minimal shift/size of each script and take the maximum of the values. VerticalMetrics metrics = verticalMetrics(reference); - LayoutUnit baseAscent = ascentForChild(*reference.base); - LayoutUnit baseDescent = reference.base->logicalHeight() - baseAscent; - LayoutUnit baseItalicCorrection = std::min(reference.base->logicalWidth(), italicCorrection(reference)); - LayoutUnit horizontalOffset; + LayoutUnit baseAscent = ascentForChild(*reference.base) + reference.base->marginBefore(); + LayoutUnit baseDescent = reference.base->logicalHeight() + reference.base->marginLogicalHeight() - baseAscent; + LayoutUnit baseItalicCorrection = std::min(reference.base->logicalWidth() + reference.base->marginLogicalWidth(), italicCorrection(reference)); + LayoutUnit horizontalOffset = borderAndPaddingStart(); - LayoutUnit ascent = std::max(baseAscent, metrics.ascent + metrics.supShift); - LayoutUnit descent = std::max(baseDescent, metrics.descent + metrics.subShift); + LayoutUnit ascent = std::max(baseAscent, metrics.ascent + metrics.supShift) + borderAndPaddingBefore(); + LayoutUnit descent = std::max(baseDescent, metrics.descent + metrics.subShift) + borderAndPaddingAfter(); setLogicalHeight(ascent + descent); switch (scriptType()) { case MathMLScriptsElement::ScriptType::Sub: case MathMLScriptsElement::ScriptType::Under: { - setLogicalWidth(reference.base->logicalWidth() + std::max(0_lu, reference.firstPostScript->logicalWidth() - baseItalicCorrection + space)); - LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, *reference.base), ascent - baseAscent); + LayoutUnit baseWidth = reference.base->logicalWidth() + reference.base->marginLogicalWidth(); + LayoutUnit contentWidth = baseWidth + std::max(0_lu, reference.firstPostScript->logicalWidth() + reference.firstPostScript->marginLogicalWidth() - baseItalicCorrection + space); + setLogicalWidth(contentWidth + borderAndPaddingLogicalWidth()); + LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset + reference.base->marginStart(), *reference.base), ascent - baseAscent + reference.base->marginBefore()); reference.base->setLocation(baseLocation); - horizontalOffset += reference.base->logicalWidth(); + horizontalOffset += baseWidth; LayoutUnit scriptAscent = ascentForChild(*reference.firstPostScript); - LayoutPoint scriptLocation(mirrorIfNeeded(horizontalOffset - baseItalicCorrection, *reference.firstPostScript), ascent + metrics.subShift - scriptAscent); + LayoutPoint scriptLocation(mirrorIfNeeded(horizontalOffset - baseItalicCorrection + reference.firstPostScript->marginStart(), *reference.firstPostScript), ascent + metrics.subShift - scriptAscent); reference.firstPostScript->setLocation(scriptLocation); } break; case MathMLScriptsElement::ScriptType::Super: case MathMLScriptsElement::ScriptType::Over: { - setLogicalWidth(reference.base->logicalWidth() + std::max(0_lu, reference.firstPostScript->logicalWidth() + space)); - LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, *reference.base), ascent - baseAscent); + LayoutUnit baseWidth = reference.base->logicalWidth() + reference.base->marginLogicalWidth(); + LayoutUnit contentWidth = baseWidth + std::max(0_lu, reference.firstPostScript->logicalWidth() + reference.firstPostScript->marginLogicalWidth() + space); + setLogicalWidth(contentWidth + borderAndPaddingLogicalWidth()); + LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset + reference.base->marginStart(), *reference.base), ascent - baseAscent + reference.base->marginBefore()); reference.base->setLocation(baseLocation); - horizontalOffset += reference.base->logicalWidth(); + horizontalOffset += baseWidth; LayoutUnit scriptAscent = ascentForChild(*reference.firstPostScript); - LayoutPoint scriptLocation(mirrorIfNeeded(horizontalOffset, *reference.firstPostScript), ascent - metrics.supShift - scriptAscent); + LayoutPoint scriptLocation(mirrorIfNeeded(horizontalOffset + reference.firstPostScript->marginStart(), *reference.firstPostScript), ascent - metrics.supShift - scriptAscent); reference.firstPostScript->setLocation(scriptLocation); } break; @@ -411,21 +417,21 @@ void RenderMathMLScripts::layoutBlock(bool relayoutChildren, LayoutUnit) case MathMLScriptsElement::ScriptType::UnderOver: case MathMLScriptsElement::ScriptType::Multiscripts: { // Calculate the logical width. - LayoutUnit logicalWidth; + LayoutUnit logicalWidth = borderAndPaddingLogicalWidth(); auto subScript = reference.firstPreScript; while (subScript) { auto supScript = subScript->nextSiblingBox(); ASSERT(supScript); - LayoutUnit subSupPairWidth = std::max(subScript->logicalWidth(), supScript->logicalWidth()); + LayoutUnit subSupPairWidth = std::max(subScript->logicalWidth() + subScript->marginLogicalWidth(), supScript->logicalWidth() + supScript->marginLogicalWidth()); logicalWidth += subSupPairWidth + space; subScript = supScript->nextSiblingBox(); } - logicalWidth += reference.base->logicalWidth(); + logicalWidth += reference.base->logicalWidth() + reference.base->marginLogicalWidth(); subScript = reference.firstPostScript; while (subScript && subScript != reference.prescriptDelimiter) { auto supScript = subScript->nextSiblingBox(); ASSERT(supScript); - LayoutUnit subSupPairWidth = std::max(std::max(0_lu, subScript->logicalWidth() - baseItalicCorrection), supScript->logicalWidth()); + LayoutUnit subSupPairWidth = std::max(std::max(0_lu, subScript->logicalWidth() + subScript->marginLogicalWidth() - baseItalicCorrection), supScript->logicalWidth() + supScript->marginLogicalWidth()); logicalWidth += subSupPairWidth + space; subScript = supScript->nextSiblingBox(); } @@ -435,31 +441,31 @@ void RenderMathMLScripts::layoutBlock(bool relayoutChildren, LayoutUnit) while (subScript) { auto supScript = subScript->nextSiblingBox(); ASSERT(supScript); - LayoutUnit subSupPairWidth = std::max(subScript->logicalWidth(), supScript->logicalWidth()); + LayoutUnit subSupPairWidth = std::max(subScript->logicalWidth() + subScript->marginLogicalWidth(), supScript->logicalWidth() + supScript->marginLogicalWidth()); horizontalOffset += space + subSupPairWidth; LayoutUnit subAscent = ascentForChild(*subScript); - LayoutPoint subScriptLocation(mirrorIfNeeded(horizontalOffset - subScript->logicalWidth(), *subScript), ascent + metrics.subShift - subAscent); + LayoutPoint subScriptLocation(mirrorIfNeeded(horizontalOffset - subScript->marginEnd() - subScript->logicalWidth(), *subScript), ascent + metrics.subShift - subAscent); subScript->setLocation(subScriptLocation); LayoutUnit supAscent = ascentForChild(*supScript); - LayoutPoint supScriptLocation(mirrorIfNeeded(horizontalOffset - supScript->logicalWidth(), *supScript), ascent - metrics.supShift - supAscent); + LayoutPoint supScriptLocation(mirrorIfNeeded(horizontalOffset - supScript->marginEnd() - supScript->logicalWidth(), *supScript), ascent - metrics.supShift - supAscent); supScript->setLocation(supScriptLocation); subScript = supScript->nextSiblingBox(); } - LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, *reference.base), ascent - baseAscent); + LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset + reference.base->marginStart(), *reference.base), ascent - baseAscent + reference.base->marginBefore()); reference.base->setLocation(baseLocation); - horizontalOffset += reference.base->logicalWidth(); + horizontalOffset += reference.base->logicalWidth() + reference.base->marginLogicalWidth(); subScript = reference.firstPostScript; while (subScript && subScript != reference.prescriptDelimiter) { auto supScript = subScript->nextSiblingBox(); ASSERT(supScript); LayoutUnit subAscent = ascentForChild(*subScript); - LayoutPoint subScriptLocation(mirrorIfNeeded(horizontalOffset - baseItalicCorrection, *subScript), ascent + metrics.subShift - subAscent); + LayoutPoint subScriptLocation(mirrorIfNeeded(horizontalOffset - baseItalicCorrection + subScript->marginStart(), *subScript), ascent + metrics.subShift - subAscent); subScript->setLocation(subScriptLocation); LayoutUnit supAscent = ascentForChild(*supScript); - LayoutPoint supScriptLocation(mirrorIfNeeded(horizontalOffset, *supScript), ascent - metrics.supShift - supAscent); + LayoutPoint supScriptLocation(mirrorIfNeeded(horizontalOffset + supScript->marginStart(), *supScript), ascent - metrics.supShift - supAscent); supScript->setLocation(supScriptLocation); - LayoutUnit subSupPairWidth = std::max(subScript->logicalWidth(), supScript->logicalWidth()); + LayoutUnit subSupPairWidth = std::max(subScript->logicalWidth() + subScript->marginLogicalWidth(), supScript->logicalWidth() + supScript->marginLogicalWidth()); horizontalOffset += subSupPairWidth + space; subScript = supScript->nextSiblingBox(); } @@ -478,7 +484,7 @@ std::optional RenderMathMLScripts::firstLineBaseline() const auto* base = firstChildBox(); if (!base) return std::optional(); - return LayoutUnit { roundf(ascentForChild(*base) + base->logicalTop()) }; + return LayoutUnit { roundf(ascentForChild(*base) + base->marginBefore() + base->logicalTop()) }; } } diff --git a/Source/WebCore/rendering/mathml/RenderMathMLSpace.cpp b/Source/WebCore/rendering/mathml/RenderMathMLSpace.cpp index b69933c3ba798..109b65d5ebe3e 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLSpace.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLSpace.cpp @@ -49,7 +49,7 @@ void RenderMathMLSpace::computePreferredLogicalWidths() { ASSERT(preferredLogicalWidthsDirty()); - m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = spaceWidth(); + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = spaceWidth() + borderAndPaddingLogicalWidth(); setPreferredLogicalWidthsDirty(false); } @@ -81,10 +81,12 @@ void RenderMathMLSpace::layoutBlock(bool relayoutChildren, LayoutUnit) if (!relayoutChildren && simplifiedLayout()) return; - setLogicalWidth(spaceWidth()); + recomputeLogicalWidth(); + + setLogicalWidth(spaceWidth() + borderAndPaddingLogicalWidth()); LayoutUnit height, depth; getSpaceHeightAndDepth(height, depth); - setLogicalHeight(height + depth); + setLogicalHeight(height + depth + borderAndPaddingLogicalHeight()); updateScrollInfoAfterLayout(); @@ -95,7 +97,7 @@ std::optional RenderMathMLSpace::firstLineBaseline() const { LayoutUnit height, depth; getSpaceHeightAndDepth(height, depth); - return height; + return height + borderAndPaddingBefore(); } } diff --git a/Source/WebCore/rendering/mathml/RenderMathMLUnderOver.cpp b/Source/WebCore/rendering/mathml/RenderMathMLUnderOver.cpp index 371864a576453..282e2ac223f64 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLUnderOver.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLUnderOver.cpp @@ -108,7 +108,7 @@ void RenderMathMLUnderOver::stretchHorizontalOperatorsAndLayoutChildren() } else { isAllStretchyOperators = false; child->layoutIfNeeded(); - stretchWidth = std::max(stretchWidth, child->logicalWidth()); + stretchWidth = std::max(stretchWidth, child->logicalWidth() + child->marginLogicalWidth()); } } @@ -116,7 +116,7 @@ void RenderMathMLUnderOver::stretchHorizontalOperatorsAndLayoutChildren() for (size_t i = 0; i < embellishedOperators.size(); i++) { stretchyOperators[i]->resetStretchSize(); fixLayoutAfterStretch(*embellishedOperators[i], *stretchyOperators[i]); - stretchWidth = std::max(stretchWidth, embellishedOperators[i]->logicalWidth()); + stretchWidth = std::max(stretchWidth, embellishedOperators[i]->logicalWidth() + embellishedOperators[i]->marginLogicalWidth()); } } @@ -197,22 +197,24 @@ void RenderMathMLUnderOver::computePreferredLogicalWidths() return; } - LayoutUnit preferredWidth = base().maxPreferredLogicalWidth(); + LayoutUnit preferredWidth = base().maxPreferredLogicalWidth() + base().marginLogicalWidth(); if (scriptType() == MathMLScriptsElement::ScriptType::Under || scriptType() == MathMLScriptsElement::ScriptType::UnderOver) - preferredWidth = std::max(preferredWidth, under().maxPreferredLogicalWidth()); + preferredWidth = std::max(preferredWidth, under().maxPreferredLogicalWidth() + under().marginLogicalWidth()); if (scriptType() == MathMLScriptsElement::ScriptType::Over || scriptType() == MathMLScriptsElement::ScriptType::UnderOver) - preferredWidth = std::max(preferredWidth, over().maxPreferredLogicalWidth()); + preferredWidth = std::max(preferredWidth, over().maxPreferredLogicalWidth() + over().marginLogicalWidth()); - m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = preferredWidth; + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = preferredWidth + borderAndPaddingLogicalWidth(); setPreferredLogicalWidthsDirty(false); } LayoutUnit RenderMathMLUnderOver::horizontalOffset(const RenderBox& child) const { - return (logicalWidth() - child.logicalWidth()) / 2; + LayoutUnit contentBoxInlineSize = logicalWidth() - borderAndPaddingLogicalWidth(); + LayoutUnit childMarginBoxInlineSize = child.logicalWidth() + child.marginLogicalWidth(); + return borderLeft() + paddingLeft() + (contentBoxInlineSize - childMarginBoxInlineSize) / 2 + child.marginLeft(); } bool RenderMathMLUnderOver::hasAccent(bool accentUnder) const @@ -314,6 +316,7 @@ void RenderMathMLUnderOver::layoutBlock(bool relayoutChildren, LayoutUnit pageLo } recomputeLogicalWidth(); + computeAndSetBlockDirectionMarginsOfChildren(); stretchHorizontalOperatorsAndLayoutChildren(); @@ -321,45 +324,52 @@ void RenderMathMLUnderOver::layoutBlock(bool relayoutChildren, LayoutUnit pageLo ASSERT(scriptType() == MathMLScriptsElement::ScriptType::Over || !under().needsLayout()); ASSERT(scriptType() == MathMLScriptsElement::ScriptType::Under || !over().needsLayout()); - LayoutUnit logicalWidth = base().logicalWidth(); + LayoutUnit logicalWidth = base().logicalWidth() + base().marginLogicalWidth(); if (scriptType() == MathMLScriptsElement::ScriptType::Under || scriptType() == MathMLScriptsElement::ScriptType::UnderOver) - logicalWidth = std::max(logicalWidth, under().logicalWidth()); + logicalWidth = std::max(logicalWidth, under().logicalWidth() + under().marginLogicalWidth()); if (scriptType() == MathMLScriptsElement::ScriptType::Over || scriptType() == MathMLScriptsElement::ScriptType::UnderOver) - logicalWidth = std::max(logicalWidth, over().logicalWidth()); - setLogicalWidth(logicalWidth); + logicalWidth = std::max(logicalWidth, over().logicalWidth() + over().marginLogicalWidth()); + setLogicalWidth(logicalWidth + borderAndPaddingLogicalWidth()); VerticalParameters parameters = verticalParameters(); - LayoutUnit verticalOffset; + LayoutUnit verticalOffset = borderAndPaddingBefore(); if (scriptType() == MathMLScriptsElement::ScriptType::Over || scriptType() == MathMLScriptsElement::ScriptType::UnderOver) { verticalOffset += parameters.overExtraAscender; + verticalOffset += over().marginBefore(); over().setLocation(LayoutPoint(horizontalOffset(over()), verticalOffset)); if (parameters.useUnderOverBarFallBack) { verticalOffset += over().logicalHeight(); + verticalOffset += over().marginAfter(); if (hasAccent()) { - LayoutUnit baseAscent = ascentForChild(base()); + LayoutUnit baseAscent = ascentForChild(base()) + base().marginBefore(); if (baseAscent < parameters.accentBaseHeight) verticalOffset += parameters.accentBaseHeight - baseAscent; } else verticalOffset += parameters.overGapMin; } else { - LayoutUnit overAscent = ascentForChild(over()); - verticalOffset += std::max(over().logicalHeight() + parameters.overGapMin, overAscent + parameters.overShiftMin); + LayoutUnit overAscent = ascentForChild(over()) + over().marginBefore(); + verticalOffset += std::max(over().logicalHeight() + over().marginAfter() + parameters.overGapMin, overAscent + parameters.overShiftMin); } } + verticalOffset += base().marginBefore(); base().setLocation(LayoutPoint(horizontalOffset(base()), verticalOffset)); verticalOffset += base().logicalHeight(); + verticalOffset += base().marginAfter(); if (scriptType() == MathMLScriptsElement::ScriptType::Under || scriptType() == MathMLScriptsElement::ScriptType::UnderOver) { if (parameters.useUnderOverBarFallBack) { if (!hasAccentUnder()) verticalOffset += parameters.underGapMin; } else { - LayoutUnit underAscent = ascentForChild(under()); + LayoutUnit underAscent = ascentForChild(under()) + under().marginBefore(); verticalOffset += std::max(parameters.underGapMin, parameters.underShiftMin - underAscent); } + verticalOffset += under().marginBefore(); under().setLocation(LayoutPoint(horizontalOffset(under()), verticalOffset)); verticalOffset += under().logicalHeight(); + verticalOffset += under().marginAfter(); verticalOffset += parameters.underExtraDescender; } + verticalOffset += borderAndPaddingAfter(); setLogicalHeight(verticalOffset); diff --git a/Source/WebCore/rendering/shapes/BoxShape.cpp b/Source/WebCore/rendering/shapes/BoxShape.cpp index b398f576e705f..0b892d3b7eb9a 100644 --- a/Source/WebCore/rendering/shapes/BoxShape.cpp +++ b/Source/WebCore/rendering/shapes/BoxShape.cpp @@ -80,9 +80,7 @@ RoundedRect computeRoundedRectForBoxShape(CSSBoxType box, const RenderBox& rende // fill-box compute to content-box for HTML elements. case CSSBoxType::FillBox: case CSSBoxType::ContentBox: - return style.getRoundedInnerBorderFor(renderer.borderBoxRect(), - renderer.paddingTop() + renderer.borderTop(), renderer.paddingBottom() + renderer.borderBottom(), - renderer.paddingLeft() + renderer.borderLeft(), renderer.paddingRight() + renderer.borderRight()); + return renderer.roundedContentBoxRect(renderer.borderBoxRect()); // stroke-box, view-box compute to border-box for HTML elements. case CSSBoxType::BorderBox: case CSSBoxType::StrokeBox: diff --git a/Source/WebCore/rendering/shapes/ShapeOutsideInfo.cpp b/Source/WebCore/rendering/shapes/ShapeOutsideInfo.cpp index fc7b1b204ea2c..3f01e2d0bbe9a 100644 --- a/Source/WebCore/rendering/shapes/ShapeOutsideInfo.cpp +++ b/Source/WebCore/rendering/shapes/ShapeOutsideInfo.cpp @@ -39,6 +39,7 @@ #include "RenderFragmentContainer.h" #include "RenderImage.h" #include "RenderView.h" +#include namespace WebCore { diff --git a/Source/WebCore/rendering/style/BasicShapes.h b/Source/WebCore/rendering/style/BasicShapes.h index a1987a8102eb5..17d2b2d35c9c7 100644 --- a/Source/WebCore/rendering/style/BasicShapes.h +++ b/Source/WebCore/rendering/style/BasicShapes.h @@ -314,6 +314,8 @@ class BasicShapePath final : public BasicShape { const SVGPathByteStream* pathData() const { return m_byteStream.get(); } const std::unique_ptr& byteStream() const { return m_byteStream; } + const Path& path(const FloatRect&) final; + bool canBlend(const BasicShape&) const final; Ref blend(const BasicShape& from, const BlendingContext&) const final; @@ -323,8 +325,6 @@ class BasicShapePath final : public BasicShape { Type type() const final { return Type::Path; } - const Path& path(const FloatRect&) final; - bool operator==(const BasicShape&) const final; void dump(TextStream&) const final; diff --git a/Source/WebCore/rendering/style/GridPositionsResolver.cpp b/Source/WebCore/rendering/style/GridPositionsResolver.cpp index 91b6d07b68a7d..996312363148c 100644 --- a/Source/WebCore/rendering/style/GridPositionsResolver.cpp +++ b/Source/WebCore/rendering/style/GridPositionsResolver.cpp @@ -38,6 +38,7 @@ #include "RenderStyleInlines.h" #include "StyleGridData.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/rendering/style/RenderStyle.cpp b/Source/WebCore/rendering/style/RenderStyle.cpp index 773b8955e48f8..41209d0f1fe8e 100644 --- a/Source/WebCore/rendering/style/RenderStyle.cpp +++ b/Source/WebCore/rendering/style/RenderStyle.cpp @@ -27,6 +27,7 @@ #include "CSSParser.h" #include "CSSPropertyNames.h" #include "CSSPropertyParser.h" +#include "ColorBlending.h" #include "ComputedStyleExtractor.h" #include "ContentData.h" #include "CursorList.h" @@ -434,6 +435,11 @@ void RenderStyle::copyPseudoElementsFrom(const RenderStyle& other) addCachedPseudoStyle(makeUnique(cloneIncludingPseudoElements(*pseudoElementStyle))); } +void RenderStyle::copyPseudoElementBitsFrom(const RenderStyle& other) +{ + m_nonInheritedFlags.pseudoBits = other.m_nonInheritedFlags.pseudoBits; +} + bool RenderStyle::operator==(const RenderStyle& other) const { // compare everything except the pseudoStyle pointer @@ -1416,11 +1422,7 @@ bool RenderStyle::outOfFlowPositionStyleDidChange(const RenderStyle* other) cons // https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers // Determine if there is a style change that causes an element to become or stop // being absolutely or fixed positioned - if (other && m_nonInheritedData.ptr() != other->m_nonInheritedData.ptr()) { - if (hasOutOfFlowPosition() != other->hasOutOfFlowPosition()) - return true; - } - return false; + return other && hasOutOfFlowPosition() != other->hasOutOfFlowPosition(); } StyleDifference RenderStyle::diff(const RenderStyle& other, OptionSet& changedContextSensitiveProperties) const @@ -3093,15 +3095,22 @@ Color RenderStyle::colorWithColorFilter(const StyleColor& color) const return colorByApplyingColorFilter(colorResolvingCurrentColor(color)); } -Color RenderStyle::usedAccentColor() const +Color RenderStyle::usedAccentColor(OptionSet styleColorOptions) const { if (hasAutoAccentColor()) return { }; + auto resolvedAccentColor = colorResolvingCurrentColor(accentColor()); + + if (!resolvedAccentColor.isOpaque()) { + auto computedCanvasColor = RenderTheme::singleton().systemColor(CSSValueCanvas, styleColorOptions); + resolvedAccentColor = blendSourceOver(computedCanvasColor, resolvedAccentColor); + } + if (hasAppleColorFilter()) - return colorByApplyingColorFilter(colorResolvingCurrentColor(accentColor())); + return colorByApplyingColorFilter(resolvedAccentColor); - return colorResolvingCurrentColor(accentColor()); + return resolvedAccentColor; } Color RenderStyle::usedScrollbarThumbColor() const diff --git a/Source/WebCore/rendering/style/RenderStyle.h b/Source/WebCore/rendering/style/RenderStyle.h index 97468707869e3..bb63e48e6dd37 100644 --- a/Source/WebCore/rendering/style/RenderStyle.h +++ b/Source/WebCore/rendering/style/RenderStyle.h @@ -198,6 +198,7 @@ enum class ScrollSnapStop : bool; enum class ScrollbarWidth : uint8_t; enum class SpeakAs : uint8_t; enum class StyleAppearance : uint8_t; +enum class StyleColorOptions : uint8_t; enum class StyleDifference : uint8_t; enum class StyleDifferenceContextSensitiveProperty : uint8_t; enum class TableLayoutType : bool; @@ -337,6 +338,7 @@ class RenderStyle final : public CanMakeCheckedPtr { void copyNonInheritedFrom(const RenderStyle&); void copyContentFrom(const RenderStyle&); void copyPseudoElementsFrom(const RenderStyle&); + void copyPseudoElementBitsFrom(const RenderStyle&); ContentPosition resolvedJustifyContentPosition(const StyleContentAlignmentData& normalValueBehavior) const; ContentDistribution resolvedJustifyContentDistribution(const StyleContentAlignmentData& normalValueBehavior) const; @@ -2157,7 +2159,7 @@ class RenderStyle final : public CanMakeCheckedPtr { inline const StyleColor& floodColor() const; inline const StyleColor& lightingColor() const; - Color usedAccentColor() const; + Color usedAccentColor(OptionSet) const; inline const StyleColor& accentColor() const; inline bool hasAutoAccentColor() const; diff --git a/Source/WebCore/rendering/style/SVGRenderStyle.cpp b/Source/WebCore/rendering/style/SVGRenderStyle.cpp index 550d7fa011a76..1257c8e94a323 100644 --- a/Source/WebCore/rendering/style/SVGRenderStyle.cpp +++ b/Source/WebCore/rendering/style/SVGRenderStyle.cpp @@ -302,6 +302,8 @@ void SVGRenderStyle::conservativelyCollectChangedAnimatableProperties(const SVGR changingProperties.m_properties.set(CSSPropertyX); if (first.y != second.y) changingProperties.m_properties.set(CSSPropertyY); + if (first.d != second.d) + changingProperties.m_properties.set(CSSPropertyD); }; auto conservativelyCollectChangedAnimatablePropertiesViaInheritedResourceData = [&](auto& first, auto& second) { diff --git a/Source/WebCore/rendering/style/ShapeValue.cpp b/Source/WebCore/rendering/style/ShapeValue.cpp index bda0d97dd3859..eadb0c083c147 100644 --- a/Source/WebCore/rendering/style/ShapeValue.cpp +++ b/Source/WebCore/rendering/style/ShapeValue.cpp @@ -59,7 +59,7 @@ bool ShapeValue::canBlend(const ShapeValue& to) const if (m_cssBox != to.cssBox()) return false; - if (auto* toShape = to.shape()) + if (RefPtr toShape = to.shape()) return m_shape && m_shape->canBlend(*toShape); return false; diff --git a/Source/WebCore/rendering/svg/RenderSVGPath.cpp b/Source/WebCore/rendering/svg/RenderSVGPath.cpp index e34d1438d6a76..63ea4a14fa824 100644 --- a/Source/WebCore/rendering/svg/RenderSVGPath.cpp +++ b/Source/WebCore/rendering/svg/RenderSVGPath.cpp @@ -292,4 +292,14 @@ bool RenderSVGPath::isRenderingDisabled() const return !hasPath() || path().isEmpty(); } +void RenderSVGPath::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + if (RefPtr pathElement = dynamicDowncast(graphicsElement())) { + if (!oldStyle || style().d() != oldStyle->d()) + pathElement->pathDidChange(); + } + + RenderSVGShape::styleDidChange(diff, oldStyle); +} + } diff --git a/Source/WebCore/rendering/svg/RenderSVGPath.h b/Source/WebCore/rendering/svg/RenderSVGPath.h index d61ff0868c6cc..7de46282cc072 100644 --- a/Source/WebCore/rendering/svg/RenderSVGPath.h +++ b/Source/WebCore/rendering/svg/RenderSVGPath.h @@ -50,6 +50,8 @@ class RenderSVGPath final : public RenderSVGShape { void strokeShape(GraphicsContext&) const override; bool shapeDependentStrokeContains(const FloatPoint&, PointCoordinateSpace = GlobalCoordinateSpace) override; + void styleDidChange(StyleDifference, const RenderStyle*) final; + bool shouldStrokeZeroLengthSubpath() const; Path* zeroLengthLinecapPath(const FloatPoint&) const; FloatRect zeroLengthSubpathRect(const FloatPoint&, float) const; diff --git a/Source/WebCore/rendering/svg/legacy/LegacyRenderSVGPath.cpp b/Source/WebCore/rendering/svg/legacy/LegacyRenderSVGPath.cpp index e5dcc89e4bf94..4c50150b473f9 100644 --- a/Source/WebCore/rendering/svg/legacy/LegacyRenderSVGPath.cpp +++ b/Source/WebCore/rendering/svg/legacy/LegacyRenderSVGPath.cpp @@ -296,4 +296,14 @@ bool LegacyRenderSVGPath::isRenderingDisabled() const return !hasPath() || path().isEmpty(); } +void LegacyRenderSVGPath::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + if (RefPtr pathElement = dynamicDowncast(graphicsElement())) { + if (!oldStyle || style().d() != oldStyle->d()) + pathElement->pathDidChange(); + } + + LegacyRenderSVGShape::styleDidChange(diff, oldStyle); +} + } diff --git a/Source/WebCore/rendering/svg/legacy/LegacyRenderSVGPath.h b/Source/WebCore/rendering/svg/legacy/LegacyRenderSVGPath.h index a6b731e0c66bf..28334b08b391c 100644 --- a/Source/WebCore/rendering/svg/legacy/LegacyRenderSVGPath.h +++ b/Source/WebCore/rendering/svg/legacy/LegacyRenderSVGPath.h @@ -47,6 +47,8 @@ class LegacyRenderSVGPath final : public LegacyRenderSVGShape { void strokeShape(GraphicsContext&) const override; bool shapeDependentStrokeContains(const FloatPoint&, PointCoordinateSpace = GlobalCoordinateSpace) override; + void styleDidChange(StyleDifference, const RenderStyle*) final; + bool shouldStrokeZeroLengthSubpath() const; Path* zeroLengthLinecapPath(const FloatPoint&) const; FloatRect zeroLengthSubpathRect(const FloatPoint&, float) const; diff --git a/Source/WebCore/rendering/updating/RenderTreeBuilder.cpp b/Source/WebCore/rendering/updating/RenderTreeBuilder.cpp index 699fdebf579b5..834aff5eff1b0 100644 --- a/Source/WebCore/rendering/updating/RenderTreeBuilder.cpp +++ b/Source/WebCore/rendering/updating/RenderTreeBuilder.cpp @@ -73,6 +73,7 @@ #include "RenderTreeBuilderSVG.h" #include "RenderTreeBuilderTable.h" #include "RenderTreeMutationDisallowedScope.h" +#include "RenderVideo.h" #include "RenderView.h" #include @@ -477,7 +478,8 @@ void RenderTreeBuilder::attachToRenderElementInternal(RenderElement& parent, Ren // in order to compute static position for out of flow boxes, the parent has to run normal flow layout as well (as opposed to simplified) // FIXME: Introduce a dirty bit to bridge the gap between parent and containing block which would // not trigger layout but a simple traversal all the way to the direct parent and also expand it non-direct parent cases. - if (newChild->containingBlock() == &parent) + // FIXME: RenderVideo's setNeedsLayout pattern does not play well with this optimization: see webkit.org/b/276253 + if (newChild->containingBlock() == &parent && !is(*newChild)) parent.setOutOfFlowChildNeedsStaticPositionLayout(); else parent.setChildNeedsLayout(); diff --git a/Source/WebCore/rendering/updating/RenderTreeBuilderBlock.cpp b/Source/WebCore/rendering/updating/RenderTreeBuilderBlock.cpp index 8a97a2daf3188..0b03619e5a4dd 100644 --- a/Source/WebCore/rendering/updating/RenderTreeBuilderBlock.cpp +++ b/Source/WebCore/rendering/updating/RenderTreeBuilderBlock.cpp @@ -48,31 +48,25 @@ static bool canDropAnonymousBlock(const RenderBlock& anonymousBlock) return true; } -static bool canMergeContiguousAnonymousBlocks(RenderObject& oldChild, RenderObject* previous, RenderObject* next) +static bool canMergeContiguousAnonymousBlocks(const RenderObject& rendererToBeRemoved, const RenderObject* previous, const RenderObject* next) { - ASSERT(!oldChild.renderTreeBeingDestroyed()); + ASSERT(!rendererToBeRemoved.renderTreeBeingDestroyed()); - if (oldChild.isInline()) + if (rendererToBeRemoved.isInline()) return false; - if (auto* boxModelObject = dynamicDowncast(oldChild); boxModelObject && boxModelObject->continuation()) + if (previous && (!previous->isAnonymousBlock() || !canDropAnonymousBlock(downcast(*previous)))) return false; - if (previous) { - if (!previous->isAnonymousBlock()) - return false; - RenderBlock& previousAnonymousBlock = downcast(*previous); - if (!canDropAnonymousBlock(previousAnonymousBlock)) - return false; - } - if (next) { - if (!next->isAnonymousBlock()) - return false; - RenderBlock& nextAnonymousBlock = downcast(*next); - if (!canDropAnonymousBlock(nextAnonymousBlock)) - return false; - } - return true; + if (next && (!next->isAnonymousBlock() || !canDropAnonymousBlock(downcast(*next)))) + return false; + + auto* boxToBeRemoved = dynamicDowncast(rendererToBeRemoved); + if (!boxToBeRemoved || !boxToBeRemoved->continuation()) + return true; + + // Let's merge pre and post anonymous block containers when the continuation triggering box (rendererToBeRemoved) is going away. + return previous && next; } static RenderBlock* continuationBefore(RenderBlock& parent, RenderObject* beforeChild) diff --git a/Source/WebCore/style/AnchorPositionEvaluator.cpp b/Source/WebCore/style/AnchorPositionEvaluator.cpp index 4457a87e3c667..30d266be6e8b9 100644 --- a/Source/WebCore/style/AnchorPositionEvaluator.cpp +++ b/Source/WebCore/style/AnchorPositionEvaluator.cpp @@ -26,16 +26,19 @@ #include "AnchorPositionEvaluator.h" #include "Document.h" +#include "DocumentInlines.h" #include "Element.h" #include "StyleScope.h" namespace WebCore::Style { -Length AnchorPositionEvaluator::resolveAnchorValue(const CSSAnchorValue* anchorValue, const Element& element) +Length AnchorPositionEvaluator::resolveAnchorValue(const CSSAnchorValue* anchorValue, const Element* element) { - auto& anchorPositionedStateMap = element.document().styleScope().anchorPositionedStateMap(); + if (!element) + return Length(0, LengthType::Fixed); - auto* anchorPositionedElementState = anchorPositionedStateMap.ensure(element, [&] { + auto& anchorPositionedStateMap = element->protectedDocument()->checkedStyleScope()->anchorPositionedStateMap(); + auto* anchorPositionedElementState = anchorPositionedStateMap.ensure(*element, [&] { return WTF::makeUnique(); }).iterator->value.get(); diff --git a/Source/WebCore/style/AnchorPositionEvaluator.h b/Source/WebCore/style/AnchorPositionEvaluator.h index b7ded1b0b1250..434c342106832 100644 --- a/Source/WebCore/style/AnchorPositionEvaluator.h +++ b/Source/WebCore/style/AnchorPositionEvaluator.h @@ -49,7 +49,7 @@ using AnchorPositionedStateMap = WeakHashMap -#include namespace WebCore { namespace Style { diff --git a/Source/WebCore/style/StyleBuilderConverter.h b/Source/WebCore/style/StyleBuilderConverter.h index 3b0d6a52e6754..5140931b46a50 100644 --- a/Source/WebCore/style/StyleBuilderConverter.h +++ b/Source/WebCore/style/StyleBuilderConverter.h @@ -79,6 +79,7 @@ #include "TransformFunctions.h" #include "ViewTimeline.h" #include "WillChangeData.h" +#include namespace WebCore { namespace Style { @@ -266,8 +267,8 @@ inline Length BuilderConverter::convertLength(const BuilderState& builderState, return Length(primitiveValue.cssCalcValue()->createCalculationValue(conversionData)); if (primitiveValue.isAnchor()) { - auto& anchorPositionedElement = *builderState.element(); - return AnchorPositionEvaluator::resolveAnchorValue(primitiveValue.cssAnchorValue(), anchorPositionedElement); + RefPtr anchorPositionedElement = builderState.element(); + return AnchorPositionEvaluator::resolveAnchorValue(primitiveValue.cssAnchorValue(), anchorPositionedElement.get()); } ASSERT_NOT_REACHED(); @@ -756,10 +757,10 @@ inline RefPtr BuilderConverter::convertRayPathOperation(BuilderSt return RayPathOperation::create(rayValue.angle()->computeDegrees(), size, rayValue.isContaining()); } -inline RefPtr BuilderConverter::convertSVGPath(BuilderState& builderState, const CSSValue& value) +inline RefPtr BuilderConverter::convertSVGPath(BuilderState&, const CSSValue& value) { if (auto* pathValue = dynamicDowncast(value)) - return basicShapePathForValue(*pathValue, builderState.style().usedZoom()); + return basicShapePathForValue(*pathValue); ASSERT(is(value)); ASSERT(downcast(value).valueID() == CSSValueNone); @@ -1312,12 +1313,12 @@ inline void BuilderConverter::createImplicitNamedGridLinesFromGridArea(const Nam for (auto& area : namedGridAreas.map) { GridSpan areaSpan = direction == GridTrackSizingDirection::ForRows ? area.value.rows : area.value.columns; { - auto& startVector = namedGridLines.map.add(area.key + "-start"_s, Vector()).iterator->value; + auto& startVector = namedGridLines.map.add(makeString(area.key, "-start"_s), Vector()).iterator->value; startVector.append(areaSpan.startLine()); std::sort(startVector.begin(), startVector.end()); } { - auto& endVector = namedGridLines.map.add(area.key + "-end"_s, Vector()).iterator->value; + auto& endVector = namedGridLines.map.add(makeString(area.key, "-end"_s), Vector()).iterator->value; endVector.append(areaSpan.endLine()); std::sort(endVector.begin(), endVector.end()); } diff --git a/Source/WebCore/style/StyleInvalidator.cpp b/Source/WebCore/style/StyleInvalidator.cpp index 59e625941740c..733341a7ac898 100644 --- a/Source/WebCore/style/StyleInvalidator.cpp +++ b/Source/WebCore/style/StyleInvalidator.cpp @@ -163,7 +163,8 @@ Invalidator::CheckDescendants Invalidator::invalidateIfNeeded(Element& element, switch (element.styleValidity()) { case Validity::Valid: - case Validity::AnimationInvalid: { + case Validity::AnimationInvalid: + case Validity::InlineStyleInvalid: { for (auto& ruleSet : m_ruleSets) { ElementRuleCollector ruleCollector(element, *ruleSet, selectorMatchingState); ruleCollector.setMode(SelectorChecker::Mode::CollectingRulesIgnoringVirtualPseudoElements); diff --git a/Source/WebCore/style/StyleRelations.cpp b/Source/WebCore/style/StyleRelations.cpp index 37225df8f1f62..c624aee2ada98 100644 --- a/Source/WebCore/style/StyleRelations.cpp +++ b/Source/WebCore/style/StyleRelations.cpp @@ -147,5 +147,17 @@ void commitRelations(std::unique_ptr relations, Update& update) } } +void copyRelations(RenderStyle& to, const RenderStyle& from) +{ + if (from.emptyState()) + to.setEmptyState(true); + if (from.firstChildState()) + to.setFirstChildState(); + if (from.lastChildState()) + to.setLastChildState(); + if (from.unique()) + to.setUnique(); +} + } } diff --git a/Source/WebCore/style/StyleRelations.h b/Source/WebCore/style/StyleRelations.h index 4bde0682b4259..3cda10b51fd5c 100644 --- a/Source/WebCore/style/StyleRelations.h +++ b/Source/WebCore/style/StyleRelations.h @@ -69,6 +69,7 @@ struct Relation { using Relations = Vector; std::unique_ptr commitRelationsToRenderStyle(RenderStyle&, const Element&, const Relations&); +void copyRelations(RenderStyle&, const RenderStyle&); void commitRelations(std::unique_ptr, Update&); } diff --git a/Source/WebCore/style/StyleResolver.cpp b/Source/WebCore/style/StyleResolver.cpp index 345046f383497..2b169db2c0a98 100644 --- a/Source/WebCore/style/StyleResolver.cpp +++ b/Source/WebCore/style/StyleResolver.cpp @@ -239,19 +239,9 @@ void Resolver::addKeyframeStyle(Ref&& rule) document().keyframesRuleDidChange(animationName); } -BuilderContext Resolver::builderContext(const State& state) -{ - return { - document(), - *state.parentStyle(), - state.rootElementStyle(), - state.element() - }; -} - -ResolvedStyle Resolver::styleForElement(Element& element, const ResolutionContext& context, RuleMatchingBehavior matchingBehavior) +auto Resolver::initializeStateAndStyle(const Element& element, const ResolutionContext& context) -> State { - auto state = State(element, context.parentStyle, context.documentElementStyle); + auto state = State { element, context.parentStyle, context.documentElementStyle }; if (state.parentStyle()) { state.setStyle(RenderStyle::createPtrWithRegisteredInitialValues(document().customPropertyRegistry())); @@ -265,9 +255,8 @@ ResolvedStyle Resolver::styleForElement(Element& element, const ResolutionContex state.setParentStyle(RenderStyle::clonePtr(*state.style())); } - auto& style = *state.style(); - if (element.isLink()) { + auto& style = *state.style(); style.setIsLink(true); InsideLink linkState = document().visitedLinkState().determineLinkState(element); if (linkState != InsideLink::NotInside) { @@ -278,6 +267,24 @@ ResolvedStyle Resolver::styleForElement(Element& element, const ResolutionContex style.setInsideLink(linkState); } + return state; +} + +BuilderContext Resolver::builderContext(const State& state) +{ + return { + document(), + *state.parentStyle(), + state.rootElementStyle(), + state.element() + }; +} + +ResolvedStyle Resolver::styleForElement(Element& element, const ResolutionContext& context, RuleMatchingBehavior matchingBehavior) +{ + auto state = initializeStateAndStyle(element, context); + auto& style = *state.style(); + UserAgentStyle::ensureDefaultStyleSheetsForElement(element); ElementRuleCollector collector(element, m_ruleSets, context.selectorMatchingState); @@ -300,14 +307,33 @@ ResolvedStyle Resolver::styleForElement(Element& element, const ResolutionContex applyMatchedProperties(state, collector.matchResult()); Adjuster adjuster(document(), *state.parentStyle(), context.parentBoxStyle, &element); - adjuster.adjust(*state.style(), state.userAgentAppearanceStyle()); + adjuster.adjust(style, state.userAgentAppearanceStyle()); - if (state.style()->usesViewportUnits()) + if (style.usesViewportUnits()) document().setHasStyleWithViewportUnits(); return { state.takeStyle(), WTFMove(elementStyleRelations), collector.releaseMatchResult() }; } +ResolvedStyle Resolver::styleForElementWithCachedMatchResult(Element& element, const ResolutionContext& context, const MatchResult& matchResult, const RenderStyle& existingRenderStyle) +{ + auto state = initializeStateAndStyle(element, context); + auto& style = *state.style(); + + style.copyPseudoElementBitsFrom(existingRenderStyle); + copyRelations(style, existingRenderStyle); + + applyMatchedProperties(state, matchResult); + + Adjuster adjuster(document(), *state.parentStyle(), context.parentBoxStyle, &element); + adjuster.adjust(style, state.userAgentAppearanceStyle()); + + if (style.usesViewportUnits()) + document().setHasStyleWithViewportUnits(); + + return { state.takeStyle(), { }, makeUnique(matchResult) }; +} + std::unique_ptr Resolver::styleForKeyframe(Element& element, const RenderStyle& elementStyle, const ResolutionContext& context, const StyleRuleKeyframe& keyframe, BlendingKeyframe& blendingKeyframe) { // Add all the animating properties to the keyframe. diff --git a/Source/WebCore/style/StyleResolver.h b/Source/WebCore/style/StyleResolver.h index dc6d46908120c..4eb5a72e76664 100644 --- a/Source/WebCore/style/StyleResolver.h +++ b/Source/WebCore/style/StyleResolver.h @@ -92,6 +92,7 @@ class Resolver : public RefCounted, public CanMakeSingleThreadWeakPtr< ~Resolver(); ResolvedStyle styleForElement(Element&, const ResolutionContext&, RuleMatchingBehavior = RuleMatchingBehavior::MatchAllRules); + ResolvedStyle styleForElementWithCachedMatchResult(Element&, const ResolutionContext&, const MatchResult&, const RenderStyle& existingRenderStyle); void keyframeStylesForAnimation(Element&, const RenderStyle& elementStyle, const ResolutionContext&, BlendingKeyframes&); @@ -161,6 +162,7 @@ class Resolver : public RefCounted, public CanMakeSingleThreadWeakPtr< class State; + State initializeStateAndStyle(const Element&, const ResolutionContext&); BuilderContext builderContext(const State&); void applyMatchedProperties(State&, const MatchResult&); diff --git a/Source/WebCore/style/StyleScope.cpp b/Source/WebCore/style/StyleScope.cpp index feb2518899e2d..bd245f459e41d 100644 --- a/Source/WebCore/style/StyleScope.cpp +++ b/Source/WebCore/style/StyleScope.cpp @@ -207,6 +207,7 @@ void Scope::releaseMemory() clearResolver(); m_sharedShadowTreeResolvers.clear(); + m_cachedMatchResults.clear(); } Scope& Scope::forNode(Node& node) @@ -722,6 +723,8 @@ void Scope::scheduleUpdate(UpdateType update) // FIXME: The m_isUpdatingStyleResolver test is here because extension stylesheets can get us here from Resolver::appendAuthorStyleSheets. if (!m_isUpdatingStyleResolver && !m_document.isResolvingTreeStyle()) clearResolver(); + + m_cachedMatchResults.clear(); } if (!m_pendingUpdate || *m_pendingUpdate < update) { @@ -962,6 +965,47 @@ bool Scope::updateQueryContainerState(QueryContainerUpdateContext& context) return !containersToInvalidate.isEmpty(); } +const MatchResult* Scope::cachedMatchResult(const Element& element) +{ + auto it = m_cachedMatchResults.find(element); + if (it == m_cachedMatchResults.end()) + return { }; + + auto& matchResult = *it->value; + + auto inlineStyleMatches = [&] { + auto* styledElement = dynamicDowncast(element); + if (!styledElement || !styledElement->inlineStyle()) + return false; + + auto& inlineStyle = *styledElement->inlineStyle(); + + for (auto& declaration : matchResult.authorDeclarations) { + if (&declaration.properties.get() == &inlineStyle) + return true; + } + return false; + }(); + + if (!inlineStyleMatches) { + m_cachedMatchResults.remove(it); + return { }; + } + + return &matchResult; +} + +void Scope::updateCachedMatchResult(const Element& element, const MatchResult& matchResult) +{ + // For now we cache match results if there is mutable inline style. This way we can avoid + // selector matching when it gets mutated again. + auto* styledElement = dynamicDowncast(element); + if (styledElement && styledElement->inlineStyle() && styledElement->inlineStyle()->isMutable()) + m_cachedMatchResults.set(element, makeUniqueRef(matchResult)); + else + m_cachedMatchResults.remove(element); +} + HTMLSlotElement* assignedSlotForScopeOrdinal(const Element& element, ScopeOrdinal scopeOrdinal) { ASSERT(scopeOrdinal >= ScopeOrdinal::FirstSlot); diff --git a/Source/WebCore/style/StyleScope.h b/Source/WebCore/style/StyleScope.h index 232b6588200c1..9937851d680d0 100644 --- a/Source/WebCore/style/StyleScope.h +++ b/Source/WebCore/style/StyleScope.h @@ -67,6 +67,7 @@ namespace Style { class CustomPropertyRegistry; class Resolver; class RuleSet; +struct MatchResult; class Scope final : public CanMakeWeakPtr, public CanMakeCheckedPtr { WTF_MAKE_FAST_ALLOCATED; @@ -134,6 +135,9 @@ class Scope final : public CanMakeWeakPtr, public CanMakeCheckedPtr, public CanMakeCheckedPtr m_viewportStateOnPreviousMediaQueryEvaluation; WeakHashMap m_queryContainerStates; + mutable WeakHashMap, WeakPtrImplWithEventTargetData> m_cachedMatchResults; UniqueRef m_customPropertyRegistry; UniqueRef m_counterStyleRegistry; diff --git a/Source/WebCore/style/StyleTreeResolver.cpp b/Source/WebCore/style/StyleTreeResolver.cpp index 9bac4a012daed..4d9d099dd7b46 100644 --- a/Source/WebCore/style/StyleTreeResolver.cpp +++ b/Source/WebCore/style/StyleTreeResolver.cpp @@ -134,7 +134,7 @@ void TreeResolver::popScope() return m_scopeStack.removeLast(); } -ResolvedStyle TreeResolver::styleForStyleable(const Styleable& styleable, ResolutionType resolutionType, const ResolutionContext& resolutionContext) +ResolvedStyle TreeResolver::styleForStyleable(const Styleable& styleable, ResolutionType resolutionType, const ResolutionContext& resolutionContext, const RenderStyle* existingStyle) { if (resolutionType == ResolutionType::AnimationOnly && styleable.lastStyleChangeEventStyle() && !styleable.hasPropertiesOverridenAfterAnimation()) return { RenderStyle::clonePtr(*styleable.lastStyleChangeEventStyle()) }; @@ -153,7 +153,7 @@ ResolvedStyle TreeResolver::styleForStyleable(const Styleable& styleable, Resolu if (resolutionType == ResolutionType::FastPathInherit) { // If the only reason we are computing the style is that some parent inherited properties changed, we can just copy them. - auto style = RenderStyle::clonePtr(*existingStyle(element)); + auto style = RenderStyle::clonePtr(*existingStyle); style->fastPathInheritFrom(parent().style); return { WTFMove(style) }; } @@ -161,11 +161,18 @@ ResolvedStyle TreeResolver::styleForStyleable(const Styleable& styleable, Resolu if (auto style = scope().sharingResolver.resolve(styleable, *m_update)) return { WTFMove(style) }; + if (resolutionType == ResolutionType::FullWithMatchResultCache) { + if (auto cachedMatchResult = m_document.styleScope().cachedMatchResult(element)) + return scope().resolver->styleForElementWithCachedMatchResult(element, resolutionContext, *cachedMatchResult, *existingStyle); + } + auto elementStyle = scope().resolver->styleForElement(element, resolutionContext); if (elementStyle.relations) commitRelations(WTFMove(elementStyle.relations), *m_update); + m_document.styleScope().updateCachedMatchResult(element, *elementStyle.matchResult); + return elementStyle; } @@ -263,7 +270,7 @@ auto TreeResolver::resolveElement(Element& element, const RenderStyle* existingS auto resolutionContext = makeResolutionContext(); Styleable styleable { element, { } }; - auto resolvedStyle = styleForStyleable(styleable, resolutionType, resolutionContext); + auto resolvedStyle = styleForStyleable(styleable, resolutionType, resolutionContext, existingStyle); auto update = createAnimatedElementUpdate(WTFMove(resolvedStyle), styleable, parent().change, resolutionContext); if (!affectsRenderedSubtree(element, *update.style)) { @@ -919,6 +926,8 @@ auto TreeResolver::determineResolutionType(const Element& element, const RenderS return ResolutionType::AnimationOnly; if (combinedValidity == Validity::Valid && element.hasInvalidRenderer()) return existingStyle ? ResolutionType::RebuildUsingExisting : ResolutionType::Full; + if (combinedValidity == Validity::InlineStyleInvalid && existingStyle) + return ResolutionType::FullWithMatchResultCache; } if (combinedValidity > Validity::Valid) @@ -1060,7 +1069,7 @@ void TreeResolver::resolveComposedTree() if (resolutionType) { element.resetComputedStyle(); - if (*resolutionType != ResolutionType::AnimationOnly) + if (*resolutionType == ResolutionType::Full) element.resetStyleRelations(); if (element.hasCustomStyleResolveCallbacks()) diff --git a/Source/WebCore/style/StyleTreeResolver.h b/Source/WebCore/style/StyleTreeResolver.h index 15f634750e9f3..2c04ce042b8da 100644 --- a/Source/WebCore/style/StyleTreeResolver.h +++ b/Source/WebCore/style/StyleTreeResolver.h @@ -63,8 +63,8 @@ class TreeResolver { bool hasUnresolvedAnchorPositionedElements() const { return m_hasUnresolvedAnchorPositionedElements; } private: - enum class ResolutionType : uint8_t { RebuildUsingExisting, AnimationOnly, FastPathInherit, Full }; - ResolvedStyle styleForStyleable(const Styleable&, ResolutionType, const ResolutionContext&); + enum class ResolutionType : uint8_t { RebuildUsingExisting, AnimationOnly, FastPathInherit, FullWithMatchResultCache, Full }; + ResolvedStyle styleForStyleable(const Styleable&, ResolutionType, const ResolutionContext&, const RenderStyle* existingStyle); void resolveComposedTree(); diff --git a/Source/WebCore/style/StyleValidity.h b/Source/WebCore/style/StyleValidity.h index f98eb28086229..36a9d44a2cc34 100644 --- a/Source/WebCore/style/StyleValidity.h +++ b/Source/WebCore/style/StyleValidity.h @@ -31,6 +31,7 @@ namespace Style { enum class Validity : uint8_t { Valid, AnimationInvalid, + InlineStyleInvalid, ElementInvalid, SubtreeInvalid, }; diff --git a/Source/WebCore/style/UserAgentStyle.cpp b/Source/WebCore/style/UserAgentStyle.cpp index 68ac70afd7264..01992610a5aa1 100644 --- a/Source/WebCore/style/UserAgentStyle.cpp +++ b/Source/WebCore/style/UserAgentStyle.cpp @@ -65,6 +65,7 @@ #include "StyleSheetContents.h" #include "UserAgentStyleSheets.h" #include +#include namespace WebCore { namespace Style { @@ -205,7 +206,7 @@ void UserAgentStyle::ensureDefaultStyleSheetsForElement(const Element& element) if (is(element)) { if (is(element) || is(element)) { if (!plugInsStyleSheet && element.document().page()) { - String plugInsRules = RenderTheme::singleton().extraPlugInsStyleSheet() + element.document().page()->chrome().client().plugInExtraStyleSheet(); + auto plugInsRules = makeString(RenderTheme::singleton().extraPlugInsStyleSheet(), element.document().page()->chrome().client().plugInExtraStyleSheet()); if (plugInsRules.isEmpty()) plugInsRules = String(StringImpl::createWithoutCopying(plugInsUserAgentStyleSheet)); plugInsStyleSheet = parseUASheet(plugInsRules); @@ -217,10 +218,9 @@ void UserAgentStyle::ensureDefaultStyleSheetsForElement(const Element& element) if (!mediaControlsStyleSheet) { String mediaRules = RenderTheme::singleton().mediaControlsStyleSheet(); if (mediaRules.isEmpty()) - mediaRules = String(StringImpl::createWithoutCopying(mediaControlsUserAgentStyleSheet)) + RenderTheme::singleton().extraMediaControlsStyleSheet(); + mediaRules = makeString(String(StringImpl::createWithoutCopying(mediaControlsUserAgentStyleSheet)), RenderTheme::singleton().extraMediaControlsStyleSheet()); mediaControlsStyleSheet = parseUASheet(mediaRules); addToDefaultStyle(*mediaControlsStyleSheet); - } } #endif // ENABLE(VIDEO) && !ENABLE(MODERN_MEDIA_CONTROLS) diff --git a/Source/WebCore/svg/SVGAngleValue.cpp b/Source/WebCore/svg/SVGAngleValue.cpp index 79afe080dbf45..5b3ce7016b6fb 100644 --- a/Source/WebCore/svg/SVGAngleValue.cpp +++ b/Source/WebCore/svg/SVGAngleValue.cpp @@ -25,7 +25,7 @@ #include "SVGParserUtilities.h" #include #include -#include +#include #include namespace WebCore { diff --git a/Source/WebCore/svg/SVGAnimateTransformElement.cpp b/Source/WebCore/svg/SVGAnimateTransformElement.cpp index 31758ebab6d0d..eb875cf12a6be 100644 --- a/Source/WebCore/svg/SVGAnimateTransformElement.cpp +++ b/Source/WebCore/svg/SVGAnimateTransformElement.cpp @@ -67,7 +67,7 @@ void SVGAnimateTransformElement::attributeChanged(const QualifiedName& name, con String SVGAnimateTransformElement::animateRangeString(const String& string) const { - return SVGTransformValue::prefixForTransformType(m_type) + string + ')'; + return makeString(SVGTransformValue::prefixForTransformType(m_type), string, ')'); } } diff --git a/Source/WebCore/svg/SVGDocumentExtensions.cpp b/Source/WebCore/svg/SVGDocumentExtensions.cpp index f5624614b02c5..6cc200e589746 100644 --- a/Source/WebCore/svg/SVGDocumentExtensions.cpp +++ b/Source/WebCore/svg/SVGDocumentExtensions.cpp @@ -39,6 +39,7 @@ #include "ScriptableDocumentParser.h" #include "ShadowRoot.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/svg/SVGElement.cpp b/Source/WebCore/svg/SVGElement.cpp index 8454a93b58f0b..b32626cd86ffc 100644 --- a/Source/WebCore/svg/SVGElement.cpp +++ b/Source/WebCore/svg/SVGElement.cpp @@ -65,6 +65,7 @@ #include #include #include +#include namespace WebCore { @@ -840,7 +841,7 @@ bool SVGElement::rendererIsNeeded(const RenderStyle& style) return false; } -CSSPropertyID SVGElement::cssPropertyIdForSVGAttributeName(const QualifiedName& attrName) +CSSPropertyID SVGElement::cssPropertyIdForSVGAttributeName(const QualifiedName& attrName, const Settings& settings) { if (!attrName.namespaceURI().isNull()) return CSSPropertyInvalid; @@ -870,6 +871,10 @@ CSSPropertyID SVGElement::cssPropertyIdForSVGAttributeName(const QualifiedName& return CSSPropertyCx; case AttributeNames::cyAttr: return CSSPropertyCy; + case AttributeNames::dAttr: + if (settings.cssDPropertyEnabled()) + return CSSPropertyD; + break; case AttributeNames::directionAttr: return CSSPropertyDirection; case AttributeNames::displayAttr: @@ -995,14 +1000,14 @@ CSSPropertyID SVGElement::cssPropertyIdForSVGAttributeName(const QualifiedName& bool SVGElement::hasPresentationalHintsForAttribute(const QualifiedName& name) const { - if (cssPropertyIdForSVGAttributeName(name) > 0) + if (cssPropertyIdForSVGAttributeName(name, document().settings()) > 0) return true; return StyledElement::hasPresentationalHintsForAttribute(name); } void SVGElement::collectPresentationalHintsForAttribute(const QualifiedName& name, const AtomString& value, MutableStyleProperties& style) { - CSSPropertyID propertyID = cssPropertyIdForSVGAttributeName(name); + CSSPropertyID propertyID = cssPropertyIdForSVGAttributeName(name, document().settings()); if (propertyID > 0) addPropertyToPresentationalHintStyle(style, propertyID, value); } @@ -1015,7 +1020,7 @@ void SVGElement::updateSVGRendererForElementChange() void SVGElement::svgAttributeChanged(const QualifiedName& attrName) { - CSSPropertyID propId = cssPropertyIdForSVGAttributeName(attrName); + CSSPropertyID propId = cssPropertyIdForSVGAttributeName(attrName, document().settings()); if (propId > 0) { invalidateInstances(); return; diff --git a/Source/WebCore/svg/SVGElement.h b/Source/WebCore/svg/SVGElement.h index d8d7e40bbcb4b..d58e68993f07b 100644 --- a/Source/WebCore/svg/SVGElement.h +++ b/Source/WebCore/svg/SVGElement.h @@ -45,6 +45,7 @@ class SVGPropertyAnimatorFactory; class SVGResourceElementClient; class SVGSVGElement; class SVGUseElement; +class Settings; class Timer; class SVGElement : public StyledElement, public SVGPropertyOwner { @@ -181,7 +182,7 @@ class SVGElement : public StyledElement, public SVGPropertyOwner { SVGElementRareData& ensureSVGRareData(); void reportAttributeParsingError(SVGParsingError, const QualifiedName&, const AtomString&); - static CSSPropertyID cssPropertyIdForSVGAttributeName(const QualifiedName&); + static CSSPropertyID cssPropertyIdForSVGAttributeName(const QualifiedName&, const Settings&); bool hasPresentationalHintsForAttribute(const QualifiedName&) const override; void collectPresentationalHintsForAttribute(const QualifiedName&, const AtomString&, MutableStyleProperties&) override; diff --git a/Source/WebCore/svg/SVGFEConvolveMatrixElement.cpp b/Source/WebCore/svg/SVGFEConvolveMatrixElement.cpp index b164ab13559d2..4ea777d5a0b37 100644 --- a/Source/WebCore/svg/SVGFEConvolveMatrixElement.cpp +++ b/Source/WebCore/svg/SVGFEConvolveMatrixElement.cpp @@ -29,6 +29,7 @@ #include "SVGNames.h" #include "SVGParserUtilities.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/svg/SVGFEGaussianBlurElement.cpp b/Source/WebCore/svg/SVGFEGaussianBlurElement.cpp index d4b1818d6ab58..f006b406767ff 100644 --- a/Source/WebCore/svg/SVGFEGaussianBlurElement.cpp +++ b/Source/WebCore/svg/SVGFEGaussianBlurElement.cpp @@ -30,6 +30,7 @@ #include "SVGNames.h" #include "SVGParserUtilities.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/svg/SVGFitToViewBox.cpp b/Source/WebCore/svg/SVGFitToViewBox.cpp index cc0ae06d450ec..5e447880fc185 100644 --- a/Source/WebCore/svg/SVGFitToViewBox.cpp +++ b/Source/WebCore/svg/SVGFitToViewBox.cpp @@ -30,6 +30,7 @@ #include "SVGNames.h" #include "SVGParserUtilities.h" #include "SVGPreserveAspectRatioValue.h" +#include #include #include diff --git a/Source/WebCore/svg/SVGFontFaceElement.cpp b/Source/WebCore/svg/SVGFontFaceElement.cpp index 6d6ff0087eeba..8441624eb12b0 100644 --- a/Source/WebCore/svg/SVGFontFaceElement.cpp +++ b/Source/WebCore/svg/SVGFontFaceElement.cpp @@ -80,7 +80,7 @@ Ref SVGFontFaceElement::protectedFontFaceRule() const void SVGFontFaceElement::attributeChanged(const QualifiedName& name, const AtomString& oldValue, const AtomString& newValue, AttributeModificationReason attributeModificationReason) { - CSSPropertyID propertyId = cssPropertyIdForSVGAttributeName(name); + CSSPropertyID propertyId = cssPropertyIdForSVGAttributeName(name, document().settings()); if (propertyId > 0) { // FIXME: Parse using the @font-face descriptor grammars, not the property grammars. Ref fontFaceRule = m_fontFaceRule; diff --git a/Source/WebCore/svg/SVGLengthValue.cpp b/Source/WebCore/svg/SVGLengthValue.cpp index 2c1e79b4655dd..890fcb60ef104 100644 --- a/Source/WebCore/svg/SVGLengthValue.cpp +++ b/Source/WebCore/svg/SVGLengthValue.cpp @@ -27,7 +27,7 @@ #include "SVGLengthContext.h" #include "SVGParserUtilities.h" #include -#include +#include #include #include diff --git a/Source/WebCore/svg/SVGPathByteStreamBuilder.cpp b/Source/WebCore/svg/SVGPathByteStreamBuilder.cpp index a4b77b5705d20..0803dcc9c6192 100644 --- a/Source/WebCore/svg/SVGPathByteStreamBuilder.cpp +++ b/Source/WebCore/svg/SVGPathByteStreamBuilder.cpp @@ -23,7 +23,6 @@ #include "SVGPathSeg.h" #include "SVGPathStringViewSource.h" -#include namespace WebCore { diff --git a/Source/WebCore/svg/SVGPathByteStreamSource.cpp b/Source/WebCore/svg/SVGPathByteStreamSource.cpp index 60420b69d490f..993295989beff 100644 --- a/Source/WebCore/svg/SVGPathByteStreamSource.cpp +++ b/Source/WebCore/svg/SVGPathByteStreamSource.cpp @@ -19,7 +19,6 @@ #include "config.h" #include "SVGPathByteStreamSource.h" -#include namespace WebCore { diff --git a/Source/WebCore/svg/SVGPathElement.cpp b/Source/WebCore/svg/SVGPathElement.cpp index 71c376ed98a0d..0d894caedae6e 100644 --- a/Source/WebCore/svg/SVGPathElement.cpp +++ b/Source/WebCore/svg/SVGPathElement.cpp @@ -22,6 +22,7 @@ #include "config.h" #include "SVGPathElement.h" +#include "CSSBasicShapes.h" #include "LegacyRenderSVGPath.h" #include "LegacyRenderSVGResource.h" #include "RenderSVGPath.h" @@ -31,7 +32,9 @@ #include "SVGNames.h" #include "SVGPathUtilities.h" #include "SVGPoint.h" +#include "SVGRenderStyle.h" #include +#include namespace WebCore { @@ -142,6 +145,8 @@ void SVGPathElement::svgAttributeChanged(const QualifiedName& attrName) path->setNeedsShapeUpdate(); updateSVGRendererForElementChange(); + if (document().settings().cssDPropertyEnabled()) + setPresentationalHintStyleIsDirty(); invalidateResourceImageBuffersIfNeeded(); return; } @@ -174,11 +179,15 @@ void SVGPathElement::removedFromAncestor(RemovalType removalType, ContainerNode& float SVGPathElement::getTotalLength() const { + protectedDocument()->updateLayoutIgnorePendingStylesheets({ LayoutOptions::ContentVisibilityForceLayout }, this); + return getTotalLengthOfSVGPathByteStream(pathByteStream()); } ExceptionOr> SVGPathElement::getPointAtLength(float distance) const { + protectedDocument()->updateLayoutIgnorePendingStylesheets({ LayoutOptions::ContentVisibilityForceLayout }, this); + // Spec: Clamp distance to [0, length]. distance = clampTo(distance, 0, getTotalLength()); @@ -188,6 +197,8 @@ ExceptionOr> SVGPathElement::getPointAtLength(float distance) cons unsigned SVGPathElement::getPathSegAtLength(float length) const { + protectedDocument()->updateLayoutIgnorePendingStylesheets({ LayoutOptions::ContentVisibilityForceLayout }, this); + return getSVGPathSegAtLengthFromSVGPathByteStream(pathByteStream(), length); } @@ -216,4 +227,44 @@ RenderPtr SVGPathElement::createElementRenderer(RenderStyle&& sty return createRenderer(*this, WTFMove(style)); } +const SVGPathByteStream& SVGPathElement::pathByteStream() const +{ + if (CheckedPtr renderer = this->renderer()) { + if (RefPtr basicShapePath = renderer->style().d()) { + if (WeakPtr pathData = basicShapePath->pathData()) + return *pathData; + } + } + + return Ref { m_pathSegList }->currentPathByteStream(); +} + +Path SVGPathElement::path() const +{ + if (CheckedPtr renderer = this->renderer()) { + if (RefPtr basicShapePath = renderer->style().d()) + return basicShapePath->path({ }); + } + + return Ref { m_pathSegList }->currentPath(); +} + +void SVGPathElement::collectPresentationalHintsForAttribute(const QualifiedName& name, const AtomString& value, MutableStyleProperties& style) +{ + if (name == SVGNames::dAttr && document().settings().cssDPropertyEnabled()) { + // In the case of the `d` property, we want to avoid providing a string value since it will require + // the path data to be parsed again and path data can be unwieldy. + auto property = cssPropertyIdForSVGAttributeName(name, document().protectedSettings()); + // The WindRule value passed here is not relevant for the `d` property. + auto cssPathValue = CSSPathValue::create(Ref { m_pathSegList }->currentPathByteStream(), WindRule::NonZero); + addPropertyToPresentationalHintStyle(style, property, WTFMove(cssPathValue)); + } else + SVGGeometryElement::collectPresentationalHintsForAttribute(name, value, style); +} + +void SVGPathElement::pathDidChange() +{ + invalidateMPathDependencies(); +} + } diff --git a/Source/WebCore/svg/SVGPathElement.h b/Source/WebCore/svg/SVGPathElement.h index 7a5b731fc18aa..3da599a6f4167 100644 --- a/Source/WebCore/svg/SVGPathElement.h +++ b/Source/WebCore/svg/SVGPathElement.h @@ -96,10 +96,12 @@ class SVGPathElement final : public SVGGeometryElement { Ref& pathSegList() { return m_pathSegList->baseVal(); } RefPtr& animatedPathSegList() { return m_pathSegList->animVal(); } - const SVGPathByteStream& pathByteStream() const { return m_pathSegList->currentPathByteStream(); } - Path path() const { return m_pathSegList->currentPath(); } + const SVGPathByteStream& pathByteStream() const; + Path path() const; size_t approximateMemoryCost() const final { return m_pathSegList->approximateMemoryCost(); } + void pathDidChange(); + static void clearCache(); private: @@ -120,6 +122,8 @@ class SVGPathElement final : public SVGGeometryElement { void invalidateMPathDependencies(); + void collectPresentationalHintsForAttribute(const QualifiedName&, const AtomString&, MutableStyleProperties&) final; + private: Ref m_pathSegList { SVGAnimatedPathSegList::create(this) }; }; diff --git a/Source/WebCore/svg/SVGPathSegListBuilder.cpp b/Source/WebCore/svg/SVGPathSegListBuilder.cpp index 8d433cdfe2bb2..dcd2a18154d9e 100644 --- a/Source/WebCore/svg/SVGPathSegListBuilder.cpp +++ b/Source/WebCore/svg/SVGPathSegListBuilder.cpp @@ -27,7 +27,6 @@ #include "SVGPathSegImpl.h" #include "SVGPathSegList.h" -#include namespace WebCore { diff --git a/Source/WebCore/svg/SVGPathSegListSource.cpp b/Source/WebCore/svg/SVGPathSegListSource.cpp index 5bc0610d4461c..3bde5c38a3043 100644 --- a/Source/WebCore/svg/SVGPathSegListSource.cpp +++ b/Source/WebCore/svg/SVGPathSegListSource.cpp @@ -24,7 +24,6 @@ #include "SVGPathSeg.h" #include "SVGPathSegList.h" #include "SVGPathSegValue.h" -#include namespace WebCore { diff --git a/Source/WebCore/svg/SVGPolyElement.cpp b/Source/WebCore/svg/SVGPolyElement.cpp index d5fbe79060d94..a355c4e46fd73 100644 --- a/Source/WebCore/svg/SVGPolyElement.cpp +++ b/Source/WebCore/svg/SVGPolyElement.cpp @@ -29,6 +29,7 @@ #include "SVGDocumentExtensions.h" #include "SVGParserUtilities.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/svg/SVGPreserveAspectRatioValue.cpp b/Source/WebCore/svg/SVGPreserveAspectRatioValue.cpp index af285d93aeaf7..35537931f7cd1 100644 --- a/Source/WebCore/svg/SVGPreserveAspectRatioValue.cpp +++ b/Source/WebCore/svg/SVGPreserveAspectRatioValue.cpp @@ -27,7 +27,7 @@ #include "AffineTransform.h" #include "FloatRect.h" #include "SVGParserUtilities.h" -#include +#include #include #include diff --git a/Source/WebCore/svg/SVGZoomAndPan.cpp b/Source/WebCore/svg/SVGZoomAndPan.cpp index e10e688378a85..265795da37832 100644 --- a/Source/WebCore/svg/SVGZoomAndPan.cpp +++ b/Source/WebCore/svg/SVGZoomAndPan.cpp @@ -22,7 +22,6 @@ #include "config.h" #include "SVGZoomAndPan.h" -#include #include namespace WebCore { diff --git a/Source/WebCore/svg/properties/SVGAnimatedPropertyPairAccessorImpl.h b/Source/WebCore/svg/properties/SVGAnimatedPropertyPairAccessorImpl.h index 71d0fb4c93636..d2ea2e709e0ea 100644 --- a/Source/WebCore/svg/properties/SVGAnimatedPropertyPairAccessorImpl.h +++ b/Source/WebCore/svg/properties/SVGAnimatedPropertyPairAccessorImpl.h @@ -31,6 +31,7 @@ #include "SVGAnimatedPropertyPairAccessor.h" #include "SVGAnimatedPropertyPairAnimatorImpl.h" #include "SVGNames.h" +#include namespace WebCore { diff --git a/Source/WebCore/svg/properties/SVGPropertyTraits.h b/Source/WebCore/svg/properties/SVGPropertyTraits.h index 41da89dffe11e..c7851b99cb287 100644 --- a/Source/WebCore/svg/properties/SVGPropertyTraits.h +++ b/Source/WebCore/svg/properties/SVGPropertyTraits.h @@ -29,6 +29,7 @@ #include "FloatRect.h" #include "QualifiedName.h" #include "SVGParserUtilities.h" +#include namespace WebCore { diff --git a/Source/WebCore/testing/Internals.cpp b/Source/WebCore/testing/Internals.cpp index 064dc2845ba95..2512cc6fe18ff 100644 --- a/Source/WebCore/testing/Internals.cpp +++ b/Source/WebCore/testing/Internals.cpp @@ -260,8 +260,8 @@ #include #include #include +#include #include -#include #include #if USE(CG) @@ -841,6 +841,8 @@ static String styleValidityToToString(Style::Validity validity) return "NoStyleChange"_s; case Style::Validity::AnimationInvalid: return "AnimationInvalid"_s; + case Style::Validity::InlineStyleInvalid: + return "InlineStyleInvalid"_s; case Style::Validity::ElementInvalid: return "InlineStyleChange"_s; case Style::Validity::SubtreeInvalid: @@ -6094,11 +6096,6 @@ bool Internals::isMediaStreamSourceEnded(MediaStreamTrack& track) const return track.source().isEnded(); } -bool Internals::isMediaStreamTrackPowerEfficient(const MediaStreamTrack& track) const -{ - return track.source().isPowerEfficient(); -} - bool Internals::isMockRealtimeMediaSourceCenterEnabled() { return MockRealtimeMediaSourceCenter::mockRealtimeMediaSourceCenterEnabled(); diff --git a/Source/WebCore/testing/Internals.h b/Source/WebCore/testing/Internals.h index ce4ab82ba84ef..8a539475a7278 100644 --- a/Source/WebCore/testing/Internals.h +++ b/Source/WebCore/testing/Internals.h @@ -981,7 +981,6 @@ class Internals final void setMediaStreamSourceInterrupted(MediaStreamTrack&, bool); bool isMediaStreamSourceInterrupted(MediaStreamTrack&) const; bool isMediaStreamSourceEnded(MediaStreamTrack&) const; - bool isMediaStreamTrackPowerEfficient(const MediaStreamTrack&) const; bool isMockRealtimeMediaSourceCenterEnabled(); bool shouldAudioTrackPlay(const AudioTrack&); #endif // ENABLE(MEDIA_STREAM) diff --git a/Source/WebCore/testing/Internals.idl b/Source/WebCore/testing/Internals.idl index afb194f5afae9..5989c946e4566 100644 --- a/Source/WebCore/testing/Internals.idl +++ b/Source/WebCore/testing/Internals.idl @@ -1103,7 +1103,6 @@ typedef (FetchRequest or FetchResponse) FetchObject; [Conditional=MEDIA_STREAM] undefined setMediaStreamSourceInterrupted(MediaStreamTrack track, boolean interrupted); [Conditional=MEDIA_STREAM] boolean isMediaStreamSourceInterrupted(MediaStreamTrack track); [Conditional=MEDIA_STREAM] boolean isMediaStreamSourceEnded(MediaStreamTrack track); - [Conditional=MEDIA_STREAM] boolean isMediaStreamTrackPowerEfficient(MediaStreamTrack track); [Conditional=MEDIA_STREAM] boolean isMockRealtimeMediaSourceCenterEnabled(); [Conditional=MEDIA_STREAM] boolean shouldAudioTrackPlay(AudioTrack track); diff --git a/Source/WebCore/testing/MockContentFilterSettings.cpp b/Source/WebCore/testing/MockContentFilterSettings.cpp index 35c2f32cad748..c393ced56a6e0 100644 --- a/Source/WebCore/testing/MockContentFilterSettings.cpp +++ b/Source/WebCore/testing/MockContentFilterSettings.cpp @@ -33,6 +33,7 @@ #include "MockContentFilter.h" #include "MockContentFilterManager.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/testing/MockPageOverlayClient.cpp b/Source/WebCore/testing/MockPageOverlayClient.cpp index 2958f45465aa0..82e00e6d46545 100644 --- a/Source/WebCore/testing/MockPageOverlayClient.cpp +++ b/Source/WebCore/testing/MockPageOverlayClient.cpp @@ -34,6 +34,7 @@ #include "PageOverlayController.h" #include "PlatformMouseEvent.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/testing/WebFakeXRDevice.cpp b/Source/WebCore/testing/WebFakeXRDevice.cpp index 266a5d275d8b2..ce035852629cc 100644 --- a/Source/WebCore/testing/WebFakeXRDevice.cpp +++ b/Source/WebCore/testing/WebFakeXRDevice.cpp @@ -161,7 +161,13 @@ void SimulatedXRDevice::frameTimerFired() for (auto& layer : m_layers) { #if PLATFORM(COCOA) PlatformXR::FrameData::LayerSetupData layerSetupData; - layerSetupData.physicalSize[0] = { static_cast(layer.value.width()), static_cast(layer.value.height()) }; + auto width = layer.value.width(); + auto height = layer.value.height(); + layerSetupData.physicalSize[0] = { static_cast(width), static_cast(height) }; + layerSetupData.viewports[0] = { 0, 0, width, height }; + layerSetupData.physicalSize[1] = { 0, 0 }; + layerSetupData.viewports[1] = { 0, 0, 0, 0 }; + auto layerData = makeUniqueRef(PlatformXR::FrameData::LayerData { .layerSetup = layerSetupData, }); diff --git a/Source/WebCore/testing/XRSimulateUserActivationFunction.idl b/Source/WebCore/testing/XRSimulateUserActivationFunction.idl index 4c8c50b6313fc..35f9ebffad9ea 100644 --- a/Source/WebCore/testing/XRSimulateUserActivationFunction.idl +++ b/Source/WebCore/testing/XRSimulateUserActivationFunction.idl @@ -24,5 +24,6 @@ [ Conditional=WEBXR, - EnabledBySetting=WebXREnabled + EnabledBySetting=WebXREnabled, + IsStrongCallback ] callback XRSimulateUserActivationFunction = undefined (); diff --git a/Source/WebCore/workers/Worker.cpp b/Source/WebCore/workers/Worker.cpp index c5d2ecc9cc813..482b94cf5e829 100644 --- a/Source/WebCore/workers/Worker.cpp +++ b/Source/WebCore/workers/Worker.cpp @@ -54,6 +54,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/workers/WorkerLocation.cpp b/Source/WebCore/workers/WorkerLocation.cpp index 08a47c1f3001a..ea2d08396c0aa 100644 --- a/Source/WebCore/workers/WorkerLocation.cpp +++ b/Source/WebCore/workers/WorkerLocation.cpp @@ -28,6 +28,7 @@ #include "SecurityOrigin.h" #include "WebCoreOpaqueRoot.h" +#include #include namespace WebCore { diff --git a/Source/WebCore/workers/WorkerScriptLoader.cpp b/Source/WebCore/workers/WorkerScriptLoader.cpp index 8d5b5ec45d2e0..b54d0244d5a60 100644 --- a/Source/WebCore/workers/WorkerScriptLoader.cpp +++ b/Source/WebCore/workers/WorkerScriptLoader.cpp @@ -45,6 +45,7 @@ #include "WorkerScriptLoaderClient.h" #include "WorkerThreadableLoader.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/workers/service/FetchEvent.cpp b/Source/WebCore/workers/service/FetchEvent.cpp index eea5c2173b9da..d1f7834991e11 100644 --- a/Source/WebCore/workers/service/FetchEvent.cpp +++ b/Source/WebCore/workers/service/FetchEvent.cpp @@ -34,6 +34,7 @@ #include "JSFetchResponse.h" #include "Logging.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/workers/service/SWClientConnection.cpp b/Source/WebCore/workers/service/SWClientConnection.cpp index 509e1f28781ab..1ccbe219b50f4 100644 --- a/Source/WebCore/workers/service/SWClientConnection.cpp +++ b/Source/WebCore/workers/service/SWClientConnection.cpp @@ -46,6 +46,7 @@ #include "WorkerGlobalScope.h" #include "WorkerSWClientConnection.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/workers/service/ServiceWorkerClients.cpp b/Source/WebCore/workers/service/ServiceWorkerClients.cpp index 584237fae7774..267a98821fa65 100644 --- a/Source/WebCore/workers/service/ServiceWorkerClients.cpp +++ b/Source/WebCore/workers/service/ServiceWorkerClients.cpp @@ -34,6 +34,7 @@ #include "ServiceWorkerGlobalScope.h" #include "ServiceWorkerThread.h" #include "WebCoreOpaqueRoot.h" +#include #include namespace WebCore { diff --git a/Source/WebCore/workers/service/ServiceWorkerJob.cpp b/Source/WebCore/workers/service/ServiceWorkerJob.cpp index 971b47dd20a7a..024a3ff315d38 100644 --- a/Source/WebCore/workers/service/ServiceWorkerJob.cpp +++ b/Source/WebCore/workers/service/ServiceWorkerJob.cpp @@ -37,6 +37,7 @@ #include "ServiceWorkerRegistration.h" #include "WorkerFetchResult.h" #include "WorkerRunLoop.h" +#include namespace WebCore { diff --git a/Source/WebCore/workers/service/ServiceWorkerJobDataIdentifier.h b/Source/WebCore/workers/service/ServiceWorkerJobDataIdentifier.h index bfcfd41a86325..584fddd6778d1 100644 --- a/Source/WebCore/workers/service/ServiceWorkerJobDataIdentifier.h +++ b/Source/WebCore/workers/service/ServiceWorkerJobDataIdentifier.h @@ -27,6 +27,8 @@ #include "ServiceWorkerTypes.h" +#include + namespace WebCore { struct ServiceWorkerJobDataIdentifier { diff --git a/Source/WebCore/workers/service/ServiceWorkerRegistrationKey.cpp b/Source/WebCore/workers/service/ServiceWorkerRegistrationKey.cpp index f1c27301000a7..3f5aa08c937ff 100644 --- a/Source/WebCore/workers/service/ServiceWorkerRegistrationKey.cpp +++ b/Source/WebCore/workers/service/ServiceWorkerRegistrationKey.cpp @@ -30,6 +30,7 @@ #include "RegistrableDomain.h" #include "SecurityOrigin.h" #include +#include #include namespace WebCore { diff --git a/Source/WebCore/workers/service/ServiceWorkerWindowClient.cpp b/Source/WebCore/workers/service/ServiceWorkerWindowClient.cpp index 9f1e62c22fac7..0f591638d7d8f 100644 --- a/Source/WebCore/workers/service/ServiceWorkerWindowClient.cpp +++ b/Source/WebCore/workers/service/ServiceWorkerWindowClient.cpp @@ -32,6 +32,7 @@ #include "ServiceWorkerClients.h" #include "ServiceWorkerGlobalScope.h" #include "ServiceWorkerThread.h" +#include namespace WebCore { diff --git a/Source/WebCore/workers/service/context/ServiceWorkerThread.cpp b/Source/WebCore/workers/service/context/ServiceWorkerThread.cpp index d59bfd17bf4fb..34eb2cc67f059 100644 --- a/Source/WebCore/workers/service/context/ServiceWorkerThread.cpp +++ b/Source/WebCore/workers/service/context/ServiceWorkerThread.cpp @@ -58,6 +58,7 @@ #include #include #include +#include using namespace PAL; diff --git a/Source/WebCore/workers/service/server/SWRegistrationDatabase.cpp b/Source/WebCore/workers/service/server/SWRegistrationDatabase.cpp index 05bf92d29c358..278f69a27ce39 100644 --- a/Source/WebCore/workers/service/server/SWRegistrationDatabase.cpp +++ b/Source/WebCore/workers/service/server/SWRegistrationDatabase.cpp @@ -42,6 +42,7 @@ #include "WorkerType.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/workers/service/server/SWServerJobQueue.cpp b/Source/WebCore/workers/service/server/SWServerJobQueue.cpp index 844375eaa3a76..d1c61270aef85 100644 --- a/Source/WebCore/workers/service/server/SWServerJobQueue.cpp +++ b/Source/WebCore/workers/service/server/SWServerJobQueue.cpp @@ -36,6 +36,7 @@ #include "ServiceWorkerUpdateViaCache.h" #include "WorkerFetchResult.h" #include "WorkerType.h" +#include namespace WebCore { diff --git a/Source/WebCore/workers/shared/SharedWorker.cpp b/Source/WebCore/workers/shared/SharedWorker.cpp index 9427bb4ecaff6..d6b53eef8f1fc 100644 --- a/Source/WebCore/workers/shared/SharedWorker.cpp +++ b/Source/WebCore/workers/shared/SharedWorker.cpp @@ -43,6 +43,7 @@ #include "WorkerOptions.h" #include #include +#include namespace WebCore { diff --git a/Source/WebCore/workers/shared/context/SharedWorkerThreadProxy.cpp b/Source/WebCore/workers/shared/context/SharedWorkerThreadProxy.cpp index 775b819c5c6a5..9862028470290 100644 --- a/Source/WebCore/workers/shared/context/SharedWorkerThreadProxy.cpp +++ b/Source/WebCore/workers/shared/context/SharedWorkerThreadProxy.cpp @@ -50,6 +50,7 @@ #include "WorkerInitializationData.h" #include "WorkerThread.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/worklets/Worklet.cpp b/Source/WebCore/worklets/Worklet.cpp index 67baf70efddaa..685b7f559edc0 100644 --- a/Source/WebCore/worklets/Worklet.cpp +++ b/Source/WebCore/worklets/Worklet.cpp @@ -39,6 +39,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/xml/CustomXPathNSResolver.idl b/Source/WebCore/xml/CustomXPathNSResolver.idl index 86c56ecca0276..1f43f0552c9b8 100644 --- a/Source/WebCore/xml/CustomXPathNSResolver.idl +++ b/Source/WebCore/xml/CustomXPathNSResolver.idl @@ -23,7 +23,7 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -// Not using [IsWeakCallback] here as XPathNSResolver wrappers always temporary (no reference is kept) -callback interface CustomXPathNSResolver { +// Using [IsStrongCallback] here as XPathNSResolver wrappers are always temporary (no reference is kept) +[ IsStrongCallback ] callback interface CustomXPathNSResolver { [ImplementedAs=lookupNamespaceURIForBindings, SkipCallbackInvokeCheck] DOMString? lookupNamespaceURI([AtomString] DOMString? prefix); }; diff --git a/Source/WebCore/xml/XMLHttpRequest.cpp b/Source/WebCore/xml/XMLHttpRequest.cpp index 25d2564949305..20f1e3a5158cd 100644 --- a/Source/WebCore/xml/XMLHttpRequest.cpp +++ b/Source/WebCore/xml/XMLHttpRequest.cpp @@ -65,6 +65,7 @@ #include #include #include +#include namespace WebCore { diff --git a/Source/WebCore/xml/XPathFunctions.cpp b/Source/WebCore/xml/XPathFunctions.cpp index 38df7e52a7dff..66ba8ac0751b4 100644 --- a/Source/WebCore/xml/XPathFunctions.cpp +++ b/Source/WebCore/xml/XPathFunctions.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/xml/XPathParser.cpp b/Source/WebCore/xml/XPathParser.cpp index 9621d9f900eb2..a94912d15e47d 100644 --- a/Source/WebCore/xml/XPathParser.cpp +++ b/Source/WebCore/xml/XPathParser.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include extern int xpathyyparse(WebCore::XPath::Parser&); diff --git a/Source/WebCore/xml/XSLStyleSheetLibxslt.cpp b/Source/WebCore/xml/XSLStyleSheetLibxslt.cpp index 3bf5e104c7c69..19ea2a5141240 100644 --- a/Source/WebCore/xml/XSLStyleSheetLibxslt.cpp +++ b/Source/WebCore/xml/XSLStyleSheetLibxslt.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include namespace WebCore { diff --git a/Source/WebCore/xml/XSLTProcessor.cpp b/Source/WebCore/xml/XSLTProcessor.cpp index dcc74ed5a5920..2a18af2253d5e 100644 --- a/Source/WebCore/xml/XSLTProcessor.cpp +++ b/Source/WebCore/xml/XSLTProcessor.cpp @@ -40,6 +40,7 @@ #include "XMLDocument.h" #include "markup.h" #include +#include namespace WebCore { diff --git a/Source/WebCore/xml/parser/XMLDocumentParserLibxml2.cpp b/Source/WebCore/xml/parser/XMLDocumentParserLibxml2.cpp index 0be4a7460cfeb..fe0ccba38310e 100644 --- a/Source/WebCore/xml/parser/XMLDocumentParserLibxml2.cpp +++ b/Source/WebCore/xml/parser/XMLDocumentParserLibxml2.cpp @@ -69,6 +69,7 @@ #include "XMLDocumentParserScope.h" #include #include +#include #include #include diff --git a/Source/WebDriver/Session.cpp b/Source/WebDriver/Session.cpp index a1d7c42682af1..0e142dcdaa6aa 100644 --- a/Source/WebDriver/Session.cpp +++ b/Source/WebDriver/Session.cpp @@ -34,6 +34,7 @@ #include #include #include +#include namespace WebDriver { diff --git a/Source/WebDriver/WebDriverService.cpp b/Source/WebDriver/WebDriverService.cpp index 7ecde218f1d0b..2d32bbae3106a 100644 --- a/Source/WebDriver/WebDriverService.cpp +++ b/Source/WebDriver/WebDriverService.cpp @@ -31,6 +31,7 @@ #include "SessionHost.h" #include #include +#include #include #include diff --git a/Source/WebDriver/socket/SessionHostSocket.cpp b/Source/WebDriver/socket/SessionHostSocket.cpp index b7e8428c59b65..f89ae09a43112 100644 --- a/Source/WebDriver/socket/SessionHostSocket.cpp +++ b/Source/WebDriver/socket/SessionHostSocket.cpp @@ -28,7 +28,7 @@ #include #include -#include +#include namespace WebDriver { diff --git a/Source/WebGPU/WGSL/AST/ASTCallExpression.h b/Source/WebGPU/WGSL/AST/ASTCallExpression.h index 138de4e719b60..6439077494675 100644 --- a/Source/WebGPU/WGSL/AST/ASTCallExpression.h +++ b/Source/WebGPU/WGSL/AST/ASTCallExpression.h @@ -26,6 +26,7 @@ #pragma once #include "ASTExpression.h" +#include namespace WGSL { class BoundsCheckVisitor; @@ -54,6 +55,8 @@ class CallExpression final : public Expression { bool isConstructor() const { return m_isConstructor; } + const OptionSet& visibility() const { return m_visibility; } + private: CallExpression(SourceSpan span, Expression::Ref&& target, Expression::List&& arguments) : Expression(span) @@ -69,6 +72,7 @@ class CallExpression final : public Expression { Expression::List m_arguments; bool m_isConstructor { false }; + OptionSet m_visibility { ShaderStage::Compute, ShaderStage::Vertex, ShaderStage::Fragment }; }; } // namespace AST diff --git a/Source/WebGPU/WGSL/AST/ASTStringDumper.cpp b/Source/WebGPU/WGSL/AST/ASTStringDumper.cpp index b635e7b2860ca..56148ee342d6b 100644 --- a/Source/WebGPU/WGSL/AST/ASTStringDumper.cpp +++ b/Source/WebGPU/WGSL/AST/ASTStringDumper.cpp @@ -30,12 +30,13 @@ #include "WGSLShaderModule.h" #include #include +#include namespace WGSL::AST { struct Indent { Indent(StringDumper& dumper) - : scope(dumper.m_indent, dumper.m_indent + " "_s) + : scope(dumper.m_indent, makeString(dumper.m_indent, " "_s)) { } SetForScope scope; }; diff --git a/Source/WebGPU/WGSL/AttributeValidator.cpp b/Source/WebGPU/WGSL/AttributeValidator.cpp index 199c71260466d..a8d03f47436bc 100644 --- a/Source/WebGPU/WGSL/AttributeValidator.cpp +++ b/Source/WebGPU/WGSL/AttributeValidator.cpp @@ -31,7 +31,7 @@ #include "Constraints.h" #include "WGSLShaderModule.h" #include -#include +#include namespace WGSL { @@ -316,13 +316,20 @@ void AttributeValidator::visit(AST::Structure& structure) member.m_offset = offset; alignment = std::max(alignment, *fieldAlignment); - size = UNLIKELY(size.hasOverflowed()) ? currentSize : offset + *fieldSize; + size = offset; + size += *fieldSize; + if (UNLIKELY(size.hasOverflowed())) + size = currentSize; if (previousMember) previousMember->m_padding = offset - previousSize; previousMember = &member; - previousSize = UNLIKELY(size.hasOverflowed()) ? currentSize : offset + typeSize; + + previousSize = offset; + previousSize += typeSize; + if (UNLIKELY(previousSize.hasOverflowed())) + previousSize = currentSize; } unsigned finalSize; if (UNLIKELY(size.hasOverflowed())) diff --git a/Source/WebGPU/WGSL/ConstantFunctions.h b/Source/WebGPU/WGSL/ConstantFunctions.h index 2236037604815..b4af7414278c6 100644 --- a/Source/WebGPU/WGSL/ConstantFunctions.h +++ b/Source/WebGPU/WGSL/ConstantFunctions.h @@ -32,7 +32,7 @@ #include #include #include -#include +#include namespace WGSL { diff --git a/Source/WebGPU/WGSL/EntryPointRewriter.cpp b/Source/WebGPU/WGSL/EntryPointRewriter.cpp index f16260f3de414..65a87433c8073 100644 --- a/Source/WebGPU/WGSL/EntryPointRewriter.cpp +++ b/Source/WebGPU/WGSL/EntryPointRewriter.cpp @@ -32,6 +32,7 @@ #include "Types.h" #include "WGSL.h" #include "WGSLShaderModule.h" +#include namespace WGSL { diff --git a/Source/WebGPU/WGSL/GlobalSorting.cpp b/Source/WebGPU/WGSL/GlobalSorting.cpp index 21a4b4dbb33cf..52a61f8ff850b 100644 --- a/Source/WebGPU/WGSL/GlobalSorting.cpp +++ b/Source/WebGPU/WGSL/GlobalSorting.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include namespace WGSL { diff --git a/Source/WebGPU/WGSL/GlobalVariableRewriter.cpp b/Source/WebGPU/WGSL/GlobalVariableRewriter.cpp index 1b80ae5021443..4f5c9898d741f 100644 --- a/Source/WebGPU/WGSL/GlobalVariableRewriter.cpp +++ b/Source/WebGPU/WGSL/GlobalVariableRewriter.cpp @@ -36,7 +36,7 @@ #include #include #include -#include +#include namespace WGSL { @@ -366,8 +366,7 @@ void RewriteGlobalVariables::visit(AST::CompoundStatement& statement) void RewriteGlobalVariables::visit(AST::CompoundAssignmentStatement& statement) { - Packing lhsPacking = pack(Packing::Either, statement.leftExpression()); - ASSERT(lhsPacking != Packing::Either); + Packing lhsPacking = pack(Packing::Unpacked, statement.leftExpression()); pack(lhsPacking, statement.rightExpression()); } diff --git a/Source/WebGPU/WGSL/MangleNames.cpp b/Source/WebGPU/WGSL/MangleNames.cpp index 23186fec40e85..ad590f6a59829 100644 --- a/Source/WebGPU/WGSL/MangleNames.cpp +++ b/Source/WebGPU/WGSL/MangleNames.cpp @@ -33,6 +33,7 @@ #include "WGSL.h" #include "WGSLShaderModule.h" #include +#include namespace WGSL { diff --git a/Source/WebGPU/WGSL/Metal/MetalFunctionWriter.cpp b/Source/WebGPU/WGSL/Metal/MetalFunctionWriter.cpp index d3d46e9b8660d..53a5a755fd7f7 100644 --- a/Source/WebGPU/WGSL/Metal/MetalFunctionWriter.cpp +++ b/Source/WebGPU/WGSL/Metal/MetalFunctionWriter.cpp @@ -40,7 +40,6 @@ #include #include #include -#include namespace WGSL { @@ -1050,7 +1049,7 @@ void FunctionDefinitionWriter::visit(const Type* type) }, [&](const Struct& structure) { m_stringBuilder.append(structure.structure.name()); - if (shouldPackType() && type->isConstructible() && structure.structure.role() == AST::StructureRole::UserDefinedResource) + if (shouldPackType() && structure.structure.role() == AST::StructureRole::UserDefinedResource) m_stringBuilder.append("::PackedType"_s); }, [&](const PrimitiveStruct& structure) { @@ -2276,7 +2275,22 @@ void FunctionDefinitionWriter::visit(AST::CallStatement& statement) void FunctionDefinitionWriter::visit(AST::CompoundAssignmentStatement& statement) { - visit(statement.leftExpression()); + bool serialized = false; + auto* leftExpression = &statement.leftExpression(); + if (auto* identity = dynamicDowncast(*leftExpression)) + leftExpression = &identity->expression(); + if (auto* call = dynamicDowncast(*leftExpression)) { + auto& target = call->target(); + if (auto* identifier = dynamicDowncast(target)) { + if (identifier->identifier() == "__unpack"_s) { + serialized = true; + visit(call->arguments()[0]); + } + } + } + if (!serialized) + visit(statement.leftExpression()); + m_stringBuilder.append(" = "_s); serializeBinaryExpression(statement.leftExpression(), statement.operation(), statement.rightExpression()); } diff --git a/Source/WebGPU/WGSL/Parser.cpp b/Source/WebGPU/WGSL/Parser.cpp index e661b4b67050d..70f09ac2cf4ca 100644 --- a/Source/WebGPU/WGSL/Parser.cpp +++ b/Source/WebGPU/WGSL/Parser.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include namespace WGSL { diff --git a/Source/WebGPU/WGSL/TypeCheck.cpp b/Source/WebGPU/WGSL/TypeCheck.cpp index 47ad57d3a514f..c54688da76132 100644 --- a/Source/WebGPU/WGSL/TypeCheck.cpp +++ b/Source/WebGPU/WGSL/TypeCheck.cpp @@ -40,7 +40,7 @@ #include #include #include -#include +#include namespace WGSL { @@ -1393,16 +1393,47 @@ void TypeChecker::visit(AST::CallExpression& call) m_shaderModule.setUsesDot4U8Packed(); else if (targetName == "extractBits"_s) m_shaderModule.setUsesExtractBits(); - else if (targetName == "textureGather"_s) { - auto& component = call.arguments()[0]; - if (satisfies(component.inferredType(), Constraints::ConcreteInteger)) { - auto& constant = component.constantValue(); - if (!constant) - typeError(InferBottom::No, component.span(), "the component argument must be a const-expression"_s); - else { - auto componentValue = constant->integerValue(); - if (componentValue < 0 || componentValue > 3) - typeError(InferBottom::No, component.span(), "the component argument must be at least 0 and at most 3. component is "_s, String::number(componentValue)); + else if ( + targetName == "textureGather"_s + || targetName == "textureGatherCompare"_s + || targetName == "textureSample"_s + || targetName == "textureSampleBias"_s + || targetName == "textureSampleCompare"_s + || targetName == "textureSampleCompareLevel"_s + || targetName == "textureSampleGrad"_s + || targetName == "textureSampleLevel"_s + ) { + if (targetName == "textureGather"_s) { + auto& component = call.arguments()[0]; + if (satisfies(component.inferredType(), Constraints::ConcreteInteger)) { + auto& constant = component.constantValue(); + if (!constant) + typeError(InferBottom::No, component.span(), "the component argument must be a const-expression"_s); + else { + auto componentValue = constant->integerValue(); + if (componentValue < 0 || componentValue > 3) + typeError(InferBottom::No, component.span(), "the component argument must be at least 0 and at most 3. component is "_s, String::number(componentValue)); + } + } + } + + auto& lastArg = call.arguments().last(); + auto* vectorType = std::get_if(lastArg.inferredType()); + if (!vectorType || vectorType->size != 2 || vectorType->element != m_types.i32Type()) + return; + + auto& maybeConstant = lastArg.constantValue(); + if (!maybeConstant.has_value()) { + typeError(InferBottom::No, lastArg.span(), "the offset argument must be a const-expression"_s); + return; + } + + auto& vector = std::get(*maybeConstant); + for (unsigned i = 0; i < 2; ++i) { + auto& i32 = std::get(vector.elements[i]); + if (i32 < -8 || i32 > 7) { + typeError(InferBottom::No, lastArg.span(), "each component of the offset argument must be at least -8 and at most 7. offset component "_s, String::number(i), " is "_s, String::number(i32)); + break; } } } @@ -1935,9 +1966,10 @@ const Type* TypeChecker::chooseOverload(ASCIILiteral kind, const SourceSpan& spa callArguments[i].m_inferredType = overload->parameters[i]; inferred(overload->result); - if (expression && it->value.kind == OverloadedDeclaration::Constructor) { - if (auto* call = dynamicDowncast(*expression)) - call->m_isConstructor = true; + if (expression && is(*expression)) { + auto& call = uncheckedDowncast(*expression); + call.m_isConstructor = it->value.kind == OverloadedDeclaration::Constructor; + call.m_visibility = it->value.visibility; } unsigned argumentCount = callArguments.size(); diff --git a/Source/WebGPU/WGSL/VisibilityValidator.cpp b/Source/WebGPU/WGSL/VisibilityValidator.cpp new file mode 100644 index 0000000000000..bee9dc4915330 --- /dev/null +++ b/Source/WebGPU/WGSL/VisibilityValidator.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2024 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +#include "config.h" +#include "VisibilityValidator.h" + +#include "AST.h" +#include "ASTVisitor.h" +#include "WGSLEnums.h" +#include "WGSLShaderModule.h" +#include + +namespace WGSL { + +class VisibilityValidator : public AST::Visitor { +public: + VisibilityValidator(ShaderModule&); + + std::optional validate(); + + void visit(AST::Function&) override; + void visit(AST::CallExpression&) override; + +private: + template + void error(const SourceSpan&, Arguments&&...); + + ShaderModule& m_shaderModule; + Vector m_errors; + ShaderStage m_stage; +}; + +VisibilityValidator::VisibilityValidator(ShaderModule& shaderModule) + : m_shaderModule(shaderModule) +{ +} + +std::optional VisibilityValidator::validate() +{ + for (auto& entryPoint : m_shaderModule.callGraph().entrypoints()) { + m_stage = entryPoint.stage; + visit(entryPoint.function); + + } + + if (m_errors.isEmpty()) + return std::nullopt; + return FailedCheck { WTFMove(m_errors), { } }; +} + +void VisibilityValidator::visit(AST::Function& function) +{ + AST::Visitor::visit(function); + + for (auto& callee : m_shaderModule.callGraph().callees(function)) + visit(*callee.target); +} + +void VisibilityValidator::visit(AST::CallExpression& call) +{ + AST::Visitor::visit(call); + if (!call.visibility().contains(m_stage)) + error(call.span(), "built-in cannot be used by "_s, toString(m_stage), " pipeline stage"_s); +} + +template +void VisibilityValidator::error(const SourceSpan& span, Arguments&&... arguments) +{ + m_errors.append({ makeString(std::forward(arguments)...), span }); +} + +std::optional validateVisibility(ShaderModule& shaderModule) +{ + return VisibilityValidator(shaderModule).validate(); +} + +} // namespace WGSL diff --git a/Source/WebGPU/WGSL/VisibilityValidator.h b/Source/WebGPU/WGSL/VisibilityValidator.h new file mode 100644 index 0000000000000..44bd24cf1dd23 --- /dev/null +++ b/Source/WebGPU/WGSL/VisibilityValidator.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +#pragma once + +#include "WGSL.h" + +namespace WGSL { + +class ShaderModule; + +std::optional validateVisibility(ShaderModule&); + +} // namespace WGSL diff --git a/Source/WebGPU/WGSL/WGSL.cpp b/Source/WebGPU/WGSL/WGSL.cpp index 79ccde33048ce..dc6a4891d0ccc 100644 --- a/Source/WebGPU/WGSL/WGSL.cpp +++ b/Source/WebGPU/WGSL/WGSL.cpp @@ -39,6 +39,7 @@ #include "PhaseTimer.h" #include "PointerRewriter.h" #include "TypeCheck.h" +#include "VisibilityValidator.h" #include "WGSLShaderModule.h" namespace WGSL { @@ -77,6 +78,7 @@ std::variant staticCheck(const String& wgsl, const CHECK_PASS(validateAttributes, shaderModule); RUN_PASS(buildCallGraph, shaderModule); CHECK_PASS(validateIO, shaderModule); + CHECK_PASS(validateVisibility, shaderModule); Vector warnings { }; return std::variant(std::in_place_type, WTFMove(warnings), WTFMove(shaderModule)); diff --git a/Source/WebGPU/WGSL/tests/invalid/texture-offset.wgsl b/Source/WebGPU/WGSL/tests/invalid/texture-offset.wgsl new file mode 100644 index 0000000000000..ed6aee79d1f4e --- /dev/null +++ b/Source/WebGPU/WGSL/tests/invalid/texture-offset.wgsl @@ -0,0 +1,18 @@ +// RUN: %not %wgslc | %check + +@group(0) @binding(0) var s: sampler; +@group(0) @binding(23) var td2d: texture_depth_2d; + +@fragment +fn main() { + var offset = vec2i(0); + + // CHECK-L: the offset argument must be a const-expression + _ = textureGather(td2d, s, vec2f(0), offset); + + // CHECK-L: each component of the offset argument must be at least -8 and at most 7. offset component 0 is -9 + _ = textureGather(td2d, s, vec2f(0), vec2i(-9)); + + // CHECK-L: each component of the offset argument must be at least -8 and at most 7. offset component 0 is 8 + _ = textureGather(td2d, s, vec2f(0), vec2i(8)); +} diff --git a/Source/WebGPU/WGSL/tests/invalid/visibility.wgsl b/Source/WebGPU/WGSL/tests/invalid/visibility.wgsl new file mode 100644 index 0000000000000..d6524167995e7 --- /dev/null +++ b/Source/WebGPU/WGSL/tests/invalid/visibility.wgsl @@ -0,0 +1,11 @@ +// RUN: %not %wgslc | %check + +fn f() { + // CHECK-L: built-in cannot be used by fragment pipeline stage + storageBarrier(); +} + +@fragment +fn main() { + f(); +} diff --git a/Source/WebGPU/WGSL/tests/valid/overload.wgsl b/Source/WebGPU/WGSL/tests/valid/overload.wgsl index 62828e62db8ca..3d55a7c3e0233 100644 --- a/Source/WebGPU/WGSL/tests/valid/overload.wgsl +++ b/Source/WebGPU/WGSL/tests/valid/overload.wgsl @@ -4505,7 +4505,7 @@ fn testTextureNumSamples() // 16.7.8 // RUN: %metal-compile testTextureSample -@compute @workgroup_size(1) +@fragment fn testTextureSample() { // [].(Texture[F32, Texture1d], Sampler, F32) => Vector[F32, 4], @@ -4557,7 +4557,7 @@ fn testTextureSample() // 16.7.9 // RUN: %metal-compile testTextureSampleBias -@compute @workgroup_size(1) +@fragment fn testTextureSampleBias() { // [].(Texture[F32, Texture2d], Sampler, Vector[F32, 2], F32) => Vector[F32, 4], @@ -4587,7 +4587,7 @@ fn testTextureSampleBias() // 16.7.10 // RUN: %metal-compile testTextureSampleCompare -@compute @workgroup_size(1) +@fragment fn testTextureSampleCompare() { // [].(texture_depth_2d, sampler_comparison, vec2[f32], f32) => f32, diff --git a/Source/WebGPU/WGSL/tests/valid/packing.wgsl b/Source/WebGPU/WGSL/tests/valid/packing.wgsl index 0248bc754c5d8..4b937add735b5 100644 --- a/Source/WebGPU/WGSL/tests/valid/packing.wgsl +++ b/Source/WebGPU/WGSL/tests/valid/packing.wgsl @@ -447,6 +447,26 @@ fn testRuntimeArray() s.y[0] = 0; } +@group(0) @binding(11) var D: array; +fn testPackedVec3CompoundAssignment() +{ + D[0] += vec3f(1); +} + +struct InconstructibleS { + x: vec3f, + y: atomic +} +struct InconstructibleT { + x: InconstructibleS, +} + +@group(0) @binding(12) var global: InconstructibleT; + +fn testInconstructible() { + var x = global.x.x; +} + @compute @workgroup_size(1) fn main() { @@ -458,4 +478,6 @@ fn main() _ = testUnaryOperations(); _ = testCall(); testRuntimeArray(); + testPackedVec3CompoundAssignment(); + testInconstructible(); } diff --git a/Source/WebGPU/WebGPU.xcodeproj/project.pbxproj b/Source/WebGPU/WebGPU.xcodeproj/project.pbxproj index 4d4b1ce211937..5e099982de05e 100644 --- a/Source/WebGPU/WebGPU.xcodeproj/project.pbxproj +++ b/Source/WebGPU/WebGPU.xcodeproj/project.pbxproj @@ -181,6 +181,8 @@ 97D398E62B06A85B00D8C4AA /* ASTDiagnosticDirective.h in Headers */ = {isa = PBXBuildFile; fileRef = 97D398E42B06A85B00D8C4AA /* ASTDiagnosticDirective.h */; }; 97D398E72B06A85B00D8C4AA /* ASTDiagnostic.h in Headers */ = {isa = PBXBuildFile; fileRef = 97D398E52B06A85B00D8C4AA /* ASTDiagnostic.h */; }; 97D398E92B2387F300D8C4AA /* ASTTypeAlias.h in Headers */ = {isa = PBXBuildFile; fileRef = 97D398E82B2387F300D8C4AA /* ASTTypeAlias.h */; }; + 97DE28472C348D8A00F4DEC3 /* VisibilityValidator.h in Headers */ = {isa = PBXBuildFile; fileRef = 97DE28452C348D8A00F4DEC3 /* VisibilityValidator.h */; }; + 97DE28482C348D8A00F4DEC3 /* VisibilityValidator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 97DE28462C348D8A00F4DEC3 /* VisibilityValidator.cpp */; }; 97E21C8B2A1F5DCC009CEB0E /* ASTDecrementIncrementStatement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 97E21C8A2A1F5DCC009CEB0E /* ASTDecrementIncrementStatement.cpp */; }; 97E21C972A2512F7009CEB0E /* ConstantValue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 97E21C942A2512F7009CEB0E /* ConstantValue.cpp */; }; 97E21C982A2512F7009CEB0E /* ConstantValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 97E21C952A2512F7009CEB0E /* ConstantValue.h */; }; @@ -479,6 +481,8 @@ 97D398E42B06A85B00D8C4AA /* ASTDiagnosticDirective.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTDiagnosticDirective.h; sourceTree = ""; }; 97D398E52B06A85B00D8C4AA /* ASTDiagnostic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTDiagnostic.h; sourceTree = ""; }; 97D398E82B2387F300D8C4AA /* ASTTypeAlias.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTTypeAlias.h; sourceTree = ""; }; + 97DE28452C348D8A00F4DEC3 /* VisibilityValidator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VisibilityValidator.h; sourceTree = ""; }; + 97DE28462C348D8A00F4DEC3 /* VisibilityValidator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VisibilityValidator.cpp; sourceTree = ""; }; 97E21C8A2A1F5DCC009CEB0E /* ASTDecrementIncrementStatement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ASTDecrementIncrementStatement.cpp; sourceTree = ""; }; 97E21C942A2512F7009CEB0E /* ConstantValue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ConstantValue.cpp; sourceTree = ""; }; 97E21C952A2512F7009CEB0E /* ConstantValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConstantValue.h; sourceTree = ""; }; @@ -687,6 +691,8 @@ 978A9131298BBFD300B37E5E /* Types.h */, 978A9136298D40F100B37E5E /* TypeStore.cpp */, 978A9135298D40F100B37E5E /* TypeStore.h */, + 97DE28462C348D8A00F4DEC3 /* VisibilityValidator.cpp */, + 97DE28452C348D8A00F4DEC3 /* VisibilityValidator.h */, 1CEBD8022716BF8200A5254D /* WGSL.cpp */, 1CEBD7F72716B34400A5254D /* WGSL.h */, 97FA1A8729C085A60052D650 /* wgslc.cpp */, @@ -957,6 +963,7 @@ 978A912F298AD3DA00B37E5E /* TypeCheck.h in Headers */, 978A9133298BBFD300B37E5E /* Types.h in Headers */, 978A9137298D40F100B37E5E /* TypeStore.h in Headers */, + 97DE28472C348D8A00F4DEC3 /* VisibilityValidator.h in Headers */, 1CEBD7F82716B34400A5254D /* WGSL.h in Headers */, 97099AC72AC49AAB003B41F8 /* WGSLEnums.h in Headers */, 9776BE7629957E12002D6D93 /* WGSLShaderModule.h in Headers */, @@ -1191,6 +1198,7 @@ 97296766299C09BC001C8BD4 /* TypeDeclarations.rb in Sources */, 978A9134298BBFD300B37E5E /* Types.cpp in Sources */, 978A9138298D40F100B37E5E /* TypeStore.cpp in Sources */, + 97DE28482C348D8A00F4DEC3 /* VisibilityValidator.cpp in Sources */, 1CEBD8032716BF8200A5254D /* WGSL.cpp in Sources */, 97099AC62AC49AAB003B41F8 /* WGSLEnums.cpp in Sources */, ); diff --git a/Source/WebGPU/WebGPU/CommandEncoder.mm b/Source/WebGPU/WebGPU/CommandEncoder.mm index 92fbb250ba509..e5a65c99d6d0d 100644 --- a/Source/WebGPU/WebGPU/CommandEncoder.mm +++ b/Source/WebGPU/WebGPU/CommandEncoder.mm @@ -577,7 +577,7 @@ static bool isRenderableTextureView(const TextureView& texture) if (hasDepthComponent) { const auto& mtlAttachment = mtlDescriptor.depthAttachment; auto clearDepth = std::clamp(RenderPassEncoder::quantizedDepthValue(attachment->depthClearValue, textureView.format()), 0., 1.); - mtlAttachment.clearDepth = clearDepth; + mtlAttachment.clearDepth = attachment->depthLoadOp == WGPULoadOp_Clear ? clearDepth : 1.0; mtlAttachment.texture = metalDepthStencilTexture; mtlAttachment.level = 0; mtlAttachment.loadAction = loadAction(attachment->depthLoadOp); diff --git a/Source/WebGPU/WebGPU/RenderBundleEncoder.mm b/Source/WebGPU/WebGPU/RenderBundleEncoder.mm index 623e7ce8ca994..e29f286da7d8d 100644 --- a/Source/WebGPU/WebGPU/RenderBundleEncoder.mm +++ b/Source/WebGPU/WebGPU/RenderBundleEncoder.mm @@ -1008,11 +1008,11 @@ - (instancetype)initWithICB:(id)icb containerBuffer:(i } m_bindGroups.set(groupIndex, &group); - if (group.vertexArgumentBuffer()) { + if (auto vertexBindGroupBufferIndex = m_device->vertexBufferIndexForBindGroup(groupIndex); group.vertexArgumentBuffer() && m_vertexBuffers.size() > vertexBindGroupBufferIndex) { addResource(m_resources, group.vertexArgumentBuffer(), MTLRenderStageVertex); - m_vertexBuffers[m_device->vertexBufferIndexForBindGroup(groupIndex)] = { .buffer = group.vertexArgumentBuffer(), .offset = 0, .dynamicOffsetCount = dynamicOffsetCount, .dynamicOffsets = dynamicOffsets->data(), .size = group.vertexArgumentBuffer().length }; + m_vertexBuffers[vertexBindGroupBufferIndex] = { .buffer = group.vertexArgumentBuffer(), .offset = 0, .dynamicOffsetCount = dynamicOffsetCount, .dynamicOffsets = dynamicOffsets->data(), .size = group.vertexArgumentBuffer().length }; } - if (group.fragmentArgumentBuffer()) { + if (group.fragmentArgumentBuffer() && m_fragmentBuffers.size() > groupIndex) { addResource(m_resources, group.fragmentArgumentBuffer(), MTLRenderStageFragment); m_fragmentBuffers[groupIndex] = { .buffer = group.fragmentArgumentBuffer(), .offset = 0, .dynamicOffsetCount = dynamicOffsetCount, .dynamicOffsets = dynamicOffsets->data(), .size = group.fragmentArgumentBuffer().length }; } diff --git a/Source/WebInspectorUI/UserInterface/External/CSSDocumentation/CSSDocumentation-overrides.json b/Source/WebInspectorUI/UserInterface/External/CSSDocumentation/CSSDocumentation-overrides.json index 64931e2cf180b..0b324fbe3a88f 100644 --- a/Source/WebInspectorUI/UserInterface/External/CSSDocumentation/CSSDocumentation-overrides.json +++ b/Source/WebInspectorUI/UserInterface/External/CSSDocumentation/CSSDocumentation-overrides.json @@ -8,5 +8,14 @@ "-webkit-appearance": { "description": "Changes the appearance of buttons and other controls to resemble native controls.", "syntax": "none | button | checkbox | listbox | menulist | menulist-button | meter | progress-bar | push-button | radio | searchfield | slider-horizontal | slider-vertical | square-button | textarea | textfield | -apple-pay-button" + }, + "overlay": { + "description": "The overlay CSS property specifies whether an element appearing in the top layer (for example, a shown popover or modal element) is actually rendered in the top layer. This property is only relevant within a list of transition-property values, and only if allow-discrete is set as the transition-behavior." + }, + "text-wrap-mode": { + "description": "The text-wrap-mode CSS property controls whether the text inside an element is wrapped. The different values provide alternate ways of wrapping the content of a block element. It can also be set, and reset, using the text-wrap shorthand." + }, + "text-wrap-style": { + "description": "The text-wrap-style CSS property controls how text inside an element is wrapped. The different values provide alternate ways of wrapping the content of a block element. It can also be set, and reset, using the text-wrap shorthand." } } diff --git a/Source/WebInspectorUI/UserInterface/External/CSSDocumentation/CSSDocumentation.js b/Source/WebInspectorUI/UserInterface/External/CSSDocumentation/CSSDocumentation.js index 7377ab40f9d4f..b868c78d7e819 100644 --- a/Source/WebInspectorUI/UserInterface/External/CSSDocumentation/CSSDocumentation.js +++ b/Source/WebInspectorUI/UserInterface/External/CSSDocumentation/CSSDocumentation.js @@ -157,8 +157,7 @@ CSSDocumentation = { }, "-webkit-line-clamp": { "description": "The -webkit-line-clamp CSS property allows limiting of the contents of a block container to the specified number of lines.", - "syntax": "none | ", - "url": "https://developer.mozilla.org/docs/Web/CSS/-webkit-line-clamp" + "syntax": "none | " }, "-webkit-mask": { "description": "The mask CSS property alters the visibility of an element by either partially or fully hiding it. This is accomplished by either masking or clipping the image at specific points.", @@ -340,6 +339,15 @@ CSSDocumentation = { "description": "Provides alternative text for assistive technology to replace the generated content of a ::before or ::after element.", "url": "https://developer.mozilla.org/docs/Web/CSS/alt" }, + "anchor-name": { + "description": "The anchor-name property declares that an element is an anchor element, and gives it a list of anchor names to be targeted by.", + "syntax": "none | #", + "url": "https://developer.mozilla.org/docs/Web/CSS/anchor-name" + }, + "anchor-scope": { + "description": "This property scopes the specified anchor names, and lookups for these anchor names, to this element\u2019s subtree", + "syntax": "none | all | #" + }, "animation": { "description": "Shorthand property combines six of the animation properties into a single property.", "syntax": "#", @@ -1094,6 +1102,10 @@ CSSDocumentation = { "description": "@counter-style descriptor. Specifies a fallback counter style to be used when the current counter style can't create a representation for a given counter value.", "syntax": "" }, + "field-sizing": { + "description": "The field-sizing CSS property enables you to control the sizing behavior of elements that are given a default preferred size, such as form control elements. This property enables you to override the default sizing behavior, allowing form controls to adjust in size to fit their contents.", + "syntax": "content | fixed" + }, "fill": { "description": "Paints the interior of the given graphical element." }, @@ -1220,9 +1232,14 @@ CSSDocumentation = { }, "font-synthesis": { "description": "Controls whether user agents are allowed to synthesize bold or oblique font faces when a font family lacks bold or italic faces.", - "syntax": "none | [ weight || style || small-caps ]", + "syntax": "none | [ weight || style || small-caps || position]", "url": "https://developer.mozilla.org/docs/Web/CSS/font-synthesis" }, + "font-synthesis-position": { + "description": "The font-synthesis-position CSS property lets you specify whether or not a browser may synthesize the subscript and superscript \"position\" typefaces when they are missing in a font family, while using font-variant-position to set the positions.", + "syntax": "auto | none", + "url": "https://developer.mozilla.org/docs/Web/CSS/font-synthesis-position" + }, "font-synthesis-small-caps": { "description": "The font-synthesis-small-caps CSS property lets you specify whether or not the browser may synthesize small-caps typeface when it is missing in a font family. Small-caps glyphs typically use the form of uppercase letters but are reduced to the size of lowercase letters.", "syntax": "auto | none", @@ -1240,7 +1257,7 @@ CSSDocumentation = { }, "font-variant": { "description": "Specifies variant representations of the font", - "syntax": "normal | none | [ || || || || stylistic() || historical-forms || styleset(#) || character-variant(#) || swash() || ornaments() || annotation() || [ small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps ] || || || || ordinal || slashed-zero || || || ruby ]", + "syntax": "normal | none | [ || || || || stylistic( ) || historical-forms || styleset( # ) || character-variant( # ) || swash( ) || ornaments( ) || annotation( ) || [ small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps ] || || || || ordinal || slashed-zero || || || ruby ]", "url": "https://developer.mozilla.org/docs/Web/CSS/font-variant" }, "font-variant-alternates": { @@ -1398,7 +1415,7 @@ CSSDocumentation = { }, "height": { "description": "Specifies the height of the content area, padding area or border area (depending on 'box-sizing') of certain boxes.", - "syntax": "{1,2}", + "syntax": "auto | | | min-content | max-content | fit-content | fit-content()", "url": "https://developer.mozilla.org/docs/Web/CSS/height" }, "hyphenate-character": { @@ -1450,7 +1467,7 @@ CSSDocumentation = { }, "initial-value": { "description": "Specifies the initial value of the custom property registration represented by the @property rule, controlling the property\u2019s initial value.", - "syntax": "" + "syntax": "?" }, "inline-size": { "description": "Size of an element in the direction specified by 'writing-mode'.", @@ -1466,6 +1483,11 @@ CSSDocumentation = { "syntax": "<'top'>{1,4}", "url": "https://developer.mozilla.org/docs/Web/CSS/inset" }, + "inset-area": { + "description": "Most common use-cases of anchor positioning only need to worry about the edges of the positioned element\u2019s containing block, and the edges of the default anchor element. These lines can be thought of as defining a 3x3 grid; inset-area lets you easily set up the positioned element\u2019s inset properties by specifying what area of this inset-area grid you want the positioned element to be in", + "syntax": "none | ", + "url": "https://developer.mozilla.org/docs/Web/CSS/inset-area" + }, "inset-block": { "description": "The inset-block CSS property defines the logical block start and end offsets of an element, which maps to physical offsets depending on the element's writing mode, directionality, and text orientation. It corresponds to the top and bottom, or right and left properties depending on the values defined for writing-mode, direction, and text-orientation.", "syntax": "<'top'>{1,2}", @@ -1544,7 +1566,8 @@ CSSDocumentation = { }, "line-clamp": { "description": "The line-clamp property allows limiting the contents of a block container to the specified number of lines; remaining content is fragmented away and neither rendered nor measured. Optionally, it also allows inserting content into the last line box to indicate the continuity of truncated/interrupted content.", - "syntax": "none | " + "syntax": "none | ", + "url": "https://developer.mozilla.org/docs/Web/CSS/-webkit-line-clamp" }, "line-gap-override": { "description": "Describes the line-gap metric of a font.", @@ -1767,7 +1790,7 @@ CSSDocumentation = { }, "max-height": { "description": "Allows authors to constrain content height to a certain range.", - "syntax": "", + "syntax": "none | | min-content | max-content | fit-content | fit-content()", "url": "https://developer.mozilla.org/docs/Web/CSS/max-height" }, "max-inline-size": { @@ -1781,13 +1804,9 @@ CSSDocumentation = { }, "max-width": { "description": "Allows authors to constrain content width to a certain range.", - "syntax": "", + "syntax": "none | | min-content | max-content | fit-content | fit-content()", "url": "https://developer.mozilla.org/docs/Web/CSS/max-width" }, - "max-zoom": { - "description": "The max-zoom CSS descriptor sets the maximum zoom factor of a document defined by the @viewport at-rule. The browser will not zoom in any further than this, whether automatically or at the user's request.\n\nA zoom factor of 1.0 or 100% corresponds to no zooming. Larger values are zoomed in. Smaller values are zoomed out.", - "syntax": "auto | | " - }, "min-block-size": { "description": "Minimal size of an element in the direction opposite that of the direction specified by 'writing-mode'.", "syntax": "<'min-width'>", @@ -1795,7 +1814,7 @@ CSSDocumentation = { }, "min-height": { "description": "Allows authors to constrain content height to a certain range.", - "syntax": "", + "syntax": "auto | | | min-content | max-content | fit-content | fit-content()", "url": "https://developer.mozilla.org/docs/Web/CSS/min-height" }, "min-inline-size": { @@ -1805,13 +1824,9 @@ CSSDocumentation = { }, "min-width": { "description": "Allows authors to constrain content width to a certain range.", - "syntax": "", + "syntax": "auto | | | min-content | max-content | fit-content | fit-content()", "url": "https://developer.mozilla.org/docs/Web/CSS/min-width" }, - "min-zoom": { - "description": "The min-zoom CSS descriptor sets the minimum zoom factor of a document defined by the @viewport at-rule. The browser will not zoom out any further than this, whether automatically or at the user's request.\n\nA zoom factor of 1.0 or 100% corresponds to no zooming. Larger values are zoomed in. Smaller values are zoomed out.", - "syntax": "auto | | " - }, "mix-blend-mode": { "description": "Defines the formula that must be used to mix the colors with the backdrop.", "syntax": " | plus-lighter", @@ -1910,10 +1925,6 @@ CSSDocumentation = { "syntax": "", "url": "https://developer.mozilla.org/docs/Web/CSS/order" }, - "orientation": { - "description": "The orientation CSS @media media feature can be used to apply styles based on the orientation of the viewport (or the page box, for paged media).", - "syntax": "auto | portrait | landscape" - }, "orphans": { "description": "Specifies the minimum number of line boxes in a block container that must be left in a fragment before a fragmentation break.", "syntax": "", @@ -1926,7 +1937,7 @@ CSSDocumentation = { }, "outline-color": { "description": "The color of the outline.", - "syntax": " | invert", + "syntax": "auto | ", "url": "https://developer.mozilla.org/docs/Web/CSS/outline-color" }, "outline-offset": { @@ -1988,6 +1999,11 @@ CSSDocumentation = { "syntax": "visible | hidden | clip | scroll | auto", "url": "https://developer.mozilla.org/docs/Web/CSS/overflow-y" }, + "overlay": { + "description": "The overlay CSS property specifies whether an element appearing in the top layer (for example, a shown popover or modal element) is actually rendered in the top layer. This property is only relevant within a list of transition-property values, and only if allow-discrete is set as the transition-behavior.", + "syntax": "none | auto", + "url": "https://developer.mozilla.org/docs/Web/CSS/overlay" + }, "override-colors": { "description": "The override-colors CSS descriptor is used to override colors in the chosen base-palette for a color font.", "syntax": "[ ]#" @@ -2140,6 +2156,31 @@ CSSDocumentation = { "syntax": "static | relative | absolute | sticky | fixed", "url": "https://developer.mozilla.org/docs/Web/CSS/position" }, + "position-anchor": { + "description": "The position-anchor property defines the default anchor specifier for all anchor functions on the element, allowing multiple elements to use the same set of anchor functions (and position options lists!) while changing which anchor element each is referring to.", + "syntax": "", + "url": "https://developer.mozilla.org/docs/Web/CSS/position-anchor" + }, + "position-try": { + "description": "This shorthand sets both position-try-options and position-try-order. If <'position-try-order'> is omitted, it\u2019s set to the property\u2019s initial value.", + "syntax": "<'position-try-order'>? <'position-try-options'>", + "url": "https://developer.mozilla.org/docs/Web/CSS/position-try" + }, + "position-try-options": { + "description": "This property provides a list of alternate positioning styles to try when the absolutely positioned box overflows its inset-modified containing block. This position options list is initially empty.", + "syntax": "none | [ [ || ] | inset-area( <'inset-area'> ) ]#", + "url": "https://developer.mozilla.org/docs/Web/CSS/position-try-options" + }, + "position-try-order": { + "description": "This property specifies the order in which the position options list will be tried.", + "syntax": "normal | ", + "url": "https://developer.mozilla.org/docs/Web/CSS/position-try-order" + }, + "position-visibility": { + "description": "There are times when an element\u2019s anchors are not appropriate for positioning the element with, and it would be better to simply not display the element at all. position-visibility provides several conditions where this could be the case.", + "syntax": "always | [ anchors-valid || anchors-visible || no-overflow ]", + "url": "https://developer.mozilla.org/docs/Web/CSS/position-visibility" + }, "prefix": { "description": "@counter-style descriptor. Specifies a that is prepended to the marker representation.", "syntax": "" @@ -2636,6 +2677,14 @@ CSSDocumentation = { "syntax": "wrap | nowrap | balance | stable | pretty", "url": "https://developer.mozilla.org/docs/Web/CSS/text-wrap" }, + "text-wrap-mode": { + "description": "The text-wrap-mode CSS property controls whether the text inside an element is wrapped. The different values provide alternate ways of wrapping the content of a block element. It can also be set, and reset, using the text-wrap shorthand.", + "syntax": "auto | wrap | nowrap" + }, + "text-wrap-style": { + "description": "The text-wrap-style CSS property controls how text inside an element is wrapped. The different values provide alternate ways of wrapping the content of a block element. It can also be set, and reset, using the text-wrap shorthand.", + "syntax": "auto | balance | stable | pretty" + }, "timeline-scope": { "description": "The timeline-scope CSS property modifies the scope of a named animation timeline.", "syntax": "none | #", @@ -2676,6 +2725,11 @@ CSSDocumentation = { "syntax": "#", "url": "https://developer.mozilla.org/docs/Web/CSS/transition" }, + "transition-behavior": { + "description": "The transition-behavior CSS property specifies whether transitions will be started for properties whose animation behavior is discrete.", + "syntax": "#", + "url": "https://developer.mozilla.org/docs/Web/CSS/transition-behavior" + }, "transition-delay": { "description": "Defines when the transition will start. It allows a transition to begin execution some period of time from when it is applied.", "syntax": "Test"]; + [webView focusElementAndEnsureEditorStateUpdate:@"document.body"]; + + NSString *setSelectionJavaScript = @"" + "(() => {" + " const range = document.createRange();" + " range.setStart(document.querySelector('span').firstChild, 4);" + " range.setEnd(document.querySelector('span').firstChild, 4);" + " " + " var selection = window.getSelection();" + " selection.removeAllRanges();" + " selection.addRange(range);" + "})();"; + [webView stringByEvaluatingJavaScript:setSelectionJavaScript]; + + RetainPtr adaptiveImageGlyphData = [NSData dataWithContentsOfURL:[NSBundle.mainBundle URLForResource:@"adaptive-image-glyph" withExtension:@"heic" subdirectory:@"TestWebKitAPI.resources"]]; + + RetainPtr adaptiveImageGlyph = adoptNS([[NSAdaptiveImageGlyph alloc] initWithImageContent:adaptiveImageGlyphData.get()]); + + [webView insertAdaptiveImageGlyph:adaptiveImageGlyph.get()]; + [webView waitForNextPresentationUpdate]; + + EXPECT_WK_STREQ([webView stringByEvaluatingJavaScript:@"getComputedStyle(document.querySelector('picture').children[1]).fontSize"], "64px"); +} + TEST(AdaptiveImageGlyph, InsertAndRemoveWKAttachments) { auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]); diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/CookieAcceptPolicy.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/CookieAcceptPolicy.mm index c4ef6ff618021..b3237dc1b6132 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/CookieAcceptPolicy.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/CookieAcceptPolicy.mm @@ -33,6 +33,7 @@ #import #import #import +#import @interface CookieAcceptPolicyMessageHandler : NSObject @end diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/CookiePrivateBrowsing.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/CookiePrivateBrowsing.mm index 025e9bd102b1e..f63186c023e37 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/CookiePrivateBrowsing.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/CookiePrivateBrowsing.mm @@ -37,7 +37,7 @@ #import #import #import -#import +#import #import static bool didRunJavaScriptAlertForCookiePrivateBrowsing; diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/DocumentEditingContext.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/DocumentEditingContext.mm index 115b31fe9e854..8cbb9bef44310 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/DocumentEditingContext.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/DocumentEditingContext.mm @@ -37,6 +37,7 @@ #import #import #import +#import static constexpr auto longTextString = "Here's to the crazy ones. The misfits. The rebels. The troublemakers. The round pegs in the square holes. " "The ones who see things differently. They're not fond of rules. And they have no respect for the status quo. " diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/Download.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/Download.mm index d1b3ec9ad6978..8baad71839199 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/Download.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/Download.mm @@ -58,6 +58,7 @@ #import #import #import +#import #import #if PLATFORM(MAC) diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/EventAttribution.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/EventAttribution.mm index 35df5e1692848..01efa20615692 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/EventAttribution.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/EventAttribution.mm @@ -50,7 +50,7 @@ #import #import #import -#import +#import #if HAVE(RSA_BSSA) diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/FTP.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/FTP.mm index 4065c76fcd406..f7c6d6ae47a1b 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/FTP.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/FTP.mm @@ -38,7 +38,7 @@ #import #import #import -#import +#import #import namespace TestWebKitAPI { diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/MediaLoading.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/MediaLoading.mm index 9a566f33a7d23..48741dcc8bfce 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/MediaLoading.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/MediaLoading.mm @@ -30,7 +30,7 @@ #import "TestUIDelegate.h" #import "TestWKWebView.h" #import -#import +#import #import diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/MediaSession.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/MediaSession.mm index c59d6583b9a95..db93ca1c1709b 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/MediaSession.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/MediaSession.mm @@ -39,6 +39,7 @@ #import #import #import +#import #import #import diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/NetworkProcess.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/NetworkProcess.mm index 8e2909a0af258..ea96115acc022 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/NetworkProcess.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/NetworkProcess.mm @@ -42,6 +42,7 @@ #import #import #import +#import TEST(NetworkProcess, Entitlements) { diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm index 87d4ab80e4246..763542a8442ab 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm @@ -63,7 +63,7 @@ #import #import #import -#import +#import #import #import diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/Proxy.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/Proxy.mm index 70d2799aef94f..a67781547886a 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/Proxy.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/Proxy.mm @@ -38,7 +38,7 @@ #import #import #import -#import +#import @interface ProxyDelegate : NSObject - (NSString *)waitForAlert; diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/ResourceLoadStatistics.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/ResourceLoadStatistics.mm index 9b092cd0de719..a0899bad5b8d1 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/ResourceLoadStatistics.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/ResourceLoadStatistics.mm @@ -47,7 +47,7 @@ #import #import #import -#import +#import static bool finishedNavigation = false; diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/SOAuthorizationTests.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/SOAuthorizationTests.mm index 3793e23dd03df..ee33a94198b4e 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/SOAuthorizationTests.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/SOAuthorizationTests.mm @@ -38,15 +38,16 @@ #import #import #import -#import #import #import #import #import #import -#import +#import #import +#import + static bool navigationCompleted = false; static bool appSSONavigationFailed = false; static bool policyForAppSSOPerformed = false; diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/ServiceWorkerBasic.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/ServiceWorkerBasic.mm index 71ca675654cdd..b5bf7e8e6f747 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/ServiceWorkerBasic.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/ServiceWorkerBasic.mm @@ -63,7 +63,7 @@ #import #import #import -#import +#import #import #import diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm index e63eeb4e55988..668cc2d57ed0b 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm @@ -47,6 +47,7 @@ #import #import #import +#import namespace TestWebKitAPI { diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/StorageQuota.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/StorageQuota.mm index a9d59578f5d92..233536aca7b89 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/StorageQuota.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/StorageQuota.mm @@ -44,7 +44,6 @@ #import #import #import -#import #import #import diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/TLSDeprecation.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/TLSDeprecation.mm index 39fded4748a83..dbab281becf18 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/TLSDeprecation.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/TLSDeprecation.mm @@ -37,7 +37,7 @@ #import #import #import -#import +#import #if PLATFORM(IOS_FAMILY) #import diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm index 3f1528ce11baa..d111cd972166b 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm @@ -38,8 +38,7 @@ #import #import #import -#import -#import +#import typedef void(^ItemCallback)(_WKTextManipulationItem *); typedef void(^ItemListCallback)(NSArray<_WKTextManipulationItem *> *); diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/VideoControlsManager.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/VideoControlsManager.mm index e5832e960eae3..424f233b44cc0 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/VideoControlsManager.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/VideoControlsManager.mm @@ -119,6 +119,60 @@ - (void)_handleControlledElementIDResponse:(NSString *)identifier _isDoneQueryingControlledElementID = true; } +- (BOOL)_playPredominantOrNowPlayingMediaSession +{ + __block bool done = false; + __block BOOL result = NO; + [self _playPredominantOrNowPlayingMediaSession:^(BOOL success) { + result = success; + done = true; + }]; + TestWebKitAPI::Util::run(&done); + return result; +} + +- (BOOL)_pauseNowPlayingMediaSession +{ + __block bool done = false; + __block BOOL result = NO; + [self _pauseNowPlayingMediaSession:^(BOOL success) { + result = success; + done = true; + }]; + TestWebKitAPI::Util::run(&done); + return result; +} + +- (void)waitForVideoToPlay +{ + [self waitForVideoToPlay:@"video"]; +} + +- (void)waitForVideoToPlay:(NSString *)selector +{ + while (true) { + __auto_type scriptToRun = [NSString stringWithFormat:@"(function() {" + " let video = document.querySelector('%@');" + " return !video.paused && video.readyState >= HTMLMediaElement.HAVE_ENOUGH_DATA;" + "})();", selector]; + if ([[self objectByEvaluatingJavaScript:scriptToRun] boolValue]) + return; + } +} + +- (BOOL)isVideoPaused:(NSString *)selector +{ + return [[self objectByEvaluatingJavaScript:[NSString stringWithFormat:@"document.querySelector('%@').paused", selector]] boolValue]; +} + +- (void)waitForVideoToPause +{ + while (true) { + if ([self isVideoPaused:@"video"]) + return; + } +} + @end namespace TestWebKitAPI { @@ -502,6 +556,37 @@ - (void)_handleControlledElementIDResponse:(NSString *)identifier EXPECT_EQ(1.0, [[webView objectByEvaluatingJavaScript:@"document.getElementsByTagName('video')[0].defaultPlaybackRate"] doubleValue]); } +TEST(VideoControlsManager, TogglePlaybackForControlledVideo) +{ + RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 500, 500)); + [webView synchronouslyLoadTestPageNamed:@"large-video-with-audio"]; + [webView waitForMediaControlsToShow]; + + BOOL paused = [webView _pauseNowPlayingMediaSession]; + EXPECT_TRUE(paused); + [webView waitForVideoToPause]; + + BOOL played = [webView _playPredominantOrNowPlayingMediaSession]; + EXPECT_TRUE(played); + [webView waitForVideoToPlay]; +} + +TEST(VideoControlsManager, StartPlayingLargestVideoInViewport) +{ + RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 640, 480)); + [webView synchronouslyLoadTestPageNamed:@"large-videos-with-audio"]; + + Util::waitForConditionWithLogging([webView] -> bool { + return [[webView stringByEvaluatingJavaScript:@"!!document.getElementById('foo')?.canPlayThrough"] boolValue]; + }, 5, @"Expected first video to finish loading."); + + BOOL played = [webView _playPredominantOrNowPlayingMediaSession]; + EXPECT_TRUE(played); + [webView waitForVideoToPlay:@"#foo"]; + EXPECT_TRUE([webView isVideoPaused:@"#bar"]); + EXPECT_TRUE([webView isVideoPaused:@"#baz"]); +} + } // namespace TestWebKitAPI #endif // PLATFORM(MAC) diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm index 9fd6d6273d120..9763c78863fac 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm @@ -1402,12 +1402,7 @@ @implementation FileWrapper [htmlAttachment expectRequestedDataToBe:nil]; } -// FIXME: Investigate why 280261@main seems to have made this time out on open source iOS. -#if PLATFORM(IOS_FAMILY) && !USE(APPLE_INTERNAL_SDK) -TEST(WKAttachmentTests, DISABLED_InvalidateAttachmentsAfterWebProcessTermination) -#else TEST(WKAttachmentTests, InvalidateAttachmentsAfterWebProcessTermination) -#endif { auto webView = webViewForTestingAttachments(); RetainPtr<_WKAttachment> pdfAttachment; diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKHTTPCookieStore.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKHTTPCookieStore.mm index 2907b5a30d2f0..f31b6f703ad0a 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKHTTPCookieStore.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKHTTPCookieStore.mm @@ -41,6 +41,7 @@ #import #import #import +#import #import static bool gotFlag; @@ -877,21 +878,23 @@ HTTPServer server(TestWebKitAPI::HTTPServer::UseCoroutines::Yes, [&] (Connection request.append(0); if (path == "/redirect"_s) { co_await connection.awaitableSend( - "HTTP/1.1 302 Found\r\n" - "Location: http://localhost:"_s + serverPort + "/com\r\n" - "Content-Length: 0\r\n" - "\r\n"_s); + makeString( + "HTTP/1.1 302 Found\r\n" + "Location: http://localhost:"_s, serverPort, "/com\r\n" + "Content-Length: 0\r\n" + "\r\n"_s)); } else if (path == "/com"_s) { co_await connection.awaitableSend( - "HTTP/1.1 302 Found\r\n" - "Location: http://127.0.0.1:"_s + serverPort + "/destination\r\n"_s - "Set-Cookie: Default=1\r\n" - "Set-Cookie: SameSite_None=1; SameSite=None\r\n" - "Set-Cookie: SameSite_None_Secure=1; secure; SameSite=None\r\n" - "Set-Cookie: SameSite_Lax=1; SameSite=Lax\r\n" - "Set-Cookie: SameSite_Strict=1; SameSite=Strict\r\n" - "Content-Length: 0\r\n" - "\r\n"_s); + makeString( + "HTTP/1.1 302 Found\r\n" + "Location: http://127.0.0.1:"_s, serverPort, "/destination\r\n" + "Set-Cookie: Default=1\r\n" + "Set-Cookie: SameSite_None=1; SameSite=None\r\n" + "Set-Cookie: SameSite_None_Secure=1; secure; SameSite=None\r\n" + "Set-Cookie: SameSite_Lax=1; SameSite=Lax\r\n" + "Set-Cookie: SameSite_Strict=1; SameSite=Strict\r\n" + "Content-Length: 0\r\n" + "\r\n"_s)); } else if (path == "/destination"_s) { EXPECT_TRUE(strnstr(request.data(), "Host: 127.0.0.1:", request.size())); EXPECT_FALSE(strnstr(request.data(), "Cookie:", request.size())); @@ -932,21 +935,23 @@ HTTPServer server(TestWebKitAPI::HTTPServer::UseCoroutines::Yes, [&] (Connection request.append(0); if (path == "/redirect"_s) { co_await connection.awaitableSend( - "HTTP/1.1 302 Found\r\n" - "Location: http://localhost:"_s + serverPort + "/com\r\n" - "Content-Length: 0\r\n" - "\r\n"_s); + makeString( + "HTTP/1.1 302 Found\r\n" + "Location: http://localhost:"_s, serverPort, "/com\r\n" + "Content-Length: 0\r\n" + "\r\n"_s)); } else if (path == "/com"_s) { co_await connection.awaitableSend( - "HTTP/1.1 302 Found\r\n" - "Location: http://127.0.0.1:"_s + serverPort + "/ninja\r\n"_s - "Set-Cookie: Default=1\r\n" - "Set-Cookie: SameSite_None=1; SameSite=None\r\n" - "Set-Cookie: SameSite_None_Secure=1; secure; SameSite=None\r\n" - "Set-Cookie: SameSite_Lax=1; SameSite=Lax\r\n" - "Set-Cookie: SameSite_Strict=1; SameSite=Strict\r\n" - "Content-Length: 0\r\n" - "\r\n"_s); + makeString( + "HTTP/1.1 302 Found\r\n" + "Location: http://127.0.0.1:"_s, serverPort, "/ninja\r\n" + "Set-Cookie: Default=1\r\n" + "Set-Cookie: SameSite_None=1; SameSite=None\r\n" + "Set-Cookie: SameSite_None_Secure=1; secure; SameSite=None\r\n" + "Set-Cookie: SameSite_Lax=1; SameSite=Lax\r\n" + "Set-Cookie: SameSite_Strict=1; SameSite=Strict\r\n" + "Content-Length: 0\r\n" + "\r\n"_s)); } else if (path == "/websocket"_s) { EXPECT_TRUE(strnstr(request.data(), "Host: localhost:", request.size())); EXPECT_FALSE(strnstr(request.data(), "Cookie:", request.size())); @@ -979,15 +984,16 @@ HTTPServer server(TestWebKitAPI::HTTPServer::UseCoroutines::Yes, [&] (Connection request.append(0); if (path == "/redirect"_s) { co_await connection.awaitableSend( - "HTTP/1.1 302 Found\r\n" - "Location: ws://localhost:"_s + serverPort + "/websocket\r\n" - "Set-Cookie: Default=1\r\n" - "Set-Cookie: SameSite_None=1; SameSite=None\r\n" - "Set-Cookie: SameSite_None_Secure=1; secure; SameSite=None\r\n" - "Set-Cookie: SameSite_Lax=1; SameSite=Lax\r\n" - "Set-Cookie: SameSite_Strict=1; SameSite=Strict\r\n" - "Content-Length: 0\r\n" - "\r\n"_s); + makeString( + "HTTP/1.1 302 Found\r\n" + "Location: ws://localhost:"_s, serverPort, "/websocket\r\n" + "Set-Cookie: Default=1\r\n" + "Set-Cookie: SameSite_None=1; SameSite=None\r\n" + "Set-Cookie: SameSite_None_Secure=1; secure; SameSite=None\r\n" + "Set-Cookie: SameSite_Lax=1; SameSite=Lax\r\n" + "Set-Cookie: SameSite_Strict=1; SameSite=Strict\r\n" + "Content-Length: 0\r\n" + "\r\n"_s)); } else if (path == "/websocket"_s) { EXPECT_TRUE(strnstr(request.data(), "Host: localhost:", request.size())); EXPECT_FALSE(strnstr(request.data(), "Cookie:", request.size())); @@ -1020,27 +1026,30 @@ HTTPServer server(TestWebKitAPI::HTTPServer::UseCoroutines::Yes, [&] (Connection request.append(0); if (path == "/redirect"_s) { co_await connection.awaitableSend( - "HTTP/1.1 302 Found\r\n" - "Location: ws://localhost:"_s + serverPort + "/com\r\n" - "Content-Length: 0\r\n" - "\r\n"_s); + makeString( + "HTTP/1.1 302 Found\r\n" + "Location: ws://localhost:"_s, serverPort, "/com\r\n" + "Content-Length: 0\r\n" + "\r\n"_s)); } else if (path == "/com"_s) { co_await connection.awaitableSend( - "HTTP/1.1 302 Found\r\n" - "Location: ws://127.0.0.1:"_s + serverPort + "/redirect2\r\n"_s - "Set-Cookie: Default=1\r\n" - "Set-Cookie: SameSite_None=1; SameSite=None\r\n" - "Set-Cookie: SameSite_None_Secure=1; secure; SameSite=None\r\n" - "Set-Cookie: SameSite_Lax=1; SameSite=Lax\r\n" - "Set-Cookie: SameSite_Strict=1; SameSite=Strict\r\n" - "Content-Length: 0\r\n" - "\r\n"_s); + makeString( + "HTTP/1.1 302 Found\r\n" + "Location: ws://127.0.0.1:"_s, serverPort, "/redirect2\r\n" + "Set-Cookie: Default=1\r\n" + "Set-Cookie: SameSite_None=1; SameSite=None\r\n" + "Set-Cookie: SameSite_None_Secure=1; secure; SameSite=None\r\n" + "Set-Cookie: SameSite_Lax=1; SameSite=Lax\r\n" + "Set-Cookie: SameSite_Strict=1; SameSite=Strict\r\n" + "Content-Length: 0\r\n" + "\r\n"_s)); } else if (path == "/redirect2"_s) { co_await connection.awaitableSend( - "HTTP/1.1 302 Found\r\n" - "Location: ws://localhost:"_s + serverPort + "/websocket\r\n" - "Content-Length: 0\r\n" - "\r\n"_s); + makeString( + "HTTP/1.1 302 Found\r\n" + "Location: ws://localhost:"_s, serverPort, "/websocket\r\n" + "Content-Length: 0\r\n" + "\r\n"_s)); } else if (path == "/websocket"_s) { EXPECT_TRUE(strnstr(request.data(), "Host: localhost:", request.size())); EXPECT_FALSE(strnstr(request.data(), "Cookie:", request.size())); diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKURLSchemeHandler-1.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKURLSchemeHandler-1.mm index 1bdbd357a7fbb..572601fcd2af7 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKURLSchemeHandler-1.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKURLSchemeHandler-1.mm @@ -52,7 +52,7 @@ #import #import #import -#import +#import #import #import #import diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtensionAPIWebNavigation.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtensionAPIWebNavigation.mm index 4eae49216479f..6df8a28c5f150 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtensionAPIWebNavigation.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtensionAPIWebNavigation.mm @@ -31,6 +31,7 @@ #import "WebExtensionUtilities.h" #import +#import namespace TestWebKitAPI { diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebContentProcessDidTerminate.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebContentProcessDidTerminate.mm index 73f1d4584f4c5..f824c60585e45 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebContentProcessDidTerminate.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebContentProcessDidTerminate.mm @@ -180,12 +180,7 @@ - (void)userContentController:(WKUserContentController *)userContentController d EXPECT_TRUE(receivedScriptMessage); } -// FIXME: Investigate why 280261@main seems to have made this time out on open source iOS. -#if PLATFORM(IOS_FAMILY) && !USE(APPLE_INTERNAL_SDK) -TEST(WKNavigation, DISABLED_AutomaticVisibleViewReloadAfterWebProcessCrash) -#else TEST(WKNavigation, AutomaticVisibleViewReloadAfterWebProcessCrash) -#endif { auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]); auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100) configuration:configuration.get() addToWindow:YES]); @@ -220,12 +215,7 @@ - (void)userContentController:(WKUserContentController *)userContentController d EXPECT_FALSE(startedLoad); } -// FIXME: Investigate why 280261@main seems to have made this time out on open source iOS. -#if PLATFORM(IOS_FAMILY) && !USE(APPLE_INTERNAL_SDK) -TEST(WKNavigation, DISABLEDAutomaticHiddenViewDelayedReloadAfterWebProcessCrash) -#else TEST(WKNavigation, AutomaticHiddenViewDelayedReloadAfterWebProcessCrash) -#endif { auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]); auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100) configuration:configuration.get() addToWindow:YES]); diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm index 9413447f6e4e6..9dbec266d7242 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm @@ -60,6 +60,7 @@ #import #import #import +#import // FIXME: Work through enabling on iOS #if ENABLE(NOTIFICATIONS) && ENABLE(NOTIFICATION_EVENT) && PLATFORM(MAC) @@ -286,30 +287,28 @@ static void cleanUpTestWebPushD(NSURL *tempDir) return true; } -static void sendConfigurationWithAuditToken(xpc_connection_t connection) +static WebKit::WebPushD::WebPushDaemonConnectionConfiguration defaultWebPushDaemonConfiguration() { audit_token_t token = { 0, 0, 0, 0, 0, 0, 0, 0 }; mach_msg_type_number_t auditTokenCount = TASK_AUDIT_TOKEN_COUNT; - kern_return_t result = task_info(mach_task_self(), TASK_AUDIT_TOKEN, (task_info_t)(&token), &auditTokenCount); - if (result != KERN_SUCCESS) { - EXPECT_TRUE(false); - return; - } + task_info(mach_task_self(), TASK_AUDIT_TOKEN, (task_info_t)(&token), &auditTokenCount); - WebKit::WebPushD::WebPushDaemonConnectionConfiguration configuration; - configuration.hostAppAuditTokenData = Vector(sizeof(token)); - memcpy(configuration.hostAppAuditTokenData->data(), &token, sizeof(token)); + Vector auditToken(sizeof(token)); + memcpy(auditToken.data(), &token, sizeof(token)); - auto sender = WebPushXPCConnectionMessageSender { connection }; - sender.sendWithoutUsingIPCConnection(Messages::PushClientConnection::UpdateConnectionConfiguration(configuration)); + return { .hostAppAuditTokenData = WTFMove(auditToken) }; } -RetainPtr createAndConfigureConnectionToService(const char* serviceName) +RetainPtr createAndConfigureConnectionToService(const char* serviceName, std::optional configuration = std::nullopt) { auto connection = adoptNS(xpc_connection_create_mach_service(serviceName, dispatch_get_main_queue(), 0)); xpc_connection_set_event_handler(connection.get(), ^(xpc_object_t) { }); xpc_connection_activate(connection.get()); - sendConfigurationWithAuditToken(connection.get()); + auto sender = WebPushXPCConnectionMessageSender { connection.get() }; + + if (!configuration) + configuration = defaultWebPushDaemonConfiguration(); + sender.sendWithoutUsingIPCConnection(Messages::PushClientConnection::UpdateConnectionConfiguration(configuration.value())); return WTFMove(connection); } @@ -327,40 +326,18 @@ static void sendConfigurationWithAuditToken(xpc_connection_t connection) interrupted = true; return; } - if (xpc_get_type(request) != XPC_TYPE_DICTIONARY) - return; - const char* debugMessage = xpc_dictionary_get_string(request, WebKit::WebPushD::protocolDebugMessageKey); - if (!debugMessage) - return; - - NSString *nsMessage = [NSString stringWithUTF8String:debugMessage]; - - // Ignore possible connections/messages from webpushtool - if ([nsMessage hasPrefix:@"[webpushtool "]) - return; - - bool stringMatches = [nsMessage hasPrefix:@"[com.apple.WebKit.TestWebKitAPI"] || [nsMessage hasPrefix:@"[TestWebKitAPI"]; - stringMatches = stringMatches && [nsMessage hasSuffix:@" Turned Debug Mode on"]; - - EXPECT_TRUE(stringMatches); - if (!stringMatches) - WTFLogAlways("String does not match, actual string was %@", nsMessage); - - done = true; }); - xpc_connection_activate(connection.get()); - sendConfigurationWithAuditToken(connection.get()); - - // Enable debug messages, and wait for the resulting debug message - { - auto sender = WebPushXPCConnectionMessageSender { connection.get() }; - sender.sendWithoutUsingIPCConnection(Messages::PushClientConnection::SetDebugModeIsEnabled(true)); - TestWebKitAPI::Util::run(&done); - } - // Sending a message with a higher protocol version should cause the connection to be terminated + // Send a basic message and make sure its reply handler ran. auto sender = WebPushXPCConnectionMessageSender { connection.get() }; + sender.sendWithoutUsingIPCConnection(Messages::PushClientConnection::UpdateConnectionConfiguration(defaultWebPushDaemonConfiguration())); + sender.sendWithAsyncReplyWithoutUsingIPCConnection(Messages::PushClientConnection::GetPushTopicsForTesting(), ^(Vector, Vector) { + done = true; + }); + TestWebKitAPI::Util::run(&done); + + // Sending a message with a higher protocol version should cause the connection to be terminated. sender.setShouldIncrementProtocolVersionForTesting(); __block bool messageReplied = false; sender.sendWithAsyncReplyWithoutUsingIPCConnection(Messages::PushClientConnection::RemoveAllPushSubscriptions(), ^(bool result) { @@ -819,6 +796,44 @@ void injectPushMessage(NSDictionary *apsUserInfo) TestWebKitAPI::Util::run(&done); } + // FIXME: remove this once we add fetchPushMessage to WKWebsiteDataStore. + void didShowNotificationForTesting() + { + auto configuration = defaultWebPushDaemonConfiguration(); + configuration.pushPartitionString = m_pushPartition; + configuration.dataStoreIdentifier = m_dataStoreIdentifier; + + auto utilityConnection = createAndConfigureConnectionToService("org.webkit.webpushtestdaemon.service", WTFMove(configuration)); + auto sender = WebPushXPCConnectionMessageSender { utilityConnection.get() }; + + bool done = false; + sender.sendWithAsyncReplyWithoutUsingIPCConnection(Messages::PushClientConnection::DidShowNotificationForTesting(m_url.get()), [&]() { + done = true; + }); + TestWebKitAPI::Util::run(&done); + } + + // FIXME: switch to WKWebsiteDataStore method once we add that. + std::optional fetchPushMessage() + { + auto configuration = defaultWebPushDaemonConfiguration(); + configuration.pushPartitionString = m_pushPartition; + configuration.dataStoreIdentifier = m_dataStoreIdentifier; + + auto utilityConnection = createAndConfigureConnectionToService("org.webkit.webpushtestdaemon.service", WTFMove(configuration)); + auto sender = WebPushXPCConnectionMessageSender { utilityConnection.get() }; + + std::optional result; + bool done = false; + sender.sendWithAsyncReplyWithoutUsingIPCConnection(Messages::PushClientConnection::GetPendingPushMessage(), [&](std::optional message) { + result = WTFMove(message); + done = true; + }); + TestWebKitAPI::Util::run(&done); + + return result; + } + RetainPtr> fetchPushMessages() { __block bool gotMessages = false; @@ -1343,6 +1358,59 @@ void SetUp() override ASSERT_TRUE(v->hasPushSubscription()); } +TEST_F(WebPushDTest, ImplicitSilentPushTimerCancelledOnShowingNotification) +{ + for (auto& v : webViews()) + v->subscribe(); + ASSERT_EQ(subscribedTopicsCount(), webViews().size()); + + for (auto& v : webViews()) { + ASSERT_TRUE(v->hasPushSubscription()); + + for (unsigned i = 0; i < WebKit::WebPushD::maxSilentPushCount; i++) { + v->injectPushMessage(@{ }); + auto message = v->fetchPushMessage(); + ASSERT_TRUE(message.has_value()); + v->didShowNotificationForTesting(); + } + + [NSThread sleepForTimeInterval:(WebKit::WebPushD::silentPushTimeoutForTesting.seconds() + 0.5)]; + ASSERT_TRUE(v->hasPushSubscription()); + } +} + +TEST_F(WebPushDTest, ImplicitSilentPushTimerCausesUnsubscribe) +{ + for (auto& v : webViews()) { + v->subscribe(); + v->disableShowNotifications(); + } + ASSERT_EQ(subscribedTopicsCount(), webViews().size()); + + int i = 1; + for (auto& v : webViews()) { + ASSERT_TRUE(v->hasPushSubscription()); + + for (unsigned i = 0; i < WebKit::WebPushD::maxSilentPushCount; i++) { + v->injectPushMessage(@{ }); + auto message = v->fetchPushMessage(); + ASSERT_TRUE(message.has_value()); + } + + bool unsubscribed = false; + TestWebKitAPI::Util::waitForConditionWithLogging([&] { + unsubscribed = !v->hasPushSubscription(); + [NSThread sleepForTimeInterval:0.25]; + return unsubscribed; + }, 5, @"Timed out waiting for push subscription to be unsubscribed."); + ASSERT_TRUE(unsubscribed); + + // Unsubscribing from this data store should not affect subscriptions in other data stores. + ASSERT_EQ(subscribedTopicsCount(), webViews().size() - i); + i++; + } +} + TEST_F(WebPushDTest, TooManySilentPushesCausesUnsubscribe) { for (auto& v : webViews()) { diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebsitePolicies.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebsitePolicies.mm index 9594df76a42cc..6e477ab7e7c3c 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebsitePolicies.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebsitePolicies.mm @@ -55,6 +55,7 @@ #import #import #import +#import #import #import @@ -2048,7 +2049,7 @@ - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSStrin { "/subframe2.html"_s, { ""_s } }, }, TestWebKitAPI::HTTPServer::Protocol::Http); - server.addResponse("/subframe1.html"_s, { ""_s }); + server.addResponse("/subframe1.html"_s, { makeString(""_s) }); __block auto port = server.port(); auto handler = adoptNS([TestURLSchemeHandler new]); diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WritingTools.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WritingTools.mm index 6a500126978fa..e5106c8a8bc11 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WritingTools.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WritingTools.mm @@ -221,6 +221,31 @@ - (NSString *)ensureAttachmentForImageElement return attachmentIdentifier.autorelease(); } +- (void)attemptEditingForTesting +{ + [self stringByEvaluatingJavaScript:@"document.execCommand('selectAll', false, null)"]; + [self _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil]; + [self _synchronouslyExecuteEditCommand:@"InsertText" argument:@"Test"]; +} + +- (void)waitForContentValue:(NSString *)expectedValue { + do { + if ([[self contentsAsStringWithoutNBSP] isEqual:expectedValue]) + break; + + TestWebKitAPI::Util::runFor(0.1_s); + } while (true); +} + +- (void)waitForSelectionValue:(NSString *)expectedValue { + do { + if ([[self stringByEvaluatingJavaScript:@"window.getSelection().toString()"] isEqual: expectedValue]) + break; + + TestWebKitAPI::Util::runFor(0.1_s); + } while (true); +}; + @end struct ColorExpectation { @@ -334,8 +359,13 @@ static void checkColor(WebCore::CocoaColor *color, std::optional

        %@

        ", originalText]]); + + [webView focusDocumentBodyAndSelectAll]; + + __block bool finished = false; + [[webView writingToolsDelegate] willBeginWritingToolsSession:session.get() requestContexts:^(NSArray *contexts) { + EXPECT_EQ(1UL, contexts.count); + + [webView attemptEditingForTesting]; + EXPECT_WK_STREQ(originalText, [webView contentsAsString]); + + [[webView writingToolsDelegate] proofreadingSession:session.get() didReceiveSuggestions:@[ adoptNS([[WTTextSuggestion alloc] initWithOriginalRange:NSMakeRange(2, 9) replacement:@"frequently"]).get() ] processedRange:NSMakeRange(0, 9) inContext:contexts.firstObject finished:NO]; + + [webView attemptEditingForTesting]; + EXPECT_WK_STREQ([originalText stringByReplacingOccurrencesOfString:@"frequetly" withString:@"frequently"], [webView contentsAsString]); + + [[webView writingToolsDelegate] proofreadingSession:session.get() didReceiveSuggestions:@[ adoptNS([[WTTextSuggestion alloc] initWithOriginalRange:NSMakeRange(21, 5) replacement:@"things"]).get() ] processedRange:NSMakeRange(0, 27) inContext:contexts.firstObject finished:YES]; + + EXPECT_WK_STREQ(proofreadText, [webView contentsAsString]); + + [webView attemptEditingForTesting]; + EXPECT_WK_STREQ("Test", [webView contentsAsString]); + + [[webView writingToolsDelegate] didEndWritingToolsSession:session.get() accepted:YES]; + EXPECT_WK_STREQ("Test", [webView contentsAsString]); + + finished = true; + }]; + + TestWebKitAPI::Util::run(&finished); +} + +TEST(WritingTools, CompositionWithAttemptedEditing) +{ + auto session = adoptNS([[WTSession alloc] initWithType:WTSessionTypeComposition textViewDelegate:nil]); + + NSString *source = @"
        An NSAttributedString object manage character strings and associated sets of attributes (for example, font and kerning) that apply to individual characters or ranges of characters in the string. An association of characters and their attributes is called an attributed string. The cluster's two public classes, NSAttributedString and NSMutableAttributedString, declare the programmatic interface for read-only attbuted strings and modifiable attributed strings, respectively.

        An attributed string identifies attributes by name, using an NSDictionary object to store a value under the specified name. You can assign any attribute name/value pair you wish to a range of characters—it is up to your application to interpret custom attributes (see Attributed String Programming Guide). If you are using attributed strings with the Core Text framework, you can also use the attribute keys defined by that framework
        "; + + auto webView = adoptNS([[WritingToolsWKWebView alloc] initWithHTMLString:source]); + [webView focusDocumentBodyAndSelectAll]; + + __block bool finished = false; + + [[webView writingToolsDelegate] willBeginWritingToolsSession:session.get() requestContexts:^(NSArray *contexts) { + NSString *originalText = @"An NSAttributedString object manage character strings and associated sets of attributes (for example, font and kerning) that apply to individual characters or ranges of characters in the string. An association of characters and their attributes is called an attributed string. The cluster's two public classes, NSAttributedString and NSMutableAttributedString, declare the programmatic interface for read-only attbuted strings and modifiable attributed strings, respectively.\n\nAn attributed string identifies attributes by name, using an NSDictionary object to store a value under the specified name. You can assign any attribute name/value pair you wish to a range of characters—it is up to your application to interpret custom attributes (see Attributed String Programming Guide). If you are using attributed strings with the Core Text framework, you can also use the attribute keys defined by that framework"; + EXPECT_WK_STREQ(originalText, contexts.firstObject.attributedText.string); + + [webView attemptEditingForTesting]; + EXPECT_WK_STREQ(originalText, [webView contentsAsStringWithoutNBSP]); + + [[webView writingToolsDelegate] writingToolsSession:session.get() didReceiveAction:WTActionCompositionRestart]; + + auto attributedText = adoptNS([[NSAttributedString alloc] initWithString:@"NSAttributedString is a class in the Objective-C programming language that represents a string with attributes. It allows you to set and retrieve attributes for individual characters or ranges of characters in the string. NSAttributedString is a subclass of NSMutableAttributedString, which is a class that allows you to modify the attributes of an attributed string."]); + + [[webView writingToolsDelegate] compositionSession:session.get() didReceiveText:attributedText.get() replacementRange:NSMakeRange(0, 476) inContext:contexts.firstObject finished:NO]; + + [webView attemptEditingForTesting]; + EXPECT_WK_STREQ("NSAttributedString is a class in the Objective-C programming language that represents a string with attributes. It allows you to set and retrieve attributes for individual characters or ranges of characters in the string. NSAttributedString is a subclass of NSMutableAttributedString, which is a class that allows you to modify the attributes of an attributed string.\nAn attributed string identifies attributes by name, using an NSDictionary object to store a value under the specified name. You can assign any attribute name/value pair you wish to a range of characters—it is up to your application to interpret custom attributes (see Attributed String Programming Guide). If you are using attributed strings with the Core Text framework, you can also use the attribute keys defined by that framework", [webView contentsAsStringWithoutNBSP]); + + NSString *result = @"NSAttributedString is a class in the Objective-C programming language that represents a string with attributes. It allows you to set and retrieve attributes for individual characters or ranges of characters in the string. NSAttributedString is a subclass of NSMutableAttributedString, which is a class that allows you to modify the attributes of an attributed string.\n\nAn attributed string is a type of string that can contain attributes. Attributes are properties that can be set on a string and can be accessed and manipulated by other code. The attributes can be used to add custom information to a string, such as colors, fonts, or images. The Core Text framework provides a set of attribute keys that can be used to define the attributes that can be used in attributed strings."; + + auto longerAttributedText = adoptNS([[NSAttributedString alloc] initWithString:result]); + + [[webView writingToolsDelegate] compositionSession:session.get() didReceiveText:longerAttributedText.get() replacementRange:NSMakeRange(0, 910) inContext:contexts.firstObject finished:YES]; + + + [webView waitForContentValue:result]; + EXPECT_WK_STREQ(result, [webView contentsAsStringWithoutNBSP]); + + [webView attemptEditingForTesting]; + EXPECT_WK_STREQ(result, [webView contentsAsStringWithoutNBSP]); + + [[webView writingToolsDelegate] didEndWritingToolsSession:session.get() accepted:YES]; + [webView attemptEditingForTesting]; + EXPECT_WK_STREQ("Test", [webView contentsAsStringWithoutNBSP]); + + finished = true; + }]; + + TestWebKitAPI::Util::run(&finished); +} + TEST(WritingTools, CompositionWithUndoAfterEnding) { auto session = adoptNS([[WTSession alloc] initWithType:WTSessionTypeComposition textViewDelegate:nil]); @@ -623,6 +739,7 @@ static void checkColor(WebCore::CocoaColor *color, std::optional

        Hey do you wanna go see a movie this weekend

        "]); + auto webView = adoptNS([[WritingToolsWKWebView alloc] initWithHTMLString:@"

        Hey do you wanna go see a movie this weekend - start

        "]); [webView focusDocumentBodyAndSelectAll]; __block bool finished = false; @@ -696,32 +815,34 @@ static void checkColor(WebCore::CocoaColor *color, std::optional *contexts) { EXPECT_EQ(1UL, contexts.count); - EXPECT_WK_STREQ(@"Hey do you wanna go see a movie this weekend", contexts.firstObject.attributedText.string); + EXPECT_WK_STREQ(@"Hey do you wanna go see a movie this weekend - start", contexts.firstObject.attributedText.string); __auto_type rewrite = ^(NSString *rewrittenText) { [[webView writingToolsDelegate] writingToolsSession:session.get() didReceiveAction:WTActionCompositionRestart]; auto attributedText = adoptNS([[NSAttributedString alloc] initWithString:rewrittenText]); - [[webView writingToolsDelegate] compositionSession:session.get() didReceiveText:attributedText.get() replacementRange:NSMakeRange(0, 44) inContext:contexts.firstObject finished:NO]; + [[webView writingToolsDelegate] compositionSession:session.get() didReceiveText:attributedText.get() replacementRange:NSMakeRange(0, 52) inContext:contexts.firstObject finished:NO]; + [webView waitForNextPresentationUpdate]; - [[webView writingToolsDelegate] compositionSession:session.get() didReceiveText:attributedText.get() replacementRange:NSMakeRange(0, 44) inContext:contexts.firstObject finished:YES]; + [[webView writingToolsDelegate] compositionSession:session.get() didReceiveText:attributedText.get() replacementRange:NSMakeRange(0, 52) inContext:contexts.firstObject finished:YES]; + [webView waitForContentValue:rewrittenText]; EXPECT_WK_STREQ(rewrittenText, [webView contentsAsStringWithoutNBSP]); }; - rewrite(@"A"); - rewrite(@"B"); + rewrite(@"Would you like to see a movie? - first"); + rewrite(@"I'm going to a film, would you like to join? - second"); [webView _synchronouslyExecuteEditCommand:@"Undo" argument:@""]; - EXPECT_WK_STREQ(@"A", [webView contentsAsStringWithoutNBSP]); + EXPECT_WK_STREQ(@"Would you like to see a movie? - first", [webView contentsAsStringWithoutNBSP]); - rewrite(@"C"); + rewrite(@"Hey I'm going to a movie and I'd love for you to join me! - third"); [webView _synchronouslyExecuteEditCommand:@"Undo" argument:@""]; - EXPECT_WK_STREQ(@"A", [webView contentsAsStringWithoutNBSP]); + EXPECT_WK_STREQ(@"Would you like to see a movie? - first", [webView contentsAsStringWithoutNBSP]); - rewrite(@"D"); + rewrite(@"We're watching a movie if you want to come! - fourth"); finished = true; }]; @@ -751,9 +872,11 @@ static void checkColor(WebCore::CocoaColor *color, std::optional *attributes, NSError *error) { EXPECT_NULL(error); @@ -1066,7 +1197,6 @@ static void checkColor(WebCore::CocoaColor *color, std::optional

        Sunset in Cupertino

        "]); [webView focusDocumentBodyAndSelectAll]; + auto waitForValue = [webView]() { + do { + if ([webView stringByEvaluatingJavaScript:@"document.querySelector('img').complete"].boolValue) + break; + + TestWebKitAPI::Util::runFor(0.1_s); + } while (true); + }; + __block bool finished = false; [[webView writingToolsDelegate] willBeginWritingToolsSession:session.get() requestContexts:^(NSArray *contexts) { [[webView writingToolsDelegate] writingToolsSession:session.get() didReceiveAction:WTActionCompositionRestart]; @@ -1107,8 +1246,8 @@ static void checkColor(WebCore::CocoaColor *color, std::optionalHello World" writingToolsBehavior:PlatformWritingToolsBehaviorComplete]); - [webView focusDocumentBodyAndSelectAll]; + + [webView selectAll:nil]; + [webView waitForNextPresentationUpdate]; EXPECT_EQ([webView writingToolsBehaviorForTesting], PlatformWritingToolsBehaviorLimited); } @@ -1798,7 +1943,7 @@ - (void)textSystemWillBeginEditingDuringSessionWithUUID:(NSUUID *)sessionUUID EXPECT_EQ(0U, [webView transparentContentMarkerCount:@"document.body.childNodes[0]"]); - [[webView writingToolsDelegate] didBeginWritingToolsSession:session.get() contexts:contexts]; + [[webView writingToolsDelegate] writingToolsSession:session.get() didReceiveAction:WTActionCompositionRestart]; waitForValue(1U); EXPECT_EQ(1U, [webView transparentContentMarkerCount:@"document.body.childNodes[0]"]); @@ -1905,10 +2050,6 @@ InstanceMethodSwizzler swizzler(PAL::getWTWritingToolsClass(), @selector(schedul NSMenuItem *writingToolsMenuItem = [proposedMenu itemWithIdentifier:_WKMenuItemIdentifierWritingTools]; EXPECT_NULL(writingToolsMenuItem); #endif - -#if PLATFORM(IOS_FAMILY) - EXPECT_EQ(UIWritingToolsBehaviorNone, [[webView effectiveTextInputTraits] writingToolsBehavior]); -#endif } TEST(WritingTools, APIWithBehaviorDefault) @@ -1952,10 +2093,6 @@ InstanceMethodSwizzler swizzler(PAL::getWTWritingToolsClass(), @selector(schedul NSMenuItem *writingToolsMenuItem = [proposedMenu itemWithIdentifier:_WKMenuItemIdentifierWritingTools]; EXPECT_NOT_NULL(writingToolsMenuItem); #endif - -#if PLATFORM(IOS_FAMILY) - EXPECT_EQ(UIWritingToolsBehaviorLimited, [[webView effectiveTextInputTraits] writingToolsBehavior]); -#endif } TEST(WritingTools, APIWithBehaviorComplete) @@ -1999,10 +2136,6 @@ InstanceMethodSwizzler swizzler(PAL::getWTWritingToolsClass(), @selector(schedul NSMenuItem *writingToolsMenuItem = [proposedMenu itemWithIdentifier:_WKMenuItemIdentifierWritingTools]; EXPECT_NOT_NULL(writingToolsMenuItem); #endif - -#if PLATFORM(IOS_FAMILY) - EXPECT_EQ(UIWritingToolsBehaviorComplete, [[webView effectiveTextInputTraits] writingToolsBehavior]); -#endif } @interface IsWritingToolsAvailableKVOWrapper : NSObject @@ -2184,7 +2317,6 @@ InstanceMethodSwizzler swizzler(PAL::getWTWritingToolsClass(), @selector(schedul RetainPtr attributedText = adoptNS([[NSAttributedString alloc] initWithString:@"A"]); [[webView writingToolsDelegate] compositionSession:session.get() didReceiveText:attributedText.get() replacementRange:NSMakeRange(0, 0) inContext:contexts.firstObject finished:YES]; - [webView waitForNextPresentationUpdate]; EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"!document.body.innerHTML.includes('font-size: 12px')"].boolValue); @@ -2224,14 +2356,18 @@ InstanceMethodSwizzler swizzler(PAL::getWTWritingToolsClass(), @selector(schedul [[webView writingToolsDelegate] writingToolsSession:session.get() didReceiveAction:WTActionCompositionRestart]; - RetainPtr attributedText = adoptNS([[NSAttributedString alloc] initWithString:@""]); + RetainPtr attributedText = adoptNS([[NSAttributedString alloc] initWithString:@"DD"]); [[webView writingToolsDelegate] compositionSession:session.get() didReceiveText:attributedText.get() replacementRange:NSMakeRange(0, 5) inContext:contexts.firstObject finished:YES]; + [webView waitForSelectionValue:@"DD"]; + auto selectionAfterDidReceiveText = [webView stringByEvaluatingJavaScript:@"window.getSelection().toString()"]; + EXPECT_WK_STREQ(@"DD", selectionAfterDidReceiveText); + [[webView writingToolsDelegate] didEndWritingToolsSession:session.get() accepted:YES]; auto selectionAfterEnd = [webView stringByEvaluatingJavaScript:@"window.getSelection().toString()"]; - EXPECT_WK_STREQ(@"BBBB CCCC\n\nXXXX YYYY ZZZZ", selectionAfterEnd); + EXPECT_WK_STREQ(@"DD", selectionAfterEnd); finished = true; }]; @@ -2268,14 +2404,31 @@ InstanceMethodSwizzler swizzler(PAL::getWTWritingToolsClass(), @selector(schedul [[webView writingToolsDelegate] writingToolsSession:session.get() didReceiveAction:WTActionCompositionRestart]; - RetainPtr attributedText = adoptNS([[NSAttributedString alloc] initWithString:@""]); + RetainPtr attributedText = adoptNS([[NSAttributedString alloc] initWithString:@"DD"]); [[webView writingToolsDelegate] compositionSession:session.get() didReceiveText:attributedText.get() replacementRange:NSMakeRange(0, 5) inContext:contexts.firstObject finished:YES]; + [webView waitForSelectionValue:@"DD"]; + auto selectionAfterDidReceiveText = [webView stringByEvaluatingJavaScript:@"window.getSelection().toString()"]; + EXPECT_WK_STREQ(@"DD", selectionAfterDidReceiveText); + + // Simulate clicking/tapping away to end the session. + NSString *setSelectionToCaret = @"" + "(() => {" + " const first = document.getElementById('p').childNodes[0].firstChild;" + " const range = document.createRange();" + " range.setStart(first, 0);" + " range.setEnd(first, 0);" + " " + " var selection = window.getSelection();" + " selection.removeAllRanges();" + " selection.addRange(range);" + "})();"; + [webView stringByEvaluatingJavaScript:setSelectionToCaret]; [[webView writingToolsDelegate] didEndWritingToolsSession:session.get() accepted:YES]; auto selectionAfterEnd = [webView stringByEvaluatingJavaScript:@"window.getSelection().toString()"]; - EXPECT_WK_STREQ(@"BBBB CCCC", selectionAfterEnd); + EXPECT_WK_STREQ(@"", selectionAfterEnd); finished = true; }]; @@ -2312,6 +2465,7 @@ InstanceMethodSwizzler swizzler(PAL::getWTWritingToolsClass(), @selector(schedul RetainPtr attributedText = adoptNS([[NSAttributedString alloc] initWithString:@"Z"]); [[webView writingToolsDelegate] compositionSession:session.get() didReceiveText:attributedText.get() replacementRange:NSMakeRange(0, 0) inContext:contexts.firstObject finished:YES]; + [webView waitForNextPresentationUpdate]; [[webView writingToolsDelegate] didEndWritingToolsSession:session.get() accepted:YES]; @@ -2372,4 +2526,58 @@ InstanceMethodSwizzler swizzler(PAL::getWTWritingToolsClass(), @selector(schedul TestWebKitAPI::Util::run(&finished); } +TEST(WritingTools, SmartReplyWithInsertedSpace) +{ + auto session = adoptNS([[WTSession alloc] initWithType:WTSessionTypeComposition textViewDelegate:nil]); + [session setCompositionSessionType:WTCompositionSessionTypeSmartReply]; + + auto webView = adoptNS([[WritingToolsWKWebView alloc] initWithHTMLString:@""]); + + NSString *script = @"document.body.focus()"; +#if PLATFORM(IOS_FAMILY) + [webView evaluateJavaScriptAndWaitForInputSessionToChange:script]; +#else + [webView stringByEvaluatingJavaScript:script]; +#endif + [webView waitForNextPresentationUpdate]; + +#if PLATFORM(IOS_FAMILY) + [[webView textInputContentView] insertText:@" "]; +#else + [webView insertText:@" "]; +#endif + + NSString *modifySelectionJavascript = @"" + "(() => {" + " const first = document.querySelector('body').childNodes[0];" + " const range = document.createRange();" + " range.setStart(first, 0);" + " range.setEnd(first, 1);" + " " + " var selection = window.getSelection();" + " selection.removeAllRanges();" + " selection.addRange(range);" + "})();"; + + [webView stringByEvaluatingJavaScript:modifySelectionJavascript]; + [webView waitForNextPresentationUpdate]; + + __block bool finished = false; + [[webView writingToolsDelegate] willBeginWritingToolsSession:session.get() requestContexts:^(NSArray *contexts) { + [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil]; + + EXPECT_EQ(1UL, contexts.count); + + RetainPtr attributedText = adoptNS([[NSAttributedString alloc] initWithString:@"I want ice cream."]); + + [[webView writingToolsDelegate] compositionSession:session.get() didReceiveText:attributedText.get() replacementRange:contexts.firstObject.range inContext:contexts.firstObject finished:NO]; + + EXPECT_WK_STREQ(@"I want ice cream.", [webView contentsAsString]); + + finished = true; + }]; + + TestWebKitAPI::Util::run(&finished); +} + #endif diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm index 2bd670cb2a715..8c58013d7bc94 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm @@ -60,7 +60,7 @@ #import #import #import -#import +#import static bool webAuthenticationPanelRan = false; static bool webAuthenticationPanelFailed = false; diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/large-videos-with-audio.html b/Tools/TestWebKitAPI/Tests/WebKitCocoa/large-videos-with-audio.html index 0fdde2f2aa648..589985d2fee95 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/large-videos-with-audio.html +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/large-videos-with-audio.html @@ -9,10 +9,20 @@ } catch(e) { } }, 0); } + + function handleCanPlayThrough(event) { + event.target.canPlayThrough = true; + } + diff --git a/Tools/TestWebKitAPI/Tests/WebKitGLib/TestWebKitSettings.cpp b/Tools/TestWebKitAPI/Tests/WebKitGLib/TestWebKitSettings.cpp index b0640a5b3db21..882d975b2b17f 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitGLib/TestWebKitSettings.cpp +++ b/Tools/TestWebKitAPI/Tests/WebKitGLib/TestWebKitSettings.cpp @@ -36,7 +36,7 @@ #include #include #include -#include +#include static WebKitTestServer* gServer; diff --git a/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm b/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm index 0f886f6d352e7..dc7b6a08c155d 100644 --- a/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm +++ b/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm @@ -904,6 +904,49 @@ static BOOL shouldSimulateKeyboardInputOnTextInsertionOverride(id, SEL) EXPECT_FALSE([overrideUndoManager canUndo]); } +TEST(KeyboardInputTests, NewUndoGroupClosesPreviousTypingCommand) +{ + auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)]); + auto inputDelegate = adoptNS([TestInputDelegate new]); + [inputDelegate setFocusStartsInputSessionPolicyHandler:[](WKWebView *, id<_WKFocusedElementInfo>) { + return _WKFocusStartsInputSessionPolicyAllow; + }]; + __block bool didStartInputSession = false; + [inputDelegate setDidStartInputSessionHandler:^(WKWebView *, id<_WKFormInputSession>) { + didStartInputSession = true; + }]; + [webView _setInputDelegate:inputDelegate.get()]; + [webView synchronouslyLoadHTMLString:@""]; + [webView objectByEvaluatingJavaScript:@"document.body.focus()"]; + Util::run(&didStartInputSession); + + RetainPtr contentView = [webView textInputContentView]; + auto insertText = ^(NSString *text) { + [contentView insertText:text]; + [webView waitForNextPresentationUpdate]; + }; + + insertText(@"Foo"); + + [[contentView undoManager] beginUndoGrouping]; + [webView waitForNextPresentationUpdate]; + + insertText(@" "); + insertText(@"bar"); + + [[contentView undoManager] endUndoGrouping]; + [webView waitForNextPresentationUpdate]; + EXPECT_WK_STREQ("Foo bar", [webView contentsAsString]); + + [[contentView undoManager] undo]; + [webView waitForNextPresentationUpdate]; + EXPECT_WK_STREQ("Foo", [webView contentsAsString]); + + [[contentView undoManager] undo]; + [webView waitForNextPresentationUpdate]; + EXPECT_WK_STREQ("", [webView contentsAsString]); +} + static UIView * nilResizableSnapshotViewFromRect(id, SEL, CGRect, BOOL, UIEdgeInsets) { return nil; diff --git a/Tools/TestWebKitAPI/Tests/ios/Viewport.mm b/Tools/TestWebKitAPI/Tests/ios/Viewport.mm index 8a5b6cc6f0e8d..fff2035a7b2c1 100644 --- a/Tools/TestWebKitAPI/Tests/ios/Viewport.mm +++ b/Tools/TestWebKitAPI/Tests/ios/Viewport.mm @@ -38,6 +38,7 @@ #import #import #import +#import namespace TestWebKitAPI { diff --git a/Tools/TestWebKitAPI/Tests/mac/MouseEventTests.mm b/Tools/TestWebKitAPI/Tests/mac/MouseEventTests.mm index 6216355afb815..fd4b7d70509f2 100644 --- a/Tools/TestWebKitAPI/Tests/mac/MouseEventTests.mm +++ b/Tools/TestWebKitAPI/Tests/mac/MouseEventTests.mm @@ -35,6 +35,7 @@ #import #import #import +#import namespace TestWebKitAPI { diff --git a/Tools/TestWebKitAPI/cocoa/HTTPServer.mm b/Tools/TestWebKitAPI/cocoa/HTTPServer.mm index 29b7258912eba..9ebb276b8d672 100644 --- a/Tools/TestWebKitAPI/cocoa/HTTPServer.mm +++ b/Tools/TestWebKitAPI/cocoa/HTTPServer.mm @@ -36,6 +36,7 @@ #import #import #import +#import #import #import diff --git a/Tools/TestWebKitAPI/win/PlatformUtilitiesWin.cpp b/Tools/TestWebKitAPI/win/PlatformUtilitiesWin.cpp index 8727ed0ee96fb..1a2324106a728 100644 --- a/Tools/TestWebKitAPI/win/PlatformUtilitiesWin.cpp +++ b/Tools/TestWebKitAPI/win/PlatformUtilitiesWin.cpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace TestWebKitAPI { namespace Util { diff --git a/Tools/TestWebKitAPI/wpe/PlatformWebViewWPE.cpp b/Tools/TestWebKitAPI/wpe/PlatformWebViewWPE.cpp index 7a94d1ac5dbfc..f0b8145f7087f 100644 --- a/Tools/TestWebKitAPI/wpe/PlatformWebViewWPE.cpp +++ b/Tools/TestWebKitAPI/wpe/PlatformWebViewWPE.cpp @@ -64,6 +64,7 @@ PlatformWebView::PlatformWebView(WKPageRef relatedPage) PlatformWebView::~PlatformWebView() { + WKRelease(m_view); delete m_window; } diff --git a/Tools/WebKitTestRunner/Configurations/WebKitTestRunnerApp-iOS-simulator.entitlements b/Tools/WebKitTestRunner/Configurations/WebKitTestRunnerApp-iOS-simulator.entitlements new file mode 100644 index 0000000000000..2ccd5a5a43d19 --- /dev/null +++ b/Tools/WebKitTestRunner/Configurations/WebKitTestRunnerApp-iOS-simulator.entitlements @@ -0,0 +1,20 @@ + + + + + keychain-access-groups + + com.apple.WebKitTestRunnerApp + + com.apple.developer.WebKit.ServiceWorkers + + com.apple.Pasteboard.paste-unchecked + + com.apple.private.webkit.adattributiond.testing + + com.apple.developer.web-browser + + com.apple.developer.web-browser-engine.host + + + diff --git a/Tools/WebKitTestRunner/Configurations/WebKitTestRunnerApp-iOS.entitlements b/Tools/WebKitTestRunner/Configurations/WebKitTestRunnerApp-iOS.entitlements index 8460e4d1a22da..e7f274777fdf5 100644 --- a/Tools/WebKitTestRunner/Configurations/WebKitTestRunnerApp-iOS.entitlements +++ b/Tools/WebKitTestRunner/Configurations/WebKitTestRunnerApp-iOS.entitlements @@ -14,9 +14,5 @@ com.apple.runningboard.launch_extensions - com.apple.developer.web-browser - - com.apple.developer.web-browser-engine.host - diff --git a/Tools/WebKitTestRunner/Configurations/WebKitTestRunnerApp-visionOS.entitlements b/Tools/WebKitTestRunner/Configurations/WebKitTestRunnerApp-visionOS.entitlements deleted file mode 100644 index dddfaab06ec81..0000000000000 --- a/Tools/WebKitTestRunner/Configurations/WebKitTestRunnerApp-visionOS.entitlements +++ /dev/null @@ -1,16 +0,0 @@ - - - - - keychain-access-groups - - com.apple.WebKitTestRunnerApp - - com.apple.developer.WebKit.ServiceWorkers - - com.apple.Pasteboard.paste-unchecked - - com.apple.private.webkit.adattributiond.testing - - - diff --git a/Tools/WebKitTestRunner/Configurations/WebKitTestRunnerApp.xcconfig b/Tools/WebKitTestRunner/Configurations/WebKitTestRunnerApp.xcconfig index e2ae48ffe6c0a..10914e5013a30 100644 --- a/Tools/WebKitTestRunner/Configurations/WebKitTestRunnerApp.xcconfig +++ b/Tools/WebKitTestRunner/Configurations/WebKitTestRunnerApp.xcconfig @@ -38,6 +38,7 @@ STRIP_STYLE = debugging; SKIP_INSTALL[sdk=macosx*] = YES; EXCLUDED_SOURCE_FILE_NAMES[config=Production] = $(inherited) *.appex +EXCLUDED_SOURCE_FILE_NAMES[sdk=iphoneos*] = $(inherited) *.appex; EXCLUDED_SOURCE_FILE_NAMES[sdk=macosx*] = $(inherited) ios/* AppDelegate.m *.appex; EXCLUDED_SOURCE_FILE_NAMES[sdk=appletv*] = $(inherited) ios/Launch.storyboard *.appex; EXCLUDED_SOURCE_FILE_NAMES[sdk=watch*] = $(inherited) ios/Launch.storyboard *.appex; @@ -45,9 +46,10 @@ EXCLUDED_SOURCE_FILE_NAMES[sdk=xr*] = $(inherited) *.appex; TARGETED_DEVICE_FAMILY = 1,2,4,7; -CODE_SIGN_ENTITLEMENTS[sdk=iphone*] = Configurations/WebKitTestRunnerApp-iOS.entitlements; +CODE_SIGN_ENTITLEMENTS[sdk=iphonesimulator*] = Configurations/WebKitTestRunnerApp-iOS-simulator.entitlements; +CODE_SIGN_ENTITLEMENTS[sdk=iphoneos*] = Configurations/WebKitTestRunnerApp-iOS.entitlements; CODE_SIGN_ENTITLEMENTS[sdk=appletv*] = Configurations/WebKitTestRunnerApp-watchOS.entitlements; CODE_SIGN_ENTITLEMENTS[sdk=watch*] = Configurations/WebKitTestRunnerApp-watchOS.entitlements; -CODE_SIGN_ENTITLEMENTS[sdk=xr*] = Configurations/WebKitTestRunnerApp-visionOS.entitlements; +CODE_SIGN_ENTITLEMENTS[sdk=xr*] = Configurations/WebKitTestRunnerApp-iOS.entitlements; APPLY_RULES_IN_COPY_FILES = YES; diff --git a/Tools/WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp b/Tools/WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp index edc2d9c7125c3..bdf0c324fbe66 100644 --- a/Tools/WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp +++ b/Tools/WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include @@ -352,6 +353,8 @@ void InjectedBundlePage::prepare() WKBundleClearHistoryForTesting(m_page); + WKBundleFrameClearOpener(WKBundlePageGetMainFrame(m_page)); + WKBundlePageSetTracksRepaints(m_page, false); // Force consistent "responsive" behavior for WebPage::eventThrottlingDelay() for testing. Tests can override via internals. diff --git a/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp b/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp index 34cbfa73f4450..a0255307a5a04 100644 --- a/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp +++ b/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp @@ -56,8 +56,8 @@ #include #include #include +#include #include -#include namespace WTR { diff --git a/Tools/WebKitTestRunner/InjectedBundle/atspi/AccessibilityUIElementAtspi.cpp b/Tools/WebKitTestRunner/InjectedBundle/atspi/AccessibilityUIElementAtspi.cpp index 6d56f83db1dc2..363211441207c 100644 --- a/Tools/WebKitTestRunner/InjectedBundle/atspi/AccessibilityUIElementAtspi.cpp +++ b/Tools/WebKitTestRunner/InjectedBundle/atspi/AccessibilityUIElementAtspi.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include diff --git a/Tools/WebKitTestRunner/InjectedBundle/cocoa/AccessibilityTextMarkerRangeCocoa.mm b/Tools/WebKitTestRunner/InjectedBundle/cocoa/AccessibilityTextMarkerRangeCocoa.mm new file mode 100644 index 0000000000000..38da56589b9f4 --- /dev/null +++ b/Tools/WebKitTestRunner/InjectedBundle/cocoa/AccessibilityTextMarkerRangeCocoa.mm @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2011 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +#import "config.h" +#import "AccessibilityTextMarkerRange.h" + +namespace WTR { + +bool AccessibilityTextMarkerRange::isEqual(AccessibilityTextMarkerRange* other) +{ + return [(__bridge id)platformTextMarkerRange() isEqual:(__bridge id)other->platformTextMarkerRange()]; +} + +} // namespace WTR + diff --git a/Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityTextMarkerRangeMac.mm b/Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityTextMarkerRangeMac.mm deleted file mode 100644 index f5f5892a098a6..0000000000000 --- a/Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityTextMarkerRangeMac.mm +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2011 Apple Inc. All Rights Reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. 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. - * - * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. - */ - -#import "config.h" -#import "AccessibilityTextMarkerRange.h" - -namespace WTR { - -bool AccessibilityTextMarkerRange::isEqual(AccessibilityTextMarkerRange* other) -{ - return [(__bridge id)platformTextMarkerRange() isEqual:(__bridge id)other->platformTextMarkerRange()]; -} - -} // namespace WTR - diff --git a/Tools/WebKitTestRunner/PlatformWin.cmake b/Tools/WebKitTestRunner/PlatformWin.cmake index 2cbb643cc6cfc..6a4d95b62ff27 100644 --- a/Tools/WebKitTestRunner/PlatformWin.cmake +++ b/Tools/WebKitTestRunner/PlatformWin.cmake @@ -26,3 +26,5 @@ list(APPEND TestRunnerInjectedBundle_SOURCES InjectedBundle/win/InjectedBundleWin.cpp InjectedBundle/win/TestRunnerWin.cpp ) + +add_executable(WebKitTestRunnerWS win/WebKitTestRunnerWS.cpp) diff --git a/Tools/WebKitTestRunner/TestController.cpp b/Tools/WebKitTestRunner/TestController.cpp index 618344236f3c0..076ad1f7005f7 100644 --- a/Tools/WebKitTestRunner/TestController.cpp +++ b/Tools/WebKitTestRunner/TestController.cpp @@ -94,7 +94,7 @@ #include #include #include -#include +#include #if PLATFORM(COCOA) #include @@ -1175,11 +1175,6 @@ bool TestController::resetStateToConsistentValues(const TestOptions& options, Re WKPageClearUserMediaState(m_mainWebView->page()); - WKPageSetPageZoomFactor(m_mainWebView->page(), 1); - WKPageSetTextZoomFactor(m_mainWebView->page(), 1); - - WKPageClearOpenerForTesting(m_mainWebView->page()); - // Reset notification permissions m_webNotificationProvider.reset(); m_notificationOriginsToDenyOnPrompt.clear(); @@ -2706,27 +2701,22 @@ void TestController::downloadDidStart(WKDownloadRef download) WKStringRef TestController::decideDestinationWithSuggestedFilename(WKDownloadRef download, WKStringRef filename) { - String suggestedFilename = toWTFString(filename); + auto suggestedFilename = toWTFString(filename); - if (m_shouldLogDownloadCallbacks) { - StringBuilder builder; - builder.append("Downloading URL with suggested filename \""_s); - builder.append(suggestedFilename); - builder.append("\"\n"_s); - m_currentInvocation->outputText(builder.toString()); - } + if (m_shouldLogDownloadCallbacks) + m_currentInvocation->outputText(makeString("Downloading URL with suggested filename \""_s, suggestedFilename, "\"\n"_s)); const char* dumpRenderTreeTemp = libraryPathForTesting(); if (!dumpRenderTreeTemp) return nullptr; - String temporaryFolder = String::fromUTF8(dumpRenderTreeTemp); + auto temporaryFolder = String::fromUTF8(dumpRenderTreeTemp); if (suggestedFilename.isEmpty()) suggestedFilename = "Unknown"_s; - String destination = temporaryFolder + pathSeparator + suggestedFilename; + auto destination = makeString(temporaryFolder, pathSeparator, suggestedFilename); if (auto downloadIndex = m_downloadIndex++) - destination = destination + downloadIndex; + destination = makeString(destination, downloadIndex); if (FileSystem::fileExists(destination)) FileSystem::deleteFile(destination); diff --git a/Tools/WebKitTestRunner/TestInvocation.cpp b/Tools/WebKitTestRunner/TestInvocation.cpp index 9442212677aea..e4d97f4cec1b4 100644 --- a/Tools/WebKitTestRunner/TestInvocation.cpp +++ b/Tools/WebKitTestRunner/TestInvocation.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #if PLATFORM(MAC) && !PLATFORM(IOS_FAMILY) #include @@ -166,9 +167,7 @@ void TestInvocation::invoke() { TestController::singleton().configureViewForTest(*this); - auto page = TestController::singleton().mainWebView()->page(); - - WKPageSetAddsVisitedLinks(page, false); + WKPageSetAddsVisitedLinks(TestController::singleton().mainWebView()->page(), false); m_textOutput.clear(); @@ -176,6 +175,11 @@ void TestInvocation::invoke() WKHTTPCookieStoreSetHTTPCookieAcceptPolicy(WKWebsiteDataStoreGetHTTPCookieStore(TestController::singleton().websiteDataStore()), kWKHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain, nullptr, nullptr); + // FIXME: We should clear out visited links here. + + WKPageSetPageZoomFactor(TestController::singleton().mainWebView()->page(), 1); + WKPageSetTextZoomFactor(TestController::singleton().mainWebView()->page(), 1); + postPageMessage("BeginTest", createTestSettingsDictionary()); m_startedTesting = true; diff --git a/Tools/WebKitTestRunner/WebKitTestRunner.xcodeproj/project.pbxproj b/Tools/WebKitTestRunner/WebKitTestRunner.xcodeproj/project.pbxproj index 63acc27b0d1d5..e86c685c42f91 100644 --- a/Tools/WebKitTestRunner/WebKitTestRunner.xcodeproj/project.pbxproj +++ b/Tools/WebKitTestRunner/WebKitTestRunner.xcodeproj/project.pbxproj @@ -79,7 +79,7 @@ 29A8FCCB145EF02E009045A6 /* JSAccessibilityTextMarker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 29210EE1144CDE6789815EE5 /* JSAccessibilityTextMarker.cpp */; }; 29A8FCDD145F0337009045A6 /* JSAccessibilityTextMarkerRange.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 29A8FCE1345E7021006AA5A6 /* JSAccessibilityTextMarkerRange.cpp */; }; 29A8FCE2145F037B009045A6 /* AccessibilityTextMarkerRange.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 29A8FCE1145F037B009045A6 /* AccessibilityTextMarkerRange.cpp */; }; - 29A8FCE5145F0464009045A6 /* AccessibilityTextMarkerRangeMac.mm in Sources */ = {isa = PBXBuildFile; fileRef = 29A8FCE4145F0464009045A6 /* AccessibilityTextMarkerRangeMac.mm */; }; + 29A8FCE5145F0464009045A6 /* AccessibilityTextMarkerRangeCocoa.mm in Sources */ = {isa = PBXBuildFile; fileRef = 29A8FCE4145F0464009045A6 /* AccessibilityTextMarkerRangeCocoa.mm */; }; 2D058E0922E2EE2200E4C145 /* UIScriptControllerCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D058E0822E2EE2200E4C145 /* UIScriptControllerCocoa.h */; }; 2D058E0B22E2EF6D00E4C145 /* UIScriptControllerMac.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D058E0A22E2EF6D00E4C145 /* UIScriptControllerMac.h */; }; 2DB6187E1D7D58D400978D19 /* CoreGraphicsTestSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DB6187D1D7D58D400978D19 /* CoreGraphicsTestSPI.h */; }; @@ -325,7 +325,7 @@ 29A8FCE1145F037B009045A6 /* AccessibilityTextMarkerRange.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AccessibilityTextMarkerRange.cpp; sourceTree = ""; }; 29A8FCE1345E7021006AA5A6 /* JSAccessibilityTextMarkerRange.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSAccessibilityTextMarkerRange.cpp; sourceTree = ""; }; 29A8FCE1345E7021006AA5A7 /* JSAccessibilityTextMarkerRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSAccessibilityTextMarkerRange.h; sourceTree = ""; }; - 29A8FCE4145F0464009045A6 /* AccessibilityTextMarkerRangeMac.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AccessibilityTextMarkerRangeMac.mm; sourceTree = ""; }; + 29A8FCE4145F0464009045A6 /* AccessibilityTextMarkerRangeCocoa.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AccessibilityTextMarkerRangeCocoa.mm; sourceTree = ""; }; 2D058E0822E2EE2200E4C145 /* UIScriptControllerCocoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIScriptControllerCocoa.h; sourceTree = ""; }; 2D058E0A22E2EF6D00E4C145 /* UIScriptControllerMac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIScriptControllerMac.h; sourceTree = ""; }; 2D0BEE1722EAD1360092B738 /* UIScriptControllerIOS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UIScriptControllerIOS.h; sourceTree = ""; }; @@ -474,10 +474,10 @@ E1BA671D1742DA5A00C20251 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; E1C642C417CBCD4C00D66A3C /* WebKitTestRunnerPasteboard.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WebKitTestRunnerPasteboard.mm; sourceTree = ""; }; E1C642C517CBCD4C00D66A3C /* WebKitTestRunnerPasteboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebKitTestRunnerPasteboard.h; sourceTree = ""; }; - E3660D6A2C2F663800BA7138 /* WebKitTestRunnerApp-visionOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "WebKitTestRunnerApp-visionOS.entitlements"; sourceTree = ""; }; E372BC352C08E29C006DFE67 /* NetworkingExtension.appex */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.extensionkit-extension"; path = NetworkingExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; E372BC372C08EB01006DFE67 /* GPUExtension.appex */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.extensionkit-extension"; path = GPUExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; E372BC392C08F323006DFE67 /* WebContentExtension.appex */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.extensionkit-extension"; path = WebContentExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + E3C2C0312C34DEFC000C7F2E /* WebKitTestRunnerApp-iOS-simulator.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "WebKitTestRunnerApp-iOS-simulator.entitlements"; sourceTree = ""; }; F4010B7C24DA204800A876E2 /* PoseAsClass.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = PoseAsClass.mm; path = ../TestRunnerShared/cocoa/PoseAsClass.mm; sourceTree = ""; }; F4010B7D24DA204800A876E2 /* PoseAsClass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PoseAsClass.h; path = ../TestRunnerShared/cocoa/PoseAsClass.h; sourceTree = ""; }; F415C22A27AF52D30028F505 /* UIPasteboardConsistencyEnforcer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UIPasteboardConsistencyEnforcer.h; sourceTree = ""; }; @@ -678,6 +678,7 @@ children = ( 299E2AA41E3DEE140065DC30 /* AccessibilityCommonCocoa.h */, 29210EAB144CACB200835BB6 /* AccessibilityCommonCocoa.mm */, + 29A8FCE4145F0464009045A6 /* AccessibilityTextMarkerRangeCocoa.mm */, 65EB859F11EC67CC0034D300 /* ActivateFontsCocoa.mm */, 0FEB909E1905A776000FDBF3 /* InjectedBundlePageCocoa.mm */, ); @@ -816,7 +817,6 @@ isa = PBXGroup; children = ( 8034C6611487636400AC32E9 /* AccessibilityControllerMac.mm */, - 29A8FCE4145F0464009045A6 /* AccessibilityTextMarkerRangeMac.mm */, 29210EAB144CACB200835BB5 /* AccessibilityUIElementMac.mm */, BC8DAD771316D7B900EC96FC /* InjectedBundleMac.mm */, ); @@ -942,8 +942,8 @@ BC25197111D15E61002EBC01 /* InjectedBundle.xcconfig */, 57A0062C22976E4D00AD08BD /* WebKitTestRunner.entitlements */, A18510381B9ADF2200744AEB /* WebKitTestRunner.xcconfig */, + E3C2C0312C34DEFC000C7F2E /* WebKitTestRunnerApp-iOS-simulator.entitlements */, 9B0D132E2036D346008FC8FB /* WebKitTestRunnerApp-iOS.entitlements */, - E3660D6A2C2F663800BA7138 /* WebKitTestRunnerApp-visionOS.entitlements */, 311183AA212B1AC70077BCE0 /* WebKitTestRunnerApp-watchOS.entitlements */, A18510391B9ADFF800744AEB /* WebKitTestRunnerApp.xcconfig */, BC251A1811D16795002EBC01 /* WebKitTestRunnerLibrary.xcconfig */, @@ -1372,7 +1372,7 @@ 2E63ED8B1891AD7E002A7AFC /* AccessibilityTextMarkerIOS.mm in Sources */, 29210EB5144CACD500835BB5 /* AccessibilityTextMarkerMac.mm in Sources */, 29A8FCE2145F037B009045A6 /* AccessibilityTextMarkerRange.cpp in Sources */, - 29A8FCE5145F0464009045A6 /* AccessibilityTextMarkerRangeMac.mm in Sources */, + 29A8FCE5145F0464009045A6 /* AccessibilityTextMarkerRangeCocoa.mm in Sources */, 29210EAE144CACB700835BB5 /* AccessibilityUIElement.cpp in Sources */, 2E63EDA11891B291002A7AFC /* AccessibilityUIElementIOS.mm in Sources */, 29210EDA144CC3EA00835BB5 /* AccessibilityUIElementMac.mm in Sources */, diff --git a/Tools/WebKitTestRunner/cocoa/CrashReporterInfo.mm b/Tools/WebKitTestRunner/cocoa/CrashReporterInfo.mm index ee1cb3db433ea..946a1ef4d3aa9 100644 --- a/Tools/WebKitTestRunner/cocoa/CrashReporterInfo.mm +++ b/Tools/WebKitTestRunner/cocoa/CrashReporterInfo.mm @@ -30,6 +30,7 @@ #import #import #import +#import namespace WTR { diff --git a/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm b/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm index 8f744766d0179..d6512142002ed 100644 --- a/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm +++ b/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm @@ -66,6 +66,7 @@ #import #import #import +#import #import diff --git a/Tools/WebKitTestRunner/cocoa/TestWebsiteDataStoreDelegate.h b/Tools/WebKitTestRunner/cocoa/TestWebsiteDataStoreDelegate.h index 69e0437083ffe..4a72aad3874bc 100644 --- a/Tools/WebKitTestRunner/cocoa/TestWebsiteDataStoreDelegate.h +++ b/Tools/WebKitTestRunner/cocoa/TestWebsiteDataStoreDelegate.h @@ -48,5 +48,4 @@ - (NSString*)lastUpdatedBackgroundFetchIdentifier; - (NSArray*)reportedWindowProxyAccessDomains; - (void)clearReportedWindowProxyAccessDomains; -- (NSData *)webCryptoMasterKey; @end diff --git a/Tools/WebKitTestRunner/cocoa/TestWebsiteDataStoreDelegate.mm b/Tools/WebKitTestRunner/cocoa/TestWebsiteDataStoreDelegate.mm index 744de58eefaef..b2221091f9e8d 100644 --- a/Tools/WebKitTestRunner/cocoa/TestWebsiteDataStoreDelegate.mm +++ b/Tools/WebKitTestRunner/cocoa/TestWebsiteDataStoreDelegate.mm @@ -153,12 +153,11 @@ - (void)clearReportedWindowProxyAccessDomains [_windowProxyAccessDomains removeAllObjects]; } -- (NSData *)webCryptoMasterKey +- (void)webCryptoMasterKey:(void (^)(NSData *))completionHandler { // Not so random key constexpr size_t keyLength = 16; uint8_t keyBytes[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf }; - NSData *key = [NSData dataWithBytes:keyBytes length:(keyLength)]; - return key; + completionHandler([NSData dataWithBytes:keyBytes length:(keyLength)]); } @end diff --git a/Tools/WebKitTestRunner/cocoa/WKTextExtractionTestingHelpers.mm b/Tools/WebKitTestRunner/cocoa/WKTextExtractionTestingHelpers.mm index 895d6244c564d..5ac1434f08b1d 100644 --- a/Tools/WebKitTestRunner/cocoa/WKTextExtractionTestingHelpers.mm +++ b/Tools/WebKitTestRunner/cocoa/WKTextExtractionTestingHelpers.mm @@ -29,6 +29,7 @@ #import "WKTextExtractionItem.h" #import #import +#import #import #import #import diff --git a/Tools/WebKitTestRunner/gtk/TestControllerGtk.cpp b/Tools/WebKitTestRunner/gtk/TestControllerGtk.cpp index c8ff875f26332..da4d10226e3d5 100644 --- a/Tools/WebKitTestRunner/gtk/TestControllerGtk.cpp +++ b/Tools/WebKitTestRunner/gtk/TestControllerGtk.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #if USE(SKIA) diff --git a/Tools/WebKitTestRunner/libwpe/PlatformWebViewClient.h b/Tools/WebKitTestRunner/libwpe/PlatformWebViewClient.h index 88162ae76cc85..1b438567d54a2 100644 --- a/Tools/WebKitTestRunner/libwpe/PlatformWebViewClient.h +++ b/Tools/WebKitTestRunner/libwpe/PlatformWebViewClient.h @@ -33,7 +33,10 @@ namespace WTR { class PlatformWebViewClient { WTF_MAKE_FAST_ALLOCATED; public: - virtual ~PlatformWebViewClient() = default; + virtual ~PlatformWebViewClient() + { + WKRelease(m_view); + } virtual void addToWindow() = 0; virtual void removeFromWindow() = 0; diff --git a/Tools/WebKitTestRunner/win/TestControllerWin.cpp b/Tools/WebKitTestRunner/win/TestControllerWin.cpp index 5b05192e2abd1..aa52dc4546592 100644 --- a/Tools/WebKitTestRunner/win/TestControllerWin.cpp +++ b/Tools/WebKitTestRunner/win/TestControllerWin.cpp @@ -53,7 +53,7 @@ static const double maximumWaitForWebProcessToCrash = 60; static LONG WINAPI exceptionFilter(EXCEPTION_POINTERS*) { - fputs("#CRASHED\n", stderr); + fprintf(stderr, "#CRASHED - WebKitTestRunner (pid %lu)\n", GetCurrentProcessId()); fflush(stderr); return EXCEPTION_CONTINUE_SEARCH; } diff --git a/Tools/WebKitTestRunner/win/WebKitTestRunnerWS.cpp b/Tools/WebKitTestRunner/win/WebKitTestRunnerWS.cpp new file mode 100644 index 0000000000000..e48684094cf40 --- /dev/null +++ b/Tools/WebKitTestRunner/win/WebKitTestRunnerWS.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2024 Sony Interactive Entertainment Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. + */ + +#include +#include +#include + +#pragma comment(lib, "user32") + +#define DESKTOP_NAME L"Default" + +bool replace(std::wstring& str, const std::wstring& from, const std::wstring& to) +{ + const auto pos = str.find(from); + const int len = from.length(); + if (pos == std::wstring::npos) + return false; + str.replace(pos, len, to); + return true; +} + +bool containsShowWindowOption(std::wstring& cmd) +{ + return cmd.find(L"--show-window") != std::wstring::npos; +} + +int wmain() +{ + std::wstring cmd = GetCommandLine(); + if (!replace(cmd, L"WebKitTestRunnerWS", L"WebKitTestRunner")) { + std::wcout << L"WebKitTestRunnerWS not found: " << cmd << std::endl; + return EXIT_FAILURE; + } + + std::wstring winStaName = L"WebKit_" + std::to_wstring(GetCurrentProcessId()); + HDESK desktop = nullptr; + if (!containsShowWindowOption(cmd)) { + HWINSTA windowStation = CreateWindowStation(winStaName.data(), CWF_CREATE_ONLY, GENERIC_ALL, nullptr); + if (windowStation) { + if (SetProcessWindowStation(windowStation)) { + desktop = CreateDesktop(DESKTOP_NAME, nullptr, nullptr, 0, GENERIC_ALL, nullptr); + if (!desktop) + std::wcout << L"CreateDesktop failed with error: " << GetLastError() << std::endl; + } else + std::wcout << L"SetProcessWindowStation failed with error: " << GetLastError() << std::endl; + } + } + + std::wstring desktopName = winStaName + L'\\' + DESKTOP_NAME; + PROCESS_INFORMATION processInformation { }; + STARTUPINFO startupInfo { }; + startupInfo.cb = sizeof(startupInfo); + startupInfo.lpDesktop = desktop ? desktopName.data() : nullptr; + if (!CreateProcess(0, cmd.data(), nullptr, nullptr, true, 0, 0, 0, &startupInfo, &processInformation)) { + std::wcout << L"CreateProcess failed: " << GetLastError() << std::endl; + return EXIT_FAILURE; + } + WaitForSingleObject(processInformation.hProcess, INFINITE); + DWORD exitCode; + GetExitCodeProcess(processInformation.hProcess, &exitCode); + return exitCode; +} diff --git a/Tools/WebKitTestRunner/wpe/TestControllerWPE.cpp b/Tools/WebKitTestRunner/wpe/TestControllerWPE.cpp index edf57043e4718..2083d592a91b7 100644 --- a/Tools/WebKitTestRunner/wpe/TestControllerWPE.cpp +++ b/Tools/WebKitTestRunner/wpe/TestControllerWPE.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #if USE(CAIRO) #include diff --git a/Tools/gtk/manifest.txt.in b/Tools/gtk/manifest.txt.in index 38e25f82c047a..4f153dbd347be 100644 --- a/Tools/gtk/manifest.txt.in +++ b/Tools/gtk/manifest.txt.in @@ -100,6 +100,7 @@ exclude Tools/gtk/make-dist.py exclude Tools/gtk/ycm_extra_conf.py file Tools/glib/common.py +file Tools/glib/fix-glib-resources-depfile.pl file Tools/glib/generate-inspector-gresource-manifest.py file Tools/glib/generate-modern-media-controls-gresource-manifest.py file Tools/glib/generate-pdfjs-resource-manifest.py diff --git a/Tools/wpe/manifest.txt.in b/Tools/wpe/manifest.txt.in index 2f2a0260b9b43..beb02d39a8df6 100644 --- a/Tools/wpe/manifest.txt.in +++ b/Tools/wpe/manifest.txt.in @@ -94,6 +94,7 @@ exclude Tools/wpe/jhbuild.modules exclude Tools/wpe/jhbuildrc file Tools/glib/common.py +file Tools/glib/fix-glib-resources-depfile.pl file Tools/glib/generate-inspector-gresource-manifest.py file Tools/glib/generate-modern-media-controls-gresource-manifest.py file Tools/glib/generate-pdfjs-resource-manifest.py diff --git a/Websites/perf.webkit.org/public/api/build-requests.php b/Websites/perf.webkit.org/public/api/build-requests.php index 7aa47a35e6d10..84b6e4885a726 100644 --- a/Websites/perf.webkit.org/public/api/build-requests.php +++ b/Websites/perf.webkit.org/public/api/build-requests.php @@ -76,8 +76,7 @@ function update_builds($db, $updates) { $is_build = $request_row['request_order'] < 0; if ($is_build) { $db->query_and_fetch_all('UPDATE build_requests SET request_status = \'failed\' - WHERE request_group = $1 AND request_order > $2', - array($request_row['request_group'], $request_row['request_order'])); + WHERE request_group = $1 AND request_order >= 0', array($request_row['request_group'])); } $fields_to_update['status'] = 'failed'; $db->update_row('build_requests', 'request', array('id' => $id), $fields_to_update); diff --git a/Websites/perf.webkit.org/public/api/test-groups.php b/Websites/perf.webkit.org/public/api/test-groups.php index 36f3fd488b9ee..d4dde9e7b2280 100644 --- a/Websites/perf.webkit.org/public/api/test-groups.php +++ b/Websites/perf.webkit.org/public/api/test-groups.php @@ -35,7 +35,7 @@ function main($path) { } elseif ($path[0] == 'ready-for-notification') { $test_groups = $db->query_and_fetch_all("SELECT * FROM analysis_test_groups WHERE EXISTS(SELECT 1 FROM build_requests - WHERE request_group = testgroup_id + WHERE request_group = testgroup_id AND request_order >= 0 AND request_status IN ('pending', 'scheduled', 'running', 'canceled')) IS FALSE AND testgroup_needs_notification IS TRUE AND testgroup_hidden IS FALSE"); diff --git a/Websites/perf.webkit.org/public/include/uploaded-file-helpers.php b/Websites/perf.webkit.org/public/include/uploaded-file-helpers.php index 2010c315118df..3710eeb983f29 100644 --- a/Websites/perf.webkit.org/public/include/uploaded-file-helpers.php +++ b/Websites/perf.webkit.org/public/include/uploaded-file-helpers.php @@ -102,7 +102,7 @@ function upload_file_in_transaction($db, $input_file, $remote_user, $additional_ $db->begin_transaction(); $file_row = $db->select_or_insert_row('uploaded_files', 'file', - array('sha256' => $uploaded_file['sha256'], 'deleted_at' => null), $uploaded_file, '*'); + array('sha256' => $uploaded_file['sha256']), $uploaded_file, '*'); if (!$file_row) exit_with_error('FailedToInsertFileData'); @@ -127,6 +127,13 @@ function upload_file_in_transaction($db, $input_file, $remote_user, $additional_ exit_with_error('FailedToMoveUploadedFile'); } + if ($file_row['file_deleted_at']) { + if (!$db->update_row('uploaded_files', 'file', array('id' => $file_row['file_id']), array('deleted_at' => null))) { + $db->rollback_transaction(); + exit_with_error('FailedToClearDeletedAtField'); + } + } + $db->commit_transaction(); return format_uploaded_file($file_row); diff --git a/Websites/perf.webkit.org/public/v3/components/instant-file-uploader.js b/Websites/perf.webkit.org/public/v3/components/instant-file-uploader.js index 693051595ecfa..2307000621adc 100644 --- a/Websites/perf.webkit.org/public/v3/components/instant-file-uploader.js +++ b/Websites/perf.webkit.org/public/v3/components/instant-file-uploader.js @@ -164,7 +164,7 @@ class InstantFileUploader extends ComponentBase { const uploadProgress = this._uploadProgress; for (let file of files) { UploadedFile.fetchUploadedFileWithIdenticalHash(file).then((uploadedFile) => { - if (uploadedFile) { + if (uploadedFile && !uploadedFile.deletedAt()) { this._didUploadFile(file, uploadedFile); return; } diff --git a/Websites/perf.webkit.org/public/v3/models/build-request.js b/Websites/perf.webkit.org/public/v3/models/build-request.js index 8ae33411b13e6..4b097a96a8da4 100644 --- a/Websites/perf.webkit.org/public/v3/models/build-request.js +++ b/Websites/perf.webkit.org/public/v3/models/build-request.js @@ -91,7 +91,7 @@ class BuildRequest extends DataModelObject { buildId() { return this._buildId; } createdAt() { return this._createdAt; } - async findBuildRequestWithSameRoots() + async findBuildRequestWithSameRoots(scheduledRequestIdList = []) { if (!this.isBuild()) return null; @@ -121,7 +121,7 @@ class BuildRequest extends DataModelObject { continue; if (buildRequest.hasCompleted()) return buildRequest; - if (buildRequest.isScheduled() + if ((buildRequest.isScheduled() || scheduledRequestIdList.includes(+buildRequest.id())) && (!scheduledBuildRequest || buildRequest.createdAt() < scheduledBuildRequest.createdAt())) { scheduledBuildRequest = buildRequest; } diff --git a/Websites/perf.webkit.org/server-tests/api-test-groups.js b/Websites/perf.webkit.org/server-tests/api-test-groups.js index b4eb6db035b0f..e3c6b41ea9d97 100644 --- a/Websites/perf.webkit.org/server-tests/api-test-groups.js +++ b/Websites/perf.webkit.org/server-tests/api-test-groups.js @@ -91,6 +91,29 @@ describe('/api/test-groups', function () { assert.deepStrictEqual(content.commits, []); assert.deepStrictEqual(content.uploadedFiles, []); }); + + it('should list a test group if all test type requests are failed but at least one build type request is still running', async () => { + const database = TestServer.database(); + await MockData.addMockBuildRequestsWithRoots(database, ['running', 'failed', 'failed', 'failed', 'completed', 'completed', 'completed', 'running']); + const content = await TestServer.remoteAPI().getJSON('/api/test-groups/ready-for-notification'); + assert.strictEqual(content.status, 'OK'); + assert.strictEqual(content.testGroups.length, 1); + const testGroup = content.testGroups[0]; + assert.strictEqual(parseInt(testGroup.id), 700); + assert.strictEqual(parseInt(testGroup.task), 600); + assert.strictEqual(testGroup.name, 'test with root built'); + assert.strictEqual(testGroup.author, null); + assert.strictEqual(testGroup.hidden, false); + assert.strictEqual(testGroup.needsNotification, true); + assert.deepStrictEqual(testGroup.buildRequests, ['800', '801', '802', '803']); + assert.deepStrictEqual(testGroup.commitSets, ['500', '501', '500', '501']); + + assert.strictEqual(content.buildRequests.length, 4); + assert.strictEqual(content.buildRequests[0].status, 'running'); + assert.strictEqual(content.buildRequests[1].status, 'failed'); + assert.strictEqual(content.buildRequests[2].status, 'failed'); + assert.strictEqual(content.buildRequests[3].status, 'failed'); + }); }); describe('/api/test-groups/', () => { diff --git a/Websites/perf.webkit.org/server-tests/privileged-api-upload-file-tests.js b/Websites/perf.webkit.org/server-tests/privileged-api-upload-file-tests.js index 47e938a233498..4ff9aa9ab3d90 100644 --- a/Websites/perf.webkit.org/server-tests/privileged-api-upload-file-tests.js +++ b/Websites/perf.webkit.org/server-tests/privileged-api-upload-file-tests.js @@ -121,7 +121,7 @@ describe('/privileged-api/upload-file', function () { }); }); - it('should re-upload the file when the previously uploaded file had been deleted', () => { + it('should re-upload the file when the previously uploaded file had been deleted and reuse the file id', () => { const db = TestServer.database(); const limitInMB = TestServer.testConfig().uploadFileLimitInMB; let uploadedFile1; @@ -139,26 +139,21 @@ describe('/privileged-api/upload-file', function () { uploadedFile2 = response['uploadedFile']; return db.selectAll('uploaded_files', 'id'); }).then((rows) => { - assert.notStrictEqual(uploadedFile1.id, uploadedFile2.id); - assert.strictEqual(rows.length, 2); + assert.strictEqual(uploadedFile1.id, uploadedFile2.id); + assert.strictEqual(rows.length, 1); assert.strictEqual(rows[0].id, parseInt(uploadedFile1.id)); - assert.strictEqual(rows[1].id, parseInt(uploadedFile2.id)); assert.strictEqual(rows[0].filename, 'some.dat'); assert.strictEqual(rows[0].filename, uploadedFile1.filename); - assert.strictEqual(rows[1].filename, 'other.dat'); - assert.strictEqual(rows[1].filename, uploadedFile2.filename); assert.strictEqual(parseInt(rows[0].size), limitInMB * 1024 * 1024); assert.strictEqual(parseInt(rows[0].size), parseInt(uploadedFile1.size)); assert.strictEqual(parseInt(rows[0].size), parseInt(uploadedFile2.size)); - assert.strictEqual(rows[0].size, rows[1].size); + assert.strictEqual(rows[0].deleted_at, null); assert.strictEqual(rows[0].sha256, '5256ec18f11624025905d057d6befb03d77b243511ac5f77ed5e0221ce6d84b5'); assert.strictEqual(rows[0].sha256, uploadedFile1.sha256); assert.strictEqual(rows[0].sha256, uploadedFile2.sha256); - assert.strictEqual(rows[0].sha256, rows[1].sha256); assert.strictEqual(rows[0].extension, '.dat'); - assert.strictEqual(rows[1].extension, '.dat'); }); }); diff --git a/Websites/perf.webkit.org/server-tests/tools-buildbot-triggerable-tests.js b/Websites/perf.webkit.org/server-tests/tools-buildbot-triggerable-tests.js index e7ba42492e4d3..36b199267241e 100644 --- a/Websites/perf.webkit.org/server-tests/tools-buildbot-triggerable-tests.js +++ b/Websites/perf.webkit.org/server-tests/tools-buildbot-triggerable-tests.js @@ -8,7 +8,7 @@ const MockRemoteAPI = require('../unit-tests/resources/mock-remote-api.js').Mock const TestServer = require('./resources/test-server.js'); const prepareServerTest = require('./resources/common-operations.js').prepareServerTest; const MockLogger = require('./resources/mock-logger.js').MockLogger; - +const crypto = require('crypto'); function assertRequestAndResolve(request, method, url, content) { assert.strictEqual(request.method, method); @@ -1072,7 +1072,10 @@ describe('BuildbotTriggerable', function () { }); it('should skip updating a completed build request whose test group has completed and not listed in a triggerable', async () => { - await MockData.addMockBuildRequestsWithRoots(TestServer.database(), ['completed', 'completed', 'completed', 'completed', 'pending', 'pending', 'pending', 'pending']); + const db = TestServer.database(); + await MockData.addMockBuildRequestsWithRoots(db, ['completed', 'completed', 'completed', 'completed', 'pending', 'pending', 'pending', 'pending']); + await db.insert('uploaded_files', {id: 103, filename: 'root-103', extension: '.tgz', size: 1, sha256: crypto.createHash('sha256').update('root-103').digest('hex')}); + await db.query('UPDATE commit_set_items SET commitset_root_file=103 WHERE commitset_set=501'); await Manifest.fetch(); const config = MockData.mockTestSyncConfigWithPatchAcceptingBuilder(); const logger = new MockLogger; @@ -2094,6 +2097,602 @@ describe('BuildbotTriggerable', function () { assert.equal(BuildRequest.findById(701).status(), 'pending'); assert.equal(BuildRequest.findById(701).statusUrl(), null); }); + + it('should only schedule one of two build requests if they can share roots the first build type request is finished', async () => { + const db = TestServer.database(); + await MockData.addMockBuildRequestsWithRoots(db, + ['completed', 'pending', 'pending', 'pending', 'pending', 'pending', 'pending', 'pending']); + + await Manifest.fetch(); + const config = MockData.mockTestSyncConfigWithPatchAcceptingBuilder(); + const logger = new MockLogger; + const workerInfo = {name: 'sync-worker', password: 'password'}; + const triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, workerInfo, 2, logger); + const syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); + assertRequestAndResolve(MockRemoteAPI.requests[0], 'GET', MockData.buildbotBuildersURL(), MockData.mockBuildbotBuilders()); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(BuildRequest.all().length, 8); + let buildRequest = BuildRequest.findById(800); + let anotherBuildRequest = BuildRequest.findById(900); + assert.strictEqual(buildRequest.status(), 'completed'); + assert.strictEqual(anotherBuildRequest.status(), 'pending'); + assert.strictEqual(buildRequest.statusUrl(), 'http://build.webkit.org/buids/1'); + assert.strictEqual(anotherBuildRequest.statusUrl(), null); + let commitSet = buildRequest.commitSet(); + let anotherCommitSet = anotherBuildRequest.commitSet(); + assert.ok(commitSet.equalsIgnoringRoot(anotherCommitSet)); + assert.ok(!commitSet.equals(anotherCommitSet)); + + assert.strictEqual(MockRemoteAPI.requests.length, 3); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.pendingBuildsUrl('some tester')); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.pendingBuildsUrl('some-builder-1')); + MockRemoteAPI.requests[1].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.pendingBuildsUrl('some builder 2')); + MockRemoteAPI.requests[2].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 3); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.recentBuildsUrl('some tester', 2)); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.recentBuildsUrl('some-builder-1', 2)); + MockRemoteAPI.requests[1].resolve(MockData.finishedBuild({buildRequestId: 800})); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.recentBuildsUrl('some builder 2', 2)); + MockRemoteAPI.requests[2].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 1); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'POST'); + assert.strictEqual(MockRemoteAPI.requests[0].url, '/api/v2/forceschedulers/force-some-builder-1'); + assert.deepEqual(MockRemoteAPI.requests[0].data, {jsonrpc: '2.0', method: 'force', id: '801', + params: {forcescheduler: 'force-some-builder-1', os: '10.11 15A284', wk: '192736', + 'wk-patch': 'http://localhost:8180/api/uploaded-file/102.txt', 'build-request-id': '801'}}); + MockRemoteAPI.requests[0].resolve('OK') + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 3); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.pendingBuildsUrl('some tester')); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.pendingBuildsUrl('some-builder-1')); + MockRemoteAPI.requests[1].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.pendingBuildsUrl('some builder 2')); + MockRemoteAPI.requests[2].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 3); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.recentBuildsUrl('some tester', 2)); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.recentBuildsUrl('some-builder-1', 2)); + MockRemoteAPI.requests[1].resolve({'builds': [MockData.runningBuildData({buildRequestId: 801}), MockData.finishedBuildData({buildRequestId: 800})]}); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.recentBuildsUrl('some builder 2', 2)); + MockRemoteAPI.requests[2].resolve({}); + MockRemoteAPI.reset(); + + await syncPromise; + await BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithPatchAcceptingBuilder().triggerableName); + assert.strictEqual(BuildRequest.all().length, 8); + buildRequest = BuildRequest.findById(800); + anotherBuildRequest = BuildRequest.findById(900); + assert.strictEqual(buildRequest.status(), 'completed'); + assert.strictEqual(anotherBuildRequest.status(), 'completed'); + assert.strictEqual(buildRequest.statusUrl(), 'http://build.webkit.org/buids/1'); + assert.strictEqual(anotherBuildRequest.statusUrl(), 'http://build.webkit.org/buids/1'); + }); + + it('should only schedule one of two build requests and schedule the second one if the first one failed', async () => { + const db = TestServer.database(); + await MockData.addMockBuildRequestsWithRoots(db, + ['pending', 'pending', 'pending', 'pending', 'pending', 'pending', 'pending', 'pending']); + await Promise.all([ + db.query('UPDATE commit_set_items SET commitset_root_file=NULL WHERE commitset_set=500'), + db.query('UPDATE build_requests SET request_url=NULL WHERE request_id=800') + ]); + + await Manifest.fetch(); + const config = MockData.mockTestSyncConfigWithPatchAcceptingBuilder(); + config.builders['builder-3'] = {'builder': 'other builder', properties: {forcescheduler: 'force-other-builder'}, 'supportedRepetitionTypes': ['alternating', 'sequential', 'paired-parallel']}; + config.buildConfigurations[0].builders.push('builder-3'); + const logger = new MockLogger; + const workerInfo = {name: 'sync-worker', password: 'password'}; + const triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, workerInfo, 2, logger); + + let syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); + assertRequestAndResolve(MockRemoteAPI.requests[0], 'GET', MockData.buildbotBuildersURL(), MockData.mockBuildbotBuilders()); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(BuildRequest.all().length, 8); + let buildRequest800 = BuildRequest.findById(800); + let buildRequest801 = BuildRequest.findById(801); + let buildRequest900 = BuildRequest.findById(900); + let buildRequest901 = BuildRequest.findById(901); + assert.strictEqual(buildRequest800.status(), 'pending'); + assert.strictEqual(buildRequest801.status(), 'pending'); + assert.strictEqual(buildRequest900.status(), 'pending'); + assert.strictEqual(buildRequest901.status(), 'pending'); + assert.strictEqual(buildRequest800.statusUrl(), null); + assert.strictEqual(buildRequest801.statusUrl(), null); + assert.strictEqual(buildRequest900.statusUrl(), null); + assert.strictEqual(buildRequest901.statusUrl(), null); + let commitSet = buildRequest800.commitSet(); + let anotherCommitSet = buildRequest900.commitSet(); + assert.ok(commitSet.equals(anotherCommitSet)); + + assert.strictEqual(MockRemoteAPI.requests.length, 4); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.pendingBuildsUrl('some tester')); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.pendingBuildsUrl('some-builder-1')); + MockRemoteAPI.requests[1].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.pendingBuildsUrl('some builder 2')); + MockRemoteAPI.requests[2].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[3].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[3].url, MockData.pendingBuildsUrl('other builder')); + MockRemoteAPI.requests[3].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 4); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.recentBuildsUrl('some tester', 2)); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.recentBuildsUrl('some-builder-1', 2)); + MockRemoteAPI.requests[1].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.recentBuildsUrl('some builder 2', 2)); + MockRemoteAPI.requests[2].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[3].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[3].url, MockData.recentBuildsUrl('other builder', 2)); + MockRemoteAPI.requests[3].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 1); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'POST'); + assert.strictEqual(MockRemoteAPI.requests[0].url, '/api/v2/forceschedulers/force-some-builder-1'); + assert.deepEqual(MockRemoteAPI.requests[0].data, {jsonrpc: '2.0', method: 'force', id: '800', + params: {forcescheduler: 'force-some-builder-1', os: '10.11 15A284', wk: '191622', + 'wk-patch': 'http://localhost:8180/api/uploaded-file/100.txt', 'build-request-id': '800'}}); + MockRemoteAPI.requests[0].resolve('OK') + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 1); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'POST'); + assert.strictEqual(MockRemoteAPI.requests[0].url, '/api/v2/forceschedulers/force-some-builder-2'); + assert.deepEqual(MockRemoteAPI.requests[0].data, {jsonrpc: '2.0', method: 'force', id: '801', + params: {forcescheduler: 'force-some-builder-2', os: '10.11 15A284', wk: '192736', + 'wk-patch': 'http://localhost:8180/api/uploaded-file/102.txt', 'build-request-id': '801'}}); + MockRemoteAPI.requests[0].resolve('OK') + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 4); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.pendingBuildsUrl('some tester')); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.pendingBuildsUrl('some-builder-1')); + MockRemoteAPI.requests[1].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.pendingBuildsUrl('some builder 2')); + MockRemoteAPI.requests[2].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[3].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[3].url, MockData.pendingBuildsUrl('other builder')); + MockRemoteAPI.requests[3].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 4); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.recentBuildsUrl('some tester', 2)); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.recentBuildsUrl('some-builder-1', 2)); + MockRemoteAPI.requests[1].resolve(MockData.runningBuild({buildRequestId: 800, builderId: 2, buildTag: 100})); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.recentBuildsUrl('some builder 2', 2)); + MockRemoteAPI.requests[2].resolve(MockData.runningBuild({buildRequestId: 801, builderId: 3, buildTag: 10})); + assert.strictEqual(MockRemoteAPI.requests[3].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[3].url, MockData.recentBuildsUrl('other builder', 2)); + MockRemoteAPI.requests[3].resolve({}); + MockRemoteAPI.reset(); + + await syncPromise; + await BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithPatchAcceptingBuilder().triggerableName); + assert.strictEqual(BuildRequest.all().length, 8); + buildRequest800 = BuildRequest.findById(800); + buildRequest801 = BuildRequest.findById(801); + buildRequest900 = BuildRequest.findById(900); + buildRequest901 = BuildRequest.findById(901); + assert.strictEqual(buildRequest800.status(), 'running'); + assert.strictEqual(buildRequest801.status(), 'running'); + assert.strictEqual(buildRequest900.status(), 'pending'); + assert.strictEqual(buildRequest901.status(), 'pending'); + assert.strictEqual(buildRequest800.statusUrl(), 'http://build.webkit.org/#/builders/2/builds/100'); + assert.strictEqual(buildRequest801.statusUrl(), 'http://build.webkit.org/#/builders/3/builds/10'); + assert.strictEqual(buildRequest900.statusUrl(), null); + assert.strictEqual(buildRequest901.statusUrl(), null); + + syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); + assertRequestAndResolve(MockRemoteAPI.requests[0], 'GET', MockData.buildbotBuildersURL(), MockData.mockBuildbotBuilders()); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 4); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.pendingBuildsUrl('some tester')); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.pendingBuildsUrl('some-builder-1')); + MockRemoteAPI.requests[1].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.pendingBuildsUrl('some builder 2')); + MockRemoteAPI.requests[2].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[3].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[3].url, MockData.pendingBuildsUrl('other builder')); + MockRemoteAPI.requests[3].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 4); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.recentBuildsUrl('some tester', 2)); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.recentBuildsUrl('some-builder-1', 2)); + MockRemoteAPI.requests[1].resolve(MockData.runningBuild({buildRequestId: 800, builderId: 2, buildTag: 100})); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.recentBuildsUrl('some builder 2', 2)); + MockRemoteAPI.requests[2].resolve(MockData.finishedBuild({buildRequestId: 801, builderId: 3, buildTag: 10})); + assert.strictEqual(MockRemoteAPI.requests[3].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[3].url, MockData.recentBuildsUrl('other builder', 2)); + MockRemoteAPI.requests[3].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 4); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.pendingBuildsUrl('some tester')); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.pendingBuildsUrl('some-builder-1')); + MockRemoteAPI.requests[1].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.pendingBuildsUrl('some builder 2')); + MockRemoteAPI.requests[2].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[3].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[3].url, MockData.pendingBuildsUrl('other builder')); + MockRemoteAPI.requests[3].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 4); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.recentBuildsUrl('some tester', 2)); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.recentBuildsUrl('some-builder-1', 2)); + MockRemoteAPI.requests[1].resolve(MockData.runningBuild({buildRequestId: 800, builderId: 2, buildTag: 100})); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.recentBuildsUrl('some builder 2', 2)); + MockRemoteAPI.requests[2].resolve(MockData.finishedBuild({buildRequestId: 801, builderId: 3, buildTag: 10})); + assert.strictEqual(MockRemoteAPI.requests[3].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[3].url, MockData.recentBuildsUrl('other builder', 2)); + MockRemoteAPI.requests[3].resolve({}); + MockRemoteAPI.reset(); + + await syncPromise; + await BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithPatchAcceptingBuilder().triggerableName); + assert.strictEqual(BuildRequest.all().length, 8); + buildRequest800 = BuildRequest.findById(800); + buildRequest801 = BuildRequest.findById(801); + buildRequest900 = BuildRequest.findById(900); + buildRequest901 = BuildRequest.findById(901); + assert.strictEqual(buildRequest800.status(), 'running'); + assert.strictEqual(buildRequest801.status(), 'failed'); + assert.strictEqual(buildRequest900.status(), 'pending'); + assert.strictEqual(buildRequest901.status(), 'pending'); + assert.strictEqual(buildRequest800.statusUrl(), 'http://build.webkit.org/#/builders/2/builds/100'); + assert.strictEqual(buildRequest801.statusUrl(), 'http://build.webkit.org/#/builders/3/builds/10'); + assert.strictEqual(buildRequest900.statusUrl(), null); + assert.strictEqual(buildRequest901.statusUrl(), null); + + syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); + assertRequestAndResolve(MockRemoteAPI.requests[0], 'GET', MockData.buildbotBuildersURL(), MockData.mockBuildbotBuilders()); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 4); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.pendingBuildsUrl('some tester')); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.pendingBuildsUrl('some-builder-1')); + MockRemoteAPI.requests[1].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.pendingBuildsUrl('some builder 2')); + MockRemoteAPI.requests[2].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[3].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[3].url, MockData.pendingBuildsUrl('other builder')); + MockRemoteAPI.requests[3].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 4); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.recentBuildsUrl('some tester', 2)); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.recentBuildsUrl('some-builder-1', 2)); + MockRemoteAPI.requests[1].resolve(MockData.runningBuild({buildRequestId: 800, builderId: 2, buildTag: 100})); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.recentBuildsUrl('some builder 2', 2)); + MockRemoteAPI.requests[2].resolve(MockData.finishedBuild({buildRequestId: 801, builderId: 3, buildTag: 10})); + assert.strictEqual(MockRemoteAPI.requests[3].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[3].url, MockData.recentBuildsUrl('other builder', 2)); + MockRemoteAPI.requests[3].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 1); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'POST'); + assert.strictEqual(MockRemoteAPI.requests[0].url, '/api/v2/forceschedulers/force-some-builder-2'); + assert.deepEqual(MockRemoteAPI.requests[0].data, {jsonrpc: '2.0', method: 'force', id: '901', + params: {forcescheduler: 'force-some-builder-2', os: '10.11 15A284', wk: '192736', + 'wk-patch': 'http://localhost:8180/api/uploaded-file/102.txt', 'build-request-id': '901'}}); + MockRemoteAPI.requests[0].resolve('OK') + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 4); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.pendingBuildsUrl('some tester')); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.pendingBuildsUrl('some-builder-1')); + MockRemoteAPI.requests[1].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.pendingBuildsUrl('some builder 2')); + MockRemoteAPI.requests[2].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[3].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[3].url, MockData.pendingBuildsUrl('other builder')); + MockRemoteAPI.requests[3].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 4); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.recentBuildsUrl('some tester', 2)); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.recentBuildsUrl('some-builder-1', 2)); + MockRemoteAPI.requests[1].resolve(MockData.runningBuild({buildRequestId: 800, builderId: 2, buildTag: 100})); + assert.strictEqual(MockRemoteAPI.requests[2].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[2].url, MockData.recentBuildsUrl('some builder 2', 2)); + MockRemoteAPI.requests[2].resolve({builds: [MockData.runningBuildData({buildRequestId: 901, builderId: 3, buildTag: 11}), + MockData.finishedBuildData({buildRequestId: 801, builderId: 3, buildTag: 10})]}); + assert.strictEqual(MockRemoteAPI.requests[3].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[3].url, MockData.recentBuildsUrl('other builder', 2)); + MockRemoteAPI.requests[3].resolve({}); + MockRemoteAPI.reset(); + + await syncPromise; + + await BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithPatchAcceptingBuilder().triggerableName); + assert.strictEqual(BuildRequest.all().length, 8); + buildRequest800 = BuildRequest.findById(800); + buildRequest801 = BuildRequest.findById(801); + buildRequest900 = BuildRequest.findById(900); + buildRequest901 = BuildRequest.findById(901); + assert.strictEqual(buildRequest800.status(), 'running'); + assert.strictEqual(buildRequest801.status(), 'failed'); + assert.strictEqual(buildRequest900.status(), 'pending'); + assert.strictEqual(buildRequest901.status(), 'running'); + assert.strictEqual(buildRequest800.statusUrl(), 'http://build.webkit.org/#/builders/2/builds/100'); + assert.strictEqual(buildRequest801.statusUrl(), 'http://build.webkit.org/#/builders/3/builds/10'); + assert.strictEqual(buildRequest900.statusUrl(), null); + assert.strictEqual(buildRequest901.statusUrl(), 'http://build.webkit.org/#/builders/3/builds/11'); + }); + + it('should fallback to use non-idle worker if all workers are busy', async () => { + const db = TestServer.database(); + await MockData.addMockBuildRequestsWithRoots(db, + ['pending', 'pending', 'pending', 'pending', 'pending', 'pending', 'pending', 'pending']); + await Promise.all([ + db.query('UPDATE commit_set_items SET commitset_root_file=NULL WHERE commitset_set=500'), + db.query('UPDATE build_requests SET request_url=NULL WHERE request_id=800') + ]); + + await Manifest.fetch(); + const config = MockData.mockTestSyncConfigWithPatchAcceptingBuilder(); + delete config.builders['builder-2']; + delete config.buildConfigurations[0].builders.pop(); + const logger = new MockLogger; + const workerInfo = {name: 'sync-worker', password: 'password'}; + const triggerable = new BuildbotTriggerable(config, TestServer.remoteAPI(), MockRemoteAPI, workerInfo, 2, logger); + + let syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); + assertRequestAndResolve(MockRemoteAPI.requests[0], 'GET', MockData.buildbotBuildersURL(), MockData.mockBuildbotBuilders()); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(BuildRequest.all().length, 8); + let buildRequest800 = BuildRequest.findById(800); + let buildRequest801 = BuildRequest.findById(801); + let buildRequest900 = BuildRequest.findById(900); + let buildRequest901 = BuildRequest.findById(901); + assert.strictEqual(buildRequest800.status(), 'pending'); + assert.strictEqual(buildRequest801.status(), 'pending'); + assert.strictEqual(buildRequest900.status(), 'pending'); + assert.strictEqual(buildRequest901.status(), 'pending'); + assert.strictEqual(buildRequest800.statusUrl(), null); + assert.strictEqual(buildRequest801.statusUrl(), null); + assert.strictEqual(buildRequest900.statusUrl(), null); + assert.strictEqual(buildRequest901.statusUrl(), null); + let commitSet = buildRequest800.commitSet(); + let anotherCommitSet = buildRequest900.commitSet(); + assert.ok(commitSet.equals(anotherCommitSet)); + + assert.strictEqual(MockRemoteAPI.requests.length, 2); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.pendingBuildsUrl('some tester')); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.pendingBuildsUrl('some-builder-1')); + MockRemoteAPI.requests[1].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 2); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.recentBuildsUrl('some tester', 2)); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.recentBuildsUrl('some-builder-1', 2)); + MockRemoteAPI.requests[1].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 1); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'POST'); + assert.strictEqual(MockRemoteAPI.requests[0].url, '/api/v2/forceschedulers/force-some-builder-1'); + assert.deepEqual(MockRemoteAPI.requests[0].data, {jsonrpc: '2.0', method: 'force', id: '800', + params: {forcescheduler: 'force-some-builder-1', os: '10.11 15A284', wk: '191622', + 'wk-patch': 'http://localhost:8180/api/uploaded-file/100.txt', 'build-request-id': '800'}}); + MockRemoteAPI.requests[0].resolve('OK') + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 2); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.pendingBuildsUrl('some tester')); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.pendingBuildsUrl('some-builder-1')); + MockRemoteAPI.requests[1].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 2); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.recentBuildsUrl('some tester', 2)); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.recentBuildsUrl('some-builder-1', 2)); + MockRemoteAPI.requests[1].resolve(MockData.runningBuild({buildRequestId: 800, builderId: 2, buildTag: 100})); + MockRemoteAPI.requests[1].resolve({}); + MockRemoteAPI.reset(); + + await syncPromise; + await BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithPatchAcceptingBuilder().triggerableName); + assert.strictEqual(BuildRequest.all().length, 8); + buildRequest800 = BuildRequest.findById(800); + buildRequest801 = BuildRequest.findById(801); + buildRequest900 = BuildRequest.findById(900); + buildRequest901 = BuildRequest.findById(901); + assert.strictEqual(buildRequest800.status(), 'running'); + assert.strictEqual(buildRequest801.status(), 'pending'); + assert.strictEqual(buildRequest900.status(), 'pending'); + assert.strictEqual(buildRequest901.status(), 'pending'); + assert.strictEqual(buildRequest800.statusUrl(), 'http://build.webkit.org/#/builders/2/builds/100'); + assert.strictEqual(buildRequest801.statusUrl(), null); + assert.strictEqual(buildRequest900.statusUrl(), null); + assert.strictEqual(buildRequest901.statusUrl(), null); + + syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); + assertRequestAndResolve(MockRemoteAPI.requests[0], 'GET', MockData.buildbotBuildersURL(), MockData.mockBuildbotBuilders()); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 2); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.pendingBuildsUrl('some tester')); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.pendingBuildsUrl('some-builder-1')); + MockRemoteAPI.requests[1].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 2); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.recentBuildsUrl('some tester', 2)); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.recentBuildsUrl('some-builder-1', 2)); + MockRemoteAPI.requests[1].resolve(MockData.runningBuild({buildRequestId: 800, builderId: 2, buildTag: 100})); + MockRemoteAPI.requests[1].resolve({}); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 1); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'POST'); + assert.strictEqual(MockRemoteAPI.requests[0].url, '/api/v2/forceschedulers/force-some-builder-1'); + assert.deepEqual(MockRemoteAPI.requests[0].data, {jsonrpc: '2.0', method: 'force', id: '801', + params: {forcescheduler: 'force-some-builder-1', os: '10.11 15A284', wk: '192736', + 'wk-patch': 'http://localhost:8180/api/uploaded-file/102.txt', 'build-request-id': '801'}}); + MockRemoteAPI.requests[0].resolve('OK') + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 2); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.pendingBuildsUrl('some tester')); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.pendingBuildsUrl('some-builder-1')); + MockRemoteAPI.requests[1].resolve(MockData.pendingBuild({buildRequestId: 801, builderId: 2, buildbotBuildRequestId: 111})); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(MockRemoteAPI.requests.length, 2); + assert.strictEqual(MockRemoteAPI.requests[0].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[0].url, MockData.recentBuildsUrl('some tester', 2)); + MockRemoteAPI.requests[0].resolve({}); + assert.strictEqual(MockRemoteAPI.requests[1].method, 'GET'); + assert.strictEqual(MockRemoteAPI.requests[1].url, MockData.recentBuildsUrl('some-builder-1', 2)); + MockRemoteAPI.requests[1].resolve(MockData.runningBuild({buildRequestId: 800, builderId: 2, buildTag: 100})); + MockRemoteAPI.requests[1].resolve({}); + MockRemoteAPI.reset(); + + await syncPromise; + await BuildRequest.fetchForTriggerable(MockData.mockTestSyncConfigWithPatchAcceptingBuilder().triggerableName); + assert.strictEqual(BuildRequest.all().length, 8); + buildRequest800 = BuildRequest.findById(800); + buildRequest801 = BuildRequest.findById(801); + buildRequest900 = BuildRequest.findById(900); + buildRequest901 = BuildRequest.findById(901); + assert.strictEqual(buildRequest800.status(), 'running'); + assert.strictEqual(buildRequest801.status(), 'scheduled'); + assert.strictEqual(buildRequest900.status(), 'pending'); + assert.strictEqual(buildRequest901.status(), 'pending'); + assert.strictEqual(buildRequest800.statusUrl(), 'http://build.webkit.org/#/builders/2/builds/100'); + assert.strictEqual(buildRequest801.statusUrl(), 'http://build.webkit.org/#/buildrequests/111'); + assert.strictEqual(buildRequest900.statusUrl(), null); + assert.strictEqual(buildRequest901.statusUrl(), null); + }); }); describe('updateTriggerables', () => { diff --git a/Websites/perf.webkit.org/server-tests/tools-sync-buildbot-integration-tests.js b/Websites/perf.webkit.org/server-tests/tools-sync-buildbot-integration-tests.js index 4b2549d408f24..3b8f54151a1ed 100644 --- a/Websites/perf.webkit.org/server-tests/tools-sync-buildbot-integration-tests.js +++ b/Websites/perf.webkit.org/server-tests/tools-sync-buildbot-integration-tests.js @@ -55,7 +55,7 @@ function configWithOneTesterTwoBuilders(testConfigurationsOverride = [{types: [' }, 'builder-3': { builder: 'other builder', - properties: {forcescheduler: 'force-ab-builds'}, + properties: {forcescheduler: 'force-ab-builds-1'}, supportedRepetitionTypes: ['alternating', 'sequential', 'paired-parallel'] }, }, @@ -107,7 +107,7 @@ const configWithPlatformName = { }, 'builder-3': { builder: 'other builder', - properties: {forcescheduler: 'force-ab-builds'}, + properties: {forcescheduler: 'force-ab-builds-1'}, supportedRepetitionTypes: ['alternating', 'sequential', 'paired-parallel'] }, }, @@ -264,13 +264,13 @@ function createTestGroupWihOwnedCommit() }); } -function uploadRoot(buildRequestId, buildTag, repositoryList = ["WebKit"], buildTime = '2017-05-10T02:54:08.666') +function uploadRoot(buildRequestId, buildTag, repositoryList = ["WebKit"], buildTime = '2017-05-10T02:54:08.666', builderName='some builder') { return TemporaryFile.makeTemporaryFile(`root${buildTag}.dat`, `root for build ${buildTag} and repository list at ${buildTime}`).then((rootFile) => { return TestServer.remoteAPI().postFormData('/api/upload-root/', { workerName: 'sync-worker', workerPassword: 'password', - builderName: 'some builder', + builderName: builderName, buildTag: buildTag, buildTime: buildTime, buildRequest: buildRequestId, @@ -496,18 +496,26 @@ describe('sync-buildbot', function () { {'wk': '191622', 'wk-patch': RemoteAPI.url('/api/uploaded-file/1.dat'), 'checkbox': 'build-wk', 'build-wk': true, 'build-request-id': '1', 'forcescheduler': 'force-ab-builds'}}); await MockRemoteAPI.waitForRequest(); - assert.strictEqual(requests.length, 10); - assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some tester'), {}); - assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some builder'), + assert.strictEqual(requests.length, 8); + assertAndResolveRequest(requests[7], 'POST', '/api/v2/forceschedulers/force-ab-builds-1', 'OK'); + assert.deepEqual(requests[7].data, {'id': '2', 'jsonrpc': '2.0', 'method': 'force', 'params': + {'wk': '191622', 'checkbox': 'build-wk', 'build-wk': true, 'build-request-id': '2', 'forcescheduler': 'force-ab-builds-1'}}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 11); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('some builder'), MockData.pendingBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})); - assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + assertAndResolveRequest(requests[10], 'GET', MockData.pendingBuildsUrl('other builder'), + MockData.pendingBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2})); await MockRemoteAPI.waitForRequest(); - assert.strictEqual(requests.length, 13); - assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); - assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some builder', 2), + assert.strictEqual(requests.length, 14); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('some builder', 2), MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildNumber: firstBuildNumber, statusDescription: 'Building WebKit'})); - assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); + assertAndResolveRequest(requests[13], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildNumber: secondBuildNumber, statusDescription: 'Building WebKit'})); await syncPromise; let testGroups = await TestGroup.fetchForTask(taskId, true); @@ -536,9 +544,9 @@ describe('sync-buildbot', function () { otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); - assert.strictEqual(otherBuildRequest.statusLabel(), 'Waiting'); - assert.strictEqual(otherBuildRequest.statusUrl(), null); - assert.strictEqual(otherBuildRequest.statusDescription(), null); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', 124)); + assert.strictEqual(otherBuildRequest.statusDescription(), 'Building WebKit'); assert.strictEqual(otherBuildRequest.buildId(), null); otherCommitSet = otherBuildRequest.commitSet(); @@ -563,7 +571,8 @@ describe('sync-buildbot', function () { assertAndResolveRequest(requests[3], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); assertAndResolveRequest(requests[4], 'GET', MockData.recentBuildsUrl('some builder', 2), MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber, statusDescription: 'Compiling WTF'})); - assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); + assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber, statusDescription: 'Compiling WTF'})); await MockRemoteAPI.waitForRequest(); assert.strictEqual(requests.length, 9); @@ -576,7 +585,8 @@ describe('sync-buildbot', function () { assertAndResolveRequest(requests[9], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some builder', 2), MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber, statusDescription: 'Compiling WTF'})); - assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber, statusDescription: 'Compiling WTF'})); await syncPromise; await TestGroup.fetchForTask(taskId, true); @@ -605,9 +615,9 @@ describe('sync-buildbot', function () { otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); - assert.strictEqual(otherBuildRequest.statusLabel(), 'Waiting'); - assert.strictEqual(otherBuildRequest.statusUrl(), null); - assert.strictEqual(otherBuildRequest.statusDescription(), null); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildNumber)); + assert.strictEqual(otherBuildRequest.statusDescription(), 'Compiling WTF'); assert.strictEqual(otherBuildRequest.buildId(), null); otherCommitSet = otherBuildRequest.commitSet(); @@ -617,6 +627,7 @@ describe('sync-buildbot', function () { assert.deepEqual(otherCommitSet.allRootFiles(), []); await uploadRoot(parseInt(buildRequest.id()), firstBuildNumber); + await uploadRoot(parseInt(otherBuildRequest.id()), secondBuildNumber, ["WebKit"], '2017-05-10T02:54:08.666', 'other builder'); testGroups = await TestGroup.fetchForTask(taskId, true); @@ -646,18 +657,21 @@ describe('sync-buildbot', function () { otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); - assert.strictEqual(otherBuildRequest.statusLabel(), 'Waiting'); - assert.strictEqual(otherBuildRequest.statusUrl(), null); - assert.strictEqual(otherBuildRequest.statusDescription(), null); - assert.strictEqual(otherBuildRequest.buildId(), null); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Completed'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildNumber)); + assert.strictEqual(otherBuildRequest.statusDescription(), 'Compiling WTF'); + assert.notEqual(otherBuildRequest.buildId(), null); otherCommitSet = otherBuildRequest.commitSet(); assert.strictEqual(otherCommitSet.revisionForRepository(webkit), '191622'); assert.strictEqual(otherCommitSet.patchForRepository(webkit), null); - assert.strictEqual(otherCommitSet.rootForRepository(webkit), null); - assert.deepEqual(otherCommitSet.allRootFiles(), []); + let otherWebkitRoot = otherCommitSet.rootForRepository(webkit); + assert(otherWebkitRoot instanceof UploadedFile); + assert.strictEqual(otherWebkitRoot.filename(), 'root124.dat'); + assert.deepEqual(otherCommitSet.allRootFiles(), [otherWebkitRoot]); MockRemoteAPI.reset(); + const firstTestBuildTag = 100; syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); assertAndResolveRequest(requests[0], 'GET', MockData.buildbotBuildersURL(), MockData.mockBuildbotBuilders()); MockRemoteAPI.reset(); @@ -673,13 +687,15 @@ describe('sync-buildbot', function () { assertAndResolveRequest(requests[3], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); assertAndResolveRequest(requests[4], 'GET', MockData.recentBuildsUrl('some builder', 2), MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber})); - assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); + assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber})); await MockRemoteAPI.waitForRequest(); assert.strictEqual(requests.length, 7); - assertAndResolveRequest(requests[6], 'POST', '/api/v2/forceschedulers/force-ab-builds', 'OK'); - assert.deepEqual(requests[6].data, {'id': '2', 'jsonrpc': '2.0', 'method': 'force', 'params': - {'wk': '191622', 'build-request-id': '2', 'forcescheduler': 'force-ab-builds', 'checkbox': 'build-wk', 'build-wk': true}}); + assertAndResolveRequest(requests[6], 'POST', '/api/v2/forceschedulers/force-ab-tests', 'OK'); + assert.deepEqual(requests[6].data, {'id': '3', 'jsonrpc': '2.0', 'method': 'force', 'params': + {'wk': '191622', 'build-request-id': '3', 'forcescheduler': 'force-ab-tests', 'test': 'some-test', + 'roots': '[{"url":"http://localhost:8180/api/uploaded-file/2.dat"}]'}}); await MockRemoteAPI.waitForRequest(); assert.strictEqual(requests.length, 10); @@ -689,20 +705,18 @@ describe('sync-buildbot', function () { await MockRemoteAPI.waitForRequest(); assert.strictEqual(requests.length, 13); - assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); - assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some builder', 2), { - 'builds': [ - MockData.runningBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 2, buildTag: secondBuildNumber}), - MockData.finishedBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber})] - }); - assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); + assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some tester', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('some tester'), buildRequestId: 3, buildTag: firstTestBuildTag})); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber})); + assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber})); await syncPromise; await TestGroup.fetchForTask(taskId, true); assert.strictEqual(testGroups.length, 1); testGroup = testGroups[0]; - webkit = Repository.findById(MockData.webkitRepositoryId()); assert.strictEqual(testGroup.buildRequests().length, 6); buildRequest = testGroup.buildRequests()[0]; @@ -713,32 +727,194 @@ describe('sync-buildbot', function () { assert.strictEqual(buildRequest.statusDescription(), null); assert.notEqual(buildRequest.buildId(), null); - commitSet = buildRequest.commitSet(); + otherBuildRequest = testGroup.buildRequests()[1]; + assert(otherBuildRequest.isBuild()); + assert(!otherBuildRequest.isTest()); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Completed'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildNumber)); + assert.strictEqual(otherBuildRequest.statusDescription(), null); + assert.notEqual(otherBuildRequest.buildId(), null); + + let firstTestRequest = testGroup.buildRequests()[2]; + assert(!firstTestRequest.isBuild()); + assert(firstTestRequest.isTest()); + assert.strictEqual(firstTestRequest.statusLabel(), 'Running'); + assert.strictEqual(firstTestRequest.statusUrl(), MockData.statusUrl('some tester', firstTestBuildTag)); + assert.strictEqual(firstTestRequest.statusDescription(), null); + assert.strictEqual(firstTestRequest.buildId(), null); + + let secondTestRequest = testGroup.buildRequests()[3]; + assert(!secondTestRequest.isBuild()); + assert(secondTestRequest.isTest()); + assert.strictEqual(secondTestRequest.statusLabel(), 'Waiting'); + assert.strictEqual(secondTestRequest.statusUrl(), null); + assert.strictEqual(secondTestRequest.statusDescription(), null); + assert.strictEqual(secondTestRequest.buildId(), null); + + MockRemoteAPI.reset(); + syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); + assertAndResolveRequest(requests[0], 'GET', MockData.buildbotBuildersURL(), MockData.mockBuildbotBuilders()); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 3); + assertAndResolveRequest(requests[0], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[1], 'GET', MockData.pendingBuildsUrl('some builder'), {}); + assertAndResolveRequest(requests[2], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 6); + + assertAndResolveRequest(requests[3], 'GET', MockData.recentBuildsUrl('some tester', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('some tester'), buildRequestId: 3, buildTag: firstTestBuildTag})); + assertAndResolveRequest(requests[4], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber})); + assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber})); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 7); + assertAndResolveRequest(requests[6], 'POST', '/api/v2/forceschedulers/force-ab-tests', 'OK'); + assert.deepEqual(requests[6].data, {'id': '4', 'jsonrpc': '2.0', 'method': 'force', 'params': + {'wk': '191622', 'build-request-id': '4', 'forcescheduler': 'force-ab-tests', 'test': 'some-test', + 'roots': '[{"url":"http://localhost:8180/api/uploaded-file/3.dat"}]'}}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 10); + assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some tester'), + MockData.pendingBuild({builderId: MockData.builderIDForName('some tester'), buildRequestId: 4, + buildbotBuildRequestId: 2})); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some builder'), {}); + assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 13); + assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some tester', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('some tester'), buildRequestId: 3, buildTag: firstTestBuildTag})); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber})); + assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber})); + await syncPromise; + + await TestGroup.fetchForTask(taskId, true); + + assert.strictEqual(testGroups.length, 1); + testGroup = testGroups[0]; + assert.strictEqual(testGroup.buildRequests().length, 6); + + buildRequest = testGroup.buildRequests()[0]; + assert.strictEqual(buildRequest.statusLabel(), 'Completed'); + + otherBuildRequest = testGroup.buildRequests()[1]; + assert.strictEqual(otherBuildRequest.statusLabel(), 'Completed'); + + firstTestRequest = testGroup.buildRequests()[2]; + assert(!firstTestRequest.isBuild()); + assert(firstTestRequest.isTest()); + assert.strictEqual(firstTestRequest.statusLabel(), 'Running'); + assert.strictEqual(firstTestRequest.statusUrl(), MockData.statusUrl('some tester', firstTestBuildTag)); + assert.strictEqual(firstTestRequest.statusDescription(), null); + assert.strictEqual(firstTestRequest.buildId(), null); + + secondTestRequest = testGroup.buildRequests()[3]; + assert(!secondTestRequest.isBuild()); + assert(secondTestRequest.isTest()); + assert.strictEqual(secondTestRequest.statusLabel(), 'Scheduled'); + assert.strictEqual(secondTestRequest.statusUrl(), 'http://build.webkit.org/#/buildrequests/2'); + assert.strictEqual(secondTestRequest.statusDescription(), null); + assert.strictEqual(secondTestRequest.buildId(), null); + }); + + it('should not process test type requests until all building tasks are completed even if the second building task completes before the first one', async () => { + const requests = MockRemoteAPI.requests; + let syncPromise; + const triggerable = await createTriggerable(); + const firstBuildNumber = 123; + const secondBuildNumber = 124; + let testGroup = await createTestGroupWithPatch(); + + const taskId = testGroup.task().id(); + let webkit = Repository.findById(MockData.webkitRepositoryId()); + assert.strictEqual(testGroup.buildRequests().length, 6); + + let buildRequest = testGroup.buildRequests()[0]; + assert(buildRequest.isBuild()); + assert(!buildRequest.isTest()); + assert.strictEqual(buildRequest.statusLabel(), 'Waiting'); + assert.strictEqual(buildRequest.statusUrl(), null); + assert.strictEqual(buildRequest.statusDescription(), null); + assert.strictEqual(buildRequest.buildId(), null); + + let commitSet = buildRequest.commitSet(); assert.strictEqual(commitSet.revisionForRepository(webkit), '191622'); - webkitPatch = commitSet.patchForRepository(webkit); + let webkitPatch = commitSet.patchForRepository(webkit); assert(webkitPatch instanceof UploadedFile); assert.strictEqual(webkitPatch.filename(), 'patch.dat'); - webkitRoot = commitSet.rootForRepository(webkit); - assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); - assert.deepEqual(commitSet.allRootFiles(), [webkitRoot]); + assert.strictEqual(commitSet.rootForRepository(webkit), null); + assert.deepEqual(commitSet.allRootFiles(), []); - otherBuildRequest = testGroup.buildRequests()[1]; + let otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); - assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); - assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('some builder', secondBuildNumber)); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Waiting'); + assert.strictEqual(otherBuildRequest.statusUrl(), null); assert.strictEqual(otherBuildRequest.statusDescription(), null); assert.strictEqual(otherBuildRequest.buildId(), null); - otherCommitSet = otherBuildRequest.commitSet(); + let otherCommitSet = otherBuildRequest.commitSet(); assert.strictEqual(otherCommitSet.revisionForRepository(webkit), '191622'); assert.strictEqual(otherCommitSet.patchForRepository(webkit), null); assert.strictEqual(otherCommitSet.rootForRepository(webkit), null); assert.deepEqual(otherCommitSet.allRootFiles(), []); - await uploadRoot(parseInt(otherBuildRequest.id()), 124); - testGroups = await TestGroup.fetchForTask(taskId, true); + syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); + assertAndResolveRequest(requests[0], 'GET', MockData.buildbotBuildersURL(), MockData.mockBuildbotBuilders()); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 3); + assertAndResolveRequest(requests[0], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[1], 'GET', MockData.pendingBuildsUrl('some builder'), {}); + assertAndResolveRequest(requests[2], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 6); + assertAndResolveRequest(requests[3], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[4], 'GET', MockData.recentBuildsUrl('some builder', 2), {}); + assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); + + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 7); + assertAndResolveRequest(requests[6], 'POST', '/api/v2/forceschedulers/force-ab-builds', 'OK'); + assert.deepEqual(requests[6].data, {'id': '1', 'jsonrpc': '2.0', 'method': 'force', 'params': + {'wk': '191622', 'wk-patch': RemoteAPI.url('/api/uploaded-file/1.dat'), 'checkbox': 'build-wk', 'build-wk': true, 'build-request-id': '1', 'forcescheduler': 'force-ab-builds'}}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 8); + assertAndResolveRequest(requests[7], 'POST', '/api/v2/forceschedulers/force-ab-builds-1', 'OK'); + assert.deepEqual(requests[7].data, {'id': '2', 'jsonrpc': '2.0', 'method': 'force', 'params': + {'wk': '191622', 'checkbox': 'build-wk', 'build-wk': true, 'build-request-id': '2', 'forcescheduler': 'force-ab-builds-1'}}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 11); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('some builder'), + MockData.pendingBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})); + assertAndResolveRequest(requests[10], 'GET', MockData.pendingBuildsUrl('other builder'), + MockData.pendingBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2})); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 14); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildNumber: firstBuildNumber, statusDescription: 'Building WebKit'})); + assertAndResolveRequest(requests[13], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildNumber: secondBuildNumber, statusDescription: 'Building WebKit'})); + await syncPromise; + + let testGroups = await TestGroup.fetchForTask(taskId, true); assert.strictEqual(testGroups.length, 1); testGroup = testGroups[0]; @@ -748,83 +924,461 @@ describe('sync-buildbot', function () { buildRequest = testGroup.buildRequests()[0]; assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); - assert.strictEqual(buildRequest.statusLabel(), 'Completed'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildNumber)); - assert.strictEqual(buildRequest.statusDescription(), null); - assert.notEqual(buildRequest.buildId(), null); + assert.strictEqual(buildRequest.statusLabel(), 'Running'); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusDescription(), 'Building WebKit'); + assert.strictEqual(buildRequest.buildId(), null); commitSet = buildRequest.commitSet(); assert.strictEqual(commitSet.revisionForRepository(webkit), '191622'); webkitPatch = commitSet.patchForRepository(webkit); assert(webkitPatch instanceof UploadedFile); assert.strictEqual(webkitPatch.filename(), 'patch.dat'); - webkitRoot = commitSet.rootForRepository(webkit); - assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); - assert.deepEqual(commitSet.allRootFiles(), [webkitRoot]); + assert.strictEqual(commitSet.rootForRepository(webkit), null); + assert.deepEqual(commitSet.allRootFiles(), []); otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); - assert.strictEqual(otherBuildRequest.statusLabel(), 'Completed'); - assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('some builder', secondBuildNumber)); - assert.strictEqual(otherBuildRequest.statusDescription(), null); - assert.notEqual(otherBuildRequest.buildId(), null); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', 124)); + assert.strictEqual(otherBuildRequest.statusDescription(), 'Building WebKit'); + assert.strictEqual(otherBuildRequest.buildId(), null); otherCommitSet = otherBuildRequest.commitSet(); assert.strictEqual(otherCommitSet.revisionForRepository(webkit), '191622'); assert.strictEqual(otherCommitSet.patchForRepository(webkit), null); - const otherWebkitRoot = otherCommitSet.rootForRepository(webkit); - assert(otherWebkitRoot instanceof UploadedFile); - assert.strictEqual(otherWebkitRoot.filename(), 'root124.dat'); - assert.deepEqual(otherCommitSet.allRootFiles(), [otherWebkitRoot]); - }); + assert.strictEqual(otherCommitSet.rootForRepository(webkit), null); + assert.deepEqual(otherCommitSet.allRootFiles(), []); - async function resolveSyncerToBuildBotRequests(requestResolutionList) - { - const requests = MockRemoteAPI.requests; - let resolutionIndexOffset = 0; - for (let i = 0; i < requestResolutionList.length; i++) { - const resolutions = requestResolutionList[i]; - assert.strictEqual(requests.length, resolutionIndexOffset + resolutions.length); - resolutions.forEach((resolution, index) => { - assertAndResolveRequest(requests[resolutionIndexOffset + index], resolution.method, resolution.url, resolution.resolve); - if ('data' in resolution) - assert.deepEqual(requests[resolutionIndexOffset + index].data, resolution.data); - }); - resolutionIndexOffset += resolutions.length; - if (i != requestResolutionList.length - 1) - await MockRemoteAPI.waitForRequest(); - } MockRemoteAPI.reset(); - } + syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); + assertAndResolveRequest(requests[0], 'GET', MockData.buildbotBuildersURL(), MockData.mockBuildbotBuilders()); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); - function validateFirstTwoBuildRequestsInTestGroup(testGroup, buildRequestOverride = {}, otherBuildRequestOverride = {}) - { - const webkit = Repository.findById(MockData.webkitRepositoryId()); + assert.strictEqual(requests.length, 3); + assertAndResolveRequest(requests[0], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[1], 'GET', MockData.pendingBuildsUrl('some builder'), {}); + assertAndResolveRequest(requests[2], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 6); + assertAndResolveRequest(requests[3], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[4], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber, statusDescription: 'Compiling WTF'})); + assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber, statusDescription: 'Compiling WTF'})); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 9); + assertAndResolveRequest(requests[6], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some builder'), {}); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 12); + assertAndResolveRequest(requests[9], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber, statusDescription: 'Compiling WTF'})); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber, statusDescription: 'Compiling WTF'})); + await syncPromise; + + await TestGroup.fetchForTask(taskId, true); + + assert.strictEqual(testGroups.length, 1); + testGroup = testGroups[0]; + webkit = Repository.findById(MockData.webkitRepositoryId()); assert.strictEqual(testGroup.buildRequests().length, 6); - const buildRequest = testGroup.buildRequests()[0]; + buildRequest = testGroup.buildRequests()[0]; assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); - assert.strictEqual(buildRequest.statusLabel(), buildRequestOverride.statusLabel || 'Waiting'); - assert.strictEqual(buildRequest.statusUrl(), buildRequestOverride.statusUrl || null); - assert.strictEqual(buildRequest.statusDescription(), buildRequestOverride.statusDescription || null); - assert.strictEqual(buildRequest.buildId(), buildRequestOverride.buildId || null); + assert.strictEqual(buildRequest.statusLabel(), 'Running'); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildNumber)); + assert.strictEqual(buildRequest.statusDescription(), 'Compiling WTF'); + assert.strictEqual(buildRequest.buildId(), null); - const commitSet = buildRequest.commitSet(); + commitSet = buildRequest.commitSet(); assert.strictEqual(commitSet.revisionForRepository(webkit), '191622'); - const webkitPatch = commitSet.patchForRepository(webkit); + webkitPatch = commitSet.patchForRepository(webkit); assert(webkitPatch instanceof UploadedFile); assert.strictEqual(webkitPatch.filename(), 'patch.dat'); - if (!buildRequestOverride.webkitRootUploaded) { - assert.strictEqual(commitSet.rootForRepository(webkit), null); - assert.deepEqual(commitSet.allRootFiles(), []); - } - else { - const webkitRoot = commitSet.rootForRepository(webkit); - assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.strictEqual(commitSet.rootForRepository(webkit), null); + assert.deepEqual(commitSet.allRootFiles(), []); + + otherBuildRequest = testGroup.buildRequests()[1]; + assert(otherBuildRequest.isBuild()); + assert(!otherBuildRequest.isTest()); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildNumber)); + assert.strictEqual(otherBuildRequest.statusDescription(), 'Compiling WTF'); + assert.strictEqual(otherBuildRequest.buildId(), null); + + otherCommitSet = otherBuildRequest.commitSet(); + assert.strictEqual(otherCommitSet.revisionForRepository(webkit), '191622'); + assert.strictEqual(otherCommitSet.patchForRepository(webkit), null); + assert.strictEqual(otherCommitSet.rootForRepository(webkit), null); + assert.deepEqual(otherCommitSet.allRootFiles(), []); + + await uploadRoot(parseInt(otherBuildRequest.id()), secondBuildNumber, ["WebKit"], '2017-05-10T02:54:08.666', 'other builder'); + + testGroups = await TestGroup.fetchForTask(taskId, true); + + assert.strictEqual(testGroups.length, 1); + testGroup = testGroups[0]; + webkit = Repository.findById(MockData.webkitRepositoryId()); + assert.strictEqual(testGroup.buildRequests().length, 6); + + buildRequest = testGroup.buildRequests()[0]; + assert(buildRequest.isBuild()); + assert(!buildRequest.isTest()); + assert.strictEqual(buildRequest.statusLabel(), 'Running'); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildNumber)); + assert.strictEqual(buildRequest.statusDescription(), 'Compiling WTF'); + assert.strictEqual(buildRequest.buildId(), null); + + commitSet = buildRequest.commitSet(); + assert.strictEqual(commitSet.revisionForRepository(webkit), '191622'); + webkitPatch = commitSet.patchForRepository(webkit); + assert(webkitPatch instanceof UploadedFile); + assert.strictEqual(webkitPatch.filename(), 'patch.dat'); + assert.strictEqual(commitSet.rootForRepository(webkit), null); + assert.deepEqual(commitSet.allRootFiles(), []); + + otherBuildRequest = testGroup.buildRequests()[1]; + assert(otherBuildRequest.isBuild()); + assert(!otherBuildRequest.isTest()); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Completed'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildNumber)); + assert.strictEqual(otherBuildRequest.statusDescription(), 'Compiling WTF'); + assert.notEqual(otherBuildRequest.buildId(), null); + + otherCommitSet = otherBuildRequest.commitSet(); + assert.strictEqual(otherCommitSet.revisionForRepository(webkit), '191622'); + assert.strictEqual(otherCommitSet.patchForRepository(webkit), null); + let otherWebkitRoot = otherCommitSet.rootForRepository(webkit); + assert(otherWebkitRoot instanceof UploadedFile); + assert.strictEqual(otherWebkitRoot.filename(), 'root124.dat'); + assert.deepEqual(otherCommitSet.allRootFiles(), [otherWebkitRoot]); + + MockRemoteAPI.reset(); + const firstTestBuildTag = 100; + syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); + assertAndResolveRequest(requests[0], 'GET', MockData.buildbotBuildersURL(), MockData.mockBuildbotBuilders()); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 3); + assertAndResolveRequest(requests[0], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[1], 'GET', MockData.pendingBuildsUrl('some builder'), {}); + assertAndResolveRequest(requests[2], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 6); + assertAndResolveRequest(requests[3], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[4], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber, statusDescription: 'Compiling WTF'})); + assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber})); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 9); + assertAndResolveRequest(requests[6], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some builder'), {}); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 12); + assertAndResolveRequest(requests[9], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber, statusDescription: 'Compiling WTF'})); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber})); + await syncPromise; + + await TestGroup.fetchForTask(taskId, true); + + assert.strictEqual(testGroups.length, 1); + testGroup = testGroups[0]; + assert.strictEqual(testGroup.buildRequests().length, 6); + + buildRequest = testGroup.buildRequests()[0]; + assert(buildRequest.isBuild()); + assert(!buildRequest.isTest()); + assert.strictEqual(buildRequest.statusLabel(), 'Running'); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildNumber)); + assert.strictEqual(buildRequest.statusDescription(), 'Compiling WTF'); + assert.strictEqual(buildRequest.buildId(), null); + + otherBuildRequest = testGroup.buildRequests()[1]; + assert(otherBuildRequest.isBuild()); + assert(!otherBuildRequest.isTest()); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Completed'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildNumber)); + assert.strictEqual(otherBuildRequest.statusDescription(), null); + assert.notEqual(otherBuildRequest.buildId(), null); + + let firstTestRequest = testGroup.buildRequests()[2]; + assert(!firstTestRequest.isBuild()); + assert(firstTestRequest.isTest()); + assert.strictEqual(firstTestRequest.statusLabel(), 'Waiting'); + assert.strictEqual(firstTestRequest.statusUrl(), null); + assert.strictEqual(firstTestRequest.statusDescription(), null); + assert.strictEqual(firstTestRequest.buildId(), null); + + let secondTestRequest = testGroup.buildRequests()[3]; + assert(!secondTestRequest.isBuild()); + assert(secondTestRequest.isTest()); + assert.strictEqual(secondTestRequest.statusLabel(), 'Waiting'); + assert.strictEqual(secondTestRequest.statusUrl(), null); + assert.strictEqual(secondTestRequest.statusDescription(), null); + assert.strictEqual(secondTestRequest.buildId(), null); + + await uploadRoot(parseInt(buildRequest.id()), firstBuildNumber); + + testGroups = await TestGroup.fetchForTask(taskId, true); + + assert.strictEqual(testGroups.length, 1); + testGroup = testGroups[0]; + webkit = Repository.findById(MockData.webkitRepositoryId()); + assert.strictEqual(testGroup.buildRequests().length, 6); + + buildRequest = testGroup.buildRequests()[0]; + assert(buildRequest.isBuild()); + assert(!buildRequest.isTest()); + assert.strictEqual(buildRequest.statusLabel(), 'Completed'); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildNumber)); + assert.strictEqual(buildRequest.statusDescription(), 'Compiling WTF'); + assert.notEqual(buildRequest.buildId(), null); + + commitSet = buildRequest.commitSet(); + assert.strictEqual(commitSet.revisionForRepository(webkit), '191622'); + webkitPatch = commitSet.patchForRepository(webkit); + assert(webkitPatch instanceof UploadedFile); + assert.strictEqual(webkitPatch.filename(), 'patch.dat'); + let webkitRoot = commitSet.rootForRepository(webkit); + assert(webkitRoot instanceof UploadedFile); + assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.deepEqual(commitSet.allRootFiles(), [webkitRoot]); + + otherBuildRequest = testGroup.buildRequests()[1]; + assert(otherBuildRequest.isBuild()); + assert(!otherBuildRequest.isTest()); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Completed'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildNumber)); + assert.strictEqual(otherBuildRequest.statusDescription(), null); + assert.notEqual(otherBuildRequest.buildId(), null); + + otherCommitSet = otherBuildRequest.commitSet(); + assert.strictEqual(otherCommitSet.revisionForRepository(webkit), '191622'); + assert.strictEqual(otherCommitSet.patchForRepository(webkit), null); + otherWebkitRoot = otherCommitSet.rootForRepository(webkit); + assert(otherWebkitRoot instanceof UploadedFile); + assert.strictEqual(otherWebkitRoot.filename(), 'root124.dat'); + assert.deepEqual(otherCommitSet.allRootFiles(), [otherWebkitRoot]); + + MockRemoteAPI.reset(); + syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); + assertAndResolveRequest(requests[0], 'GET', MockData.buildbotBuildersURL(), MockData.mockBuildbotBuilders()); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 3); + assertAndResolveRequest(requests[0], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[1], 'GET', MockData.pendingBuildsUrl('some builder'), {}); + assertAndResolveRequest(requests[2], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 6); + assertAndResolveRequest(requests[3], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[4], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber})); + assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber})); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 7); + assertAndResolveRequest(requests[6], 'POST', '/api/v2/forceschedulers/force-ab-tests', 'OK'); + assert.deepEqual(requests[6].data, {'id': '3', 'jsonrpc': '2.0', 'method': 'force', 'params': + {'wk': '191622', 'build-request-id': '3', 'forcescheduler': 'force-ab-tests', 'test': 'some-test', + 'roots': `[{"url":"http://localhost:8180/api/uploaded-file/${webkitRoot.id()}.dat"}]`}}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 10); + assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some builder'), {}); + assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 13); + assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some tester', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('some tester'), buildRequestId: 3, buildTag: firstTestBuildTag})); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber})); + assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber})); + await syncPromise; + + await TestGroup.fetchForTask(taskId, true); + + assert.strictEqual(testGroups.length, 1); + testGroup = testGroups[0]; + assert.strictEqual(testGroup.buildRequests().length, 6); + + buildRequest = testGroup.buildRequests()[0]; + assert(buildRequest.isBuild()); + assert(!buildRequest.isTest()); + assert.strictEqual(buildRequest.statusLabel(), 'Completed'); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildNumber)); + assert.strictEqual(buildRequest.statusDescription(), null); + assert.notEqual(buildRequest.buildId(), null); + + otherBuildRequest = testGroup.buildRequests()[1]; + assert(otherBuildRequest.isBuild()); + assert(!otherBuildRequest.isTest()); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Completed'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildNumber)); + assert.strictEqual(otherBuildRequest.statusDescription(), null); + assert.notEqual(otherBuildRequest.buildId(), null); + + firstTestRequest = testGroup.buildRequests()[2]; + assert(!firstTestRequest.isBuild()); + assert(firstTestRequest.isTest()); + assert.strictEqual(firstTestRequest.statusLabel(), 'Running'); + assert.strictEqual(firstTestRequest.statusUrl(), MockData.statusUrl('some tester', firstTestBuildTag)); + assert.strictEqual(firstTestRequest.statusDescription(), null); + assert.strictEqual(firstTestRequest.buildId(), null); + + secondTestRequest = testGroup.buildRequests()[3]; + assert(!secondTestRequest.isBuild()); + assert(secondTestRequest.isTest()); + assert.strictEqual(secondTestRequest.statusLabel(), 'Waiting'); + assert.strictEqual(secondTestRequest.statusUrl(), null); + assert.strictEqual(secondTestRequest.statusDescription(), null); + assert.strictEqual(secondTestRequest.buildId(), null); + + MockRemoteAPI.reset(); + syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); + assertAndResolveRequest(requests[0], 'GET', MockData.buildbotBuildersURL(), MockData.mockBuildbotBuilders()); + MockRemoteAPI.reset(); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 3); + assertAndResolveRequest(requests[0], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[1], 'GET', MockData.pendingBuildsUrl('some builder'), {}); + assertAndResolveRequest(requests[2], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 6); + + assertAndResolveRequest(requests[3], 'GET', MockData.recentBuildsUrl('some tester', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('some tester'), buildRequestId: 3, buildTag: firstTestBuildTag})); + assertAndResolveRequest(requests[4], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber})); + assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber})); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 7); + assertAndResolveRequest(requests[6], 'POST', '/api/v2/forceschedulers/force-ab-tests', 'OK'); + assert.deepEqual(requests[6].data, {'id': '4', 'jsonrpc': '2.0', 'method': 'force', 'params': + {'wk': '191622', 'build-request-id': '4', 'forcescheduler': 'force-ab-tests', 'test': 'some-test', + 'roots': `[{"url":"http://localhost:8180/api/uploaded-file/${otherWebkitRoot.id()}.dat"}]`}}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 10); + assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some tester'), + MockData.pendingBuild({builderId: MockData.builderIDForName('some tester'), buildRequestId: 4, + buildbotBuildRequestId: 2})); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some builder'), {}); + assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + await MockRemoteAPI.waitForRequest(); + + assert.strictEqual(requests.length, 13); + assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some tester', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('some tester'), buildRequestId: 3, buildTag: firstTestBuildTag})); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber})); + assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber})); + await syncPromise; + + await TestGroup.fetchForTask(taskId, true); + + assert.strictEqual(testGroups.length, 1); + testGroup = testGroups[0]; + assert.strictEqual(testGroup.buildRequests().length, 6); + + buildRequest = testGroup.buildRequests()[0]; + assert.strictEqual(buildRequest.statusLabel(), 'Completed'); + + otherBuildRequest = testGroup.buildRequests()[1]; + assert.strictEqual(otherBuildRequest.statusLabel(), 'Completed'); + + firstTestRequest = testGroup.buildRequests()[2]; + assert(!firstTestRequest.isBuild()); + assert(firstTestRequest.isTest()); + assert.strictEqual(firstTestRequest.statusLabel(), 'Running'); + assert.strictEqual(firstTestRequest.statusUrl(), MockData.statusUrl('some tester', firstTestBuildTag)); + assert.strictEqual(firstTestRequest.statusDescription(), null); + assert.strictEqual(firstTestRequest.buildId(), null); + + secondTestRequest = testGroup.buildRequests()[3]; + assert(!secondTestRequest.isBuild()); + assert(secondTestRequest.isTest()); + assert.strictEqual(secondTestRequest.statusLabel(), 'Scheduled'); + assert.strictEqual(secondTestRequest.statusUrl(), 'http://build.webkit.org/#/buildrequests/2'); + assert.strictEqual(secondTestRequest.statusDescription(), null); + assert.strictEqual(secondTestRequest.buildId(), null); + }); + + async function resolveSyncerToBuildBotRequests(requestResolutionList) + { + const requests = MockRemoteAPI.requests; + let resolutionIndexOffset = 0; + for (let i = 0; i < requestResolutionList.length; i++) { + const resolutions = requestResolutionList[i]; + assert.strictEqual(requests.length, resolutionIndexOffset + resolutions.length); + resolutions.forEach((resolution, index) => { + assertAndResolveRequest(requests[resolutionIndexOffset + index], resolution.method, resolution.url, resolution.resolve); + if ('data' in resolution) + assert.deepEqual(requests[resolutionIndexOffset + index].data, resolution.data); + }); + resolutionIndexOffset += resolutions.length; + if (i != requestResolutionList.length - 1) + await MockRemoteAPI.waitForRequest(); + } + MockRemoteAPI.reset(); + } + + function validateFirstTwoBuildRequestsInTestGroup(testGroup, buildRequestOverride = {}, otherBuildRequestOverride = {}) + { + const webkit = Repository.findById(MockData.webkitRepositoryId()); + assert.strictEqual(testGroup.buildRequests().length, 6); + + const buildRequest = testGroup.buildRequests()[0]; + assert(buildRequest.isBuild()); + assert(!buildRequest.isTest()); + assert.strictEqual(buildRequest.statusLabel(), buildRequestOverride.statusLabel || 'Waiting'); + assert.strictEqual(buildRequest.statusUrl(), buildRequestOverride.statusUrl || null); + assert.strictEqual(buildRequest.statusDescription(), buildRequestOverride.statusDescription || null); + assert.strictEqual(buildRequest.buildId(), buildRequestOverride.buildId || null); + + const commitSet = buildRequest.commitSet(); + assert.strictEqual(commitSet.revisionForRepository(webkit), '191622'); + const webkitPatch = commitSet.patchForRepository(webkit); + assert(webkitPatch instanceof UploadedFile); + assert.strictEqual(webkitPatch.filename(), 'patch.dat'); + if (!buildRequestOverride.webkitRootUploaded) { + assert.strictEqual(commitSet.rootForRepository(webkit), null); + assert.deepEqual(commitSet.allRootFiles(), []); + } + else { + const webkitRoot = commitSet.rootForRepository(webkit); + assert(webkitRoot instanceof UploadedFile); + assert.strictEqual(webkitRoot.filename(), 'root123.dat'); assert.deepEqual(commitSet.allRootFiles(), [webkitRoot]); } @@ -836,23 +1390,155 @@ describe('sync-buildbot', function () { assert.strictEqual(otherBuildRequest.statusDescription(), otherBuildRequestOverride.statusDescription || null); assert.strictEqual(otherBuildRequest.buildId(), otherBuildRequestOverride.buildId || null); - const otherCommitSet = otherBuildRequest.commitSet(); - assert.strictEqual(otherCommitSet.revisionForRepository(webkit), '191622'); - assert.strictEqual(otherCommitSet.patchForRepository(webkit), null); + const otherCommitSet = otherBuildRequest.commitSet(); + assert.strictEqual(otherCommitSet.revisionForRepository(webkit), '191622'); + assert.strictEqual(otherCommitSet.patchForRepository(webkit), null); + + if (!otherBuildRequestOverride.webkitRootUploaded) { + assert.strictEqual(otherCommitSet.rootForRepository(webkit), null); + assert.deepEqual(otherCommitSet.allRootFiles(), []); + } + else { + const otherWebkitRoot = otherCommitSet.rootForRepository(webkit); + assert(otherWebkitRoot instanceof UploadedFile); + assert.strictEqual(otherWebkitRoot.filename(), 'root124.dat'); + assert.deepEqual(otherCommitSet.allRootFiles(), [otherWebkitRoot]); + } + } + + it('should be able to schedule a "paired-parallel" build request for building a patch on buildbot', async () => { + let syncPromise; + const triggerable = await createTriggerable(configWithOneTesterTwoBuilders([ + { builders: ['builder-1'], types: ['some'], platforms: ['some platform'], supportedRepetitionTypes: ['alternating', 'paired-parallel'] }])); + const firstBuildNumber = 123; + const secondBuildNumber = 124; + let testGroup = await createTestGroupWithPatch('paired-parallel'); + + const taskId = testGroup.task().id(); + assert.strictEqual(testGroup.buildRequests().length, 6); + validateFirstTwoBuildRequestsInTestGroup(testGroup); + + syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); + await resolveSyncerToBuildBotRequests([ + [ + { method: 'GET', url: MockData.buildbotBuildersURL(), resolve: MockData.mockBuildbotBuilders() }, + ], + [ + { method: 'GET', url: MockData.pendingBuildsUrl('some tester'), resolve: {} }, + { method: 'GET', url: MockData.pendingBuildsUrl('some builder'), resolve: {} }, + { method: 'GET', url: MockData.pendingBuildsUrl('other builder'), resolve: {} }, + ], + [ + { method: 'GET', url: MockData.recentBuildsUrl('some tester', 2), resolve: {} }, + { method: 'GET', url: MockData.recentBuildsUrl('some builder', 2), resolve: MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber, statusDescription: 'Compiling WTF'}) }, + { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber, statusDescription: 'Compiling WTF'}) }, + ], + [ + { method: 'GET', url: MockData.pendingBuildsUrl('some tester'), resolve: {} }, + { method: 'GET', url: MockData.pendingBuildsUrl('some builder'), resolve: {} }, + { method: 'GET', url: MockData.pendingBuildsUrl('other builder'), resolve: {} }, + ], + [ + { method: 'GET', url: MockData.recentBuildsUrl('some tester', 2), resolve: {} }, + { method: 'GET', url: MockData.recentBuildsUrl('some builder', 2), resolve: MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber, statusDescription: 'Compiling WTF'}) }, + { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber, statusDescription: 'Compiling WTF'}) }, + ] + ]); + await syncPromise; + + let testGroups = await TestGroup.fetchForTask(taskId, true); + assert.strictEqual(testGroups.length, 1); + assert.strictEqual(testGroups[0].buildRequests().length, 6); + validateFirstTwoBuildRequestsInTestGroup(testGroups[0], + {statusLabel: 'Running', statusUrl: MockData.statusUrl('some builder', firstBuildNumber), statusDescription: 'Compiling WTF'}, + {statusLabel: 'Running', statusUrl: MockData.statusUrl('other builder', secondBuildNumber), statusDescription: 'Compiling WTF'}); + + await uploadRoot(parseInt(testGroups[0].buildRequests()[0].id()), firstBuildNumber); + testGroups = await TestGroup.fetchForTask(taskId, true); + assert.strictEqual(testGroups.length, 1); + validateFirstTwoBuildRequestsInTestGroup(testGroups[0], + {statusLabel: 'Completed', statusUrl: MockData.statusUrl('some builder', firstBuildNumber), statusDescription: 'Compiling WTF', buildId: '1', webkitRootUploaded: true}, + {statusLabel: 'Running', statusUrl: MockData.statusUrl('other builder', secondBuildNumber), statusDescription: 'Compiling WTF'}); + + syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); + await resolveSyncerToBuildBotRequests([ + [ + { method: 'GET', url: MockData.buildbotBuildersURL(), resolve: MockData.mockBuildbotBuilders() }, + ], + [ + { method: 'GET', url: MockData.pendingBuildsUrl('some tester'), resolve: {} }, + { method: 'GET', url: MockData.pendingBuildsUrl('some builder'), resolve: {} }, + { method: 'GET', url: MockData.pendingBuildsUrl('other builder'), resolve: {} }, + ], + [ + { method: 'GET', url: MockData.recentBuildsUrl('some tester', 2), resolve: {} }, + { method: 'GET', url: MockData.recentBuildsUrl('some builder', 2), resolve: MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: '1', buildTag: firstBuildNumber}) }, + { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: {} }, + ], + [ + { method: 'GET', url: MockData.pendingBuildsUrl('some tester'), resolve: {} }, + { method: 'GET', url: MockData.pendingBuildsUrl('some builder'), resolve: {} }, + { method: 'GET', url: MockData.pendingBuildsUrl('other builder'), resolve: {} }, + ], + [ + { method: 'GET', url: MockData.recentBuildsUrl('some tester', 2), resolve: {} }, + { method: 'GET', url: MockData.recentBuildsUrl('some builder', 2), resolve: MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: '1', buildTag: firstBuildNumber}) }, + { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber, statusDescription: 'Compiling WTF'}) }, + ] + ]); + await syncPromise; + + await TestGroup.fetchForTask(taskId, true); + assert.strictEqual(testGroups.length, 1); + validateFirstTwoBuildRequestsInTestGroup(testGroups[0], + {statusLabel: 'Completed', statusUrl: MockData.statusUrl('some builder', firstBuildNumber), buildId: '1', webkitRootUploaded: true}, + {statusLabel: 'Running', statusUrl: MockData.statusUrl('other builder', secondBuildNumber), statusDescription: 'Compiling WTF'}); + + await uploadRoot(parseInt(testGroups[0].buildRequests()[1].id()), secondBuildNumber); + testGroups = await TestGroup.fetchForTask(taskId, true); + assert.strictEqual(testGroups.length, 1); + validateFirstTwoBuildRequestsInTestGroup(testGroups[0], + {statusLabel: 'Completed', statusUrl: MockData.statusUrl('some builder', firstBuildNumber), buildId: '1', webkitRootUploaded: true}, + {statusLabel: 'Completed', statusUrl: MockData.statusUrl('other builder', secondBuildNumber), buildId: '2', webkitRootUploaded: true, statusDescription: 'Compiling WTF'}); + + syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); + await resolveSyncerToBuildBotRequests([ + [ + { method: 'GET', url: MockData.buildbotBuildersURL(), resolve: MockData.mockBuildbotBuilders() }, + ], + [ + { method: 'GET', url: MockData.pendingBuildsUrl('some tester'), resolve: {} }, + { method: 'GET', url: MockData.pendingBuildsUrl('some builder'), resolve: {} }, + { method: 'GET', url: MockData.pendingBuildsUrl('other builder'), resolve: {} }, + ], + [ + { method: 'GET', url: MockData.recentBuildsUrl('some tester', 2), resolve: {} }, + { method: 'GET', url: MockData.recentBuildsUrl('some builder', 2), resolve: MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: '1', buildTag: firstBuildNumber}) }, + { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber}) }, + ], + [ + { method: 'GET', url: MockData.pendingBuildsUrl('some tester'), resolve: {} }, + { method: 'GET', url: MockData.pendingBuildsUrl('some builder'), resolve: {} }, + { method: 'GET', url: MockData.pendingBuildsUrl('other builder'), resolve: {} }, + ], + [ + { method: 'GET', url: MockData.recentBuildsUrl('some tester', 2), resolve: {} }, + { method: 'GET', url: MockData.recentBuildsUrl('some builder', 2), resolve: MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: '1', buildTag: firstBuildNumber}) }, + { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber}) }, + ] + ]); + await syncPromise; + + await TestGroup.fetchForTask(taskId, true); + assert.strictEqual(testGroups.length, 1); + assert.strictEqual(testGroups[0].buildRequests().length, 6); - if (!otherBuildRequestOverride.webkitRootUploaded) { - assert.strictEqual(otherCommitSet.rootForRepository(webkit), null); - assert.deepEqual(otherCommitSet.allRootFiles(), []); - } - else { - const otherWebkitRoot = otherCommitSet.rootForRepository(webkit); - assert(otherWebkitRoot instanceof UploadedFile); - assert.strictEqual(otherWebkitRoot.filename(), 'root124.dat'); - assert.deepEqual(otherCommitSet.allRootFiles(), [otherWebkitRoot]); - } - } + const buildRequest = testGroups[0].buildRequests()[2]; + assert(buildRequest.isTest()); + assert.strictEqual(buildRequest.statusLabel(), 'Waiting'); + }); - it('should be able to schedule a "paired-parallel" build request for building a patch on buildbot', async () => { + it('should still schedule other build type requests even when a build request has not finished', async () => { let syncPromise; const triggerable = await createTriggerable(configWithOneTesterTwoBuilders([ { builders: ['builder-1'], types: ['some'], platforms: ['some platform'], supportedRepetitionTypes: ['alternating', 'paired-parallel'] }])); @@ -879,6 +1565,12 @@ describe('sync-buildbot', function () { { method: 'GET', url: MockData.recentBuildsUrl('some builder', 2), resolve: MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber, statusDescription: 'Compiling WTF'}) }, { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: {} }, ], + [ + { + method: 'POST', url: '/api/v2/forceschedulers/force-ab-builds-1', resolve: 'OK', + data: {'id': '2', 'jsonrpc': '2.0', 'method': 'force', 'params': {'wk': '191622', 'build-request-id': '2', 'forcescheduler': 'force-ab-builds-1', 'checkbox': 'build-wk', 'build-wk': true}} + }, + ], [ { method: 'GET', url: MockData.pendingBuildsUrl('some tester'), resolve: {} }, { method: 'GET', url: MockData.pendingBuildsUrl('some builder'), resolve: {} }, @@ -887,7 +1579,7 @@ describe('sync-buildbot', function () { [ { method: 'GET', url: MockData.recentBuildsUrl('some tester', 2), resolve: {} }, { method: 'GET', url: MockData.recentBuildsUrl('some builder', 2), resolve: MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber, statusDescription: 'Compiling WTF'}) }, - { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: {} }, + { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber, statusDescription: 'Compiling WTF'}) }, ] ]); await syncPromise; @@ -895,12 +1587,16 @@ describe('sync-buildbot', function () { let testGroups = await TestGroup.fetchForTask(taskId, true); assert.strictEqual(testGroups.length, 1); assert.strictEqual(testGroups[0].buildRequests().length, 6); - validateFirstTwoBuildRequestsInTestGroup(testGroups[0], {statusLabel: 'Running', statusUrl: MockData.statusUrl('some builder', firstBuildNumber), statusDescription: 'Compiling WTF'}); + validateFirstTwoBuildRequestsInTestGroup(testGroups[0], + {statusLabel: 'Running', statusUrl: MockData.statusUrl('some builder', firstBuildNumber), statusDescription: 'Compiling WTF'}, + {statusLabel: 'Running', statusUrl: MockData.statusUrl('other builder', secondBuildNumber), statusDescription: 'Compiling WTF'}); await uploadRoot(parseInt(testGroups[0].buildRequests()[0].id()), firstBuildNumber); testGroups = await TestGroup.fetchForTask(taskId, true); assert.strictEqual(testGroups.length, 1); - validateFirstTwoBuildRequestsInTestGroup(testGroups[0], {statusLabel: 'Completed', statusUrl: MockData.statusUrl('some builder', firstBuildNumber), statusDescription: 'Compiling WTF', buildId: '1', webkitRootUploaded: true}); + validateFirstTwoBuildRequestsInTestGroup(testGroups[0], + {statusLabel: 'Completed', statusUrl: MockData.statusUrl('some builder', firstBuildNumber), statusDescription: 'Compiling WTF', buildId: '1', webkitRootUploaded: true}, + {statusLabel: 'Running', statusUrl: MockData.statusUrl('other builder', secondBuildNumber), statusDescription: 'Compiling WTF'}); syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); await resolveSyncerToBuildBotRequests([ @@ -917,12 +1613,6 @@ describe('sync-buildbot', function () { { method: 'GET', url: MockData.recentBuildsUrl('some builder', 2), resolve: MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: '1', buildTag: firstBuildNumber}) }, { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: {} }, ], - [ - { - method: 'POST', url: '/api/v2/forceschedulers/force-ab-builds', resolve: 'OK', - data: {'id': '2', 'jsonrpc': '2.0', 'method': 'force', 'params': {'wk': '191622', 'build-request-id': '2', 'forcescheduler': 'force-ab-builds', 'checkbox': 'build-wk', 'build-wk': true}} - }, - ], [ { method: 'GET', url: MockData.pendingBuildsUrl('some tester'), resolve: {} }, { method: 'GET', url: MockData.pendingBuildsUrl('some builder'), resolve: {} }, @@ -930,26 +1620,24 @@ describe('sync-buildbot', function () { ], [ { method: 'GET', url: MockData.recentBuildsUrl('some tester', 2), resolve: {} }, - { method: 'GET', url: MockData.recentBuildsUrl('some builder', 2), resolve: { - builds: [ - MockData.runningBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 2, buildTag: secondBuildNumber}), - MockData.finishedBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber}) - ]} }, - { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: {} }, + { method: 'GET', url: MockData.recentBuildsUrl('some builder', 2), resolve: MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: '1', buildTag: firstBuildNumber}) }, + { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber, statusDescription: 'Compiling WTF'}) }, ] ]); await syncPromise; await TestGroup.fetchForTask(taskId, true); assert.strictEqual(testGroups.length, 1); - validateFirstTwoBuildRequestsInTestGroup(testGroups[0], {statusLabel: 'Completed', statusUrl: MockData.statusUrl('some builder', firstBuildNumber), buildId: '1', webkitRootUploaded: true}, - {statusLabel: 'Running', statusUrl: MockData.statusUrl('some builder', secondBuildNumber)}); + validateFirstTwoBuildRequestsInTestGroup(testGroups[0], + {statusLabel: 'Completed', statusUrl: MockData.statusUrl('some builder', firstBuildNumber), buildId: '1', webkitRootUploaded: true}, + {statusLabel: 'Running', statusUrl: MockData.statusUrl('other builder', secondBuildNumber), statusDescription: 'Compiling WTF'}); - await uploadRoot(parseInt(testGroups[0].buildRequests()[1].id()), 124); + await uploadRoot(parseInt(testGroups[0].buildRequests()[1].id()), secondBuildNumber); testGroups = await TestGroup.fetchForTask(taskId, true); assert.strictEqual(testGroups.length, 1); - validateFirstTwoBuildRequestsInTestGroup(testGroups[0], {statusLabel: 'Completed', statusUrl: MockData.statusUrl('some builder', firstBuildNumber), buildId: '1', webkitRootUploaded: true}, - {statusLabel: 'Completed', statusUrl: MockData.statusUrl('some builder', secondBuildNumber), buildId: '2', webkitRootUploaded: true}); + validateFirstTwoBuildRequestsInTestGroup(testGroups[0], + {statusLabel: 'Completed', statusUrl: MockData.statusUrl('some builder', firstBuildNumber), buildId: '1', webkitRootUploaded: true}, + {statusLabel: 'Completed', statusUrl: MockData.statusUrl('other builder', secondBuildNumber), buildId: '2', webkitRootUploaded: true, statusDescription: 'Compiling WTF'}); syncPromise = triggerable.initSyncers().then(() => triggerable.syncOnce()); await resolveSyncerToBuildBotRequests([ @@ -963,10 +1651,8 @@ describe('sync-buildbot', function () { ], [ { method: 'GET', url: MockData.recentBuildsUrl('some tester', 2), resolve: {} }, - { method: 'GET', url: MockData.recentBuildsUrl('some builder', 2), resolve: { builds: [ - MockData.finishedBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 2, buildTag: secondBuildNumber}), - MockData.finishedBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber})]}}, - { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: {} }, + { method: 'GET', url: MockData.recentBuildsUrl('some builder', 2), resolve: MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: '1', buildTag: firstBuildNumber}) }, + { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber}) }, ], [ { method: 'GET', url: MockData.pendingBuildsUrl('some tester'), resolve: {} }, @@ -975,10 +1661,8 @@ describe('sync-buildbot', function () { ], [ { method: 'GET', url: MockData.recentBuildsUrl('some tester', 2), resolve: {} }, - { method: 'GET', url: MockData.recentBuildsUrl('some builder', 2), resolve: { builds: [ - MockData.finishedBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 2, buildTag: secondBuildNumber}), - MockData.finishedBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildNumber})]}}, - { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: {} }, + { method: 'GET', url: MockData.recentBuildsUrl('some builder', 2), resolve: MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: '1', buildTag: firstBuildNumber}) }, + { method: 'GET', url: MockData.recentBuildsUrl('other builder', 2), resolve: MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildNumber}) }, ] ]); await syncPromise; @@ -997,6 +1681,8 @@ describe('sync-buildbot', function () { let triggerable; let taskId = null; let syncPromise; + const firstBuildTag = 12; + const secondBuildTag = 24; return createTriggerable().then((newTriggerable) => { triggerable = newTriggerable; return createTestGroupWithPatch(); @@ -1056,18 +1742,26 @@ describe('sync-buildbot', function () { {'wk': '191622', 'wk-patch': RemoteAPI.url('/api/uploaded-file/1.dat'), 'checkbox': 'build-wk', 'build-wk': true, 'build-request-id': '1', 'forcescheduler': 'force-ab-builds'}}); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 10); - assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some tester'), {}); - assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some builder'), + assert.strictEqual(requests.length, 8); + assertAndResolveRequest(requests[7], 'POST', '/api/v2/forceschedulers/force-ab-builds-1', 'OK'); + assert.deepEqual(requests[7].data, {'id': '2', 'jsonrpc': '2.0', 'method': 'force', 'params': + {'wk': '191622', 'checkbox': 'build-wk', 'build-wk': true, 'build-request-id': '2', 'forcescheduler': 'force-ab-builds-1'}}); + return MockRemoteAPI.waitForRequest(); + }).then(() => { + assert.strictEqual(requests.length, 11); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('some builder'), MockData.pendingBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})); - assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + assertAndResolveRequest(requests[10], 'GET', MockData.pendingBuildsUrl('other builder'), + MockData.pendingBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2})); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 13); - assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); - assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some builder', 2), - MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})); - assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); + assert.strictEqual(requests.length, 14); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildTag})); + assertAndResolveRequest(requests[13], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildTag})); return syncPromise; }).then(() => { return TestGroup.fetchForTask(taskId, true); @@ -1081,7 +1775,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Running'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.strictEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -1095,8 +1789,8 @@ describe('sync-buildbot', function () { const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); - assert.strictEqual(otherBuildRequest.statusLabel(), 'Waiting'); - assert.strictEqual(otherBuildRequest.statusUrl(), null); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.strictEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -1105,7 +1799,7 @@ describe('sync-buildbot', function () { assert.strictEqual(otherCommitSet.rootForRepository(webkit), null); assert.deepEqual(otherCommitSet.allRootFiles(), []); - return uploadRoot(parseInt(buildRequest.id()), 123); + return uploadRoot(parseInt(buildRequest.id()), firstBuildTag); }).then(() => { return TestGroup.fetchForTask(taskId, true); }).then((testGroups) => { @@ -1118,7 +1812,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Completed'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.notEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -1128,14 +1822,14 @@ describe('sync-buildbot', function () { assert.strictEqual(webkitPatch.filename(), 'patch.dat'); const webkitRoot = commitSet.rootForRepository(webkit); assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.strictEqual(webkitRoot.filename(), `root${firstBuildTag}.dat`); assert.deepEqual(commitSet.allRootFiles(), [webkitRoot]); const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); - assert.strictEqual(otherBuildRequest.statusLabel(), 'Waiting'); - assert.strictEqual(otherBuildRequest.statusUrl(), null); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.strictEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -1160,29 +1854,22 @@ describe('sync-buildbot', function () { assertAndResolveRequest(requests[3], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); assertAndResolveRequest(requests[4], 'GET', MockData.recentBuildsUrl('some builder', 2), MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})); - assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); - return MockRemoteAPI.waitForRequest(); - }).then(() => { - assert.strictEqual(requests.length, 7); - assertAndResolveRequest(requests[6], 'POST', '/api/v2/forceschedulers/force-ab-builds', 'OK'); - assert.deepEqual(requests[6].data, {'id': '2', 'jsonrpc': '2.0', 'method': 'force', 'params': - {'wk': '191622', 'build-request-id': '2', 'forcescheduler': 'force-ab-builds', 'checkbox': 'build-wk', 'build-wk': true}}); + assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2})); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 10); - assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some tester'), {}); - assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some builder'), {}); - assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + assert.strictEqual(requests.length, 9); + assertAndResolveRequest(requests[6], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some builder'), {}); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('other builder'), {}); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 13); - assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); - assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some builder', 2), { - 'builds': [ - MockData.runningBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 2}), - MockData.finishedBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})] - }); - assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); + assert.strictEqual(requests.length, 12); + assertAndResolveRequest(requests[9], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2})); return syncPromise; }).then(() => { return TestGroup.fetchForTask(taskId, true); @@ -1196,7 +1883,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Completed'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.notEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -1206,14 +1893,14 @@ describe('sync-buildbot', function () { assert.strictEqual(webkitPatch.filename(), 'patch.dat'); const webkitRoot = commitSet.rootForRepository(webkit); assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.strictEqual(webkitRoot.filename(), `root${firstBuildTag}.dat`); assert.deepEqual(commitSet.allRootFiles(), [webkitRoot]); const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); - assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.strictEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -1222,7 +1909,7 @@ describe('sync-buildbot', function () { assert.strictEqual(otherCommitSet.rootForRepository(webkit), null); assert.deepEqual(otherCommitSet.allRootFiles(), []); - return uploadRoot(parseInt(otherBuildRequest.id()), 124); + return uploadRoot(parseInt(otherBuildRequest.id()), secondBuildTag); }).then(() => { return TestGroup.fetchForTask(taskId, true); }).then((testGroups) => { @@ -1235,7 +1922,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Completed'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.notEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -1245,14 +1932,14 @@ describe('sync-buildbot', function () { assert.strictEqual(webkitPatch.filename(), 'patch.dat'); const webkitRoot = commitSet.rootForRepository(webkit); assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.strictEqual(webkitRoot.filename(), `root${firstBuildTag}.dat`); assert.deepEqual(commitSet.allRootFiles(), [webkitRoot]); const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); assert.strictEqual(otherBuildRequest.statusLabel(), 'Completed'); - assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.notEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -1260,7 +1947,7 @@ describe('sync-buildbot', function () { assert.strictEqual(otherCommitSet.patchForRepository(webkit), null); const otherWebkitRoot = otherCommitSet.rootForRepository(webkit); assert(otherWebkitRoot instanceof UploadedFile); - assert.strictEqual(otherWebkitRoot.filename(), 'root124.dat'); + assert.strictEqual(otherWebkitRoot.filename(), `root${secondBuildTag}.dat`); assert.deepEqual(otherCommitSet.allRootFiles(), [otherWebkitRoot]); }); }); @@ -1270,6 +1957,8 @@ describe('sync-buildbot', function () { let triggerable; let taskId = null; let syncPromise; + let firstBuildTag = 12; + let secondBuildTag = 24; return createTriggerable(configWithPlatformName).then((newTriggerable) => { triggerable = newTriggerable; return createTestGroupWithPatch(); @@ -1329,18 +2018,26 @@ describe('sync-buildbot', function () { {'wk': '191622', 'wk-patch': RemoteAPI.url('/api/uploaded-file/1.dat'), 'build-request-id': '1', 'forcescheduler': 'force-ab-builds', 'checkbox': 'build-wk', 'build-wk': true, 'platform-name': 'some platform'}}); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 10); - assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some tester'), {}); - assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some builder'), + assert.strictEqual(requests.length, 8); + assertAndResolveRequest(requests[7], 'POST', '/api/v2/forceschedulers/force-ab-builds-1', 'OK'); + assert.deepEqual(requests[7].data, {'id': '2', 'jsonrpc': '2.0', 'method': 'force', 'params': + {'wk': '191622', 'checkbox': 'build-wk', 'build-wk': true, 'build-request-id': '2', 'forcescheduler': 'force-ab-builds-1', 'platform-name': 'some platform'}}); + return MockRemoteAPI.waitForRequest(); + }).then(() => { + assert.strictEqual(requests.length, 11); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('some builder'), MockData.pendingBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})); - assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + assertAndResolveRequest(requests[10], 'GET', MockData.pendingBuildsUrl('other builder'), + MockData.pendingBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2})); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 13); - assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); - assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some builder', 2), - MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})); - assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); + assert.strictEqual(requests.length, 14); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildTag})); + assertAndResolveRequest(requests[13], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildTag})); return syncPromise; }).then(() => { return TestGroup.fetchForTask(taskId, true); @@ -1354,7 +2051,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Running'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.strictEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -1368,8 +2065,8 @@ describe('sync-buildbot', function () { const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); - assert.strictEqual(otherBuildRequest.statusLabel(), 'Waiting'); - assert.strictEqual(otherBuildRequest.statusUrl(), null); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.strictEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -1378,7 +2075,7 @@ describe('sync-buildbot', function () { assert.strictEqual(otherCommitSet.rootForRepository(webkit), null); assert.deepEqual(otherCommitSet.allRootFiles(), []); - return uploadRoot(parseInt(buildRequest.id()), 123); + return uploadRoot(parseInt(buildRequest.id()), firstBuildTag); }).then(() => { return TestGroup.fetchForTask(taskId, true); }).then((testGroups) => { @@ -1391,7 +2088,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Completed'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.notEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -1401,14 +2098,14 @@ describe('sync-buildbot', function () { assert.strictEqual(webkitPatch.filename(), 'patch.dat'); const webkitRoot = commitSet.rootForRepository(webkit); assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.strictEqual(webkitRoot.filename(), `root${firstBuildTag}.dat`); assert.deepEqual(commitSet.allRootFiles(), [webkitRoot]); const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); - assert.strictEqual(otherBuildRequest.statusLabel(), 'Waiting'); - assert.strictEqual(otherBuildRequest.statusUrl(), null); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.strictEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -1432,30 +2129,23 @@ describe('sync-buildbot', function () { assert.strictEqual(requests.length, 6); assertAndResolveRequest(requests[3], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); assertAndResolveRequest(requests[4], 'GET', MockData.recentBuildsUrl('some builder', 2), - MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})); - assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); - return MockRemoteAPI.waitForRequest(); - }).then(() => { - assert.strictEqual(requests.length, 7); - assertAndResolveRequest(requests[6], 'POST', '/api/v2/forceschedulers/force-ab-builds', 'OK'); - assert.deepEqual(requests[6].data, {'id': '2', 'jsonrpc': '2.0', 'method': 'force', 'params': - {'wk': '191622', 'build-request-id': '2', 'forcescheduler': 'force-ab-builds', 'checkbox': 'build-wk', 'build-wk': true, 'platform-name': 'some platform'}}); + MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildTag})); + assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildTag})); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 10); - assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some tester'), {}); - assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some builder'), {}); - assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + assert.strictEqual(requests.length, 9); + assertAndResolveRequest(requests[6], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some builder'), {}); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('other builder'), {}); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 13); - assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); - assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some builder', 2), { - 'builds': [ - MockData.runningBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 2}), - MockData.finishedBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})] - }); - assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); + assert.strictEqual(requests.length, 12); + assertAndResolveRequest(requests[9], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildTag})); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildTag})); return syncPromise; }).then(() => { return TestGroup.fetchForTask(taskId, true); @@ -1469,7 +2159,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Completed'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.notEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -1479,14 +2169,14 @@ describe('sync-buildbot', function () { assert.strictEqual(webkitPatch.filename(), 'patch.dat'); const webkitRoot = commitSet.rootForRepository(webkit); assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.strictEqual(webkitRoot.filename(), `root${firstBuildTag}.dat`); assert.deepEqual(commitSet.allRootFiles(), [webkitRoot]); const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); - assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.strictEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -1495,7 +2185,7 @@ describe('sync-buildbot', function () { assert.strictEqual(otherCommitSet.rootForRepository(webkit), null); assert.deepEqual(otherCommitSet.allRootFiles(), []); - return uploadRoot(parseInt(otherBuildRequest.id()), 124); + return uploadRoot(parseInt(otherBuildRequest.id()), secondBuildTag); }).then(() => { return TestGroup.fetchForTask(taskId, true); }).then((testGroups) => { @@ -1508,7 +2198,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Completed'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.notEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -1518,14 +2208,14 @@ describe('sync-buildbot', function () { assert.strictEqual(webkitPatch.filename(), 'patch.dat'); const webkitRoot = commitSet.rootForRepository(webkit); assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.strictEqual(webkitRoot.filename(), `root${firstBuildTag}.dat`); assert.deepEqual(commitSet.allRootFiles(), [webkitRoot]); const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); assert.strictEqual(otherBuildRequest.statusLabel(), 'Completed'); - assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.notEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -1533,7 +2223,7 @@ describe('sync-buildbot', function () { assert.strictEqual(otherCommitSet.patchForRepository(webkit), null); const otherWebkitRoot = otherCommitSet.rootForRepository(webkit); assert(otherWebkitRoot instanceof UploadedFile); - assert.strictEqual(otherWebkitRoot.filename(), 'root124.dat'); + assert.strictEqual(otherWebkitRoot.filename(), `root${secondBuildTag}.dat`); assert.deepEqual(otherCommitSet.allRootFiles(), [otherWebkitRoot]); }); }); @@ -1731,7 +2421,7 @@ describe('sync-buildbot', function () { }); }); - it('should cancel builds for testing when a build to build a patch fails', () => { + it('should cancel all test type requests for testing when a build to build a patch fails', () => { const requests = MockRemoteAPI.requests; let triggerable; let taskId = null; @@ -1777,16 +2467,23 @@ describe('sync-buildbot', function () { MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 1, buildTag: 312})); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 9); - assertAndResolveRequest(requests[6], 'GET', MockData.pendingBuildsUrl('some tester'), {}); - assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some builder'), {}); - assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + assert.strictEqual(requests.length, 7); + assertAndResolveRequest(requests[6], 'POST', '/api/v2/forceschedulers/force-ab-builds', 'OK'); + assert.deepEqual(requests[6].data, {'id': '2', 'jsonrpc': '2.0', 'method': 'force', 'params': + {'wk': '191622', 'build-request-id': '2', 'forcescheduler': 'force-ab-builds', 'checkbox': 'build-wk', 'build-wk': true}}); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 12); - assertAndResolveRequest(requests[9], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); - assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some builder', 2), {}); - assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('other builder', 2), + assert.strictEqual(requests.length, 10); + assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some builder'), + MockData.pendingBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 2, buildbotBuildRequestId: 20})); + assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + return MockRemoteAPI.waitForRequest(); + }).then(() => { + assert.strictEqual(requests.length, 13); + assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some builder', 2), {}); + assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('other builder', 2), MockData.finishedBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 1, buildTag: 312})); return syncPromise; }).then(() => { @@ -1803,10 +2500,22 @@ describe('sync-buildbot', function () { assert(buildReqeusts[1].isBuild()); assert(!buildReqeusts[1].isTest()); - assert.strictEqual(buildReqeusts[1].statusLabel(), 'Failed'); - assert.strictEqual(buildReqeusts[1].statusUrl(), null); + assert.strictEqual(buildReqeusts[1].statusLabel(), 'Scheduled'); + assert.strictEqual(buildReqeusts[1].statusUrl(), 'http://build.webkit.org/#/buildrequests/20'); assert.strictEqual(buildReqeusts[1].buildId(), null); + assert(!buildReqeusts[2].isBuild()); + assert(buildReqeusts[2].isTest()); + assert.strictEqual(buildReqeusts[2].statusLabel(), 'Failed'); + assert.strictEqual(buildReqeusts[2].statusUrl(), null); + assert.strictEqual(buildReqeusts[2].buildId(), null); + + assert(!buildReqeusts[3].isBuild()); + assert(buildReqeusts[3].isTest()); + assert.strictEqual(buildReqeusts[3].statusLabel(), 'Failed'); + assert.strictEqual(buildReqeusts[3].statusUrl(), null); + assert.strictEqual(buildReqeusts[3].buildId(), null); + function assertTestBuildHasFailed(request) { assert(!request.isBuild()); @@ -1826,6 +2535,8 @@ describe('sync-buildbot', function () { let triggerable; let taskId = null; let syncPromise; + const firstBuildTag = 12; + const secondBuildTag = 24; return createTriggerable().then((newTriggerable) => { triggerable = newTriggerable; return createTestGroupWihOwnedCommit(); @@ -1888,18 +2599,26 @@ describe('sync-buildbot', function () { {'wk': '191622', 'build-request-id': '1', 'forcescheduler': 'force-ab-builds', 'owned-commits': `{"WebKit":[{"revision":"owned-jsc-6161","repository":"JavaScriptCore","ownerRevision":"191622"}]}`}}); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 10); - assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some tester'), {}); - assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some builder'), + assert.strictEqual(requests.length, 8); + assertAndResolveRequest(requests[7], 'POST', '/api/v2/forceschedulers/force-ab-builds-1', 'OK'); + assert.deepEqual(requests[7].data, {'id': '2', 'jsonrpc': '2.0', 'method': 'force', 'params': + {'wk': '192736', 'build-request-id': '2', 'forcescheduler': 'force-ab-builds-1', 'owned-commits': `{"WebKit":[{"revision":"owned-jsc-9191","repository":"JavaScriptCore","ownerRevision":"192736"}]}`}}); + return MockRemoteAPI.waitForRequest(); + }).then(() => { + assert.strictEqual(requests.length, 11); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('some builder'), MockData.pendingBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})); - assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + assertAndResolveRequest(requests[10], 'GET', MockData.pendingBuildsUrl('other builder'), + MockData.pendingBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2})); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 13); - assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); - assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some builder', 2), - MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})); - assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); + assert.strictEqual(requests.length, 14); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildTag})); + assertAndResolveRequest(requests[13], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildTag})); return syncPromise; }).then(() => { return TestGroup.fetchForTask(taskId, true); @@ -1914,7 +2633,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Running'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.strictEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -1928,8 +2647,8 @@ describe('sync-buildbot', function () { const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); - assert.strictEqual(otherBuildRequest.statusLabel(), 'Waiting'); - assert.strictEqual(otherBuildRequest.statusUrl(), null); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.strictEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -1940,7 +2659,7 @@ describe('sync-buildbot', function () { assert.strictEqual(otherCommitSet.ownerRevisionForRepository(ownedJSC), otherCommitSet.revisionForRepository(webkit)); assert.deepEqual(otherCommitSet.allRootFiles(), []); - return uploadRoot(parseInt(buildRequest.id()), 123).then(() => uploadRoot(parseInt(buildRequest.id()), 123, [{ownerRepository: 'WebKit', ownedRepository: 'JavaScriptCore'}], '2017-05-10T02:54:09.666')); + return uploadRoot(parseInt(buildRequest.id()), firstBuildTag).then(() => uploadRoot(parseInt(buildRequest.id()), firstBuildTag, [{ownerRepository: 'WebKit', ownedRepository: 'JavaScriptCore'}], '2017-05-10T02:54:09.666')); }).then(() => { return TestGroup.fetchForTask(taskId, true); }).then((testGroups) => { @@ -1954,7 +2673,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Completed'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.notEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -1964,17 +2683,17 @@ describe('sync-buildbot', function () { assert.strictEqual(commitSet.ownerRevisionForRepository(ownedJSC), commitSet.revisionForRepository(webkit)); const webkitRoot = commitSet.rootForRepository(webkit); assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.strictEqual(webkitRoot.filename(), `root${firstBuildTag}.dat`); const jscRoot = commitSet.rootForRepository(ownedJSC); assert(jscRoot instanceof UploadedFile); - assert.strictEqual(jscRoot.filename(), 'root123.dat'); + assert.strictEqual(jscRoot.filename(), `root${firstBuildTag}.dat`); assert.deepEqual(commitSet.allRootFiles(), [webkitRoot, jscRoot]); const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); - assert.strictEqual(otherBuildRequest.statusLabel(), 'Waiting'); - assert.strictEqual(otherBuildRequest.statusUrl(), null); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.strictEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -2004,26 +2723,20 @@ describe('sync-buildbot', function () { assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 7); - assertAndResolveRequest(requests[6], 'POST', '/api/v2/forceschedulers/force-ab-builds', 'OK'); - assert.deepEqual(requests[6].data, {'id': '2', 'jsonrpc': '2.0', 'method': 'force', 'params': - {'wk': '192736', 'build-request-id': '2', 'forcescheduler': 'force-ab-builds', 'owned-commits': `{"WebKit":[{"revision":"owned-jsc-9191","repository":"JavaScriptCore","ownerRevision":"192736"}]}`}}); - return MockRemoteAPI.waitForRequest(); - }).then(() => { - assert.strictEqual(requests.length, 10); - assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some tester'), {}); - assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some builder'), {}); - assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + assert.strictEqual(requests.length, 9); + assertAndResolveRequest(requests[6], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some builder'), + MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildTag})); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('other builder'), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildTag})); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 13); - assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); - assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some builder', 2), { - 'builds': [ - MockData.runningBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 2}), - MockData.finishedBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})] - }); - assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); + assert.strictEqual(requests.length, 12); + assertAndResolveRequest(requests[9], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildTag})); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildTag})); return syncPromise; }).then(() => { return TestGroup.fetchForTask(taskId, true); @@ -2038,7 +2751,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Completed'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.notEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -2048,17 +2761,17 @@ describe('sync-buildbot', function () { assert.strictEqual(commitSet.ownerRevisionForRepository(ownedJSC), commitSet.revisionForRepository(webkit)); const webkitRoot = commitSet.rootForRepository(webkit); assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.strictEqual(webkitRoot.filename(), `root${firstBuildTag}.dat`); const jscRoot = commitSet.rootForRepository(ownedJSC); assert(jscRoot instanceof UploadedFile); - assert.strictEqual(jscRoot.filename(), 'root123.dat'); + assert.strictEqual(jscRoot.filename(), `root${firstBuildTag}.dat`); assert.deepEqual(commitSet.allRootFiles(), [webkitRoot, jscRoot]); const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); - assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.strictEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -2069,7 +2782,7 @@ describe('sync-buildbot', function () { assert.strictEqual(otherCommitSet.ownerRevisionForRepository(ownedJSC), otherCommitSet.revisionForRepository(webkit)); assert.deepEqual(otherCommitSet.allRootFiles(), []); - return uploadRoot(parseInt(otherBuildRequest.id()), 124).then(() => uploadRoot(parseInt(otherBuildRequest.id()), 124, [{ownerRepository: 'WebKit', ownedRepository: 'JavaScriptCore'}], '2017-05-10T02:54:09.666')); + return uploadRoot(parseInt(otherBuildRequest.id()), secondBuildTag).then(() => uploadRoot(parseInt(otherBuildRequest.id()), secondBuildTag, [{ownerRepository: 'WebKit', ownedRepository: 'JavaScriptCore'}], '2017-05-10T02:54:09.666')); }).then(() => { return TestGroup.fetchForTask(taskId, true); }).then((testGroups) => { @@ -2083,7 +2796,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Completed'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.notEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -2093,17 +2806,17 @@ describe('sync-buildbot', function () { assert.strictEqual(commitSet.ownerRevisionForRepository(ownedJSC), commitSet.revisionForRepository(webkit)); const webkitRoot = commitSet.rootForRepository(webkit); assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.strictEqual(webkitRoot.filename(), `root${firstBuildTag}.dat`); const jscRoot = commitSet.rootForRepository(ownedJSC); assert(jscRoot instanceof UploadedFile); - assert.strictEqual(jscRoot.filename(), 'root123.dat'); + assert.strictEqual(jscRoot.filename(), `root${firstBuildTag}.dat`); assert.deepEqual(commitSet.allRootFiles(), [webkitRoot, jscRoot]); const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); assert.strictEqual(otherBuildRequest.statusLabel(), 'Completed'); - assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.notEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -2113,10 +2826,10 @@ describe('sync-buildbot', function () { assert.strictEqual(otherCommitSet.ownerRevisionForRepository(ownedJSC), otherCommitSet.revisionForRepository(webkit)); const otherWebkitRoot = otherCommitSet.rootForRepository(webkit); assert(otherWebkitRoot instanceof UploadedFile); - assert.strictEqual(otherWebkitRoot.filename(), 'root124.dat'); + assert.strictEqual(otherWebkitRoot.filename(), `root${secondBuildTag}.dat`); const otherJSCRoot = otherCommitSet.rootForRepository(ownedJSC); assert(otherJSCRoot instanceof UploadedFile); - assert.strictEqual(otherJSCRoot.filename(), 'root124.dat'); + assert.strictEqual(otherJSCRoot.filename(), `root${secondBuildTag}.dat`); assert.deepEqual(otherCommitSet.allRootFiles(), [otherWebkitRoot, otherJSCRoot]); }); }); @@ -2126,6 +2839,8 @@ describe('sync-buildbot', function () { let triggerable; let taskId = null; let syncPromise; + const firstBuildTag = 12; + const secondBuildTag = 24; return createTriggerable().then((newTriggerable) => { triggerable = newTriggerable; return createTestGroupWihOwnedCommit(); @@ -2185,21 +2900,29 @@ describe('sync-buildbot', function () { assert.strictEqual(requests.length, 7); assertAndResolveRequest(requests[6], 'POST', '/api/v2/forceschedulers/force-ab-builds', 'OK'); assert.deepEqual(requests[6].data, {'id': '1', 'jsonrpc': '2.0', 'method': 'force', 'params': - {'wk': '191622', 'build-request-id': '1', 'forcescheduler': 'force-ab-builds', 'owned-commits': `{"WebKit":[{"revision":"owned-jsc-6161","repository":"JavaScriptCore","ownerRevision":"191622"}]}`}}); + {'wk': '191622', 'build-request-id': '1', 'forcescheduler': 'force-ab-builds', 'owned-commits': `{"WebKit":[{"revision":"owned-jsc-6161","repository":"JavaScriptCore","ownerRevision":"191622"}]}`}}); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 10); - assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some tester'), {}); - assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some builder'), + assert.strictEqual(requests.length, 8); + assertAndResolveRequest(requests[7], 'POST', '/api/v2/forceschedulers/force-ab-builds-1', 'OK'); + assert.deepEqual(requests[7].data, {'id': '2', 'jsonrpc': '2.0', 'method': 'force', 'params': + {'wk': '192736', 'build-request-id': '2', 'forcescheduler': 'force-ab-builds-1', 'owned-commits': `{"WebKit":[{"revision":"owned-jsc-9191","repository":"JavaScriptCore","ownerRevision":"192736"}]}`}}); + return MockRemoteAPI.waitForRequest(); + }).then(() => { + assert.strictEqual(requests.length, 11); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('some builder'), MockData.pendingBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})); - assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + assertAndResolveRequest(requests[10], 'GET', MockData.pendingBuildsUrl('other builder'), + MockData.pendingBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2})); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 13); - assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); - assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some builder', 2), - MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})); - assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); + assert.strictEqual(requests.length, 14); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildTag})); + assertAndResolveRequest(requests[13], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildTag})); return syncPromise; }).then(() => { return TestGroup.fetchForTask(taskId, true); @@ -2214,7 +2937,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Running'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.strictEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -2228,8 +2951,8 @@ describe('sync-buildbot', function () { const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); - assert.strictEqual(otherBuildRequest.statusLabel(), 'Waiting'); - assert.strictEqual(otherBuildRequest.statusUrl(), null); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.strictEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -2240,7 +2963,7 @@ describe('sync-buildbot', function () { assert.strictEqual(otherCommitSet.ownerRevisionForRepository(ownedJSC), otherCommitSet.revisionForRepository(webkit)); assert.deepEqual(otherCommitSet.allRootFiles(), []); - return uploadRoot(parseInt(buildRequest.id()), 123); + return uploadRoot(parseInt(buildRequest.id()), firstBuildTag); }).then(() => { return TestGroup.fetchForTask(taskId, true); }).then((testGroups) => { @@ -2254,7 +2977,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Running'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.strictEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -2264,14 +2987,14 @@ describe('sync-buildbot', function () { assert.strictEqual(commitSet.ownerRevisionForRepository(ownedJSC), commitSet.revisionForRepository(webkit)); const webkitRoot = commitSet.rootForRepository(webkit); assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.strictEqual(webkitRoot.filename(), `root${firstBuildTag}.dat`); assert.deepEqual(commitSet.allRootFiles(), [webkitRoot]); const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); - assert.strictEqual(otherBuildRequest.statusLabel(), 'Waiting'); - assert.strictEqual(otherBuildRequest.statusUrl(), null); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.strictEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -2281,7 +3004,7 @@ describe('sync-buildbot', function () { assert.strictEqual(otherCommitSet.ownerRevisionForRepository(webkit), null); assert.strictEqual(otherCommitSet.ownerRevisionForRepository(ownedJSC), otherCommitSet.revisionForRepository(webkit)); assert.deepEqual(otherCommitSet.allRootFiles(), []); - return uploadRoot(parseInt(buildRequest.id()), 123, [{ownerRepository: 'WebKit', ownedRepository: 'JavaScriptCore'}], '2017-05-10T02:54:09.666'); + return uploadRoot(parseInt(buildRequest.id()), firstBuildTag, [{ownerRepository: 'WebKit', ownedRepository: 'JavaScriptCore'}], '2017-05-10T02:54:09.666'); }).then(() => { return TestGroup.fetchForTask(taskId, true); }).then((testGroups) => { @@ -2295,7 +3018,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Completed'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.notEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -2305,17 +3028,17 @@ describe('sync-buildbot', function () { assert.strictEqual(commitSet.ownerRevisionForRepository(ownedJSC), commitSet.revisionForRepository(webkit)); const webkitRoot = commitSet.rootForRepository(webkit); assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.strictEqual(webkitRoot.filename(), `root${firstBuildTag}.dat`); const jscRoot = commitSet.rootForRepository((ownedJSC)); assert(jscRoot instanceof UploadedFile); - assert.strictEqual(jscRoot.filename(), 'root123.dat'); + assert.strictEqual(jscRoot.filename(), `root${firstBuildTag}.dat`); assert.deepEqual(commitSet.allRootFiles(), [webkitRoot, jscRoot]); const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); - assert.strictEqual(otherBuildRequest.statusLabel(), 'Waiting'); - assert.strictEqual(otherBuildRequest.statusUrl(), null); + assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.strictEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -2340,31 +3063,25 @@ describe('sync-buildbot', function () { }).then(() => { assert.strictEqual(requests.length, 6); assertAndResolveRequest(requests[3], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[4], 'GET', MockData.recentBuildsUrl('some builder', 2), - MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})); - assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); - return MockRemoteAPI.waitForRequest(); - }).then(() => { - assert.strictEqual(requests.length, 7); - assertAndResolveRequest(requests[6], 'POST', '/api/v2/forceschedulers/force-ab-builds', 'OK'); - assert.deepEqual(requests[6].data, {'id': '2', 'jsonrpc': '2.0', 'method': 'force', 'params': - {'wk': '192736', 'build-request-id': '2', 'forcescheduler': 'force-ab-builds', 'owned-commits': `{"WebKit":[{"revision":"owned-jsc-9191","repository":"JavaScriptCore","ownerRevision":"192736"}]}`}}); + MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildTag})); + assertAndResolveRequest(requests[5], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildTag})); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 10); - assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some tester'), {}); - assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('some builder'), {}); - assertAndResolveRequest(requests[9], 'GET', MockData.pendingBuildsUrl('other builder'), {}); + assert.strictEqual(requests.length, 9); + assertAndResolveRequest(requests[6], 'GET', MockData.pendingBuildsUrl('some tester'), {}); + assertAndResolveRequest(requests[7], 'GET', MockData.pendingBuildsUrl('some builder'), {}); + assertAndResolveRequest(requests[8], 'GET', MockData.pendingBuildsUrl('other builder'), {}); return MockRemoteAPI.waitForRequest(); }).then(() => { - assert.strictEqual(requests.length, 13); - assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); - assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('some builder', 2), { - 'builds': [ - MockData.runningBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 2}), - MockData.finishedBuildData({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1})] - }); - assertAndResolveRequest(requests[12], 'GET', MockData.recentBuildsUrl('other builder', 2), {}); + assert.strictEqual(requests.length, 12); + assertAndResolveRequest(requests[9], 'GET', MockData.recentBuildsUrl('some tester', 2), {}); + assertAndResolveRequest(requests[10], 'GET', MockData.recentBuildsUrl('some builder', 2), + MockData.finishedBuild({builderId: MockData.builderIDForName('some builder'), buildRequestId: 1, buildTag: firstBuildTag})); + assertAndResolveRequest(requests[11], 'GET', MockData.recentBuildsUrl('other builder', 2), + MockData.runningBuild({builderId: MockData.builderIDForName('other builder'), buildRequestId: 2, buildTag: secondBuildTag})); return syncPromise; }).then(() => { return TestGroup.fetchForTask(taskId, true); @@ -2379,7 +3096,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Completed'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.notEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -2389,17 +3106,17 @@ describe('sync-buildbot', function () { assert.strictEqual(commitSet.ownerRevisionForRepository(ownedJSC), commitSet.revisionForRepository(webkit)); const webkitRoot = commitSet.rootForRepository(webkit); assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.strictEqual(webkitRoot.filename(), `root${firstBuildTag}.dat`); const jscRoot = commitSet.rootForRepository(ownedJSC); assert(jscRoot instanceof UploadedFile); - assert.strictEqual(jscRoot.filename(), 'root123.dat'); + assert.strictEqual(jscRoot.filename(), `root${firstBuildTag}.dat`); assert.deepEqual(commitSet.allRootFiles(), [webkitRoot, jscRoot]); const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); - assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.strictEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -2410,7 +3127,7 @@ describe('sync-buildbot', function () { assert.strictEqual(otherCommitSet.ownerRevisionForRepository(ownedJSC), otherCommitSet.revisionForRepository(webkit)); assert.deepEqual(otherCommitSet.allRootFiles(), []); - return uploadRoot(parseInt(otherBuildRequest.id()), 124); + return uploadRoot(parseInt(otherBuildRequest.id()), secondBuildTag); }).then(() => { return TestGroup.fetchForTask(taskId, true); }).then((testGroups) => { @@ -2424,7 +3141,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Completed'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.notEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -2434,17 +3151,17 @@ describe('sync-buildbot', function () { assert.strictEqual(commitSet.ownerRevisionForRepository(ownedJSC), commitSet.revisionForRepository(webkit)); const webkitRoot = commitSet.rootForRepository(webkit); assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.strictEqual(webkitRoot.filename(), `root${firstBuildTag}.dat`); const jscRoot = commitSet.rootForRepository(ownedJSC); assert(jscRoot instanceof UploadedFile); - assert.strictEqual(jscRoot.filename(), 'root123.dat'); + assert.strictEqual(jscRoot.filename(), `root${firstBuildTag}.dat`); assert.deepEqual(commitSet.allRootFiles(), [webkitRoot, jscRoot]); const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); assert.strictEqual(otherBuildRequest.statusLabel(), 'Running'); - assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.strictEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -2454,9 +3171,9 @@ describe('sync-buildbot', function () { assert.strictEqual(otherCommitSet.ownerRevisionForRepository(ownedJSC), otherCommitSet.revisionForRepository(webkit)); const otherWebkitRoot = otherCommitSet.rootForRepository(webkit); assert(otherWebkitRoot instanceof UploadedFile); - assert.strictEqual(otherWebkitRoot.filename(), 'root124.dat'); + assert.strictEqual(otherWebkitRoot.filename(), `root${secondBuildTag}.dat`); assert.deepEqual(otherCommitSet.allRootFiles(), [otherWebkitRoot]); - return uploadRoot(parseInt(otherBuildRequest.id()), 124, [{ownerRepository: 'WebKit', ownedRepository: 'JavaScriptCore'}], '2017-05-10T02:54:09.666'); + return uploadRoot(parseInt(otherBuildRequest.id()), secondBuildTag, [{ownerRepository: 'WebKit', ownedRepository: 'JavaScriptCore'}], '2017-05-10T02:54:09.666'); }).then(() => { return TestGroup.fetchForTask(taskId, true); }).then((testGroups) => { @@ -2470,7 +3187,7 @@ describe('sync-buildbot', function () { assert(buildRequest.isBuild()); assert(!buildRequest.isTest()); assert.strictEqual(buildRequest.statusLabel(), 'Completed'); - assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(buildRequest.statusUrl(), MockData.statusUrl('some builder', firstBuildTag)); assert.notEqual(buildRequest.buildId(), null); const commitSet = buildRequest.commitSet(); @@ -2480,17 +3197,17 @@ describe('sync-buildbot', function () { assert.strictEqual(commitSet.ownerRevisionForRepository(ownedJSC), commitSet.revisionForRepository(webkit)); const webkitRoot = commitSet.rootForRepository(webkit); assert(webkitRoot instanceof UploadedFile); - assert.strictEqual(webkitRoot.filename(), 'root123.dat'); + assert.strictEqual(webkitRoot.filename(), `root${firstBuildTag}.dat`); const jscRoot = commitSet.rootForRepository(ownedJSC); assert(jscRoot instanceof UploadedFile); - assert.strictEqual(jscRoot.filename(), 'root123.dat'); + assert.strictEqual(jscRoot.filename(), `root${firstBuildTag}.dat`); assert.deepEqual(commitSet.allRootFiles(), [webkitRoot, jscRoot]); const otherBuildRequest = testGroup.buildRequests()[1]; assert(otherBuildRequest.isBuild()); assert(!otherBuildRequest.isTest()); assert.strictEqual(otherBuildRequest.statusLabel(), 'Completed'); - assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('some builder', 124)); + assert.strictEqual(otherBuildRequest.statusUrl(), MockData.statusUrl('other builder', secondBuildTag)); assert.notEqual(otherBuildRequest.buildId(), null); const otherCommitSet = otherBuildRequest.commitSet(); @@ -2500,10 +3217,10 @@ describe('sync-buildbot', function () { assert.strictEqual(otherCommitSet.ownerRevisionForRepository(ownedJSC), otherCommitSet.revisionForRepository(webkit)); const otherWebkitRoot = otherCommitSet.rootForRepository(webkit); assert(otherWebkitRoot instanceof UploadedFile); - assert.strictEqual(otherWebkitRoot.filename(), 'root124.dat'); + assert.strictEqual(otherWebkitRoot.filename(), `root${secondBuildTag}.dat`); const otherJSCRoot = otherCommitSet.rootForRepository(ownedJSC); assert(otherJSCRoot instanceof UploadedFile); - assert.strictEqual(otherJSCRoot.filename(), 'root124.dat'); + assert.strictEqual(otherJSCRoot.filename(), `root${secondBuildTag}.dat`); assert.deepEqual(otherCommitSet.allRootFiles(), [otherWebkitRoot, otherJSCRoot]); }); }); diff --git a/Websites/perf.webkit.org/tools/js/buildbot-syncer.js b/Websites/perf.webkit.org/tools/js/buildbot-syncer.js index 719ee95d0504a..e1759f033dbb8 100644 --- a/Websites/perf.webkit.org/tools/js/buildbot-syncer.js +++ b/Websites/perf.webkit.org/tools/js/buildbot-syncer.js @@ -148,7 +148,7 @@ class BuildbotSyncer { return this._remote.postJSON(path, data); } - scheduleRequestInGroupIfAvailable(newRequest, requestsInGroup, workerName) + scheduleRequestInGroupIfAvailable(newRequest, requestsInGroup, workerName, useWorkerWithoutRequestsInProgress = false) { assert(newRequest instanceof BuildRequest); @@ -161,9 +161,13 @@ class BuildbotSyncer { for (let entry of this._entryList) { let entryPreventsNewRequest = entry.isPending(); if (entry.isInProgress()) { - const requestInProgress = BuildRequest.findById(entry.buildRequestId()); - if (!requestInProgress || requestInProgress.testGroupId() != newRequest.testGroupId()) + if (useWorkerWithoutRequestsInProgress) entryPreventsNewRequest = true; + else { + const requestInProgress = BuildRequest.findById(entry.buildRequestId()); + if (!requestInProgress || requestInProgress.testGroupId() != newRequest.testGroupId()) + entryPreventsNewRequest = true; + } } if (entryPreventsNewRequest) { if (!entry.workerName()) @@ -178,20 +182,22 @@ class BuildbotSyncer { } } - for (let [name, entry] of latestEntryByWorkerName.entries()) { - const latestActiveBuildRequest = BuildRequest.findById(entry.buildRequestId()); - if (!latestActiveBuildRequest) - continue; - if (latestActiveBuildRequest.testGroupId() == newRequest.testGroupId()) - continue; - const testGroup = latestActiveBuildRequest.testGroup(); - if (!testGroup) { - // The request might be for a very old test group we didn't fetch. - continue; + if (newRequest.isTest()) { + for (let [name, entry] of latestEntryByWorkerName.entries()) { + const latestActiveBuildRequest = BuildRequest.findById(entry.buildRequestId()); + if (!latestActiveBuildRequest) + continue; + if (latestActiveBuildRequest.testGroupId() == newRequest.testGroupId()) + continue; + const testGroup = latestActiveBuildRequest.testGroup(); + if (!testGroup) { + // The request might be for a very old test group we didn't fetch. + continue; + } + if (testGroup.hasFinished() && !testGroup.mayNeedMoreRequests()) + continue; + usedWorkers.add(name); } - if (testGroup.hasFinished() && !testGroup.mayNeedMoreRequests()) - continue; - usedWorkers.add(name); } if (!this._workerList || hasPendingBuildsWithoutWorkerNameSpecified) { diff --git a/Websites/perf.webkit.org/tools/js/buildbot-triggerable.js b/Websites/perf.webkit.org/tools/js/buildbot-triggerable.js index 7a6bc57fa93c8..f80d2fc559a2c 100644 --- a/Websites/perf.webkit.org/tools/js/buildbot-triggerable.js +++ b/Websites/perf.webkit.org/tools/js/buildbot-triggerable.js @@ -93,17 +93,23 @@ class BuildbotTriggerable { let rootReuseUpdates = { }; this._logger.log('Scheduling builds'); const testGroupList = Array.from(buildRequestsByGroup.values()).sort(function (a, b) { return a.groupOrder - b.groupOrder; }); + const scheduledRequestIdList = []; for (const group of testGroupList) { - const request = this._nextRequestInGroup(group, updates); - if (!validRequests.has(request)) - continue; + const nextRequests = this._nextRequestsInGroup(group, updates); + for (const request of nextRequests) { + if (!validRequests.has(request)) + continue; - const shouldDefer = this._shouldDeferSequentialTestRequestForNewCommitSet(request); - if (shouldDefer) - this._logger.log(`Defer scheduling build request "${request.id()}" until completed build requests for previous configuration meet the expectation or exhaust retry.`); - else - await this._scheduleRequest(group, request, rootReuseUpdates); + const shouldDefer = this._shouldDeferSequentialTestRequestForNewCommitSet(request); + if (shouldDefer) + this._logger.log(`Defer scheduling build request "${request.id()}" until completed build requests for previous configuration meet the expectation or exhaust retry.`); + else { + const scheduled = await this._scheduleRequest(group, request, rootReuseUpdates, scheduledRequestIdList); + if (scheduled) + scheduledRequestIdList.push(parseInt(request.id())); + } + } } // Pull all buildbots for the second time since the previous step may have scheduled more builds @@ -120,25 +126,25 @@ class BuildbotTriggerable { 'buildRequestUpdates': updates}); } - async _scheduleRequest(testGroup, buildRequest, updates) + async _scheduleRequest(testGroup, buildRequest, updates, scheduledRequestIdList) { - const buildRequestForRootReuse = await buildRequest.findBuildRequestWithSameRoots(); + const buildRequestForRootReuse = await buildRequest.findBuildRequestWithSameRoots(scheduledRequestIdList); if (buildRequestForRootReuse) { if (!buildRequestForRootReuse.hasCompleted()) { this._logger.log(`Found build request ${buildRequestForRootReuse.id()} is building the same root, will wait until it finishes.`); - return; + return null; } this._logger.log(`Will reuse existing root built from ${buildRequestForRootReuse.id()} for ${buildRequest.id()}`); updates[buildRequest.id()] = {status: 'completed', url: buildRequestForRootReuse.statusUrl(), statusDescription: buildRequestForRootReuse.statusDescription(), buildRequestForRootReuse: buildRequestForRootReuse.id()}; - return; + return null; } return this._scheduleRequestIfWorkerIsAvailable(buildRequest, testGroup.requests, - buildRequest.isBuild() ? testGroup.buildSyncer : testGroup.testSyncer, - buildRequest.isBuild() ? testGroup.buildWorkerName : testGroup.testWorkerName); + buildRequest.isBuild() ? null : testGroup.testSyncer, + buildRequest.isBuild() ? null : testGroup.testWorkerName); } _shouldDeferSequentialTestRequestForNewCommitSet(buildRequest) @@ -227,14 +233,7 @@ class BuildbotTriggerable { associatedRequests.add(request); - if (request.isBuild()) { - assert(!info.buildSyncer || info.buildSyncer == syncer); - if (entry.workerName()) { - assert(!info.buildWorkerName || info.buildWorkerName == entry.workerName()); - info.buildWorkerName = entry.workerName(); - } - info.buildSyncer = syncer; - } else { + if (request.isTest()) { assert(!info.testSyncer || info.testSyncer == syncer); if (entry.workerName()) { assert(!info.testWorkerName || info.testWorkerName == entry.workerName()); @@ -267,17 +266,27 @@ class BuildbotTriggerable { return updates; } - _nextRequestInGroup(groupInfo, pendingUpdates) + _nextRequestsInGroup(groupInfo, pendingUpdates) { + const buildTypeRequests = groupInfo.requests.filter(request => request.isBuild()); + const pendingBuildTypeRequests = buildTypeRequests.filter( + request => request.isPending() && !(request.id() in pendingUpdates)); + if (pendingBuildTypeRequests.length) + return pendingBuildTypeRequests; + + if (!buildTypeRequests.every(request => request.hasCompleted())) + return []; + for (const request of groupInfo.requests) { - if (request.isScheduled() || (request.id() in pendingUpdates && pendingUpdates[request.id()]['status'] == 'scheduled')) - return null; - if (request.isPending() && !(request.id() in pendingUpdates)) - return request; + if ((request.isScheduled() || (request.id() in pendingUpdates && pendingUpdates[request.id()]['status'] == 'scheduled'))) + return []; + if (request.isPending() && !(request.id() in pendingUpdates)) { + return [request]; + } if (request.isBuild() && !request.hasCompleted()) - return null; // A build request is still pending, scheduled, running, or failed. + return []; // A build request is still pending, scheduled, running, or failed. } - return null; + return []; } _scheduleRequestIfWorkerIsAvailable(nextRequest, requestsInGroup, syncer, workerName) @@ -286,28 +295,30 @@ class BuildbotTriggerable { return null; const isFirstRequest = nextRequest == requestsInGroup[0] || !nextRequest.order(); - if (!isFirstRequest) { + if (!isFirstRequest && nextRequest.isTest()) { if (syncer) - return this._scheduleRequestWithLog(syncer, nextRequest, requestsInGroup, workerName); + return this._scheduleRequestWithLog(syncer, nextRequest, requestsInGroup, workerName, false); this._logger.error(`Could not identify the syncer for ${nextRequest.id()}.`); } - // Pick a new syncer for the first test. - for (const syncer of this._syncers) { - // Only schedule A/B tests to queues whose last job was successful. - if (syncer.isTester() && !nextRequest.order() && !syncer.lastCompletedBuildSuccessful()) - continue; + for (const useWorkerWithoutRequestsInProgress of [true, false]) { + // Pick a new syncer for the first test. + for (const syncer of this._syncers) { + // Only schedule A/B tests to queues whose last job was successful. + if (syncer.isTester() && !nextRequest.order() && !syncer.lastCompletedBuildSuccessful()) + continue; - const promise = this._scheduleRequestWithLog(syncer, nextRequest, requestsInGroup, null); - if (promise) - return promise; + const promise = this._scheduleRequestWithLog(syncer, nextRequest, requestsInGroup, null, useWorkerWithoutRequestsInProgress); + if (promise) + return promise; + } } return null; } - _scheduleRequestWithLog(syncer, request, requestsInGroup, workerName) + _scheduleRequestWithLog(syncer, request, requestsInGroup, workerName, useWorkerWithoutRequestsInProgress) { - const promise = syncer.scheduleRequestInGroupIfAvailable(request, requestsInGroup, workerName); + const promise = syncer.scheduleRequestInGroupIfAvailable(request, requestsInGroup, workerName, useWorkerWithoutRequestsInProgress); if (!promise) return promise; this._logger.log(`Scheduling build request ${request.id()}${workerName ? ' on ' + workerName : ''} in ${syncer.builderName()}`); @@ -321,7 +332,7 @@ class BuildbotTriggerable { for (let request of buildRequests) { let groupId = request.testGroupId(); if (!map.has(groupId)) // Don't use real TestGroup objects to avoid executing postgres query in the server - map.set(groupId, {id: groupId, groupOrder: groupOrder++, requests: [request], buildSyncer: null, testSyncer: null, workerName: null}); + map.set(groupId, {id: groupId, groupOrder: groupOrder++, requests: [request], testSyncer: null, workerName: null}); else map.get(groupId).requests.push(request); } diff --git a/metadata/contributors.json b/metadata/contributors.json index 2ad5bb829cfb2..141c8e36523c4 100644 --- a/metadata/contributors.json +++ b/metadata/contributors.json @@ -8231,6 +8231,17 @@ "zackr" ] }, + { + "emails" : [ + "z_ridouh@apple.com", + "zakr@apple.com" + ], + "github" : "zakariaridouh", + "name" : "Zak Ridouh", + "nicks" : [ + "zakr" + ] + }, { "emails" : [ "zlieber@chromium.org" diff --git a/windows-release.ps1 b/windows-release.ps1 index cdbe7c9949c5f..d96c9d34439a9 100644 --- a/windows-release.ps1 +++ b/windows-release.ps1 @@ -172,7 +172,7 @@ cmake -S . -B $WebKitBuild ` -DUSE_THIN_ARCHIVES=OFF ` -DENABLE_JIT=ON ` -DENABLE_DFG_JIT=ON ` - -DENABLE_FTL_JIT=OFF ` + -DENABLE_FTL_JIT=ON ` -DENABLE_SAMPLING_PROFILER=ON ` -DENABLE_WEBASSEMBLY=ON ` -DUSE_BUN_JSC_ADDITIONS=ON `