Skip to content

Commit

Permalink
Python test runner
Browse files Browse the repository at this point in the history
  • Loading branch information
x0k committed Jun 3, 2024
1 parent 1e6a3df commit 30735b8
Show file tree
Hide file tree
Showing 19 changed files with 206 additions and 16 deletions.
26 changes: 25 additions & 1 deletion astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
import { defineConfig } from "astro/config";
import { viteStaticCopy } from "vite-plugin-static-copy";
import { createLogger } from "vite";

import mdx from "@astrojs/mdx";
import svelte from "@astrojs/svelte";
import tailwind from "@astrojs/tailwind";
import icon from "astro-icon";

const logger = createLogger();
const loggerWarn = logger.warn;

logger.warn = (msg, options) => {
// Ignore warnings from pyodide distribution
if (msg.includes("/public/pyodide/")) return;
loggerWarn(msg, options);
};

// https://astro.build/config
export default defineConfig({
site: "https://x0k.github.io",
base: "/ppp",
integrations: [tailwind(), icon(), mdx(), svelte()],
vite: {
customLogger: logger,
optimizeDeps: {
exclude: ["pyodide"],
},
assetsInclude: ["**/*.wasm"],
build: {
rollupOptions: {
Expand All @@ -20,7 +35,16 @@ export default defineConfig({
},
},
},
plugins: [],
plugins: [
viteStaticCopy({
targets: [
{
src: "node_modules/pyodide/*.*",
dest: "./assets/pyodide",
},
],
}),
],
},
markdown: {
shikiConfig: {
Expand Down
Binary file modified bun.lockb
Binary file not shown.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"astro-icon": "^1.1.0",
"fast-deep-equal": "^3.1.3",
"monaco-editor": "^0.49.0",
"pyodide": "^0.26.0",
"tailwindcss": "^3.4.3",
"typescript": "^5.4.5"
},
Expand All @@ -29,6 +30,7 @@
"@types/color": "^3.0.6",
"color": "^4.2.3",
"daisyui": "^4.11.1",
"svelte": "5.0.0-next.133"
"svelte": "5.0.0-next.133",
"vite-plugin-static-copy": "^1.0.5"
}
}
2 changes: 2 additions & 0 deletions src/adapters/monaco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ import { Language } from "@/lib/testing";
export const MONACO_LANGUAGE_ID: Record<Language, string> = {
[Language.PHP]: "php",
[Language.TypeScript]: "typescript",
[Language.JavaScript]: "javascript",
[Language.Python]: "python",
};
11 changes: 10 additions & 1 deletion src/content/design-patterns/factory/editor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
import { testCases, CaseType, type Inputs, type Outputs, CASE_TYPES } from './test-cases'
import { testRunnerFactories as phpTestRunnerFactories } from './php/test-runners'
import { tsTestRunnerFactories, jsTestRunnerFactories } from './js/test-runners'
import { testRunnerFactories as pyTestRunnerFactories } from './python/test-runners'
const INITIAL_VALUES: Record<Language, Promise<string>> = {
[Language.PHP]: import('./php/code.php?raw').then(m => m.default),
[Language.TypeScript]: import('./js/code.ts?raw').then(m => m.default),
[Language.JavaScript]: import('./js/code.js?raw').then(m => m.default),
[Language.Python]: import('./python/code.py?raw').then(m => m.default),
}
const CASES_FACTORIES: Record<Language, () => TestCasesStates<CaseType, Inputs, Outputs>> = {
Expand All @@ -36,13 +38,20 @@
testCase: testCases[id],
testRunner: jsTestRunnerFactories[id],
})) as TestCasesStates<CaseType, Inputs, Outputs>,
[Language.Python]: () => CASE_TYPES.map(id => ({
id,
isRunning: false,
lastTestId: -1,
testCase: testCases[id],
testRunner: pyTestRunnerFactories[id],
})) as TestCasesStates<CaseType, Inputs, Outputs>,
}
const defaultLanguage = Language.PHP;
</script>

<Editor
{defaultLanguage}
languages={[Language.PHP, Language.TypeScript, Language.JavaScript]}
languages={Object.keys(CASES_FACTORIES) as Language[]}
onLanguageChange={async (lang, model) => {
model.setValue(await INITIAL_VALUES[lang])
}}
Expand Down
17 changes: 10 additions & 7 deletions src/content/design-patterns/factory/js/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ const enum PaymentSystemType {
CatBank = "cat-bank",
}







