Skip to content

Commit

Permalink
feat: add snyk-cpp-plugin
Browse files Browse the repository at this point in the history
In order to test C/C++ projects:
* Add source mode for C/C++ projects.
* Use snyk-cpp-plugin to scan and display C/C++ projects.
* Use scan & display plugin mechanism in snyk test.
  • Loading branch information
gitphill committed Aug 19, 2020
1 parent f99a053 commit c28237b
Show file tree
Hide file tree
Showing 12 changed files with 223 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -76,6 +76,7 @@
"proxy-from-env": "^1.0.0",
"semver": "^6.0.0",
"snyk-config": "3.1.0",
"snyk-cpp-plugin": "1.2.0",
"snyk-docker-plugin": "3.17.0",
"snyk-go-plugin": "1.16.0",
"snyk-gradle-plugin": "3.5.1",
Expand Down
11 changes: 11 additions & 0 deletions src/cli/commands/test/index.ts
Expand Up @@ -48,6 +48,7 @@ import {
} from './formatters';
import * as utils from './utils';
import { getIacDisplayedOutput } from './iac-output';
import { getEcosystem, testEcosystem } from '../../../lib/ecosystems';

const debug = Debug('snyk-test');
const SEPARATOR = '\n-------------------------------------------------------\n';
Expand Down Expand Up @@ -104,6 +105,16 @@ async function test(...args: MethodArgs): Promise<TestCommandResult> {
}
}

const ecosystem = getEcosystem(options);
if (ecosystem) {
const commandResult = await testEcosystem(
ecosystem,
args as string[],
options,
);
return commandResult;
}

