diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f08e8b2e..e581b50c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,11 +8,10 @@ on: jobs: test: runs-on: macOS-latest + timeout-minutes: 20 strategy: matrix: - api-level: [28, 29] - target: [default, google_apis] - abi: [x86, x86_64] + api-level: [21, 29] steps: - name: checkout uses: actions/checkout@v1 @@ -29,6 +28,6 @@ jobs: uses: ./ with: api-level: ${{ matrix.api-level }} - target: ${{ matrix.target }} - abi: ${{ matrix.abi }} + target: default + abi: x86 headless: true diff --git a/__tests__/input-validator.test.ts b/__tests__/input-validator.test.ts new file mode 100644 index 00000000..b3f519cc --- /dev/null +++ b/__tests__/input-validator.test.ts @@ -0,0 +1,71 @@ +import * as validator from '../src/input-validator'; + +describe('api-level validator tests', () => { + it('Throws if api-level is not a number', () => { + const func = function() { validator.checkApiLevel('api'); } + expect(func).toThrowError(`Unexpected API level: 'api'.`); + }); + + it('Throws if api-level is not an integer', () => { + const func = function() { validator.checkApiLevel('29.1'); } + expect(func).toThrowError(`Unexpected API level: '29.1'.`); + }); + + it('Throws if api-level is lower than min API supported', () => { + const func = function() { validator.checkApiLevel('20'); } + expect(func).toThrowError(`Minimum API level supported is ${validator.MIN_API_LEVEL}.`); + }); + + it('Validates successfully with valid api-level', () => { + const func1 = function() { validator.checkApiLevel('21'); } + expect(func1).not.toThrow(); + + const func2 = function() { validator.checkApiLevel('29'); } + expect(func2).not.toThrow(); + }); +}); + +describe('target validator tests', () => { + it('Throws if target is unknown', () => { + const func = function() { validator.checkTarget('some-target'); } + expect(func).toThrowError(`Value for input.target 'some-target' is unknown. Supported options: ${validator.VALID_TARGETS}`); + }); + + it('Validates successfully with valid target', () => { + const func1 = function() { validator.checkTarget('default'); } + expect(func1).not.toThrow(); + + const func2 = function() { validator.checkTarget('google_apis'); } + expect(func2).not.toThrow(); + }); +}); + +describe('abi validator tests', () => { + it('Throws if abi is unknown', () => { + const func = function() { validator.checkAbi('some-abi'); } + expect(func).toThrowError(`Value for input.abi 'some-abi' is unknown. Supported options: ${validator.VALID_ABIS}`); + }); + + it('Validates successfully with valid abi', () => { + const func1 = function() { validator.checkAbi('x86'); } + expect(func1).not.toThrow(); + + const func2 = function() { validator.checkAbi('x86_64'); } + expect(func2).not.toThrow(); + }); +}); + +describe('headless validator tests', () => { + it('Throws if headless is not a boolean', () => { + const func = function() { validator.checkHeadless('yes'); } + expect(func).toThrowError(`Input for input.headless should be either 'true' or 'false'.`); + }); + + it('Validates successfully if headless is either true or false', () => { + const func1 = function() { validator.checkHeadless('true'); } + expect(func1).not.toThrow(); + + const func2 = function() { validator.checkHeadless('false'); } + expect(func2).not.toThrow(); + }); +}); diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts deleted file mode 100644 index 7d18ab3a..00000000 --- a/__tests__/main.test.ts +++ /dev/null @@ -1,3 +0,0 @@ -test('test action inputs', async() => { - // TODO -}); diff --git a/action.yml b/action.yml index 87c5ea7f..60d403bb 100644 --- a/action.yml +++ b/action.yml @@ -7,7 +7,7 @@ inputs: required: true target: description: 'target of the system image - default of google_apis' - default: 'google_apis' + default: 'default' abi: description: 'cpu/abi of the system image - x86 or x86_64' default: 'x86' diff --git a/lib/input-validator.js b/lib/input-validator.js new file mode 100644 index 00000000..98bfd751 --- /dev/null +++ b/lib/input-validator.js @@ -0,0 +1,32 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MIN_API_LEVEL = 21; +exports.VALID_TARGETS = ['default', 'google_apis']; +exports.VALID_ABIS = ['x86', 'x86_64']; +function checkApiLevel(apiLevel) { + if (isNaN(Number(apiLevel)) || !Number.isInteger(Number(apiLevel))) { + throw new Error(`Unexpected API level: '${apiLevel}'.`); + } + if (Number(apiLevel) < exports.MIN_API_LEVEL) { + throw new Error(`Minimum API level supported is ${exports.MIN_API_LEVEL}.`); + } +} +exports.checkApiLevel = checkApiLevel; +function checkTarget(target) { + if (!exports.VALID_TARGETS.includes(target)) { + throw new Error(`Value for input.target '${target}' is unknown. Supported options: ${exports.VALID_TARGETS}.`); + } +} +exports.checkTarget = checkTarget; +function checkAbi(abi) { + if (!exports.VALID_ABIS.includes(abi)) { + throw new Error(`Value for input.abi '${abi}' is unknown. Supported options: ${exports.VALID_ABIS}.`); + } +} +exports.checkAbi = checkAbi; +function checkHeadless(headless) { + if (headless !== 'true' && headless !== 'false') { + throw new Error(`Input for input.headless should be either 'true' or 'false'.`); + } +} +exports.checkHeadless = checkHeadless; diff --git a/lib/main.js b/lib/main.js index cdb6ec7a..b9cd281a 100644 --- a/lib/main.js +++ b/lib/main.js @@ -17,6 +17,7 @@ var __importStar = (this && this.__importStar) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); const core = __importStar(require("@actions/core")); const sdk_installer_1 = require("./sdk-installer"); +const input_validator_1 = require("./input-validator"); function run() { return __awaiter(this, void 0, void 0, function* () { try { @@ -24,31 +25,22 @@ function run() { if (process.platform !== 'darwin') { throw new Error('This action is expected to be run within a macOS virtual machine to enable hardware acceleration.'); } - // TODO test inputs - // TODO test real inputs in test.yml - // api-level is required - // TODO use InputOptions {true} - const apiLevel = core.getInput('api-level'); - // TODO check apiLevel is number and within valid range - console.log(`API level - ${apiLevel}`); - // target is optional with default + // API level of the platform and system image + const apiLevel = core.getInput('api-level', { required: true }); + input_validator_1.checkApiLevel(apiLevel); + console.log(`API level: ${apiLevel}`); + // target of the system image const target = core.getInput('target'); - if (target !== 'default' && target !== 'google_apis') { - throw new Error(`Target ${target} is unknown. Please use either 'default' or 'google_apis'.`); - } - console.log(`target - ${target}`); - // abi is optional with default + input_validator_1.checkTarget(target); + console.log(`target: ${target}`); + // CPU / ABI of the system image const abi = core.getInput('abi'); - if (abi !== 'x86' && abi !== 'x86_64') { - throw new Error(`abi ${abi} is unknown (ARM-based emulators are not supported). Please use either 'x86' or 'x86_64'.`); - } - console.log(`cpu/abi - ${abi}`); - // headless is optional with default + input_validator_1.checkAbi(abi); + console.log(`cpu/abi: ${abi}`); + // headless mode const headless = core.getInput('headless'); - if (headless !== 'true' && headless !== 'false') { - throw new Error(`Input 'headless' should be either 'true' or 'false'.`); - } - console.log(`headless mode - ${headless}`); + input_validator_1.checkHeadless(headless); + console.log(`headless mode: ${headless}`); // install SDK yield sdk_installer_1.installAndroidSdk(Number(apiLevel), target, abi); // TODO start emulator diff --git a/lib/sdk-installer.js b/lib/sdk-installer.js index 965f34c9..338dfe39 100644 --- a/lib/sdk-installer.js +++ b/lib/sdk-installer.js @@ -24,7 +24,7 @@ const SDK_URL = 'https://dl.google.com/android/repository/sdk-tools-darwin-43337 * Downloads and installs the Android SDK for the macOS platform, including SDK platform for the chosen API level, latest build tools, platform tools, Android Emulator, * and the system image for the chosen API level, cpu/abi, and target. */ -function installAndroidSdk(apiLevel, abi, target) { +function installAndroidSdk(apiLevel, target, abi) { return __awaiter(this, void 0, void 0, function* () { // download Android SDK if not already installed if (fs.existsSync(`${process.env.ANDROID_HOME}/tools/bin/sdkmanager`)) { @@ -37,8 +37,7 @@ function installAndroidSdk(apiLevel, abi, target) { } // install specific SDK tools console.log('Installing build tools, platform tools, platform and system image.'); - yield exec.exec(`yes | ${process.env.ANDROID_HOME}/tools/bin/sdkmanager --licenses`); - yield exec.exec(`${process.env.ANDROID_HOME}/tools/bin/sdkmanager --update`); + yield exec.exec(`echo "y" | ${process.env.ANDROID_HOME}/tools/bin/sdkmanager --licenses > /dev/null`); yield exec.exec(`${process.env.ANDROID_HOME}/tools/bin/sdkmanager "build-tools;${BUILD_TOOLS_VERSION}"`); yield exec.exec(`${process.env.ANDROID_HOME}/tools/bin/sdkmanager "platform-tools"`); yield exec.exec(`${process.env.ANDROID_HOME}/tools/bin/sdkmanager "platforms;android-${apiLevel}"`); diff --git a/src/input-validator.ts b/src/input-validator.ts new file mode 100644 index 00000000..8d004990 --- /dev/null +++ b/src/input-validator.ts @@ -0,0 +1,32 @@ +import { isNumber } from "util"; + +export const MIN_API_LEVEL = 21; +export const VALID_TARGETS: Array = ['default', 'google_apis']; +export const VALID_ABIS: Array = ['x86', 'x86_64']; + +export function checkApiLevel(apiLevel: string): void { + if (isNaN(Number(apiLevel)) || !Number.isInteger(Number(apiLevel))) { + throw new Error(`Unexpected API level: '${apiLevel}'.`); + } + if (Number(apiLevel) < MIN_API_LEVEL) { + throw new Error(`Minimum API level supported is ${MIN_API_LEVEL}.`); + } +} + +export function checkTarget(target: string): void { + if (!VALID_TARGETS.includes(target)) { + throw new Error(`Value for input.target '${target}' is unknown. Supported options: ${VALID_TARGETS}.`); + } +} + +export function checkAbi(abi: string): void { + if (!VALID_ABIS.includes(abi)) { + throw new Error(`Value for input.abi '${abi}' is unknown. Supported options: ${VALID_ABIS}.`); + } +} + +export function checkHeadless(headless: string): void { + if (headless !== 'true' && headless !== 'false') { + throw new Error(`Input for input.headless should be either 'true' or 'false'.`); + } +} diff --git a/src/main.ts b/src/main.ts index 707b7fb9..f23fb9de 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,7 @@ import * as core from '@actions/core'; -import { installAndroidSdk } from './sdk-installer' +import { InputOptions } from "@actions/core/lib/core"; +import { installAndroidSdk } from './sdk-installer'; +import { checkApiLevel, checkTarget, checkAbi, checkHeadless } from './input-validator'; async function run() { try { @@ -8,34 +10,24 @@ async function run() { throw new Error('This action is expected to be run within a macOS virtual machine to enable hardware acceleration.'); } - // TODO test inputs - // TODO test real inputs in test.yml - - // api-level is required - // TODO use InputOptions {true} - const apiLevel = core.getInput('api-level'); - // TODO check apiLevel is number and within valid range + // API level of the platform and system image + const apiLevel = core.getInput('api-level', {required: true}); + checkApiLevel(apiLevel); console.log(`API level: ${apiLevel}`); - // target is optional with default + // target of the system image const target = core.getInput('target'); - if (target !== 'default' && target !== 'google_apis') { - throw new Error(`Target ${target} is unknown. Please use either 'default' or 'google_apis'.`); - } + checkTarget(target); console.log(`target: ${target}`); - // abi is optional with default + // CPU / ABI of the system image const abi = core.getInput('abi'); - if (abi !== 'x86' && abi !== 'x86_64') { - throw new Error(`abi ${abi} is unknown (ARM-based emulators are not supported). Please use either 'x86' or 'x86_64'.`); - } + checkAbi(abi); console.log(`cpu/abi: ${abi}`); - // headless is optional with default + // headless mode const headless = core.getInput('headless'); - if (headless !== 'true' && headless !== 'false') { - throw new Error(`Input 'headless' should be either 'true' or 'false'.`); - } + checkHeadless(headless); console.log(`headless mode: ${headless}`); // install SDK