var crypto = require("crypto"); var U = require(".."); var List = U.List; var os = require("os"); var sandbox = require("./sandbox"); // Reduce a test case by iteratively replacing AST nodes with various // permutations. Each AST_Statement in the tree is also speculatively dropped // to determine whether it is needed. If the altered tree and the last known // good tree produce the same output after being run, then the permutation // survives to the next generation and is the basis for subsequent iterations. // The test case is reduced as a consequence of complex expressions being // replaced with simpler ones. Note that a reduced test case will have // different runtime output - it is not functionally equivalent to the // original. The only criteria is that once the generated reduced test case is // run without minification, it will produce different output from the code // minified with `minify_options`. Returns a `minify` result object. Error.stackTraceLimit = Infinity; module.exports = function reduce_test(testcase, minify_options, r
aboutsummaryrefslogtreecommitdiff
path: root/gnu/services/spice.scm
blob: b8d2f8486e9c7fb3775f4741708f98e2da75d319 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2016 David Craven <david@craven.ch>
;;; Copyright © 2017 Clément Lassieur <clement@lassieur.org>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (gnu services spice)
  #:use-module (gnu packages spice)
  #:use-module (gnu services)
  #:use-module (gnu services shepherd)
  #:use-module (guix deprecation)
  #:use-module (guix gexp)
  #:use-module (guix records)
  #:export (spice-vdagent-configuration
            spice-vdagent-configuration?
            spice-vdagent-service-type
            spice-vdagent-service))  ; deprecated

(define-record-type* <spice-vdagent-configuration>
  spice-vdagent-configuration make-spice-vdagent-configuration
  spice-vdagent-configuration?
  (spice-vdagent spice-vdagent-configuration-spice-vdagent
                 (default spice-vdagent)))