export function case1(
type: PaymentSystemType,
base: number,
Expand All @@ -12,20 +18,17 @@ export function case1(
throw new Error("Not implemented");
}

const enum PaymentSystemActionType {
Payment = "payment",
Payout = "payout",
}









const enum PaymentSystemActionType {
Payment = "payment",
Payout = "payout",
}

export function case2(
type: PaymentSystemType,
base: number,
Expand Down
38 changes: 38 additions & 0 deletions src/content/design-patterns/factory/python/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from enum import Enum

class PaymentSystemType(Enum):
PAYPAL = "paypal"
WEBMONEY = "webmoney"
CAT_BANK = "cat-bank"







def case1(
type: PaymentSystemType,
base: int,
amount: int
) -> int:
raise NotImplementedError

class PaymentSystemActionType(Enum):
PAYMENT = "payment"
PAYOUT = "payout"








def case2(
type: PaymentSystemType,
base: int,
action: PaymentSystemActionType,
amount: int
) -> int:
raise NotImplementedError
40 changes: 40 additions & 0 deletions src/content/design-patterns/factory/python/test-runners.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { TestRunnerFactory } from "@/lib/testing";
import { PyTestRunner, pyRuntimeFactory } from "@/lib/testing/python";

import {
type SimpleTestInput,
type ComplexTestInput,
CaseType,
type Inputs,
type Outputs,
} from "../test-cases";

class SimpleTestRunner extends PyTestRunner<SimpleTestInput, number> {
protected caseExecutionCode({
paymentSystem,
base,
amount,
}: SimpleTestInput): string {
return `case1("${paymentSystem}", ${base}, ${amount})`;
}
}

class ComplexTestRunner extends PyTestRunner<ComplexTestInput, number> {
protected caseExecutionCode({
paymentSystem,
base,
action,
amount,
}: ComplexTestInput): string {
return `case2("${paymentSystem}", ${base}, "${action}", ${amount})`;
}
}

export const testRunnerFactories: {
[k in CaseType]: TestRunnerFactory<Inputs[k], Outputs[k]>;
} = {
[CaseType.Simple]: async (code: string) =>
new SimpleTestRunner(await pyRuntimeFactory(), code),
[CaseType.Complex]: async (code: string) =>
new ComplexTestRunner(await pyRuntimeFactory(), code),
};
1 change: 1 addition & 0 deletions src/lib/testing/js/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./model";
export * from "./js-test-runner";
export * from "./ts-test-runner";
2 changes: 1 addition & 1 deletion src/lib/testing/js/js-test-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export abstract class JsTestRunner<M, I, O> implements TestRunner<I, O> {

async run(input: I): Promise<O> {
const transformedCode = this.transformCode(this.code);
const m = await import(transformedCode);
const m = await import(/* @vite-ignore */ transformedCode);
return this.executeTest(m, input);
}

Expand Down
1 change: 1 addition & 0 deletions src/lib/testing/js/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { version as tsVersion } from "typescript";
10 changes: 7 additions & 3 deletions src/lib/testing/languages.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { version } from "typescript";
import { tsVersion } from './js'
import { version as phpVersion } from './php'
import { version as pyVersion } from './python'

export enum Language {
PHP = "php",
Python = "python",
TypeScript = "typescript",
JavaScript = "javascript",
}

export const LANGUAGE_TITLE: Record<Language, string> = {
[Language.PHP]: "PHP 8.3",
[Language.TypeScript]: `TypeScript ${version}`,
[Language.PHP]: `PHP ${phpVersion}`,
[Language.TypeScript]: `TypeScript ${tsVersion}`,
[Language.Python]: `Python ${pyVersion}`,
[Language.JavaScript]: "JavaScript",
};
1 change: 1 addition & 0 deletions src/lib/testing/php/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./model";
export * from "./php-test-runner";
export * from "./fail-safe-php";
export * from "./php-runtime-factory";
1 change: 1 addition & 0 deletions src/lib/testing/php/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const version = "8.3";
6 changes: 4 additions & 2 deletions src/lib/testing/php/php-runtime-factory.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { WebPHP } from '@php-wasm/web';
import { WebPHP } from "@php-wasm/web";

export const phpRuntimeFactory = () => WebPHP.loadRuntime('8.3')
import { version } from "./model";

export const phpRuntimeFactory = () => WebPHP.loadRuntime(version);
3 changes: 3 additions & 0 deletions src/lib/testing/python/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './model'
export * from './py-test-runner'
export * from './py-runtime-factory'
5 changes: 5 additions & 0 deletions src/lib/testing/python/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// This is only package version, maybe we need it in the future
// export { version } from "pyodide";

// According to https://github.com/pyodide/pyodide/pull/4435
export const version = "3.12.1";
8 changes: 8 additions & 0 deletions src/lib/testing/python/py-runtime-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { loadPyodide } from "pyodide";

export const pyRuntimeFactory = () =>
loadPyodide({
indexURL: import.meta.env.DEV
? undefined
: `${import.meta.env.BASE_URL}/assets/pyodide`,
});
46 changes: 46 additions & 0 deletions src/lib/testing/python/py-test-runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { loadPyodide } from "pyodide";
import type { PyProxy } from "pyodide/ffi";

import type { TestRunner } from "../testing";

function isPyProxy(obj: any): obj is PyProxy {
return typeof obj === "object" && obj;
}

export abstract class PyTestRunner<I, O> implements TestRunner<I, O> {
private proxies: PyProxy[] = [];

constructor(
protected readonly python: Awaited<ReturnType<typeof loadPyodide>>,
protected readonly code: string
) {}

protected caseExecutionCode(input: I): string {
throw new Error("Not implemented");
}

protected transformCode(input: I): string {
return `${this.code}\n${this.caseExecutionCode(input)}`;
}

protected transformResult(result: any): O {
if (isPyProxy(result)) {
this.proxies.push(result);
return result.toJs({ pyproxies: this.proxies });
}
return result;
}

async run(inputIn: I): Promise<O> {
return this.transformResult(
await this.python.runPythonAsync(this.transformCode(inputIn))
);
}

[Symbol.dispose](): void {
for (const p of this.proxies) {
p.destroy();
}
this.proxies.length = 0;
}
}

0 comments on commit 30735b8

Please sign in to comment.