Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,30 @@ jobs:
script: ./gradlew connectedCheck
```

If you want to automatically clean up the created AVD after the tests run (to save disk space):

```yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4

- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
cleanup-avd: true
script: ./gradlew connectedCheck
```

If you need a specific [SDK Extensions](https://developer.android.com/guide/sdk-extensions) for the system image but not the platform:

```yml
Expand Down Expand Up @@ -223,6 +247,7 @@ jobs:
| `disable-spellchecker` | Optional | `false` | Whether to disable spellchecker - `true` or `false`. |
| `disable-linux-hw-accel` | Optional | `auto` | Whether to disable hardware acceleration on Linux machines - `true`, `false` or `auto`.|
| `enable-hw-keyboard` | Optional | `false` | Whether to enable hardware keyboard - `true` or `false`. |
| `cleanup-avd` | Optional | `false` | Whether to delete the created AVD after execution - `true` or `false`. |
| `emulator-build` | Optional | N/A | Build number of a specific version of the emulator binary to use e.g. `6061023` for emulator v29.3.0.0. |
| `working-directory` | Optional | `./` | A custom working directory - e.g. `./android` if your root Gradle project is under the `./android` sub-directory within your repository. Will be used for `script` & `pre-emulator-launch-script`. |
| `ndk` | Optional | N/A | Version of NDK to install - e.g. `21.0.6113669` |
Expand Down
21 changes: 21 additions & 0 deletions __tests__/input-validator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,27 @@ describe('enable-hw-keyboard validator tests', () => {
});
});

describe('cleanup-avd validator tests', () => {
it('Throws if cleanup-avd is not a boolean', () => {
const func = () => {
validator.checkCleanupAvd('yes');
};
expect(func).toThrowError(`Input for input.cleanup-avd should be either 'true' or 'false'.`);
});

it('Validates successfully if cleanup-avd is either true or false', () => {
const func1 = () => {
validator.checkCleanupAvd('true');
};
expect(func1).not.toThrow();

const func2 = () => {
validator.checkCleanupAvd('false');
};
expect(func2).not.toThrow();
});
});

