Sends a notice to IRC with the current build result.
2 |
3 |
ircNotification([channel, nick, server])
4 |
5 | Sends a notification to IRC with the specified channel,
6 | nick, and server. By default it will connect to
7 | irc.mozilla.org:6697 as fxtest and join the
8 | #fx-test-alerts channel.
9 |
5 | Writes a JSON file containing the items from the capabilities
6 | map to the specified path (for use by
7 |
8 | pytest-selenium). If omitted, the path defaults to
9 | capabilities.json in the working directory.
10 |
8 | Publishes the message, to Pulse with the specified
9 | exchange and routingKey. If a schema is provided
10 | then it will be used to check that the message is valid. If the message
11 | fails to pass validation, details will be output to the console log and
12 | ProcessingException will be thrown.
13 |
14 |
--------------------------------------------------------------------------------
/vars/writeCapabilities.groovy:
--------------------------------------------------------------------------------
1 | import groovy.json.JsonOutput
2 |
3 | /** Write capabilities to JSON file
4 | *
5 | * @param desiredCapabilities capabilities to include in the file
6 | * @param path destination for capabilities file
7 | */
8 | def call(Map desiredCapabilities, String path = 'capabilities.json') {
9 | defaultCapabilities = [
10 | build: env.BUILD_TAG,
11 | public: 'public restricted'
12 | ]
13 | capabilities = defaultCapabilities.clone()
14 | capabilities.putAll(desiredCapabilities)
15 | json = JsonOutput.toJson([capabilities: capabilities])
16 | writeFile file: path, text: json
17 | }
18 |
--------------------------------------------------------------------------------
/vars/publishToPulse.groovy:
--------------------------------------------------------------------------------
1 | /** Publish message to a Pulse exchange
2 | *
3 | * @param exchange exchange to send message to
4 | * @param routingKey routing key to use
5 | * @param message message to send
6 | * @param schema optional schema to validate against
7 | */
8 | def call(String exchange,
9 | String routingKey,
10 | String message,
11 | String schema = null) {
12 | if ( schema != null ) {
13 | def jsonSchemaValidator = new org.mozilla.fxtest.JsonSchemaValidator()
14 | jsonSchemaValidator.validate(message, schema)
15 | }
16 | def pulse = new org.mozilla.fxtest.Pulse()
17 | pulse.publish(exchange, routingKey, message)
18 | }
19 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Community Participation Guidelines
2 |
3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines.
4 | For more details, please read the
5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
6 |
7 | ## How to Report
8 | For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page.
9 |
10 |
16 |
--------------------------------------------------------------------------------
/vars/ircNotification.groovy:
--------------------------------------------------------------------------------
1 | /** Send a notice to IRC with the current build result
2 | *
3 | * @param channel channel to join
4 | * @param nick nickname to use
5 | * @param server server to connect to
6 | */
7 | def call(String channel = '#fx-test-alerts',
8 | String nick = 'fxtest',
9 | String server = 'irc.mozilla.org:6697') {
10 | nick = "${nick}-${BUILD_NUMBER}"
11 | result = currentBuild.result ?: 'SUCCESS'
12 | message = "Project ${JOB_NAME} build #${BUILD_NUMBER}: ${result}: ${BUILD_URL}"
13 | sh """
14 | (
15 | echo NICK ${nick}
16 | echo USER ${nick} 8 * : ${nick}
17 | sleep 5
18 | echo "JOIN ${channel}"
19 | echo "NOTICE ${channel} :${message}"
20 | echo QUIT
21 | ) | openssl s_client -connect ${server}
22 | """
23 | }
24 |
--------------------------------------------------------------------------------
/vars/submitToTreeherder.txt:
--------------------------------------------------------------------------------
1 |
11 | Submits the build result for project to Treeherder using the
12 | specified jobSymbol and jobName. If provided,
13 | files located by artifactPath and logPath will be
14 | published to Amazon S3 and linked from the build results. By default the
15 | job will have a groupSymbol of 'j', and a
16 | groupName of 'Executed by Jenkins'. To leave the job ungrouped
17 | pass a groupSymbol of '?'.
18 |
19 |
--------------------------------------------------------------------------------
/src/org/mozilla/fxtest/Pulse.groovy:
--------------------------------------------------------------------------------
1 | package org.mozilla.fxtest
2 |
3 | @Grab(group='com.rabbitmq', module='amqp-client', version='4.1.0')
4 | import com.rabbitmq.client.AMQP
5 | import com.rabbitmq.client.ConnectionFactory
6 | import com.rabbitmq.client.MessageProperties
7 |
8 | @NonCPS
9 | def publish(String exchange, String routingKey, String payload) {
10 | def factory = new ConnectionFactory()
11 | factory.setUri("amqp://${PULSE}@pulse.mozilla.org:5671")
12 | factory.useSslProtocol()
13 | def connection = factory.newConnection()
14 | def channel = connection.createChannel()
15 | channel.exchangeDeclare exchange, 'topic', true
16 |
17 | def properties = new AMQP.BasicProperties.Builder()
18 | .contentType('application/json')
19 | .deliveryMode(2)
20 | .build()
21 |
22 | channel.basicPublish exchange, routingKey, properties, payload.bytes
23 | echo "Published payload to Pulse on $exchange with routing key $routingKey."
24 | echo payload
25 | channel.close()
26 | connection.close()
27 | }
28 |
--------------------------------------------------------------------------------
/vars/publishToS3.groovy:
--------------------------------------------------------------------------------
1 | /** Publish files to an Amazon S3 bucket
2 | *
3 | * @param path path to the file(s) to publish
4 | * @param bucket bucket and destination for file(s)
5 | * @param region region bucket belongs to
6 | * @return list of [name, url] maps for published file(s)
7 | */
8 | def call(String path, String bucket, String region = 'us-east-1') {
9 | step([$class: 'S3BucketPublisher',
10 | consoleLogLevel: 'INFO',
11 | dontWaitForConcurrentBuildCompletion: false,
12 | entries: [[
13 | bucket: "$bucket/${BUILD_TAG}",
14 | excludedFile: '',
15 | flatten: true,
16 | gzipFiles: true,
17 | keepForever: false,
18 | managedArtifacts: false,
19 | noUploadOnFailure: false,
20 | selectedRegion: region,
21 | showDirectlyInBrowser: false,
22 | sourceFile: path,
23 | storageClass: 'STANDARD',
24 | uploadFromSlave: false,
25 | useServerSideEncryption: false]],
26 | pluginFailureResultConstraint: 'SUCCESS',
27 | profileName: 'fx-test-jenkins-s3-publisher'])
28 | return getLinks(bucket, path)
29 | }
30 |
31 | def getLinks(bucket, path) {
32 | def links = []
33 | def files = findFiles(glob: path)
34 | for ( f in files ) {
35 | links.add([
36 | name: f.name,
37 | url: "https://s3.amazonaws.com/$bucket/${BUILD_TAG}/$f.name"])
38 | }
39 | return links
40 | }
41 |
--------------------------------------------------------------------------------
/src/org/mozilla/fxtest/JsonSchemaValidator.groovy:
--------------------------------------------------------------------------------
1 | package org.mozilla.fxtest
2 |
3 | @Grab(group='com.fasterxml.jackson.core', module='jackson-databind', version='2.8.8')
4 | import com.fasterxml.jackson.databind.ObjectMapper
5 |
6 | @Grab(group='com.fasterxml.jackson.dataformat', module='jackson-dataformat-yaml', version='2.8.3')
7 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
8 |
9 | @Grab(group='com.github.fge', module='json-schema-validator', version='2.2.5')
10 | import com.github.fge.jackson.JsonLoader
11 | import com.github.fge.jsonschema.core.exceptions.ProcessingException
12 | import com.github.fge.jsonschema.main.JsonSchemaFactory
13 |
14 | @NonCPS
15 | def validate(payload, schema) {
16 | def mapper = new ObjectMapper()
17 | def yamlFactory = new YAMLFactory()
18 | def jsonSchemaFactory = JsonSchemaFactory.byDefault()
19 | def schemaJsonNode = mapper.readTree(yamlFactory.createParser(schema))
20 | def jsonSchema = jsonSchemaFactory.getJsonSchema(schemaJsonNode)
21 | def payloadJsonNode = mapper.readTree(payload)
22 | def report = jsonSchema.validate(payloadJsonNode)
23 | if ( !report.isSuccess() ) {
24 | for ( message in report ) {
25 | echo "$message"
26 | }
27 | throw new ProcessingException('Failure validating Pulse payload against schema.')
28 | } else {
29 | echo 'Sucessfully validated Pulse payload against schema.'
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/serviceBookTests.groovy:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import org.junit.Before
4 | import org.junit.Test
5 |
6 | import com.lesfurets.jenkins.unit.BasePipelineTest
7 |
8 | class ServiceBookTests extends BasePipelineTest {
9 |
10 |
11 | @Override
12 | @Before
13 | void setUp() throws Exception {
14 | super.setUp()
15 | String fileContents = new File('tests/projects.json').text
16 | def resp = [content: fileContents]
17 | helper.registerAllowedMethod('httpRequest', [String.class], {url -> resp})
18 | }
19 |
20 | @Test
21 | void allowedOrgs() {
22 | def script = loadScript('src/org/mozilla/fxtest/ServiceBook.groovy')
23 | for ( i in ['Kinto', 'mozilla', 'mozilla-services'] ) {
24 | assert script.validURL("https://github.com/${i}/foo") == true
25 | }
26 | }
27 |
28 | @Test
29 | void disallowedOrgs() {
30 | def script = loadScript('src/org/mozilla/fxtest/ServiceBook.groovy')
31 | for ( i in ['kinto', 'Mozilla', 'davehunt'] ) {
32 | assert script.validURL("https://github.com/${i}/foo") == false
33 | }
34 | }
35 |
36 | @Test
37 | void projectWithTests() {
38 | def script = loadScript('src/org/mozilla/fxtest/ServiceBook.groovy')
39 |
40 | // kinto has 1 jenkins enabled project
41 | def tests = script.getProjectTests('kinto')
42 | assert tests != null
43 | assert tests.size() == 1
44 |
45 | // this project does not exists
46 | assert script.getProjectTests('IDONTEXIST') == null
47 |
48 | // Balrog has zero jenkins tests
49 | def balrog = script.getProjectTests('Balrog')
50 | assert balrog.size() == 0
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/submitToTreeherderTests.groovy:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import org.junit.Before
4 | import org.junit.Test
5 |
6 | import com.lesfurets.jenkins.unit.BasePipelineTest
7 |
8 | class SubmitToTreeherderTests extends BasePipelineTest {
9 |
10 |
11 | @Override
12 | @Before
13 | void setUp() throws Exception {
14 | super.setUp()
15 | }
16 |
17 | @Test
18 | void defaultGroup() {
19 | def script = loadScript('vars/submitToTreeherder.groovy')
20 | def display = script.getDisplay('myJobSymbol', 'myJobName')
21 | assert display.groupSymbol == 'j'
22 | assert display.groupName == 'Executed by Jenkins'
23 | }
24 |
25 | @Test
26 | void customGroup() {
27 | def script = loadScript('vars/submitToTreeherder.groovy')
28 | def display = script.getDisplay('myJobSymbol', 'myJobName', 'myGroupSymbol', 'myGroupName')
29 | assert display.groupSymbol == 'myGroupSymbol'
30 | assert display.groupName == 'myGroupName'
31 | }
32 |
33 | @Test
34 | void noGroup() {
35 | def script = loadScript('vars/submitToTreeherder.groovy')
36 | def display = script.getDisplay('myJobSymbol', 'myJobName', '?', null)
37 | assert display.groupSymbol == '?'
38 | assert !display.containsKey('groupName')
39 | }
40 |
41 | @Test
42 | void getLogsReturnsListOfLinkMaps() {
43 | helper.registerAllowedMethod('publishToS3', [String.class, String.class], {path, bucket ->
44 | [[url:'linkURL', name:'linkName']]})
45 | def script = loadScript('vars/submitToTreeherder.groovy')
46 | def links = script.getLogs('foo')
47 | assert links.size() == 1
48 | assert links[0].url == 'linkURL'
49 | assert links[0].name == 'linkName'
50 | }
51 |
52 | @Test
53 | void tbplLogNameIsModified() {
54 | helper.registerAllowedMethod('publishToS3', [String.class, String.class], {path, bucket ->
55 | [[url:'linkURL', name:'my-tbpl-log']]})
56 | def script = loadScript('vars/submitToTreeherder.groovy')
57 | def links = script.getLogs('foo')
58 | assert links.size() == 1
59 | assert links[0].url == 'linkURL'
60 | assert links[0].name == 'buildbot_text'
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/org/mozilla/fxtest/ServiceBook.groovy:
--------------------------------------------------------------------------------
1 | package org.mozilla.fxtest
2 | import groovy.json.JsonSlurperClassic;
3 |
4 | // Servicebook iterator to get operational tests for a given project
5 | Object getProjectTests(String name) {
6 | def projectName = name.toLowerCase();
7 | def resp = httpRequest "https://servicebook-api.stage.mozaws.net/api/project";
8 | def jsonSlurper = new JsonSlurperClassic();
9 | def projects = jsonSlurper.parseText(resp.content);
10 |
11 | for (project in projects.data) {
12 | if (project.name.toLowerCase() == projectName) {
13 | echo "The project " + name + " was found!"
14 |
15 | def jenkin_tests = [];
16 | for (test in project.tests) {
17 | if (test.jenkins_pipeline) {
18 | echo "Adding one test " + test.name
19 | jenkin_tests << test;
20 | }
21 | }
22 | return jenkin_tests;
23 | }
24 | }
25 | echo "The project " + name + " was not found"
26 | return null;
27 | }
28 |
29 |
30 |
31 | def validURL(url) {
32 | allowedOrgs = ['Kinto', 'mozilla', 'mozilla-services']
33 |
34 | for (allowedOrg in allowedOrgs) {
35 | if (url.startsWith('https://github.com/' + allowedOrg + '/')) {
36 | return true
37 | }
38 | }
39 | echo url + " is not a valid url"
40 | return false
41 | }
42 |
43 |
44 | def runStage(test) {
45 | stage(test.name) {
46 | if (validURL(test.url)) {
47 | echo "checking out " + test.url + ".git"
48 | node {
49 | checkout([
50 | $class: 'GitSCM',
51 | branches: [[name: '*/master']],
52 | doGenerateSubmoduleConfigurations: false,
53 | extensions: [[$class: 'CleanCheckout']],
54 | submoduleCfg: [],
55 | userRemoteConfigs: [[url: test.url + '.git']]
56 | ])
57 | }
58 | echo "checked out"
59 | node {
60 | sh "chmod +x run"
61 | sh "${WORKSPACE}/run"
62 | }
63 | } else {
64 | throw new IOException(test.url + " is not allowed")
65 | }
66 | }
67 | }
68 |
69 | def testProject(String name) {
70 | echo "Testing project " + name
71 | def failures = []
72 | def tests = getProjectTests(name)
73 |
74 | for (test in tests) {
75 | try {
76 | echo "Running " + test
77 | echo "URL is " + test.url
78 | runStage(test)
79 | } catch (exc) {
80 | echo test.name + " failed"
81 | echo "Caught: ${exc}"
82 | failures.add(test.name)
83 | }
84 | }
85 | stage('Ship it!') {
86 | node {
87 | if (failures.size == 0) {
88 | sh 'exit 0'
89 | } else {
90 | sh 'exit 1'
91 | }
92 | }
93 | }
94 | }
95 |
96 |
97 | return this;
98 |
--------------------------------------------------------------------------------
/vars/submitToTreeherder.groovy:
--------------------------------------------------------------------------------
1 | import groovy.json.JsonOutput
2 | import java.text.SimpleDateFormat
3 |
4 | /** Submit build results to Treeherder
5 | *
6 | * @param project project to submit results for
7 | * @param jobSymbol symbol for the job
8 | * @param jobName name for the job
9 | * @param artifactPath path for artifact(s) to publish
10 | * @param logPath path for log(s) to publish
11 | * @param groupSymbol symbol for the job group
12 | * @param groupName name for the job group
13 | */
14 | def call(String project,
15 | String jobSymbol,
16 | String jobName,
17 | String artifactPath = null,
18 | String logPath = null,
19 | String groupSymbol = null,
20 | String groupName = null) {
21 | machine = getMachine()
22 | payload = [
23 | taskId: UUID.randomUUID().toString(),
24 | buildSystem: machine['name'],
25 | origin: [kind: 'github.com', project: project, revision: "${GIT_COMMIT}"],
26 | display: getDisplay(jobSymbol, jobName, groupSymbol, groupName),
27 | state: 'completed',
28 | result: getResult(),
29 | jobKind: 'test',
30 | timeScheduled: getDateTime(currentBuild.timeInMillis),
31 | timeStarted: getDateTime(currentBuild.startTimeInMillis),
32 | timeCompleted: getDateTime(currentBuild.startTimeInMillis + currentBuild.duration),
33 | reason: 'scheduled', // TODO build cause: currentBuild.rawBuild.getCause().getShortDescription()
34 | productName: project,
35 | buildMachine: machine,
36 | runMachine: machine,
37 | jobInfo: [links: getJobLinks(artifactPath)],
38 | logs: getLogs(logPath),
39 | version: 1
40 | ]
41 |
42 | // TODO include ec2-metadata output in payload
43 | exchange = "exchange/${PULSE_USR}/jobs"
44 | routingKey = "${PULSE_USR}.${payload.productName}"
45 |
46 | // Skipping schema validation due to https://github.com/mozilla/fxtest-jenkins-pipeline/issues/22
47 | // schema = libraryResource 'org/mozilla/fxtest/pulse/schemas/treeherder.yml'
48 | // publishToPulse(exchange, routingKey, JsonOutput.toJson(payload), schema)
49 |
50 | publishToPulse(exchange, routingKey, JsonOutput.toJson(payload))
51 | treeherderURL = "https://treeherder.mozilla.org/#/jobs?repo=${payload.productName}&revision=${payload.origin.revision}"
52 | echo "Results will be available to view at $treeherderURL"
53 | }
54 |
55 | def getMachine() {
56 | os = System.getProperty("os.name").toLowerCase().replaceAll('\\W', '-')
57 | version = System.getProperty("os.version").toLowerCase().replaceAll('\\W', '-')
58 | architecture = System.getProperty("os.arch")
59 | return [
60 | name: new URI(env.JENKINS_URL).getHost().split('\\.')[0],
61 | platform: [os, version, architecture].join('-'),
62 | os: os,
63 | architecture: architecture
64 | ]
65 | }
66 |
67 | def getDisplay(jobSymbol, jobName, groupSymbol = null, groupName = null) {
68 | display = [
69 | jobSymbol: jobSymbol,
70 | jobName: jobName,
71 | groupSymbol: groupSymbol ? groupSymbol : 'j'
72 | ]
73 | if ( groupName != null ) {
74 | display.groupName = groupName
75 | } else if ( groupSymbol != '?' ) {
76 | display.groupName = 'Executed by Jenkins'
77 | }
78 | return display
79 | }
80 |
81 | def getResult() {
82 | switch(currentBuild.result) {
83 | case 'FAILURE':
84 | case 'UNSTABLE':
85 | return 'fail'
86 | case 'SUCCESS':
87 | case null:
88 | return 'success'
89 | default:
90 | return 'unknown'
91 | }
92 | }
93 |
94 | def getDateTime(timeInMillis) {
95 | time = new Date(timeInMillis)
96 | return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(time)
97 | }
98 |
99 | def getJobLinks(artifactPath) {
100 | links = [[url: env.BUILD_URL, linkText: env.BUILD_TAG, label: 'build']]
101 | if ( artifactPath != null ) {
102 | artifactLinks = publishToS3(artifactPath, 'net-mozaws-stage-fx-test-treeherder')
103 | for (link in artifactLinks) {
104 | links.add([url: link.url, linkText: link.name, label: 'artifact uploaded'])
105 | }
106 | }
107 | return links
108 | }
109 |
110 | def getLogs(logPath) {
111 | links = []
112 | if ( logPath != null ) {
113 | logLinks = publishToS3(logPath, 'net-mozaws-stage-fx-test-treeherder')
114 | for (link in logLinks) {
115 | name = link.name =~ 'tbpl' ? 'buildbot_text' : link.name
116 | links.add([url: link.url, name: name])
117 | }
118 | }
119 | return links
120 | }
121 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Shared libraries for Jenkins Pipeline
2 | This repository holds
3 | [shared libraries](https://jenkins.io/doc/book/pipeline/shared-libraries/) for
4 | [Jenkins pipelines](https://jenkins.io/doc/book/pipeline/) used by
5 | [Firefox Test Engineering](https://wiki.mozilla.org/TestEngineering).
6 |
7 | [](https://travis-ci.org/mozilla/fxtest-jenkins-pipeline)
8 |
9 | # Pipeline Steps
10 | ## ircNotification
11 | Sends a notification to IRC with the specified `channel`, `nick`, and `server`.
12 | By default it will connect to `irc.mozilla.org:6697` as `fxtest` and join the
13 | `#fx-test-alerts` channel.
14 |
15 | ### Examples
16 | ```groovy
17 | // use defaults
18 | ircNotification()
19 |
20 | // specify a channel
21 | ircNotification('#fx-test-alerts')
22 |
23 | // specify all values
24 | ircNotification(
25 | channel: '#fx-test-alerts',
26 | nick: 'fxtest',
27 | server: 'irc.mozilla.org:6697'
28 | )
29 | ```
30 |
31 | ## publishToPulse
32 | Publishes a `message`, to [Pulse] with the specified `exchange` and
33 | `routingKey`. If a schema is provided then it will be used to check that the
34 | message is valid. If the message fails to pass validation, details will be
35 | output to the console log and `ProcessingException` will be thrown.
36 |
37 | ### Requirements
38 | * Pulse credentials to be configured in Jenkins.
39 | * [Pipeline Model Definition Plugin] v1.2 or later.
40 | * [Pipeline Utility Steps Plugin].
41 |
42 | ### Examples
43 | ```groovy
44 | // configure environment variables from credentials
45 | environment {
46 | PULSE = credentials('PULSE')
47 | }
48 |
49 | // send message without schema validation
50 | publishToPulse(
51 | exchange: "exchange/${PULSE_USR}",
52 | routingKey: "${PULSE_USR}.foo",
53 | message: 'foo'
54 | )
55 |
56 | // send message with schema validation from resources
57 | schema = libraryResource 'org/mozilla/fxtest/pulse/schemas/treeherder.json'
58 | publishToPulse(
59 | exchange: "exchange/${PULSE_USR}",
60 | routingKey: "${PULSE_USR}.foo",
61 | message: 'foo',
62 | schema: schema
63 | )
64 | ```
65 |
66 | ## publishToS3
67 | Publishes the files at `path` to the specified Amazon S3 `bucket` and `region`.
68 | Defaults to region `us-east-1`.
69 |
70 | ### Requirements
71 | * [Amazon S3] bucket with appropriate permissions.
72 | * [S3 Plugin] with profile configured in Jenkins.
73 |
74 | ### Examples
75 | ```groovy
76 | // single file with default bucket and region
77 | publishToS3('results.html')
78 |
79 | // multiple files with specified bucket and region
80 | publishToS3(
81 | path: 'results/*',
82 | bucket: 'foo',
83 | region: 'bar'
84 | )
85 | ```
86 |
87 | ## submitToActiveData
88 | Publishes the structured log(s) at `logPath` to ActiveData.
89 |
90 | ### Requirements
91 | See [publishToS3](#publishtos3).
92 |
93 | ### Examples
94 | ```groovy
95 | submitToActiveData('results/raw.txt')
96 | ```
97 |
98 | ## submitToTreeherder
99 | Submits the build result for `project` to [Treeherder] using the specified
100 | `jobSymbol` and `jobName`. If provided, files located by `artifactPath` and
101 | `logPath` will be published to Amazon S3 and linked from the build results. By
102 | default the job will have a `groupSymbol` of 'j', and a `groupName` of
103 | 'Executed by Jenkins'. To leave the job ungrouped pass a `groupSymbol` of '?'.
104 |
105 | ### Requirements
106 | See [publishToS3](#publishtos3) and [publishToPulse](#publishtopulse).
107 |
108 | ### Examples
109 | ```groovy
110 | // submit default grouped build results without artifacts or logs
111 | submitToTreeherder(
112 | project: 'foo',
113 | jobSymbol: 'T',
114 | jobName: 'Tests'
115 | )
116 |
117 | // submit ungrouped build results without artifacts or logs
118 | submitToTreeherder(
119 | project: 'foo',
120 | jobSymbol: 'T',
121 | jobName: 'Tests',
122 | groupSymbol: '?'
123 | )
124 |
125 | // submit custom grouped build results with artifacts and log
126 | submitToTreeherder(
127 | project: 'foo',
128 | jobSymbol: 'I',
129 | jobName: 'Integration tests',
130 | artifactPath: 'results/*',
131 | logPath: 'results/tbpl.txt',
132 | groupSymbol: 'T',
133 | groupName: 'Tests'
134 | )
135 | ```
136 |
137 | ## writeCapabilities
138 | Writes a JSON file containing the items from the `capabilities` map to the
139 | specified `path` (for use by [pytest-selenium]). If omitted, the `path`
140 | defaults to `capabilities.json` in the working directory.
141 |
142 | ### Examples
143 | ```groovy
144 | capabilities = [
145 | browserName: 'Firefox',
146 | version: '51.0',
147 | platform: 'Windows 10'
148 | ]
149 |
150 | // write capabilities to default path
151 | writeCapabilities(capabilities)
152 |
153 | // write capabilities to specified path
154 | writeCapabilities(
155 | desiredCapabilities: capabilities,
156 | path: 'fx51win10.json'
157 | )
158 | ```
159 | # ServiceBook
160 | ## testProject
161 | Queries the Service Book project API for the given project `name`, iterates over
162 | its associated test repositories, checks them out from SCM, and executes their
163 | ```run``` file(s). Finally, it returns ```exit 0``` on successful/passing
164 | tests, and ```exit 1``` in the event of failed builds.
165 |
166 | ## Examples
167 | ```groovy
168 | @Library('fxtest') _
169 |
170 | def sb = new org.mozilla.fxtest.ServiceBook()
171 | sb.testProject('kinto')
172 | ```
173 |
174 | ## Requirements
175 | * [HTTP Request Plugin] v1.8.20 or later.
176 |
177 | # How to run tests
178 |
179 | Make sure you have the latest Gradle install and run:
180 |
181 | ```sh
182 | $ gradle check
183 | BUILD SUCCESSFUL in 1s
184 | 2 actionable tasks: 2 up-to-date
185 | ```
186 |
187 | # Version History
188 |
189 | ### 1.10 (2018-01-15)
190 | * Remove superfluous test stage when using Service Book.
191 | * Use `GIT_COMMIT` when reporting revision to Treeherder.
192 |
193 | ### 1.9 (2017-09-14)
194 | * Fixed the production Service Book API URL
195 |
196 | ### 1.8 (2017-09-14)
197 | * Greatly simplified Service Book API calls using the HTTP Request Plugin.
198 |
199 | ### 1.7 (2017-09-04)
200 | * Introduced ```ServiceBook``` class, with ```testProject``` method to execute tests for all pipeline associated with the specified project name.
201 |
202 | ### 1.6 (2017-04-13)
203 | * Changed TBPL log name to `buildbot_text` in Treeherder message for log parsing. ([#12](https://github.com/mozilla/fxtest-jenkins-pipeline/issues/12))
204 | * Switched to YAML schema for Treeherder message validation. ([#2](https://github.com/mozilla/fxtest-jenkins-pipeline/issues/2))
205 | * Added link to Treeherder results to console log. ([#11](https://github.com/mozilla/fxtest-jenkins-pipeline/issues/11))
206 | * Provided a default group for Treeherder jobs. ([#13](https://github.com/mozilla/fxtest-jenkins-pipeline/issues/13))
207 |
208 | ### 1.5 (2017-03-31)
209 | * Changed S3 profile to `fx-test-jenkins-s3-publisher`.
210 |
211 | ### 1.4 (2017-02-31)
212 | * Introduced `publishToS3`, `publishToPulse`, and `submitToTreeherder` steps.
213 |
214 | ### 1.3 (2017-02-23)
215 | * Don't mark jobs as unstable if `submitToActiveData` fails.
216 |
217 | ### 1.2 (2017-02-22)
218 | * Added `submitToActiveData` step for publishing structured logs to S3 for processing by ActiveData.
219 |
220 | ### 1.1 (2017-02-13)
221 | * Changed order of arguments for `ircNotification` for ease of specifying alternate channel.
222 |
223 | ### 1.0 (2017-02-13)
224 | * Initial release with `ircNotification` and `writeCapabilities` steps.
225 |
226 | [Pulse]: https://wiki.mozilla.org/Auto-tools/Projects/Pulse
227 | [Pipeline Utility Steps Plugin]: https://wiki.jenkins-ci.org/display/JENKINS/Pipeline+Utility+Steps+Plugin
228 | [S3 Plugin]: https://wiki.jenkins-ci.org/display/JENKINS/S3+Plugin
229 | [Amazon S3]: https://aws.amazon.com/s3/
230 | [Treeherder]: https://wiki.mozilla.org/Auto-tools/Projects/Treeherder
231 | [pytest-selenium]: http://pytest-selenium.readthedocs.io/en/latest/user_guide.html#capabilities-files
232 | [Pipeline Model Definition Plugin]: https://wiki.jenkins-ci.org/display/JENKINS/Pipeline+Model+Definition+Plugin
233 | [HTTP Request Plugin]: https://wiki.jenkins.io/display/JENKINS/HTTP+Request+Plugin
234 |
--------------------------------------------------------------------------------
/resources/org/mozilla/fxtest/pulse/schemas/treeherder.yml:
--------------------------------------------------------------------------------
1 | $schema: "http://json-schema.org/draft-04/schema#"
2 | title: "Job Definition"
3 | description: |
4 | Definition of a single job that can be added to Treeherder
5 | Project is determined by the routing key, so we don't need to specify it here.
6 | type: "object"
7 | properties:
8 | taskId:
9 | title: "taskId"
10 | description: |
11 | This could just be what was formerly submitted as a job_guid in the
12 | REST API.
13 | type: "string"
14 | pattern: "^[A-Za-z0-9/+-]+$"
15 | minLength: 1
16 | maxLength: 50
17 | retryId:
18 | title: "retryId"
19 | description: |
20 | The infrastructure retry iteration on this job. The number of times this
21 | job has been retried by the infrastructure.
22 | If it's the 1st time running, then it should be 0. If this is the first
23 | retry, it will be 1, etc.
24 | type: "integer"
25 | default: 0
26 | minimum: 0
27 |
28 | isRetried:
29 | description: True indicates this job has been retried.
30 | type: "boolean"
31 |
32 | buildSystem:
33 | description: |
34 | The name of the build system that initiated this content. Some examples
35 | are "buildbot" and "taskcluster". But this could be any name. This
36 | value will be used in the routing key for retriggering jobs in the
37 | publish-job-action task.
38 | type: "string"
39 | pattern: "^[\\w-]+$"
40 | minLength: 1
41 | maxLength: 25
42 |
43 | origin:
44 | oneOf:
45 | - type: "object"
46 | description: |
47 | PREFERRED: An HG job that only has a revision. This is for all
48 | jobs going forward.
49 | properties:
50 | kind:
51 | type: "string"
52 | enum: ['hg.mozilla.org']
53 | project:
54 | type: "string"
55 | pattern: "^[\\w-]+$"
56 | minLength: 1
57 | maxLength: 50
58 | revision:
59 | type: "string"
60 | pattern: "^[0-9a-f]+$"
61 | minLength: 40
62 | maxLength: 40
63 | pushLogID:
64 | type: "integer"
65 | required: [kind, project, revision]
66 |
67 | - type: "object"
68 | description: |
69 | BACKWARD COMPATABILITY: An HG job that only has a revision_hash.
70 | Some repos like mozilla-beta have not yet merged in the code that
71 | allows them access to the revision.
72 | properties:
73 | kind:
74 | type: "string"
75 | enum: ['hg.mozilla.org']
76 | project:
77 | type: "string"
78 | pattern: "^[\\w-]+$"
79 | minLength: 1
80 | maxLength: 50
81 | revision_hash:
82 | type: "string"
83 | pattern: "^[0-9a-f]+$"
84 | minLength: 40
85 | maxLength: 40
86 | pushLogID:
87 | type: "integer"
88 | required: [kind, project, revision_hash]
89 |
90 | - type: "object"
91 | properties:
92 | kind:
93 | type: "string"
94 | enum: ['github.com']
95 | owner:
96 | description: |
97 | This could be the organization or the individual git username
98 | depending on who owns the repo.
99 | type: "string"
100 | pattern: "^[\\w-]+$"
101 | minLength: 1
102 | maxLength: 50
103 | project:
104 | type: "string"
105 | pattern: "^[\\w-]+$"
106 | minLength: 1
107 | maxLength: 50
108 | revision:
109 | type: "string"
110 | minLength: 40
111 | maxLength: 40
112 | pullRequestID:
113 | type: "integer"
114 | required: [kind, project, revision]
115 |
116 | display:
117 | type: "object"
118 | properties:
119 | jobSymbol:
120 | title: "jobSymbol"
121 | type: "string"
122 | minLength: 0
123 | maxLength: 25
124 | chunkId:
125 | title: "chunkId"
126 | type: "integer"
127 | minimum: 1
128 | chunkCount:
129 | title: "chunkCount"
130 | type: "integer"
131 | minimum: 1
132 | groupSymbol:
133 | title: "group symbol"
134 | type: "string"
135 | minLength: 1
136 | maxLength: 25
137 | jobName:
138 | title: "job name"
139 | type: "string"
140 | minLength: 1
141 | maxLength: 100
142 | groupName:
143 | title: "group name"
144 | type: "string"
145 | minLength: 1
146 | maxLength: 100
147 | required:
148 | - jobName
149 | - jobSymbol
150 | - groupSymbol
151 |
152 |
153 | state:
154 | title: "state"
155 | description: |
156 | unscheduled: not yet scheduled
157 | pending: not yet started
158 | running: currently in progress
159 | completed: Job ran through to completion
160 | type: "string"
161 | enum:
162 | - unscheduled
163 | - pending
164 | - running
165 | - completed
166 | result:
167 | title: "result"
168 | description: |
169 | fail: A failure
170 | exception: An infrastructure error/exception
171 | success: Build/Test executed without error or failure
172 | canceled: The job was cancelled by a user
173 | unknown: When the job is not yet completed
174 | type: "string"
175 | enum:
176 | - success
177 | - fail
178 | - exception
179 | - canceled
180 | - unknown
181 | jobKind:
182 | type: "string"
183 | default: "other"
184 | enum:
185 | - build
186 | - test
187 | - other
188 | tier:
189 | type: "integer"
190 | minimum: 1
191 | maximum: 3
192 |
193 | coalesced:
194 | description: The job guids that were coalesced to this job.
195 | title: "coalesced"
196 | type: "array"
197 | items:
198 | title: "job guid"
199 | type: "string"
200 | pattern: "^[\\w/+-]+$"
201 | minLength: 1
202 | maxLength: 50
203 |
204 |
205 | # time data
206 | timeScheduled:
207 | type: "string"
208 | format: "date-time"
209 | timeStarted:
210 | type: "string"
211 | format: "date-time"
212 | timeCompleted:
213 | type: "string"
214 | format: "date-time"
215 |
216 | labels:
217 | title: "labels"
218 | description: |
219 | Labels are a dimension of a platform. The values here can vary wildly,
220 | so most strings are valid for this. The list of labels that are used
221 | is maleable going forward.
222 |
223 | These were formerly known as "Options" within "Option Collections" but
224 | calling labels now so they can be understood to be just strings that
225 | denotes a characteristic of the job.
226 |
227 | Some examples of labels that have been used:
228 | opt Optimize Compiler GCC optimize flags
229 | debug Debug flags passed in
230 | pgo Profile Guided Optimization - Like opt, but runs with profiling, then builds again using that profiling
231 | asan Address Sanitizer
232 | tsan Thread Sanitizer Build
233 | type: "array"
234 | items:
235 | type: "string"
236 | minLength: 1
237 | maxLength: 50
238 | pattern: "^[\\w-]+$"
239 |
240 | owner:
241 | description: |
242 | Description of who submitted the job: gaia | scheduler name | username | email
243 | title: "owner"
244 | type: "string"
245 | minLength: 1
246 | maxLength: 50
247 | reason:
248 | description: |
249 | Examples include:
250 | - scheduled
251 | - scheduler
252 | - Self-serve: Rebuilt by foo@example.com
253 | - Self-serve: Requested by foo@example.com
254 | - The Nightly scheduler named 'b2g_mozilla-inbound periodic' triggered this build
255 | - unknown
256 | type: "string"
257 | minLength: 1
258 | maxLength: 125
259 | productName:
260 | description: |
261 | Examples include:
262 | - 'b2g'
263 | - 'firefox'
264 | - 'taskcluster'
265 | - 'xulrunner'
266 | type: "string"
267 | minLength: 1
268 | maxLength: 125
269 |
270 | buildMachine:
271 | $ref: "#/definitions/machine"
272 | runMachine:
273 | $ref: "#/definitions/machine"
274 |
275 | jobInfo:
276 | description: |
277 | Definition of the Job Info for a job. These are extra data
278 | fields that go along with a job that will be displayed in
279 | the details panel within Treeherder.
280 | id: "jobInfo"
281 | type: object
282 | properties:
283 | summary:
284 | type: string
285 | description: |
286 | Plain text description of the job and its state. Submitted with
287 | the final message about a task.
288 | links:
289 | type: array
290 | items:
291 | - type: object
292 | description: |
293 | List of URLs shown as key/value pairs. Shown as:
294 | "