Browse Source

Add tests fot trufstuf

Daniyar Chambylov 2 years ago
parent
commit
9ef5f971c2
4 changed files with 257 additions and 41 deletions
  1. 3 0
      .gitignore
  2. 2 0
      package.json
  3. 29 41
      src/analysis/mythx/trufstuf.ts
  4. 223 0
      test/analysis/mythx/trufstuf.spec.ts

+ 3 - 0
.gitignore

@@ -5,3 +5,6 @@
 node_modules
 out
 tmp
+.nyc_output
+coverage
+

+ 2 - 0
package.json

@@ -80,6 +80,8 @@
     "mocha": "^5.2.0",
     "mock-require": "^3.0.3",
     "nyc": "^13.1.0",
+    "proxyquire": "^2.1.0",
+    "sinon": "^7.2.3",
     "source-map-support": "^0.5.10",
     "ts-mocha": "^2.0.0",
     "ts-node": "^7.0.1",

+ 29 - 41
src/analysis/mythx/trufstuf.ts

@@ -3,16 +3,18 @@
 
 import * as fs from 'fs';
 import * as path from 'path';
-import * as assert from 'assert';
 import * as util from 'util';
 
 const readdir = util.promisify(fs.readdir);
+const fsStat = util.promisify(fs.stat);
 
 
 // Directories that must be in a truffle project
 
 const TRUFFLE_ROOT_DIRS = ['contracts', 'migrations'];
 
