Project

General

Profile

« Previous | Next » 

Revision 87eea6d6

Added by Andreas Kohlbecker over 3 years ago

ref #9188 more robust and efficient DrushExecuter

View differences:

src/main/java/eu/etaxonomy/dataportal/DataPortalContext.java
13 13
import java.net.URI;
14 14

  
15 15
import eu.etaxonomy.drush.DrushExecuter;
16
import eu.etaxonomy.drush.DrushExecutionFailure;
16 17

  
17 18
/**
18 19
 *
......
55 56
        return pageHeader + " | " + getSiteName();
56 57
    }
57 58

  
58
    public DrushExecuter drushExecuter() throws IOException, InterruptedException {
59
    public DrushExecuter drushExecuter() throws IOException, InterruptedException, DrushExecutionFailure {
59 60
        DrushExecuter dex = new DrushExecuter();
60 61
        dex.setDrupalRoot(drupalRoot);
61 62
        dex.setSiteURI(siteUri);
src/main/java/eu/etaxonomy/dataportal/junit/CdmDataPortalTestBase.java
21 21
import eu.etaxonomy.dataportal.DrupalVars;
22 22
import eu.etaxonomy.dataportal.selenium.WebDriverFactory;
23 23
import eu.etaxonomy.drush.DrushExecuter;
24
import eu.etaxonomy.drush.DrushExecutionFailure;
24 25

  
25 26
/**
26 27
 * @author a.kohlbecker
......
71 72
	}
72 73

  
73 74
	@After
74
    public void resetToOriginalState() throws IOException, InterruptedException {
75
    public void resetToOriginalState() throws IOException, InterruptedException, DrushExecutionFailure {
75 76
        restoreOriginalVars();
76 77
    }
77 78

  
......
86 87
     *
87 88
     * @throws IOException
88 89
     * @throws InterruptedException
90
     * @throws DrushExecutionFailure
89 91
     */
90
    protected void setDrupalVar(String varKey, String varValue) throws IOException, InterruptedException {
92
    protected void setDrupalVar(String varKey, String varValue) throws IOException, InterruptedException, DrushExecutionFailure {
91 93
        DrushExecuter dex = getContext().drushExecuter();
92 94
        List<Object> result = dex.execute(DrushExecuter.variableGet, varKey);
93 95
        assertEquals(1, result.size());
......
96 98
            drupalVarsBeforeTest.put(varKey, result.get(0));
97 99
        }
98 100
        result = dex.execute(DrushExecuter.variableSet, varKey, varValue);
99
        assertEquals("success", result.get(1));
100 101
    }
