• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LearnLib / learnlib / 16696694729

02 Aug 2025 06:52PM UTC coverage: 94.289% (-0.09%) from 94.383%
16696694729

push

github

mtf90
add CLI membership oracles

closes #149

111 of 131 new or added lines in 4 files covered. (84.73%)

12630 of 13395 relevant lines covered (94.29%)

1.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.33
/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/CLIOracle.java
1
/* Copyright (C) 2013-2025 TU Dortmund University
2
 * This file is part of LearnLib <https://learnlib.de>.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package de.learnlib.oracle.membership;
17

18
import java.io.IOException;
19
import java.util.List;
20
import java.util.Objects;
21

22
import de.learnlib.oracle.SingleQueryOracle;
23
import net.automatalib.common.util.process.ProcessUtil;
24
import net.automatalib.word.Word;
25
import org.checkerframework.checker.nullness.qual.Nullable;
26
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
27
import org.slf4j.Logger;
28
import org.slf4j.LoggerFactory;
29

30
/**
31
 * An oracle that delegates its queries to an external program via the command-line interface. Acceptance of the queries
32
 * is determined based on the program's return code where {@code 0} indicates success and any other value indicates
33
 * failure.
34
 * <p>
35
 * Queries are translated to program arguments (via the symbol's {@link #toString()} method). Depending on whether a
36
 * {@code reset} symbol has been specified, this oracle assumes either a stateless ({@code reset == null}) or stateful
37
 * ({@code reset != null}) communication.
38
 * <p>
39
 * In a stateless communication, all symbols of a query are passed to the program at once and invocations should be
40
 * treated independently from each other. In a stateful communication, the program is executed multiple times with a
41
 * single query symbol each, preceded by a single invocation with only the {@code reset} symbol. The exit code of the
42
 * last invocation determines the query response.
43
 *
44
 * @param <I>
45
 *         input symbol type
46
 */
47
public class CLIOracle<I> implements SingleQueryOracle<I, Boolean> {
48

49
    private static final Logger LOGGER = LoggerFactory.getLogger(CLIOracle.class);
2✔
50

51
    private final List<String> commandLine;
52
    private final @Nullable String reset;
53

54
    /**
55
     * Constructor. Does not set a {@code reset} symbol.
56
     *
57
     * @param commandLine
58
     *         the command line, containing the main binary and potential additional arguments
59
     *
60
     * @see #CLIOracle(List, String)
61
     */
62
    public CLIOracle(List<String> commandLine) {
63
        this(commandLine, null);
2✔
64
    }
2✔
65

66
    /**
67
     * Constructor.
68
     *
69
     * @param commandLine
70
     *         the command line, containing the main binary and potential additional arguments
71
     * @param reset
72
     *         the symbol passed to the program to indicate a reset
73
     */
74
    public CLIOracle(List<String> commandLine, @Nullable String reset) {
2✔
75
        this.commandLine = commandLine;
2✔
76
        this.reset = reset;
2✔
77
    }
2✔
78

79
    @Override
80
    public Boolean answerQuery(Word<I> prefix, Word<I> suffix) {
81
        return reset == null ? answerStatelessQuery(prefix, suffix) : answerStatefulQuery(prefix, suffix);
2✔
82
    }
83

84
    @SuppressWarnings("toarray.nullable.elements") // we make sure fo fill the remaining elements with non-null values
85
    private boolean answerStatelessQuery(Word<I> prefix, Word<I> suffix) {
86
        final String[] args = new String[this.commandLine.size() + prefix.size() + suffix.size()];
2✔
87
        int idx = this.commandLine.size();
2✔
88

89
        this.commandLine.toArray(args);
2✔
90

91
        for (I p : prefix) {
2✔
92
            args[idx++] = Objects.toString(p);
2✔
93
        }
2✔
94

95
        for (I s : suffix) {
2✔
96
            args[idx++] = Objects.toString(s);
2✔
97
        }
2✔
98

99
        try {
100
            return ProcessUtil.invokeProcess(args, LOGGER::debug, LOGGER::warn) == 0;
2✔
NEW
101
        } catch (IOException | InterruptedException e) {
×
NEW
102
            LOGGER.warn("Error while invoking process", e);
×
NEW
103
            return false;
×
104
        }
105
    }
106

107
    @RequiresNonNull("this.reset")
108
    private boolean answerStatefulQuery(Word<I> prefix, Word<I> suffix) {
109
        try {
110
            int returnCode = ProcessUtil.invokeProcess(toCommand(commandLine, reset), LOGGER::debug, LOGGER::warn);
2✔
111

112
            for (I p : prefix) {
2✔
113
                returnCode = ProcessUtil.invokeProcess(toCommand(commandLine, p), LOGGER::debug, LOGGER::warn);
2✔
114
            }
2✔
115

116
            for (I s : suffix) {
2✔
117
                returnCode = ProcessUtil.invokeProcess(toCommand(commandLine, s), LOGGER::debug, LOGGER::warn);
2✔
118
            }
2✔
119

120
            return returnCode == 0;
2✔
NEW
121
        } catch (IOException | InterruptedException e) {
×
NEW
122
            LOGGER.warn("Error while invoking process", e);
×
NEW
123
            return false;
×
124
        }
125
    }
126

127
    @SuppressWarnings("toarray.nullable.elements") // we make sure fo fill the remaining elements with non-null values
128
    static <T> String[] toCommand(List<String> args, T arg) {
129
        final String[] result = new String[args.size() + 1];
2✔
130

131
        args.toArray(result);
2✔
132
        result[args.size()] = Objects.toString(arg);
2✔
133

134
        return result;
2✔
135
    }
136
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc