Статьи

Maven: запустить внешний процесс, не блокируя вашу сборку

Давайте предположим, что мы должны выполнить кучу приемочных тестов с BDD-инфраструктурой, такой как Cucumber, как часть сборки Maven .

Использование Maven Failsafe Plugin не сложно. Но у него есть неявное требование : контейнер, в котором находится реализация, которую мы собираемся протестировать, должен быть уже запущен .

Многие контейнеры, такие как Jetty или JBoss, предоставляют свои собственные плагины Maven , чтобы позволить запускать сервер как часть работы Maven. И есть также хороший общий плагин Maven Cargo, который предлагает реализацию того же поведения для многих различных контейнеров.

Эти плагины позволяют, например, запускать сервер в начале задания Maven, развертывать реализацию, которую вы хотите протестировать, запускать тесты и останавливать сервер в конце.

Все механизмы, которые я описал, работают, и они обычно очень полезны для различных подходов к тестированию.

К сожалению, я не могу применить это решение, если мой контейнер не является поддерживаемым контейнером. Если не очевидно, я решил написать собственный плагин или добавить поддержку для моего конкретного контейнера в Maven Cargo.

В моем конкретном случае мне нужно было найти способ использовать JBoss Fuse от Red Hat, контейнер на базе Karaf .

Я решил постараться сохранить его легким и не писать полнофункциональный плагин Maven и в конечном итоге положиться на плагин GMaven , или как я недавно прочитал в интернете «Gradle для бедных» .

GMaven — это в основном плагин для добавления поддержки Groovy к вашей работе в Maven, позволяющий вам выполнять фрагменты Groovy как часть вашей работы . Мне это нравится, потому что это позволяет мне pom.xml скрипты прямо в pom.xml .

Он также позволяет вам определять ваш скрипт в отдельном файле и выполнять его, но это то же самое поведение, которого вы могли бы достичь с помощью простого Java и плагина Maven Exec ; решение, которое мне не очень нравится, потому что скрывает реализацию и усложняет представление о том, чего пытается достичь полная сборка.

Очевидно, что этот подход имеет смысл, если сценарий, который вы собираетесь написать, достаточно прост, чтобы быть автоописательным.

  Я опишу свое решение, начав с того, что поделюсь с вами своими пробами и ошибками и ссылками на различные статьи и посты, которые я нашел:

Сначала я решил использовать Maven Exec Plugin для непосредственного запуска моего контейнера. Что-то вроде того, что было предложено здесь http://stackoverflow.com/questions/3491937/i-want-to-execute-shell-commands-from-mavens-pom-xml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<plugin>
   <groupid>org.codehaus.mojo</groupid>
   <artifactid>exec-maven-plugin</artifactid>
   <version>1.1.1</version>
   <executions>
     <execution>
       <id>some-execution</id>
       <phase>compile</phase>
       <goals>
         <goal>exec</goal>
       </goals>
     </execution>
   </executions>
   <configuration>
     <executable>hostname</executable>
   </configuration>
 </plugin>

Этот вызов плагина, как часть задания Maven, фактически позволяет мне запускать контейнер , но у него есть огромный недостаток: жизненный цикл Maven останавливается до тех пор, пока внешний процесс не завершится или не будет остановлен вручную.

Это связано с тем, что выполнение внешнего процесса является «синхронным», а Maven не считает выполнение команды завершенным, поэтому он никогда не выполняется с остальными инструкциями по сборке.

Это не то, что мне было нужно, поэтому я искал что-то другое.

Сначала я нашел это предложение, чтобы запустить фоновый процесс, чтобы позволить Maven не блокировать: http://mojo.10943.n7.nabble.com/exec-maven-plugin-How-to-start-a-background-process -с-Exec-Exec-td36097.html

