Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JS and Wasm for 2.0.x version #197

Merged
merged 3 commits into from
Dec 21, 2023
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 package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"@babel/plugin-transform-runtime": "~7.22.4",
"@babel/preset-env": "~7.22.4",
"@babel/runtime-corejs2": "~7.22.2",
"@playwright/test": "^1.37.1",
"@playwright/test": "^1.40.1",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's required for Wasm test in chrome with enabled WasmGC.

"@typescript-eslint/eslint-plugin": "^6.4.0",
"@typescript-eslint/parser": "^6.4.0",
"babel-loader": "^9.1.3",
Expand Down
2 changes: 1 addition & 1 deletion src/executable-code/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export default class ExecutableCode {
const args = targetNode.hasAttribute(ATTRIBUTES.ARGUMENTS) ? targetNode.getAttribute(ATTRIBUTES.ARGUMENTS) : "";
const hiddenDependencies = this.getHiddenDependencies(targetNode);
const outputHeight = targetNode.getAttribute(ATTRIBUTES.OUTPUT_HEIGHT) || null;
const targetPlatform = getTargetById(targetNode.getAttribute(ATTRIBUTES.PLATFORM));
const targetPlatform = getTargetById(targetNode.getAttribute(ATTRIBUTES.PLATFORM)) || TargetPlatforms.JAVA;
const targetNodeStyle = targetNode.getAttribute(ATTRIBUTES.STYLE);
const jsLibs = this.getJsLibraries(targetNode, targetPlatform);
const isFoldedButton = targetNode.getAttribute(ATTRIBUTES.FOLDED_BUTTON) !== "false";
Expand Down
10 changes: 3 additions & 7 deletions src/lib/crosslink.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { compressToBase64 } from 'lz-string';

import { isKeyOfObject } from '../utils/types';
import { TargetPlatforms, TargetPlatformsKeys } from '../utils/platforms';
import { getTargetById, TargetPlatformsKeys } from '../utils/platforms';

import {
escapeRegExp,
Expand Down Expand Up @@ -34,11 +33,8 @@ export function generateCrosslink(code: string, options?: LinkOptions) {

if (options && options.targetPlatform) {
const target =
options.targetPlatform && options.targetPlatform.toUpperCase();

if (!isKeyOfObject(target, TargetPlatforms))
throw new Error('Invalid target platform');

options.targetPlatform && getTargetById(options.targetPlatform);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's real bug - it's impossible to pass js-ir platform. Cause expected js_ir.
Test below.

if (!target) throw new Error('Invalid target platform');
opts.targetPlatform = options.targetPlatform;
}

Expand Down
4 changes: 2 additions & 2 deletions src/utils/platforms/TargetPlatform.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export default class TargetPlatform {
private id: string;
private printableName: string;
id: string;
printableName: string;

constructor(id: string, printableName: string) {
this.id = id;
Expand Down
10 changes: 4 additions & 6 deletions src/utils/platforms/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { isKeyOfObject } from '../types';

import TargetPlatform from './TargetPlatform';
import { TargetPlatforms } from './TargetPlatforms';
import { isKeyOfObject } from '../types';

export function getTargetById(id?: string | null) {
const key = id && id.toUpperCase();
return key && isKeyOfObject(key, TargetPlatforms)
? TargetPlatforms[key]
: TargetPlatforms.JAVA;
const key = id && id.toUpperCase().replace(/-/g, '_');

return isKeyOfObject(key, TargetPlatforms) ? TargetPlatforms[key] : null;
}

export function isJavaRelated(platform: TargetPlatform) {
Expand Down
13 changes: 6 additions & 7 deletions src/webdemo-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,27 +50,26 @@ export default class WebDemoApi {
* @returns {*|PromiseLike<T>|Promise<T>}
*/
static translateKotlinToJs(code, compilerVersion, platform, args, hiddenDependencies) {
const MINIMAL_MINOR_VERSION_IR = 5
const MINIMAL_MINOR_VERSION_WASM = 9
const minor = parseInt(compilerVersion.split(".")[1]);
const MINIMAL_VERSION_IR = '1.5.0';
const MINIMAL_VERSION_WASM = '1.9.0';

if (platform === TargetPlatforms.JS_IR && minor < MINIMAL_MINOR_VERSION_IR) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main issue 2.0.0 has minor less then 5 or 9.

if (platform === TargetPlatforms.JS_IR && compilerVersion < MINIMAL_VERSION_IR) {
return Promise.resolve({
output: "",
errors: [{
severity: "ERROR",
message: `JS IR compiler backend accessible only since 1.${MINIMAL_MINOR_VERSION_IR}.0 version`
message: `JS IR compiler backend accessible only since ${MINIMAL_VERSION_IR} version`
}],
jsCode: ""
})
}

if (platform === TargetPlatforms.WASM && minor < MINIMAL_MINOR_VERSION_WASM) {
if (platform === TargetPlatforms.WASM && compilerVersion < MINIMAL_VERSION_WASM) {
return Promise.resolve({
output: "",
errors: [{
severity: "ERROR",
message: `Wasm compiler backend accessible only since 1.${MINIMAL_MINOR_VERSION_WASM}.0 version`
message: `Wasm compiler backend accessible only since ${MINIMAL_VERSION_WASM} version`
}],
jsCode: ""
})
Expand Down
4 changes: 2 additions & 2 deletions tests/crosslink.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ test.describe('crosslink: library', () => {
checkLink(generateCrosslink('simple'), { code: 'simple' });

// Pass platforms with codeWithSample
checkLink(generateCrosslink('platform', { targetPlatform: 'JAVA' }), {
checkLink(generateCrosslink('platform', { targetPlatform: 'js-ir' }), {
code: 'platform',
targetPlatform: 'JAVA',
targetPlatform: 'js-ir',
});

// Invalid target
Expand Down
139 changes: 139 additions & 0 deletions tests/restrictions.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { expect, Page, test } from '@playwright/test';

import { readFileSync } from 'fs';
import { join } from 'path';

import { gotoHtmlWidget } from './utlis/server/playground';

import { RESULT_SELECTOR, WIDGET_SELECTOR } from './utlis/selectors';

import { prepareNetwork, printlnCode } from './utlis';
import { mockRunRequest, waitRunRequest } from './utlis/mocks/compiler';
import { runButton } from './utlis/interactions';
import { makeJSPrintCode } from './utlis/mocks/result';

const OUTPUTS = Object.freeze({
'js-ir': {
jsCode: makeJSPrintCode('Hello, world!'),
errors: { 'File.kt': [] },
exception: null,
text: '<outStream>Hello, world!\n</outStream>',
},
wasm: JSON.parse(
readFileSync(join(__dirname, 'utlis/mocks/wasm.json'), 'utf-8'),
),
});

const VERSIONS = [
{ version: '1.3.10' },
{ version: '1.9.20', latestStable: true },
{ version: '2.0.1' },
] as const;

test.describe('platform restrictions', () => {
test.beforeEach(async ({ page, baseURL }) => {
await prepareNetwork(page, baseURL, {
versions: (route) =>
route.fulfill({
body: JSON.stringify(VERSIONS),
}),
}); // offline mode
});

test('JS_IR for unsupported version', async ({ page }) => {
await shouldFailedRun(
page,
'js-ir',
'1.3.10',
'JS IR compiler backend accessible only since 1.5.0 version',
);
});

test('JS_IR for supported by minor version', async ({ page }) => {
await shouldSuccessRun(page, 'js-ir', '1.9.0');
});

test('JS_IR for supported by major version', async ({ page }) => {
await shouldSuccessRun(page, 'js-ir', '2.0.1');
});

test('WASM for unsupported version', async ({ page }) => {
await shouldFailedRun(
page,
'wasm',
'1.3.10',
'Wasm compiler backend accessible only since 1.9.0 version',
);
});

test('WASM for supported by minor version', async ({ page, browserName }) => {
test.skip(
browserName !== 'chromium',
"WASM doesn't supported in this browser",
);
await shouldSuccessRun(page, 'wasm', '1.9.0');
});

test('WASM for supported by major version', async ({ page, browserName }) => {
test.skip(
browserName !== 'chromium',
"WASM doesn't supported in this browser",
);
await shouldSuccessRun(page, 'wasm', '2.0.1');
});
});

async function shouldSuccessRun(
page: Page,
platform: keyof typeof OUTPUTS,
version: string,
) {
await gotoHtmlWidget(
page,
{ selector: 'code', version: version },
/* language=html */ `
<code data-target-platform='${platform}'>${printlnCode(
'Hello, world!',
)}</code>
`,
);

const resolveRun = await mockRunRequest(page);

const editor = page.locator(WIDGET_SELECTOR);

await Promise.all([waitRunRequest(page), runButton(editor)]);

resolveRun({
json: Object.freeze(OUTPUTS[platform]),
});

// playground loaded
await expect(editor.locator(RESULT_SELECTOR)).toBeVisible();
await expect(editor.locator(RESULT_SELECTOR)).toContainText('Hello, world!');
}

async function shouldFailedRun(
page: Page,
platform: string,
version: string,
text: string,
) {
await gotoHtmlWidget(
page,
{ selector: 'code', version: version },
/* language=html */ `
<code data-target-platform='${platform}'>${printlnCode(
'Hello, world!',
)}</code>
`,
);

const editor = page.locator(WIDGET_SELECTOR);
await runButton(editor);

await expect(editor.locator(RESULT_SELECTOR)).toBeVisible();
await expect(
editor.locator(RESULT_SELECTOR).locator('.test-fail'),
).toContainText(text);
}
5 changes: 4 additions & 1 deletion tests/utlis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ export async function refuseExternalUrls(
export async function prepareNetwork(
page: Page | BrowserContext,
baseURL: string,
options?: {
versions: Parameters<typeof mockVersions>[1];
},
) {
const unRefuse = await refuseExternalUrls(page, baseURL);
const unVersions = await mockVersions(page);
const unVersions = await mockVersions(page, options?.versions);

return async () => {
await unVersions();
Expand Down
4 changes: 3 additions & 1 deletion tests/utlis/mocks/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ function isRunRequest(url: URL | string) {

return (
uri.host === API_HOST &&
uri.pathname.match(/^\/?\/api\/\d+\.\d+\.\d+\/compiler\/run$/) !== null
(uri.pathname.match(/^\/?\/api\/\d+\.\d+\.\d+\/compiler\/run$/) !== null ||
uri.pathname.match(/^\/?\/api\/\d+\.\d+\.\d+\/compiler\/translate$/) !==
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catch for both types of targets.

null)
);
}

Expand Down
3 changes: 3 additions & 0 deletions tests/utlis/mocks/result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function makeJSPrintCode(text: string) {
return `var moduleId = function (_) {\n 'use strict';\n //region block: pre-declaration\n setMetadataFor(Unit, 'Unit', objectMeta);\n setMetadataFor(BaseOutput, 'BaseOutput', classMeta);\n setMetadataFor(NodeJsOutput, 'NodeJsOutput', classMeta, BaseOutput);\n setMetadataFor(BufferedOutput, 'BufferedOutput', classMeta, BaseOutput, VOID, BufferedOutput);\n setMetadataFor(BufferedOutputToConsoleLog, 'BufferedOutputToConsoleLog', classMeta, BufferedOutput, VOID, BufferedOutputToConsoleLog);\n //endregion\n function Unit() {\n }\n protoOf(Unit).toString = function () {\n return 'kotlin.Unit';\n };\n var Unit_instance;\n function Unit_getInstance() {\n return Unit_instance;\n }\n function get_output() {\n _init_properties_console_kt__rfg7jv();\n return output;\n }\n var output;\n function BaseOutput() {\n }\n function NodeJsOutput(outputStream) {\n BaseOutput.call(this);\n this.outputStream_1 = outputStream;\n }\n protoOf(NodeJsOutput).print_o1pwgy_k$ = function (message) {\n // Inline function 'kotlin.io.String' call\n var messageString = String(message);\n this.outputStream_1.write(messageString);\n };\n function BufferedOutputToConsoleLog() {\n BufferedOutput.call(this);\n }\n protoOf(BufferedOutputToConsoleLog).print_o1pwgy_k$ = function (message) {\n // Inline function 'kotlin.io.String' call\n var s = String(message);\n // Inline function 'kotlin.text.nativeLastIndexOf' call\n // Inline function 'kotlin.js.asDynamic' call\n var i = s.lastIndexOf('\\n', 0);\n if (i >= 0) {\n var tmp = this;\n var tmp_0 = this.buffer_1;\n // Inline function 'kotlin.text.substring' call\n // Inline function 'kotlin.js.asDynamic' call\n tmp.buffer_1 = tmp_0 + s.substring(0, i);\n this.flush_shahbo_k$();\n // Inline function 'kotlin.text.substring' call\n var this_0 = s;\n var startIndex = i + 1 | 0;\n // Inline function 'kotlin.js.asDynamic' call\n s = this_0.substring(startIndex);\n }\n this.buffer_1 = this.buffer_1 + s;\n };\n protoOf(BufferedOutputToConsoleLog).flush_shahbo_k$ = function () {\n console.log(this.buffer_1);\n this.buffer_1 = '';\n };\n function BufferedOutput() {\n BaseOutput.call(this);\n this.buffer_1 = '';\n }\n protoOf(BufferedOutput).print_o1pwgy_k$ = function (message) {\n var tmp = this;\n var tmp_0 = this.buffer_1;\n // Inline function 'kotlin.io.String' call\n tmp.buffer_1 = tmp_0 + String(message);\n };\n function print(message) {\n _init_properties_console_kt__rfg7jv();\n get_output().print_o1pwgy_k$(message);\n }\n var properties_initialized_console_kt_gll9dl;\n function _init_properties_console_kt__rfg7jv() {\n if (!properties_initialized_console_kt_gll9dl) {\n properties_initialized_console_kt_gll9dl = true;\n // Inline function 'kotlin.run' call\n // Inline function 'kotlin.contracts.contract' call\n // Inline function 'kotlin.io.output.<anonymous>' call\n var isNode = typeof process !== 'undefined' && process.versions && !!process.versions.node;\n output = isNode ? new NodeJsOutput(process.stdout) : new BufferedOutputToConsoleLog();\n }\n }\n function implement(interfaces) {\n var maxSize = 1;\n var masks = [];\n var inductionVariable = 0;\n var last = interfaces.length;\n while (inductionVariable < last) {\n var i = interfaces[inductionVariable];\n inductionVariable = inductionVariable + 1 | 0;\n var currentSize = maxSize;\n var tmp1_elvis_lhs = i.prototype.$imask$;\n var imask = tmp1_elvis_lhs == null ? i.$imask$ : tmp1_elvis_lhs;\n if (!(imask == null)) {\n masks.push(imask);\n currentSize = imask.length;\n }\n var iid = i.$metadata$.iid;\n var tmp;\n if (iid == null) {\n tmp = null;\n } else {\n // Inline function 'kotlin.let' call\n // Inline function 'kotlin.contracts.contract' call\n // Inline function 'kotlin.js.implement.<anonymous>' call\n tmp = bitMaskWith(iid);\n }\n var iidImask = tmp;\n if (!(iidImask == null)) {\n masks.push(iidImask);\n currentSize = Math.max(currentSize, iidImask.length);\n }\n if (currentSize > maxSize) {\n maxSize = currentSize;\n }\n }\n return compositeBitMask(maxSize, masks);\n }\n function bitMaskWith(activeBit) {\n var numberIndex = activeBit >> 5;\n var intArray = new Int32Array(numberIndex + 1 | 0);\n var positionInNumber = activeBit & 31;\n var numberWithSettledBit = 1 << positionInNumber;\n intArray[numberIndex] = intArray[numberIndex] | numberWithSettledBit;\n return intArray;\n }\n function compositeBitMask(capacity, masks) {\n var tmp = 0;\n var tmp_0 = new Int32Array(capacity);\n while (tmp < capacity) {\n var tmp_1 = tmp;\n var result = 0;\n var inductionVariable = 0;\n var last = masks.length;\n while (inductionVariable < last) {\n var mask = masks[inductionVariable];\n inductionVariable = inductionVariable + 1 | 0;\n if (tmp_1 < mask.length) {\n result = result | mask[tmp_1];\n }\n }\n tmp_0[tmp_1] = result;\n tmp = tmp + 1 | 0;\n }\n return tmp_0;\n }\n function protoOf(constructor) {\n return constructor.prototype;\n }\n function defineProp(obj, name, getter, setter) {\n return Object.defineProperty(obj, name, {configurable: true, get: getter, set: setter});\n }\n function objectCreate(proto) {\n return Object.create(proto);\n }\n function classMeta(name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity) {\n return createMetadata('class', name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity, null);\n }\n function createMetadata(kind, name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity, iid) {\n var undef = VOID;\n return {kind: kind, simpleName: name, associatedObjectKey: associatedObjectKey, associatedObjects: associatedObjects, suspendArity: suspendArity, $kClass$: undef, defaultConstructor: defaultConstructor, iid: iid};\n }\n function setMetadataFor(ctor, name, metadataConstructor, parent, interfaces, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity) {\n if (!(parent == null)) {\n ctor.prototype = Object.create(parent.prototype);\n ctor.prototype.constructor = ctor;\n }\n var metadata = metadataConstructor(name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity == null ? [] : suspendArity);\n ctor.$metadata$ = metadata;\n if (!(interfaces == null)) {\n var receiver = !(metadata.iid == null) ? ctor : ctor.prototype;\n receiver.$imask$ = implement(interfaces);\n }\n }\n function objectMeta(name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity) {\n return createMetadata('object', name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity, null);\n }\n function get_VOID() {\n _init_properties_void_kt__3zg9as();\n return VOID;\n }\n var VOID;\n var properties_initialized_void_kt_e4ret2;\n function _init_properties_void_kt__3zg9as() {\n if (!properties_initialized_void_kt_e4ret2) {\n properties_initialized_void_kt_e4ret2 = true;\n VOID = void 0;\n }\n }\n function main() {\n print('${text}');\n }\n //region block: init\n Unit_instance = new Unit();\n //endregion\nif (typeof get_output !== "undefined") {\n get_output();\n output = new BufferedOutput();\n _.output = get_output();\n}\n main();\n return _;\n}(typeof moduleId === 'undefined' ? {} : moduleId);\nmoduleId.output?.buffer_1;\n\n` as const;
}
Loading