When I execute a testing witch need the ending result of a vscode.windows.terminal the testing gave me a false positive. I need wait until the terminal operation end in order to execute the asserts.
I use a class named Stack witch have a pom file. My test start with the execution of cd and mvn clean install using the vscode.windows.terminal. The idea of the test assertion is verify the existence of the target file.
const buildProgram = () => {
const terminal = vscode.window.createTerminal();
terminal.show();
terminal.sendText('cd ' + stackDirectory);
terminal.sendText('mvn clean install');
}
it("Stack Project build taget directory exists", function() {
const promise = Promise.all([buildProgram()])
.then(() => {
return fs.existsSync(stackDirectory + "/target");
});
expect(promise).to.eventually.equal(false);
});
This test runs without problem but in the end the target directory is not created.
Here is a function which listens for terminal exit then resolves promise. Essensially we sendText(yourCommandHere+";exit") such that the terminal exits after executing your command, and then listen for that exit.
Additionally, I would believe that when sending multiple commands its better to send them on one line, eg: terminal.sendText(command+";", false); terminal.sendText(command2+";exit") such that we assure the commands are run in the correct order.
export async function callInInteractiveTerminal(
command: string,
shellPath?: string,
name?: string,
location?: vscode.TerminalLocation
): Promise<vscode.TerminalExitStatus> {
const terminal = vscode.window.createTerminal({
shellPath,
name,
location: location ? location : vscode.TerminalLocation.Panel,
});
terminal.show();
terminal.sendText(command, false);
terminal.sendText("; exit");
return new Promise((resolve, reject) => {
const disposeToken = vscode.window.onDidCloseTerminal(
async (closedTerminal) => {
if (closedTerminal === terminal) {
disposeToken.dispose();
if (terminal.exitStatus !== undefined) {
resolve(terminal.exitStatus);
} else {
reject("Terminal exited with undefined status");
}
}
}
);
});
}
After further research it seems this function only works when terminalLocation is TerminalLocation.Panel, not TerminalLocation.Editor.
I'm also a little worried that a fast command might have the exit run before the eventlistener starts, but it works for my use case since i only use it when the terminal needs human interaction.
I find a possible solution replacing the test for this:
it("Stack Project build taget directory exists", function() {
buildProgram();
return new Promise((resolve, reject) => setTimeout(function(){
// Assert here.
if(fs.existsSync(stackDirectory + "/target")){
resolve();
}
reject();
}, 5000));
}).timeout('7s');
I don't really like the idea of using timeout but I don't find a way to say if the terminal is busy.
Related
I have made an extension that opens a file dialog. What I would like to do is after the file is selected, I want a python file to run. What I need is the VS Code command to run a file (or perhaps a python file specifically?).
here is a working example where the command I use is a command that comments the selected line in the currently active editor. It works perfectly so I know this structure is generally correct. I just need the right command to replace the comment line command.
below is the code in questions with the working command I mentioned above. I found it using this resource: where I found the comment line command
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const { ChildProcess } = require('child_process');
const vscode = require('vscode');
const { execFile } = require('node:child_process');
const { stdout, stderr } = require('process');
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
/**
* #param {vscode.ExtensionContext} context
*/
function activate(context) {
let disposable = vscode.commands.registerCommand('fileDialog.openFile', function () {
const options = {
canSelectMany: false,
openLabel: 'Open'
};
vscode.window.showOpenDialog(options).then(fileUri => {
if (fileUri && fileUri[0]) {
console.log('Selected file: ' + fileUri[0].fsPath);
vscode.commands.executeCommand('python.execInInterminal');
}
});
});
context.subscriptions.push(disposable);
}
// this method is called when your extension is deactivated
function deactivate() {}
module.exports = {
activate,
deactivate
}
You can go for child_process' exec or spawn if you only need to run the Python script(s).
If you really prefer to rely on the Python extension, then you'll need to at least feed the script's Uri into the executeCommand's argument - its the 2nd part of what you found.
function activate(context) {
let disposable = vscode.commands.registerCommand('fileDialog.openFile', function () {
const options = {
canSelectMany: false,
openLabel: 'Open',
filters: {
'Python script': ['py']
}
};
vscode.window.showOpenDialog(options).then((fileUris) => {
// This will always get an array of Uris regardless of canSelectMany being false
fileUris?.forEach(async (fileUri) => {
console.log(`Selected file: ${fileUri.fsPath}`);
await vscode.commands.executeCommand('python.execInInterminal', fileUri);
});
});
});
context.subscriptions.push(disposable);
}
If you want to handle more things, you can refer to the thenable unittest code they included in the repo:
https://github.com/microsoft/vscode-python/blob/main/src/test/smoke/runInTerminal.smoke.test.ts#L46-L48
I am wanting to make a function that receives a String that is the input and returns me an output as a string, but I am not able because of the response delay
var resultado = "old value";
function execShell(cmd) {
exec("uname", (error, data, getter) => {
if(error){
console.log("error",error.message);
return;
}
if(getter){
console.log("data",data);
return;
}
console.log(`need before exec: ${data}`);
resultado = data;
});
}
/* shell command for Linux */
execShell('uname');
console.log(`need after exec: ${resultado}`);
What happens here is, that that the callbacks are not executed from top to bottom. This means that console.log(need after exec: ${resultado}); is called directly after execShell and the child process hasn't returned yet.
You could use the sync version to execute it:
const cp = require("child_process");
const result = cp.execSync("uname").toString(); // the .toString() is here to convert from the buffer to a string
console.log(`result after exec ${result}`);
The docs are https://nodejs.org/dist/latest-v14.x/docs/api/child_process.html#child_process_child_process_execsync_command_options
You could use a NPM package that helps with the shell handling if that's what you are building: https://github.com/shelljs/shelljs which wraps alot of the child process parts with an easier API.
I have an instability issue.
I'm using openzeppelin-test-helpers, and first had an issue with typescript saying Could not find a declaration file for module '#openzeppelin/test-helpers'.. This issue was solved by creating a .d.ts file containing declare module "#openzeppelin/test-helpers";.
However, adding this created a new problem, which is that now, most of the time, only one file is being run by rm -rf build && truffle test (I guess this is similar to truffle test --reset).
I got 2 test files. The first one looks like:
require("chai")
.use(require("chai-as-promised"))
.should();
const EventHandler = artifacts.require("EventHandler");
const { expectRevert } = require("#openzeppelin/test-helpers");
contract("EventHandler", function([_, superAdmin0, admin0, device0]) {
beforeEach(async function() {
this.eventHandler = await EventHandler.new(superAdmin0);
});
describe("Initial tests", function() {
it("should print hello", async function() {
await this.eventHandler
.printHello()
.should.eventually.equal("Hello");
});
});
});
The second one looks like:
require("chai")
.use(require("chai-as-promised"))
.should();
const { expectRevert } = require("#openzeppelin/test-helpers");
const EventHandler = artifacts.require("EventHandler");
contract("Roles", function([_, superAdmin0, superAdmin1, admin0, device0]) {
beforeEach(async function() {
this.EventHandler = await EventHandler.new(superAdmin0);
});
it("...should work", async function() {});
});
When I comment the content of one file, or just what's inside contract(..., {}), the other file works just fine and the tests pass successfully.
However, whenever I leave those 2 files uncommented, I get a massive error:
Error: Returned values aren't valid, did it run Out of Gas?
Of course, resetting ganache-cli didn't solve anything...
Does anyone know where it could come from?
I've got a script that synchronously installs non-built-in modules at startup that looks like this
const cp = require('child_process')
function requireOrInstall (module) {
try {
require.resolve(module)
} catch (e) {
console.log(`Could not resolve "${module}"\nInstalling`)
cp.execSync(`npm install ${module}`)
console.log(`"${module}" has been installed`)
}
console.log(`Requiring "${module}"`)
try {
return require(module)
} catch (e) {
console.log(require.cache)
console.log(e)
}
}
const http = require('http')
const path = require('path')
const fs = require('fs')
const ffp = requireOrInstall('find-free-port')
const express = requireOrInstall('express')
const socket = requireOrInstall('socket.io')
// List goes on...
When I uninstall modules, they get installed successfully when I start the server again, which is what I want. However, the script starts throwing Cannot find module errors when I uninstall the first or first two modules of the list that use the function requireOrInstall. That's right, the errors only occur when the script has to install either the first or the first two modules, not when only the second module needs installing.
In this example, the error will be thrown when I uninstall find-free-port, unless I move its require at least one spot down ¯\_(• _ •)_/¯
I've also tried adding a delay directly after the synchronous install to give it a little more breathing time with the following two lines:
var until = new Date().getTime() + 1000
while (new Date().getTime() < until) {}
The pause was there. It didn't fix anything.
#velocityzen came with the idea to check the cache, which I've now added to the script. It doesn't show anything out of the ordinary.
#vaughan's comment on another question noted that this exact error occurs when requiring a module twice. I've changed the script to use require.resolve(), but the error still remains.
Does anybody know what could be causing this?
Edit
Since the question has been answered, I'm posting the one-liner (139 characters!). It doesn't globally define child_modules, has no last try-catch and doesn't log anything in the console:
const req=async m=>{let r=require;try{r.resolve(m)}catch(e){r('child_process').execSync('npm i '+m);await setImmediate(()=>{})}return r(m)}
The name of the function is req() and can be used like in #alex-rokabilis' answer.
It seems that the require operation after an npm install needs a certain delay.
Also the problem is worse in windows, it will always fail if the module needs to be npm installed.
It's like at a specific event snapshot is already known what modules can be required and what cannot. Probably that's why require.cache was mentioned in the comments. Nevertheless I suggest you to check the 2 following solutions.
1) Use a delay
const cp = require("child_process");
const requireOrInstall = async module => {
try {
require.resolve(module);
} catch (e) {
console.log(`Could not resolve "${module}"\nInstalling`);
cp.execSync(`npm install ${module}`);
// Use one of the two awaits below
// The first one waits 1000 milliseconds
// The other waits until the next event cycle
// Both work
await new Promise(resolve => setTimeout(() => resolve(), 1000));
await new Promise(resolve => setImmediate(() => resolve()));
console.log(`"${module}" has been installed`);
}
console.log(`Requiring "${module}"`);
try {
return require(module);
} catch (e) {
console.log(require.cache);
console.log(e);
}
}
const main = async() => {
const http = require("http");
const path = require("path");
const fs = require("fs");
const ffp = await requireOrInstall("find-free-port");
const express = await requireOrInstall("express");
const socket = await requireOrInstall("socket.io");
}
main();
await always needs a promise to work with, but it's not needed to explicitly create one as await will wrap whatever it is waiting for in a promise if it isn't handed one.
2) Use a cluster
const cp = require("child_process");
function requireOrInstall(module) {
try {
require.resolve(module);
} catch (e) {
console.log(`Could not resolve "${module}"\nInstalling`);
cp.execSync(`npm install ${module}`);
console.log(`"${module}" has been installed`);
}
console.log(`Requiring "${module}"`);
try {
return require(module);
} catch (e) {
console.log(require.cache);
console.log(e);
process.exit(1007);
}
}
const cluster = require("cluster");
if (cluster.isMaster) {
cluster.fork();
cluster.on("exit", (worker, code, signal) => {
if (code === 1007) {
cluster.fork();
}
});
} else if (cluster.isWorker) {
// The real work here for the worker
const http = require("http");
const path = require("path");
const fs = require("fs");
const ffp = requireOrInstall("find-free-port");
const express = requireOrInstall("express");
const socket = requireOrInstall("socket.io");
process.exit(0);
}
The idea here is to re-run the process in case of a missing module. This way we fully reproduce a manual npm install so as you guess it works! Also it seems more synchronous rather the first option, but a bit more complex.
I think your best option is either:
(ugly) to install package globally, instead of locally
(best solution ?) to define YOUR new 'package repository installation', when installing, AND when requiring
First, you may consider using the npm-programmatic package.
Then, you may define your repository path with something like:
const PATH='/tmp/myNodeModuleRepository';
Then, replace your installation instruction with something like:
const npm = require('npm-programmatic');
npm.install(`${module}`, {
cwd: PATH,
save:true
}
Eventually, replace your failback require instruction, with something like:
return require(module, { paths: [ PATH ] });
If it is still not working, you may update the require.cache variable, for instance to invalide a module, you can do something like:
delete require.cache[process.cwd() + 'node_modules/bluebird/js/release/bluebird.js'];
You may need to update it manually, to add information about your new module, before loading it.
cp.execSync is an async call so try check if the module is installed in it's call back function. I have tried it, installation is clean now:
const cp = require('child_process')
function requireOrInstall (module) {
try {
require.resolve(module)
} catch (e) {
console.log(`Could not resolve "${module}"\nInstalling`)
cp.execSync(`npm install ${module}`, () => {
console.log(`"${module}" has been installed`)
try {
return require(module)
} catch (e) {
console.log(require.cache)
console.log(e)
}
})
}
console.log(`Requiring "${module}"`)
}
const http = require('http')
const path = require('path')
const fs = require('fs')
const ffp = requireOrInstall('find-free-port')
const express = requireOrInstall('express')
const socket = requireOrInstall('socket.io')
When node_modules not available yet :
When node_modules available already:
If I have a watcher like this:
gulp.watch('js/**/*.js').on('change', path => {
gulp.series(build, reload)();
});
...and task build would look like this:
const build = done => {
return gulp
.src(path) // Use "path" here
.pipe(
rename({
dirname: ''
})
)
.pipe(uglify())
.pipe(gulp.dest('build'));
};
How can I pass path argument to the build task?
As an exercise I believe I got this working as you wanted. But first let me say that the traditional way of limiting the source pipeline would be with something like gulp-newer. You should see if that accomplishes what you want.
But here is something that may work for you [not well tested!]:
function build (path) {
return new Promise(resolve => {
// using setTimeout() to prove async/await is working as expected
setTimeout(() => {
resolve('resolved');
}, 2000);
// put your gulp.src pipeline here using 'path'
console.log("2 path = " + path);
});
};
function anotherTask (path) {
return new Promise(resolve => {
// put your gulp.src pipeline here
console.log("4 path = " + path); });
};
function testWatch () {
console.log("in testWatch");
// debounceDelay because gulp likes to call the watcher 2 or 3times otherwise
// see [gulp watch task running multiple times when a file is saved][2]
var watcher = gulp.watch('js/**/*.js', { debounceDelay: 2000 });
// I added the async/await because I wasn't sure those functions would be run in series
// as you wanted.
// With the event listener route I couldn't get gulp.series to work,
// so went with async/await.
watcher.on('change', async function(path, stats) {
console.log('1 File ' + path + ' was changed');
await build(path);
console.log("3 after build");
// I would assume that the **last** task in the chain doesn't need 'await'
// or to return a promise as in anotherTask
await anotherTask(path);
console.log("5 after anotherTask");
});
};
gulp.task('default', gulp.series(testWatch));
gulp watch running multiple times mentioned above in code.
Output (my js watch src is different than yours) :
in testWatch
1 File src\js\main.js was changed
2 path = src\js\main.js
3 after build
4 path = src\js\main.js
5 after anotherTask
1 File src\js\taxonomy.js was changed
2 path = src\js\taxonomy.js
3 after build
4 path = src\js\taxonomy.js
5 after anotherTask