describe('emulator-build validator tests', () => {
it('Throws if emulator-build is not a number', () => {
const func = () => {
Expand Down
2 changes: 2 additions & 0 deletions action-types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ inputs:
type: string
enable-hw-keyboard:
type: boolean
cleanup-avd:
type: boolean
emulator-build:
type: string
working-directory:
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ inputs:
enable-hw-keyboard:
description: 'whether to enable hardware keyboard - `true` or `false`.'
default: 'false'
cleanup-avd:
description: 'whether to delete the created AVD after execution - `true` or `false`'
default: 'false'
emulator-build:
description: 'build number of a specific version of the emulator binary to use - e.g. `6061023` for emulator v29.3.0.0'
working-directory:
Expand Down
22 changes: 21 additions & 1 deletion lib/emulator-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.killEmulator = exports.launchEmulator = exports.createAvd = void 0;
exports.deleteAvd = exports.killEmulator = exports.launchEmulator = exports.createAvd = void 0;
const exec = __importStar(require("@actions/exec"));
const fs = __importStar(require("fs"));
/**
Expand Down Expand Up @@ -142,6 +142,26 @@ function killEmulator(port) {
});
}
exports.killEmulator = killEmulator;
/**
* Deletes the specified AVD.
*/
function deleteAvd(avdName) {
return __awaiter(this, void 0, void 0, function* () {
try {
console.log(`::group::Delete AVD`);
console.log(`Deleting AVD '${avdName}'.`);
yield exec.exec(`avdmanager delete avd -n "${avdName}"`);
console.log(`AVD '${avdName}' deleted successfully.`);
}
catch (error) {
console.log(`Failed to delete AVD '${avdName}': ${error instanceof Error ? error.message : error}`);
}
finally {
console.log(`::endgroup::`);
}
});
}
exports.deleteAvd = deleteAvd;
function adb(port, command) {
return __awaiter(this, void 0, void 0, function* () {
return yield exec.exec(`adb -s emulator-${port} ${command}`);
Expand Down
8 changes: 7 additions & 1 deletion lib/input-validator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkDiskSize = exports.checkEmulatorBuild = exports.checkEnableHardwareKeyboard = exports.checkDisableLinuxHardwareAcceleration = exports.checkDisableSpellchecker = exports.checkDisableAnimations = exports.checkPort = exports.checkForceAvdCreation = exports.checkChannel = exports.checkArch = exports.playstoreTargetSubstitution = exports.MAX_PORT = exports.MIN_PORT = exports.VALID_CHANNELS = exports.VALID_ARCHS = exports.MIN_API_LEVEL = void 0;
exports.checkDiskSize = exports.checkEmulatorBuild = exports.checkCleanupAvd = exports.checkEnableHardwareKeyboard = exports.checkDisableLinuxHardwareAcceleration = exports.checkDisableSpellchecker = exports.checkDisableAnimations = exports.checkPort = exports.checkForceAvdCreation = exports.checkChannel = exports.checkArch = exports.playstoreTargetSubstitution = exports.MAX_PORT = exports.MIN_PORT = exports.VALID_CHANNELS = exports.VALID_ARCHS = exports.MIN_API_LEVEL = void 0;
exports.MIN_API_LEVEL = 15;
exports.VALID_ARCHS = ['x86', 'x86_64', 'arm64-v8a'];
exports.VALID_CHANNELS = ['stable', 'beta', 'dev', 'canary'];
Expand Down Expand Up @@ -67,6 +67,12 @@ function checkEnableHardwareKeyboard(enableHardwareKeyboard) {
}
}
exports.checkEnableHardwareKeyboard = checkEnableHardwareKeyboard;
function checkCleanupAvd(cleanupAvd) {
if (!isValidBoolean(cleanupAvd)) {
throw new Error(`Input for input.cleanup-avd should be either 'true' or 'false'.`);
}
}
exports.checkCleanupAvd = checkCleanupAvd;
function checkEmulatorBuild(emulatorBuild) {
if (isNaN(Number(emulatorBuild)) || !Number.isInteger(Number(emulatorBuild))) {
throw new Error(`Unexpected emulator build: '${emulatorBuild}'.`);
Expand Down
17 changes: 16 additions & 1 deletion lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ const fs_1 = require("fs");
function run() {
return __awaiter(this, void 0, void 0, function* () {
let port = input_validator_1.MIN_PORT;
let avdName = '';
let cleanupAvd = false;
try {
console.log(`::group::Configure emulator`);
let linuxSupportKVM = false;
Expand Down Expand Up @@ -95,7 +97,7 @@ function run() {
(0, input_validator_1.checkDiskSize)(diskSize);
console.log(`Disk size: ${diskSize}`);
// custom name used for creating the AVD
const avdName = core.getInput('avd-name');
avdName = core.getInput('avd-name');
console.log(`AVD name: ${avdName}`);
// force AVD creation
const forceAvdCreationInput = core.getInput('force-avd-creation');
Expand Down Expand Up @@ -135,6 +137,11 @@ function run() {
(0, input_validator_1.checkEnableHardwareKeyboard)(enableHardwareKeyboardInput);
const enableHardwareKeyboard = enableHardwareKeyboardInput === 'true';
console.log(`enable hardware keyboard: ${enableHardwareKeyboard}`);
// cleanup AVD after execution
const cleanupAvdInput = core.getInput('cleanup-avd');
(0, input_validator_1.checkCleanupAvd)(cleanupAvdInput);
cleanupAvd = cleanupAvdInput === 'true';
console.log(`cleanup AVD: ${cleanupAvd}`);
// emulator build
const emulatorBuildInput = core.getInput('emulator-build');
if (emulatorBuildInput) {
Expand Down Expand Up @@ -222,10 +229,18 @@ function run() {
}
// finally kill the emulator
yield (0, emulator_manager_1.killEmulator)(port);
// cleanup AVD if requested
if (cleanupAvd) {
yield (0, emulator_manager_1.deleteAvd)(avdName);
}
}
catch (error) {
// kill the emulator so the action can exit
yield (0, emulator_manager_1.killEmulator)(port);
// cleanup AVD if requested, even on error
if (cleanupAvd) {
yield (0, emulator_manager_1.deleteAvd)(avdName);
}
core.setFailed(error instanceof Error ? error.message : error);
}
});
Expand Down
16 changes: 16 additions & 0 deletions src/emulator-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,22 @@ export async function killEmulator(port: number): Promise<void> {
}
}

/**
* Deletes the specified AVD.
*/
export async function deleteAvd(avdName: string): Promise<void> {
try {
console.log(`::group::Delete AVD`);
console.log(`Deleting AVD '${avdName}'.`);
await exec.exec(`avdmanager delete avd -n "${avdName}"`);
console.log(`AVD '${avdName}' deleted successfully.`);
} catch (error) {
console.log(`Failed to delete AVD '${avdName}': ${error instanceof Error ? error.message : error}`);
} finally {
console.log(`::endgroup::`);
}
}

async function adb(port: number, command: string): Promise<number> {
return await exec.exec(`adb -s emulator-${port} ${command}`);
}
Expand Down
6 changes: 6 additions & 0 deletions src/input-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ export function checkEnableHardwareKeyboard(enableHardwareKeyboard: string): voi
}
}

export function checkCleanupAvd(cleanupAvd: string): void {
if (!isValidBoolean(cleanupAvd)) {
throw new Error(`Input for input.cleanup-avd should be either 'true' or 'false'.`);
}
}

export function checkEmulatorBuild(emulatorBuild: string): void {
if (isNaN(Number(emulatorBuild)) || !Number.isInteger(Number(emulatorBuild))) {
throw new Error(`Unexpected emulator build: '${emulatorBuild}'.`);
Expand Down
25 changes: 23 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@ import {
checkForceAvdCreation,
checkChannel,
checkEnableHardwareKeyboard,
checkCleanupAvd,
checkDiskSize,
checkPort,
playstoreTargetSubstitution,
MIN_PORT,
} from './input-validator';
import { createAvd, launchEmulator, killEmulator } from './emulator-manager';
import { createAvd, launchEmulator, killEmulator, deleteAvd } from './emulator-manager';
import * as exec from '@actions/exec';
import { parseScript } from './script-parser';
import { getChannelId } from './channel-id-mapper';
import { accessSync, constants } from 'fs';

async function run() {
let port: number = MIN_PORT;
let avdName: string = '';
let cleanupAvd: boolean = false;

try {
console.log(`::group::Configure emulator`);
let linuxSupportKVM = false;
Expand Down Expand Up @@ -85,7 +89,7 @@ async function run() {
console.log(`Disk size: ${diskSize}`);

// custom name used for creating the AVD
const avdName = core.getInput('avd-name');
avdName = core.getInput('avd-name');
console.log(`AVD name: ${avdName}`);

// force AVD creation
Expand Down Expand Up @@ -134,6 +138,12 @@ async function run() {
const enableHardwareKeyboard = enableHardwareKeyboardInput === 'true';
console.log(`enable hardware keyboard: ${enableHardwareKeyboard}`);

// cleanup AVD after execution
const cleanupAvdInput = core.getInput('cleanup-avd');
checkCleanupAvd(cleanupAvdInput);
cleanupAvd = cleanupAvdInput === 'true';
console.log(`cleanup AVD: ${cleanupAvd}`);

// emulator build
const emulatorBuildInput = core.getInput('emulator-build');
if (emulatorBuildInput) {
Expand Down Expand Up @@ -231,9 +241,20 @@ async function run() {

// finally kill the emulator
await killEmulator(port);

// cleanup AVD if requested
if (cleanupAvd) {
await deleteAvd(avdName);
}
} catch (error) {
// kill the emulator so the action can exit
await killEmulator(port);

// cleanup AVD if requested, even on error
if (cleanupAvd) {
await deleteAvd(avdName);
}

core.setFailed(error instanceof Error ? error.message : (error as string));
}
}
Expand Down