فهرست منبع

Initial work on in-browser test runner

Run `grunt test` and open up the `build/test/index.html` to run the
tests.
toby 8 سال پیش
والد
کامیت
500522bdeb

+ 76 - 7
Gruntfile.js

@@ -9,6 +9,10 @@ module.exports = function(grunt) {
         "A persistent task which creates a development build whenever source files are modified.",
         ["clean:dev", "concat:css", "concat:js", "copy:htmlDev", "copy:staticDev", "chmod:build", "watch"]);
 
+    grunt.registerTask("test",
+        "A persistent task which creates a test build whenever source files are modified.",
+        ["clean:dev", "concat:cssTest", "concat:jsTest", "copy:htmlTest", "copy:staticTest", "chmod:build", "watch"]);
+
     grunt.registerTask("prod",
         "Creates a production-ready build. Use the --msg flag to add a compile message.",
         ["eslint", "exec:stats", "clean", "jsdoc", "concat", "copy:htmlDev", "copy:htmlProd", "copy:htmlInline",
@@ -50,7 +54,7 @@ module.exports = function(grunt) {
 
 
     // JS includes
-    var jsFiles = [
+    var jsIncludes = [
         // Third party framework libraries
         "src/js/lib/jquery-2.1.1.js",
         "src/js/lib/bootstrap-3.3.6.js",
@@ -154,10 +158,27 @@ module.exports = function(grunt) {
         "src/js/views/html/*.js",
         "!src/js/views/html/main.js",
 
-        // Start the app!
-        "src/js/views/html/main.js",
     ];
 
+    var jsAppFiles = [].concat(
+        jsIncludes,
+        [
+            // Start the main app!
+            "src/js/views/html/main.js",
+        ]
+    );
+
+    var jsTestFiles = [].concat(
+        jsIncludes,
+        [
+            "src/js/lib/vuejs/vue.min.js",
+            "src/js/test/*.js",
+            "src/js/test/tests/**/*",
+            // Start the test runner app!
+            "src/js/test/views/html/main.js",
+        ]
+    );
+
     var banner = '/**\n\
  * CyberChef - The Cyber Swiss Army Knife\n\
  *\n\
@@ -198,6 +219,7 @@ module.exports = function(grunt) {
             config: ["src/js/config/**/*.js"],
             views: ["src/js/views/**/*.js"],
             operations: ["src/js/operations/**/*.js"],
+            tests: ["src/js/test/**/*.js"],
         },
         jsdoc: {
             options: {
@@ -239,12 +261,35 @@ module.exports = function(grunt) {
                 ],
                 dest: "build/dev/styles.css"
             },
+            cssTest: {
+                options: {
+                    banner: banner.replace(/\/\*\*/g, "/*!"),
+                    process: function(content, srcpath) {
+                        // Change special comments from /** to /*! to comply with cssmin
+                        content = content.replace(/^\/\*\* /g, "/*! ");
+                        return grunt.template.process(content);
+                    }
+                },
+                src: [
+                    "src/css/lib/**/*.css",
+                    "src/css/structure/**/*.css",
+                    "src/css/themes/classic.css"
+                ],
+                dest: "build/test/styles.css"
+            },
             js: {
                 options: {
                     banner: '"use strict";\n'
                 },
-                src: jsFiles,
+                src: jsAppFiles,
                 dest: "build/dev/scripts.js"
+            },
+            jsTest: {
+                options: {
+                    banner: '"use strict";\n'
+                },
+                src: jsTestFiles,
+                dest: "build/test/scripts.js"
             }
         },
         copy: {
@@ -257,6 +302,15 @@ module.exports = function(grunt) {
                 src: "src/html/index.html",
                 dest: "build/dev/index.html"
             },
+            htmlTest: {
+                options: {
+                    process: function(content, srcpath) {
+                        return grunt.template.process(content, templateOptions);
+                    }
+                },
+                src: "src/html/test.html",
+                dest: "build/test/index.html"
+            },
             htmlProd: {
                 options: {
                     process: function(content, srcpath) {
@@ -294,6 +348,21 @@ module.exports = function(grunt) {
                     }
                 ]
             },
+            staticTest: {
+                files: [
+                    {
+                        expand: true,
+                        cwd: "src/static/",
+                        src: [
+                            "**/*",
+                            "**/.*",
+                            "!stats.txt",
+                            "!ga.html"
+                        ],
+                        dest: "build/test/"
+                    }
+                ]
+            },
             staticProd: {
                 files: [
                     {
@@ -468,15 +537,15 @@ module.exports = function(grunt) {
             },
             js: {
                 files: "src/js/**/*.js",
-                tasks: ["concat:js", "chmod:build"]
+                tasks: ["concat:js", "concat:jsTest", "chmod:build"]
             },
             html: {
                 files: "src/html/**/*.html",
-                tasks: ["copy:htmlDev", "chmod:build"]
+                tasks: ["copy:htmlDev", "copy:htmlTest", "chmod:build"]
             },
             static: {
                 files: ["src/static/**/*", "src/static/**/.*"],
-                tasks: ["copy:staticDev", "chmod:build"]
+                tasks: ["copy:staticDev", "copy:staticTest", "chmod:build"]
             },
             grunt: {
                 files: "Gruntfile.js",

+ 96 - 0
src/html/test.html

@@ -0,0 +1,96 @@
+<!-- htmlmin:ignore --><!--
+    CyberChef - The Cyber Swiss Army Knife
+    
+    @author tlwr [toby@toby.codes]
+
+    @copyright Crown Copyright 2017
+    @license Apache-2.0
+    
+      Copyright 2017 Crown Copyright
+    
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+    
+        http://www.apache.org/licenses/LICENSE-2.0
+    
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<!-- htmlmin:ignore -->
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="UTF-8">
+        <title>CyberChef Test Runner</title>
+        <link rel="icon" type="image/png" href="images/favicon.ico?__inline" />
+        <link href="styles.css" rel="stylesheet" />
+    </head>
+    <body>
+        <template id="test-status-icon-template">
+            <span>{{ getIcon() }}</span>
+        </template>
+
+        <template id="test-stats-template">
+            <div class="text-center row">
+                <div class="col-md-2"
+                     v-for="status in ['Waiting', 'Loading', 'Erroring', 'Failing']">
+                    <test-status-icon :status="status.toLowerCase()"></test-status-icon>
+                    <br>
+                    {{ status }}
+                    <br>
+                    {{ countTestsWithStatus(status.toLowerCase()) }}
+                </div>
+                <div class="col-md-2">
+                    <test-status-icon status="passing"></test-status-icon>
+                    <br>
+                    Passing
+                    <br>
+                    {{ countTestsWithStatus("passing") }}
+                    /
+                    {{ tests.length }}
+                </div>
+                <div class="col-md-2">
+                    <test-status-icon status="passing"></test-status-icon>
+                    <br>
+                    % Passing
+                    <br>
+                    {{ ((100.0 * countTestsWithStatus("passing")) / tests.length).toFixed(1) }}%
+                </div>
+            </div>
+        </template>
+
+        <template id="tests-template">
+            <table class="table table-striped">
+                <tbody>
+                    <tr v-for="test in tests">
+                        <td class="col-md-1 col-sm-4">
+                            <test-status-icon :status="test.status"></test-status-icon>
+                        </td>
+                        <td class="col-md-4 col-sm-8">
+                            {{ test.name }}
+                        </td>
+                        <td class="col-md-7 col-sm-12">
+                            <pre v-if="test.output"><code>{{ test.output }}</code></pre>
+                        </td>
+                    </tr>
+                </tbody>
+            </table>
+        </template>
+
+        <div class="container">
+            <main>
+                <h1>CyberChef Test Runner</h1>
+                <hr>
+                <test-stats :tests="tests"></test-stats>
+                <hr>
+                <tests :tests="tests"></tests>
+            </main>
+        </div>
+
+        <script type="application/javascript" src="scripts.js"></script>
+    </body>
+</html>

+ 6 - 2
src/js/.eslintrc.json

@@ -109,6 +109,10 @@
         "OutputWaiter": false,
         "RecipeWaiter": false,
         "SeasonalWaiter": false,
-        "WindowWaiter": false
+        "WindowWaiter": false,
+
+        /* test */
+        "Vue": false,
+        "TestRegister": false
     }
-}
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 5 - 0
src/js/lib/vuejs/vue.min.js


+ 97 - 0
src/js/test/TestRegister.js

@@ -0,0 +1,97 @@
+/**
+ * TestRegister.js
+ *
+ * This is so individual files can register their tests in one place, and
+ * ensure that they will get run by the frontend.
+ *
+ * @author tlwr [toby@toby.codes
+ *
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ *
+ */
+(function() {
+    /**
+     * Add a list of tests to the register.
+     *
+     * @class
+     */
+    function TestRegister() {
+        this.tests = [];
+    }
+
+    /**
+     * Add a list of tests to the register.
+     *
+     * @param {Object[]} tests
+     */
+    TestRegister.prototype.addTests = function(tests) {
+        this.tests = this.tests.concat(tests.map(function(test) {
+
+            test.status = "waiting";
+            test.output = "";
+
+            return test;
+        }));
+    };
+
+    /**
+     * Returns the list of tests.
+     *
+     * @returns {Object[]} tests
+        return this.tests;
+    };
+
+    /**
+     * Runs all the tests in the register and updates the state of each test.
+     *
+     */
+    TestRegister.prototype.runTests = function() {
+        this.tests.forEach(function(test, i) {
+            var chef = new Chef();
+
+            // This resolve is to not instantly break when async operations are
+            // supported. Marked as TODO.
+            Promise.resolve(chef.bake(
+                test.input,
+                test.recipeConfig,
+                {},
+                0,
+                0
+            ))
+                .then(function(result) {
+                    if (result.error) {
+                        if (test.expectedError) {
+                            test.status = "passing";
+                        } else {
+                            test.status = "erroring";
+                            test.output = [
+                                "Erroring",
+                                "-------",
+                                result.error.displayStr,
+                            ].join("\n");
+                        }
+                    } else {
+                        if (result.result === test.expectedOutput) {
+                            test.status = "passing";
+                        } else {
+                            test.status = "failing";
+                            test.output = [
+                                "Failing",
+                                "-------",
+                                "Expected",
+                                "-------",
+                                test.expectedOutput,
+                                "Actual",
+                                "-------",
+                                result.result,
+                            ].join("\n");
+                        }
+                    }
+                });
+        });
+    };
+
+    // Singleton TestRegister, keeping things simple and obvious.
+    window.TestRegister = new TestRegister();
+})();

+ 84 - 0
src/js/test/tests/core.js

@@ -0,0 +1,84 @@
+/**
+ * Core tests.
+ *
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ *
+ */
+TestRegister.addTests([
+    {
+        name: "Example error",
+        input: "1\n2\na\n4",
+        expectedOutput: "1\n2\n3\n4",
+        recipeConfig: [
+            {
+                op: "Fork",
+                args: ["\n", "\n", false],
+            },
+            {
+                op: "To Base",
+                args: [16],
+            },
+        ],
+    },
+    {
+        name: "Example fail",
+        input: "1\n2\na\n4",
+        expectedOutput: "1\n2\n3\n4",
+        recipeConfig: [
+            {
+                op: "Fork",
+                args: ["\n", "\n", true],
+            },
+            {
+                op: "To Base",
+                args: [16],
+            },
+        ],
+    },
+    {
+        name: "Fork: nothing",
+        input: "",
+        expectedOutput: "",
+        recipeConfig: [
+            {
+                op: "Fork",
+                args: ["\n", "\n", false],
+            },
+        ],
+    },
+    {
+        name: "Fork, Merge: nothing",
+        input: "",
+        expectedOutput: "",
+        recipeConfig: [
+            {
+                op: "Fork",
+                args: ["\n", "\n", false],
+            },
+            {
+                op: "Merge",
+                args: [],
+            },
+        ],
+    },
+    {
+        name: "Fork, (expect) Error, Merge",
+        input: "1\n2\na\n4",
+        expectedError: true,
+        recipeConfig: [
+            {
+                op: "Fork",
+                args: ["\n", "\n", false],
+            },
+            {
+                op: "To Base",
+                args: [16],
+            },
+            {
+                op: "Merge",
+                args: [],
+            },
+        ],
+    },
+]);

+ 77 - 0
src/js/test/tests/operations/Base58.js

@@ -0,0 +1,77 @@
+/**
+ * Base58 tests.
+ *
+ * @author tlwr [toby@toby.codes
+ *
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ *
+ */
+TestRegister.addTests([
+    {
+        name: "To Base58 (Bitcoin): nothing",
+        input: "",
+        expectedOutput: "",
+        recipeConfig: [
+            {
+                op: "To Base58",
+                args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"],
+            },
+        ],
+    },
+    {
+        name: "To Base58 (Ripple): nothing",
+        input: "",
+        expectedOutput: "",
+        recipeConfig: [
+            {
+                op: "To Base58",
+                args: ["rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"],
+            },
+        ],
+    },
+    {
+        name: "To Base58 (Bitcoin): 'hello world'",
+        input: "hello world",
+        expectedOutput: "StV1DL6CwTryKyV",
+        recipeConfig: [
+            {
+                op: "To Base58",
+                args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"],
+            },
+        ],
+    },
+    {
+        name: "To Base58 (Ripple): 'hello world'",
+        input: "hello world",
+        expectedOutput: "StVrDLaUATiyKyV",
+        recipeConfig: [
+            {
+                op: "To Base58",
+                args: ["rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"],
+            },
+        ],
+    },
+    {
+        name: "From Base58 (Bitcoin): 'StV1DL6CwTryKyV'",
+        input: "StV1DL6CwTryKyV",
+        expectedOutput: "hello world",
+        recipeConfig: [
+            {
+                op: "From Base58",
+                args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"],
+            },
+        ],
+    },
+    {
+        name: "From Base58 (Ripple): 'StVrDLaUATiyKyV'",
+        input: "StVrDLaUATiyKyV",
+        expectedOutput: "hello world",
+        recipeConfig: [
+            {
+                op: "From Base58",
+                args: ["rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"],
+            },
+        ],
+    },
+]);

+ 33 - 0
src/js/test/tests/operations/MorseCode.js

@@ -0,0 +1,33 @@
+/**
+ * Base58 tests.
+ *
+ * @author tlwr [toby@toby.codes
+ *
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ *
+ */
+TestRegister.addTests([
+    {
+        name: "To Morse Code: 'SOS'",
+        input: "SOS",
+        expectedOutput: "... --- ...",
+        recipeConfig: [
+            {
+                op: "To Morse Code",
+                args: ["-/.", "Space", "Line feed"],
+            },
+        ],
+    },
+    {
+        name: "From Morse Code '... --- ...'",
+        input: "... --- ...",
+        expectedOutput: "SOS",
+        recipeConfig: [
+            {
+                op: "From Morse Code",
+                args: ["Space", "Line feed"],
+            },
+        ],
+    },
+]);

+ 58 - 0
src/js/test/views/html/main.js

@@ -0,0 +1,58 @@
+/**
+ * main.js
+ *
+ * Simple VueJS app for running all the tests and displaying some basic stats.
+ * @author tlwr [toby@toby.codes]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ *
+ */
+(function() {
+    Vue.component("test-status-icon", {
+        template: "#test-status-icon-template",
+        props: ["status"],
+
+        methods: {
+            getIcon: function() {
+                var icons = {
+                    waiting: "⌚",
+                    loading: "⚡",
+                    passing: "✔️️",
+                    failing: "❌",
+                    erroring: "☠️",
+                };
+
+                return icons[this.status];
+            }
+        },
+    });
+
+    Vue.component("test-stats", {
+        template: "#test-stats-template",
+        props: ["tests"],
+
+        methods: {
+            countTestsWithStatus: function(status) {
+                return this.tests.filter(function(test) {
+                    return test.status === status;
+                }).length;
+            },
+        },
+    });
+
+    Vue.component("tests", {
+        template: "#tests-template",
+        props: ["tests"],
+    });
+
+    window.TestRunner = new Vue({
+        el: "main",
+        data: {
+            tests: TestRegister.getTests(),
+        },
+
+        mounted: function() {
+            TestRegister.runTests();
+        },
+    });
+})();

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است