aboutsummaryrefslogtreecommitdiffstats
path: root/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/TestProcessFactory.java
blob: 4e831dc286585bb8da9f32bb35965a0c2ca08557 (plain) (blame)
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.task.util.process;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;

/**
 * @author hakonhall
 */
public class TestProcessFactory implements ProcessFactory {
    private static class SpawnCall {
        private final String commandDescription;
        private final Function<CommandLine, ChildProcess2> callback;

        private SpawnCall(String commandDescription,
                          Function<CommandLine, ChildProcess2> callback) {
            this.commandDescription = commandDescription;
            this.callback = callback;
        }
    }
    private final List<SpawnCall> expectedSpawnCalls = new ArrayList<>();
    private final List<CommandLine> spawnCommandLines = new ArrayList<>();

    private boolean muteVerifyAllCommandsExecuted = false;

    /** Forward call to spawn() to callback. */
    public TestProcessFactory interceptSpawn(String commandDescription,
                                             Function<CommandLine, ChildProcess2> callback) {
        expectedSpawnCalls.add(new SpawnCall(commandDescription, callback));
        return this;
    }

    // Convenience method for the caller to avoid having to create a TestChildProcess2 instance.
    public TestProcessFactory expectSpawn(String commandLineString, TestChildProcess2 toReturn) {
        int commandIndex = expectedSpawnCalls.size();
        return interceptSpawn(
                commandLineString,
                commandLine -> defaultSpawn(commandLine, commandLineString, toReturn, commandIndex));
    }

    // Convenience method for the caller to avoid having to create a TestChildProcess2 instance.
    public TestProcessFactory expectSpawn(String commandLine, int exitCode, String output) {
        return expectSpawn(commandLine, new TestChildProcess2(exitCode, output));
    }

    /** Ignore the CommandLine passed to spawn(), just return successfully with the given output. */
    public TestProcessFactory ignoreSpawn(String output) {
        return interceptSpawn(
                "[call index " + expectedSpawnCalls.size() + "]",
                commandLine -> new TestChildProcess2(0, output));
    }

    public TestProcessFactory ignoreSpawn() {
        return ignoreSpawn("");
    }

    public void verifyAllCommandsExecuted() {
        if (muteVerifyAllCommandsExecuted) return;

        if (spawnCommandLines.size() < expectedSpawnCalls.size()) {
            int missingCommandIndex = spawnCommandLines.size();
            throw new IllegalStateException("Command #" + missingCommandIndex +
                    " never executed: " +
                    expectedSpawnCalls.get(missingCommandIndex).commandDescription +
                    "\nExpected commands:\n" + getExpectedCommandLines() +
                    "\nActual commands:\n" + spawnCommandLines);
        }
    }

    /**
     * WARNING: CommandLine is mutable, and e.g. reusing a CommandLine for the next call
     * would make the CommandLine in this list no longer reflect the original CommandLine.
     */
    public List<CommandLine> getMutableCommandLines() {
        return spawnCommandLines;
    }

    @Override
    public ChildProcess2 spawn(CommandLine commandLine) {
        String commandLineString = commandLine.toString(false);
        if (spawnCommandLines.size() + 1 > expectedSpawnCalls.size()) {
            throw new IllegalStateException("Too many invocations: " + commandLineString);
        }
        spawnCommandLines.add(commandLine);

        return expectedSpawnCalls.get(spawnCommandLines.size() - 1).callback.apply(commandLine);
    }

    private ChildProcess2 defaultSpawn(CommandLine commandLine,
                                              String expectedCommandLineString,
                                              ChildProcess2 toReturn,
                                              int commandSequenceNumber) {
        String actualCommandLineString = commandLine.toString(false);
        if (!Objects.equals(actualCommandLineString, expectedCommandLineString)) {
            muteVerifyAllCommandsExecuted = true;
            throw new IllegalArgumentException("Expected command #" + commandSequenceNumber + " to be: \n" +
                                               "  \"" + expectedCommandLineString + "\"\n" +
                                               "but got:\n" +
                                               "  \"" + actualCommandLineString + "\"");
        }

        return toReturn;
    }

    private List<String> getExpectedCommandLines() {
        return expectedSpawnCalls.stream()
                .map(spawnCall -> spawnCall.commandDescription)
                .toList();
    }

}