101 102

  
102
    protected void restoreOriginalVars() throws IOException, InterruptedException {
103
    protected void restoreOriginalVars() throws IOException, InterruptedException, DrushExecutionFailure {
103 104
        DrushExecuter dex = getContext().drushExecuter();
104 105
        boolean fail = false;
105 106
        for(String varKey : drupalVarsBeforeTest.keySet()) {
106 107
            try {
107 108
                List<Object> result = dex.execute(DrushExecuter.variableSet, varKey, drupalVarsBeforeTest.get(varKey).toString());
108
                assertEquals("success", result.get(1));
109 109
            } catch (Exception e) {
110 110
                logger.error("FATAL ERROR: Restoring the original drupal variable " + varKey + " = " + drupalVarsBeforeTest.get(varKey) + " failed.", e);
111 111
                fail = true;
src/main/java/eu/etaxonomy/drush/DrushExecuter.java
19 19
import java.util.Scanner;
20 20
import java.util.regex.Matcher;
21 21
import java.util.regex.Pattern;
22
import java.util.stream.Collectors;
22 23

  
23 24
import org.apache.commons.io.IOUtils;
24 25
import org.apache.commons.lang3.SystemUtils;
......
53 54

  
54 55
    private String sshHost = null;
55 56

  
56
    public DrushExecuter() throws IOException, InterruptedException {
57
    public DrushExecuter() throws IOException, InterruptedException, DrushExecutionFailure {
57 58
        findDrushCommand();
58 59
    }
59 60

  
60 61
    /**
61 62
     * The execution of this command via
62
     * <code>DrushExecuter.execute({@linkplain DrushCommand#version})</code> results in
63
     * <code>DrushExecuter.execute({@linkplain #version})</code> results in
63 64
     * a {@code List<String>} return variable with the following elements:
64 65
     *
65 66
     * <ol>
......
79 80
     * Executes {@code drush vget --exact <variable-key>}
80 81
     * <p>
81 82
     * The execution of this command via
82
     * <code>DrushExecuter.execute({@linkplain DrushCommand#variableSet})</code> results in
83
     * <code>DrushExecuter.execute({@linkplain DrushExecuter#variableSet})</code> results in
83 84
     * a {@code List<String>} return variable with the following elements:
84 85
     *
85 86
     * <ol>
......
88 89
     * <li>value</li>
89 90
     * <ol>
90 91
     */
91
    public static DrushCommand variableGet = new DrushCommand(Arrays.asList("vget", "--exact", "--format=json", "%s"));
92
    public static DrushCommand variableGet = new DrushCommand(Arrays.asList("vget", "--exact", "--format=json", "%s"), true);
92 93
    /**
93 94
     * Executes {@code drush vset --exact <variable-key> <variable-value>}
94 95
     * <p>
95 96
     * The execution of this command via
96
     * <code>DrushExecuter.execute({@linkplain DrushCommand#variableSet})</code> results in
97
     * a {@code List<String>} return variable with the following elements:
98
     *
99
     * <ol>
100
     * <li>value</li>
101
     * <li>status</li>
102
     * <ol>
97
     * <code>DrushExecuter.execute({@linkplain DrushExecuter#variableSet})</code> will not return any values.
98
     * The command will fail with an {@link DrushExecutionFailure} if setting the variable was not successful.
103 99
     */
104
    public static DrushCommand variableSet = new DrushCommand(Arrays.asList("--yes", "vset", "%s", "%s"), null,
105
            ".*set\\sto*(.*)\\..*\\[(\\w+)\\]"
106
            );
100
    public static DrushCommand variableSet = new DrushCommand(Arrays.asList("--yes", "vset", "%s", "%s"), false);
107 101

  
108 102
    /**
109 103
     * @throws IOException
110 104
     *             if an I/O error occurs in the ProcessBuilder
111 105
     * @throws InterruptedException
112 106
     *             if the Process was interrupted
107
     * @throws DrushExecutionFailure
108
     *              if the drush command execution fails with an error code
113 109
     */
114
    private void findDrushCommand() throws IOException, InterruptedException {
110
    private void findDrushCommand() throws IOException, InterruptedException, DrushExecutionFailure {
115 111

  
116 112
        if (SystemUtils.IS_OS_WINDOWS) {
117 113
            throw new RuntimeException("not yet implmented for Windows");
118 114
        }
119

  
120
        List<Object> matches = execute(version);
121
        assert !((String) matches.get(0)).isEmpty() : "No suitable drush command found in the system";
122
        String majorVersion = (String) matches.get(0);
123
        if (Integer.valueOf(majorVersion) < 8) {
115
        if(DrushCommand.majorVersion == null) {
116
            List<Object> matches = execute(version);
117
            DrushCommand.majorVersion = (String) matches.get(0);
118
            DrushCommand.minorVersion = (String) matches.get(1);
119
            DrushCommand.patchLevel = (String) matches.get(2);
120
        }
121
        if(DrushCommand.majorVersion.isEmpty()) {
122
            throw new RuntimeException("No suitable drush command found in the system");
123
        }
124
        if (Integer.valueOf(DrushCommand.majorVersion) < 8) {
124 125
            throw new RuntimeException("drush version >= 8 required");
125 126
        }
127
    }
126 128

  
129
    public String drushVersion() {
130
        return DrushCommand.majorVersion + "." + DrushCommand.minorVersion + "." + DrushCommand.patchLevel;
127 131
    }
128 132

  
129 133
    /**
......
131 135
     *             if an I/O error occurs in the ProcessBuilder
132 136
     * @throws InterruptedException
133 137
     *             if the Process was interrupted
138
     * @throws DrushExecutionFailure
139
     *              if the drush command execution fails with an error code
134 140
     */
135
    public List<Object> execute(DrushCommand cmd, String... value) throws IOException, InterruptedException {
141
    public List<Object> execute(DrushCommand cmd, String... value) throws IOException, InterruptedException, DrushExecutionFailure {
136 142

  
137 143
        List<String> executableWithArgs = new ArrayList<>();
138 144

  
......
186 192
                logger.error(error);
187 193
            }
188 194
        } else {
189
            throw new RuntimeException(IOUtils.toString(process.getErrorStream()));
195
            throw new DrushExecutionFailure(
196
                    executableWithArgs,
197
                    IOUtils.toString(process.getInputStream()),
198
                    IOUtils.toString(process.getErrorStream())
199
                    );
190 200
        }
191 201
        return matches;
192 202
    }
......
200 210
                if (out == null) {
201 211
                    break;
202 212
                }
203
                if (out != null) {
204
                    Matcher m = regex.matcher(out);
205
                    int patternMatchCount = 0;
206
                    while (m.find()) {
207
                        patternMatchCount++;
208
                        if (m.groupCount() > 0) {
209
                            for (int g = 1; g <= m.groupCount(); g++) {
210
                                matches.add(m.group(g));
211
                                logger.debug("match[" + patternMatchCount + "." + g + "]: " + m.group(g));
212
                            }
213
                        } else {
214
                            matches.add(m.group(0));
215
                            logger.debug("entire pattern match[" + patternMatchCount + ".0]: " + m.group(0));
213
                Matcher m = regex.matcher(out);
214
                int patternMatchCount = 0;
215
                while (m.find()) {
216
                    patternMatchCount++;
217
                    if (m.groupCount() > 0) {
218
                        for (int g = 1; g <= m.groupCount(); g++) {
219
                            matches.add(m.group(g));
220
                            logger.debug("match[" + patternMatchCount + "." + g + "]: " + m.group(g));
216 221
                        }
222
                    } else {
223
                        matches.add(m.group(0));
224
                        logger.debug("entire pattern match[" + patternMatchCount + ".0]: " + m.group(0));
217 225
                    }
218 226
                }
219 227
            }
......
227 235
    }
228 236

  
229 237
    /**
230
     * @param matches
231
     * @param stream
232 238
     * @return depending on the drupal variable type different return types are possible:
233 239
     *  <ul>
234 240
     *  <li>Object</li>
......
238 244
     *  <li>Double</li>
239 245
     *  <li>Integer</li>
240 246
     *  </ul>
241
     *
242
     * @throws IOException
243 247
     */
244 248
    protected String readExecutionResponse(List<Object> matches, InputStream stream) throws IOException {
245 249
        String out = IOUtils.toString(stream);
......
265 269

  
266 270
    public static class DrushCommand {
267 271

  
272
        private static String majorVersion;
273
        private static String minorVersion;
274
        private static String patchLevel;
268 275
        Pattern outRegex;
269 276
        Pattern errRegex;
270 277
        boolean jsonResult = false;
278
        boolean failOnError = false;
271 279
        List<String> args = new ArrayList<>();
272 280

  
281
        /**
282
         * For drush commands not supporting output formatting.
283
         *
284
         * @param args
285
         *            the command arguments
286
         * @param outRegex
287
         *            Regular expression to parse the error stream, capture
288
         *            groups will be put into the <code>List</code> of strings
289
         *            returned by
290
         *            {@link DrushExecuter#execute(DrushCommand, String...)}
291
         * @param errRegex
292
         *            Regular expression to parse the error stream, capture
293
         *            groups will be put into the <code>List</code> of strings
294
         *            returned by
295
         *            {@link DrushExecuter#execute(DrushCommand, String...)}
296
         */
273 297
        public DrushCommand(List<String> args, String outRegex, String errRegex) {
274 298
            this.args = args;
275 299
            if (outRegex != null) {
......
281 305
        }
282 306

  
283 307
        /**
284
         * For drush commands suopporting the {@code --format=json} option.
308
         * For drush commands which don't require return value parsing by regex or
309
         * which support the {@code --format=json} option to return structured data.
310
         *
285 311
         * @param args
312
         *            the command arguments
286 313
         */
287
        public DrushCommand(List<String> args) {
314
        public DrushCommand(List<String> args, boolean jsonResult) {
288 315
            this.args = args;
289
            this.jsonResult = true;
316
            this.jsonResult = jsonResult;
317
        }
318

  
319
        public String commandLineString() {
320
            return args.stream().collect(Collectors.joining(" "));
290 321
        }
322

  
291 323
    }
292 324

  
293 325
    /**
......
295 327
     * it too much dependent from the local environment. Once the
296 328
     * <code>DrushExecuter</code> is being used in the selenium test suite will
297 329
     * be tested implicitly anyway.
330
     *
331
     * @throws DrushExecutionFailure
332
     *              if the drush command execution fails with an error code
298 333
     */
299
    public static void main(String[] args) throws URISyntaxException {
334
    public static void main(String[] args) throws URISyntaxException, DrushExecutionFailure {
300 335
        DrushExecuter.logger.setLevel(Level.DEBUG);
301 336
        try {
302 337
            DrushExecuter dex = new DrushExecuter();
......
307 342
//            dex.execute(help);
308 343
            results = dex.execute(variableSet, "cdm_webservice_url",
309 344
                    "http://api.cybertaxonomy.org/cyprus/");
345
            results = dex.execute(variableGet, "cdm_webservice_url");
310 346
            if (!results.get(0).equals("http://api.cybertaxonomy.org/cyprus/")) {
311 347
                throw new RuntimeException("unexpected result item 0: " + results.get(0));
312 348
            }
313
            if (!results.get(1).equals("success")) {
314
                throw new RuntimeException("unexpected result item 1: " + results.get(0));
349
            // test for command failure:
350
            DrushExecutionFailure expectedFailure = null;
351
            try {
352
                dex.setDrupalRoot(new File("/home/andreas/workspaces/www/invalid-folder"));
353
                results = dex.execute(variableGet, "cdm_webservice_url");
354
            } catch(DrushExecutionFailure e) {
355
                expectedFailure = e;
315 356
            }
316
            results = dex.execute(variableGet, "cdm_webservice_url");
317
            if (!results.get(0).equals("http://api.cybertaxonomy.org/cyprus/")) {
318
                throw new RuntimeException("unexpected result item 0: " + results.get(0));
357
            if(expectedFailure == null) {
358
                throw new AssertionError("DrushExecutionFailure expected due to command failure");
359
            } else {
360
                logger.debug("invalid command has failed as expected");
319 361
            }
320 362
            // testing remote execution via ssh
321 363
            dex.sshHost = "edit-int";
src/main/java/eu/etaxonomy/drush/DrushExecutionFailure.java
1
/**
2
* Copyright (C) 2020 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.drush;
10

  
11
import java.util.List;
12
import java.util.stream.Collectors;
13

  
14
/**
15
 * @author a.kohlbecker
16
 * @since Aug 14, 2020
17
 */
18
public class DrushExecutionFailure extends Exception {
19

  
20
    String out, error, commandLineString;
21

  
22
    /**
23
     * @param cmd
24
     * @param out
25
     * @param error
26
     * @param matches
27
     */
28
    public DrushExecutionFailure(List<String> cmd, String out, String error) {
29
        this.out = out;
30
        this.error = error;
31
        this.commandLineString = cmd.stream().collect(Collectors.joining(" "));
32
    }
33

  
34
    @Override
35
    public String getMessage() {
36
        return "'" + commandLineString + "' failed with '" + error + "'";
37

  
38
    }
39

  
40
    @Override
41
    public String getLocalizedMessage() {
42
        return getMessage();
43

  
44
    }
45

  
46

  
47

  
48
    private static final long serialVersionUID = -4840009591742936935L;
49

  
50
}
src/test/java/eu/etaxonomy/dataportal/selenium/tests/reference/SpecimensTopDownViewTest.java
29 29
import eu.etaxonomy.dataportal.junit.DataPortalContextSuite.DataPortalContexts;
30 30
import eu.etaxonomy.dataportal.pages.TaxonPage;
31 31
import eu.etaxonomy.drush.DrushExecuter;
32
import eu.etaxonomy.drush.DrushExecutionFailure;
32 33

  
33 34
/**
34 35
 * @author a.kohlbecker
......
40 41
    private static final UUID glenodinium_apiculatum_t = UUID.fromString("d245083e-3bda-435f-9bb3-bdc2249ff23c");
41 42

  
42 43
    @Before
43
    public void switchToView() throws IOException, InterruptedException {
44
    public void switchToView() throws IOException, InterruptedException, DrushExecutionFailure {
44 45
        Logger.getLogger(DrushExecuter.class).setLevel(Level.DEBUG);
45 46
        setDrupalVar(DrupalVars.CDM_DATAPORTAL_TAXONPAGE_TABS, "1");
46 47
        setDrupalVar(DrupalVars.CDM_SPECIMEN_LIST_VIEW_MODE, "derivate_path");

Also available in: Unified diff