// Promise waterfall to test all other paths sequentially
for (const path of args as string[]) {
// Create a copy of the options so a specific test can
Expand Down
7 changes: 7 additions & 0 deletions src/cli/modes.ts
Expand Up @@ -7,6 +7,13 @@ interface ModeData {
}

const modes: Record<string, ModeData> = {
source: {
allowedCommands: ['test'],
config: (args): [] => {
args['source'] = true;
return args;
},
},
container: {
allowedCommands: ['test', 'monitor'],
config: (args): [] => {
Expand Down
62 changes: 62 additions & 0 deletions src/lib/ecosystems.ts
@@ -0,0 +1,62 @@
import * as cppPlugin from 'snyk-cpp-plugin';
import { Options } from './types';
import { TestCommandResult } from '../cli/commands/types';

interface Artifact {
type: string;
data: any;
meta?: { [key: string]: any };
}

interface ScanResult {
artifacts: Artifact[];
}

export interface EcosystemPlugin {
scan: (options: Options) => Promise<ScanResult[]>;
display: (scanResults: ScanResult[]) => Promise<string>;
}

export type Ecosystem = 'cpp';

const EcosystemPlugins: {
readonly [ecosystem in Ecosystem]: EcosystemPlugin;
} = {
cpp: cppPlugin,
};

export function getPlugin(ecosystem: Ecosystem): EcosystemPlugin {
return EcosystemPlugins[ecosystem];
}

export function getEcosystem(options: Options): Ecosystem | null {
if (options.source) {
return 'cpp';
}
return null;
}

export async function testEcosystem(
ecosystem: Ecosystem,
paths: string[],
options: Options,
): Promise<TestCommandResult> {
const plugin = getPlugin(ecosystem);
let allScanResults: ScanResult[] = [];
for (const path of paths) {
options.path = path;
const scanResults = await plugin.scan(options);
allScanResults = allScanResults.concat(scanResults);
}

const stringifiedData = JSON.stringify(allScanResults, null, 2);
if (options.json) {
return TestCommandResult.createJsonTestCommandResult(stringifiedData);
}

const readableResult = await plugin.display(allScanResults);
return TestCommandResult.createHumanReadableTestCommandResult(
readableResult,
stringifiedData,
);
}
1 change: 1 addition & 0 deletions src/lib/types.ts
Expand Up @@ -45,6 +45,7 @@ export interface Options {
path: string;
docker?: boolean;
iac?: boolean;
source?: boolean; // C/C++ Ecosystem Support
file?: string;
policy?: string;
json?: boolean;
Expand Down
86 changes: 86 additions & 0 deletions test/ecosystems.spec.ts
@@ -0,0 +1,86 @@
import { Options } from '../src/lib/types';
import * as cppPlugin from 'snyk-cpp-plugin';
import * as path from 'path';
import * as fs from 'fs';
import { getPlugin, getEcosystem, testEcosystem } from '../src/lib/ecosystems';
import { TestCommandResult } from '../src/cli/commands/types';

describe('ecosystems', () => {
describe('getPlugin', () => {
it('should return c++ plugin when cpp ecosystem is given', () => {
const actual = getPlugin('cpp');
const expected = cppPlugin;
expect(actual).toBe(expected);
});

it('should return undefined when ecosystem is not supported', () => {
const actual = getPlugin('unsupportedEcosystem' as any);
const expected = undefined;
expect(actual).toBe(expected);
});
});

describe('getEcosystem', () => {
it('should return c++ ecosystem when options source is true', () => {
const options: Options = {
source: true,
path: '',
};
const actual = getEcosystem(options);
const expected = 'cpp';
expect(actual).toBe(expected);
});

it('should return null when options source is false', () => {
const options: Options = {
source: false,
path: '',
};
const actual = getEcosystem(options);
const expected = null;
expect(actual).toBe(expected);
});
});

describe('testEcosystem', () => {
const fixturePath = path.join(__dirname, 'fixtures', 'cpp-project');
const cwd = process.cwd();

function readFixture(filename: string) {
const filePath = path.join(fixturePath, filename);
return fs.readFileSync(filePath, 'utf-8');
}

beforeAll(() => {
process.chdir(fixturePath);
});
afterAll(() => {
process.chdir(cwd);
});

it('should return human readable result when no json option given', async () => {
const display = readFixture('display.txt');
const scan = readFixture('scan.json');
const stringifiedData = JSON.stringify(JSON.parse(scan), null, 2);
const expected = TestCommandResult.createHumanReadableTestCommandResult(
display,
stringifiedData,
);
const actual = await testEcosystem('cpp', ['.'], { path: '' });
expect(actual).toEqual(expected);
});

it('should return json result when json option', async () => {
const scan = readFixture('scan.json');
const stringifiedData = JSON.stringify(JSON.parse(scan), null, 2);
const expected = TestCommandResult.createJsonTestCommandResult(
stringifiedData,
);
const actual = await testEcosystem('cpp', ['.'], {
path: '',
json: true,
});
expect(actual).toEqual(expected);
});
});
});
4 changes: 4 additions & 0 deletions test/fixtures/cpp-project/add.cpp
@@ -0,0 +1,4 @@
int add(int x, int y)
{
return x + y;
}
1 change: 1 addition & 0 deletions test/fixtures/cpp-project/add.h
@@ -0,0 +1 @@
int add(int x, int y);
3 changes: 3 additions & 0 deletions test/fixtures/cpp-project/display.txt
@@ -0,0 +1,3 @@
52d1b046047db9ea0c581cafd4c68fe5 add.cpp
aeca71a6e39f99a24ecf4c088eee9cb8 add.h
ad3365b3370ef6b1c3e778f875055f19 main.cpp
7 changes: 7 additions & 0 deletions test/fixtures/cpp-project/main.cpp
@@ -0,0 +1,7 @@
#include <iostream>
#include "add.h"

int main() {
std::cout << "The sum of 3 and 4 is " << add(3, 4) << '\n';
return 0;
}
23 changes: 23 additions & 0 deletions test/fixtures/cpp-project/scan.json
@@ -0,0 +1,23 @@
[
{
"artifacts": [
{
"type": "cpp-fingerprints",
"data": [
{
"filePath": "add.cpp",
"hash": "52d1b046047db9ea0c581cafd4c68fe5"
},
{
"filePath": "add.h",
"hash": "aeca71a6e39f99a24ecf4c088eee9cb8"
},
{
"filePath": "main.cpp",
"hash": "ad3365b3370ef6b1c3e778f875055f19"
}
]
}
]
}
]
17 changes: 17 additions & 0 deletions test/modes.spec.ts
Expand Up @@ -134,6 +134,23 @@ describe('when have a valid mode and command', () => {
expect(cliArgs['docker']).toBeTruthy();
expect(cliArgs['experimental']).toBeTruthy();
});

it('"source test" should set source option and test command', () => {
const expectedCommand = 'test';
const expectedArgs = {
_: [],
source: true,
};
const cliCommand = 'source';
const cliArgs = {
_: ['test'],
};

const command = parseMode(cliCommand, cliArgs);
expect(command).toBe(expectedCommand);
expect(cliArgs).toEqual(expectedArgs);
expect(cliArgs['source']).toBeTruthy();
});
});

describe('when have a valid mode, command and exists a command alias', () => {
Expand Down

0 comments on commit c28237b

Please sign in to comment.