Идея здесь состоит в том, чтобы выполнить сценарий оболочки, который запускает фоновый процесс и немедленно возвращается .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
<plugin>
  <groupid>org.codehaus.mojo</groupid>
  <artifactid>exec-maven-plugin</artifactid>
  <version>1.2.1</version>
  <executions>
    <execution>
      <id>start-server</id>
      <phase>pre-integration-test</phase>
      <goals>
        <goal>exec</goal>
      </goals>
      <configuration>
        <executable>src/test/scripts/run.sh</executable>
        <arguments>
          <argument>{server.home}/bin/server</argument>
        </arguments>
      </configuration>
    </execution>
  </executions>
</plugin>

и сценарий

1
2
3
#! /bin/sh
$* > /dev/null 2>&1 &
exit 0

Этот подход действительно работает . Моя сборка Maven не останавливается, и выполняются следующие шаги жизненного цикла. Но у меня сейчас новая проблема.

Мои следующие шаги немедленно выполняются.

У меня нет возможности вызвать продолжение только после того, как мой контейнер запущен и работает. Просматривая немного больше, я нашел эту хорошую статью: http://avianey.blogspot.co.uk/2012/12/maven-it-case-background-process.html Статья, очень хорошо написанная, кажется, описывает точно мою сценарий. Это также применимо к моему точному контексту, пытаясь создать вкус Карафа. Для запуска процесса в фоновом режиме используется другой подход — плагин Antrun Maven . Я попробовал и, к несчастью, нахожусь в той же ситуации, что и раньше . Интеграционные тесты выполняются сразу после запроса на запуск контейнера, но до того, как контейнер будет готов .

Убедившись, что я не могу найти никакого готового решения, я решил взломать текущее с помощью какого-то императивного кода.

Я подумал, что мог бы вставить «сценарий ожидания» после запуска запроса, но до запуска интеграционного теста, который мог бы проверить условие, которое гарантирует мне, что контейнер доступен.

Итак, если контейнер запущен на этом этапе:

1
pre-integration-test

и мои приемочные испытания начались в течение ближайшего

1
integration-test

Я могу вставить некоторую логику в pre-integration-test который продолжает опрашивать мой контейнер и который возвращается только после того, как контейнер «считается» доступным.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
import static com.jayway.restassured.RestAssured.*;
println("Wait for FUSE to be available")
for(int i = 0; i < 30; i++) {
    try{
        def response = with().get("http://localhost:8383/hawtio")
        def status = response.getStatusLine()
        println(status)
        } catch(Exception e){
            Thread.sleep(1000)
            continue
        }finally{
            print(".")
        }
        if( !(status ==~ /.*OK.*/) )
            Thread.sleep(1000)
 
}

И выполняется этим экземпляром GMaven:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<plugin>
    <groupid>org.codehaus.gmaven</groupid>
    <artifactid>gmaven-plugin</artifactid>
    <configuration>
        <providerselection>1.8</providerselection>
    </configuration>
    <executions>
        <execution>
            <id>########### wait for FUSE to be available ############</id>
            <phase>pre-integration-test</phase>
            <goals>
                <goal>execute</goal>
            </goals>
            <configuration>
                <source><![CDATA[
                            import static com.jayway.restassured.RestAssured.*;
                            ...
                            }
                            ]]>
            </configuration>
        </execution>
    </executions>
</plugin>

Мой (некрасивый) скрипт использует логику « Успокоение» и логику на основе исключений, чтобы в течение 30 секунд проверять, будет ли доступен веб-ресурс, который, как я знаю, развертывает мой контейнер.

Эта проверка не так надежна, как хотелось бы , поскольку она проверяет конкретный ресурс, но необязательно подтверждение того, что весь процесс развертывания завершен. В конце концов, лучшим решением было бы использование некоторого API-интерфейса управления, который мог бы проверять состояние контейнера, но, честно говоря, я не знаю, выставлены ли они Карафом, и моей простой проверки было достаточно для моего случая ограниченного использования.

С вызовом GMaven, теперь моя сборка maven ведет себя так, как я хотел.

В этом посте был показан способ обогатить ваш скрипт Maven некоторой программной логикой без необходимости написания полнофункционального плагина Maven. Поскольку у вас есть полный доступ к контексту Groovy, вы можете подумать, что можете выполнить любую задачу, которая может оказаться полезной. Например, вы также можете запустить фоновые потоки , которые позволят жизненному циклу Maven развиваться, продолжая выполнять вашу логику.