(define (spice-vdagent-shepherd-service config)
  "Return a <shepherd-service> for spice-vdagentd with CONFIG."
  (define spice-vdagent (spice-vdagent-configuration-spice-vdagent config))

  (define spice-vdagentd-command
    (list
     (file-append spice-vdagent "/sbin/spice-vdagentd")
     "-x"))

  (list
   (shepherd-service
    (documentation "Spice vdagentd service")
    (requirement '(dbus-system))
    (provision '(spice-vdagentd))
    (start #~(lambda args
               ;; spice-vdagentd supports being activated upon the client
               ;; connecting to its socket; when not using such feature, the
               ;; socket should not exist before vdagentd creates it itself.
               (mkdir-p "/run/spice-vdagentd")
               (false-if-exception
                (delete-file "/run/spice-vdagentd/spice-vdagent-sock"))
               (fork+exec-command '#$spice-vdagentd-command)))
    (stop #~(make-kill-destructor)))))

(define spice-vdagent-profile
  (compose list spice-vdagent-configuration-spice-vdagent))

(define spice-vdagent-service-type
  (service-type
   (name 'spice-vdagent)
   (default-value (spice-vdagent-configuration))
   (extensions
    (list (service-extension shepherd-root-service-type
                             spice-vdagent-shepherd-service)
          (service-extension profile-service-type
                             spice-vdagent-profile)))
   (description "Start the @command{vdagentd} and @command{vdagent} daemons
from the @code{spice-vdagent} package to enable window resizing and clipboard
sharing for @acronym{VM, virtual machine} guests.")))

(define-deprecated (spice-vdagent-service
                    #:optional (config (spice-vdagent-configuration)))
  "Start the @command{vdagentd} and @command{vdagent} daemons
from @var{spice-vdagent} to enable guest window resizing and
clipboard sharing."
  (service spice-vdagent-service-type config))
rt._permute += step) * steps | 0) % 2) { case 0: CHANGED = true; return List.skip; default: if (!has_exit(node)) { // hoist function declaration body var body = node.body; node.body = []; body.push(node); // retain function with empty body to be dropped later CHANGED = true; return List.splice(body); } } } else if (node instanceof U.AST_DWLoop) { var expr = [ node.condition, node.body, null, // intentional ][ (node.start._permute * steps | 0) % 3 ]; node.start._permute += step; if (!expr) { if (node.body[0] instanceof U.AST_Break) { if (node instanceof U.AST_Do) { CHANGED = true; return List.skip; } expr = node.condition; // AST_While - fall through } } if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) { CHANGED = true; return to_statement(expr); } } else if (node instanceof U.AST_Finally) { // drop finally block node.start._permute++; CHANGED = true; return null; } else if (node instanceof U.AST_For) { var expr = [ node.init, node.condition, node.step, node.body, ][ (node.start._permute * steps | 0) % 4 ]; node.start._permute += step; if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) { CHANGED = true; return to_statement(expr); } } else if (node instanceof U.AST_ForIn) { var expr; switch ((node.start._permute * steps | 0) % 3) { case 0: if (!(node.init instanceof U.AST_Definitions && node.init.definitions[0].name instanceof U.AST_Destructured)) { expr = node.init; } break; case 1: expr = node.object; break; case 2: if (!has_loopcontrol(node.body, node, parent)) expr = node.body; break; } node.start._permute += step; if (expr) { CHANGED = true; return to_statement(expr); } } else if (node instanceof U.AST_If) { var expr = [ node.condition, node.body, node.alternative, ][ (node.start._permute * steps | 0) % 3 ]; node.start._permute += step; if (expr) { // replace if statement with its condition, then block or else block CHANGED = true; return to_statement(expr); } } else if (node instanceof U.AST_Object) { // first property's value var expr = node.properties[0] instanceof U.AST_ObjectKeyVal && node.properties[0].value; if (expr) { node.start._permute++; CHANGED = true; return expr; } } else if (node instanceof U.AST_PropAccess) { var expr = [ node.expression, node.property instanceof U.AST_Node && node.property, ][ node.start._permute++ % 2 ]; if (expr) { CHANGED = true; return expr; } } else if (node instanceof U.AST_SimpleStatement) { if (node.body instanceof U.AST_Call && node.body.expression instanceof U.AST_Function) { // hoist simple statement IIFE function expression body node.start._permute++; if (!has_exit(node.body.expression)) { var body = node.body.expression.body; node.body.expression.body = []; CHANGED = true; return List.splice(body); } } } else if (node instanceof U.AST_Switch) { var expr = [ node.expression, // switch expression node.body[0] && node.body[0].expression, // first case expression or undefined node.body[0] && node.body[0], // first case body or undefined ][ (node.start._permute * steps | 0) % 4 ]; node.start._permute += step; if (expr && (!(expr instanceof U.AST_Statement) || !has_loopcontrol(expr, node, parent))) { CHANGED = true; return expr instanceof U.AST_SwitchBranch ? new U.AST_BlockStatement({ body: expr.body.slice(), start: {}, }) : to_statement(expr); } } else if (node instanceof U.AST_Try) { var body = [ node.body, node.bcatch && node.bcatch.body, node.bfinally && node.bfinally.body, null, // intentional ][ (node.start._permute * steps | 0) % 4 ]; node.start._permute += step; if (body) { // replace try statement with try block, catch block, or finally block CHANGED = true; return new U.AST_BlockStatement({ body: body, start: {}, }); } else { // replace try with a break or return if first in try statement if (node.body[0] instanceof U.AST_Break || node.body[0] instanceof U.AST_Return) { CHANGED = true; return node.body[0]; } } } else if (node instanceof U.AST_Unary) { node.start._permute++; CHANGED = true; return node.expression; } else if (node instanceof U.AST_Var) { if (node.definitions.length == 1 && node.definitions[0].value) { // first declaration value node.start._permute++; CHANGED = true; return to_statement(node.definitions[0].value); } } else if (node instanceof U.AST_LabeledStatement) { if (node.body instanceof U.AST_Statement && !has_loopcontrol(node.body, node.body, node)) { // replace labelled statement with its non-labelled body node.start._permute = REPLACEMENTS.length; CHANGED = true; return node.body; } } if (in_list) { // drop switch branches if (parent instanceof U.AST_Switch && parent.expression != node) { node.start._permute++; CHANGED = true; return List.skip; } // replace or skip statement if (node instanceof U.AST_Statement) { node.start._permute++; CHANGED = true; return List.skip; } // remove this node unless its the sole element of a (transient) sequence if (!(parent instanceof U.AST_Sequence) || parent.expressions.length > 1) { node.start._permute++; CHANGED = true; return List.skip; } // skip element/property from (destructured) array/object if (parent instanceof U.AST_Array || parent instanceof U.AST_Destructured || parent instanceof U.AST_Object) { node.start._permute++; CHANGED = true; return List.skip; } } // replace this node var newNode = is_statement(node) ? new U.AST_EmptyStatement({ start: {}, }) : U.parse(REPLACEMENTS[node.start._permute % REPLACEMENTS.length | 0], { expression: true, }); newNode.start._permute = ++node.start._permute; CHANGED = true; return newNode; }, function(node, in_list) { if (node instanceof U.AST_Sequence) { // expand single-element sequence if (node.expressions.length == 1) return node.expressions[0]; } else if (node instanceof U.AST_Try) { // expand orphaned try block if (!node.bcatch && !node.bfinally) return new U.AST_BlockStatement({ body: node.body, start: {}, }); } else if (node instanceof U.AST_Definitions) { // remove empty var statement if (node.definitions.length == 0) return in_list ? List.skip : new U.AST_EmptyStatement({ start: {}, }); } }); var diff_error_message; for (var pass = 1; pass <= 3; ++pass) { var testcase_ast = U.parse(testcase); if (diff_error_message === testcase) { // only difference detected is in error message, so expose that and try again testcase_ast.transform(new U.TreeTransformer(function(node, descend) { if (node.TYPE == "Call" && node.expression.print_to_string() == "console.log") { return to_sequence(node.args); } if (node instanceof U.AST_Catch && node.argname) { descend(node, this); node.body.unshift(new U.AST_SimpleStatement({ body: wrap_with_console_log(new U.AST_SymbolRef(node.argname)), start: {}, })); return node; } })); var code = testcase_ast.print_to_string(); var diff = test_for_diff(code, minify_options, result_cache, max_timeout); if (diff && !diff.timed_out && !diff.error) { testcase = code; differs = diff; } else { testcase_ast = U.parse(testcase); } } diff_error_message = null; testcase_ast.walk(new U.TreeWalker(function(node) { // unshare start props to retain visit data between iterations node.start = JSON.parse(JSON.stringify(node.start)); node.start._permute = 0; })); var before_iterations = testcase; for (var c = 0; c < max_iterations; ++c) { if (verbose && pass == 1 && c % 25 == 0) { log("// reduce test pass " + pass + ", iteration " + c + ": " + testcase.length + " bytes"); } var CHANGED = false; var code_ast = testcase_ast.clone(true).transform(tt); if (!CHANGED) break; try { var code = code_ast.print_to_string(); } catch (ex) { // AST is not well formed. // no harm done - just log the error, ignore latest change and continue iterating. log("*** Error generating code from AST."); log(ex.stack); log("*** Discarding permutation and continuing."); continue; } var diff = test_for_diff(code, minify_options, result_cache, max_timeout); if (diff) { if (diff.timed_out) { // can't trust the validity of `code_ast` and `code` when timed out. // no harm done - just ignore latest change and continue iterating. } else if (diff.error) { // something went wrong during minify() - could be malformed AST or genuine bug. // no harm done - just log code & error, ignore latest change and continue iterating. log("*** Error during minification."); log(code); log(diff.error.stack); log("*** Discarding permutation and continuing."); } else if (is_error(diff.unminified_result) && is_error(diff.minified_result) && diff.unminified_result.name == diff.minified_result.name) { // ignore difference in error messages caused by minification diff_error_message = testcase; } else { // latest permutation is valid, so use it as the basis of new changes testcase_ast = code_ast; testcase = code; differs = diff; } } } if (before_iterations === testcase) break; if (verbose) { log("// reduce test pass " + pass + ": " + testcase.length + " bytes"); } } var beautified = U.minify(testcase, { compress: false, mangle: false, output: { beautify: true, braces: true, comments: true, }, }); testcase = { code: testcase, }; if (!beautified.error) { diff = test_for_diff(beautified.code, minify_options, result_cache, max_timeout); if (diff && !diff.timed_out && !diff.error) { testcase = beautified; testcase.code = "// (beautified)\n" + testcase.code; differs = diff; } } var lines = [ "" ]; if (isNaN(max_timeout)) { lines.push("// minify error: " + to_comment(strip_color_codes(differs.minified_result.stack))); } else { var unminified_result = strip_color_codes(differs.unminified_result); var minified_result = strip_color_codes(differs.minified_result); if (trim_trailing_whitespace(unminified_result) == trim_trailing_whitespace(minified_result)) { lines.push( "// (stringified)", "// output: " + JSON.stringify(unminified_result), "// minify: " + JSON.stringify(minified_result) ); } else { lines.push( "// output: " + to_comment(unminified_result), "// minify: " + to_comment(minified_result) ); } } lines.push("// options: " + to_comment(minify_options_json)); testcase.code += lines.join("\n"); testcase.warnings = warnings; return testcase; } }; function strip_color_codes(value) { return ("" + value).replace(/\u001b\[\d+m/g, ""); } function to_comment(value) { return ("" + value).replace(/\n/g, "\n// "); } function trim_trailing_whitespace(value) { return ("" + value).replace(/\s+$/, ""); } function has_exit(fn) { var found = false; var tw = new U.TreeWalker(function(node) { if (found) return found; if (node instanceof U.AST_Exit) { return found = true; } if (node instanceof U.AST_Scope && node !== fn) { return true; // don't descend into nested functions } }); fn.walk(tw); return found; } function has_loopcontrol(body, loop, label) { var found = false; var tw = new U.TreeWalker(function(node) { if (found) return true; if (node instanceof U.AST_LoopControl && this.loopcontrol_target(node) === loop) { return found = true; } }); if (label instanceof U.AST_LabeledStatement) tw.push(label); tw.push(loop); body.walk(tw); return found; } function is_error(result) { return typeof result == "object" && typeof result.name == "string" && typeof result.message == "string"; } function is_timed_out(result) { return is_error(result) && /timed out/.test(result.message); } function is_statement(node) { return node instanceof U.AST_Statement && !(node instanceof U.AST_AsyncFunction || node instanceof U.AST_Function); } function merge_sequence(array, node) { if (node instanceof U.AST_Sequence) { array.push.apply(array, node.expressions); } else { array.push(node); } return array; } function to_sequence(expressions) { if (expressions.length == 0) return new U.AST_Number({value: 0, start: {}}); if (expressions.length == 1) return expressions[0]; return new U.AST_Sequence({ expressions: expressions.reduce(merge_sequence, []), start: {}, }); } function to_statement(node) { return is_statement(node) ? node : new U.AST_SimpleStatement({ body: node, start: {}, }); } function wrap_with_console_log(node) { // wrap with console.log() return new U.AST_Call({ expression: new U.AST_Dot({ expression: new U.AST_SymbolRef({ name: "console", start: {}, }), property: "log", start: {}, }), args: [ node ], start: {}, }); } function run_code(code, toplevel, result_cache, timeout) { var key = crypto.createHash("sha1").update(code).digest("base64"); var value = result_cache[key]; if (!value) { var start = Date.now(); result_cache[key] = value = { result: sandbox.run_code(code, toplevel, timeout), elapsed: Date.now() - start, }; } return value; } function compare_run_code(code, minify_options, result_cache, max_timeout) { var minified = U.minify(code, minify_options); if (minified.error) return minified; var toplevel = sandbox.has_toplevel(minify_options); var unminified = run_code(code, toplevel, result_cache, max_timeout); var timeout = Math.min(100 * unminified.elapsed, max_timeout); var minified_result = run_code(minified.code, toplevel, result_cache, timeout).result; if (sandbox.same_stdout(unminified.result, minified_result)) { return is_timed_out(unminified.result) && is_timed_out(minified_result) && { timed_out: true, }; } return { unminified_result: unminified.result, minified_result: minified_result, elapsed: unminified.elapsed, }; } function test_minify(code, minify_options) { var minified = U.minify(code, minify_options); return minified.error && { minified_result: minified.error, }; }