forked from dlang-community/setup-dlang
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Currently all of the tests are written as a github workflow. This makes them very inconvenient to run as one would have to be triggering the online CI every times they make a change to the code. On top of this the tests can only verify the final product of the action, if changes are made somewhere deep in the source code and they break something it will not be immediately clear what is broken. For this reason I've written tests in plain typescript that are much easier to be run but still check all the platforms (linux, windows, and macos) on x86_64 and arm64. These tests are covering a big part of the source code, mostly the logic bits in addition to mocking network responses for the code that contacts github.com or dlang.org to determine the versions that needs to be installed. This should make future work on the action way easier. This doesn't make the old tests obsolete as some parts of the code are not easily tested, like fetching release archives and properly extracting them. Add an action that will run the unittests on each system (linux, macos, windows) to assure that any person can run the tests on their system without failures. Signed-off-by: Andrei Horodniceanu <[email protected]>
- Loading branch information
Showing
14 changed files
with
8,361 additions
and
202 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
name: Run the typescript unittests | ||
on: | ||
push: | ||
branches: | ||
- "v*" | ||
pull_request: | ||
branches: | ||
- "*" | ||
defaults: | ||
run: | ||
shell: bash | ||
|
||
jobs: | ||
run-typescript-unittests: | ||
name: Run all the typescript unittests | ||
strategy: | ||
matrix: | ||
# This could be run on only one machine but run on all of them | ||
# to make sure that other developers can run the tests on | ||
# their system. | ||
os: [ macos-latest, ubuntu-latest, windows-latest ] | ||
runs-on: ${{ matrix.os }} | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-node@v4 | ||
with: | ||
node-version: 20 | ||
- name: Run `npm test` | ||
run: | | ||
set -euxo pipefail | ||
npm ci | ||
npm test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import * as main from '../src/main' | ||
import * as core from '@actions/core' | ||
import * as d from '../src/d' | ||
|
||
describe('Testing compiler when...', function(){ | ||
let originalArch: string | ||
|
||
beforeAll(function(){ | ||
originalArch = process.arch; | ||
jest.spyOn(core, 'getInput').mockReturnValue('') | ||
}); | ||
|
||
test('it is unspecified', () => { | ||
Object.defineProperty(process, 'arch', { value: 'x64' }) | ||
expect(main.getActionInputs()).toStrictEqual({ | ||
d_compiler: 'dmd-latest', gh_token: '', dub_version: '', gdmd_sha: '',}) | ||
|
||
Object.defineProperty(process, 'arch', { value: 'arm64' }) | ||
expect(main.getActionInputs()).toStrictEqual({ | ||
d_compiler: 'ldc-latest', gh_token: '', dub_version: '', gdmd_sha: '',}) | ||
}) | ||
|
||
function mockCompiler(compiler: string) { | ||
jest.spyOn(core, 'getInput').mockImplementation((key) => { | ||
if (key == "compiler") | ||
return compiler | ||
return "" | ||
}) | ||
} | ||
|
||
test('it is specified', () => { | ||
Object.defineProperty(process, 'arch', { value: 'x64' }) | ||
mockCompiler('ldc') | ||
expect(main.getActionInputs()).toStrictEqual({ | ||
d_compiler: 'ldc', gh_token: '', dub_version: '', gdmd_sha: '',}) | ||
|
||
Object.defineProperty(process, 'arch', { value: 'x64' }) | ||
mockCompiler('invalid text') | ||
expect(main.getActionInputs()).toStrictEqual({ | ||
d_compiler: 'invalid text', gh_token: '', dub_version: '', gdmd_sha: '',}) | ||
}) | ||
|
||
test('it is dmd on arm64', () => { | ||
Object.defineProperty(process, 'arch', { value: 'arm64' }) | ||
mockCompiler('dmd-beta') | ||
expect(main.getActionInputs).toThrow('dmd') | ||
}) | ||
|
||
afterAll(function(){ | ||
Object.defineProperty(process, 'arch', { value: originalArch }); | ||
}); | ||
}); | ||
|
||
test('All action inputs', () => { | ||
jest.spyOn(core, 'getInput').mockImplementation((key) => { | ||
switch (key) { | ||
case "compiler": | ||
return "x" | ||
case "dub": | ||
return "y" | ||
case "gh_token": | ||
return "z" | ||
case "gdmd_sha": | ||
return "t" | ||
default: | ||
throw new Error(`Unknown key '${key}'`) | ||
} | ||
}) | ||
|
||
expect(main.getActionInputs()).toStrictEqual({ | ||
d_compiler: 'x', dub_version: 'y', gh_token: 'z', gdmd_sha: 't' | ||
}) | ||
}) | ||
|
||
describe('Action messages', () => { | ||
let nopTool = { makeAvailable: jest.fn() } | ||
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}) | ||
|
||
beforeAll(() => { | ||
// Bypass type safety | ||
jest.spyOn(d.DMD, 'initialize').mockResolvedValue(<any>nopTool) | ||
jest.spyOn(d.LDC, 'initialize').mockResolvedValue(<any>nopTool) | ||
jest.spyOn(d.GDC, 'initialize').mockResolvedValue(<any>nopTool) | ||
jest.spyOn(d.GDMD, 'initialize').mockResolvedValue(<any>nopTool) | ||
jest.spyOn(d.Dub, 'initialize').mockResolvedValue(<any>nopTool) | ||
// Silence the failures | ||
jest.spyOn(core, 'setFailed').mockImplementation(() => {}) | ||
}) | ||
beforeEach(() => { | ||
nopTool.makeAvailable.mockClear() | ||
consoleSpy.mockClear() | ||
}) | ||
|
||
function mockInputs(compiler: string, dub: string = '') { | ||
jest.spyOn(core, 'getInput').mockImplementation((key) => { | ||
if (key == "compiler") | ||
return compiler | ||
else if (key == 'dub') | ||
return dub | ||
return '' | ||
}) | ||
} | ||
|
||
test('Specifying both compiler and dub', async () => { | ||
let compString = 'dmd-2.110.0-beta.1' | ||
let dubString = 'dub-secret-version' | ||
mockInputs(compString, dubString) | ||
await main.run() | ||
|
||
expect(nopTool.makeAvailable).toHaveBeenCalledTimes(2) | ||
expect(consoleSpy.mock.calls.length).toBe(2) | ||
expect(consoleSpy.mock.calls[0][0]).toMatch(compString) | ||
expect(consoleSpy.mock.calls[0][0]).toMatch(dubString) | ||
expect(consoleSpy.mock.calls[1][0]).toMatch('Done') | ||
}) | ||
|
||
test('Specifying only the compiler', async () => { | ||
let compString = 'dmd-2.110.0-beta.1' | ||
mockInputs(compString) | ||
await main.run() | ||
|
||
expect(nopTool.makeAvailable).toHaveBeenCalledTimes(1) | ||
expect(consoleSpy.mock.calls.length).toBe(2) | ||
expect(consoleSpy.mock.calls[0][0]).toMatch(compString) | ||
expect(consoleSpy.mock.calls[0][0]).not.toMatch('dub') | ||
expect(consoleSpy.mock.calls[1][0]).toMatch('Done') | ||
}) | ||
|
||
test('Specifying an invalid compiler', async () => { | ||
let compString = 'this-is-not-a-real-compiler' | ||
mockInputs(compString) | ||
await main.run() | ||
|
||
expect(nopTool.makeAvailable).toHaveBeenCalledTimes(0) | ||
expect(consoleSpy).toHaveBeenCalledTimes(1) | ||
expect(consoleSpy.mock.calls[0][0]).toMatch(compString) | ||
}) | ||
|
||
test('Specifying a valid compiler', async () => { | ||
// This is only for coverage's sake | ||
for (var comp of [ 'dmd', 'ldc', 'gdc', 'gdmd' ]) { | ||
mockInputs(comp) | ||
await main.run() | ||
expect(nopTool.makeAvailable).toHaveBeenCalledTimes(1) | ||
expect(consoleSpy).toHaveBeenCalledTimes(2) | ||
nopTool.makeAvailable.mockClear() | ||
consoleSpy.mockClear() | ||
} | ||
}) | ||
|
||
test('Errors are caught and displayed', async () => { | ||
let msg = 'dmd-secret-recipe' | ||
jest.spyOn(d.DMD, 'initialize').mockImplementation(async () => { | ||
throw Error(msg) | ||
}) | ||
mockInputs('dmd') | ||
await main.run() | ||
expect(consoleSpy).toHaveBeenCalledTimes(1) | ||
expect(consoleSpy.mock.calls[0][0]).toMatch(msg) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import { Compiler } from '../src/d' | ||
import fs from 'fs' | ||
|
||
describe('Test Compiler class', () => { | ||
const logSpy = jest.spyOn(console, 'log').mockReturnValue(undefined) | ||
jest.spyOn(process.stdout, 'write').mockReturnValue(true) | ||
|
||
let originalEnv = process.env | ||
afterEach(() => process.env = originalEnv) | ||
|
||
beforeEach(() => logSpy.mockClear()) | ||
|
||
const bin = '/relative/path/to/bin' | ||
const libs = [ '/first', '/second' ] | ||
const name = 'compiler_name' | ||
let c = new Compiler('url', undefined, name, 'ver', bin, libs) | ||
const root = '/root/folder' | ||
|
||
// The values are computed when d.ts is imported so | ||
// they will have the values of the host system, even | ||
// if process.platform is modified in the tests. | ||
const sep = process.platform == 'win32' ? '\\' : '/' | ||
const extension = process.platform == 'win32' ? '.exe' : '' | ||
|
||
test('Test setting PATH', () => { | ||
process.env['PATH']='/bin' | ||
c.addBinPath(root) | ||
expect(process.env['PATH']).toBe(root + bin + ':/bin') | ||
expect(logSpy).toHaveBeenCalledTimes(1) | ||
}) | ||
|
||
test('Test setting LD_LIBRARY_PATH on UNIX', () => { | ||
for (let platform of [ 'linux', 'freebsd', 'darwin' ]) { | ||
Object.defineProperty(process, 'platform', { value: platform }) | ||
|
||
process.env['LD_LIBRARY_PATH']='' | ||
jest.spyOn(fs, 'existsSync').mockReturnValue(true) | ||
|
||
c.addLibPaths(root) | ||
expect(process.env['LD_LIBRARY_PATH']).toBe( | ||
root + libs[1] + ":" + root + libs[0]) | ||
expect(logSpy).toHaveBeenCalledTimes(2) | ||
expect(logSpy.mock.calls[0][0]).toMatch(root + libs[0]) | ||
expect(logSpy.mock.calls[1][0]).toMatch(root + libs[1]) | ||
logSpy.mockClear() | ||
|
||
process.env['LD_LIBRARY_PATH']='' | ||
jest.spyOn(fs, 'existsSync') | ||
.mockReturnValueOnce(false) | ||
.mockReturnValueOnce(true) | ||
|
||
c.addLibPaths(root) | ||
expect(process.env['LD_LIBRARY_PATH']).toBe(root + libs[1]) | ||
expect(logSpy).toHaveBeenCalledTimes(1) | ||
expect(logSpy.mock.calls[0][0]).toMatch(root + libs[1]) | ||
logSpy.mockClear() | ||
} | ||
}) | ||
|
||
test('Test setting PATH for libraries on windows', () => { | ||
Object.defineProperty(process, 'platform', { value: 'win32' }) | ||
|
||
process.env['PATH']='\\bin' | ||
jest.spyOn(fs, 'existsSync').mockReturnValue(true) | ||
|
||
c.addLibPaths(root) | ||
expect(process.env['PATH']).toBe( | ||
root + libs[1] + ":" + root + libs[0] + ':\\bin') | ||
expect(logSpy).toHaveBeenCalledTimes(2) | ||
expect(logSpy.mock.calls[0][0]).toMatch(root + libs[0]) | ||
expect(logSpy.mock.calls[1][0]).toMatch(root + libs[1]) | ||
logSpy.mockClear() | ||
|
||
process.env['PATH']='\\dir' | ||
jest.spyOn(fs, 'existsSync') | ||
.mockReturnValueOnce(true) | ||
.mockReturnValueOnce(false) | ||
|
||
c.addLibPaths(root) | ||
expect(process.env['PATH']).toBe(root + libs[0] + ':\\dir') | ||
expect(logSpy).toHaveBeenCalledTimes(1) | ||
expect(logSpy.mock.calls[0][0]).toMatch(root + libs[0]) | ||
logSpy.mockClear() | ||
}) | ||
|
||
test('Test makeAvailable', async () => { | ||
jest.spyOn(c, 'getCached').mockResolvedValue(root) | ||
|
||
for (const platform of [ 'linux', 'darwin', 'freebsd' ]) { | ||
Object.defineProperty(process, 'platform', { value: platform }) | ||
jest.spyOn(fs, 'existsSync').mockReturnValue(true). | ||
mockReturnValueOnce(false) | ||
|
||
process.env['PATH'] = '/bin' | ||
process.env['LD_LIBRARY_PATH'] = '' | ||
await c.makeAvailable() | ||
|
||
expect(process.env['PATH']).toBe(root + bin + ':/bin') | ||
expect(process.env['LD_LIBRARY_PATH']).toBe(root + libs[1]) | ||
expect(process.env['DC']).toBe(`${root}${bin}${sep}${name}${extension}`) | ||
} | ||
|
||
|
||
Object.defineProperty(process, 'platform', { value: 'win32' }) | ||
jest.spyOn(fs, 'existsSync').mockReturnValue(true) | ||
|
||
process.env['PATH'] = '\\bin' | ||
//logSpy.mockRestore() | ||
await c.makeAvailable() | ||
|
||
const expPath = `${root}${libs[1]}:` + `${root}${libs[0]}:` + `${root}${bin}:` + | ||
'\\bin' | ||
expect(process.env['PATH']).toBe(expPath) | ||
expect(process.env['DC']).toBe(`${root}${bin}${sep}${name}${extension}`) | ||
}) | ||
|
||
test('DC gets set to the absolute path of the compiler', () => { | ||
for (const platform of [ 'linux', 'win32', 'darwin', 'freebsd' ]) { | ||
Object.defineProperty(process, 'platform', { value: platform }) | ||
c.setDC(root) | ||
expect(process.env['DC']).toBe(root + bin + sep + name + extension) | ||
} | ||
}) | ||
}) |
Oops, something went wrong.