Skip to content

Commit

Permalink
initial build
Browse files Browse the repository at this point in the history
  • Loading branch information
Dipto Pandit (dipandit) committed Jan 28, 2021
1 parent adef54a commit 1d30d58
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 119 deletions.
46 changes: 8 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Binary file modified img/fake-tests.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 0 additions & 43 deletions img/test-explorer.svg

This file was deleted.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Binary file added release/snort3-test-adapter-0.0.1.vsix
Binary file not shown.
77 changes: 48 additions & 29 deletions src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<string,snort3Test>}=<{suite:TestSuiteInfo, testDetails:Map<string,snort3Test>}>{};
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<TestLoadStartedEvent | TestLoadFinishedEvent>();
private readonly testStatesEmitter = new vscode.EventEmitter<TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | TestEvent>();
Expand All @@ -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 = <number>(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 = <number>(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
Expand Down Expand Up @@ -175,12 +195,10 @@ export class Snort3TestAdapter implements TestAdapter {
}
this.log.info(`Scheduling snort3 tests ${JSON.stringify(tests)}`);
this.testStatesEmitter.fire(<TestRunStartedEvent>{ 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');
Expand All @@ -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);
Expand All @@ -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<void> {
// 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();
Expand Down
2 changes: 0 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
21 changes: 14 additions & 7 deletions src/snort3Test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,35 @@ export class snort3Test {
return new Promise((resolve)=>{
testStatesEmitter.fire(<TestEvent>{ 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(<TestEvent>{ type: 'test', test: this.xmlPath, state: result });
} catch{
testStatesEmitter.fire(<TestEvent>{ type: 'test', test: this.xmlPath, state: 'errored' });
}
} else {
testStatesEmitter.fire(<TestEvent>{ type: 'test', test: this.xmlPath, state: 'errored' });
}
/*if(signal || code && code > 2) testStatesEmitter.fire(<TestEvent>{ type: 'test', test: this.xmlPath, state: 'errored' });
else if(code == 2) testStatesEmitter.fire(<TestEvent>{ type: 'test', test: this.xmlPath, state: 'skipped' });
else if(code == 1) testStatesEmitter.fire(<TestEvent>{ type: 'test', test: this.xmlPath, state: 'failed' });
else if(code == 0) testStatesEmitter.fire(<TestEvent>{ type: 'test', test: this.xmlPath, state: 'passed' });*/
this.active_child=undefined;
resolve();
});
if(!this.active_child){
testStatesEmitter.fire(<TestEvent>{ type: 'test', test: this.xmlPath, state: 'errored' });
resolve();
}
});
/* -- for debugging --
return new Promise((resolve)=>{
testStatesEmitter.fire(<TestEvent>{ type: 'test', test: this.xmlPath, state: 'running' });
setTimeout(()=>{testStatesEmitter.fire(<TestEvent>{ type: 'test', test: this.xmlPath, state: 'passed' }); resolve();},500);
});
*/
/*
return new Promise((resolve)=>{
testStatesEmitter.fire(<TestEvent>{ type: 'test', test: id, state: 'running' });
Expand Down Expand Up @@ -208,22 +214,23 @@ export async function loadSnort3Tests(rootdir:vscode.WorkspaceFolder)
const test_env = process.env;
let executor_dir = rootdir.uri.path;
let SF_PREFIX_SNORT3=<string>(vscode.workspace.getConfiguration('snort3TestExplorer').get('sf_prefix_snort3'));
let DEPENDENCY_DIR=vscode.workspace.getConfiguration('snort3TestExplorer').get('dependencies');
let DEPENDENCY_DIR=<string>(vscode.workspace.getConfiguration('snort3TestExplorer').get('dependencies'));
let SNORT3_TEST_ROOT=executor_dir;

test_env.SNORT_LUA_PATH=SF_PREFIX_SNORT3+'/etc/snort/';
test_env.SNORT_INSTALL_PREFIX=SF_PREFIX_SNORT3;
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';
test_env.SNORT_TEST_DAQ_DIR=DEPENDENCY_DIR+'/libdaq/lib/daq:'+SF_PREFIX_SNORT3+'/lib64/snort/daqs:'+SF_PREFIX_SNORT3+'/lib64/snort_extra/daqs:'+SF_PREFIX_SNORT3+'/lib64/snort_test/daqs';
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=<string>(vscode.workspace.getConfiguration('snort3TestExplorer').get('snort_srcpath'));

var snort3Tests = new Map<string,snort3Test>();
const getLastItem = (thePath: string) => thePath.substring(thePath.lastIndexOf('/') + 1)
Expand Down

0 comments on commit 1d30d58

Please sign in to comment.