mirror of https://github.com/actions/toolkit
367 lines
14 KiB
TypeScript
367 lines
14 KiB
TypeScript
import * as io from '../../io/src/io'
|
|
import * as path from 'path'
|
|
import {MatchKind} from '../src/internal-match-kind'
|
|
import {promises as fs} from 'fs'
|
|
|
|
// Mock 'os' before importing Pattern
|
|
/* eslint-disable import/first */
|
|
/* eslint-disable @typescript-eslint/promise-function-async */
|
|
// Note, @typescript-eslint/promise-function-async is a false positive due to the
|
|
// mock factory delegate which returns any. Fixed in a future version of jest.
|
|
jest.mock('os', () => jest.requireActual('os'))
|
|
const os = jest.requireMock('os')
|
|
import {Pattern} from '../src/internal-pattern'
|
|
jest.resetModuleRegistry()
|
|
|
|
const IS_WINDOWS = process.platform === 'win32'
|
|
|
|
describe('pattern', () => {
|
|
beforeAll(async () => {
|
|
await io.rmRF(getTestTemp())
|
|
})
|
|
|
|
it('counts leading negate markers', () => {
|
|
const actual = [
|
|
'/initial-includes/*.txt',
|
|
'!!/hello/two-negate-markers.txt',
|
|
'!!!!/hello/four-negate-markers.txt',
|
|
'!/initial-includes/one-negate-markers.txt',
|
|
'!!!/initial-includes/three-negate-markers.txt'
|
|
].map(x => new Pattern(x).negate)
|
|
expect(actual).toEqual([false, false, false, true, true])
|
|
})
|
|
|
|
it('escapes homedir', async () => {
|
|
const originalHomedir = os.homedir
|
|
const home = path.join(getTestTemp(), 'home-with-[and]')
|
|
await fs.mkdir(home, {recursive: true})
|
|
try {
|
|
os.homedir = () => home
|
|
const pattern = new Pattern('~/m*')
|
|
|
|
expect(pattern.searchPath).toBe(home)
|
|
expect(pattern.match(path.join(home, 'match'))).toBeTruthy()
|
|
expect(pattern.match(path.join(home, 'not-match'))).toBeFalsy()
|
|
} finally {
|
|
os.homedir = originalHomedir
|
|
}
|
|
})
|
|
|
|
it('escapes root', async () => {
|
|
const originalCwd = process.cwd()
|
|
const rootPath = path.join(getTestTemp(), 'cwd-with-[and]')
|
|
await fs.mkdir(rootPath, {recursive: true})
|
|
try {
|
|
process.chdir(rootPath)
|
|
|
|
// Relative
|
|
let pattern = new Pattern('m*')
|
|
expect(pattern.searchPath).toBe(rootPath)
|
|
expect(pattern.match(path.join(rootPath, 'match'))).toBeTruthy()
|
|
expect(pattern.match(path.join(rootPath, 'not-match'))).toBeFalsy()
|
|
|
|
if (IS_WINDOWS) {
|
|
const currentDrive = process.cwd().substr(0, 2)
|
|
expect(currentDrive.match(/^[A-Z]:$/i)).toBeTruthy()
|
|
|
|
// Relative current drive letter, e.g. C:m*
|
|
pattern = new Pattern(`${currentDrive}m*`)
|
|
expect(pattern.searchPath).toBe(rootPath)
|
|
expect(pattern.match(path.join(rootPath, 'match'))).toBeTruthy()
|
|
expect(pattern.match(path.join(rootPath, 'not-match'))).toBeFalsy()
|
|
|
|
// Relative current drive, e.g. \path\to\cwd\m*
|
|
pattern = new Pattern(
|
|
`${Pattern.globEscape(process.cwd().substr(2))}\\m*`
|
|
)
|
|
expect(pattern.searchPath).toBe(rootPath)
|
|
expect(pattern.match(path.join(rootPath, 'match'))).toBeTruthy()
|
|
expect(pattern.match(path.join(rootPath, 'not-match'))).toBeFalsy()
|
|
}
|
|
} finally {
|
|
process.chdir(originalCwd)
|
|
}
|
|
})
|
|
|
|
it('globstar matches immediately preceeding directory', () => {
|
|
const root = IS_WINDOWS ? 'C:\\' : '/'
|
|
const pattern = new Pattern(`${root}foo/bar/**`)
|
|
const actual = [
|
|
root,
|
|
`${root}foo`,
|
|
`${root}foo/bar`,
|
|
`${root}foo/bar/baz`
|
|
].map(x => pattern.match(x))
|
|
expect(actual).toEqual([
|
|
MatchKind.None,
|
|
MatchKind.None,
|
|
MatchKind.All,
|
|
MatchKind.All
|
|
])
|
|
})
|
|
|
|
it('is case insensitive match on Windows', () => {
|
|
const root = IS_WINDOWS ? 'C:\\' : '/'
|
|
const pattern = new Pattern(`${root}Foo/**/Baz`)
|
|
expect(pattern.match(`${root}Foo/Baz`)).toBe(MatchKind.All)
|
|
expect(pattern.match(`${root}Foo/bAZ`)).toBe(
|
|
IS_WINDOWS ? MatchKind.All : MatchKind.None
|
|
)
|
|
expect(pattern.match(`${root}fOO/Baz`)).toBe(
|
|
IS_WINDOWS ? MatchKind.All : MatchKind.None
|
|
)
|
|
expect(pattern.match(`${root}fOO/bar/bAZ`)).toBe(
|
|
IS_WINDOWS ? MatchKind.All : MatchKind.None
|
|
)
|
|
})
|
|
|
|
it('is case insensitive partial match on Windows', () => {
|
|
const root = IS_WINDOWS ? 'C:\\' : '/'
|
|
const pattern = new Pattern(`${root}Foo/Bar/**/Baz`)
|
|
expect(pattern.partialMatch(`${root}Foo`)).toBeTruthy()
|
|
expect(pattern.partialMatch(`${root}fOO`)).toBe(IS_WINDOWS ? true : false)
|
|
})
|
|
|
|
it('matches root', () => {
|
|
const pattern = new Pattern(IS_WINDOWS ? 'C:\\**' : '/**')
|
|
expect(pattern.match(IS_WINDOWS ? 'C:\\' : '/')).toBe(MatchKind.All)
|
|
})
|
|
|
|
it('partial matches root', () => {
|
|
if (IS_WINDOWS) {
|
|
let pattern = new Pattern('C:\\foo\\**')
|
|
expect(pattern.partialMatch('c:\\')).toBeTruthy()
|
|
pattern = new Pattern('c:\\foo\\**')
|
|
expect(pattern.partialMatch('C:\\')).toBeTruthy()
|
|
} else {
|
|
const pattern = new Pattern('/foo/**')
|
|
expect(pattern.partialMatch('/')).toBeTruthy()
|
|
}
|
|
})
|
|
|
|
it('replaces leading . segment', () => {
|
|
// Pattern is '.'
|
|
let pattern = new Pattern('.')
|
|
expect(pattern.match(process.cwd())).toBe(MatchKind.All)
|
|
expect(pattern.match(path.join(process.cwd(), 'foo'))).toBe(MatchKind.None)
|
|
|
|
// Pattern is './foo'
|
|
pattern = new Pattern('./foo')
|
|
expect(pattern.match(path.join(process.cwd(), 'foo'))).toBe(MatchKind.All)
|
|
expect(pattern.match(path.join(process.cwd(), 'bar'))).toBe(MatchKind.None)
|
|
|
|
// Pattern is '.foo'
|
|
pattern = new Pattern('.foo')
|
|
expect(pattern.match(path.join(process.cwd(), '.foo'))).toBe(MatchKind.All)
|
|
expect(pattern.match(path.join(process.cwd(), 'foo'))).toBe(MatchKind.None)
|
|
expect(pattern.match(`${process.cwd()}foo`)).toBe(MatchKind.None)
|
|
})
|
|
|
|
it('replaces leading ~ segment', async () => {
|
|
const homedir = os.homedir()
|
|
expect(homedir).toBeTruthy()
|
|
await fs.stat(homedir)
|
|
|
|
// Pattern is '~'
|
|
let pattern = new Pattern('~')
|
|
expect(pattern.match(homedir)).toBe(MatchKind.All)
|
|
expect(pattern.match(path.join(homedir, 'foo'))).toBe(MatchKind.None)
|
|
|
|
// Pattern is '~/foo'
|
|
pattern = new Pattern('~/foo')
|
|
expect(pattern.match(path.join(homedir, 'foo'))).toBe(MatchKind.All)
|
|
expect(pattern.match(path.join(homedir, 'bar'))).toBe(MatchKind.None)
|
|
|
|
// Pattern is '~foo'
|
|
pattern = new Pattern('~foo')
|
|
expect(pattern.match(path.join(process.cwd(), '~foo'))).toBe(MatchKind.All)
|
|
expect(pattern.match(path.join(homedir, 'foo'))).toBe(MatchKind.None)
|
|
expect(pattern.match(`${homedir}foo`)).toBe(MatchKind.None)
|
|
})
|
|
|
|
it('replaces leading relative root', () => {
|
|
if (IS_WINDOWS) {
|
|
const currentDrive = process.cwd().substr(0, 2)
|
|
expect(currentDrive.match(/^[A-Z]:$/i)).toBeTruthy()
|
|
const otherDrive = currentDrive.toUpperCase().startsWith('C')
|
|
? 'D:'
|
|
: 'C:'
|
|
expect(process.cwd().length).toBeGreaterThan(3) // sanity check not drive root
|
|
|
|
// Pattern is 'C:'
|
|
let pattern = new Pattern(currentDrive)
|
|
expect(pattern.match(process.cwd())).toBeTruthy()
|
|
expect(pattern.match(path.join(process.cwd(), 'foo'))).toBeFalsy()
|
|
|
|
// Pattern is 'C:foo'
|
|
pattern = new Pattern(`${currentDrive}foo`)
|
|
expect(pattern.match(path.join(process.cwd(), 'foo'))).toBeTruthy()
|
|
expect(pattern.match(path.join(process.cwd(), 'bar'))).toBeFalsy()
|
|
expect(pattern.match(`${currentDrive}\\foo`)).toBeFalsy()
|
|
|
|
// Pattern is 'X:'
|
|
pattern = new Pattern(otherDrive)
|
|
expect(pattern.match(`${otherDrive}\\`)).toBeTruthy()
|
|
expect(pattern.match(`${otherDrive}\\foo`)).toBeFalsy()
|
|
|
|
// Pattern is 'X:foo'
|
|
pattern = new Pattern(`${otherDrive}foo`)
|
|
expect(pattern.match(`${otherDrive}\\foo`)).toBeTruthy()
|
|
expect(pattern.match(`${otherDrive}\\bar`)).toBeFalsy()
|
|
|
|
// Pattern is '\\path\\to\\cwd'
|
|
pattern = new Pattern(`${process.cwd().substr(2)}\\foo`)
|
|
expect(pattern.match(path.join(process.cwd(), 'foo'))).toBeTruthy()
|
|
expect(pattern.match(path.join(process.cwd(), 'bar'))).toBeFalsy()
|
|
}
|
|
})
|
|
|
|
it('roots exclude pattern', () => {
|
|
const patternStrings = ['!hello.txt', '!**/world.txt']
|
|
const actual = patternStrings.map(x => new Pattern(x))
|
|
const expected = patternStrings
|
|
.map(x => x.substr(1))
|
|
.map(x => path.join(Pattern.globEscape(process.cwd()), x))
|
|
.map(x => `!${x}`)
|
|
.map(x => new Pattern(x))
|
|
expect(actual.map(x => x.negate)).toEqual([true, true])
|
|
expect(actual.map(x => x.segments)).toEqual(expected.map(x => x.segments))
|
|
})
|
|
|
|
it('roots include pattern', () => {
|
|
const patternStrings = ['hello.txt', '**/world.txt']
|
|
const actual = patternStrings.map(x => new Pattern(x))
|
|
const expected = patternStrings.map(
|
|
x => new Pattern(path.join(Pattern.globEscape(process.cwd()), x))
|
|
)
|
|
expect(actual.map(x => x.segments)).toEqual(expected.map(x => x.segments))
|
|
})
|
|
|
|
it('sets trailing separator', () => {
|
|
expect(new Pattern(' foo ').trailingSeparator).toBeFalsy()
|
|
expect(new Pattern(' /foo ').trailingSeparator).toBeFalsy()
|
|
expect(new Pattern('! /foo ').trailingSeparator).toBeFalsy()
|
|
expect(new Pattern(' /foo/* ').trailingSeparator).toBeFalsy()
|
|
expect(new Pattern(' /foo/** ').trailingSeparator).toBeFalsy()
|
|
expect(new Pattern(' \\foo ').trailingSeparator).toBeFalsy()
|
|
expect(new Pattern('! \\foo ').trailingSeparator).toBeFalsy()
|
|
expect(new Pattern(' \\foo\\* ').trailingSeparator).toBeFalsy()
|
|
expect(new Pattern(' \\foo\\** ').trailingSeparator).toBeFalsy()
|
|
expect(new Pattern(' foo/ ').trailingSeparator).toBeTruthy()
|
|
expect(new Pattern(' /foo/ ').trailingSeparator).toBeTruthy()
|
|
expect(new Pattern(' C:/foo/ ').trailingSeparator).toBeTruthy()
|
|
expect(new Pattern(' C:foo/ ').trailingSeparator).toBeTruthy()
|
|
expect(new Pattern(' D:foo/ ').trailingSeparator).toBeTruthy()
|
|
expect(new Pattern('! /foo/ ').trailingSeparator).toBeTruthy()
|
|
expect(new Pattern(' /foo/*/ ').trailingSeparator).toBeTruthy()
|
|
expect(new Pattern(' /foo/**/ ').trailingSeparator).toBeTruthy()
|
|
expect(new Pattern(' foo\\ ').trailingSeparator).toEqual(
|
|
IS_WINDOWS ? true : false
|
|
)
|
|
expect(new Pattern(' \\foo\\ ').trailingSeparator).toEqual(
|
|
IS_WINDOWS ? true : false
|
|
)
|
|
expect(new Pattern('! \\foo\\ ').trailingSeparator).toEqual(
|
|
IS_WINDOWS ? true : false
|
|
)
|
|
expect(new Pattern(' \\foo\\*\\ ').trailingSeparator).toEqual(
|
|
IS_WINDOWS ? true : false
|
|
)
|
|
expect(new Pattern(' \\foo\\**\\ ').trailingSeparator).toEqual(
|
|
IS_WINDOWS ? true : false
|
|
)
|
|
})
|
|
|
|
it('supports including directories only', () => {
|
|
const root = IS_WINDOWS ? 'C:\\' : '/'
|
|
const pattern = new Pattern(`${root}foo/**/`) // trailing slash
|
|
const actual = [
|
|
root,
|
|
`${root}foo/`,
|
|
`${root}foo/bar`,
|
|
`${root}foo/bar/baz`
|
|
].map(x => pattern.match(x))
|
|
expect(pattern.trailingSeparator).toBeTruthy()
|
|
expect(actual).toEqual([
|
|
MatchKind.None,
|
|
MatchKind.Directory,
|
|
MatchKind.Directory,
|
|
MatchKind.Directory
|
|
])
|
|
})
|
|
|
|
it('trims pattern', () => {
|
|
const pattern = new Pattern(' hello.txt ')
|
|
expect(pattern.segments.reverse()[0]).toBe('hello.txt')
|
|
})
|
|
|
|
it('trims whitespace after trimming negate markers', () => {
|
|
const pattern = new Pattern(' ! ! ! hello.txt ')
|
|
expect(pattern.negate).toBeTruthy()
|
|
expect(pattern.segments.reverse()[0]).toBe('hello.txt')
|
|
})
|
|
|
|
it('unescapes segments to narrow search path', () => {
|
|
// Positive
|
|
const root = IS_WINDOWS ? 'C:\\' : '/'
|
|
let pattern = new Pattern(`${root}foo/b[a]r/b*`)
|
|
expect(pattern.searchPath).toBe(`${root}foo${path.sep}bar`)
|
|
expect(pattern.match(`${root}foo/bar/baz`)).toBeTruthy()
|
|
pattern = new Pattern(`${root}foo/b[*]r/b*`)
|
|
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b*r`)
|
|
expect(pattern.match(`${root}foo/b*r/baz`)).toBeTruthy()
|
|
expect(pattern.match(`${root}foo/bar/baz`)).toBeFalsy()
|
|
pattern = new Pattern(`${root}foo/b[?]r/b*`)
|
|
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b?r`)
|
|
expect(pattern.match(`${root}foo/b?r/baz`)).toBeTruthy()
|
|
expect(pattern.match(`${root}foo/bar/baz`)).toBeFalsy()
|
|
pattern = new Pattern(`${root}foo/b[!]r/b*`)
|
|
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b!r`)
|
|
expect(pattern.match(`${root}foo/b!r/baz`)).toBeTruthy()
|
|
pattern = new Pattern(`${root}foo/b[[]ar/b*`)
|
|
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b[ar`)
|
|
expect(pattern.match(`${root}foo/b[ar/baz`)).toBeTruthy()
|
|
pattern = new Pattern(`${root}foo/b[]r/b*`)
|
|
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b[]r`)
|
|
expect(pattern.match(`${root}foo/b[]r/baz`)).toBeTruthy()
|
|
pattern = new Pattern(`${root}foo/b[r/b*`)
|
|
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b[r`)
|
|
expect(pattern.match(`${root}foo/b[r/baz`)).toBeTruthy()
|
|
pattern = new Pattern(`${root}foo/b]r/b*`)
|
|
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b]r`)
|
|
expect(pattern.match(`${root}foo/b]r/baz`)).toBeTruthy()
|
|
if (!IS_WINDOWS) {
|
|
pattern = new Pattern('/foo/b\\[a]r/b*')
|
|
expect(pattern.searchPath).toBe(`${path.sep}foo${path.sep}b[a]r`)
|
|
expect(pattern.match('/foo/b[a]r/baz')).toBeTruthy()
|
|
pattern = new Pattern('/foo/b[\\!]r/b*')
|
|
expect(pattern.searchPath).toBe(`${path.sep}foo${path.sep}b!r`)
|
|
expect(pattern.match('/foo/b!r/baz')).toBeTruthy()
|
|
pattern = new Pattern('/foo/b[\\]]r/b*')
|
|
expect(pattern.searchPath).toBe(`${path.sep}foo${path.sep}b]r`)
|
|
expect(pattern.match('/foo/b]r/baz')).toBeTruthy()
|
|
pattern = new Pattern('/foo/b[\\a]r/b*')
|
|
expect(pattern.searchPath).toBe(`${path.sep}foo${path.sep}bar`)
|
|
expect(pattern.match('/foo/bar/baz')).toBeTruthy()
|
|
}
|
|
|
|
// Negative
|
|
pattern = new Pattern(`${root}foo/b[aA]r/b*`)
|
|
expect(pattern.searchPath).toBe(`${root}foo`)
|
|
pattern = new Pattern(`${root}foo/b[!a]r/b*`)
|
|
expect(pattern.searchPath).toBe(`${root}foo`)
|
|
if (IS_WINDOWS) {
|
|
pattern = new Pattern('C:/foo/b\\[a]r/b*')
|
|
expect(pattern.searchPath).toBe(`C:\\foo\\b\\ar`)
|
|
expect(pattern.match('C:/foo/b/ar/baz')).toBeTruthy()
|
|
pattern = new Pattern('C:/foo/b[\\!]r/b*')
|
|
expect(pattern.searchPath).toBe('C:\\foo\\b[\\!]r')
|
|
expect(pattern.match('C:/foo/b[undefined/!]r/baz')).toBeTruthy() // Note, "undefined" substr to accommodate a bug in Minimatch when nocase=true
|
|
}
|
|
})
|
|
})
|
|
|
|
function getTestTemp(): string {
|
|
return path.join(__dirname, '_temp', 'internal-pattern')
|
|
}
|