diff --git a/README.md b/README.md index abb87de..c905666 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,15 @@ -# Example Test Adapter for Visual Studio Code +# Snort3 Test Explorer for Visual Studio Code -This repository contains an example for implementing a `TestAdapter` extension that works with the -[Test Explorer](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer) extension. - -More documentation can be found in the [Test Adapter API repository](https://github.com/hbenl/vscode-test-adapter-api). +Snort3 Test Explorer is a visual studio code extension that lets you run snort3 tests in the Sidebar of Visual Studio Code. This extention will activate when there is a snort3_test folder open in the workspace and automatically list all the tests available in the [Test Explorer](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer) side bar. ## Setup -* install the [Test Explorer](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer) extension -* fork and clone this repository and open it in VS Code -* run `npm install` -* run `npm run watch` or start the watch Task in VS Code -* start the debugger +* install the [Snort3 Test Explorer](https://marketplace.visualstudio.com/items?itemName=diptopandit.snort3-test-adapter) extension +* open extension settings +* set sf_prefix_snort3 to snort install path +* set snort_srcpath to snort source code path +* set dependencies to dependencies (libdaq, abcip, cpputest etc.) installation path -You should now see a second VS Code window, the Extension Development Host. -Open a folder in this window and click the "Test" icon in the Activity bar. -Now you should see the fake example test suite in the side panel: +You should now see a tree view of all the tests in the side panel: ![The fake example test suite](img/fake-tests.png) - -## Basic implementation - -* add any configuration properties that your Test Adapter needs to the `contributes.configuration.properties` section of `package.json` -* replace the `loadFakeTests()` call in `src/adapter.ts` with your code for loading the test definitions for the real test framework -* replace the `runFakeTests()` call in `src/adapter.ts` with your code for running the tests in a child process using the real test framework - -## Getting ready to publish - -* search for all occurrences of the word "example" in this project and replace them with the name of the testing framework that your Test Adapter supports -* update `package.json` with your preferred values (at a minimum you should change `author`, `publisher`, `homepage`, `repository` and `bugs`) -* create an icon for your Test Adapter (there's an SVG version of the Test Explorer icon at `img/test-explorer.svg`) and reference it in `package.json` -* replace this README with your documentation - -Now you're ready to [publish](https://code.visualstudio.com/docs/extensions/publish-extension) the first version of your Test Adapter. - -## Completing the implementation - -* implement the `debug()` method -* implement the `cancel()` method (it should kill the child process that was started by `run()` or `debug()`) -* watch the configuration for any changes that may affect the loading of test definitions and reload the test definitions if necessary -* watch the workspace for any changes to the test files and reload the test definitions if necessary -* watch the configuration for any changes that may affect the results of running the tests and emit an `autorun` event if necessary -* watch the workspace for any changes to the source files and emit an `autorun` event if necessary -* ensure that only one test run is active at a time diff --git a/img/fake-tests.png b/img/fake-tests.png index 471aca7..7d48289 100644 Binary files a/img/fake-tests.png and b/img/fake-tests.png differ diff --git a/img/test-explorer.svg b/img/test-explorer.svg deleted file mode 100644 index 2c86a19..0000000 --- a/img/test-explorer.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/package.json b/package.json index de6afe8..7622acb 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,11 @@ "type": "string", "scope": "resource" }, + "snort3TestExplorer.snort_srcpath": { + "description": "absolute path to the snort source directory", + "type": "string", + "scope": "resource" + }, "snort3TestExplorer.dependencies": { "description": "absolute path to the dependencies directory", "type": "string", diff --git a/release/snort3-test-adapter-0.0.1.vsix b/release/snort3-test-adapter-0.0.1.vsix new file mode 100644 index 0000000..11e6cef Binary files /dev/null and b/release/snort3-test-adapter-0.0.1.vsix differ diff --git a/src/adapter.ts b/src/adapter.ts index 53631d6..845cd73 100644 --- a/src/adapter.ts +++ b/src/adapter.ts @@ -5,6 +5,7 @@ import { Log } from 'vscode-test-adapter-util'; import { loadSnort3Tests, snort3Test, runTest } from './snort3Test'; import {myStatusBarItem} from './main'; import * as path from 'path'; +//import PromisePool from 'es6-promise-pool'; import {cpus} from 'os'; class jobQueue { @@ -38,16 +39,19 @@ class jobQueue { public flush(){ this.jobdata.splice(0); } + + public dispose(){ + this.flush(); + } } export class Snort3TestAdapter implements TestAdapter { private disposables: { dispose(): void }[] = []; public loadedTests: {suite:TestSuiteInfo, testDetails:Map}=<{suite:TestSuiteInfo, testDetails:Map}>{}; - private currentJobQ:jobQueue; + private currentJobQ:jobQueue = new jobQueue; private running:boolean = false; private loading:boolean = false; private cancelling:boolean = false; - //private active_jobs:snort3Test[]=[]; private readonly testsEmitter = new vscode.EventEmitter(); private readonly testStatesEmitter = new vscode.EventEmitter(); @@ -66,41 +70,57 @@ export class Snort3TestAdapter implements TestAdapter { private readonly log: Log ) { this.log.info('Initializing snort3_test adapter for '+ workspace.uri.path); - const watcher1=vscode.workspace.createFileSystemWatcher('**/*.{py,xml,sh,lua}',true,false,true) - .onDidChange((e)=>{ this.handleFileChange(e); }); - const watcher2=vscode.workspace.createFileSystemWatcher('**/*expected*',true,false,true) - .onDidChange((e)=>{ this.handleFileChange(e); }); - const watcher3=vscode.workspace.onDidChangeConfiguration((change)=>{ - if(change.affectsConfiguration('snort3TestExplorer.sf_prefix_snort3')){ - this.isTestReady=this.validate_config(); - this.load(); - } - if(change.affectsConfiguration('snort3TestExplorer.concurrency')){ - let newVal = (vscode.workspace.getConfiguration('snort3TestExplorer').get('concurrency')); - if(newVal) this.concurrency=newVal; - } - }); this.disposables.push(this.testsEmitter); this.disposables.push(this.testStatesEmitter); this.disposables.push(this.autorunEmitter); this.disposables.push(this.retireEmitter); - this.disposables.push(watcher1); - this.disposables.push(watcher2); - this.disposables.push(watcher3); - this.currentJobQ = new jobQueue; - this.isTestReady = this.validate_config(); + this.disposables.push(this.currentJobQ); + if(this.is_test_root()) + { + const watcher1=vscode.workspace.createFileSystemWatcher('**/*.{py,xml,sh,lua}',true,false,true) + .onDidChange((e)=>{ this.handleFileChange(e); }); + const watcher2=vscode.workspace.createFileSystemWatcher('**/*expected*',true,false,true) + .onDidChange((e)=>{ this.handleFileChange(e); }); + const watcher3=vscode.workspace.onDidChangeConfiguration((change)=>{ + if(change.affectsConfiguration('snort3TestExplorer.sf_prefix_snort3')){ + this.isTestReady=this.validate_config(); + this.load(); + } + if(change.affectsConfiguration('snort3TestExplorer.concurrency')){ + let newVal = (vscode.workspace.getConfiguration('snort3TestExplorer').get('concurrency')); + if(newVal) this.concurrency=newVal; + } + }); + + this.disposables.push(watcher1); + this.disposables.push(watcher2); + this.disposables.push(watcher3); + this.isTestReady = this.validate_config(); + } else { + this.dispose(); + } } + private is_test_root():boolean{ + try{ + fs.accessSync(this.workspace.uri.path + '/bin/snorttest.py', fs.constants.R_OK); + return true; + } catch { + return false; + } + } private validate_config():boolean{ - this.log.info('validating config'); - const config = vscode.workspace.getConfiguration('snort3TestExplorer'); + let config = vscode.workspace.getConfiguration('snort3TestExplorer'); + let snort_binary = config.get('sf_prefix_snort3')+'/bin/snort'; try{ - this.log.info(config.get('sf_prefix_snort3')); fs.accessSync(this.workspace.uri.path + '/bin/snorttest.py', fs.constants.R_OK); - fs.accessSync(config.get('sf_prefix_snort3')+'/bin/snort', fs.constants.R_OK); + fs.accessSync(snort_binary, fs.constants.R_OK); } catch(e) { this.log.warn(this.workspace.uri.path+": "+e); + if(e.message.search(snort_binary)) + vscode.window.showWarningMessage("Snort binary missing. \ + Make sure sf_prefix_snort3 setting is correct and snort binary is present in that path."); return false; } //don't care if this fails due to unavailable file handle @@ -175,12 +195,10 @@ export class Snort3TestAdapter implements TestAdapter { } this.log.info(`Scheduling snort3 tests ${JSON.stringify(tests)}`); this.testStatesEmitter.fire({ type: 'started', tests }); - myStatusBarItem.text=`$(beaker) $(sync~spin)Queuing jobs...`; for (const suiteOrTestId of tests) { const node = this.findNode(this.loadedTests.suite, suiteOrTestId); if (node) this.currentJobQ.post(node); } - myStatusBarItem.text=`$(beaker) Snort3 Tests`; if(this.running) return; this.running = true; var PromisePool = require('es6-promise-pool'); @@ -192,7 +210,7 @@ export class Snort3TestAdapter implements TestAdapter { const test = self.loadedTests.testDetails.get(node.id); return runTest(test,self.testStatesEmitter); } - else return null; + else return; } var test_pool = new PromisePool(testJobProducer, this.concurrency); @@ -205,13 +223,14 @@ export class Snort3TestAdapter implements TestAdapter { return; } -/* implement this method if your TestAdapter supports debugging tests +/* implement this method to run snort with gdb debugging tests async debug(tests: string[]): Promise { // start a test run in a child process and attach the debugger to it... } */ cancel(): void { + if(!this.isTestReady) return; this.log.info('Cancelling all scheduled jobs...'); this.cancelling = true; this.currentJobQ.flush(); diff --git a/src/main.ts b/src/main.ts index e5ab0ed..9a6d438 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,8 +7,6 @@ export async function activate(context: vscode.ExtensionContext) { const workspaceFolder = (vscode.workspace.workspaceFolders || [])[0]; - // create a simple logger that can be configured with the configuration variables - // `exampleExplorer.logpanel` and `exampleExplorer.logfile` const log = new Log('snort3TestExplorer', workspaceFolder, 'Snort3 Test Explorer'); context.subscriptions.push(log); diff --git a/src/snort3Test.ts b/src/snort3Test.ts index d180d88..ac5ba7c 100644 --- a/src/snort3Test.ts +++ b/src/snort3Test.ts @@ -25,11 +25,14 @@ export class snort3Test { return new Promise((resolve)=>{ testStatesEmitter.fire({ type: 'test', test: this.xmlPath, state: 'running' }); this.active_child = child_process.spawn(this.executor, - ['--daq-dir',this.test_env.SNORT_TEST_DAQ_DIR,'-x',this.test_env.SNORT_PREFIX,'.'], + ['--daq-dir',this.test_env.SNORT_TEST_DAQ_DIR, + '--plugin-path', this.test_env.SNORT_TEST_PLUGIN_PATH, + '--snort-test', this.test_env.SNORT3_TEST_ROOT,'-x',this.test_env.SNORT_PREFIX,'.'], {cwd:this.xmlPath,env:this.test_env}).once('exit',(code,signal)=>{ if(!(signal || code)){ try{ let result=fs.readFileSync(this.xmlPath+'/results','utf8').toString().substring(0,8).split('\t')[0].toLowerCase(); + if(result==='error') result = 'errored'; testStatesEmitter.fire({ type: 'test', test: this.xmlPath, state: result }); } catch{ testStatesEmitter.fire({ type: 'test', test: this.xmlPath, state: 'errored' }); @@ -37,10 +40,7 @@ export class snort3Test { } else { testStatesEmitter.fire({ type: 'test', test: this.xmlPath, state: 'errored' }); } - /*if(signal || code && code > 2) testStatesEmitter.fire({ type: 'test', test: this.xmlPath, state: 'errored' }); - else if(code == 2) testStatesEmitter.fire({ type: 'test', test: this.xmlPath, state: 'skipped' }); - else if(code == 1) testStatesEmitter.fire({ type: 'test', test: this.xmlPath, state: 'failed' }); - else if(code == 0) testStatesEmitter.fire({ type: 'test', test: this.xmlPath, state: 'passed' });*/ + this.active_child=undefined; resolve(); }); if(!this.active_child){ @@ -48,6 +48,12 @@ export class snort3Test { resolve(); } }); + /* -- for debugging -- + return new Promise((resolve)=>{ + testStatesEmitter.fire({ type: 'test', test: this.xmlPath, state: 'running' }); + setTimeout(()=>{testStatesEmitter.fire({ type: 'test', test: this.xmlPath, state: 'passed' }); resolve();},500); + }); + */ /* return new Promise((resolve)=>{ testStatesEmitter.fire({ type: 'test', test: id, state: 'running' }); @@ -208,7 +214,7 @@ export async function loadSnort3Tests(rootdir:vscode.WorkspaceFolder) const test_env = process.env; let executor_dir = rootdir.uri.path; let SF_PREFIX_SNORT3=(vscode.workspace.getConfiguration('snort3TestExplorer').get('sf_prefix_snort3')); - let DEPENDENCY_DIR=vscode.workspace.getConfiguration('snort3TestExplorer').get('dependencies'); + let DEPENDENCY_DIR=(vscode.workspace.getConfiguration('snort3TestExplorer').get('dependencies')); let SNORT3_TEST_ROOT=executor_dir; test_env.SNORT_LUA_PATH=SF_PREFIX_SNORT3+'/etc/snort/'; @@ -216,7 +222,7 @@ export async function loadSnort3Tests(rootdir:vscode.WorkspaceFolder) test_env.SNORT_PREFIX=SF_PREFIX_SNORT3; test_env.LD_LIBRARY_PATH=DEPENDENCY_DIR+'/libdaq/lib:'+SF_PREFIX_SNORT3+'/lib64:'+DEPENDENCY_DIR+'/safec/lib:'+DEPENDENCY_DIR+'/luajit/lib:'+DEPENDENCY_DIR+'/cpputest/lib64'; test_env.PKG_CONFIG_PATH=SF_PREFIX_SNORT3+'/lib64/pkgconfig:'+DEPENDENCY_DIR+'/libdaq/lib/pkgconfig:'+DEPENDENCY_DIR+'/cpputest/lib64/pkgconfig:'+DEPENDENCY_DIR+'/luajit/lib/pkgconfig:'+DEPENDENCY_DIR+'/safec/lib/pkgconfig'; - test_env.PATH=test_env.PATH+':'+SF_PREFIX_SNORT3+'/bin:'+SNORT3_TEST_ROOT+'/bin:'+DEPENDENCY_DIR+'/bin'; + test_env.PATH=test_env.PATH+':'+SF_PREFIX_SNORT3+'/bin:'+SNORT3_TEST_ROOT+'/bin:'+DEPENDENCY_DIR+'/abcip/bin:'+DEPENDENCY_DIR+'/libdaq/bin:'; test_env.PYTHONPATH=SNORT3_TEST_ROOT+'/lib'; test_env.LUA_PATH=SF_PREFIX_SNORT3+'/include/snort/lua/\?.lua\;\;'; test_env.NFS_PCAP_DIR='/nfs/netboot/snort/snort-test/pcaps'; @@ -224,6 +230,7 @@ export async function loadSnort3Tests(rootdir:vscode.WorkspaceFolder) test_env.SNORT_TEST_PLUGIN_PATH=SF_PREFIX_SNORT3+'/lib64'; test_env.SNORT_PLUGIN_PATH=SF_PREFIX_SNORT3+'/lib64'; test_env.SNORT3_TEST_ROOT=SNORT3_TEST_ROOT; + test_env.SNORT_SRCPATH=(vscode.workspace.getConfiguration('snort3TestExplorer').get('snort_srcpath')); var snort3Tests = new Map(); const getLastItem = (thePath: string) => thePath.substring(thePath.lastIndexOf('/') + 1)