semver format, from (https://flutter.io/sdk-archive/).",
70 | "visibleRule": "mode = auto && version = custom"
71 | },
72 | {
73 | "name": "customArch",
74 | "type": "string",
75 | "label": "Custom Arch",
76 | "defaultValue": "",
77 | "required": false,
78 | "helpMarkDown": "The 'dart_sdk_arch' of the SDK to use like 'x64' or 'arm64'. Use this link, (replace 'platform') https://storage.googleapis.com/flutter_infra_release/releases/releases_{platform}.json for example https://storage.googleapis.com/flutter_infra_release/releases/releases_macos.json."
79 | }
80 | ],
81 | "execution": {
82 | "Node10": {
83 | "target": "index.js"
84 | },
85 | "Node16": {
86 | "target": "index.js"
87 | },
88 | "Node20_1": {
89 | "target": "index.js"
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/tasks/install/tests/latest.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const mr = require("azure-pipelines-task-lib/mock-run");
4 | const fs = require("fs");
5 | const path = require("path");
6 | const taskPath = path.join(__dirname, "../index.js");
7 | var runner = new mr.TaskMockRunner(taskPath);
8 | function assertDirectory(path) {
9 | if (!fs.existsSync(path)) {
10 | fs.mkdirSync(path);
11 | }
12 | }
13 | // ENV
14 | const tempPath = path.join(__dirname, "..", "..", "..", "temp");
15 | const agentPath = path.join(tempPath, "agent");
16 | process.env["BUILD_BUILDNUMBER"] = "1";
17 | assertDirectory(tempPath);
18 | assertDirectory(agentPath);
19 | assertDirectory(process.env["AGENT_HOMEDIRECTORY"] = path.join(agentPath, "home"));
20 | assertDirectory(process.env["AGENT_TOOLSDIRECTORY"] = path.join(agentPath, "tools"));
21 | assertDirectory(process.env["AGENT_TEMPDIRECTORY"] = path.join(agentPath, "temp"));
22 | assertDirectory(process.env["AGENT_BUILDDIRECTORY"] = path.join(agentPath, "build"));
23 | //let tmr = require('vsts-task-lib/mock-toolrunner');
24 | //runner.registerMock('vsts-task-lib/toolrunner', tmr);
25 | runner.setInput("mode", "auto");
26 | runner.setInput("channel", "stable");
27 | runner.setInput("version", "latest");
28 | // uncomment to test custom version without "dart_sdk_arch" type
29 | // runner.setInput("version", "custom");
30 | // runner.setInput("customVersion", "1.22.6");
31 | // runner.setInput("customVersion", "3.0.2");
32 | // runner.setInput("customArch", "arm64");
33 | // to test custom url
34 | // runner.setInput("mode", "custom");
35 | // runner.setInput("customUrl", "https://storage.googleapis.com/flutter_infra_release/releases/beta/macos/flutter_macos_arm64_3.1.0-9.0.pre-beta.zip");
36 | // runner.setInput("customUrl", "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_3.0.2-stable.zip");
37 | runner.run(true);
38 |
--------------------------------------------------------------------------------
/tasks/install/tests/latest.ts:
--------------------------------------------------------------------------------
1 | import * as mr from 'azure-pipelines-task-lib/mock-run';
2 | import * as fs from 'fs';
3 | import * as path from 'path';
4 |
5 | const taskPath = path.join(__dirname, "../index.js");
6 | var runner = new mr.TaskMockRunner(taskPath);
7 |
8 | function assertDirectory(path: string) {
9 | if (!fs.existsSync(path)) {
10 | fs.mkdirSync(path);
11 | }
12 | }
13 |
14 | // ENV
15 | const tempPath = path.join(__dirname, "..", "..", "..", "temp");
16 | const agentPath = path.join(tempPath, "agent");
17 | process.env["BUILD_BUILDNUMBER"] = "1";
18 | assertDirectory(tempPath);
19 | assertDirectory(agentPath);
20 | assertDirectory(process.env["AGENT_HOMEDIRECTORY"] = path.join(agentPath, "home"));
21 | assertDirectory(process.env["AGENT_TOOLSDIRECTORY"] = path.join(agentPath, "tools"));
22 | assertDirectory(process.env["AGENT_TEMPDIRECTORY"] = path.join(agentPath, "temp"));
23 | assertDirectory(process.env["AGENT_BUILDDIRECTORY"] = path.join(agentPath, "build"));
24 |
25 | //let tmr = require('vsts-task-lib/mock-toolrunner');
26 | //runner.registerMock('vsts-task-lib/toolrunner', tmr);
27 |
28 | runner.setInput("mode", "auto");
29 | runner.setInput("channel", "stable");
30 | runner.setInput("version", "latest");
31 |
32 | // uncomment to test custom version without "dart_sdk_arch" type
33 | // runner.setInput("version", "custom");
34 | // runner.setInput("customVersion", "1.22.6");
35 | // runner.setInput("customVersion", "3.0.2");
36 | // runner.setInput("customArch", "arm64");
37 |
38 | // to test custom url
39 | // runner.setInput("mode", "custom");
40 | // runner.setInput("customUrl", "https://storage.googleapis.com/flutter_infra_release/releases/beta/macos/flutter_macos_arm64_3.1.0-9.0.pre-beta.zip");
41 | // runner.setInput("customUrl", "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_3.0.2-stable.zip");
42 |
43 | runner.run(true);
--------------------------------------------------------------------------------
/tasks/install/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES6",
4 | "module": "commonjs"
5 | },
6 | "exclude": [
7 | "node_modules"
8 | ]
9 | }
--------------------------------------------------------------------------------
/tasks/install/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "tslint:latest"
4 | ],
5 | "rules": {
6 | "member-ordering": [ false ],
7 | "interface-name": [ false ],
8 | "object-literal-sort-keys": false,
9 |
10 | "whitespace": [
11 | "check-branch",
12 | "check-decl",
13 | "check-operator",
14 | "check-module",
15 | "check-separator",
16 | "check-type"
17 | ],
18 |
19 | "one-line": [
20 | "check-open-brace",
21 | "check-whitespace"
22 | ]
23 | }
24 | }
--------------------------------------------------------------------------------
/tasks/test/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/tasks/test/icon.png
--------------------------------------------------------------------------------
/tasks/test/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tasks/test/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 | return new (P || (P = Promise))(function (resolve, reject) {
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
9 | });
10 | };
11 | Object.defineProperty(exports, "__esModule", { value: true });
12 | const task = require("azure-pipelines-task-lib/task");
13 | const path = require("path");
14 | const xml2js = require("xml2js");
15 | const FLUTTER_TOOL_PATH_ENV_VAR = 'FlutterToolPath';
16 | function main() {
17 | return __awaiter(this, void 0, void 0, function* () {
18 | // 1. Check flutter environment
19 | var flutterPath = task.getVariable(FLUTTER_TOOL_PATH_ENV_VAR) || process.env[FLUTTER_TOOL_PATH_ENV_VAR];
20 | flutterPath = path.join(flutterPath, "flutter");
21 | if (!flutterPath) {
22 | throw new Error(`The '${FLUTTER_TOOL_PATH_ENV_VAR}' environment variable must be set before using this task (you can use 'flutterinstall' task).`);
23 | }
24 | // 3. Move current working directory to project
25 | let projectDirectory = task.getPathInput('projectDirectory', false, false);
26 | if (projectDirectory) {
27 | task.debug(`Moving to ${projectDirectory}`);
28 | task.cd(projectDirectory);
29 | }
30 | // 4. Get inputs
31 | let testName = task.getInput('testName', false);
32 | let testPlainName = task.getInput('testPlainName', false);
33 | let updateGoldens = task.getBoolInput('updateGoldens', false);
34 | let concurrency = task.getInput('concurrency', false);
35 | let canPublishTests = task.getInput('publishTests', false);
36 | let canGenerateCodeCoverage = task.getBoolInput("generateCodeCoverageReport", false);
37 | let extraArgs = task.getDelimitedInput('extraArgs', ',', false);
38 | // 5. Running tests
39 | var results = yield runTests(flutterPath, (concurrency ? Number(concurrency) : null), updateGoldens, testName, testPlainName, canGenerateCodeCoverage, extraArgs);
40 | // 6. Publishing tests
41 | if (canPublishTests) {
42 | yield publishTests(results);
43 | }
44 | if (results.isSuccess) {
45 | task.setResult(task.TaskResult.Succeeded, `All tests passed`);
46 | }
47 | else {
48 | task.setResult(task.TaskResult.Failed, `Some tests failed`);
49 | }
50 | });
51 | }
52 | function publishTests(results) {
53 | return __awaiter(this, void 0, void 0, function* () {
54 | var publisher = new task.TestPublisher("JUnit");
55 | task.debug(`results: ` + JSON.stringify(results));
56 | // 1. Generating Junit XML result file
57 | var junitResults = createJunitResults(results);
58 | var xmlBuilder = new xml2js.Builder();
59 | var xml = xmlBuilder.buildObject(junitResults);
60 | var xmlPath = path.join(task.cwd(), "junit.xml");
61 | task.writeFile(xmlPath, xml);
62 | // 2. Publishing to task
63 | publisher.publish([xmlPath], "false", "", "", "VSTS - Flutter", "true", "");
64 | });
65 | }
66 | function runTests(flutter, concurrency, updateGoldens, name, plainName, canGenerateCodeCoverage, extraArgs) {
67 | return __awaiter(this, void 0, void 0, function* () {
68 | let testRunner = task.tool(flutter);
69 | testRunner.arg(['test', '--pub']);
70 | if (updateGoldens) {
71 | testRunner.arg("--update-goldens");
72 | }
73 | if (canGenerateCodeCoverage) {
74 | testRunner.arg("--coverage");
75 | }
76 | if (name) {
77 | testRunner.arg("--name=" + name);
78 | }
79 | if (plainName) {
80 | testRunner.arg("--plain-name=" + plainName);
81 | }
82 | if (concurrency) {
83 | testRunner.arg("--concurrency=" + concurrency);
84 | }
85 | if (extraArgs && extraArgs.length > 0) {
86 | testRunner.arg(extraArgs);
87 | }
88 | var currentSuite = {
89 | title: undefined,
90 | isSuccess: false,
91 | succeeded: 0,
92 | failed: 0,
93 | cases: []
94 | };
95 | var allSuitesDict = {};
96 | var results = {
97 | isSuccess: false,
98 | suites: []
99 | };
100 | let globalFailure = 0;
101 | testRunner.on('stdout', line => {
102 | let filename = undefined;
103 | const allTestRegex = /\s*\d\d:\d\d (\+\d+)?(\s+\-\d+)?:\s* \s*(.*\.dart)\s*/;
104 | const filenameRegex = /(\w+.dart)/;
105 | const allRegexResult = allTestRegex.exec(line);
106 | if (allRegexResult && allRegexResult[3]) {
107 | const filepath = allRegexResult[3];
108 | if (filepath) {
109 | filename = filenameRegex.exec(filepath)[0];
110 | }
111 | }
112 | var newSuite = {
113 | title: filename ? filename : undefined,
114 | isSuccess: false,
115 | succeeded: 0,
116 | failed: 0,
117 | cases: []
118 | };
119 | if (allSuitesDict[newSuite.title]) {
120 | currentSuite = allSuitesDict[newSuite.title];
121 | }
122 | else {
123 | currentSuite = newSuite;
124 | }
125 | currentSuite = createTestCase(currentSuite, line, globalFailure);
126 | allSuitesDict[currentSuite.title] = currentSuite;
127 | });
128 | try {
129 | yield testRunner.exec();
130 | results.isSuccess = true;
131 | }
132 | catch (_a) { }
133 | var allKeys = Object.keys(allSuitesDict);
134 | allKeys.forEach((k) => {
135 | results.suites.push(allSuitesDict[k]);
136 | });
137 | return results;
138 | });
139 | }
140 | // TODO: remove in next version upgrade
141 | // function createTestCase(suite: any, output: string, globalFailure: number) {
142 | // const testRunRegex = /\s*\d\d:\d\d (\+\d+)?(\s+\-\d+)?:\s*(.*)/;
143 | // let match = testRunRegex.exec(output);
144 | // if (match) {
145 | // var tSplits = match[3].split(': ');
146 | // var title = tSplits[tSplits.length - 1];
147 | // var successes = Number(match[1]);
148 | // var failures = match[2] ? -Number(match[2]) : suite.failed;
149 | // var newCase = {
150 | // title: title.trim(),
151 | // isSuccess: false,
152 | // started: new Date(),
153 | // ended: new Date,
154 | // };
155 | // var hasNewCase = false;
156 | // if (suite.succeeded !== successes) {
157 | // newCase.isSuccess = true;
158 | // hasNewCase = true;
159 | // }
160 | // else if (suite.failed !== failures) {
161 | // newCase.isSuccess = false;
162 | // hasNewCase = true;
163 | // }
164 | // else if (suite.cases.length <= 0
165 | // && suite.succeeded === 0
166 | // && successes === 0
167 | // && suite.failed === 0
168 | // && failures === 0) {
169 | // // handles initial test, as it is always empty with everything as 0 or empty
170 | // // index in test starts with 0
171 | // // everything is 0 meaning it's a new case
172 | // newCase.isSuccess = true;
173 | // hasNewCase = true;
174 | // } else {
175 | // newCase.isSuccess = true;
176 | // hasNewCase = true;
177 | // }
178 | // if (hasNewCase) {
179 | // if (suite.cases.length > 0) {
180 | // suite.cases[suite.cases.length - 1].ended = newCase.started;
181 | // }
182 | // }
183 | // const caseExistsIdx = suite.cases.findIndex((i) => {
184 | // return i.title.toString().trim() === newCase.title.toString().trim();
185 | // });
186 | // if (caseExistsIdx < 0 || !suite.cases[caseExistsIdx].isSuccess) {
187 | // // only add cases if they are not added before
188 | // // checks if case doesn't exist or already added with success
189 | // suite.cases.push(newCase);
190 | // }
191 | // if (newCase.isSuccess) {
192 | // suite.succeeded += 1;
193 | // } else {
194 | // suite.failed += 1;
195 | // }
196 | // // console.log('\n', '\x1b[36m',
197 | // // 'Suite : ', suite, successes, failures,
198 | // // '\x1b[0m', '\n');
199 | // }
200 | // return suite;
201 | // }
202 | function createTestCase(suite, output, globalFailure) {
203 | const testRunRegex = /\s*\d\d:\d\d (\+\d+)?(\s+\-\d+)?:\s*(.*)/;
204 | const match = testRunRegex.exec(output);
205 | if (!match || match.length < 4) {
206 | return suite;
207 | }
208 | const tSplits = match[3].split(': ');
209 | if (tSplits.length === 1) {
210 | // Log printed starting a new test suite - No test case handled here
211 | return suite;
212 | }
213 | // Sanitise test case name from other information
214 | let title = tSplits[tSplits.length - 1];
215 | title = title.replace(' [E]', '');
216 | const currentCase = suite.cases.find(testCase => testCase.title === title);
217 | if (!currentCase) {
218 | // Suite has to be marked as succeeded first, marking as failed can be done when there is a new row with [E] at the end
219 | suite.cases.push({
220 | title: title.trim(),
221 | isSuccess: true,
222 | started: new Date(),
223 | ended: new Date(),
224 | });
225 | suite.succeeded += 1;
226 | return suite;
227 | }
228 | if (match[3].endsWith(' [E]')) {
229 | currentCase.isSuccess = false;
230 | suite.failed += 1;
231 | suite.succeeded -= 1;
232 | }
233 | return suite;
234 | }
235 | function createJunitResults(results) {
236 | var testSuites = [];
237 | results.suites.forEach(suite => {
238 | var testCases = [];
239 | suite.cases.forEach(c => {
240 | var duration = (c.ended.getTime() - c.started.getTime());
241 | var s = (duration / 1000);
242 | var testCase = {
243 | "$": {
244 | "name": c.title,
245 | "classname": c.title,
246 | "time": s,
247 | }
248 | };
249 | if (!c.isSuccess) {
250 | testCase["failure"] = {
251 | "$": {
252 | "type": "FlutterError",
253 | }
254 | };
255 | }
256 | testCases.push(testCase);
257 | });
258 | var testSuite = {
259 | "$": {
260 | "name": suite.title,
261 | "timestamp": new Date().toISOString(),
262 | "errors": 0, // TODO
263 | "skipped": 0, // TODO
264 | "failures": suite.failed,
265 | "tests": (suite.failed + suite.succeeded)
266 | },
267 | "testcase": testCases
268 | };
269 | testSuites.push(testSuite);
270 | });
271 | return {
272 | "testsuites": {
273 | "testsuite": testSuites
274 | }
275 | };
276 | }
277 | main().catch(error => {
278 | task.setResult(task.TaskResult.Failed, error);
279 | });
280 |
--------------------------------------------------------------------------------
/tasks/test/index.ts:
--------------------------------------------------------------------------------
1 | import * as task from "azure-pipelines-task-lib/task";
2 | import * as path from "path";
3 | import * as xml2js from "xml2js";
4 |
5 | const FLUTTER_TOOL_PATH_ENV_VAR: string = 'FlutterToolPath';
6 |
7 | async function main(): Promise