Мое последнее предложение — постараться сохранить логику в ваших скриптах простой и не превращать их в длинные и сложные программы. Читаемость была причиной, по которой я решил использовать уверенность вместо прямого доступа к Apache HttpClient .

Это образец полного pom.xml

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
<!--
======== Start FUSE ================================================================= -->
    <modelversion>4.0.0</modelversion>
    <name>${groupId}.${artifactId}</name>
    <parent>
        <groupid>xxxxxxx</groupid>
        <artifactid>esb</artifactid>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <artifactid>acceptance</artifactid>
    <properties>
        <fuse .home="">/data/software/RedHat/FUSE/fuse_full/jboss-fuse-6.0.0.redhat-024/bin/</fuse>
    </properties>
    <build>
        <plugins>
            <plugin>
                <artifactid>maven-failsafe-plugin</artifactid>
                <version>2.12.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupid>org.apache.maven.plugins</groupid>
                <artifactid>maven-surefire-plugin</artifactid>
                <configuration>
                    <excludes>
                        <exclude>**/*Test*.java</exclude>
                    </excludes>
                </configuration>
                <executions>
                    <execution>
                        <id>integration-test</id>
                        <goals>
                            <goal>test</goal>
                        </goals>
                        <phase>integration-test</phase>
                        <configuration>
                            <excludes>
                                <exclude>none</exclude>
                            </excludes>
                            <includes>
                                <include>**/RunCucumberTests.java</include>
                            </includes>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactid>maven-antrun-plugin</artifactid>
                <version>1.6</version>
                <executions>
                    <execution>
                        <id>############## start-fuse ################</id>
                        <phase>pre-integration-test</phase>
                        <configuration>
                            <target>
                                <exec dir="${fuse.home}" executable="${fuse.home}/start" spawn="true">
                    </exec>
                            </target>
                        </configuration>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactid>maven-antrun-plugin</artifactid>
                <version>1.6</version>
                <executions>
                    <execution>
                        <id>############## stop-fuse ################</id>
                        <phase>post-integration-test</phase>
                        <configuration>
                            <target>
                                <exec dir="${fuse.home}" executable="${fuse.home}/stop" spawn="true">
                    </exec>
                            </target>
                        </configuration>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupid>org.codehaus.gmaven</groupid>
                <artifactid>gmaven-plugin</artifactid>
                <configuration>
                    <providerselection>1.8</providerselection>
                </configuration>
                <executions>
                    <execution>
                        <id>########### wait for FUSE to be available ############</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>execute</goal>
                        </goals>
                        <configuration>
                            <source><![CDATA[
import static com.jayway.restassured.RestAssured.*;
println("Wait for FUSE to be available")
for(int i = 0; i < 30; i++) {
    try{
        def response = with().get("http://localhost:8383/hawtio")
        def status = response.getStatusLine()
        println(status)
        } catch(Exception e){
            Thread.sleep(1000)
            continue
        }finally{
            print(".")
        }
        if( !(status ==~ /.*OK.*/) )
            Thread.sleep(1000)
  
}
]]>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <!-- -->
        </plugins>
    </build>
    <dependencies>
        <!-- -->
        <dependency>
            <groupid>info.cukes</groupid>
            <artifactid>cucumber-java</artifactid>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupid>info.cukes</groupid>
            <artifactid>cucumber-picocontainer</artifactid>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupid>info.cukes</groupid>
            <artifactid>cucumber-junit</artifactid>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupid>junit</groupid>
            <artifactid>junit</artifactid>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <!-- groovy script dependencies -->
        <dependency>
            <groupid>org.apache.httpcomponents</groupid>
            <artifactid>httpclient</artifactid>
            <version>4.2.5</version>
        </dependency>
        <dependency>
            <groupid>com.jayway.restassured</groupid>
            <artifactid>rest-assured</artifactid>
            <version>1.8.1</version>
        </dependency>
    </dependencies>
</project>