Bug 1688669 - Add a new test for network markers r=canaltinova

Differential Revision: https://phabricator.services.mozilla.com/D102940
This commit is contained in:
Julien Wajsberg 2021-02-15 16:01:15 +00:00
Родитель 5d5e29f665
Коммит 3b2478d420
8 изменённых файлов: 482 добавлений и 4 удалений

Просмотреть файл

@ -24,3 +24,13 @@ support-files = single_frame.html
support-files =
multi_frame.html
single_frame.html
[browser_test_marker_network_simple.js]
support-files = simple.html
[browser_test_marker_network_redirect.js]
support-files =
redirect.sjs
simple.html
page_with_resources.html
firefox-logo-nightly.svg

Просмотреть файл

@ -0,0 +1,304 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Test that we emit network markers accordingly.
* In this file we'll test the redirect cases.
*/
add_task(async function test_network_markers_redirect_simple() {
// In this test, we request an HTML page that gets redirected. This is a
// top-level navigation.
if (!AppConstants.MOZ_GECKO_PROFILER) {
return;
}
Assert.ok(
!Services.profiler.IsActive(),
"The profiler is not currently active"
);
startProfilerForMarkerTests();
const targetFileNameWithCacheBust = "simple.html?cacheBust=" + Math.random();
const url =
BASE_URL +
"redirect.sjs?" +
encodeURIComponent(targetFileNameWithCacheBust);
const targetUrl = BASE_URL + targetFileNameWithCacheBust;
await BrowserTestUtils.withNewTab(url, async contentBrowser => {
const contentPid = await SpecialPowers.spawn(
contentBrowser,
[],
() => Services.appinfo.processID
);
const { parentThread, contentThread } = await stopProfilerNowAndGetThreads(
contentPid
);
const parentNetworkMarkers = getInflatedNetworkMarkers(parentThread);
const contentNetworkMarkers = getInflatedNetworkMarkers(contentThread);
info(JSON.stringify(parentNetworkMarkers, null, 2));
info(JSON.stringify(contentNetworkMarkers, null, 2));
Assert.equal(
parentNetworkMarkers.length,
4,
`We should get 2 pairs of network markers in the parent thread.`
);
/* It looks like that for a redirection for the top level navigation, the
* content thread sees the markers for the second request only.
* See Bug 1692879. */
Assert.equal(
contentNetworkMarkers.length,
2,
`We should get one pair of network markers in the content thread.`
);
const parentRedirectMarker = parentNetworkMarkers[1];
const parentStopMarker = parentNetworkMarkers[3];
const contentStopMarker = contentNetworkMarkers[1];
Assert.objectContains(parentRedirectMarker, {
name: Expect.stringMatches(`Load \\d+:.*${escapeStringRegexp(url)}`),
data: Expect.objectContains({
status: "STATUS_REDIRECT",
URI: url,
RedirectURI: targetUrl,
requestMethod: "GET",
contentType: null,
startTime: Expect.number(),
endTime: Expect.number(),
domainLookupStart: Expect.number(),
domainLookupEnd: Expect.number(),
connectStart: Expect.number(),
tcpConnectEnd: Expect.number(),
connectEnd: Expect.number(),
requestStart: Expect.number(),
responseStart: Expect.number(),
responseEnd: Expect.number(),
id: Expect.number(),
pri: Expect.number(),
}),
});
const expectedProperties = {
name: Expect.stringMatches(
`Load \\d+:.*${escapeStringRegexp(targetUrl)}`
),
data: Expect.objectContains({
status: "STATUS_STOP",
URI: targetUrl,
requestMethod: "GET",
contentType: "text/html",
startTime: Expect.number(),
endTime: Expect.number(),
domainLookupStart: Expect.number(),
domainLookupEnd: Expect.number(),
connectStart: Expect.number(),
tcpConnectEnd: Expect.number(),
connectEnd: Expect.number(),
requestStart: Expect.number(),
responseStart: Expect.number(),
responseEnd: Expect.number(),
id: Expect.number(),
count: Expect.number(),
pri: Expect.number(),
}),
};
Assert.objectContains(parentStopMarker, expectedProperties);
// The cache information is missing from the content marker, it's only part
// of the parent marker. See Bug 1544821.
Assert.objectContains(parentStopMarker.data, {
// Because the request races with the cache, these 2 values are valid:
// "Missed" when the cache answered before we get a result from the network.
// "Unresolved" when we got a response from the network before the cache subsystem.
cache: Expect.stringMatches(/^(Missed|Unresolved)$/),
});
Assert.objectContains(contentStopMarker, expectedProperties);
});
});
add_task(async function test_network_markers_redirect_resources() {
// In this test we request an HTML file that itself contains resources that
// are redirected.
if (!AppConstants.MOZ_GECKO_PROFILER) {
return;
}
Assert.ok(
!Services.profiler.IsActive(),
"The profiler is not currently active"
);
startProfilerForMarkerTests();
const url = BASE_URL + "page_with_resources.html?cacheBust=" + Math.random();
await BrowserTestUtils.withNewTab(url, async contentBrowser => {
const contentPid = await SpecialPowers.spawn(
contentBrowser,
[],
() => Services.appinfo.processID
);
const { parentThread, contentThread } = await stopProfilerNowAndGetThreads(
contentPid
);
const parentNetworkMarkers = getInflatedNetworkMarkers(parentThread);
const contentNetworkMarkers = getInflatedNetworkMarkers(contentThread);
info(JSON.stringify(parentNetworkMarkers, null, 2));
info(JSON.stringify(contentNetworkMarkers, null, 2));
Assert.equal(
parentNetworkMarkers.length,
8,
`We should get 4 pairs of network markers in the parent thread.`
// 1 - The main page
// 2 - The SVG
// 3 - The redirected request for the second SVG request.
// 4 - The SVG, again
);
/* In this second test, the top level navigation request isn't redirected.
* Contrary to Bug 1692879 we get all network markers for redirected
* resources. */
Assert.equal(
contentNetworkMarkers.length,
8,
`We should get 4 pairs of network markers in the content thread.`
);
// The same resource firefox-logo-nightly.svg is requested twice, but the
// second time it is redirected.
// We're not interested in the main page, as we test that in other files.
// In this page we're only interested in the marker for requested resources.
const parentPairs = getPairsOfNetworkMarkers(parentNetworkMarkers);
const contentPairs = getPairsOfNetworkMarkers(contentNetworkMarkers);
// First, make sure we properly matched all start with stop markers. This
// means that both arrays should contain only arrays of 2 elements.
parentPairs.forEach(pair =>
Assert.equal(
pair.length,
2,
`For the URL ${pair[0].data.URI} we should get 2 markers in the parent process.`
)
);
contentPairs.forEach(pair =>
Assert.equal(
pair.length,
2,
`For the URL ${pair[0].data.URI} we should get 2 markers in the content process.`
)
);
const parentFirstStopMarker = parentPairs[1][1];
const parentRedirectMarker = parentPairs[2][1];
const parentSecondStopMarker = parentPairs[3][1];
const contentFirstStopMarker = contentPairs[1][1];
const contentRedirectMarker = contentPairs[2][1];
const contentSecondStopMarker = contentPairs[3][1];
const expectedCommonProperties = {
requestMethod: "GET",
startTime: Expect.number(),
endTime: Expect.number(),
id: Expect.number(),
pri: Expect.number(),
};
const expectedPropertiesForFirstMarker = {
name: Expect.stringMatches(/Load \d+:.*firefox-logo-nightly\.svg/),
data: Expect.objectContains({
...expectedCommonProperties,
status: "STATUS_STOP",
URI: Expect.stringContains("/firefox-logo-nightly.svg"),
contentType: "image/svg+xml",
domainLookupStart: Expect.number(),
domainLookupEnd: Expect.number(),
connectStart: Expect.number(),
tcpConnectEnd: Expect.number(),
connectEnd: Expect.number(),
requestStart: Expect.number(),
responseStart: Expect.number(),
responseEnd: Expect.number(),
}),
};
const expectedPropertiesForRedirectMarker = {
name: Expect.stringMatches(
/Load \d+:.*redirect.sjs\?firefox-logo-nightly\.svg/
),
data: Expect.objectContains({
...expectedCommonProperties,
status: "STATUS_REDIRECT",
URI: Expect.stringContains("/redirect.sjs?firefox-logo-nightly.svg"),
RedirectURI: Expect.stringContains("/firefox-logo-nightly.svg"),
contentType: null,
domainLookupStart: Expect.number(),
domainLookupEnd: Expect.number(),
connectStart: Expect.number(),
tcpConnectEnd: Expect.number(),
connectEnd: Expect.number(),
requestStart: Expect.number(),
responseStart: Expect.number(),
responseEnd: Expect.number(),
}),
};
const expectedPropertiesForSecondMarker = {
name: Expect.stringMatches(/Load \d+:.*firefox-logo-nightly\.svg/),
data: Expect.objectContains({
...expectedCommonProperties,
status: "STATUS_STOP",
URI: Expect.stringContains("/firefox-logo-nightly.svg"),
contentType: "image/svg+xml",
}),
};
Assert.objectContains(
parentFirstStopMarker,
expectedPropertiesForFirstMarker
);
Assert.objectContains(
contentFirstStopMarker,
expectedPropertiesForFirstMarker
);
Assert.objectContains(
parentRedirectMarker,
expectedPropertiesForRedirectMarker
);
Assert.objectContains(
contentRedirectMarker,
expectedPropertiesForRedirectMarker
);
Assert.objectContains(
parentSecondStopMarker,
expectedPropertiesForSecondMarker
);
Assert.objectContains(
contentSecondStopMarker,
expectedPropertiesForSecondMarker
);
// The cache information is missing from the content marker, it's only part
// of the parent marker. See Bug 1544821.
// Also, because the request races with the cache, these 2 values are valid:
// "Missed" when the cache answered before we get a result from the network.
// "Unresolved" when we got a response from the network before the cache subsystem.
Assert.objectContains(parentFirstStopMarker.data, {
cache: Expect.stringMatches(/^(Missed|Unresolved)$/),
});
Assert.objectContains(parentRedirectMarker.data, {
cache: Expect.stringMatches(/^(Missed|Unresolved)$/),
});
// The second request to the SVG file is already in the cache, though.
Assert.objectContains(parentSecondStopMarker.data, {
cache: "Hit",
});
});
});

Просмотреть файл

@ -0,0 +1,84 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Test that we emit network markers accordingly
*/
add_task(async function test_network_markers() {
if (!AppConstants.MOZ_GECKO_PROFILER) {
return;
}
Assert.ok(
!Services.profiler.IsActive(),
"The profiler is not currently active"
);
startProfilerForMarkerTests();
const url = BASE_URL + "simple.html?cacheBust=" + Math.random();
await BrowserTestUtils.withNewTab(url, async contentBrowser => {
const contentPid = await SpecialPowers.spawn(
contentBrowser,
[],
() => Services.appinfo.processID
);
const { parentThread, contentThread } = await stopProfilerNowAndGetThreads(
contentPid
);
const parentNetworkMarkers = getInflatedNetworkMarkers(parentThread);
const contentNetworkMarkers = getInflatedNetworkMarkers(contentThread);
info(JSON.stringify(parentNetworkMarkers, null, 2));
info(JSON.stringify(contentNetworkMarkers, null, 2));
Assert.equal(
parentNetworkMarkers.length,
2,
`We should get a pair of network markers in the parent thread.`
);
Assert.equal(
contentNetworkMarkers.length,
2,
`We should get a pair of network markers in the content thread.`
);
const parentStopMarker = parentNetworkMarkers[1];
const contentStopMarker = contentNetworkMarkers[1];
const expectedProperties = {
name: Expect.stringMatches(`Load \\d+:.*${escapeStringRegexp(url)}`),
data: Expect.objectContains({
status: "STATUS_STOP",
URI: url,
requestMethod: "GET",
contentType: "text/html",
startTime: Expect.number(),
endTime: Expect.number(),
domainLookupStart: Expect.number(),
domainLookupEnd: Expect.number(),
connectStart: Expect.number(),
tcpConnectEnd: Expect.number(),
connectEnd: Expect.number(),
requestStart: Expect.number(),
responseStart: Expect.number(),
responseEnd: Expect.number(),
id: Expect.number(),
count: Expect.number(),
pri: Expect.number(),
}),
};
Assert.objectContains(parentStopMarker, expectedProperties);
// The cache information is missing from the content marker, it's only part
// of the parent marker. See Bug 1544821.
Assert.objectContains(parentStopMarker.data, {
// Because the request races with the cache, these 2 values are valid:
// "Missed" when the cache answered before we get a result from the network.
// "Unresolved" when we got a response from the network before the cache subsystem.
cache: Expect.stringMatches(/^(Missed|Unresolved)$/),
});
Assert.objectContains(contentStopMarker, expectedProperties);
});
});

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

