(core) bump mocha version to allow parallel tests; move more tests to core

Summary:
This uses a newer version of mocha in grist-core so that tests can be run in parallel. That allows more tests to be moved without slowing things down overall. Tests moved are venerable browser tests; only the ones that "just work" or worked without too much trouble to are moved, in order to keep the diff from growing too large. Will wrestle with more in follow up.

Parallelism is at the file level, rather than the individual test.

The newer version of mocha isn't needed for grist-saas repo; tests are parallelized in our internal CI by other means. I've chosen to allocate files to workers in a cruder way than our internal CI, based on initial characters rather than an automated process. The automated process would need some reworking to be compatible with mocha running in parallel mode.

Test Plan: this diff was tested first on grist-core, then ported to grist-saas so saas repo history will correctly track history of moved files.

Reviewers: jarek

Reviewed By: jarek

Subscribers: jarek

Differential Revision: https://phab.getgrist.com/D3927
This commit is contained in:
Paul Fitzpatrick
2023-06-27 02:11:08 -04:00
parent 7d3b4b49d5
commit bcbf57d590
126 changed files with 6833 additions and 759 deletions

6
test/fixtures/plugins/.jshintrc vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"undef": true,
"unused": "vars",
"globalstrict": true,
"esnext": true
}

View File

@@ -0,0 +1,31 @@
/* global grist, self */
self.importScripts('/grist-plugin-api.js');
grist.rpc.registerImpl("testApiBrowser", {
getImportSource() {
const api = grist.rpc.getStub('GristDocAPI@grist', grist.checkers.GristDocAPI);
return api.getDocName()
.then((result) => {
const content = JSON.stringify({
tables: [{
table_name: '',
column_metadata: [{
id: 'getDocName',
type: 'Text'
}],
table_data: [[result]]
}]
});
const fileItem = {content, name: "GristDocAPI.jgrist"};
return {
item: { kind: "fileList", files: [fileItem] },
description: "GristDocAPI results"
};
});
}
});
grist.ready();

View File

@@ -0,0 +1,12 @@
name: browser-GristDocAPI
version: 0.0.0
description:
components:
safeBrowser: main.js
contributions:
importSources:
- importSource:
component: safeBrowser
name: testApiBrowser
label: Test GristDocAPI

View File

@@ -0,0 +1,28 @@
<html>
<body>
<h1 id="hello-bis-title">Hello Bis</h1>
</br>
<!-- numerous lines to produce a scrollable area !-->
0</br></br>
1</br></br>
2</br></br>
3</br></br>
4</br></br>
5</br></br>
6</br></br>
7</br></br>
8</br></br>
9</br></br>
10</br></br>
11</br></br>
12</br></br>
13</br></br>
14</br></br>
15</br></br>
16</br></br>
17</br></br>
18</br></br>
19</br></br>
20</br></br>
</body>
</html>

View File

@@ -0,0 +1,28 @@
<html>
<body>
<h1 id="hello-title">Hello</h1>
</br>
<!-- numerous lines to produce a scrollable area !-->
0</br></br>
1</br></br>
2</br></br>
3</br></br>
4</br></br>
5</br></br>
6</br></br>
7</br></br>
8</br></br>
9</br></br>
10</br></br>
11</br></br>
12</br></br>
13</br></br>
14</br></br>
15</br></br>
16</br></br>
17</br></br>
18</br></br>
19</br></br>
20</br></br>
</body>
</html>

View File

@@ -0,0 +1,14 @@
/* globals self, grist */
self.importScripts("/grist-plugin-api.js");
class CustomSection {
createSection(renderTarget) {
return grist.api.render('index.html', renderTarget);
}
}
grist.rpc.registerImpl('hello', new CustomSection(), grist.CustomSectionDescription);
grist.ready();

View File

@@ -0,0 +1,12 @@
name: helloSection
version: 0.0.0
components:
safeBrowser: main.js
contributions:
customSections:
- path: index.html
name: Hello World
- path: index-bis.html
name: Hello World (bis)
- path: test-subscribe-api.html
name: dataAPI test

View File

@@ -0,0 +1,11 @@
<html>
<head>
<script src="/grist-plugin-api.js"></script>
<script src="/jquery/dist/jquery.min.js"></script>
<script src="test-subscribe-api.js"></script>
</head>
<body>
<h1 id="data-api-section">Data API</h1>
<div id="panel"></div>
</body>
</html>

View File

