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
186 changes: 64 additions & 122 deletions os-checks/pages/testcases.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@
import type { DataTableSortMeta } from 'primevue/datatable';
import { FilterMatchMode } from '@primevue/core/api';
import type { PkgInfo } from '~/shared/info';
import { cloneDeep, uniq } from 'es-toolkit/compat';
import type { TestResult, Selection, Options } from '~/shared/testcases';
import { defaultOptions, applySelection, testcasesToOptions, summariesToTestResult } from '~/shared/testcases';

useHead({ title: 'Test Cases' });

Expand Down Expand Up @@ -126,74 +127,17 @@ watch(selectedTest, sel => dialogShow.value = sel ? true : false);
githubFetch<PkgInfo[]>({
path: "plugin/cargo/info/summaries.json"
}).then(val => {
testcases.value = summariesToTestResult(val);
testcasesFiltered.value = cloneDeep(testcases.value);
const tc = summariesToTestResult(val);
testcases.value = tc;
testcasesFiltered.value = applySelection(tc, selected);
});

type TestResult = {
idx: number,
user: string,
repo: string,
pkg: string,
bin: string,
kind: string,
name: string,
test_pass: string,
test_duration_ms: number | null,
test_error: string | null,
miri_pass: string,
miri_output: string | null,
miri_timeout: string,
};

function summariesToTestResult(pkg_info: PkgInfo[]): TestResult[] {
let result: TestResult[] = [];
let idx = 0;

for (const info of pkg_info) {
for (const [pkg, value] of Object.entries(info.pkgs)) {
for (const test of value.testcases?.tests || []) {
for (const testcase of test.testcases) {
result.push({
idx: idx++,
user: info.user,
repo: info.repo,
pkg,
bin: test.binary_name,
kind: test.kind,
name: testcase.name,
test_pass: testcase.status === "ok" ? "✅" : (testcase.status === "failed" ? "❌" : ""),
test_duration_ms: testcase.duration_ms,
test_error: testcase.error,
miri_pass: testcase.miri_pass ? "✅" : "❌",
miri_output: testcase.miri_output,
miri_timeout: testcase.miri_timeout ? "💥" : "",
});
}
}
}
}

return result;
}

function sortsChanged(meta?: DataTableSortMeta[] | null) {
if (meta) {
selected.sorts = meta;
}
}