+
+// FIXME: remove this after tests are covered and async  is used
 export function isTruffleRoot (p: string): boolean {
     for (const shortDir of TRUFFLE_ROOT_DIRS) {
         const dir = `${p}/${shortDir}`;
@@ -27,6 +29,7 @@ export function isTruffleRoot (p: string): boolean {
     return true;
 }
 
+// FIXME: remove this after tests are covered and async  is used
 // Return dirname of path p, unless we think this
 // part of a truffle project, in which case we'll
 // it is in a "contracts" directory and then the
@@ -43,6 +46,31 @@ export function getRootDir (p: string): string {
     return dirname;
 }
 
+export const isTruffleRootAsync = async (p: string): Promise<boolean> => {
+    const all = await Promise.all(TRUFFLE_ROOT_DIRS.map(async (shortDir) => {
+        try {
+            const dir = await fsStat(`${p}/${shortDir}`);
+            return dir.isDirectory();
+        } catch (err) {
+            return false;
+        }
+    }));
+    const notTruffleDirs = all.filter(x => x === false);
+    return notTruffleDirs.length === 0;
+};
+
+export const getRootDirAsync = async (p: string): Promise<string> => {
+    const dirname = path.resolve(path.dirname(p));
+    if (path.basename(dirname) === 'contracts') {
+        const parent = path.normalize(`${dirname}/..`);
+        const isRoot = await isTruffleRootAsync(parent);
+        if (isRoot) {
+            return parent;
+        }
+    }
+    return dirname;
+};
+
 /**
  * Scans Truffle smart contracts build directory and returns
  * array of paths to smart contract build JSON files.
@@ -68,43 +96,3 @@ export function getContractsDir(p: string) {
 export function getMythReportsDir(buildContractsDir: string) {
     return path.normalize(path.join(buildContractsDir, '..', 'mythx'));
 }
-
-export function getTruffleBuildJsonFiles(directory: string): Array<string> {
-    const files = fs.readdirSync(directory);
-    const filteredFiles = files.filter(f => f !== 'Migrations.json');
-    const filePaths = filteredFiles.map(f => path.join(directory, f));
-    return filePaths;
-}
-
-export function guessTruffleBuildJson(directory: string): string {
-    const jsonPaths = exports.getTruffleBuildJsonFiles(directory);
-    if (!jsonPaths || jsonPaths.length < 1) {
-        throw new Error('Build contracts folder is empty, no smart contracts to analyze.');
-    }
-    const jsonPathsFiltered = [];
-    for (const p of jsonPaths) {
-        if ((path.basename(p) !== 'Migrations.json') &&
-            (path.basename(p) !== 'mythx.json')) {
-            jsonPathsFiltered.push(p);
-        }
-    }
-    let jsonPath: string;
-    if (jsonPathsFiltered.length >= 1) {
-        jsonPath = jsonPathsFiltered[0];
-    } else {
-        jsonPath = jsonPaths[0];
-    }
-    return jsonPath;
-}
-/**
- * Extracts path to solidity file from smart contract build object
- * found in json files in truffle build directories.
- *
- * Build objects have property "sourcePath".
- * For simplicity and readabilty build object is destructured and
- * "sourcePath" property extracted to output directly.
- *
- * @param {Object} param - Smart contract build object,
- * @returns {String} - Absolute path to solidity file.
- */
-export const getSolidityFileFromJson = ({ sourcePath }) => sourcePath;

+ 223 - 0
test/analysis/mythx/trufstuf.spec.ts

@@ -0,0 +1,223 @@
+import * as assert from 'assert';
+import * as fs from 'fs';
+import * as proxyquire from 'proxyquire';
+import * as sinon from 'sinon';
+import * as trufstuf from '../../../src/analysis/mythx/trufstuf';
+
+
+describe ('analysis.mythx.trufstuf', () => {
+  describe('isTruffleRoot', () => {
+    let fsExistsSync: any, fsStatSync: any;
+
+    afterEach(() => {
+      fsExistsSync.restore();
+      fsStatSync.restore();
+    });
+
+    it('should return true for truffle directory', () => {
+      fsExistsSync = sinon.stub(fs, 'existsSync').returns(true);
+      fsStatSync = sinon.stub(fs, 'statSync').returns({
+        isDirectory: () => true,
+      });
+
+      const res = trufstuf.isTruffleRoot('test');
+      assert.ok(fsExistsSync.called);
+      assert.ok(fsStatSync.called);
+      assert.ok(res);
+    });
+
+    it('should return false when directiry does not exist', () => {
+      fsExistsSync = sinon.stub(fs, 'existsSync').returns(false);
+      fsStatSync = sinon.stub(fs, 'statSync').returns({
+        isDirectory: () => true,
+      });
+
+      const res = trufstuf.isTruffleRoot('test');
+      assert.ok(fsExistsSync.called);
+      assert.ok(!fsStatSync.called);
+      assert.ok(!res);
+    });
+
+    it('should return false when fs stat fails', () => {
+      fsExistsSync = sinon.stub(fs, 'existsSync').returns(true);
+      fsStatSync = sinon.stub(fs, 'statSync').returns(null);
+
+      const res = trufstuf.isTruffleRoot('test');
+      assert.ok(fsExistsSync.called);
+      assert.ok(fsStatSync.called);
+      assert.ok(!res);
+    });
+
+    it('should return false when path is not directory', () => {
+      fsExistsSync = sinon.stub(fs, 'existsSync').returns(true);
+      fsStatSync = sinon.stub(fs, 'statSync').returns({
+        isDirectory: () => false,
+      });
+
+      const res = trufstuf.isTruffleRoot('test');
+      assert.ok(fsExistsSync.called);
+      assert.ok(fsStatSync.called);
+      assert.ok(!res);
+    });
+  });
+
+  describe('getRootDir', () => {
+    let fsExistsSync: any, fsStatSync: any;
+
+    afterEach(() => {
+      fsExistsSync.restore();
+      fsStatSync.restore();
+    });
+
+    it('should return root path of truffle project', () => {
+      fsExistsSync = sinon.stub(fs, 'existsSync').returns(true);
+      fsStatSync = sinon.stub(fs, 'statSync').returns({
+        isDirectory: () => true,
+      });
+      const res = trufstuf.getRootDir('/path/to/truffle-project/contracts/contract.sol');
+      assert.equal(res, '/path/to/truffle-project');
+    });
+
+    it('should return directory when it is not truffle project', () => {
+      fsExistsSync = sinon.stub(fs, 'existsSync').returns(false);
+      fsStatSync = sinon.stub(fs, 'statSync').returns({
+        isDirectory: () => true,
+      });
+      const res = trufstuf.getRootDir('/path/to/truffle-project/contracts/contract.sol');
+      assert.equal(res, '/path/to/truffle-project/contracts');
+    });
+
+    it('should return directory when it does not have contracts folder', () => {
+      fsExistsSync = sinon.stub(fs, 'existsSync').returns(false);
+      fsStatSync = sinon.stub(fs, 'statSync').returns({
+        isDirectory: () => true,
+      });
+      const res = trufstuf.getRootDir('/path/to/truffle-project/contract.sol');
+      assert.equal(res, '/path/to/truffle-project');
+    });
+  });
+
+  describe('isTruffleRootAsync', () => {
+    let fsStat: any;
+    afterEach(() => {
+      fsStat.restore();
+    });
+
+    it('should resolve true for truffle directory', async () => {
+      fsStat = sinon.stub(fs, 'stat').yields(null, {
+        isDirectory: () => true,
+      });
+
+      const trufModule = proxyquire('../../../src/analysis/mythx/trufstuf', {
+        fs: { stat: fsStat },
+      });
+
+      const res = await trufModule.isTruffleRootAsync('test');
+      assert.ok(fsStat.called);
+      assert.ok(res);
+    });
+
+    it('should resolve false when fs stat fails', async () => {
+      fsStat = sinon.stub(fs, 'stat').yields(new Error('error'));
+
+      const trufModule = proxyquire('../../../src/analysis/mythx/trufstuf', {
+        fs: { stat: fsStat },
+      });
+
+      const res = await trufModule.isTruffleRootAsync('test');
+      assert.ok(fsStat.called);
+      assert.ok(!res);
+    });
+
+    it('should resolve false when path is not directory', async () => {
+      fsStat = sinon.stub(fs, 'stat').yields(null, {
+        isDirectory: () => false,
+      });
+
+      const trufModule = proxyquire('../../../src/analysis/mythx/trufstuf', {
+        fs: { stat: fsStat },
+      });
+
+      const res = await trufModule.isTruffleRootAsync('test');
+      assert.ok(fsStat.called);
+      assert.ok(!res);
+    });
+  });
+
+  describe('getRootDirAsync', () => {
+    let fsStat: any;
+    afterEach(() => {
+      fsStat.restore();
+    });
+
+    it('should resolve root path of truffle project', async () => {
+      fsStat = sinon.stub(fs, 'stat').yields(null, {
+        isDirectory: () => true,
+      });
+      const trufModule = proxyquire('../../../src/analysis/mythx/trufstuf', {
+        fs: { stat: fsStat },
+      });
+
+      const res = await trufModule.getRootDirAsync('/path/to/truffle-project/contracts/contract.sol');
+      assert.equal(res, '/path/to/truffle-project');
+    });
+
+    it('should resolve directory when it is not truffle project', async () => {
+      fsStat = sinon.stub(fs, 'stat').yields(new Error('Error'));
+      const trufModule = proxyquire('../../../src/analysis/mythx/trufstuf', {
+        fs: { stat: fsStat },
+      });
+
+      const res = await trufModule.getRootDirAsync('/path/to/truffle-project/contracts/contract.sol');
+      assert.equal(res, '/path/to/truffle-project/contracts');
+    });
+
+    it('should resolve directory when it does not have contracts folder', async () => {
+      const res = await trufstuf.getRootDirAsync('/path/to/truffle-project/contract.sol');
+      assert.equal(res, '/path/to/truffle-project');
+    });
+  });
+
+  describe('getTruffleBuildJsonFilesAsync', () => {
+    let readdir: any;
+    afterEach(() => {
+      readdir.restore();
+    });
+
+    it('should return paths to contract build json files', async () => {
+      readdir = sinon.stub(fs, 'readdir').yields(null, [
+        'Migrations.json', 'Contract.sol', 'Contract2.sol',
+      ]);
+        const trufModule = proxyquire('../../../src/analysis/mythx/trufstuf', {
+          fs: { readdir },
+        });
+
+        const res = await trufModule.getTruffleBuildJsonFilesAsync('/folder/build/contracts');
+        assert.deepEqual(res, [
+          '/folder/build/contracts/Contract.sol',
+          '/folder/build/contracts/Contract2.sol',
+        ]);
+    });
+  });
+
+  describe('getBuildContractsDir', () => {
+    it('should return build contracts dir of the project path', () => {
+      const res = trufstuf.getBuildContractsDir('/my-project');
+      assert.equal(res, '/my-project/build/contracts');
+    });
+  });
+
+  describe('getContractsDir', () => {
+    it('should return contracts dir of the project path', () => {
+      const res = trufstuf.getContractsDir('/my-project');
+      assert.equal(res, '/my-project/contracts');
+    });
+  });
+
+  describe('getMythReportsDir', () => {
+    it('should return path to mythx reports', () => {
+      const res = trufstuf.getMythReportsDir('/my-project/build/contracts');
+      assert.equal(res, '/my-project/build/mythx');
+    });
+  });
+});