@@ -0,0 +1,36 @@
/* global grist, window, $, document */
let tableId = 'Table1';
grist.ready();
grist.api.subscribe(tableId);
window.onload = () => {
showColumn('A');
};
grist.rpc.on("message", (msg) => {
if (msg.type === "docAction") {
// There could by many doc actions and fetching table is expensive, in practice this call would
// be be throttle
if (msg.action[0] === 'RenameTable') {
tableId = msg.action[2];
}
showColumn('A');
}
});
// fetch table and call the view with values of coldId
function showColumn(colId) {
grist.docApi.fetchTable(tableId).then(cols => updateView(cols[colId]));
}
// show the first column
function updateView(values) {
$("#panel").empty();
const res = $('<div class="result"></div>');
const text = document.createTextNode(JSON.stringify(values));
res.append(text);
$("#panel").append(res);
}

View File

@@ -0,0 +1,22 @@
<html>
<head>
<script src="/grist-plugin-api.js"></script>
<script src="script.js"></script>
<!-- jquery is required for running browser test (see: `test/browser/webdriverjq.js`) -->
<script src="/jquery/dist/jquery.min.js"></script>
<style type="text/css">
body {
background-color: #ffffffb0;
}
</style>
</head>
<body>
<input id="call-safePython" type="button" value="call safePython">
<input id="call-unsafeNode" type="button" value="call unsafeNode">
<input id="cancel" type="button" value="cancel">
<br/>
"name of the file: "
<input id="name" type="text">
<input id="ok" type="button" value="validate">
</body>
</html>

View File

@@ -0,0 +1,9 @@
/* global grist, self */
self.importScripts('/grist-plugin-api.js');
grist.addImporter('dummy', 'index.html', 'fullscreen');
grist.addImporter('dummy-inlined', 'index.html', 'inline');
grist.ready();

View File

@@ -0,0 +1,16 @@
name: pluginName
version: 0.0.1
components:
safeBrowser: main.js
safePython: sandbox/main.py
unsafeNode: node/main.js
contributions:
importSources:
- importSource:
component: safeBrowser
name: dummy
label: Dummy importer
- importSource:
component: safeBrowser
name: dummy-inlined
label: Inline Importer

View File

@@ -0,0 +1,4 @@
const grist = require('grist-plugin-api');
grist.rpc.registerFunc("func1", (name) => `Yo: ${name}`);
grist.ready();

View File

@@ -0,0 +1,11 @@
import sandbox
def greet(val):
return "With love: " + val
def main():
sandbox.register("func1", greet)
sandbox.run()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,44 @@
/* global grist, window, document, $ */
let resolve; // eslint-disable-line no-unused-vars
const importer = {
getImportSource: () => new Promise((_resolve) => {
resolve = _resolve;
})
};
grist.rpc.registerImpl('dummy', importer );
grist.rpc.registerImpl('dummy-inlined', importer );
grist.ready();
window.onload = function() {
callFunctionOnClick('#call-safePython', 'func1@sandbox/main.py', 'Bob');
callFunctionOnClick('#call-unsafeNode', 'func1@node/main.js', 'Alice');
document.querySelector('#cancel').addEventListener('click', () => resolve());
document.querySelector('#ok').addEventListener('click', () => {
const name = $('#name').val();
resolve({
item: {
kind: "fileList",
files: [{content: "A,B\n1,2\n", name}]
},
description: name + " selected!"
});
});
};
function callFunctionOnClick(selector, funcName, ...args) {
document.querySelector(selector).addEventListener('click', () => {
grist.rpc.callRemoteFunc(funcName, ...args)
.then(val => {
const resElement = document.createElement('h1');
resElement.classList.add(`result`);
resElement.textContent = val;
document.body.appendChild(resElement);
});
});
}

View File

@@ -0,0 +1,3 @@
version: 0.0.1
contributions:
importSources:

View File

@@ -0,0 +1,12 @@
name: crazy-plugin
version: 0.0.1
experimental: true
components:
safePython: sandbox/main.py
contributions:
fileParsers:
- fileExtensions: ["csv"]
parseFile:
component: "safePython"
name: "csv_parser"

View File

@@ -0,0 +1 @@
# nothing

View File

@@ -0,0 +1,3 @@
version: 0.0.1
contributions:
invalidContibutionPoint:

View File

@@ -0,0 +1,14 @@
name: pluginName
version: 0.0.1
components:
safePython: sandbox/main.py
deactivate:
# Let's keep it low for tests to be fast, but big enough for test to be accurate.
inactivitySec: 0.1
contributions:
fileParsers:
- fileExtensions: ["csv"]
parseFile:
component: "safePython"
name: "csv_parser"

