• 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

88.24
/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/CLIOutputOracle.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
import java.util.StringJoiner;
22
import java.util.function.BiFunction;
23

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

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

51
    private static final Logger LOGGER = LoggerFactory.getLogger(CLIOutputOracle.class);
2✔
52

53
    private final List<String> commandLine;
54
    private final BiFunction<String, Integer, D> outputTransformer;
55
    private final @Nullable String reset;
56

57
    /**
58
     * Constructor. Does not set a {@code reset} symbol.
59
     *
60
     * @param commandLine
61
     *         the command line, containing the main binary and potential additional arguments
62
     * @param outputTransformer
63
     *         the transformer for the program's output. Receives the full process output (stdin and stderr) as well as
64
     *         the length of the query prefix for properly offsetting potentially {@link Word}-based output types.
65
     *
66
     * @see #CLIOutputOracle(List, BiFunction, String)
67
     */
68
    public CLIOutputOracle(List<String> commandLine, BiFunction<String, Integer, D> outputTransformer) {
69
        this(commandLine, outputTransformer, null);
2✔
70
    }
2✔
71

72
    /**
73
     * Constructor.
74
     *
75
     * @param commandLine
76
     *         the command line, containing the main binary and potential additional arguments
77
     * @param outputTransformer
78
     *         the transformer for the program's output. Receives the full process output (stdin and stderr) as well as
79
     *         the length of the query prefix for properly offsetting potentially {@link Word}-based output types.
80
     * @param reset
81
     *         the symbol passed to the program to indicate a reset
82
     */
83
    public CLIOutputOracle(List<String> commandLine,
84
                           BiFunction<String, Integer, D> outputTransformer,
85
                           @Nullable String reset) {
2✔
86
        this.commandLine = commandLine;
2✔
87
        this.reset = reset;
2✔
88
        this.outputTransformer = outputTransformer;
2✔
89
    }
2✔
90

91
    @Override
92
    public D answerQuery(Word<I> prefix, Word<I> suffix) {
93
        return reset == null ? answerStatelessQuery(prefix, suffix) : answerStatefulQuery(prefix, suffix);
2✔
94
    }
95

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

101
        this.commandLine.toArray(args);
2✔
102

103
        for (I p : prefix) {
2✔
104
            args[idx++] = Objects.toString(p);
2✔
105
        }
2✔
106

107
        for (I s : suffix) {
2✔
108
            args[idx++] = Objects.toString(s);
2✔
109
        }
2✔
110

111
        final StringJoiner sj = new StringJoiner(System.lineSeparator());
2✔
112

113
        try {
114
            ProcessUtil.invokeProcess(args, sj::add, LOGGER::warn);
2✔
115
            return outputTransformer.apply(sj.toString(), prefix.length());
2✔
NEW
116
        } catch (IOException | InterruptedException e) {
×
NEW
117
            throw new IllegalStateException(e);
×
118
        }
119
    }
120

121
    @RequiresNonNull("this.reset")
122
    private D answerStatefulQuery(Word<I> prefix, Word<I> suffix) {
123
        final StringJoiner sj = new StringJoiner(System.lineSeparator());
2✔
124

125
        try {
126
            ProcessUtil.invokeProcess(CLIOracle.toCommand(commandLine, reset), LOGGER::debug, LOGGER::warn);
2✔
127

128
            for (I p : prefix) {
2✔
129
                ProcessUtil.invokeProcess(CLIOracle.toCommand(commandLine, p), sj::add, LOGGER::warn);
2✔
130
            }
2✔
131

132
            for (I s : suffix) {
2✔
133
                ProcessUtil.invokeProcess(CLIOracle.toCommand(commandLine, s), sj::add, LOGGER::warn);
2✔
134
            }
2✔
135

136
            return outputTransformer.apply(sj.toString(), prefix.length());
2✔
NEW
137
        } catch (IOException | InterruptedException e) {
×
NEW
138
            throw new IllegalStateException(e);
×
139
        }
140
    }
141
}
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

© 2025 Coveralls, Inc