Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/src/components/file-route-sync.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {

import { useShare } from "@/hooks/use-share";

const DEFAULT_FILE = "/tmp/examples/01-http-client/main.bal";
const DEFAULT_FILE = "/tmp/examples/01-hello-world/main.bal";
const DEFAULT_SPLAT = DEFAULT_FILE.replace(/^\/+/, "");

function normalizeSplat(splat: string | undefined): string | null {
Expand Down
1 change: 1 addition & 0 deletions e2e/fixtures/examples/01-hello-world.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello, World!
3 changes: 0 additions & 3 deletions e2e/fixtures/examples/01-http-client.txt

This file was deleted.

6 changes: 6 additions & 0 deletions e2e/fixtures/examples/02-http-client.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Top <number> popular packages for query 'aws':
<package> (<number> pulls)
<package> (<number> pulls)
<package> (<number> pulls)
<package> (<number> pulls)
<package> (<number> pulls)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
66 changes: 60 additions & 6 deletions e2e/helpers/example-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,40 @@ function getExampleOutputPath(manifestPath: string) {
);
}

function normalizeExampleOutput(output: string) {
return output.trimEnd().replace(/^time=\S+/gm, "time=<timestamp>");
// Examples that talk to live services, or print timestamps, produce values we
// cannot pin down: which packages rank highest, their versions and their
// download counts all change upstream, and log timestamps change every run.
// Those fixtures use these placeholders instead of literal values, and
// everything around them still has to match exactly.
const outputPlaceholders: Record<string, string> = {
"<number>": "\\d+",
"<package>": "[\\w.]+\\/[\\w.]+:\\d+\\.\\d+\\.\\d+[\\w.+-]*",
"<timestamp>": "\\S+",
};

function escapeRegExp(value: string) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

// Timestamps differ on every run, so a freshly written fixture must store the
// <timestamp> placeholder rather than today's literal value.
function canonicalizeExampleOutput(output: string) {
return output.replace(/^time=\S+/gm, "time=<timestamp>");
}

function toExampleOutputPattern(expectedOutput: string) {
const placeholder = /<number>|<package>|<timestamp>/g;
let pattern = "";
let lastIndex = 0;

for (const match of expectedOutput.matchAll(placeholder)) {
pattern += escapeRegExp(expectedOutput.slice(lastIndex, match.index));
pattern += outputPlaceholders[match[0]];
lastIndex = match.index + match[0].length;
}
pattern += escapeRegExp(expectedOutput.slice(lastIndex));

return new RegExp(`^${pattern}$`);
}

async function loadExampleOutput(manifestPath: string) {
Expand All @@ -29,17 +61,39 @@ export async function assertOrUpdateExampleOutput(
manifestPath: string,
) {
if (process.env.UPDATE_EXAMPLE_OUTPUTS === "1") {
const output = normalizeExampleOutput(await outputPane.innerText());
const output = (await outputPane.innerText()).trimEnd();
const outputPath = getExampleOutputPath(manifestPath);

// A fixture whose placeholders still cover the output is already correct,
// so keep it rather than baking today's values back in.
const existingOutput = await loadExampleOutput(manifestPath).catch(
() => null,
);
if (
existingOutput !== null &&
toExampleOutputPattern(existingOutput).test(output)
) {
return;
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
// <package>/<number> stand for values (rankings, pull counts) we cannot
// canonicalize back out of raw output, so overwriting here would silently
// re-bake today's literal values and break again on the very next run.
if (existingOutput !== null && /<number>|<package>/.test(existingOutput)) {
throw new Error(
`${manifestPath} fixture uses <package>/<number> placeholders and no longer matches the live output. Hand-edit ${outputPath} instead of regenerating it.`,
);
}

await mkdir(dirname(outputPath), { recursive: true });
await writeFile(outputPath, `${output}\n`);
await writeFile(outputPath, `${canonicalizeExampleOutput(output)}\n`);
Comment thread
TharmiganK marked this conversation as resolved.
return;
}

const expectedOutput = await loadExampleOutput(manifestPath);
await expect
.poll(async () => normalizeExampleOutput(await outputPane.innerText()), {
.poll(async () => (await outputPane.innerText()).trimEnd(), {
timeout: 10_000,
})
.toBe(expectedOutput);
.toMatch(toExampleOutputPattern(expectedOutput));
}
2 changes: 1 addition & 1 deletion e2e/tests/examples.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type ServiceExampleRequest = HttpRequest & {
};

const serviceExampleRequests: Record<string, ServiceExampleRequest[]> = {
"/tmp/examples/02-http-service/Ballerina.toml": [
"/tmp/examples/03-http-service/Ballerina.toml": [
{
method: "GET",
path: "/albums",
Expand Down
4 changes: 4 additions & 0 deletions examples/01-hello-world/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[package]
org = "playground"
name = "hello_world"
version = "0.1.0"
5 changes: 5 additions & 0 deletions examples/01-hello-world/main.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import ballerina/io;

public function main() {
io:println("Hello, World!");
}
39 changes: 0 additions & 39 deletions examples/01-http-client/main.bal

This file was deleted.

31 changes: 31 additions & 0 deletions examples/02-http-client/main.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import ballerina/http;
import ballerina/io;

type Package record {
string organization;
string name;
string version;
int pullCount;
};

type SearchResult record {
Package[] packages;
};

public function main() returns error? {
string query = "aws";
http:Client registry = check new ("https://api.central.ballerina.io/2.0/registry");
Comment thread
TharmiganK marked this conversation as resolved.
http:Response res = check registry->get(string `/packages?q=${query}&limit=50`);
SearchResult result = check (check res.getJsonPayload()).fromJsonWithType();
Comment thread
TharmiganK marked this conversation as resolved.

string[] popular = from Package pkg in result.packages
where pkg.pullCount > 100
Comment thread
TharmiganK marked this conversation as resolved.
order by pkg.pullCount descending
limit 5
select string `${pkg.organization}/${pkg.name}:${pkg.version} (${pkg.pullCount} pulls)`;

io:println(string `Top ${popular.length()} popular packages for query '${query}':`);
foreach string pkg in popular {
io:println(pkg);
}
}
File renamed without changes.
Loading