const selected = reactive<{
user: string | null,
repo: string | null,
pkg: string | null,
kind: string | null,
test_pass: string | null,
miri_pass: string | null,
miri_timeout: string | null,
text: any,
sorts: DataTableSortMeta[],
}>({
const selected = reactive<Selection>({
user: null,
repo: null,
pkg: null,
Expand All @@ -205,73 +149,71 @@ const selected = reactive<{
sorts: [],
});

type Options = {
user: string[],
repo: string[],
pkg: string[],
kind: string[],
test_pass: string[],
miri_pass: string[],
miri_timeout: string[],
};
function defaultOptions(): Options {
return {
user: [],
repo: [],
pkg: [],
kind: [],
test_pass: [],
miri_pass: [],
miri_timeout: [],
};
}
const options = reactive<{ val: Options }>({ val: defaultOptions() });

// init options
watch(testcases, tc => options.val = testcasesToOptions(tc));

// update table when filter selection changes
watch(
selected,
({ user, repo, pkg, kind, test_pass, miri_pass, miri_timeout }) => {
// for simplicity, the data of testcases are supposed to remain unchanged
const chosen_testcases = testcases.value.filter(test => {
let chosen = true;
if (user) chosen &&= test.user === user;
if (repo) chosen &&= test.repo === repo;
if (pkg) chosen &&= test.pkg === pkg;
if (kind) chosen &&= test.kind === repo;
if (test_pass) chosen &&= test.test_pass === test_pass;
if (miri_pass) chosen &&= test.miri_pass === miri_pass;
if (miri_timeout || miri_timeout === "") chosen &&= test.miri_timeout === miri_timeout;
return chosen;
});
testcasesFiltered.value = chosen_testcases.map((tc, idx) => {
tc.idx = idx;
return tc;
watch(selected, sel => testcasesFiltered.value = applySelection(testcases.value, sel));

// ******************* route query *******************
const route = useRoute();
function updateFilter(query: {
user?: string,
repo?: string,
pkg?: string,
kind?: string,
test_pass?: string,
miri_pass?: string,
miri_timeout?: string,
text?: string,
sorts?: string,
}) {
const { user, repo, pkg, kind, test_pass, miri_pass, miri_timeout, text, sorts } = query;

// only support single value for each param
// FIXME: empty string will not handled
if (user) selected.user = decodeURIComponent(user);
if (repo) selected.repo = decodeURIComponent(repo);
if (pkg) selected.pkg = decodeURIComponent(pkg);
if (kind) selected.kind = decodeURIComponent(kind);
if (test_pass) selected.test_pass = decodeURIComponent(test_pass);
if (miri_pass) selected.miri_pass = decodeURIComponent(miri_pass);
if (miri_timeout) selected.miri_timeout = decodeURIComponent(miri_timeout);
if (text) selected.text.global.value = decodeURIComponent(text);

if (sorts) {
const args = decodeURIComponent(sorts).split(",");
//@ts-ignore
selected.sorts = args.map(arg => {
let [field, order] = arg.split("=");
return { field, order: parseInt(order) };
});
// options.val = testcasesToOptions(chosen_testcases);
});

function testcasesToOptions(tc: TestResult[]): Options {
let opts = defaultOptions();
for (const test of tc) {
opts.user.push(test.user);
opts.repo.push(test.repo);
opts.pkg.push(test.pkg);
opts.kind.push(test.kind);
opts.test_pass.push(test.test_pass);
opts.miri_pass.push(test.miri_pass);
opts.miri_timeout.push(test.miri_timeout);
}

opts.user = uniq(opts.user).sort();
opts.repo = uniq(opts.repo).sort();
opts.pkg = uniq(opts.pkg).sort();
opts.kind = uniq(opts.kind).sort();
opts.test_pass = uniq(opts.test_pass).sort();
opts.miri_pass = uniq(opts.miri_pass).sort();
opts.miri_timeout = uniq(opts.miri_timeout).sort();
return opts;
}
updateFilter(route.query);

const router = useRouter();
watch(selected, sel => {
const { user, repo, pkg, kind, test_pass, miri_pass, miri_timeout, text, sorts } = sel;

let query: any = {};

if (user) query.user = encodeURIComponent(user);
if (repo) query.repo = encodeURIComponent(repo);
if (pkg) query.pkg = encodeURIComponent(pkg);
if (kind) query.kind = encodeURIComponent(kind);
if (test_pass) query.test_pass = encodeURIComponent(test_pass);
if (miri_pass) query.miri_pass = encodeURIComponent(miri_pass);
if (miri_timeout) query.miri_timeout = encodeURIComponent(miri_timeout);
if (text.global.value) query.text = encodeURIComponent(text.global.value);

if (sorts.length !== 0) {
const args = sorts.map(({ field, order }) => order ? `${field}=${order}` : null);
query.sorts = encodeURIComponent(args.filter(x => x).join(","));
}

router.push({ path: route.path, query });
});
</script>
129 changes: 129 additions & 0 deletions os-checks/shared/testcases.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import type { DataTableSortMeta } from 'primevue/datatable';
import { uniq } from 'es-toolkit/compat';
import type { PkgInfo } from './info';

export type TestResult = {
idx: number,
user: string,
repo: string,
pkg: string,
bin: string,
kind: string,
name: string,
test_pass: string,
test_duration_ms: number | null,
test_error: string | null,
miri_pass: string,
miri_output: string | null,
miri_timeout: string,
};

export type Selection = {
user: string | null,
repo: string | null,
pkg: string | null,
kind: string | null,
test_pass: string | null,
miri_pass: string | null,
miri_timeout: string | null,
text: any,
sorts: DataTableSortMeta[],
};

/** Called when testcases are fetched or selection is updated */
export function applySelection(
testcases: TestResult[],
{ user, repo, pkg, kind, test_pass, miri_pass, miri_timeout }: Selection
): TestResult[] {
// for simplicity, the data of testcases are supposed to remain unchanged
const chosen_testcases = testcases.filter(test => {
let chosen = true;
if (user) chosen &&= test.user === user;
if (repo) chosen &&= test.repo === repo;
if (pkg) chosen &&= test.pkg === pkg;
if (kind) chosen &&= test.kind === repo;
if (test_pass) chosen &&= test.test_pass === test_pass;
if (miri_pass) chosen &&= test.miri_pass === miri_pass;
if (miri_timeout || miri_timeout === "") chosen &&= test.miri_timeout === miri_timeout;
return chosen;
});
return chosen_testcases.map((tc, idx) => {
tc.idx = idx;
return tc;
});
}

export type Options = {
user: string[],
repo: string[],
pkg: string[],
kind: string[],
test_pass: string[],
miri_pass: string[],
miri_timeout: string[],
};

export function defaultOptions(): Options {
return {
user: [],
repo: [],
pkg: [],
kind: [],
test_pass: [],
miri_pass: [],
miri_timeout: [],
};
}

export function testcasesToOptions(tc: TestResult[]): Options {
let opts = defaultOptions();
for (const test of tc) {
opts.user.push(test.user);
opts.repo.push(test.repo);
opts.pkg.push(test.pkg);
opts.kind.push(test.kind);
opts.test_pass.push(test.test_pass);
opts.miri_pass.push(test.miri_pass);
opts.miri_timeout.push(test.miri_timeout);
}

opts.user = uniq(opts.user).sort();
opts.repo = uniq(opts.repo).sort();
opts.pkg = uniq(opts.pkg).sort();
opts.kind = uniq(opts.kind).sort();
opts.test_pass = uniq(opts.test_pass).sort();
opts.miri_pass = uniq(opts.miri_pass).sort();
opts.miri_timeout = uniq(opts.miri_timeout).sort();
return opts;
}

export function summariesToTestResult(pkg_info: PkgInfo[]): TestResult[] {
let result: TestResult[] = [];
let idx = 0;

for (const info of pkg_info) {
for (const [pkg, value] of Object.entries(info.pkgs)) {
for (const test of value.testcases?.tests || []) {
for (const testcase of test.testcases) {
result.push({
idx: idx++,
user: info.user,
repo: info.repo,
pkg,
bin: test.binary_name,
kind: test.kind,
name: testcase.name,
test_pass: testcase.status === "ok" ? "✅" : (testcase.status === "failed" ? "❌" : ""),
test_duration_ms: testcase.duration_ms,
test_error: testcase.error,
miri_pass: testcase.miri_pass ? "✅" : "❌",
miri_output: testcase.miri_output,
miri_timeout: testcase.miri_timeout ? "💥" : "",
});
}
}
}
}

return result;
}