После

Ширина:  |  Высота:  |  Размер: 19 KiB

Просмотреть файл

@ -21,8 +21,8 @@ registerCleanupFunction(() => {
});
/**
* This is a helper function that will stop the profiler of the browser running
* with PID contentPid.
* This is a helper function that will stop the profiler and returns the main
* threads for the parent process and the content process with PID contentPid.
* This happens immediately, without waiting for any sampling to happen or
* finish. Use stopProfilerAndGetThreads (without "Now") below instead to wait
* for samples before stopping.
@ -31,6 +31,7 @@ registerCleanupFunction(() => {
* @returns {Promise}
*/
async function stopProfilerNowAndGetThreads(contentPid) {
Services.profiler.Pause();
const profile = await Services.profiler.getProfileDataAsync();
Services.profiler.StopProfiler();
@ -55,8 +56,8 @@ async function stopProfilerNowAndGetThreads(contentPid) {
}
/**
* This is a helper function that will stop the profiler of the browser running
* with PID contentPid.
* This is a helper function that will stop the profiler and returns the main
* threads for the parent process and the content process with PID contentPid.
* As opposed to stopProfilerNowAndGetThreads (with "Now") above, the profiler
* in that PID will not stop until there is at least one periodic sample taken.
*

Просмотреть файл

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
</head>
<body>
Testing
<img src='firefox-logo-nightly.svg' width="24"/>
<img src='redirect.sjs?firefox-logo-nightly.svg' width="24"/>
</body>
</html>

Просмотреть файл

@ -0,0 +1,4 @@
function handleRequest(request, response) {
response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
response.setHeader("Location", decodeURIComponent(request.queryString), false);
}

Просмотреть файл

@ -127,6 +127,48 @@ function getInflatedMarkerData(thread) {
});
}
/**
* Applies the marker schema to create individual objects for each marker, then
* keeps only the network markers that match the profiler tests.
*
* @param {Object} thread The thread from a profile.
* @return {InflatedMarker[]} The filtered network markers.
*/
function getInflatedNetworkMarkers(thread) {
const markers = getInflatedMarkerData(thread);
return markers.filter(
m =>
m.data &&
m.data.type === "Network" &&
// We filter out network markers that aren't related to the test, to
// avoid intermittents.
m.data.URI.includes("/browser/tools/profiler/")
);
}
/**
* From a list of network markers, this returns pairs of start/stop markers.
* If a stop marker can't be found for a start marker, this will return an array
* of only 1 element.
*
* @param {InflatedMarker[]} networkMarkers Network markers
* @return {InflatedMarker[][]} Pairs of network markers
*/
function getPairsOfNetworkMarkers(allNetworkMarkers) {
// For each 'start' marker we want to find the 'stop' or 'redirect' marker
// with the same id.
// Note: the algorithm we use here to match markers is very crude and would
// be too slow for the real product, but because it is very simple it's good
// for a test. Also the logic will be compatible with future marker sets.
// Because we only have a few markers the performance is good enough in this
// case.
return allNetworkMarkers
.filter(({ data }) => data.status === "STATUS_START")
.map(startMarker =>
allNetworkMarkers.filter(({ data }) => data.id === startMarker.data.id)
);
}
/**
* It can be helpful to force the profiler to collect a JavaScript sample. This
* function spins on a while loop until at least one more sample is collected.
@ -221,6 +263,27 @@ function getSchema(profile, name) {
throw new Error(`Could not find a schema for "${name}".`);
}
/**
* This escapes all characters that have a special meaning in RegExps.
* This was stolen from https://github.com/sindresorhus/escape-string-regexp and
* so it is licence MIT and:
* Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com).
* See the full license in https://raw.githubusercontent.com/sindresorhus/escape-string-regexp/main/license.
* @param {string} string The string to be escaped
* @returns {string} The result
*/
function escapeStringRegexp(string) {
if (typeof string !== "string") {
throw new TypeError("Expected a string");
}
// Escape characters with special meaning either inside or outside character
// sets. Use a simple backslash escape when its always valid, and a `\xnn`
// escape when the simpler form would be disallowed by Unicode patterns
// stricter grammar.
return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
}
/** ------ Assertions helper ------ */
/**
* This assert helper function makes it easy to check a lot of properties in an