├── .gitignore
├── Jenkinsfile
├── README
├── .github
└── workflows
│ └── jenkins-security-scan.yml
├── LICENSE
├── src
├── test
│ ├── resources
│ │ ├── auto
│ │ │ └── Xvfb
│ │ ├── default
│ │ │ └── Xvfb
│ │ ├── working
│ │ │ └── Xvfb
│ │ └── failing
│ │ │ └── Xvfb
│ └── java
│ │ └── org
│ │ └── jenkinsci
│ │ └── plugins
│ │ └── xvfb
│ │ ├── AutoDisplayNameFilterStreamTest.java
│ │ ├── BaseXvfbTest.java
│ │ ├── XvfbBuildWrapperWorkflowTest.java
│ │ ├── XvfbBuildWrapperInstallationDefaultsTest.java
│ │ └── XvfbBuildWrapperTest.java
└── main
│ ├── resources
│ ├── org
│ │ └── jenkinsci
│ │ │ └── plugins
│ │ │ └── xvfb
│ │ │ ├── XvfbInstallation
│ │ │ ├── help-name.html
│ │ │ ├── help-home.html
│ │ │ └── config.jelly
│ │ │ ├── Xvfb
│ │ │ ├── help-installationName.html
│ │ │ ├── help-additionalOptions.html
│ │ │ ├── help-shutdownWithBuild.html
│ │ │ ├── help-debug.html
│ │ │ ├── help-screen.html
│ │ │ ├── help-displayNameOffset.html
│ │ │ ├── help-assignedLabels.html
│ │ │ ├── help-autoDisplayName.html
│ │ │ ├── help-displayName.html
│ │ │ ├── help-timeout.html
│ │ │ ├── help-parallelBuild.html
│ │ │ └── config.jelly
│ │ │ └── Messages.properties
│ └── index.jelly
│ ├── webapp
│ └── help-globalConfig.html
│ └── java
│ └── org
│ └── jenkinsci
│ └── plugins
│ └── xvfb
│ ├── XvfbDisposer.java
│ ├── XvfbEnvironment.java
│ ├── XvfbEnvironmentConverter.java
│ ├── AutoDisplayNameFilterStream.java
│ ├── XvfbInstallation.java
│ └── Xvfb.java
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | /.settings
2 | /.classpath
3 | /.project
4 | target
5 | /work
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env groovy
2 |
3 | /* `buildPlugin` step provided by: https://github.com/jenkins-infra/pipeline-library */
4 | buildPlugin()
5 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | This is a plugin for Jenkins-CI (http://jenkins-ci.org) that starts Xvfb (http://www.x.org/archive/current/doc/man/man1/Xvfb.1.xhtml) with the start of the job, and stops it when the job is done.
2 |
3 |
--------------------------------------------------------------------------------
/.github/workflows/jenkins-security-scan.yml:
--------------------------------------------------------------------------------
1 | name: Jenkins Security Scan
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | types: [ opened, synchronize, reopened ]
9 | workflow_dispatch:
10 |
11 | permissions:
12 | security-events: write
13 | contents: read
14 | actions: read
15 |
16 | jobs:
17 | security-scan:
18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2
19 | with:
20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate.
21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default.
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Zoran Regvart All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions
5 | are met:
6 | 1. Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | 2. Redistributions in binary form must reproduce the above copyright
9 | notice, this list of conditions and the following disclaimer in the
10 | documentation and/or other materials provided with the distribution.
11 |
12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
22 | SUCH DAMAGE.
23 |
--------------------------------------------------------------------------------
/src/test/resources/auto/Xvfb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | #
3 | # Copyright © 2012, Zoran Regvart
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # 1. Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
19 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | #
26 | # The views and conclusions contained in the software and documentation are those
27 | # of the authors and should not be interpreted as representing official policies,
28 | # either expressed or implied, of the FreeBSD Project.
29 | #
30 |
31 | echo 42 >&2
32 | tail -f /dev/null
33 |
--------------------------------------------------------------------------------
/src/test/resources/default/Xvfb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | #
3 | # Copyright © 2012, Zoran Regvart
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # 1. Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
19 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | #
26 | # The views and conclusions contained in the software and documentation are those
27 | # of the authors and should not be interpreted as representing official policies,
28 | # either expressed or implied, of the FreeBSD Project.
29 | #
30 |
31 | echo $*
32 | tail -f /dev/null
33 |
--------------------------------------------------------------------------------
/src/test/resources/working/Xvfb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | #
3 | # Copyright © 2012, Zoran Regvart
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # 1. Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
19 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | #
26 | # The views and conclusions contained in the software and documentation are those
27 | # of the authors and should not be interpreted as representing official policies,
28 | # either expressed or implied, of the FreeBSD Project.
29 | #
30 |
31 | echo $*
32 | tail -f /dev/null
33 |
--------------------------------------------------------------------------------
/src/test/resources/failing/Xvfb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | #
3 | # Copyright © 2012, Zoran Regvart
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # 1. Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
19 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | #
26 | # The views and conclusions contained in the software and documentation are those
27 | # of the authors and should not be interpreted as representing official policies,
28 | # either expressed or implied, of the FreeBSD Project.
29 | #
30 |
31 | echo This Xvfb will fail
32 | exit 1
33 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/XvfbInstallation/help-name.html:
--------------------------------------------------------------------------------
1 |
31 |
Name of the installation.
32 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/Xvfb/help-installationName.html:
--------------------------------------------------------------------------------
1 |
31 | The name of the Xvfb tool installation that Jenkins
32 | administrator set up.
33 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/Xvfb/help-additionalOptions.html:
--------------------------------------------------------------------------------
1 |
31 | Additional options to be added with the options above to the
32 | Xvfb command line.
33 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/Xvfb/help-shutdownWithBuild.html:
--------------------------------------------------------------------------------
1 |
31 | Should the display be kept until the whole job ends
32 | (including the post build steps).
33 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/Xvfb/help-debug.html:
--------------------------------------------------------------------------------
1 |
31 | If Xvfb output should appear in console log of this job,
32 | useful only if debugging Xvfb interaction.
33 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/XvfbInstallation/help-home.html:
--------------------------------------------------------------------------------
1 |
31 | Path to the directory in which Xvfb executable resides, it
32 | can be empty -- then PATH will be consulted.
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/Xvfb/help-screen.html:
--------------------------------------------------------------------------------
1 |
31 |
32 | Resolution and color depth of the created virtual frame buffer in the
33 | format WxHxD. For example:
34 | 1024x758x16
35 |
36 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 | This plugin starts before build, and stops after the build the
34 |
Xvfb
35 | virtual framebuffer X11 server.
36 |
37 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/Xvfb/help-displayNameOffset.html:
--------------------------------------------------------------------------------
1 |
31 | Offset for display names, default is 1. Display names are
32 | taken from build executor’s number, i.e. if the build is performed by
33 | executor 4, and offset is 100, display name will be 104.
34 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/Xvfb/help-assignedLabels.html:
--------------------------------------------------------------------------------
1 |
31 |
32 | If you want to start Xvfb only on specific nodes specify its name or
33 | label. See the Restrict where this project can be run option for
34 | label expressions that you can use.
35 |
36 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/Xvfb/help-autoDisplayName.html:
--------------------------------------------------------------------------------
1 |
31 | Uses the -displayfd option of Xvfb by which it chooses its
32 | own display name by scanning for an available one. This option requires
33 | a recent version of xserver, check your installation for support.
34 |
--------------------------------------------------------------------------------
/src/main/webapp/help-globalConfig.html:
--------------------------------------------------------------------------------
1 |
31 |
32 | This HTML fragment will be injected into the configuration screen
33 | when the user clicks the 'help' icon. See global.jelly for how the
34 | form decides which page to load.
35 | You can have any HTML fragment here.
36 |
37 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/Xvfb/help-displayName.html:
--------------------------------------------------------------------------------
1 |
31 | Ordinal of the display Xvfb will be running on, if left empty
32 | (default) chosen based on current build executor number. Use only
33 | if you know what you’re doing, could lead to clashes with other
34 | builds.
35 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/Xvfb/help-timeout.html:
--------------------------------------------------------------------------------
1 |
31 | A timeout of given seconds to wait before returning control
32 | to the job, this allows Xvfb to start before there is a need for it. By
33 | default set to 0, not to delay the build, since it usually takes just a
34 | few seconds for Xvfb to start, and outputting to display is not the
35 | first thing a job does.
36 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/XvfbInstallation/config.jelly:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/Xvfb/help-parallelBuild.html:
--------------------------------------------------------------------------------
1 |
31 | When running multiple Jenkins nodes on the same machine this
32 | setting influences the display number generation. The display number
33 | will be based upon node position in the list of nodes multiplied by 100
34 | to which current executor number and any given offset will be added.
35 | Using this with offset set to 0 there is a limit of 595 nodes and 35
36 | executors on a node, having more nodes or executors is not compatible
37 | with this option.
38 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/xvfb/XvfbDisposer.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2012, Zoran Regvart
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice, this
9 | * list of conditions and the following disclaimer.
10 | * 2. Redistributions in binary form must reproduce the above copyright notice,
11 | * this list of conditions and the following disclaimer in the documentation
12 | * and/or other materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | *
25 | * The views and conclusions contained in the software and documentation are those
26 | * of the authors and should not be interpreted as representing official policies,
27 | * either expressed or implied, of the FreeBSD Project.
28 | */
29 | package org.jenkinsci.plugins.xvfb;
30 |
31 | import java.io.IOException;
32 |
33 | import hudson.FilePath;
34 | import hudson.Launcher;
35 | import hudson.model.Run;
36 | import hudson.model.TaskListener;
37 | import jenkins.tasks.SimpleBuildWrapper.Disposer;
38 |
39 | public class XvfbDisposer extends Disposer {
40 |
41 | private static final long serialVersionUID = 1L;
42 |
43 | private final XvfbEnvironment xvfb;
44 |
45 | public XvfbDisposer(final XvfbEnvironment xvfb) {
46 | this.xvfb = xvfb;
47 | }
48 |
49 | @Override
50 | public void tearDown(final Run, ?> run, final FilePath workspace, final Launcher launcher, final TaskListener listener) throws IOException, InterruptedException {
51 | if (!xvfb.shutdownWithBuild) {
52 | Xvfb.shutdownAndCleanup(xvfb, launcher, listener);
53 | }
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/xvfb/XvfbEnvironment.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2012, Zoran Regvart
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice, this
9 | * list of conditions and the following disclaimer.
10 | * 2. Redistributions in binary form must reproduce the above copyright notice,
11 | * this list of conditions and the following disclaimer in the documentation
12 | * and/or other materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | *
25 | * The views and conclusions contained in the software and documentation are those
26 | * of the authors and should not be interpreted as representing official policies,
27 | * either expressed or implied, of the FreeBSD Project.
28 | */
29 | package org.jenkinsci.plugins.xvfb;
30 |
31 | import java.io.Serializable;
32 |
33 | import hudson.model.InvisibleAction;
34 |
35 | public class XvfbEnvironment extends InvisibleAction implements Serializable {
36 |
37 | private static final long serialVersionUID = 1L;
38 |
39 | /** Remote path of the frame buffer dir */
40 | public final String frameBufferDir;
41 |
42 | /** Actual display name used */
43 | public final int displayName;
44 |
45 | /** The shutdownWithBuild indicator from the job configuration. */
46 | public final boolean shutdownWithBuild;
47 |
48 | /** Random value identifying the Xvfb process. */
49 | public String cookie;
50 |
51 | public XvfbEnvironment(final String cookie, final String frameBufferDir, final int displayName, final boolean shutdownWithBuild) {
52 | this.cookie = cookie;
53 | this.frameBufferDir = frameBufferDir;
54 | this.displayName = displayName;
55 | this.shutdownWithBuild = shutdownWithBuild;
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/Messages.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright © 2012, Zoran Regvart
3 | # All rights reserved.
4 | #
5 | # Redistribution and use in source and binary forms, with or without
6 | # modification, are permitted provided that the following conditions are met:
7 | #
8 | # 1. Redistributions of source code must retain the above copyright notice, this
9 | # list of conditions and the following disclaimer.
10 | # 2. Redistributions in binary form must reproduce the above copyright notice,
11 | # this list of conditions and the following disclaimer in the documentation
12 | # and/or other materials provided with the distribution.
13 | #
14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | #
25 | # The views and conclusions contained in the software and documentation are those
26 | # of the authors and should not be interpreted as representing official policies,
27 | # either expressed or implied, of the FreeBSD Project.
28 | #
29 |
30 | XvfbBuildWrapper.DisplayName = Start Xvfb before the build, and shut it down after.
31 | XvfbBuildWrapper.NotUnix = System is not Unix, Xvfb will not be started
32 | XvfbBuildWrapper.Starting = Xvfb starting
33 | XvfbBuildWrapper.Stopping = Xvfb stopping
34 | XvfbBuildWrapper.NoInstallationsConfigured = No Xvfb installations defined, please define one in the configuration. Once defined you\u2019ll need to choose one under Advanced options for Xvfb plugin job settings and save job configuration.
35 | XvfbBuildWrapper.FailedToStart = Xvfb failed to start, consult the lines above for errors
36 | XvfbBuildWrapper.KillingZombies = Trying to kill zombie Xvfb process that\u2019s occupying display name: {0} and frame buffer directory: {1}
37 | XvfbBuildWrapper.ZombieSlainFailed = Unable to kill zombie Xvfb process, you\u2019ll need to do your own slaying.
38 | XvfbBuildWrapper.AssignedLabelString.InvalidBooleanExpression = Invalid boolean expression: {0}
39 | XvfbBuildWrapper.AssignedLabelString.NoMatch.DidYouMean = There\u2019s no slave/cloud that matches this assignment. Did you mean \u2018{1}\u2019 instead of \u2018{0}\u2019?
40 | XvfbBuildWrapper.AssignedLabelString.NoMatch = There\u2019s no slave/cloud that matches this assignment
41 | XvfbBuildWrapper.LabelLink = Slaves in label : {2}
42 |
43 | XvfbInstallation.DisplayName = Xvfb installation
44 | XvfbInstallation.HomeNotDirectory = Home path is not a directory: {0}
45 | XvfbInstallation.HomeDoesntContainXvfb = Home path does not contain Xvfb executable: {0}
46 | XvfbInstallation.XvfbIsNotExecutable = Home path contains Xvfb executable but it is not executable: {0}
47 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/xvfb/XvfbEnvironmentConverter.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2012, Zoran Regvart
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice, this
9 | * list of conditions and the following disclaimer.
10 | * 2. Redistributions in binary form must reproduce the above copyright notice,
11 | * this list of conditions and the following disclaimer in the documentation
12 | * and/or other materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | *
25 | * The views and conclusions contained in the software and documentation are those
26 | * of the authors and should not be interpreted as representing official policies,
27 | * either expressed or implied, of the FreeBSD Project.
28 | */
29 | package org.jenkinsci.plugins.xvfb;
30 |
31 | import com.thoughtworks.xstream.converters.Converter;
32 | import com.thoughtworks.xstream.converters.MarshallingContext;
33 | import com.thoughtworks.xstream.converters.UnmarshallingContext;
34 | import com.thoughtworks.xstream.io.HierarchicalStreamReader;
35 | import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
36 |
37 | public class XvfbEnvironmentConverter implements Converter {
38 |
39 | private static final String COOKIE = "cookie";
40 |
41 | private static final String REMOTE_FRAME_BUFFER_DIR_ATTR = "remoteFrameBufferDir";
42 |
43 | private static final String DISPLAY_NAME_USED_ATTR = "displayNameUsed";
44 |
45 | @Override
46 | public boolean canConvert(@SuppressWarnings("rawtypes") final Class type) {
47 | return type != null && XvfbEnvironment.class.isAssignableFrom(type);
48 | }
49 |
50 | @Override
51 | public void marshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) {
52 | final XvfbEnvironment xvfbEnvironment = (XvfbEnvironment) source;
53 |
54 | writer.addAttribute(COOKIE, xvfbEnvironment.cookie);
55 | writer.addAttribute(DISPLAY_NAME_USED_ATTR, String.valueOf(xvfbEnvironment.displayName));
56 | writer.addAttribute(REMOTE_FRAME_BUFFER_DIR_ATTR, xvfbEnvironment.frameBufferDir);
57 | }
58 |
59 | @Override
60 | public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) {
61 | final String cookie = reader.getAttribute(COOKIE);
62 |
63 | final String frameBufferDir = reader.getAttribute(REMOTE_FRAME_BUFFER_DIR_ATTR);
64 |
65 | final int displayNameUsed = Integer.parseInt(reader.getAttribute(DISPLAY_NAME_USED_ATTR));
66 |
67 | final XvfbEnvironment xvfbEnvironment = new XvfbEnvironment(cookie, frameBufferDir, displayNameUsed, false);
68 |
69 | return xvfbEnvironment;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/xvfb/AutoDisplayNameFilterStream.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2012, Zoran Regvart
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice, this
9 | * list of conditions and the following disclaimer.
10 | * 2. Redistributions in binary form must reproduce the above copyright notice,
11 | * this list of conditions and the following disclaimer in the documentation
12 | * and/or other materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | *
25 | * The views and conclusions contained in the software and documentation are those
26 | * of the authors and should not be interpreted as representing official policies,
27 | * either expressed or implied, of the FreeBSD Project.
28 | */
29 | package org.jenkinsci.plugins.xvfb;
30 |
31 | import java.io.FilterOutputStream;
32 | import java.io.IOException;
33 | import java.io.OutputStream;
34 | import java.util.concurrent.Semaphore;
35 | import java.util.concurrent.TimeUnit;
36 |
37 | public class AutoDisplayNameFilterStream extends FilterOutputStream {
38 |
39 | private final Semaphore received = new Semaphore(1);
40 |
41 | private final char[] lastLine = new char[1024];
42 |
43 | private int idx;
44 |
45 | private int displayNumber;
46 |
47 | private final long waitTime;
48 |
49 | protected AutoDisplayNameFilterStream(final OutputStream decorated) {
50 | this(decorated, 30);
51 | }
52 |
53 | protected AutoDisplayNameFilterStream(final OutputStream decorated, final int waitTime) {
54 | super(decorated);
55 |
56 | this.waitTime = waitTime;
57 |
58 | try {
59 | received.acquire();
60 | } catch (final InterruptedException e) {
61 | throw new IllegalStateException("Unable to acquire semaphore upon creation", e);
62 | }
63 |
64 | }
65 |
66 | @Override
67 | public void close() throws IOException {
68 | received.release();
69 | super.close();
70 | }
71 |
72 | public int getDisplayNumber() throws InterruptedException {
73 | if (received.tryAcquire(waitTime, TimeUnit.SECONDS)) {
74 | return displayNumber;
75 | }
76 |
77 | throw new IllegalStateException("No display name received from Xvfb within " + waitTime + " seconds");
78 | }
79 |
80 | @Override
81 | public void write(final int ch) throws IOException {
82 | if (ch == '\n' || ch == '\r') {
83 | if (idx != 0) {
84 | final String line = new String(lastLine, 0, idx);
85 | if (line.matches("\\d+")) {
86 | displayNumber = Integer.parseInt(line);
87 | received.release();
88 | }
89 | }
90 |
91 | idx = 0;
92 | }
93 | else if (idx < lastLine.length - 1) {
94 | lastLine[idx++] = (char) ch;
95 | }
96 |
97 | super.write(ch);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/xvfb/Xvfb/config.jelly:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | ${installation.name}
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 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/xvfb/AutoDisplayNameFilterStreamTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2012, Zoran Regvart
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice, this
9 | * list of conditions and the following disclaimer.
10 | * 2. Redistributions in binary form must reproduce the above copyright notice,
11 | * this list of conditions and the following disclaimer in the documentation
12 | * and/or other materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | *
25 | * The views and conclusions contained in the software and documentation are those
26 | * of the authors and should not be interpreted as representing official policies,
27 | * either expressed or implied, of the FreeBSD Project.
28 | */
29 | package org.jenkinsci.plugins.xvfb;
30 |
31 | import java.io.ByteArrayOutputStream;
32 | import java.io.PrintStream;
33 |
34 | import org.junit.Assert;
35 | import org.junit.Test;
36 |
37 | public class AutoDisplayNameFilterStreamTest {
38 |
39 | @Test
40 | public void shouldParseDisplayNumber() throws InterruptedException {
41 | final ByteArrayOutputStream out = new ByteArrayOutputStream();
42 | final AutoDisplayNameFilterStream stream = new AutoDisplayNameFilterStream(out);
43 |
44 | final PrintStream printer = new PrintStream(stream);
45 |
46 | printer.println("hello world");
47 | printer.println("the display number is next");
48 | printer.println("1234");
49 | printer.println("that was the display number");
50 |
51 | printer.flush();
52 | printer.close();
53 |
54 | Assert.assertEquals("display number should be parsed", 1234, stream.getDisplayNumber());
55 | Assert.assertTrue("should contain all that is written",
56 | new String(out.toByteArray()).matches("hello world[\\n\\r]the display number is next[\\n\\r]1234[\\n\\r]that was the display number[\\n\\r]"));
57 | }
58 |
59 | @Test
60 | public void shouldWaitAndReceiveDisplayNumber() throws InterruptedException {
61 | final ByteArrayOutputStream out = new ByteArrayOutputStream();
62 | final AutoDisplayNameFilterStream stream = new AutoDisplayNameFilterStream(out, 1);
63 |
64 | final PrintStream printer = new PrintStream(stream);
65 |
66 | printer.println("hello world");
67 | printer.println("the display number is next");
68 | printer.println("that was the display number");
69 |
70 | try {
71 | stream.getDisplayNumber();
72 | Assert.fail("No IllegalStateException received!");
73 | } catch (final IllegalStateException ignore) {
74 | }
75 |
76 | printer.println("42");
77 |
78 | Assert.assertEquals("display number should be 42", 42, stream.getDisplayNumber());
79 |
80 | printer.flush();
81 | printer.close();
82 | }
83 |
84 | @Test(expected = IllegalStateException.class)
85 | public void shouldWaitForDisplayNumber() throws InterruptedException {
86 | final ByteArrayOutputStream out = new ByteArrayOutputStream();
87 | final AutoDisplayNameFilterStream stream = new AutoDisplayNameFilterStream(out, 1);
88 |
89 | final PrintStream printer = new PrintStream(stream);
90 |
91 | printer.println("hello world");
92 | printer.println("the display number is next");
93 | printer.println("that was the display number");
94 |
95 | stream.getDisplayNumber();
96 |
97 | printer.flush();
98 | printer.close();
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/xvfb/BaseXvfbTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2012, Zoran Regvart
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice, this
9 | * list of conditions and the following disclaimer.
10 | * 2. Redistributions in binary form must reproduce the above copyright notice,
11 | * this list of conditions and the following disclaimer in the documentation
12 | * and/or other materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | *
25 | * The views and conclusions contained in the software and documentation are those
26 | * of the authors and should not be interpreted as representing official policies,
27 | * either expressed or implied, of the FreeBSD Project.
28 | */
29 | package org.jenkinsci.plugins.xvfb;
30 |
31 | import java.io.File;
32 | import java.io.FileNotFoundException;
33 | import java.io.FileOutputStream;
34 | import java.io.IOException;
35 | import java.io.InputStream;
36 |
37 | import org.junit.rules.TemporaryFolder;
38 | import org.jvnet.hudson.test.JenkinsRule;
39 |
40 | import com.google.common.io.ByteStreams;
41 | import com.google.common.io.Closeables;
42 |
43 | import hudson.DescriptorExtensionList;
44 | import hudson.model.Descriptor;
45 | import hudson.model.FreeStyleBuild;
46 | import hudson.model.FreeStyleProject;
47 | import hudson.tasks.BuildWrapper;
48 | import hudson.tools.ToolInstallation;
49 | import hudson.util.DescribableList;
50 | import jenkins.model.Jenkins;
51 |
52 | public abstract class BaseXvfbTest {
53 |
54 | protected static FreeStyleProject createFreeStyleJob(final JenkinsRule system, final String name) throws IOException {
55 | return system.jenkins.createProject(FreeStyleProject.class, name);
56 | }
57 |
58 | protected static XvfbInstallation createInstallation(final String nameAndPath, final TemporaryFolder tempDir) throws IOException, FileNotFoundException {
59 | final File xvfbDir = tempDir.newFolder(nameAndPath);
60 | final File xvfb = new File(xvfbDir, "Xvfb");
61 |
62 | InputStream xvfbFrom = null;
63 | FileOutputStream xvfbTo = null;
64 | try {
65 | xvfbFrom = XvfbBuildWrapperTest.class.getResourceAsStream("/" + nameAndPath + "/Xvfb");
66 | xvfbTo = new FileOutputStream(xvfb);
67 | ByteStreams.copy(xvfbFrom, xvfbTo);
68 | } finally {
69 | Closeables.closeQuietly(xvfbFrom);
70 | Closeables.closeQuietly(xvfbTo);
71 | }
72 | xvfb.setExecutable(true);
73 |
74 | return new XvfbInstallation(nameAndPath, xvfbDir.getAbsolutePath(), null);
75 | }
76 |
77 | protected static FreeStyleBuild runFreestyleJobWith(final JenkinsRule system, final Xvfb xvfb) throws IOException, Exception {
78 | final FreeStyleProject project = createFreeStyleJob(system, "xvfbFreestyleJob");
79 | setupXvfbOn(project, xvfb);
80 |
81 | return system.buildAndAssertSuccess(project);
82 | }
83 |
84 | protected static void setupXvfbOn(final FreeStyleProject project, final Xvfb xvfb) {
85 | final DescribableList> buildWrappers = project.getBuildWrappersList();
86 |
87 | buildWrappers.add(xvfb);
88 | }
89 |
90 | protected void setupXvfbInstallations(final Jenkins jenkins, final TemporaryFolder tempDir) throws IOException {
91 | final XvfbInstallation.DescriptorImpl installations = new XvfbInstallation.DescriptorImpl();
92 |
93 | installations.setInstallations(createInstallation("working", tempDir), createInstallation("failing", tempDir), createInstallation("auto", tempDir));
94 |
95 | final DescriptorExtensionList> toolInstallations = jenkins.getDescriptorList(ToolInstallation.class);
96 | toolInstallations.add(installations);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/xvfb/XvfbInstallation.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2012, Zoran Regvart
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice, this
9 | * list of conditions and the following disclaimer.
10 | * 2. Redistributions in binary form must reproduce the above copyright notice,
11 | * this list of conditions and the following disclaimer in the documentation
12 | * and/or other materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | *
25 | * The views and conclusions contained in the software and documentation are those
26 | * of the authors and should not be interpreted as representing official policies,
27 | * either expressed or implied, of the FreeBSD Project.
28 | */
29 | package org.jenkinsci.plugins.xvfb;
30 |
31 | import java.io.File;
32 | import java.io.IOException;
33 | import java.util.List;
34 |
35 | import org.jenkinsci.Symbol;
36 | import org.kohsuke.stapler.DataBoundConstructor;
37 | import org.kohsuke.stapler.QueryParameter;
38 | import org.kohsuke.stapler.StaplerRequest;
39 |
40 | import hudson.EnvVars;
41 | import hudson.Extension;
42 | import hudson.model.EnvironmentSpecific;
43 | import hudson.model.Node;
44 | import hudson.model.TaskListener;
45 | import hudson.slaves.NodeSpecific;
46 | import hudson.tools.ToolDescriptor;
47 | import hudson.tools.ToolInstallation;
48 | import hudson.tools.ToolProperty;
49 | import hudson.util.FormValidation;
50 | import jenkins.model.Jenkins;
51 | import net.sf.json.JSONObject;
52 |
53 | @SuppressWarnings("serial")
54 | public class XvfbInstallation extends ToolInstallation implements NodeSpecific, EnvironmentSpecific {
55 |
56 | @Extension
57 | @Symbol("xvfb")
58 | public static class DescriptorImpl extends ToolDescriptor {
59 |
60 | @Override
61 | public boolean configure(final StaplerRequest req, final JSONObject json) throws FormException {
62 | final List boundList = req.bindJSONToList(XvfbInstallation.class, json.get("tool"));
63 |
64 | setInstallations(boundList.toArray(new XvfbInstallation[boundList.size()]));
65 |
66 | return true;
67 | }
68 |
69 | @Override
70 | public FormValidation doCheckHome(@QueryParameter final File value) {
71 | // this can be used to check the existence of a file on the server, so needs to be protected
72 | if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
73 | return FormValidation.ok();
74 | }
75 |
76 | if ("".equals(value.getPath())) {
77 | // will try path
78 | return FormValidation.ok();
79 | }
80 |
81 | if (!value.isDirectory()) {
82 | return FormValidation.error(Messages.XvfbInstallation_HomeNotDirectory(value));
83 | }
84 |
85 | final File xvfbExecutable = new File(value, "Xvfb");
86 | if (!xvfbExecutable.exists()) {
87 | return FormValidation.error(Messages.XvfbInstallation_HomeDoesntContainXvfb(value));
88 | }
89 |
90 | if (!xvfbExecutable.canExecute()) {
91 | return FormValidation.error(Messages.XvfbInstallation_XvfbIsNotExecutable(value));
92 | }
93 |
94 | return FormValidation.ok();
95 | }
96 |
97 | @Override
98 | public FormValidation doCheckName(@QueryParameter final String value) {
99 | return FormValidation.validateRequired(value);
100 | }
101 |
102 | @Override
103 | public String getDisplayName() {
104 | return Messages.XvfbInstallation_DisplayName();
105 | }
106 |
107 | @Override
108 | public XvfbInstallation[] getInstallations() {
109 | return Jenkins.get().getDescriptorByType(Xvfb.XvfbBuildWrapperDescriptor.class).getInstallations();
110 | }
111 |
112 | public DescriptorImpl getToolDescriptor() {
113 | return ToolInstallation.all().get(DescriptorImpl.class);
114 | }
115 |
116 | @Override
117 | public void setInstallations(final XvfbInstallation... installations) {
118 | Jenkins.get().getDescriptorByType(Xvfb.XvfbBuildWrapperDescriptor.class).setInstallations(installations);
119 | }
120 | }
121 |
122 | @DataBoundConstructor
123 | public XvfbInstallation(final String name, final String home, final List extends ToolProperty>> properties) {
124 | super(name, home, properties);
125 | }
126 |
127 | @Override
128 | public XvfbInstallation forEnvironment(final EnvVars environment) {
129 | return new XvfbInstallation(getName(), environment.expand(getHome()), getProperties().toList());
130 | }
131 |
132 | @Override
133 | public XvfbInstallation forNode(final Node node, final TaskListener log) throws IOException, InterruptedException {
134 | return new XvfbInstallation(getName(), translateFor(node, log), getProperties().toList());
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/xvfb/XvfbBuildWrapperWorkflowTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2012, Zoran Regvart
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice, this
9 | * list of conditions and the following disclaimer.
10 | * 2. Redistributions in binary form must reproduce the above copyright notice,
11 | * this list of conditions and the following disclaimer in the documentation
12 | * and/or other materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | *
25 | * The views and conclusions contained in the software and documentation are those
26 | * of the authors and should not be interpreted as representing official policies,
27 | * either expressed or implied, of the FreeBSD Project.
28 | */
29 | package org.jenkinsci.plugins.xvfb;
30 |
31 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
32 | import org.jenkinsci.plugins.workflow.job.WorkflowJob;
33 | import org.jenkinsci.plugins.workflow.job.WorkflowRun;
34 | import org.jenkinsci.plugins.workflow.steps.CoreWrapperStep;
35 | import org.jenkinsci.plugins.workflow.steps.StepConfigTester;
36 | import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
37 | import org.junit.Rule;
38 | import org.junit.Test;
39 | import org.junit.rules.TemporaryFolder;
40 | import org.jvnet.hudson.test.JenkinsRule;
41 | import org.jvnet.hudson.test.RestartableJenkinsRule;
42 |
43 | public class XvfbBuildWrapperWorkflowTest extends BaseXvfbTest {
44 |
45 | @Rule
46 | public RestartableJenkinsRule restartableSystem = new RestartableJenkinsRule();
47 |
48 | @Rule
49 | public TemporaryFolder tempDir = new TemporaryFolder();
50 |
51 | @Test
52 | public void configurationShouldRoundTrip() {
53 | restartableSystem.then(new RestartableJenkinsRule.Step() {
54 |
55 | @Override
56 | public void run(final JenkinsRule rule) throws Throwable {
57 | final Xvfb xvfb = new Xvfb();
58 | xvfb.setAssignedLabels("");
59 | xvfb.setAdditionalOptions("");
60 |
61 | final CoreWrapperStep wrapperStep = new CoreWrapperStep(xvfb);
62 |
63 | final CoreWrapperStep testerStep = new StepConfigTester(restartableSystem.j).configRoundTrip(wrapperStep);
64 |
65 | restartableSystem.j.assertEqualDataBoundBeans(xvfb, testerStep.getDelegate());
66 | }
67 | });
68 | }
69 |
70 | @Test
71 | public void shouldAllowWorkflowRestarts() {
72 | restartableSystem.then(new RestartableJenkinsRule.Step() {
73 |
74 | @Override
75 | public void run(final JenkinsRule rule) throws Throwable {
76 | setupXvfbInstallations(restartableSystem.j.jenkins, tempDir);
77 |
78 | final WorkflowJob workflowJob = restartableSystem.j.jenkins.createProject(WorkflowJob.class, "shouldAllowWorkflowRestarts");
79 |
80 | workflowJob.setDefinition(new CpsFlowDefinition(""//
81 | + "node {\n"//
82 | + " wrap([$class: 'Xvfb', installationName: 'working']) {\n"//
83 | + " semaphore 'shouldAllowWorkflowRestarts'\n"//
84 | + " sh 'echo DISPLAY=$DISPLAY'\n"//
85 | + " }\n"//
86 | + "}", true));
87 |
88 | final WorkflowRun workflowRun = workflowJob.scheduleBuild2(0).waitForStart();
89 |
90 | SemaphoreStep.waitForStart("shouldAllowWorkflowRestarts/1", workflowRun);
91 | }
92 |
93 | });
94 |
95 | restartableSystem.then(new RestartableJenkinsRule.Step() {
96 |
97 | @Override
98 | public void run(final JenkinsRule rule) throws Throwable {
99 | SemaphoreStep.success("shouldAllowWorkflowRestarts/1", null);
100 |
101 | final WorkflowJob workflowProject = restartableSystem.j.jenkins.getItemByFullName("shouldAllowWorkflowRestarts", WorkflowJob.class);
102 |
103 | final WorkflowRun workflowRun = workflowProject.getBuildByNumber(1);
104 |
105 | restartableSystem.j.assertBuildStatusSuccess(restartableSystem.j.waitForCompletion(workflowRun));
106 |
107 | restartableSystem.j.assertLogContains("DISPLAY=:", workflowRun);
108 | }
109 |
110 | });
111 | }
112 |
113 | @Test
114 | public void shouldSupportWorkflow() {
115 | restartableSystem.then(new RestartableJenkinsRule.Step() {
116 |
117 | @Override
118 | public void run(final JenkinsRule rule) throws Throwable {
119 | setupXvfbInstallations(restartableSystem.j.jenkins, tempDir);
120 |
121 | final WorkflowJob workflowJob = restartableSystem.j.jenkins.createProject(WorkflowJob.class, "shouldSupportWorkflow");
122 |
123 | workflowJob.setDefinition(new CpsFlowDefinition(""//
124 | + "node {\n"//
125 | + " wrap([$class: 'Xvfb', installationName: 'working']) {\n"//
126 | + " sh 'echo DISPLAY=$DISPLAY'\n"//
127 | + " }\n"//
128 | + "}", true));
129 |
130 | final WorkflowRun workflowRun = workflowJob.scheduleBuild2(0).waitForStart();
131 |
132 | restartableSystem.j.assertBuildStatusSuccess(restartableSystem.j.waitForCompletion(workflowRun));
133 |
134 | restartableSystem.j.assertLogContains("DISPLAY=:", workflowRun);
135 | }
136 |
137 | });
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/xvfb/XvfbBuildWrapperInstallationDefaultsTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2012, Zoran Regvart
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice, this
9 | * list of conditions and the following disclaimer.
10 | * 2. Redistributions in binary form must reproduce the above copyright notice,
11 | * this list of conditions and the following disclaimer in the documentation
12 | * and/or other materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | *
25 | * The views and conclusions contained in the software and documentation are those
26 | * of the authors and should not be interpreted as representing official policies,
27 | * either expressed or implied, of the FreeBSD Project.
28 | */
29 | package org.jenkinsci.plugins.xvfb;
30 |
31 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
32 | import org.jenkinsci.plugins.workflow.job.WorkflowJob;
33 | import org.jenkinsci.plugins.workflow.job.WorkflowRun;
34 | import org.junit.Rule;
35 | import org.junit.Test;
36 | import org.junit.rules.TemporaryFolder;
37 | import org.jvnet.hudson.test.JenkinsRule;
38 | import org.jvnet.hudson.test.RestartableJenkinsRule;
39 |
40 | import hudson.DescriptorExtensionList;
41 | import hudson.model.Descriptor;
42 | import hudson.tools.ToolInstallation;
43 |
44 | public class XvfbBuildWrapperInstallationDefaultsTest extends BaseXvfbTest {
45 |
46 | @Rule
47 | public RestartableJenkinsRule restartableSystem = new RestartableJenkinsRule();
48 |
49 | @Rule
50 | public TemporaryFolder tempDir = new TemporaryFolder();
51 |
52 | @Test
53 | public void shouldUseTheDefaultInstalationIfNoExplicitNameSpecified() {
54 | restartableSystem.then(new RestartableJenkinsRule.Step() {
55 |
56 | @Override
57 | public void run(final JenkinsRule rule) throws Throwable {
58 | final XvfbInstallation.DescriptorImpl installations = new XvfbInstallation.DescriptorImpl();
59 |
60 | installations.setInstallations(createInstallation("default", tempDir), createInstallation("failing", tempDir));
61 |
62 | final DescriptorExtensionList> toolInstallations = restartableSystem.j.jenkins.getDescriptorList(ToolInstallation.class);
63 | toolInstallations.add(installations);
64 |
65 | final Xvfb xvfb = new Xvfb();
66 |
67 | runFreestyleJobWith(restartableSystem.j, xvfb);
68 | }
69 | });
70 | }
71 |
72 | @Test
73 | public void shouldUseTheDefaultInstallationInWorkflow() {
74 | restartableSystem.then(new RestartableJenkinsRule.Step() {
75 |
76 | @Override
77 | public void run(final JenkinsRule rule) throws Throwable {
78 | final XvfbInstallation.DescriptorImpl installations = new XvfbInstallation.DescriptorImpl();
79 |
80 | installations.setInstallations(createInstallation("default", tempDir), createInstallation("working", tempDir));
81 |
82 | final DescriptorExtensionList> toolInstallations = restartableSystem.j.jenkins.getDescriptorList(ToolInstallation.class);
83 | toolInstallations.add(installations);
84 |
85 | final WorkflowJob workflowJob = restartableSystem.j.jenkins.createProject(WorkflowJob.class, "shouldUseTheOnlyInstallationInWorkflow");
86 |
87 | workflowJob.setDefinition(new CpsFlowDefinition(""//
88 | + "node {\n"//
89 | + " wrap([$class: 'Xvfb']) {\n"//
90 | + " sh 'echo DISPLAY=$DISPLAY'\n"//
91 | + " }\n"//
92 | + "}", true));
93 |
94 | final WorkflowRun workflowRun = workflowJob.scheduleBuild2(0).waitForStart();
95 |
96 | restartableSystem.j.assertBuildStatusSuccess(restartableSystem.j.waitForCompletion(workflowRun));
97 |
98 | restartableSystem.j.assertLogContains("DISPLAY=:", workflowRun);
99 | }
100 | });
101 | }
102 |
103 | @Test
104 | public void shouldUseTheOnlyInstalationIfNoExplicitNameSpecified() {
105 | restartableSystem.then(new RestartableJenkinsRule.Step() {
106 |
107 | @Override
108 | public void run(final JenkinsRule rule) throws Throwable {
109 | final XvfbInstallation.DescriptorImpl installations = new XvfbInstallation.DescriptorImpl();
110 |
111 | installations.setInstallations(createInstallation("working", tempDir));
112 |
113 | final DescriptorExtensionList> toolInstallations = restartableSystem.j.jenkins.getDescriptorList(ToolInstallation.class);
114 | toolInstallations.add(installations);
115 |
116 | final Xvfb xvfb = new Xvfb();
117 |
118 | runFreestyleJobWith(restartableSystem.j, xvfb);
119 | }
120 | });
121 | }
122 |
123 | @Test
124 | public void shouldUseTheOnlyInstallationInWorkflow() {
125 | restartableSystem.then(new RestartableJenkinsRule.Step() {
126 |
127 | @Override
128 | public void run(final JenkinsRule rule) throws Throwable {
129 | final XvfbInstallation.DescriptorImpl installations = new XvfbInstallation.DescriptorImpl();
130 |
131 | installations.setInstallations(createInstallation("working", tempDir));
132 |
133 | final DescriptorExtensionList> toolInstallations = restartableSystem.j.jenkins.getDescriptorList(ToolInstallation.class);
134 | toolInstallations.add(installations);
135 |
136 | final WorkflowJob workflowJob = restartableSystem.j.jenkins.createProject(WorkflowJob.class, "shouldUseTheOnlyInstallationInWorkflow");
137 |
138 | workflowJob.setDefinition(new CpsFlowDefinition(""//
139 | + "node {\n"//
140 | + " wrap([$class: 'Xvfb']) {\n"//
141 | + " sh 'echo DISPLAY=$DISPLAY'\n"//
142 | + " }\n"//
143 | + "}", true));
144 |
145 | final WorkflowRun workflowRun = workflowJob.scheduleBuild2(0).waitForStart();
146 |
147 | restartableSystem.j.assertBuildStatusSuccess(restartableSystem.j.waitForCompletion(workflowRun));
148 |
149 | restartableSystem.j.assertLogContains("DISPLAY=:", workflowRun);
150 | }
151 | });
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
31 |
32 | 4.0.0
33 |
34 | org.jenkins-ci.plugins
35 | plugin
36 | 4.19
37 |
38 |
39 |
40 | xvfb
41 | 1.3-SNAPSHOT
42 | hpi
43 | Jenkins Xvfb plugin
44 | Starts and stopps Xvfb with the build
45 | http://wiki.jenkins-ci.org/display/JENKINS/Xvfb+Plugin
46 | 2012
47 |
48 |
49 |
50 | 2-clause BSD license
51 | repo
52 | http://www.opensource.org/licenses/bsd-license.php
53 |
54 |
55 |
56 |
57 |
58 | zregvart
59 | Zoran Regvart
60 | zregvart+xvfbjenkins@gmail.com
61 |
62 |
63 |
64 |
65 | scm:git:git://github.com/jenkinsci/xvfb-plugin.git
66 | scm:git:https://github.com/jenkinsci/xvfb-plugin.git
67 | https://github.com/jenkinsci/xvfb-plugin/
68 | HEAD
69 |
70 |
71 |
72 | 1.2
73 | -SNAPSHOT
74 |
75 | 2.222
76 | ${jenkins.baseline}.4
77 | 8
78 |
79 |
80 |
81 |
82 | repo.jenkins-ci.org
83 | https://repo.jenkins-ci.org/public/
84 |
85 |
86 |
87 |
88 |
89 | repo.jenkins-ci.org
90 | https://repo.jenkins-ci.org/public/
91 |
92 |
93 |
94 |
95 |
96 |
97 | com.mycila
98 | license-maven-plugin
99 | 2.11
100 |
101 | com/mycila/maven/plugin/license/templates/BSD-2.txt
102 |
103 | Zoran Regvart
104 |
105 |
106 | README
107 | LICENSE
108 | work/**
109 |
110 |
111 | XML_STYLE
112 | SCRIPT_STYLE
113 |
114 |
115 |
116 |
117 |
118 | check
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | io.jenkins.tools.bom
130 | bom-${jenkins.baseline}.x
131 | 29
132 | import
133 | pom
134 |
135 |
136 |
137 |
138 |
139 |
140 | org.jenkins-ci.plugins
141 | structs
142 |
143 |
144 | org.jenkins-ci.plugins.workflow
145 | workflow-job
146 | test
147 |
148 |
149 | org.jenkins-ci.plugins.workflow
150 | workflow-basic-steps
151 | test
152 |
153 |
154 | org.jenkins-ci.plugins.workflow
155 | workflow-cps
156 | test
157 |
158 |
159 | org.jenkins-ci.plugins.workflow
160 | workflow-durable-task-step
161 | test
162 |
163 |
164 | org.jenkins-ci.plugins.workflow
165 | workflow-step-api
166 | tests
167 | test
168 |
169 |
170 | org.jenkins-ci.plugins.workflow
171 | workflow-support
172 | tests
173 | test
174 |
175 |
176 |
177 |
178 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/xvfb/XvfbBuildWrapperTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2012, Zoran Regvart
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice, this
9 | * list of conditions and the following disclaimer.
10 | * 2. Redistributions in binary form must reproduce the above copyright notice,
11 | * this list of conditions and the following disclaimer in the documentation
12 | * and/or other materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | *
25 | * The views and conclusions contained in the software and documentation are those
26 | * of the authors and should not be interpreted as representing official policies,
27 | * either expressed or implied, of the FreeBSD Project.
28 | */
29 | package org.jenkinsci.plugins.xvfb;
30 |
31 | import static com.google.common.collect.Iterables.filter;
32 | import static org.hamcrest.MatcherAssert.assertThat;
33 | import static org.hamcrest.Matchers.contains;
34 | import static org.hamcrest.Matchers.containsInAnyOrder;
35 | import static org.hamcrest.Matchers.containsString;
36 | import static org.hamcrest.Matchers.empty;
37 | import static org.hamcrest.Matchers.greaterThanOrEqualTo;
38 | import static org.hamcrest.Matchers.hasItems;
39 | import static org.hamcrest.Matchers.hasSize;
40 | import static org.hamcrest.Matchers.instanceOf;
41 | import static org.hamcrest.Matchers.is;
42 | import static org.hamcrest.core.IsNot.not;
43 |
44 | import java.io.ByteArrayOutputStream;
45 | import java.io.File;
46 | import java.io.IOException;
47 | import java.io.OutputStream;
48 | import java.util.Arrays;
49 | import java.util.List;
50 |
51 | import org.junit.After;
52 | import org.junit.Before;
53 | import org.junit.Rule;
54 | import org.junit.Test;
55 | import org.junit.rules.TemporaryFolder;
56 | import org.jvnet.hudson.test.Issue;
57 | import org.jvnet.hudson.test.JenkinsRule;
58 |
59 | import com.google.common.base.Function;
60 | import com.google.common.base.Joiner;
61 | import com.google.common.base.Predicate;
62 | import com.google.common.base.Predicates;
63 | import com.google.common.collect.Iterables;
64 |
65 | import hudson.FilePath;
66 | import hudson.Launcher;
67 | import hudson.Launcher.LocalLauncher;
68 | import hudson.model.AbstractBuild;
69 | import hudson.model.Computer;
70 | import hudson.model.FreeStyleBuild;
71 | import hudson.model.FreeStyleProject;
72 | import hudson.model.Label;
73 | import hudson.model.Node;
74 | import hudson.model.Result;
75 | import hudson.model.StreamBuildListener;
76 | import hudson.model.queue.QueueTaskFuture;
77 | import hudson.tasks.BuildWrapper.Environment;
78 | import hudson.util.ArgumentListBuilder;
79 | import hudson.util.ProcessTree;
80 | import hudson.util.ProcessTree.OSProcess;
81 |
82 | public class XvfbBuildWrapperTest extends BaseXvfbTest {
83 |
84 | @Rule
85 | public JenkinsRule system = new JenkinsRule();
86 |
87 | @Rule
88 | public TemporaryFolder tempDir = new TemporaryFolder();
89 |
90 | @Test
91 | public void byDefaultBuildnumbersShouldBeBasedOnExecutorNumber() throws Exception {
92 | system.jenkins.setNumExecutors(3);
93 |
94 | final Xvfb xvfb = new Xvfb();
95 | xvfb.setInstallationName("working");
96 |
97 | final FreeStyleProject project1 = createFreeStyleJob(system, "byDefaultBuildnumbersShouldBeBasedOnExecutorNumber1");
98 | setupXvfbOn(project1, xvfb);
99 |
100 | final FreeStyleProject project2 = createFreeStyleJob(system, "byDefaultBuildnumbersShouldBeBasedOnExecutorNumber2");
101 | setupXvfbOn(project2, xvfb);
102 |
103 | final FreeStyleProject project3 = createFreeStyleJob(system, "byDefaultBuildnumbersShouldBeBasedOnExecutorNumber3");
104 | setupXvfbOn(project3, xvfb);
105 |
106 | final QueueTaskFuture build1Result = project1.scheduleBuild2(0);
107 | final QueueTaskFuture build2Result = project2.scheduleBuild2(0);
108 | final QueueTaskFuture build3Result = project3.scheduleBuild2(0);
109 |
110 | final FreeStyleBuild build1 = build1Result.get();
111 | final FreeStyleBuild build2 = build2Result.get();
112 | final FreeStyleBuild build3 = build3Result.get();
113 |
114 | final int display1 = build1.getAction(XvfbEnvironment.class).displayName;
115 | final int display2 = build2.getAction(XvfbEnvironment.class).displayName;
116 | final int display3 = build3.getAction(XvfbEnvironment.class).displayName;
117 |
118 | final List displayNumbersUsed = Arrays.asList(display1, display2, display3);
119 | assertThat("By default display numbers should be based on executor number, they were: " + displayNumbersUsed, displayNumbersUsed, containsInAnyOrder(1, 2, 3));
120 | }
121 |
122 | @Test
123 | @Issue("JENKINS-26848")
124 | public void inParallelBuildsBuildnumbersShouldBeOffsettedByComputerNumber() throws Exception {
125 | system.createSlave("label1", null);
126 |
127 | final Xvfb xvfb = new Xvfb();
128 | xvfb.setInstallationName("working");
129 | xvfb.setParallelBuild(true);
130 |
131 | final FreeStyleProject project = createFreeStyleJob(system, "inParallelBuildsBuildnumbersShouldBeOffsettedByComputerNumber");
132 | setupXvfbOn(project, xvfb);
133 | project.setAssignedLabel(Label.get("label1"));
134 |
135 | final FreeStyleBuild build = system.buildAndAssertSuccess(project);
136 |
137 | assertThat("For parallel builds display name should be based on computer number 100 * computer index offset", build.getAction(XvfbEnvironment.class).displayName, is(101));
138 | }
139 |
140 | @Before
141 | public void setupXvfbInstallations() throws IOException {
142 | setupXvfbInstallations(system.jenkins, tempDir);
143 | }
144 |
145 | @Test
146 | public void shouldCreateCommandLineArguments() throws IOException {
147 | final Xvfb xvfb = new Xvfb();
148 | xvfb.setInstallationName("cmd");
149 | xvfb.setDisplayName(42);
150 |
151 | final XvfbInstallation installation = new XvfbInstallation("cmd", "/usr/local/cmd-xvfb", null);
152 |
153 | final File tempDirRoot = tempDir.getRoot();
154 | final ArgumentListBuilder arguments = xvfb.createCommandArguments(installation, new FilePath(tempDirRoot), 42);
155 |
156 | assertThat(arguments.toList(), contains("/usr/local/cmd-xvfb/Xvfb", ":42", "-screen", "0", Xvfb.DEFAULT_SCREEN, "-fbdir", tempDirRoot.getAbsolutePath()));
157 | }
158 |
159 | @Test
160 | @Issue("JENKINS-32039")
161 | public void shouldCreateCommandLineArgumentsWithoutScreenIfEmpty() throws IOException {
162 | final Xvfb xvfb = new Xvfb();
163 | xvfb.setInstallationName("cmd");
164 | xvfb.setDisplayName(42);
165 | xvfb.setScreen("");
166 |
167 | final XvfbInstallation installation = new XvfbInstallation("cmd", "/usr/local/cmd-xvfb", null);
168 |
169 | final File tempDirRoot = tempDir.getRoot();
170 | final ArgumentListBuilder arguments = xvfb.createCommandArguments(installation, new FilePath(tempDirRoot), 42);
171 |
172 | assertThat(arguments.toList(), contains("/usr/local/cmd-xvfb/Xvfb", ":42", "-fbdir", tempDirRoot.getAbsolutePath()));
173 | }
174 |
175 | @Test
176 | @Issue("JENKINS-32039")
177 | public void shouldCreateCommandLineArgumentsWithoutScreenIfNotGiven() throws IOException {
178 | final Xvfb xvfb = new Xvfb();
179 | xvfb.setInstallationName("cmd");
180 | xvfb.setDisplayName(42);
181 | xvfb.setScreen(null);
182 |
183 | final XvfbInstallation installation = new XvfbInstallation("cmd", "/usr/local/cmd-xvfb", null);
184 |
185 | final File tempDirRoot = tempDir.getRoot();
186 | final ArgumentListBuilder arguments = xvfb.createCommandArguments(installation, new FilePath(tempDirRoot), 42);
187 |
188 | assertThat(arguments.toList(), contains("/usr/local/cmd-xvfb/Xvfb", ":42", "-fbdir", tempDirRoot.getAbsolutePath()));
189 | }
190 |
191 | @SuppressWarnings("unchecked")
192 | @Test
193 | public void shouldFailIfInstallationIsNotFound() throws Exception {
194 | final Xvfb xvfb = new Xvfb();
195 | xvfb.setInstallationName("nonexistant");
196 |
197 | final FreeStyleProject project = createFreeStyleJob(system, "shouldFailIfInstallationIsNotFound");
198 | setupXvfbOn(project, xvfb);
199 |
200 | final QueueTaskFuture buildResult = project.scheduleBuild2(0);
201 |
202 | final FreeStyleBuild build = buildResult.get();
203 |
204 | system.assertBuildStatus(Result.FAILURE, build);
205 |
206 | final List logLines = build.getLog(10);
207 |
208 | assertThat("If no Xvfb installations defined for use log should note that", logLines, hasItems(containsString("No Xvfb installations defined, please define one in the configuration")));
209 | }
210 |
211 | @SuppressWarnings("unchecked")
212 | @Test
213 | public void shouldFailIfNoInstallationIsDefined() throws Exception {
214 | final Xvfb xvfb = new Xvfb();
215 |
216 | final FreeStyleProject project = createFreeStyleJob(system, "shouldFailIfNoInstallationIsDefined");
217 | setupXvfbOn(project, xvfb);
218 |
219 | final QueueTaskFuture buildResult = project.scheduleBuild2(0);
220 |
221 | final FreeStyleBuild build = buildResult.get();
222 |
223 | system.assertBuildStatus(Result.FAILURE, build);
224 |
225 | final List logLines = build.getLog(10);
226 |
227 | assertThat("If no Xvfb installations defined for use log should note that", logLines, hasItems(containsString("No Xvfb installations defined, please define one in the configuration")));
228 | }
229 |
230 | @SuppressWarnings("unchecked")
231 | @Test
232 | @Issue("JENKINS-18094")
233 | public void shouldFailIfXvfbFailsToStart() throws Exception {
234 | final Xvfb xvfb = new Xvfb();
235 | xvfb.setInstallationName("failing");
236 | xvfb.setTimeout(10);
237 |
238 | final FreeStyleProject project = createFreeStyleJob(system, "shouldFailIfXvfbFailsToStart");
239 | setupXvfbOn(project, xvfb);
240 |
241 | final QueueTaskFuture buildResult = project.scheduleBuild2(0);
242 |
243 | final FreeStyleBuild build = buildResult.get();
244 |
245 | system.assertBuildStatus(Result.FAILURE, build);
246 |
247 | final List logLines = build.getLog(10);
248 |
249 | assertThat("If Xvfb fails to start log should indicate that", logLines,
250 | hasItems(containsString("This Xvfb will fail"), containsString("Xvfb failed to start, consult the lines above for errors")));
251 |
252 | }
253 |
254 | @Test
255 | public void shouldHonourSpecifiedDisplayNameOffset() throws Exception {
256 | final Xvfb xvfb = new Xvfb();
257 | xvfb.setInstallationName("working");
258 | xvfb.setDisplayNameOffset(100);
259 |
260 | final FreeStyleBuild build = runFreestyleJobWith(system, xvfb);
261 |
262 | assertThat("DISPLAY environment variable should be larger than 100 as is specified by offset of 100", build.getAction(XvfbEnvironment.class).displayName, greaterThanOrEqualTo(100));
263 | }
264 |
265 | @After
266 | public void shouldNotLeakProcesses() throws Exception {
267 | final String testXvfbDir = tempDir.getRoot().getName();
268 |
269 | final Iterable leaks = Iterables.transform(filter(ProcessTree.get(), new Predicate() {
270 | @Override
271 | public boolean apply(final OSProcess process) {
272 | final List arguments = process.getArguments();
273 | return Iterables.any(arguments, Predicates.containsPattern(testXvfbDir + "/\\w+/Xvfb"));
274 | }
275 | }), new Function() {
276 |
277 | @Override
278 | public Integer apply(final OSProcess process) {
279 | return process.getPid();
280 | }
281 | });
282 |
283 | assertThat("Found leaked processes, PIDs: " + Joiner.on(',').join(leaks), leaks.iterator().hasNext(), is(false));
284 | }
285 |
286 | @Test
287 | @Issue("JENKINS-14483")
288 | public void shouldNotRunOnNonUnixNodes() throws IOException, InterruptedException {
289 | final Xvfb xvfb = new Xvfb();
290 |
291 | final LocalLauncher launcher = new Launcher.LocalLauncher(system.createTaskListener()) {
292 | @Override
293 | public boolean isUnix() {
294 | return false;
295 | }
296 | };
297 |
298 | final OutputStream out = new ByteArrayOutputStream();
299 |
300 | final FreeStyleProject project = system.createFreeStyleProject();
301 |
302 | @SuppressWarnings("rawtypes")
303 | final Environment environment = xvfb.setUp((AbstractBuild) new FreeStyleBuild(project), launcher, new StreamBuildListener(out));
304 |
305 | assertThat("Environment should not be setup", environment, not(instanceOf(XvfbEnvironment.class)));
306 | }
307 |
308 | @Test
309 | @Issue("JENKINS-23155")
310 | public void shouldNotRunOnUnLabeledNodes() throws Exception {
311 | final FreeStyleProject project = createFreeStyleJob(system, "shouldNotRunOnUnLabeledNodes");
312 |
313 | final Xvfb xvfb = new Xvfb();
314 | xvfb.setInstallationName("working");
315 | xvfb.setAssignedLabels("label1");
316 |
317 | setupXvfbOn(project, xvfb);
318 |
319 | final FreeStyleBuild build = system.buildAndAssertSuccess(project);
320 | assertThat("Xvfb should not be started if label is not matched", build.getActions(XvfbEnvironment.class), empty());
321 | }
322 |
323 | @SuppressWarnings("unchecked")
324 | @Test
325 | @Issue("JENKINS-13046")
326 | public void shouldPassOnAdditionalCommandLineArguments() throws Exception {
327 | final Xvfb xvfb = new Xvfb();
328 | xvfb.setInstallationName("working");
329 | xvfb.setDisplayName(42);
330 | xvfb.setAdditionalOptions("additional options");
331 |
332 | final FreeStyleBuild build = runFreestyleJobWith(system, xvfb);
333 |
334 | final List logLines = build.getLog(10);
335 |
336 | assertThat("Additional command line options should be passed to Xvfb", logLines, hasItems(containsString("additional options")));
337 | }
338 |
339 | @Test
340 | public void shouldPickupXvfbAutoDeterminedDisplayNumber() throws Exception {
341 | final Xvfb xvfb = new Xvfb();
342 | xvfb.setInstallationName("auto");
343 | xvfb.setAutoDisplayName(true);
344 |
345 | final FreeStyleBuild build = runFreestyleJobWith(system, xvfb);
346 |
347 | assertThat("DISPLAY environment variable should be 42, as it was determined automatically by Xvfb", build.getAction(XvfbEnvironment.class).displayName, is(42));
348 | }
349 |
350 | @Test
351 | @Issue("JENKINS-23155")
352 | public void shouldRunOnLabeledNodes() throws Exception {
353 | final Computer computer = system.jenkins.createComputer();
354 | final Node node = computer.getNode();
355 | node.setLabelString("label1");
356 |
357 | final FreeStyleProject project = createFreeStyleJob(system, "shouldRunOnLabeledNodes");
358 | project.setAssignedNode(node);
359 |
360 | final Xvfb xvfb = new Xvfb();
361 | xvfb.setInstallationName("working");
362 | xvfb.setAssignedLabels("label1");
363 |
364 | setupXvfbOn(project, xvfb);
365 |
366 | final FreeStyleBuild build = system.buildAndAssertSuccess(project);
367 | assertThat("Xvfb should be started if label is matched", build.getActions(XvfbEnvironment.class), hasSize(1));
368 | }
369 |
370 | @Test
371 | @Issue("JENKINS-14483")
372 | public void shouldRunOnUnixNodes() throws Exception {
373 | final Xvfb xvfb = new Xvfb();
374 | xvfb.setInstallationName("working");
375 |
376 | final FreeStyleProject project = createFreeStyleJob(system, "shouldRunOnUnixNodes");
377 | setupXvfbOn(project, xvfb);
378 |
379 | final FreeStyleBuild build = system.buildAndAssertSuccess(project);
380 |
381 | final Launcher launcher = build.getBuiltOn().createLauncher(system.createTaskListener());
382 |
383 | assertThat("Launcher should be on Unix", launcher.isUnix(), is(true));
384 |
385 | assertThat("Environment should be setup", build.getActions(XvfbEnvironment.class), hasSize(1));
386 | }
387 |
388 | @Test
389 | public void shouldShutdownWithBuild() throws Exception {
390 | final Xvfb xvfb = new Xvfb();
391 | xvfb.setInstallationName("working");
392 | xvfb.setShutdownWithBuild(true);
393 |
394 | runFreestyleJobWith(system, xvfb);
395 |
396 | }
397 |
398 | @Test
399 | public void shouldUseSpecifiedDisplayName() throws Exception {
400 | final Xvfb xvfb = new Xvfb();
401 | xvfb.setInstallationName("working");
402 | xvfb.setDisplayName(42);
403 |
404 | final FreeStyleBuild build = runFreestyleJobWith(system, xvfb);
405 |
406 | assertThat("DISPLAY environment variable should be 42, as is specified by configuration", build.getAction(XvfbEnvironment.class).displayName, is(42));
407 | }
408 | }
409 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/xvfb/Xvfb.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2012, Zoran Regvart
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice, this
9 | * list of conditions and the following disclaimer.
10 | * 2. Redistributions in binary form must reproduce the above copyright notice,
11 | * this list of conditions and the following disclaimer in the documentation
12 | * and/or other materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | *
25 | * The views and conclusions contained in the software and documentation are those
26 | * of the authors and should not be interpreted as representing official policies,
27 | * either expressed or implied, of the FreeBSD Project.
28 | */
29 | package org.jenkinsci.plugins.xvfb;
30 |
31 | import java.io.ByteArrayOutputStream;
32 | import java.io.File;
33 | import java.io.IOException;
34 | import java.io.OutputStream;
35 | import java.util.ArrayList;
36 | import java.util.Arrays;
37 | import java.util.Collections;
38 | import java.util.Comparator;
39 | import java.util.Iterator;
40 | import java.util.List;
41 | import java.util.Map;
42 | import java.util.Set;
43 | import java.util.UUID;
44 | import java.util.concurrent.ConcurrentHashMap;
45 | import java.util.concurrent.CopyOnWriteArrayList;
46 |
47 | import org.kohsuke.stapler.AncestorInPath;
48 | import org.kohsuke.stapler.DataBoundConstructor;
49 | import org.kohsuke.stapler.DataBoundSetter;
50 | import org.kohsuke.stapler.QueryParameter;
51 | import org.kohsuke.stapler.StaplerRequest;
52 |
53 | import com.google.common.base.Optional;
54 | import com.thoughtworks.xstream.XStream;
55 |
56 | import antlr.ANTLRException;
57 | import hudson.CopyOnWrite;
58 | import hudson.EnvVars;
59 | import hudson.Extension;
60 | import hudson.FilePath;
61 | import hudson.Launcher;
62 | import hudson.Launcher.ProcStarter;
63 | import hudson.Proc;
64 | import hudson.Util;
65 | import hudson.XmlFile;
66 | import hudson.init.InitMilestone;
67 | import hudson.init.Initializer;
68 | import hudson.model.AbstractProject;
69 | import hudson.model.AutoCompletionCandidates;
70 | import hudson.model.Computer;
71 | import hudson.model.Executor;
72 | import hudson.model.Items;
73 | import hudson.model.Label;
74 | import hudson.model.Node;
75 | import hudson.model.Run;
76 | import hudson.model.TaskListener;
77 | import hudson.model.Run.RunnerAbortedException;
78 | import hudson.model.labels.LabelAtom;
79 | import hudson.model.listeners.RunListener;
80 | import hudson.remoting.Channel;
81 | import hudson.remoting.ChannelClosedException;
82 | import hudson.slaves.ComputerListener;
83 | import hudson.tasks.BuildWrapper;
84 | import hudson.tasks.BuildWrapperDescriptor;
85 | import hudson.tools.ToolInstallation;
86 | import hudson.util.ArgumentListBuilder;
87 | import hudson.util.FormValidation;
88 | import hudson.util.ProcessTree;
89 | import hudson.util.ProcessTree.OSProcess;
90 | import hudson.util.XStream2;
91 | import jenkins.model.Jenkins;
92 | import jenkins.security.MasterToSlaveCallable;
93 | import jenkins.tasks.SimpleBuildWrapper;
94 | import net.sf.json.JSONObject;
95 |
96 | public class Xvfb extends SimpleBuildWrapper {
97 |
98 | private static final class ComputerNameComparator implements Comparator {
99 |
100 | private static final ComputerNameComparator INSTANCE = new ComputerNameComparator();
101 |
102 | @Override
103 | public int compare(final Computer left, final Computer right) {
104 | return left.getName().compareTo(right.getName());
105 | }
106 |
107 | }
108 |
109 | @Extension(ordinal = Double.MAX_VALUE)
110 | public static class XvfbBuildWrapperDescriptor extends BuildWrapperDescriptor {
111 |
112 | @Initializer(before = InitMilestone.PLUGINS_LISTED)
113 | public static void setupBackwardCompatibility() {
114 | setupBackwardCompatibilityOn(Items.XSTREAM2);
115 | final XStream xstream = new XmlFile(new File("mapping")).getXStream();
116 | if (xstream instanceof XStream2) {
117 | setupBackwardCompatibilityOn((XStream2) xstream);
118 | }
119 | }
120 |
121 | private static void setupBackwardCompatibilityOn(final XStream2 instance) {
122 | instance.addCompatibilityAlias("org.jenkinsci.plugins.xvfb.XvfbBuildWrapper", Xvfb.class);
123 | instance.addCompatibilityAlias("org.jenkinsci.plugins.xvfb.XvfbBuildWrapper$XvfbBuildWrapperDescriptor", Xvfb.XvfbBuildWrapperDescriptor.class);
124 | }
125 |
126 | /** Xvfb installations, this descriptor persists all installations configured. */
127 | @CopyOnWrite
128 | private volatile XvfbInstallation[] installations = new XvfbInstallation[0];
129 |
130 | public XvfbBuildWrapperDescriptor() {
131 | load();
132 | }
133 |
134 | /** adopted from @see hudson.model.AbstractProject.AbstractProjectDescriptor#doAutoCompleteAssignedLabels */
135 | public AutoCompletionCandidates doAutoCompleteAssignedLabels(@AncestorInPath final AbstractProject, ?> project, @QueryParameter final String value) {
136 | final AutoCompletionCandidates candidates = new AutoCompletionCandidates();
137 | final Set labels = Jenkins.get().getLabels();
138 |
139 | for (final Label label : labels) {
140 | if (value == null || label.getName().startsWith(value)) {
141 | candidates.add(label.getName());
142 | }
143 | }
144 |
145 | return candidates;
146 | }
147 |
148 | /** adopted from @see hudson.model.AbstractProject.AbstractProjectDescriptor#doCheckAssignedLabels */
149 | public FormValidation doCheckAssignedLabels(@AncestorInPath final AbstractProject, ?> project, @QueryParameter final String value) {
150 | if (Util.fixEmpty(value) == null) {
151 | return FormValidation.ok();
152 | }
153 |
154 | try {
155 | Label.parseExpression(value);
156 | } catch (final ANTLRException e) {
157 | return FormValidation.error(e, Messages.XvfbBuildWrapper_AssignedLabelString_InvalidBooleanExpression(e.getMessage()));
158 | }
159 |
160 | final Jenkins jenkins = Jenkins.get();
161 | final Label label = jenkins.getLabel(value);
162 |
163 | if (label.isEmpty()) {
164 | for (final LabelAtom labelAtom : label.listAtoms()) {
165 | if (labelAtom.isEmpty()) {
166 | final LabelAtom nearest = LabelAtom.findNearest(labelAtom.getName());
167 | return FormValidation.warning(Messages.XvfbBuildWrapper_AssignedLabelString_NoMatch_DidYouMean(labelAtom.getName(), nearest.getDisplayName()));
168 | }
169 | }
170 | return FormValidation.warning(Messages.XvfbBuildWrapper_AssignedLabelString_NoMatch());
171 | }
172 |
173 | return FormValidation.okWithMarkup(Messages.XvfbBuildWrapper_LabelLink(jenkins.getRootUrl(), label.getUrl(), label.getNodes().size() + label.getClouds().size()));
174 | }
175 |
176 | public FormValidation doCheckDisplayName(@QueryParameter final String value) throws IOException {
177 | return validateOptionalNonNegativeInteger(value);
178 | }
179 |
180 | public FormValidation doCheckDisplayNameOffset(@QueryParameter final String value) throws IOException {
181 | return validateOptionalPositiveInteger(value);
182 | }
183 |
184 | public FormValidation doCheckTimeout(@QueryParameter final String value) throws IOException {
185 | return validateOptionalNonNegativeInteger(value);
186 | }
187 |
188 | @Override
189 | protected XmlFile getConfigFile() {
190 | final File rootDir = Jenkins.get().getRootDir();
191 |
192 | final File legacyConfiguration = new File(rootDir, "org.jenkinsci.plugins.xvfb.XvfbBuildWrapper.xml");
193 | if (legacyConfiguration.exists()) {
194 | final File newConfigurationFile = new File(rootDir, getId() + ".xml");
195 | if (!legacyConfiguration.renameTo(newConfigurationFile)) {
196 | throw new IllegalStateException("Unable to rename legacy configuration file at " + legacyConfiguration + " to " + newConfigurationFile);
197 | }
198 | }
199 |
200 | return super.getConfigFile();
201 | }
202 |
203 | @Override
204 | public String getDisplayName() {
205 | return Messages.XvfbBuildWrapper_DisplayName();
206 | }
207 |
208 | public XvfbInstallation[] getInstallations() {
209 | return installations.clone();
210 | }
211 |
212 | public XvfbInstallation.DescriptorImpl getToolDescriptor() {
213 | return ToolInstallation.all().get(XvfbInstallation.DescriptorImpl.class);
214 | }
215 |
216 | @Override
217 | public boolean isApplicable(final AbstractProject, ?> item) {
218 | return true;
219 | }
220 |
221 | @Override
222 | public BuildWrapper newInstance(final StaplerRequest req, final JSONObject formData) throws hudson.model.Descriptor.FormException {
223 | return req.bindJSON(Xvfb.class, formData);
224 | }
225 |
226 | public void setInstallations(final XvfbInstallation... installations) {
227 | if (installations == null) {
228 | this.installations = new XvfbInstallation[0];
229 | } else {
230 | this.installations = installations;
231 | }
232 | save();
233 | }
234 |
235 | private FormValidation validateOptionalNonNegativeInteger(final String value) {
236 | if (value == null || value.trim().isEmpty()) {
237 | return FormValidation.ok();
238 | }
239 |
240 | return FormValidation.validateNonNegativeInteger(value);
241 | }
242 |
243 | private FormValidation validateOptionalPositiveInteger(final String value) {
244 | if (value == null || value.trim().isEmpty()) {
245 | return FormValidation.ok();
246 | }
247 |
248 | return FormValidation.validatePositiveInteger(value);
249 | }
250 | }
251 |
252 | private static final String JENKINS_XVFB_COOKIE = "_JENKINS_XVFB_COOKIE";
253 |
254 | private static final String STDERR_FD = "2";
255 |
256 | @SuppressWarnings("rawtypes")
257 | @Extension
258 | public static final RunListener xvfbShutdownListener = new RunListener() {
259 | @Override
260 | public void onCompleted(final Run r, final TaskListener listener) {
261 | final XvfbEnvironment xvfbEnvironment = r.getAction(XvfbEnvironment.class);
262 |
263 | if (xvfbEnvironment == null || !xvfbEnvironment.shutdownWithBuild) {
264 | return;
265 | }
266 |
267 | try {
268 | final Executor executor = r.getExecutor();
269 | if (executor == null) {
270 | // not much we can do, but let's not fail the build if we can't cleanup
271 | return;
272 | }
273 |
274 | final Computer computer = executor.getOwner();
275 | final Node node = computer.getNode();
276 | if (node == null) {
277 | // not much we can do, but let's not fail the build if we can't cleanup
278 | return;
279 | }
280 |
281 | final Launcher launcher = node.createLauncher(listener);
282 |
283 | Xvfb.shutdownAndCleanup(xvfbEnvironment, launcher, listener);
284 | } catch (final IOException e) {
285 | throw new RuntimeException(e);
286 | } catch (final InterruptedException e) {
287 | throw new RuntimeException(e);
288 | }
289 | }
290 | };
291 |
292 | private static final Map> zombies = createOrLoadZombiesMap();
293 |
294 | @Extension
295 | public static final ComputerListener nodeListener = new ComputerListener() {
296 | @Override
297 | public void preOnline(final Computer c, final Channel channel, final FilePath root, final TaskListener listener) throws IOException, InterruptedException {
298 | final List zombiesAtComputer = zombies.get(c.getName());
299 |
300 | if (zombiesAtComputer == null) {
301 | return;
302 | }
303 |
304 | final List slained = new ArrayList();
305 | for (final XvfbEnvironment zombie : zombiesAtComputer) {
306 | shutdownAndCleanupZombie(channel, zombie, listener);
307 |
308 | slained.add(zombie);
309 | }
310 |
311 | zombiesAtComputer.removeAll(slained);
312 | }
313 |
314 | };
315 |
316 | private static final int MILLIS_IN_SECOND = 1000;
317 |
318 | /** default screen configuration for Xvfb, used by default, and if user left screen configuration blank */
319 | static final String DEFAULT_SCREEN = "1024x768x24";
320 |
321 | private static ConcurrentHashMap> createOrLoadZombiesMap() {
322 | Jenkins.XSTREAM.registerConverter(new XvfbEnvironmentConverter());
323 |
324 | final XmlFile fileOfZombies = zombiesFile();
325 |
326 | if (fileOfZombies.exists()) {
327 | try {
328 | @SuppressWarnings("unchecked")
329 | final ConcurrentHashMap> oldZombies = (ConcurrentHashMap>) fileOfZombies.read();
330 | fileOfZombies.delete();
331 | return oldZombies;
332 | } catch (final IOException ignore) {
333 | // ignore
334 | }
335 | }
336 |
337 | return new ConcurrentHashMap>();
338 | }
339 |
340 | static void shutdownAndCleanup(final XvfbEnvironment xvfbEnvironment, final Launcher launcher, final TaskListener listener) throws IOException, InterruptedException {
341 |
342 | listener.getLogger().println(Messages.XvfbBuildWrapper_Stopping());
343 |
344 | try {
345 | launcher.kill(Collections.singletonMap(JENKINS_XVFB_COOKIE, xvfbEnvironment.cookie));
346 | final FilePath frameBufferPath = new FilePath(launcher.getChannel(), xvfbEnvironment.frameBufferDir);
347 | frameBufferPath.deleteRecursive();
348 | } catch (final ChannelClosedException e) {
349 | synchronized (zombies) {
350 | final Computer currentComputer = Computer.currentComputer();
351 | final String computerName = currentComputer.getName();
352 |
353 | List zombiesAtComputer = zombies.get(computerName);
354 |
355 | if (zombiesAtComputer == null) {
356 | zombiesAtComputer = new CopyOnWriteArrayList();
357 | zombies.put(computerName, zombiesAtComputer);
358 | }
359 |
360 | zombiesAtComputer.add(new XvfbEnvironment(xvfbEnvironment.cookie, xvfbEnvironment.frameBufferDir, xvfbEnvironment.displayName, false));
361 |
362 | final XmlFile fileOfZombies = zombiesFile();
363 | fileOfZombies.write(zombies);
364 | }
365 | }
366 | }
367 |
368 | private static void shutdownAndCleanupZombie(final Channel channel, final XvfbEnvironment zombie, final TaskListener listener) throws IOException, InterruptedException {
369 |
370 | listener.getLogger().println(Messages.XvfbBuildWrapper_KillingZombies(zombie.displayName, zombie.frameBufferDir));
371 |
372 | try {
373 | channel.call(new MasterToSlaveCallable() {
374 | private static final long serialVersionUID = 1L;
375 |
376 | @Override
377 | public Void call() throws InterruptedException {
378 | final ProcessTree processTree = ProcessTree.get();
379 |
380 | for (final Iterator i = processTree.iterator(); i.hasNext();) {
381 | final OSProcess osProcess = i.next();
382 |
383 | final EnvVars environment = osProcess.getEnvironmentVariables();
384 |
385 | final String processCookie = environment.get(JENKINS_XVFB_COOKIE);
386 |
387 | if (processCookie != null && processCookie.equals(zombie.cookie)) {
388 | osProcess.kill();
389 | final File zombieDir = new File(zombie.frameBufferDir);
390 | if (!zombieDir.delete()) {
391 | zombieDir.deleteOnExit();
392 | }
393 | }
394 | }
395 |
396 | return null;
397 | }
398 | });
399 | } catch (final InterruptedException e) {
400 | // if we propagate the exception, slave will be obstructed from going online
401 | listener.getLogger().println(Messages.XvfbBuildWrapper_ZombieSlainFailed());
402 | e.printStackTrace(listener.getLogger());
403 | }
404 | }
405 |
406 | private static XmlFile zombiesFile() {
407 | return new XmlFile(Jenkins.XSTREAM, new File(Jenkins.get().getRootDir(), XvfbEnvironment.class.getName() + "-zombies.xml"));
408 | };
409 |
410 | /** Name of the installation used in a configured job. */
411 | private String installationName;
412 |
413 | /** X11 DISPLAY name, if NULL chosen based on current executor number. */
414 | private Integer displayName;
415 |
416 | /** Xvfb screen argument, in the form WxHxD (width x height x pixel depth), i.e. 800x600x8. */
417 | private String screen = DEFAULT_SCREEN;
418 |
419 | /** Should the Xvfb output be displayed in job output. */
420 | private boolean debug = false;
421 |
422 | /** Time in milliseconds to wait for Xvfb initialization, by default 1 second. */
423 | private long timeout = 1;
424 |
425 | /** Offset for display names, default is 1. Display names are taken from build executor's number, i.e. if the build is performed by executor 4, and offset is 100, display name will be 104. */
426 | private int displayNameOffset = 1;
427 |
428 | /** Additional options to be passed to Xvfb */
429 | private String additionalOptions;
430 |
431 | /** Should the Xvfb display be around for post build actions, i.e. should it terminate with the whole build */
432 | private boolean shutdownWithBuild = false;
433 |
434 | /** Let Xvfb pick display number */
435 | private boolean autoDisplayName = false;
436 |
437 | /** Run only on nodes labeled */
438 | private String assignedLabels;
439 |
440 | /** Run on same node in parallel */
441 | private boolean parallelBuild = false;
442 |
443 | @DataBoundConstructor
444 | public Xvfb() {
445 | }
446 |
447 | protected ArgumentListBuilder createCommandArguments(final XvfbInstallation installation, final FilePath frameBufferDir, final int displayNameUsed) {
448 | final String path = installation.getHome();
449 |
450 | final ArgumentListBuilder cmd;
451 | if ("".equals(path)) {
452 | cmd = new ArgumentListBuilder("Xvfb");
453 | }
454 | else {
455 | cmd = new ArgumentListBuilder(path + "/Xvfb");
456 | }
457 |
458 | if (autoDisplayName) {
459 | cmd.add("-displayfd", STDERR_FD);
460 | }
461 | else {
462 | cmd.add(":" + displayNameUsed);
463 | }
464 |
465 | if (screen != null && !screen.trim().isEmpty()) {
466 | cmd.add("-screen").add("0").add(screen);
467 | }
468 |
469 | cmd.add("-fbdir").add(frameBufferDir);
470 |
471 | if (additionalOptions != null) {
472 | cmd.addTokenized(additionalOptions);
473 | }
474 | return cmd;
475 | }
476 |
477 | public String getAdditionalOptions() {
478 | return additionalOptions;
479 | }
480 |
481 | public String getAssignedLabels() {
482 | return assignedLabels;
483 | }
484 |
485 | @Override
486 | public XvfbBuildWrapperDescriptor getDescriptor() {
487 | return (XvfbBuildWrapperDescriptor) super.getDescriptor();
488 | }
489 |
490 | public Integer getDisplayName() {
491 | return displayName;
492 | }
493 |
494 | public int getDisplayNameOffset() {
495 | return displayNameOffset;
496 | }
497 |
498 | public XvfbInstallation getInstallation(final EnvVars env, final Node node, final TaskListener listener) {
499 | final XvfbInstallation[] installations = getDescriptor().getInstallations();
500 |
501 | // if there is only one installation and no name specified use that
502 | if (installationName == null && installations.length == 1) {
503 | return installations[0];
504 | }
505 |
506 | // if no installation name specified use 'default'
507 | final String installationNameToUse = Optional.fromNullable(installationName).or("default");
508 |
509 | for (final XvfbInstallation installation : installations) {
510 | if (installationNameToUse.equals(installation.getName())) {
511 | try {
512 | return installation.forEnvironment(env).forNode(node, TaskListener.NULL);
513 | } catch (final IOException e) {
514 | listener.fatalError("IOException while locating installation", e);
515 | } catch (final InterruptedException e) {
516 | listener.fatalError("InterruptedException while locating installation", e);
517 | }
518 | }
519 | }
520 |
521 | return null;
522 | }
523 |
524 | public String getInstallationName() {
525 | return installationName;
526 | }
527 |
528 | public String getScreen() {
529 | return screen;
530 | }
531 |
532 | public long getTimeout() {
533 | return timeout;
534 | }
535 |
536 | public boolean isAutoDisplayName() {
537 | return autoDisplayName;
538 | }
539 |
540 | public boolean isDebug() {
541 | return debug;
542 | }
543 |
544 | public boolean isParallelBuild() {
545 | return parallelBuild;
546 | }
547 |
548 | public boolean isShutdownWithBuild() {
549 | return shutdownWithBuild;
550 | }
551 |
552 | private XvfbEnvironment launchXvfb(final Run, ?> run, final FilePath workspace, final Launcher launcher, final TaskListener listener) throws IOException, InterruptedException {
553 | final Computer currentComputer = workspace.toComputer();
554 | if (currentComputer == null) {
555 | throw new IllegalStateException("Unable to access workspace on a node running the build, cannot continue.");
556 | }
557 |
558 | int displayNameUsed = determineDisplayName(run, currentComputer);
559 |
560 | final Node currentNode = currentComputer.getNode();
561 | if (currentNode == null) {
562 | throw new IllegalStateException("Node is being removed, cannot continue");
563 | }
564 |
565 | if (!workspace.exists()) {
566 | workspace.mkdirs();
567 | }
568 |
569 | final FilePath frameBufferDir = workspace.createTempDir(".xvfb-" + run.getId() + "-", ".fbdir");
570 |
571 | final EnvVars environment = currentComputer.getEnvironment();
572 | final XvfbInstallation installation = getInstallation(environment, currentNode, listener);
573 |
574 | if (installation == null) {
575 | listener.error(Messages.XvfbBuildWrapper_NoInstallationsConfigured());
576 |
577 | throw new RunnerAbortedException();
578 | }
579 |
580 | final ArgumentListBuilder cmd = createCommandArguments(installation, frameBufferDir, displayNameUsed);
581 |
582 | final ProcStarter procStarter = launcher.launch().cmds(cmd);
583 |
584 | final ByteArrayOutputStream stdoutStream = new ByteArrayOutputStream();
585 | final OutputStream stdout = debug ? listener.getLogger() : stdoutStream;
586 |
587 | final ByteArrayOutputStream stderrStream = new ByteArrayOutputStream();
588 | final AutoDisplayNameFilterStream stderr = new AutoDisplayNameFilterStream(debug ? listener.getLogger() : stderrStream);
589 |
590 | listener.getLogger().print(Messages.XvfbBuildWrapper_Starting());
591 | procStarter.stdout(stdout).stderr(stderr);
592 |
593 | final String cookie = UUID.randomUUID().toString();
594 | procStarter.envs(Collections.singletonMap(JENKINS_XVFB_COOKIE, cookie));
595 |
596 | final Proc process = procStarter.start();
597 |
598 | Thread.sleep(timeout * MILLIS_IN_SECOND);
599 |
600 | if (!process.isAlive()) {
601 | if (!debug) {
602 | listener.getLogger().write(stdoutStream.toByteArray());
603 | listener.getLogger().write(stderrStream.toByteArray());
604 | }
605 |
606 | listener.getLogger().println();
607 |
608 | listener.error(Messages.XvfbBuildWrapper_FailedToStart());
609 |
610 | throw new RunnerAbortedException();
611 | }
612 |
613 | if (autoDisplayName) {
614 | displayNameUsed = stderr.getDisplayNumber();
615 | }
616 |
617 | final XvfbEnvironment xvfbEnvironment = new XvfbEnvironment(cookie, frameBufferDir.getRemote(), displayNameUsed, shutdownWithBuild);
618 |
619 | return xvfbEnvironment;
620 | }
621 |
622 | private int determineDisplayName(final Run, ?> run, final Computer currentComputer) {
623 | if (displayName != null) {
624 | return displayName;
625 | }
626 |
627 | if (autoDisplayName) {
628 | return -1;
629 | }
630 |
631 | final Executor executor = run.getExecutor();
632 |
633 | if (executor == null) {
634 | throw new IllegalStateException("Invoked outside of build: executor of the run is null");
635 | }
636 |
637 | final int executorNumber= executor.getNumber();
638 |
639 | if (parallelBuild) {
640 | final Computer[] computers = Jenkins.get().getComputers();
641 | final int nodeIndex = Arrays.binarySearch(computers, currentComputer, ComputerNameComparator.INSTANCE);
642 |
643 | return nodeIndex * 100 + executorNumber + displayNameOffset;
644 | }
645 | else {
646 | return executorNumber + displayNameOffset;
647 | }
648 | }
649 |
650 | @DataBoundSetter
651 | public void setAdditionalOptions(final String additionalOptions) {
652 | this.additionalOptions = additionalOptions;
653 | }
654 |
655 | @DataBoundSetter
656 | public void setAssignedLabels(final String assignedLabels) {
657 | this.assignedLabels = assignedLabels;
658 | }
659 |
660 | @DataBoundSetter
661 | public void setAutoDisplayName(final boolean autoDisplayName) {
662 | this.autoDisplayName = autoDisplayName;
663 | }
664 |
665 | @DataBoundSetter
666 | public void setDebug(final boolean debug) {
667 | this.debug = debug;
668 | }
669 |
670 | @DataBoundSetter
671 | public void setDisplayName(final Integer displayName) {
672 | this.displayName = displayName;
673 | }
674 |
675 | @DataBoundSetter
676 | public void setDisplayNameOffset(final int displayNameOffset) {
677 | this.displayNameOffset = displayNameOffset;
678 | }
679 |
680 | @DataBoundSetter
681 | public void setInstallationName(final String installationName) {
682 | this.installationName = installationName;
683 | }
684 |
685 | @DataBoundSetter
686 | public void setParallelBuild(final boolean parallelBuild) {
687 | this.parallelBuild = parallelBuild;
688 | }
689 |
690 | @DataBoundSetter
691 | public void setScreen(final String screen) {
692 | this.screen = screen;
693 | }
694 |
695 | @DataBoundSetter
696 | public void setShutdownWithBuild(final boolean shutdownWithBuild) {
697 | this.shutdownWithBuild = shutdownWithBuild;
698 | }
699 |
700 | @DataBoundSetter
701 | public void setTimeout(final long timeout) {
702 | this.timeout = timeout;
703 | }
704 |
705 | @Override
706 | public void setUp(final Context context, final Run, ?> run, final FilePath workspace, final Launcher launcher, final TaskListener listener, final EnvVars initialEnvironment)
707 | throws IOException, InterruptedException {
708 | if (assignedLabels != null && !assignedLabels.trim().isEmpty()) {
709 | final Label label;
710 | try {
711 | label = Label.parseExpression(assignedLabels);
712 | } catch (final ANTLRException e) {
713 | throw new IOException(e);
714 | }
715 |
716 | final Computer computer = Computer.currentComputer();
717 | final Node node = computer.getNode();
718 |
719 | if (!label.matches(node)) {
720 | // not running on node with requested label
721 | return;
722 | }
723 | }
724 |
725 | if (!launcher.isUnix()) {
726 | listener.getLogger().println(Messages.XvfbBuildWrapper_NotUnix());
727 |
728 | // we will not run on non Unix machines
729 | return;
730 | }
731 |
732 | @SuppressWarnings("rawtypes")
733 | final Run rawRun = run;
734 | final XvfbEnvironment xvfbEnvironment = launchXvfb(rawRun, workspace, launcher, listener);
735 | run.addAction(xvfbEnvironment);
736 |
737 | context.env("DISPLAY", ":" + xvfbEnvironment.displayName);
738 | context.setDisposer(new XvfbDisposer(xvfbEnvironment));
739 | }
740 | }
741 |
--------------------------------------------------------------------------------