View File

@@ -0,0 +1,28 @@
import time
import sandbox
# pylint: disable=unused-argument
# pylint: disable=no-member
def import_files(file_source, parse_options):
end = time.time() + 1
while time.time() < end:
pass
return {
"parseOptions": {},
# Make sure the output is a list of GristTables as documented at app/plugin/GristTable.ts
"tables": [{
"table_name": "mytable",
"column_metadata": [],
"table_data": [],
}]
}
def main():
sandbox.register("csv_parser.parseFile", import_files)
sandbox.run() # pylint: disable=no-member
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,9 @@
name: missing-components
version: 0.0.1
# missing `components` entry
contributions:
fileParsers:
- fileExtensions: ["csv"]
parseFile:
component: "safePython"
name: "csv_parser"

View File

@@ -0,0 +1,10 @@
name: missing-safePython
version: 0.0.1
components:
# missing `safePython` component
contributions:
fileParsers:
- fileExtensions: ["csv"]
parseFile:
component: "safePython"
name: "csv_parser"

View File

@@ -0,0 +1,14 @@
name: pluginName
version: 0.0.1
components:
safePython: sandbox/main.py
deactivate:
# Let's keep it low for tests to be fast, but big enough for test to be accurate.
inactivitySec: 0.1
contributions:
fileParsers:
- fileExtensions: ["csv"]
parseFile:
component: "safePython"
name: "csv_parser"

View File

@@ -0,0 +1,28 @@
import sandbox
# pylint: disable=unused-argument
# pylint: disable=no-member
# TODO: configure pylint behavior for both `test/fixtures/plugins` and
# `/plugins` folders: either to ignore them completely or to ignore
# above mentioned rules.
def import_files(file_source, parse_options=None):
return {
"parseOptions": {},
"tables": [{
"table_name": "mytable",
"column_metadata": [],
"table_data": []
}]}
def main():
# Todo: Grist should expose a register method accepting arguments as
# follow: register('csv_parser', 'importFiles', can_parse)
sandbox.register("csv_parser.parseFile", import_files)
sandbox.run() # pylint: disable=no-member
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,5 @@
const grist = require('grist-plugin-api');
grist.rpc.registerFunc("yo", (name) => `yo ${name}`);
grist.rpc.registerFunc("yoSafePython", (name) => grist.rpc.callRemoteFunc("yo@sandbox/main.py", name));
grist.ready();

View File

@@ -0,0 +1,17 @@
name: testPluginFunction
version: 0.0.1
components:
safePython: sandbox/main.py
unsafeNode: backend.js
safeBrowser: main.js
# For the purpose of this unit-test contributions property is actually
# NOT need and only provided for the sake of making this manifest
# valid,
contributions:
importSources:
- importSource:
component: "safeBrowser"
name: index.html
label: My safe importer

View File

@@ -0,0 +1,15 @@
import sandbox
def greet(name):
return "Hi " + name
def yo(name):
return "yo " + name + " from safePython"
def main():
sandbox.register("greet", greet)
sandbox.register("yo", yo)
sandbox.run()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,11 @@
name: pluginName
version: 0.0.1
components:
safePython: sandbox/main.py
contributions:
fileParsers:
- fileExtensions: ["csv"]
parseFile:
component: "safePython"
name: "csv_parser"

View File

@@ -0,0 +1,29 @@
import sandbox
# pylint: disable=unused-argument
# pylint: disable=no-member
# TODO: configure pylint behavior for both `test/fixtures/plugins` and
# `/plugins` folders: either to ignore them completely or to ignore
# above mentioned rules.
def import_files(file_source, parse_options):
parse_options.update({"NUM_ROWS" : 1})
return {
"parseOptions": parse_options,
"tables": [{
"table_name": "mytable",
"column_metadata": [],
"table_data": []
}]}
def main():
# Todo: Grist should expose a register method accepting arguments as
# follow: register('csv_parser', 'parseFile', can_parse)
sandbox.register("csv_parser.parseFile", import_files)
sandbox.run() # pylint: disable=no-member
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,10 @@
name: pluginName
version: 0.0.1
components:
safeBrowser: '.'
contributions:
importSources:
- importSource:
component: "safeBrowser"
name: index.html
label: My safe importer

View File

@@ -0,0 +1 @@
wrong manifest as well in json

View File

@@ -0,0 +1 @@
:some-wrong-manifest

View File

@@ -0,0 +1,34 @@
const grist = require('grist-plugin-api');
const {foo} = grist.rpc.getStub('foo@grist');
let tableId = 'Table1';
const colId = 'A';
let promise = Promise.resolve(true);
grist.rpc.on('message', msg => {
if (msg.type === "docAction") {
if (msg.action[0] === 'RenameTable') {
tableId = msg.action[2];
}
promise = getColValues(colId).then(foo);
}
});
function getColValues(colId) {
return grist.docApi.fetchTable(tableId).then(data => data[colId]);
}
class TestSubscribe {
invoke(api, name, args){
return grist[api][name](...args);
}
// Returns a promise that resolves when an ongoing call resolves. Resolves right-awa if plugin has
// no pending call.
waitForPlugin() {
return promise.then(() => true);
}
}
module.exports = TestSubscribe;

View File

@@ -0,0 +1,21 @@
const grist = require('grist-plugin-api');
const TestSubscribe = require('./TestSubscribe');
grist.rpc.registerImpl("testApiNode", { // todo rename to testGristDocApiNode
invoke: (name, args) => {
const api = grist.rpc.getStub("GristDocAPI@grist", grist.checkers.GristDocAPI);
return api[name](...args)
.then((result) => [`node-GristDocAPI ${name}(${args.join(",")})`, result]);
},
});
grist.rpc.registerImpl("testDocStorage", {
invoke: (name, args) => {
const api = grist.rpc.getStub("DocStorage@grist", grist.checkers.Storage);
return api[name](...args);
},
});
grist.rpc.registerImpl("testSubscribe", new TestSubscribe());
grist.ready();

View File

@@ -0,0 +1,7 @@
name: node-GristDocAPI
version: 0.0.0
description:
components:
unsafeNode: main.js
contributions: {}

View File

@@ -0,0 +1,3 @@
// die immediately.

View File

@@ -0,0 +1,11 @@
name: node-fail
version: 0.0.0
description:
components:
unsafeNode: main.js
contributions:
fileParsers:
- fileExtensions: ["csv"]
parseFile:
component: unsafeNode
name: node-fail

View File

@@ -0,0 +1,11 @@
name: minicsv
version: 0.0.0
description: minicsv
components:
unsafeNode: nodebox/main.js
contributions:
fileParsers:
- fileExtensions: ["csv"]
parseFile:
component: unsafeNode
name: MiniCSV

View File

@@ -0,0 +1,69 @@
/**
*
* A minimal CSV reader with no type detection.
* All communication done by hand - real plugins should have helper code for
* RPC.
*
*/
const csv = require('csv');
const fs = require('fs');
const path = require('path');
function readCsv(data, replier) {
csv.parse(data, {}, function(err, output) {
const result = {
parseOptions: {
options: ""
},
tables: [
{
table_name: "space-monkey" + require('dependency_test'),
column_metadata: output[0].map(name => {
return {
id: name,
type: 'Text'
};
}),
table_data: output[0].map((name, idx) => {
return output.slice(1).map(row => row[idx]);
})
}
]
};
replier(result);
});
}
function processMessage(msg, replier, error_replier) {
if (msg.meth == 'parseFile') {
var dir = msg.dir;
var fname = msg.args[0].path;
var data = fs.readFileSync(path.resolve(dir, fname));
readCsv(data, replier);
} else {
error_replier('unknown method');
}
}
process.on('message', (m) => {
const sendReply = (result) => {
process.send({
mtype: 2, /* RespData */
reqId: m.reqId,
data: result
});
};
const sendError = (txt) => {
process.send({
mtype: 3, /* RespErr */
reqId: m.reqId,
mesg: txt
});
};
processMessage(m, sendReply, sendError);
});
// Once we have a handler for 'message' set up, send home a ready
// message to give the all-clear.
process.send({ mtype: 4, data: {ready: true }});

View File

@@ -0,0 +1,3 @@
"use strict";
module.exports = 42;

View File

@@ -0,0 +1,11 @@
{
"name": "dependency_test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

View File

@@ -0,0 +1,3 @@
process.send({ greeny: true });

View File

@@ -0,0 +1,11 @@
name: node-wrong-message
version: 0.0.0
description:
components:
unsafeNode: main.js
contributions:
fileParsers:
- fileExtensions: ["csv"]
parseFile:
component: unsafeNode
name: node-wrong-message

View File

@@ -0,0 +1,10 @@
name: validPluginName
version: 0.0.1
components:
safeBrowser: '.'
contributions:
importSources:
- importSource:
component: "safeBrowser"
name: index.html
label: My custom safe importer