diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9feac5fa0f..3e87fa1b2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,13 +10,13 @@ jobs: runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@main with: fetch-depth: 2 - name: Init Test Suite - uses: potatoqualitee/psmodulecache@v4 + uses: potatoqualitee/psmodulecache@v5.1 with: - modules-to-cache: PSScriptAnalyzer, BuildHelpers, Pester:4.10.1 + modules-to-cache: BuildHelpers shell: powershell - name: Test Scoop Core shell: powershell @@ -26,13 +26,13 @@ jobs: runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@main with: fetch-depth: 2 - name: Init Test Suite - uses: potatoqualitee/psmodulecache@v4 + uses: potatoqualitee/psmodulecache@v5.1 with: - modules-to-cache: PSScriptAnalyzer, BuildHelpers, Pester:4.10.1 + modules-to-cache: BuildHelpers shell: pwsh - name: Test Scoop Core shell: pwsh diff --git a/CHANGELOG.md b/CHANGELOG.md index 94beb9b878..8ae5f38000 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,48 @@ +## [v0.3.1](https://github.com/ScoopInstaller/Scoop/compare/v0.3.0...v0.3.1) - 2022-11-15 + +### Features + +- **config:** Allow Scoop to check if apps versioned as 'nightly' are outdated ([#5166](https://github.com/ScoopInstaller/Scoop/issues/5166)) +- **checkup:** Add Windows Developer Mode check ([#5233](https://github.com/ScoopInstaller/Scoop/issues/5233)) +- **bucket:** Add 'sysinternals' bucket to known ([#5237](https://github.com/ScoopInstaller/Scoop/issues/5237)) + +### Bug Fixes + +- **decompress:** Use PS's default 'Expand-Archive()' ([#5185](https://github.com/ScoopInstaller/Scoop/issues/5185)) +- **hash:** Fix SourceForge's hash extraction ([#5189](https://github.com/ScoopInstaller/Scoop/issues/5189)) +- **decompress:** Trim ending '/' ([#5195](https://github.com/ScoopInstaller/Scoop/issues/5195)) +- **shim:** Exit if shim creating failed 'cause no git ([#5225](https://github.com/ScoopInstaller/Scoop/issues/5225)) +- **scoop-import:** Add correct architecture argument ([#5210](https://github.com/ScoopInstaller/Scoop/issues/5210)) +- **scoop-config:** Output `[DateTime]` as `[String]` ([#5232](https://github.com/ScoopInstaller/Scoop/issues/5232)) + +### Code Refactoring + +- **hash:** Use `Get-FileHash()` directly ([#5177](https://github.com/ScoopInstaller/Scoop/issues/5177)) +- **installer:** Drop the old installer ([#5186](https://github.com/ScoopInstaller/Scoop/issues/5186)) +- **unix:** Remove `unix.ps1` ([#5235](https://github.com/ScoopInstaller/Scoop/issues/5235)) + +### Builds + +- **auto-pr:** Add `CommitMessageFormat` option ([#5171](https://github.com/ScoopInstaller/Scoop/issues/5171)) +- **checkver:** Support XML default namespace ([#5191](https://github.com/ScoopInstaller/Scoop/issues/5191)) +- **pssa:** Remove unused 'ExcludeRules' ([#5201](https://github.com/ScoopInstaller/Scoop/issues/5201)) +- **schema:** Add 'installer' and 'shortcuts' to 'autoupdate' ([#5220](https://github.com/ScoopInstaller/Scoop/issues/5220)) +- **checkhashes:** Use correct version number if `UseCache` ([#5240](https://github.com/ScoopInstaller/Scoop/issues/5240)) + +### Continuous Integration + +- **module:** Update modules version ([#5209](https://github.com/ScoopInstaller/Scoop/issues/5209)) + +### Tests + +- **unix:** Fix tests in Linux and macOS ([#5179](https://github.com/ScoopInstaller/Scoop/issues/5179)) +- **pester:** Update to Pester 5 ([#5222](https://github.com/ScoopInstaller/Scoop/issues/5222)) +- **bucket:** Use BuildHelpers' EnvVars ([#5226](https://github.com/ScoopInstaller/Scoop/issues/5226)) + +### Documentation + +- **scoop-cat:** Fix help message([#5224](https://github.com/ScoopInstaller/Scoop/issues/5224)) + ## [v0.3.0](https://github.com/ScoopInstaller/Scoop/compare/v0.2.4...v0.3.0) - 2022-10-10 ### Features diff --git a/PSScriptAnalyzerSettings.psd1 b/PSScriptAnalyzerSettings.psd1 index f3af0dd8b8..9ef165869e 100644 --- a/PSScriptAnalyzerSettings.psd1 +++ b/PSScriptAnalyzerSettings.psd1 @@ -1,6 +1,3 @@ -# The PowerShell Script Analyzer will generate a warning -# diagnostic record for this file due to a bug - -# https://github.com/PowerShell/PSScriptAnalyzer/issues/472 @{ # Only diagnostic records of the specified severity will be generated. # Uncomment the following line if you only want Errors and Warnings but @@ -26,12 +23,6 @@ # will be excluded. ExcludeRules = @( # Currently Scoop widely uses Write-Host to output colored text. - 'PSAvoidUsingWriteHost', - # Temporarily allow uses of Invoke-Expression, - # this command is used by some core functions and hard to be removed. - 'PSAvoidUsingInvokeExpression', - # PSUseDeclaredVarsMoreThanAssignments doesn't currently work due to: - # https://github.com/PowerShell/PSScriptAnalyzer/issues/636 - 'PSUseDeclaredVarsMoreThanAssignments' + 'PSAvoidUsingWriteHost' ) } diff --git a/README.md b/README.md index 627b73a425..9895584498 100644 --- a/README.md +++ b/README.md @@ -120,17 +120,21 @@ The following buckets are known to scoop: - [games](https://github.com/Calinou/scoop-games) - Open source/freeware games and game-related tools - [nerd-fonts](https://github.com/matthewjberger/scoop-nerd-fonts) - Nerd Fonts - [nirsoft](https://github.com/kodybrown/scoop-nirsoft) - Almost all of the [250+](https://rasa.github.io/scoop-directory/by-apps#kodybrown_scoop-nirsoft) apps from [Nirsoft](https://nirsoft.net) +- [sysinternals](https://github.com/niheaven/scoop-sysinternals) - Sysinternals Suite and all individual application from [Microsoft](https://learn.microsoft.com/sysinternals/) - [java](https://github.com/ScoopInstaller/Java) - A collection of Java development kits (JDKs), Java runtime engines (JREs), Java's virtual machine debugging tools and Java based runtime engines. - [nonportable](https://github.com/ScoopInstaller/Nonportable) - Non-portable apps (may require UAC) - [php](https://github.com/ScoopInstaller/PHP) - Installers for most versions of PHP - [versions](https://github.com/ScoopInstaller/Versions) - Alternative versions of apps found in other buckets The main bucket is installed by default. To add any of the other buckets, type: -``` + +```console scoop bucket add bucketname ``` + For example, to add the extras bucket, type: -``` + +```console scoop bucket add extras ``` diff --git a/bin/auto-pr.ps1 b/bin/auto-pr.ps1 index 390d2117ec..766e49764d 100644 --- a/bin/auto-pr.ps1 +++ b/bin/auto-pr.ps1 @@ -11,6 +11,10 @@ .PARAMETER App Manifest name to search. Placeholders are supported. +.PARAMETER CommitMessageFormat + The format of the commit message. + will be replaced with the file name of manifest. + will be replaced with the version of the latest manifest. .PARAMETER Dir The directory where to search for manifests. .PARAMETER Push @@ -43,6 +47,7 @@ param( [String] $Upstream, [String] $OriginBranch = 'master', [String] $App = '*', + [String] $CommitMessageFormat = ': Update to version ', [ValidateScript( { if (!(Test-Path $_ -Type Container)) { throw "$_ is not a directory!" @@ -61,7 +66,6 @@ param( . "$PSScriptRoot\..\lib\manifest.ps1" . "$PSScriptRoot\..\lib\json.ps1" -. "$PSScriptRoot\..\lib\unix.ps1" if ($App -ne '*' -and (Test-Path $App -PathType Leaf)) { $Dir = Split-Path $App @@ -87,7 +91,7 @@ Optional options: exit 0 } -if (is_unix) { +if ($IsLinux -or $IsMacOS) { if (!(which hub)) { Write-Host "Please install hub ('brew install hub' or visit: https://hub.github.com/)" -ForegroundColor Yellow exit 1 @@ -110,7 +114,7 @@ function execute($cmd) { return $output } -function pull_requests($json, [String] $app, [String] $upstream, [String] $manifest) { +function pull_requests($json, [String] $app, [String] $upstream, [String] $manifest, [String] $commitMessage) { $version = $json.version $homepage = $json.homepage $branch = "manifest/$app-$version" @@ -127,7 +131,7 @@ function pull_requests($json, [String] $app, [String] $upstream, [String] $manif Write-Host "Creating update $app ($version) ..." -ForegroundColor DarkCyan execute "hub checkout -b $branch" execute "hub add $manifest" - execute "hub commit -m '${app}: Update to version $version'" + execute "hub commit -m '$commitMessage" Write-Host "Pushing update $app ($version) ..." -ForegroundColor DarkCyan execute "hub push origin $branch" @@ -142,7 +146,7 @@ function pull_requests($json, [String] $app, [String] $upstream, [String] $manif Write-Host "hub pull-request -m '' -b '$upstream' -h '$branch'" -ForegroundColor Green $msg = @" -$app`: Update to version $version +$commitMessage Hello lovely humans, a new version of [$app]($homepage) is available. @@ -155,7 +159,7 @@ a new version of [$app]($homepage) is available. hub pull-request -m "$msg" -b "$upstream" -h "$branch" if ($LASTEXITCODE -gt 0) { execute 'hub reset' - abort "Pull Request failed! (hub pull-request -m '${app}: Update to version $version' -b '$upstream' -h '$branch')" + abort "Pull Request failed! (hub pull-request -m '$commitMessage' -b '$upstream' -h '$branch')" } } @@ -189,7 +193,7 @@ hub diff --name-only | ForEach-Object { return } $version = $json.version - + $CommitMessage = $CommitMessageFormat -replace '',$app -replace '',$version if ($Push) { Write-Host "Creating update $app ($version) ..." -ForegroundColor DarkCyan execute "hub add $manifest" @@ -198,12 +202,12 @@ hub diff --name-only | ForEach-Object { $status = execute 'hub status --porcelain -uno' $status = $status | Where-Object { $_ -match "M\s{2}.*$app.json" } if ($status -and $status.StartsWith('M ') -and $status.EndsWith("$app.json")) { - execute "hub commit -m '${app}: Update to version $version'" + execute "hub commit -m '$commitMessage'" } else { Write-Host "Skipping $app because only LF/CRLF changes were detected ..." -ForegroundColor Yellow } } else { - pull_requests $json $app $Upstream $manifest + pull_requests $json $app $Upstream $manifest $CommitMessage } } diff --git a/bin/checkhashes.ps1 b/bin/checkhashes.ps1 index fac1787839..0420ee1fa8 100644 --- a/bin/checkhashes.ps1 +++ b/bin/checkhashes.ps1 @@ -47,7 +47,6 @@ param( . "$PSScriptRoot\..\lib\json.ps1" . "$PSScriptRoot\..\lib\versions.ps1" . "$PSScriptRoot\..\lib\install.ps1" -. "$PSScriptRoot\..\lib\unix.ps1" $Dir = Convert-Path $Dir if ($ForceUpdate) { $Update = $true } @@ -114,13 +113,16 @@ foreach ($current in $MANIFESTS) { $current.urls | ForEach-Object { $algorithm, $expected = get_hash $current.hashes[$count] - $version = 'HASH_CHECK' - $tmp = $expected_hash -split ':' + if ($UseCache) { + $version = $current.manifest.version + } else { + $version = 'HASH_CHECK' + } Invoke-CachedDownload $current.app $version $_ $null $null -use_cache:$UseCache $to_check = fullpath (cache_path $current.app $version $_) - $actual_hash = compute_hash $to_check $algorithm + $actual_hash = (Get-FileHash -Path $to_check -Algorithm $algorithm).Hash.ToLower() # Append type of algorithm to both expected and actual if it's not sha256 if ($algorithm -ne 'sha256') { @@ -145,12 +147,12 @@ foreach ($current in $MANIFESTS) { Write-Host 'Mismatch found ' -ForegroundColor Red $mismatched | ForEach-Object { $file = fullpath (cache_path $current.app $version $current.urls[$_]) - Write-Host "`tURL:`t`t$($current.urls[$_])" + Write-Host "`tURL:`t`t$($current.urls[$_])" if (Test-Path $file) { - Write-Host "`tFirst bytes:`t$((get_magic_bytes_pretty $file ' ').ToUpper())" + Write-Host "`tFirst bytes:`t$((get_magic_bytes_pretty $file ' ').ToUpper())" } - Write-Host "`tExpected:`t$($current.hashes[$_])" -ForegroundColor Green - Write-Host "`tActual:`t`t$($actuals[$_])" -ForegroundColor Red + Write-Host "`tExpected:`t$($current.hashes[$_])" -ForegroundColor Green + Write-Host "`tActual:`t`t$($actuals[$_])" -ForegroundColor Red } } diff --git a/bin/checkver.ps1 b/bin/checkver.ps1 index 0205a36639..56b8c8c987 100644 --- a/bin/checkver.ps1 +++ b/bin/checkver.ps1 @@ -74,7 +74,6 @@ param( . "$PSScriptRoot\..\lib\json.ps1" . "$PSScriptRoot\..\lib\versions.ps1" . "$PSScriptRoot\..\lib\install.ps1" # needed for hash generation -. "$PSScriptRoot\..\lib\unix.ps1" if ($App -ne '*' -and (Test-Path $App -PathType Leaf)) { $Dir = Split-Path $App @@ -310,12 +309,17 @@ while ($in_progress -gt 0) { # Then add them into the NamespaceManager $nsmgr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable) $nsList | ForEach-Object { - $nsmgr.AddNamespace($_.LocalName, $_.Value) + if ($_.LocalName -eq 'xmlns') { + $nsmgr.AddNamespace('ns', $_.Value) + $xpath = $xpath -replace '/([^:/]+)((?=/)|(?=$))', '/ns:$1' + } else { + $nsmgr.AddNamespace($_.LocalName, $_.Value) + } } # Getting version from XML, using XPath $ver = $xml.SelectSingleNode($xpath, $nsmgr).'#text' if (!$ver) { - next "couldn't find '$xpath' in $url" + next "couldn't find '$($xpath -replace 'ns:', '')' in $url" continue } } diff --git a/bin/install.ps1 b/bin/install.ps1 index b3942513e4..b7f35591e8 100644 --- a/bin/install.ps1 +++ b/bin/install.ps1 @@ -1,78 +1,2 @@ #Requires -Version 5 - -# remote install: -# Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh') -$old_erroractionpreference = $erroractionpreference -$erroractionpreference = 'stop' # quit if anything goes wrong - -if (($PSVersionTable.PSVersion.Major) -lt 5) { - Write-Output "PowerShell 5 or later is required to run Scoop." - Write-Output "Upgrade PowerShell: https://docs.microsoft.com/en-us/powershell/scripting/setup/installing-windows-powershell" - break -} - -# show notification to change execution policy: -$allowedExecutionPolicy = @('Unrestricted', 'RemoteSigned', 'ByPass') -if ((Get-ExecutionPolicy).ToString() -notin $allowedExecutionPolicy) { - Write-Output "PowerShell requires an execution policy in [$($allowedExecutionPolicy -join ", ")] to run Scoop." - Write-Output "For example, to set the execution policy to 'RemoteSigned' please run :" - Write-Output "'Set-ExecutionPolicy RemoteSigned -scope CurrentUser'" - break -} - -if ([System.Enum]::GetNames([System.Net.SecurityProtocolType]) -notcontains 'Tls12') { - Write-Output "Scoop requires at least .NET Framework 4.5" - Write-Output "Please download and install it first:" - Write-Output "https://www.microsoft.com/net/download" - break -} - -# get core functions -$core_url = 'https://raw.githubusercontent.com/ScoopInstaller/Scoop/master/lib/core.ps1' -Write-Output 'Initializing...' -Invoke-Expression (new-object net.webclient).downloadstring($core_url) - -# prep -if (Get-Command -Name 'scoop' -ErrorAction SilentlyContinue) { - write-host "Scoop is already installed. Run 'scoop update' to get the latest version." -f red - # don't abort if invoked with iex that would close the PS session - if ($myinvocation.mycommand.commandtype -eq 'Script') { return } else { exit 1 } -} -$dir = ensure (versiondir 'scoop' 'current') - -# download scoop zip -$zipUrl = 'https://github.com/ScoopInstaller/Scoop/archive/master.zip' -$zipFile = "$dir\scoop.zip" -Write-Output 'Downloading scoop...' -Invoke-WebRequest -Uri $zipUrl -OutFile $zipFile - -Write-Output 'Extracting...' -Add-Type -Assembly "System.IO.Compression.FileSystem" -[IO.Compression.ZipFile]::ExtractToDirectory($zipfile, "$dir\_tmp") -Copy-Item "$dir\_tmp\*master\*" $dir -Recurse -Force -Remove-Item "$dir\_tmp", $zipfile -Recurse -Force - -Write-Output 'Creating shim...' -shim "$dir\bin\scoop.ps1" $false - -# download main bucket -$dir = "$scoopdir\buckets\main" -$zipUrl = 'https://github.com/ScoopInstaller/Main/archive/master.zip' -$zipFile = "$dir\main-bucket.zip" -Write-Output 'Downloading main bucket...' -New-Item -Path $dir -Type Directory -Force | Out-Null -Invoke-WebRequest -Uri $zipUrl -OutFile $zipFile - -Write-Output 'Extracting...' -[IO.Compression.ZipFile]::ExtractToDirectory($zipfile, "$dir\_tmp") -Copy-Item "$dir\_tmp\*-master\*" $dir -Recurse -Force -Remove-Item "$dir\_tmp", $zipfile -Recurse -Force - -ensure_robocopy_in_path - -set_config LAST_UPDATE ([System.DateTime]::Now.ToString('o')) | Out-Null -success 'Scoop was installed successfully!' - -Write-Output "Type 'scoop help' for instructions." - -$erroractionpreference = $old_erroractionpreference # Reset $erroractionpreference to original value +Invoke-RestMethod https://get.scoop.sh | Invoke-Expression diff --git a/buckets.json b/buckets.json index ff5ab7c0e6..b192cac40f 100644 --- a/buckets.json +++ b/buckets.json @@ -3,6 +3,7 @@ "extras": "https://github.com/ScoopInstaller/Extras", "versions": "https://github.com/ScoopInstaller/Versions", "nirsoft": "https://github.com/kodybrown/scoop-nirsoft", + "sysinternals": "https://github.com/niheaven/scoop-sysinternals", "php": "https://github.com/ScoopInstaller/PHP", "nerd-fonts": "https://github.com/matthewjberger/scoop-nerd-fonts", "nonportable": "https://github.com/ScoopInstaller/Nonportable", diff --git a/lib/autoupdate.ps1 b/lib/autoupdate.ps1 index dc9dbffeb3..6edc8791ff 100644 --- a/lib/autoupdate.ps1 +++ b/lib/autoupdate.ps1 @@ -248,7 +248,7 @@ function get_hash_for_app([String] $app, $config, [String] $version, [String] $u 'sourceforge' { # change the URL because downloads.sourceforge.net doesn't have checksums $hashfile_url = (strip_filename (strip_fragment "https://sourceforge.net/projects/$($matches['project'])/files/$($matches['file'])")).TrimEnd('/') - $hash = find_hash_in_textfile $hashfile_url $substitutions '"$basename":.*?"sha1":\s"([a-fA-F0-9]{40})"' + $hash = find_hash_in_textfile $hashfile_url $substitutions '"$basename":.*?"sha1":\s*"([a-fA-F0-9]{40})"' } } @@ -274,7 +274,7 @@ function get_hash_for_app([String] $app, $config, [String] $version, [String] $u return $null } $file = fullpath (cache_path $app $version $url) - $hash = compute_hash $file 'sha256' + $hash = (Get-FileHash -Path $file -Algorithm SHA256).Hash.ToLower() write-host -f DarkYellow 'Computed hash: ' -NoNewline write-host -f Green $hash return $hash diff --git a/lib/core.ps1 b/lib/core.ps1 index db305b5e26..bc0365eb03 100644 --- a/lib/core.ps1 +++ b/lib/core.ps1 @@ -806,6 +806,11 @@ function shim($path, $global, $name, $arg) { } else { warn_on_overwrite "$shim.cmd" $path # find path to Git's bash so that batch scripts can run bash scripts + if (!(Get-CommandPath git)) { + error "Can't shim '$shim': 'git' is needed but not installed." + error "Please install git ('scoop install git') and try again." + exit 1 + } $gitdir = (Get-Item (Get-CommandPath git) -ErrorAction:Stop).Directory.Parent if ($gitdir.FullName -imatch 'mingw') { $gitdir = $gitdir.Parent @@ -1251,10 +1256,10 @@ if ($scoopConfig) { # END NOTE # Scoop root directory -$scoopdir = $env:SCOOP, (get_config ROOT_PATH), "$env:USERPROFILE\scoop" | Where-Object { -not [String]::IsNullOrEmpty($_) } | Select-Object -First 1 +$scoopdir = $env:SCOOP, (get_config ROOT_PATH), "$([System.Environment]::GetFolderPath('UserProfile'))\scoop" | Where-Object { -not [String]::IsNullOrEmpty($_) } | Select-Object -First 1 # Scoop global apps directory -$globaldir = $env:SCOOP_GLOBAL, (get_config GLOBAL_PATH), "$env:ProgramData\scoop" | Where-Object { -not [String]::IsNullOrEmpty($_) } | Select-Object -First 1 +$globaldir = $env:SCOOP_GLOBAL, (get_config GLOBAL_PATH), "$([System.Environment]::GetFolderPath('CommonApplicationData'))\scoop" | Where-Object { -not [String]::IsNullOrEmpty($_) } | Select-Object -First 1 # Scoop cache directory # Note: Setting the SCOOP_CACHE environment variable to use a shared directory diff --git a/lib/decompress.ps1 b/lib/decompress.ps1 index b56402b1d5..91226706d1 100644 --- a/lib/decompress.ps1 +++ b/lib/decompress.ps1 @@ -28,6 +28,7 @@ function Expand-7zipArchive { $7zPath = Get-HelperPath -Helper 7zip } $LogPath = "$(Split-Path $Path)\7zip.log" + $DestinationPath = $DestinationPath.TrimEnd('\') $ArgList = @('x', $Path, "-o$DestinationPath", '-y') $IsTar = ((strip_ext $Path) -match '\.tar$') -or ($Path -match '\.t[abgpx]z2?$') if (!$IsTar -and $ExtractDir) { @@ -240,7 +241,8 @@ function Expand-ZipArchive { $OriDestinationPath = $DestinationPath $DestinationPath = "$DestinationPath\_tmp" } - Expand-Archive -Path $Path -DestinationPath $DestinationPath -Force + # Compatible with Pscx v3 (https://github.com/Pscx/Pscx) ('Microsoft.PowerShell.Archive' is not needed for Pscx v4) + Microsoft.PowerShell.Archive\Expand-Archive -Path $Path -DestinationPath $DestinationPath -Force if ($ExtractDir) { movedir "$DestinationPath\$ExtractDir" $OriDestinationPath | Out-Null Remove-Item $DestinationPath -Recurse -Force diff --git a/lib/diagnostic.ps1 b/lib/diagnostic.ps1 index 5caf8e7c7e..e4cc97e6a2 100644 --- a/lib/diagnostic.ps1 +++ b/lib/diagnostic.ps1 @@ -53,3 +53,17 @@ function check_long_paths { return $true } + +function Get-WindowsDeveloperModeStatus { + $DevModRegistryPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" + if (!(Test-Path -Path $DevModRegistryPath) -or (Get-ItemProperty -Path ` + $DevModRegistryPath -Name AllowDevelopmentWithoutDevLicense -ErrorAction ` + SilentlyContinue).AllowDevelopmentWithoutDevLicense -ne 1) { + warn "Windows Developer Mode is not enabled. Operations relevant to symlinks may fail without proper rights." + Write-Host " You may read more about the symlinks support here:" + Write-Host " https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10/" + return $false + } + + return $true +} diff --git a/lib/getopt.ps1 b/lib/getopt.ps1 index ce12aae9f5..74ffae7c52 100644 --- a/lib/getopt.ps1 +++ b/lib/getopt.ps1 @@ -25,7 +25,7 @@ function getopt($argv, $shortopts, $longopts) { } # ensure these are arrays - $argv = @($argv) + $argv = @($argv -split ' ') $longopts = @($longopts) for ($i = 0; $i -lt $argv.Length; $i++) { diff --git a/lib/install.ps1 b/lib/install.ps1 index fe5a99f00d..cdbfdf1a79 100644 --- a/lib/install.ps1 +++ b/lib/install.ps1 @@ -1,9 +1,8 @@ -function nightly_version($date, $quiet = $false) { - $date_str = $date.tostring("yyyyMMdd") +function nightly_version($quiet = $false) { if (!$quiet) { warn "This is a nightly version. Downloaded files won't be verified." } - "nightly-$date_str" + return "nightly-$(Get-Date -Format 'yyyyMMdd')" } function install_app($app, $architecture, $global, $suggested, $use_cache = $true, $check_hash = $true) { @@ -21,7 +20,7 @@ function install_app($app, $architecture, $global, $suggested, $use_cache = $tru $is_nightly = $version -eq 'nightly' if ($is_nightly) { - $version = nightly_version $(get-date) + $version = nightly_version $check_hash = $false } @@ -660,7 +659,7 @@ function hash_for_url($manifest, $url, $arch) { function check_hash($file, $hash, $app_name) { $file = fullpath $file if(!$hash) { - warn "Warning: No hash in manifest. SHA256 for '$(fname $file)' is:`n $(compute_hash $file 'sha256')" + warn "Warning: No hash in manifest. SHA256 for '$(fname $file)' is:`n $((Get-FileHash -Path $file -Algorithm SHA256).Hash.ToLower())" return $true, $null } @@ -672,7 +671,7 @@ function check_hash($file, $hash, $app_name) { return $false, "Hash type '$algorithm' isn't supported." } - $actual = compute_hash $file $algorithm + $actual = (Get-FileHash -Path $file -Algorithm $algorithm).Hash.ToLower() $expected = $expected.ToLower() if($actual -ne $expected) { @@ -692,25 +691,6 @@ function check_hash($file, $hash, $app_name) { return $true, $null } -function compute_hash($file, $algname) { - try { - if(Test-CommandAvailable Get-FileHash) { - return (Get-FileHash -Path $file -Algorithm $algname).Hash.ToLower() - } else { - $fs = [system.io.file]::openread($file) - $alg = [system.security.cryptography.hashalgorithm]::create($algname) - $hexbytes = $alg.computehash($fs) | ForEach-Object { $_.tostring('x2') } - return [string]::join('', $hexbytes) - } - } catch { - error $_.exception.message - } finally { - if($fs) { $fs.dispose() } - if($alg) { $alg.dispose() } - } - return '' -} - # for dealing with installers function args($config, $dir, $global) { if($config) { return $config | ForEach-Object { (format $_ @{'dir'=$dir;'global'=$global}) } } diff --git a/lib/unix.ps1 b/lib/unix.ps1 deleted file mode 100644 index 1a202c3eaf..0000000000 --- a/lib/unix.ps1 +++ /dev/null @@ -1,45 +0,0 @@ -# Note: This file is for overwriting global variables and functions to make -# them unix compatible. It has to be imported after everything else! - -function is_unix() { $PSVersionTable.Platform -eq 'Unix' } -function is_mac() { $PSVersionTable.OS.ToLower().StartsWith('darwin') } -function is_linux() { $PSVersionTable.OS.ToLower().StartsWith('linux') } - -if(!(is_unix)) { - return # get the hell outta here -} - -# core.ps1 -$scoopdir = $env:SCOOP, (get_config ROOT_PATH), (Join-Path $env:HOME 'scoop') | Select-Object -First 1 -$globaldir = $env:SCOOP_GLOBAL, (get_config 'GLOBAL_PATH'), '/usr/local/scoop' | Select-Object -First 1 -$cachedir = $env:SCOOP_CACHE, (get_config 'CACHE_PATH'), (Join-Path $scoopdir 'cache') | Select-Object -First 1 - -# core.ps1 -function ensure($dir) { - mkdir -p $dir > $null - return Convert-Path $dir -} - -# install.ps1 -function compute_hash($file, $algname) { - if(is_mac) { - switch ($algname) - { - "md5" { $result = (md5 -q $file) } - "sha1" { $result = (shasum -ba 1 $file) } - "sha256" { $result = (shasum -ba 256 $file) } - "sha512" { $result = (shasum -ba 512 $file) } - default { $result = (shasum -ba 256 $file) } - } - } else { - switch ($algname) - { - "md5" { $result = (md5sum -b $file) } - "sha1" { $result = (sha1sum -b $file) } - "sha256" { $result = (sha256sum -b $file) } - "sha512" { $result = (sha512sum -b $file) } - default { $result = (sha256sum -b $file) } - } - } - return $result.split(' ') | Select-Object -first 1 -} diff --git a/lib/versions.ps1 b/lib/versions.ps1 index 60530ec193..6a55fb9cc0 100644 --- a/lib/versions.ps1 +++ b/lib/versions.ps1 @@ -147,9 +147,20 @@ function Compare-Version { $splitReferenceVersion = @(SplitVersion -Version $ReferenceVersion -Delimiter $Delimiter) $splitDifferenceVersion = @(SplitVersion -Version $DifferenceVersion -Delimiter $Delimiter) - # Nightly versions are always equal + # Nightly versions are always equal unless UPDATE_NIGHTLY is $true if ($splitReferenceVersion[0] -eq 'nightly' -and $splitDifferenceVersion[0] -eq 'nightly') { - return 0 + if (get_config UPDATE_NIGHTLY) { + # nightly versions will be compared by date if UPDATE_NIGHTLY is $true + if ($null -eq $splitReferenceVersion[1]) { + $splitReferenceVersion += Get-Date -Format 'yyyyMMdd' + } + if ($null -eq $splitDifferenceVersion[1]) { + $splitDifferenceVersion += Get-Date -Format 'yyyyMMdd' + } + return [Math]::Sign($splitDifferenceVersion[1] - $splitReferenceVersion[1]) + } else { + return 0 + } } for ($i = 0; $i -lt [Math]::Max($splitReferenceVersion.Length, $splitDifferenceVersion.Length); $i++) { diff --git a/libexec/scoop-cat.ps1 b/libexec/scoop-cat.ps1 index 0e420bb046..3e840c7149 100644 --- a/libexec/scoop-cat.ps1 +++ b/libexec/scoop-cat.ps1 @@ -1,5 +1,8 @@ # Usage: scoop cat -# Summary: Show content of specified manifest. If available, `bat` will be used to pretty-print the JSON. +# Summary: Show content of specified manifest. +# Help: Show content of specified manifest. +# If configured, `bat` will be used to pretty-print the JSON. +# See `cat_style` in `scoop help config` for further information. param($app) diff --git a/libexec/scoop-checkup.ps1 b/libexec/scoop-checkup.ps1 index 3c6c5f4f50..a775ec3964 100644 --- a/libexec/scoop-checkup.ps1 +++ b/libexec/scoop-checkup.ps1 @@ -17,6 +17,7 @@ if ($adminPrivileges) { $issues += !(check_main_bucket) $issues += !(check_long_paths) +$issues += !(Get-WindowsDeveloperModeStatus) if (!(Test-HelperInstalled -Helper 7zip)) { error "'7-Zip' is not installed! It's required for unpacking most programs. Please Run 'scoop install 7zip' or 'scoop install 7zip-zstd'." diff --git a/libexec/scoop-config.ps1 b/libexec/scoop-config.ps1 index 9395ca5f97..09487da7c5 100644 --- a/libexec/scoop-config.ps1 +++ b/libexec/scoop-config.ps1 @@ -53,7 +53,7 @@ # # default_architecture: 64bit|32bit|arm64 # Allow to configure preferred architecture for application installation. -# If not specified, architecture is determined be system. +# If not specified, architecture is determined by system. # # debug: $true|$false # Additional and detailed output will be shown. @@ -111,6 +111,10 @@ # Should be in the format 'YYYY-MM-DD', 'YYYY/MM/DD' or any other forms that accepted by '[System.DateTime]::Parse()'. # Ref: https://docs.microsoft.com/dotnet/api/system.datetime.parse?view=netframework-4.5#StringToParse # +# update_nightly: $true|$false +# Nightly version is formatted as 'nightly-yyyyMMdd' and will be updated after one day if this is set to $true. +# Otherwise, nightly version will not be updated unless `--force` is used. +# # ARIA2 configuration # ------------------- # @@ -175,7 +179,11 @@ if (!$name) { if($null -eq $value) { Write-Host "'$name' is not set" } else { - $value + if ($value -is [System.DateTime]) { + $value.ToString('o') + } else { + $value + } } } diff --git a/libexec/scoop-download.ps1 b/libexec/scoop-download.ps1 index 66c7632c92..34de7ea19f 100644 --- a/libexec/scoop-download.ps1 +++ b/libexec/scoop-download.ps1 @@ -85,7 +85,7 @@ foreach ($curr_app in $apps) { $curr_check_hash = $check_hash if ($version -eq 'nightly') { - $version = nightly_version $(get-date) + $version = nightly_version $curr_check_hash = $false } diff --git a/libexec/scoop-import.ps1 b/libexec/scoop-import.ps1 index 7e3ec1c52c..1221e127bf 100644 --- a/libexec/scoop-import.ps1 +++ b/libexec/scoop-import.ps1 @@ -40,12 +40,14 @@ foreach ($item in $import.apps) { } else { '' } - $arch = if ('64bit' -in $info) { + $arch = if ('64bit' -in $info -and '64bit' -ne $def_arch) { ' --arch 64bit' - } elseif ('32bit' -in $info) { + } elseif ('32bit' -in $info -and '32bit' -ne $def_arch) { ' --arch 32bit' - } else { + } elseif ('arm64' -in $info -and 'arm64' -ne $def_arch) { ' --arch arm64' + } else { + '' } $app = if ($item.Source -in $bucket_names) { diff --git a/libexec/scoop-update.ps1 b/libexec/scoop-update.ps1 index 84bd448f78..ad670f86c0 100644 --- a/libexec/scoop-update.ps1 +++ b/libexec/scoop-update.ps1 @@ -202,7 +202,7 @@ function update($app, $global, $quiet = $false, $independent, $suggested, $use_c $version = $manifest.version $is_nightly = $version -eq 'nightly' if ($is_nightly) { - $version = nightly_version $(get-date) $quiet + $version = nightly_version $quiet $check_hash = $false } diff --git a/schema.json b/schema.json index a9c30d3b4e..239218cc23 100644 --- a/schema.json +++ b/schema.json @@ -248,6 +248,15 @@ "hash": { "$ref": "#/definitions/hashExtractionOrArrayOfHashExtractions" }, + "installer": { + "type": "object", + "additionalProperties": false, + "properties": { + "file": { + "type": "string" + } + } + }, "license": { "$ref": "#/definitions/license" }, @@ -266,6 +275,9 @@ } } }, + "shortcuts": { + "$ref": "#/definitions/shortcutsArray" + }, "url": { "$ref": "#/definitions/autoupdateUriOrArrayOfAutoupdateUris" } diff --git a/supporting/shimexe/build.ps1 b/supporting/shimexe/build.ps1 index 54aea9c944..bf90f720ae 100644 --- a/supporting/shimexe/build.ps1 +++ b/supporting/shimexe/build.ps1 @@ -16,7 +16,7 @@ Write-Output 'Computing checksums ...' Remove-Item "$PSScriptRoot\bin\checksum.sha256" -ErrorAction Ignore Remove-Item "$PSScriptRoot\bin\checksum.sha512" -ErrorAction Ignore Get-ChildItem "$PSScriptRoot\bin\*" -Include *.exe, *.dll | ForEach-Object { - "$(compute_hash $_ 'sha256') *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha256" -Append -Encoding oem - "$(compute_hash $_ 'sha512') *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha512" -Append -Encoding oem + "$((Get-FileHash -Path $_ -Algorithm SHA256).Hash.ToLower()) *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha256" -Append -Encoding oem + "$((Get-FileHash -Path $_ -Algorithm SHA512).Hash.ToLower()) *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha512" -Append -Encoding oem } Pop-Location diff --git a/supporting/validator/build.ps1 b/supporting/validator/build.ps1 index 1fcf2c1fdc..c61db3313d 100644 --- a/supporting/validator/build.ps1 +++ b/supporting/validator/build.ps1 @@ -21,7 +21,7 @@ Write-Output 'Computing checksums ...' Remove-Item "$PSScriptRoot\bin\checksum.sha256" -ErrorAction Ignore Remove-Item "$PSScriptRoot\bin\checksum.sha512" -ErrorAction Ignore Get-ChildItem "$PSScriptRoot\bin\*" -Include *.exe, *.dll | ForEach-Object { - "$(compute_hash $_ 'sha256') *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha256" -Append -Encoding oem - "$(compute_hash $_ 'sha512') *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha512" -Append -Encoding oem + "$((Get-FileHash -Path $_ -Algorithm SHA256).Hash.ToLower()) *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha256" -Append -Encoding oem + "$((Get-FileHash -Path $_ -Algorithm SHA512).Hash.ToLower()) *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha512" -Append -Encoding oem } Pop-Location diff --git a/test/00-Project.Tests.ps1 b/test/00-Project.Tests.ps1 deleted file mode 100644 index c45d45c708..0000000000 --- a/test/00-Project.Tests.ps1 +++ /dev/null @@ -1,74 +0,0 @@ -$repo_dir = (Get-Item $MyInvocation.MyCommand.Path).Directory.Parent.FullName - -$repo_files = @( Get-ChildItem $repo_dir -File -Recurse -Force ) - -$project_file_exclusions = @( - '[\\/]\.git[\\/]', - '\.sublime-workspace$', - '\.DS_Store$', - 'supporting(\\|/)validator(\\|/)packages(\\|/)*', - 'supporting(\\|/)shimexe(\\|/)packages(\\|/)*' -) - -Describe 'Project code' { - - $files = @( - $repo_files | - Where-Object { $_.fullname -inotmatch $($project_file_exclusions -join '|') } | - Where-Object { $_.fullname -imatch '.(ps1|psm1)$' } - ) - - $files_exist = ($files.Count -gt 0) - - It $('PowerShell code files exist ({0} found)' -f $files.Count) -Skip:$(-not $files_exist) { - if (-not ($files.Count -gt 0)) { - throw 'No PowerShell code files were found' - } - } - - function Test-PowerShellSyntax { - # ref: http://powershell.org/wp/forums/topic/how-to-check-syntax-of-scripts-automatically @@ https://archive.is/xtSv6 - # originally created by Alexander Petrovskiy & Dave Wyatt - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [string[]] - $Path - ) - - process { - foreach ($scriptPath in $Path) { - $contents = Get-Content -Path $scriptPath - - if ($null -eq $contents) { - continue - } - - $errors = $null - $null = [System.Management.Automation.PSParser]::Tokenize($contents, [ref]$errors) - - New-Object psobject -Property @{ - Path = $scriptPath - SyntaxErrorsFound = ($errors.Count -gt 0) - } - } - } - } - - It 'PowerShell code files do not contain syntax errors' -Skip:$(-not $files_exist) { - $badFiles = @( - foreach ($file in $files) { - if ( (Test-PowerShellSyntax $file.FullName).SyntaxErrorsFound ) { - $file.FullName - } - } - ) - - if ($badFiles.Count -gt 0) { - throw "The following files have syntax errors: `r`n`r`n$($badFiles -join "`r`n")" - } - } - -} - -. "$PSScriptRoot\Import-File-Tests.ps1" diff --git a/test/Import-Bucket-Tests.ps1 b/test/Import-Bucket-Tests.ps1 index b42b3bdc7f..789a4b24f7 100644 --- a/test/Import-Bucket-Tests.ps1 +++ b/test/Import-Bucket-Tests.ps1 @@ -1,127 +1,54 @@ -#Requires -Version 5.0 -#Requires -Modules @{ ModuleName = 'Pester'; RequiredVersion = '4.10.1' } +#Requires -Version 5.1 +#Requires -Modules @{ ModuleName = 'BuildHelpers'; ModuleVersion = '2.0.1' } +#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.2.0' } param( - [ValidateNotNullOrEmpty()] - [String] - $repo_dir = (Split-Path -Path $MyInvocation.PSCommandPath -Parent) + [String] $BucketPath = $MyInvocation.PSScriptRoot ) -. "$PSScriptRoot\Scoop-TestLib.ps1" -. "$PSScriptRoot\..\lib\manifest.ps1" -. "$PSScriptRoot\..\lib\unix.ps1" +. "$PSScriptRoot\Scoop-00File.Tests.ps1" -TestPath $BucketPath -$bucketdir = $repo_dir -if (Test-Path("$repo_dir\..\bucket")) { - $bucketdir = "$repo_dir\..\bucket" -} elseif (Test-Path("$repo_dir\bucket")) { - $bucketdir = "$repo_dir\bucket" -} - -# Tests for non manifest files -$repo_files = @(Get-ChildItem -Path $repo_dir -File -Recurse) -$project_file_exclusions = @( - '[\\/]\.git[\\/]', - '.sublime-workspace$', - '.DS_Store$' -) -. "$PSScriptRoot\Import-File-Tests.ps1" - -# Tests for manifest files -Describe 'Manifest Validator' -Tag 'Validator' { - BeforeAll { - $schema = "$PSScriptRoot\..\schema.json" - $working_dir = setup_working 'manifest' - Add-Type -Path "$PSScriptRoot\..\supporting\validator\bin\Newtonsoft.Json.dll" - Add-Type -Path "$PSScriptRoot\..\supporting\validator\bin\Newtonsoft.Json.Schema.dll" - Add-Type -Path "$PSScriptRoot\..\supporting\validator\bin\Scoop.Validator.dll" - } - - It 'Scoop.Validator is available' { - ([System.Management.Automation.PSTypeName]'Scoop.Validator').Type | Should -Be 'Scoop.Validator' - } - - Context 'parse_json function' { - It 'fails with invalid json' { - { parse_json "$working_dir\broken_wget.json" } | Should -Throw +Describe 'Manifest validates against the schema' { + BeforeDiscovery { + $bucketDir = if (Test-Path "$BucketPath\bucket") { + "$BucketPath\bucket" + } else { + $BucketPath } - } - - Context 'schema validation' { - It 'fails with broken schema' { - $validator = New-Object Scoop.Validator("$working_dir\broken_schema.json", $true) - $validator.Validate("$working_dir\wget.json") | Should -BeFalse - $validator.Errors.Count | Should -Be 1 - $validator.Errors | Select-Object -First 1 | Should -Match 'broken_schema.*(line 6).*(position 4)' - } - It 'fails with broken manifest' { - $validator = New-Object Scoop.Validator($schema, $true) - $validator.Validate("$working_dir\broken_wget.json") | Should -BeFalse - $validator.Errors.Count | Should -Be 1 - $validator.Errors | Select-Object -First 1 | Should -Match 'broken_wget.*(line 5).*(position 4)' + if ($env:CI -eq $true) { + Set-BuildEnvironment -Force + $changedManifests = @(Get-GitChangedFile -Path $bucketDir -Include '*.json' -Commit $env:BHCommitHash) } - It 'fails with invalid manifest' { - $validator = New-Object Scoop.Validator($schema, $true) - $validator.Validate("$working_dir\invalid_wget.json") | Should -BeFalse - $validator.Errors.Count | Should -Be 16 - $validator.Errors | Select-Object -First 1 | Should -Match "Property 'randomproperty' has not been defined and the schema does not allow additional properties\." - $validator.Errors | Select-Object -Last 1 | Should -Match 'Required properties are missing from object: version\.' + $manifestFiles = (Get-ChildItem $bucketDir -Filter '*.json' -Recurse).FullName + if ($changedManifests) { + $manifestFiles = $manifestFiles | Where-Object { $_ -in $changedManifests } } } -} -Describe 'manifest validates against the schema' -Tag 'Manifests' { BeforeAll { - $schema = "$PSScriptRoot\..\schema.json" - $changed_manifests = @() - if ($env:CI -eq $true) { - # AppVeyor - $commit = if ($env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT) { $env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT } else { $env:APPVEYOR_REPO_COMMIT } - - # GitHub Actions - if ($env:GITHUB_SHA) { - $commit = $env:GITHUB_SHA - } - $changed_manifests = (Get-GitChangedFile -Path $repo_dir -Include '*.json' -Commit $commit) - } - $manifest_files = Get-ChildItem $bucketdir -Filter '*.json' -Recurse - $validator = New-Object Scoop.Validator($schema, $true) + Add-Type -Path "$PSScriptRoot\..\supporting\validator\bin\Scoop.Validator.dll" + # Could not use backslash '\' in Linux/macOS for .NET object 'Scoop.Validator' + $validator = New-Object Scoop.Validator("$PSScriptRoot/../schema.json", $true) + $global:quotaExceeded = $false } - - $quota_exceeded = $false - - $manifest_files | ForEach-Object { - $skip_manifest = ($changed_manifests -inotcontains $_.FullName) - if ($env:CI -ne $true -or $changed_manifests -imatch 'schema.json') { - $skip_manifest = $false - } - It "$_" -Skip:$skip_manifest { + It '<_>' -TestCases $manifestFiles { + if ($global:quotaExceeded) { + Set-ItResult -Skipped -Because 'Schema validation limit exceeded.' + } else { $file = $_ # exception handling may overwrite $_ - - if (!($quota_exceeded)) { - try { - $validator.Validate($file.fullname) - - if ($validator.Errors.Count -gt 0) { - Write-Host -f red " [-] $_ has $($validator.Errors.Count) Error$(If($validator.Errors.Count -gt 1) { 's' })!" - Write-Host -f yellow $validator.ErrorsAsString - } - $validator.Errors.Count | Should -Be 0 - } catch { - if ($_.exception.message -like '*The free-quota limit of 1000 schema validations per hour has been reached.*') { - $quota_exceeded = $true - Write-Host -f darkyellow 'Schema validation limit exceeded. Will skip further validations.' - } else { - throw - } + try { + $validator.Validate($file) + if ($validator.Errors.Count -gt 0) { + Write-Host " [-] $_ has $($validator.Errors.Count) Error$(If($validator.Errors.Count -gt 1) { 's' })!" -ForegroundColor Red + Write-Host $validator.ErrorsAsString -ForegroundColor Yellow + } + $validator.Errors.Count | Should -Be 0 + } catch { + if ($_.Exception.Message -like '*The free-quota limit of 1000 schema validations per hour has been reached.*') { + $global:quotaExceeded = $true + Set-ItResult -Skipped -Because 'Schema validation limit exceeded.' + } else { + throw } } - - $manifest = parse_json $file.fullname - $url = arch_specific 'url' $manifest '32bit' - $url64 = arch_specific 'url' $manifest '64bit' - if (!$url) { - $url = $url64 - } - $url | Should -Not -BeNullOrEmpty } } } diff --git a/test/Import-File-Tests.ps1 b/test/Import-File-Tests.ps1 deleted file mode 100644 index 5aadcd3ade..0000000000 --- a/test/Import-File-Tests.ps1 +++ /dev/null @@ -1,134 +0,0 @@ -if ([String]::IsNullOrEmpty($MyInvocation.PSScriptRoot)) { - Write-Error 'This script should not be called directly! It has to be imported from a buckets test file!' - exit 1 -} - -Describe 'Style constraints for non-binary project files' { - - $files = @( - # gather all files except '*.exe', '*.zip', or any .git repository files - $repo_files | - Where-Object { $_.fullname -inotmatch $($project_file_exclusions -join '|') } | - Where-Object { $_.fullname -inotmatch '(.exe|.zip|.dll)$' } | - Where-Object { $_.fullname -inotmatch '(unformatted)' } - ) - - $files_exist = ($files.Count -gt 0) - - It $('non-binary project files exist ({0} found)' -f $files.Count) -Skip:$(-not $files_exist) { - if (-not ($files.Count -gt 0)) { - throw 'No non-binary project were found' - } - } - - It 'files do not contain leading UTF-8 BOM' -Skip:$(-not $files_exist) { - # UTF-8 BOM == 0xEF 0xBB 0xBF - # see http://www.powershellmagazine.com/2012/12/17/pscxtip-how-to-determine-the-byte-order-mark-of-a-text-file @@ https://archive.is/RgT42 - # ref: http://poshcode.org/2153 @@ https://archive.is/sGnnu - $badFiles = @( - foreach ($file in $files) { - if ((Get-Command Get-Content).parameters.ContainsKey('AsByteStream')) { - # PowerShell Core (6.0+) '-Encoding byte' is replaced by '-AsByteStream' - $content = ([char[]](Get-Content $file.FullName -AsByteStream -TotalCount 3) -join '') - } else { - $content = ([char[]](Get-Content $file.FullName -Encoding byte -TotalCount 3) -join '') - } - if ([regex]::match($content, '(?ms)^\xEF\xBB\xBF').success) { - $file.FullName - } - } - ) - - if ($badFiles.Count -gt 0) { - throw "The following files have utf-8 BOM: `r`n`r`n$($badFiles -join "`r`n")" - } - } - - It 'files end with a newline' -Skip:$(-not $files_exist) { - $badFiles = @( - foreach ($file in $files) { - # Ignore previous TestResults.xml - if ($file -match 'TestResults.xml') { - continue - } - $string = [System.IO.File]::ReadAllText($file.FullName) - if ($string.Length -gt 0 -and $string[-1] -ne "`n") { - $file.FullName - } - } - ) - - if ($badFiles.Count -gt 0) { - throw "The following files do not end with a newline: `r`n`r`n$($badFiles -join "`r`n")" - } - } - - It 'file newlines are CRLF' -Skip:$(-not $files_exist) { - $badFiles = @( - foreach ($file in $files) { - $content = [System.IO.File]::ReadAllText($file.FullName) - if (!$content) { - throw "File contents are null: $($file.FullName)" - } - $lines = [regex]::split($content, '\r\n') - $lineCount = $lines.Count - - for ($i = 0; $i -lt $lineCount; $i++) { - if ( [regex]::match($lines[$i], '\r|\n').success ) { - $file.FullName - break - } - } - } - ) - - if ($badFiles.Count -gt 0) { - throw "The following files have non-CRLF line endings: `r`n`r`n$($badFiles -join "`r`n")" - } - } - - It 'files have no lines containing trailing whitespace' -Skip:$(-not $files_exist) { - $badLines = @( - foreach ($file in $files) { - # Ignore previous TestResults.xml - if ($file -match 'TestResults.xml') { - continue - } - $lines = [System.IO.File]::ReadAllLines($file.FullName) - $lineCount = $lines.Count - - for ($i = 0; $i -lt $lineCount; $i++) { - if ($lines[$i] -match '\s+$') { - 'File: {0}, Line: {1}' -f $file.FullName, ($i + 1) - } - } - } - ) - - if ($badLines.Count -gt 0) { - throw "The following $($badLines.Count) lines contain trailing whitespace: `r`n`r`n$($badLines -join "`r`n")" - } - } - - It 'any leading whitespace consists only of spaces (excepting makefiles)' -Skip:$(-not $files_exist) { - $badLines = @( - foreach ($file in $files) { - if ($file.fullname -inotmatch '(^|.)makefile$') { - $lines = [System.IO.File]::ReadAllLines($file.FullName) - $lineCount = $lines.Count - - for ($i = 0; $i -lt $lineCount; $i++) { - if ($lines[$i] -notmatch '^[ ]*(\S|$)') { - 'File: {0}, Line: {1}' -f $file.FullName, ($i + 1) - } - } - } - } - ) - - if ($badLines.Count -gt 0) { - throw "The following $($badLines.Count) lines contain TABs within leading whitespace: `r`n`r`n$($badLines -join "`r`n")" - } - } - -} diff --git a/test/Scoop-00File.Tests.ps1 b/test/Scoop-00File.Tests.ps1 new file mode 100644 index 0000000000..4161c37792 --- /dev/null +++ b/test/Scoop-00File.Tests.ps1 @@ -0,0 +1,189 @@ +param( + [String] $TestPath = "$PSScriptRoot\.." +) + +BeforeDiscovery { + $project_file_exclusions = @( + '[\\/]\.git[\\/]', + '\.sublime-workspace$', + '\.DS_Store$', + 'supporting(\\|/)validator(\\|/)packages(\\|/)*', + 'supporting(\\|/)shimexe(\\|/)packages(\\|/)*' + ) + $repo_files = (Get-ChildItem $TestPath -File -Recurse).FullName | + Where-Object { $_ -inotmatch $($project_file_exclusions -join '|') } +} + +Describe 'Code Syntax' -ForEach @(, $repo_files) -Tag 'File' { + BeforeAll { + $files = @( + $_ | Where-Object { $_ -imatch '.(ps1|psm1)$' } + ) + function Test-PowerShellSyntax { + # ref: http://powershell.org/wp/forums/topic/how-to-check-syntax-of-scripts-automatically @@ https://archive.is/xtSv6 + # originally created by Alexander Petrovskiy & Dave Wyatt + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [string[]] + $Path + ) + + process { + foreach ($scriptPath in $Path) { + $contents = Get-Content -Path $scriptPath + + if ($null -eq $contents) { + continue + } + + $errors = $null + $null = [System.Management.Automation.PSParser]::Tokenize($contents, [ref]$errors) + + New-Object psobject -Property @{ + Path = $scriptPath + SyntaxErrorsFound = ($errors.Count -gt 0) + } + } + } + } + + } + + It 'PowerShell code files do not contain syntax errors' { + $badFiles = @( + foreach ($file in $files) { + if ( (Test-PowerShellSyntax $file).SyntaxErrorsFound ) { + $file + } + } + ) + + if ($badFiles.Count -gt 0) { + throw "The following files have syntax errors: `r`n`r`n$($badFiles -join "`r`n")" + } + } + +} + +Describe 'Style constraints for non-binary project files' -ForEach @(, $repo_files) -Tag 'File' { + BeforeAll { + $files = @( + # gather all files except '*.exe', '*.zip', or any .git repository files + $_ | + Where-Object { $_ -inotmatch '(.exe|.zip|.dll)$' } | + Where-Object { $_ -inotmatch '(unformatted)' } + ) + } + + It 'files do not contain leading UTF-8 BOM' { + # UTF-8 BOM == 0xEF 0xBB 0xBF + # see http://www.powershellmagazine.com/2012/12/17/pscxtip-how-to-determine-the-byte-order-mark-of-a-text-file @@ https://archive.is/RgT42 + # ref: http://poshcode.org/2153 @@ https://archive.is/sGnnu + $badFiles = @( + foreach ($file in $files) { + if ((Get-Command Get-Content).parameters.ContainsKey('AsByteStream')) { + # PowerShell Core (6.0+) '-Encoding byte' is replaced by '-AsByteStream' + $content = ([char[]](Get-Content $file -AsByteStream -TotalCount 3) -join '') + } else { + $content = ([char[]](Get-Content $file -Encoding byte -TotalCount 3) -join '') + } + if ([regex]::match($content, '(?ms)^\xEF\xBB\xBF').success) { + $file + } + } + ) + + if ($badFiles.Count -gt 0) { + throw "The following files have utf-8 BOM: `r`n`r`n$($badFiles -join "`r`n")" + } + } + + It 'files end with a newline' { + $badFiles = @( + foreach ($file in $files) { + # Ignore previous TestResults.xml + if ($file -match 'TestResults.xml') { + continue + } + $string = [System.IO.File]::ReadAllText($file) + if ($string.Length -gt 0 -and $string[-1] -ne "`n") { + $file + } + } + ) + + if ($badFiles.Count -gt 0) { + throw "The following files do not end with a newline: `r`n`r`n$($badFiles -join "`r`n")" + } + } + + It 'file newlines are CRLF' { + $badFiles = @( + foreach ($file in $files) { + $content = [System.IO.File]::ReadAllText($file) + if (!$content) { + throw "File contents are null: $($file)" + } + $lines = [regex]::split($content, '\r\n') + $lineCount = $lines.Count + + for ($i = 0; $i -lt $lineCount; $i++) { + if ( [regex]::match($lines[$i], '\r|\n').success ) { + $file + break + } + } + } + ) + + if ($badFiles.Count -gt 0) { + throw "The following files have non-CRLF line endings: `r`n`r`n$($badFiles -join "`r`n")" + } + } + + It 'files have no lines containing trailing whitespace' { + $badLines = @( + foreach ($file in $files) { + # Ignore previous TestResults.xml + if ($file -match 'TestResults.xml') { + continue + } + $lines = [System.IO.File]::ReadAllLines($file) + $lineCount = $lines.Count + + for ($i = 0; $i -lt $lineCount; $i++) { + if ($lines[$i] -match '\s+$') { + 'File: {0}, Line: {1}' -f $file, ($i + 1) + } + } + } + ) + + if ($badLines.Count -gt 0) { + throw "The following $($badLines.Count) lines contain trailing whitespace: `r`n`r`n$($badLines -join "`r`n")" + } + } + + It 'any leading whitespace consists only of spaces (excepting makefiles)' { + $badLines = @( + foreach ($file in $files) { + if ($file -inotmatch '(^|.)makefile$') { + $lines = [System.IO.File]::ReadAllLines($file) + $lineCount = $lines.Count + + for ($i = 0; $i -lt $lineCount; $i++) { + if ($lines[$i] -notmatch '^[ ]*(\S|$)') { + 'File: {0}, Line: {1}' -f $file, ($i + 1) + } + } + } + } + ) + + if ($badLines.Count -gt 0) { + throw "The following $($badLines.Count) lines contain TABs within leading whitespace: `r`n`r`n$($badLines -join "`r`n")" + } + } + +} diff --git a/test/Scoop-00Linting.Tests.ps1 b/test/Scoop-00Linting.Tests.ps1 new file mode 100644 index 0000000000..45fbf3611c --- /dev/null +++ b/test/Scoop-00Linting.Tests.ps1 @@ -0,0 +1,33 @@ +Describe 'PSScriptAnalyzer' -Tag 'Linter' { + BeforeDiscovery { + $scriptDir = @('.', 'bin', 'lib', 'libexec', 'test') + } + + BeforeAll { + $lintSettings = "$PSScriptRoot\..\PSScriptAnalyzerSettings.psd1" + } + + It 'PSScriptAnalyzerSettings.ps1 should exist' { + $lintSettings | Should -Exist + } + + Context 'Linting all *.psd1, *.psm1 and *.ps1 files' { + BeforeEach { + $analysis = Invoke-ScriptAnalyzer -Path "$PSScriptRoot\..\$_" -Settings $lintSettings + } + It 'Should pass: <_>' -TestCases $scriptDir { + $analysis | Should -HaveCount 0 + if ($analysis) { + foreach ($result in $analysis) { + switch -wildCard ($result.ScriptName) { + '*.psm1' { $type = 'Module' } + '*.ps1' { $type = 'Script' } + '*.psd1' { $type = 'Manifest' } + } + Write-Warning " [*] $($result.Severity): $($result.Message)" + Write-Warning " $($result.RuleName) in $type`: $directory\$($result.ScriptName):$($result.Line)" + } + } + } + } +} diff --git a/test/Scoop-Alias.Tests.ps1 b/test/Scoop-Alias.Tests.ps1 index 1f696f5a89..bd365718a8 100644 --- a/test/Scoop-Alias.Tests.ps1 +++ b/test/Scoop-Alias.Tests.ps1 @@ -1,56 +1,45 @@ -. "$PSScriptRoot\Scoop-TestLib.ps1" -. "$PSScriptRoot\..\lib\core.ps1" -. "$PSScriptRoot\..\lib\help.ps1" -. "$PSScriptRoot\..\libexec\scoop-alias.ps1" | Out-Null - -Describe 'add_alias' -Tag 'Scoop' { - Mock shimdir { "$env:TEMP\shims" } - Mock set_config { } - Mock get_config { @{} } - - $shimdir = shimdir - ensure $shimdir - - Context "alias doesn't exist" { - It 'creates a new alias' { - $alias_file = "$shimdir\scoop-rm.ps1" - $alias_file | Should -Not -Exist - - add_alias 'rm' '"hello, world!"' - & $alias_file | Should -Be 'hello, world!' - } +BeforeAll { + . "$PSScriptRoot\Scoop-TestLib.ps1" + . "$PSScriptRoot\..\lib\core.ps1" + . "$PSScriptRoot\..\lib\help.ps1" + . "$PSScriptRoot\..\libexec\scoop-alias.ps1" | Out-Null +} + +Describe 'Manipulate Alias' -Tag 'Scoop' { + BeforeAll { + Mock shimdir { "$TestDrive\shims" } + Mock set_config { } + Mock get_config { @{} } + + $shimdir = shimdir + ensure $shimdir } - Context 'alias exists' { - It 'does not change existing alias' { - $alias_file = "$shimdir\scoop-rm.ps1" - New-Item $alias_file -Type File -Force - $alias_file | Should -Exist + It 'Creates a new alias if alias doesn''t exist' { + $alias_file = "$shimdir\scoop-rm.ps1" + $alias_file | Should -Not -Exist - add_alias 'rm' 'test' - $alias_file | Should -FileContentMatch '' - } + add_alias 'rm' '"hello, world!"' + & $alias_file | Should -Be 'hello, world!' } -} -Describe 'rm_alias' -Tag 'Scoop' { - Mock shimdir { "$env:TEMP\shims" } - Mock set_config { } - Mock get_config { @{} } + It 'Does not change existing alias if alias exists' { + $alias_file = "$shimdir\scoop-rm.ps1" + New-Item $alias_file -Type File -Force + $alias_file | Should -Exist - $shimdir = shimdir - ensure $shimdir + add_alias 'rm' 'test' + & $alias_file | Should -Not -Be 'test' + } - Context 'alias exists' { - It 'removes an existing alias' { - $alias_file = "$shimdir\scoop-rm.ps1" - add_alias 'rm' '"hello, world!"' + It 'Removes an existing alias' { + $alias_file = "$shimdir\scoop-rm.ps1" + add_alias 'rm' '"hello, world!"' - $alias_file | Should -Exist - Mock get_config { @(@{'rm' = 'scoop-rm' }) } + $alias_file | Should -Exist + Mock get_config { @(@{'rm' = 'scoop-rm' }) } - rm_alias 'rm' - $alias_file | Should -Not -Exist - } + rm_alias 'rm' + $alias_file | Should -Not -Exist } } diff --git a/test/Scoop-Config.Tests.ps1 b/test/Scoop-Config.Tests.ps1 index 604a25b2f0..9826af82a2 100644 --- a/test/Scoop-Config.Tests.ps1 +++ b/test/Scoop-Config.Tests.ps1 @@ -1,17 +1,16 @@ -. "$PSScriptRoot\Scoop-TestLib.ps1" -. "$PSScriptRoot\..\lib\core.ps1" +BeforeAll { + . "$PSScriptRoot\Scoop-TestLib.ps1" + . "$PSScriptRoot\..\lib\core.ps1" +} Describe 'config' -Tag 'Scoop' { BeforeAll { - $configFile = "$env:TEMP\ScoopTestFixtures\config.json" - if (Test-Path $configFile) { - Remove-Item -Path $configFile -Force - } + $configFile = [IO.Path]::GetTempFileName() $unicode = [Regex]::Unescape('\u4f60\u597d\u3053\u3093\u306b\u3061\u306f') # 你好こんにちは } - BeforeEach { - $scoopConfig = $null + AfterAll { + Remove-Item -Path $configFile -Force } It 'load_cfg should return null if config file does not exist' { diff --git a/test/Scoop-Core.Tests.ps1 b/test/Scoop-Core.Tests.ps1 index 398cbfaad2..4c95067e23 100644 --- a/test/Scoop-Core.Tests.ps1 +++ b/test/Scoop-Core.Tests.ps1 @@ -1,10 +1,8 @@ -. "$PSScriptRoot\Scoop-TestLib.ps1" -. "$PSScriptRoot\..\lib\core.ps1" -. "$PSScriptRoot\..\lib\install.ps1" -. "$PSScriptRoot\..\lib\unix.ps1" - -$repo_dir = (Get-Item $MyInvocation.MyCommand.Path).directory.parent.FullName -$isUnix = is_unix +BeforeAll { + . "$PSScriptRoot\Scoop-TestLib.ps1" + . "$PSScriptRoot\..\lib\core.ps1" + . "$PSScriptRoot\..\lib\install.ps1" +} Describe 'Get-AppFilePath' -Tag 'Scoop' { BeforeAll { @@ -128,15 +126,14 @@ Describe 'is_directory' -Tag 'Scoop' { } } -Describe 'movedir' -Tag 'Scoop' { - $extract_dir = 'subdir' - $extract_to = $null - +Describe 'movedir' -Tag 'Scoop', 'Windows' { BeforeAll { $working_dir = setup_working 'movedir' + $extract_dir = 'subdir' + $extract_to = $null } - It 'moves directories with no spaces in path' -Skip:$isUnix { + It 'moves directories with no spaces in path' { $dir = "$working_dir\user" movedir "$dir\_tmp\$extract_dir" "$dir\$extract_to" @@ -144,7 +141,7 @@ Describe 'movedir' -Tag 'Scoop' { "$dir\_tmp\$extract_dir" | Should -Not -Exist } - It 'moves directories with spaces in path' -Skip:$isUnix { + It 'moves directories with spaces in path' { $dir = "$working_dir\user with space" movedir "$dir\_tmp\$extract_dir" "$dir\$extract_to" @@ -157,7 +154,7 @@ Describe 'movedir' -Tag 'Scoop' { "$dir\_tmp" | Should -Not -Exist } - It 'moves directories with quotes in path' -Skip:$isUnix { + It 'moves directories with quotes in path' { $dir = "$working_dir\user with 'quote" movedir "$dir\_tmp\$extract_dir" "$dir\$extract_to" @@ -166,14 +163,14 @@ Describe 'movedir' -Tag 'Scoop' { } } -Describe 'shim' -Tag 'Scoop' { +Describe 'shim' -Tag 'Scoop', 'Windows' { BeforeAll { $working_dir = setup_working 'shim' $shimdir = shimdir $(ensure_in_path $shimdir) | Out-Null } - It "links a file onto the user's path" -Skip:$isUnix { + It "links a file onto the user's path" { { Get-Command 'shim-test' -ea stop } | Should -Throw { Get-Command 'shim-test.ps1' -ea stop } | Should -Throw { Get-Command 'shim-test.cmd' -ea stop } | Should -Throw @@ -186,15 +183,13 @@ Describe 'shim' -Tag 'Scoop' { shim-test | Should -Be 'Hello, world!' } - Context 'user with quote' { - It 'shims a file with quote in path' -Skip:$isUnix { - { Get-Command 'shim-test' -ea stop } | Should -Throw - { shim-test } | Should -Throw + It 'shims a file with quote in path' { + { Get-Command 'shim-test' -ea stop } | Should -Throw + { shim-test } | Should -Throw - shim "$working_dir\user with 'quote\shim-test.ps1" $false 'shim-test' - { Get-Command 'shim-test' -ea stop } | Should -Not -Throw - shim-test | Should -Be 'Hello, world!' - } + shim "$working_dir\user with 'quote\shim-test.ps1" $false 'shim-test' + { Get-Command 'shim-test' -ea stop } | Should -Not -Throw + shim-test | Should -Be 'Hello, world!' } AfterEach { @@ -202,14 +197,14 @@ Describe 'shim' -Tag 'Scoop' { } } -Describe 'rm_shim' -Tag 'Scoop' { +Describe 'rm_shim' -Tag 'Scoop', 'Windows' { BeforeAll { $working_dir = setup_working 'shim' $shimdir = shimdir $(ensure_in_path $shimdir) | Out-Null } - It 'removes shim from path' -Skip:$isUnix { + It 'removes shim from path' { shim "$working_dir\shim-test.ps1" $false 'shim-test' rm_shim 'shim-test' $shimdir @@ -221,7 +216,7 @@ Describe 'rm_shim' -Tag 'Scoop' { } } -Describe 'get_app_name_from_shim' -Tag 'Scoop' { +Describe 'get_app_name_from_shim' -Tag 'Scoop', 'Windows' { BeforeAll { $working_dir = setup_working 'shim' $shimdir = shimdir @@ -229,11 +224,11 @@ Describe 'get_app_name_from_shim' -Tag 'Scoop' { Mock appsdir { $working_dir } } - It 'returns empty string if file does not exist' -Skip:$isUnix { + It 'returns empty string if file does not exist' { get_app_name_from_shim 'non-existent-file' | Should -Be '' } - It 'returns app name if file exists and is a shim to an app' -Skip:$isUnix { + It 'returns app name if file exists and is a shim to an app' { ensure "$working_dir/mockapp/current/" Write-Output '' | Out-File "$working_dir/mockapp/current/mockapp1.ps1" shim "$working_dir/mockapp/current/mockapp1.ps1" $false 'shim-test1' @@ -246,7 +241,7 @@ Describe 'get_app_name_from_shim' -Tag 'Scoop' { get_app_name_from_shim "$shim_path2" | Should -Be 'mockapp' } - It 'returns empty string if file exists and is not a shim' -Skip:$isUnix { + It 'returns empty string if file exists and is not a shim' { Write-Output 'lorem ipsum' | Out-File -Encoding ascii "$working_dir/mock-shim.ps1" get_app_name_from_shim "$working_dir/mock-shim.ps1" | Should -Be '' } @@ -263,35 +258,33 @@ Describe 'get_app_name_from_shim' -Tag 'Scoop' { } } -Describe 'ensure_robocopy_in_path' -Tag 'Scoop' { - $shimdir = shimdir $false - Mock versiondir { $repo_dir } +Describe 'ensure_robocopy_in_path' -Tag 'Scoop', 'Windows' { + BeforeAll { + $shimdir = shimdir $false + Mock versiondir { "$PSScriptRoot\.." } + } - Context 'robocopy is not in path' { - It 'shims robocopy when not on path' -Skip:$isUnix { - Mock Test-CommandAvailable { $false } - Test-CommandAvailable robocopy | Should -Be $false + It 'shims robocopy when not on path' { + Mock Test-CommandAvailable { $false } + Test-CommandAvailable robocopy | Should -Be $false - ensure_robocopy_in_path + ensure_robocopy_in_path - # "$shimdir/robocopy.ps1" | should -exist - "$shimdir/robocopy.exe" | Should -Exist + # "$shimdir/robocopy.ps1" | should -exist + "$shimdir/robocopy.exe" | Should -Exist - # clean up - rm_shim robocopy $(shimdir $false) | Out-Null - } + # clean up + rm_shim robocopy $(shimdir $false) | Out-Null } - Context 'robocopy is in path' { - It 'does not shim robocopy when it is in path' -Skip:$isUnix { - Mock Test-CommandAvailable { $true } - Test-CommandAvailable robocopy | Should -Be $true + It 'does not shim robocopy when it is in path' { + Mock Test-CommandAvailable { $true } + Test-CommandAvailable robocopy | Should -Be $true - ensure_robocopy_in_path + ensure_robocopy_in_path - # "$shimdir/robocopy.ps1" | should -not -exist - "$shimdir/robocopy.exe" | Should -Not -Exist - } + # "$shimdir/robocopy.ps1" | should -not -exist + "$shimdir/robocopy.exe" | Should -Not -Exist } } diff --git a/test/Scoop-Decompress.Tests.ps1 b/test/Scoop-Decompress.Tests.ps1 index b568ef8e56..b4cc1ef2d1 100644 --- a/test/Scoop-Decompress.Tests.ps1 +++ b/test/Scoop-Decompress.Tests.ps1 @@ -1,14 +1,13 @@ -. "$PSScriptRoot\Scoop-TestLib.ps1" -. "$PSScriptRoot\..\lib\core.ps1" -. "$PSScriptRoot\..\lib\decompress.ps1" -. "$PSScriptRoot\..\lib\install.ps1" -. "$PSScriptRoot\..\lib\manifest.ps1" -. "$PSScriptRoot\..\lib\versions.ps1" -. "$PSScriptRoot\..\lib\unix.ps1" - -$isUnix = is_unix +BeforeAll { + . "$PSScriptRoot\Scoop-TestLib.ps1" + . "$PSScriptRoot\..\lib\core.ps1" + . "$PSScriptRoot\..\lib\decompress.ps1" + . "$PSScriptRoot\..\lib\install.ps1" + . "$PSScriptRoot\..\lib\manifest.ps1" + . "$PSScriptRoot\..\lib\versions.ps1" +} -Describe 'Decompression function' -Tag 'Scoop', 'Decompress' { +Describe 'Decompression function' -Tag 'Scoop', 'Windows', 'Decompress' { BeforeAll { $working_dir = setup_working 'decompress' @@ -19,13 +18,17 @@ Describe 'Decompression function' -Tag 'Scoop', 'Decompress' { return $to } - It 'Decompression test cases should exist' { + } + Context 'Decompression test cases should exist' { + BeforeAll { $testcases = "$working_dir\TestCases.zip" + } + It 'Test cases should exist and hash should match' { $testcases | Should -Exist - compute_hash $testcases 'sha256' | Should -Be '791bfce192917a2ff225dcdd87d23ae5f720b20178d85e68e4b1b56139cf8e6a' - if (!$isUnix) { - Microsoft.PowerShell.Archive\Expand-Archive $testcases $working_dir - } + (Get-FileHash -Path $testcases -Algorithm SHA256).Hash.ToLower() | Should -Be '791bfce192917a2ff225dcdd87d23ae5f720b20178d85e68e4b1b56139cf8e6a' + } + It 'Test cases should be extracted correctly' { + { Microsoft.PowerShell.Archive\Expand-Archive -Path $testcases -DestinationPath $working_dir } | Should -Not -Throw } } @@ -49,14 +52,14 @@ Describe 'Decompression function' -Tag 'Scoop', 'Decompress' { $test6_3 = "$working_dir\7ZipTest6.part03.rar" } - It 'extract normal compressed file' -Skip:$isUnix { + It 'extract normal compressed file' { $to = test_extract 'Expand-7zipArchive' $test1 $to | Should -Exist "$to\empty" | Should -Exist (Get-ChildItem $to).Count | Should -Be 1 } - It 'extract nested compressed file' -Skip:$isUnix { + It 'extract nested compressed file' { # file ext: tgz $to = test_extract 'Expand-7zipArchive' $test2 $to | Should -Exist @@ -70,28 +73,28 @@ Describe 'Decompression function' -Tag 'Scoop', 'Decompress' { (Get-ChildItem $to).Count | Should -Be 1 } - It 'extract nested compressed file with different inner name' -Skip:$isUnix { + It 'extract nested compressed file with different inner name' { $to = test_extract 'Expand-7zipArchive' $test4 $to | Should -Exist "$to\empty" | Should -Exist (Get-ChildItem $to).Count | Should -Be 1 } - It 'extract splited 7z archives (.001, .002, ...)' -Skip:$isUnix { + It 'extract splited 7z archives (.001, .002, ...)' { $to = test_extract 'Expand-7zipArchive' $test5_1 $to | Should -Exist "$to\empty" | Should -Exist (Get-ChildItem $to).Count | Should -Be 1 } - It 'extract splited RAR archives (.part01.rar, .part02.rar, ...)' -Skip:$isUnix { + It 'extract splited RAR archives (.part01.rar, .part02.rar, ...)' { $to = test_extract 'Expand-7zipArchive' $test6_1 $to | Should -Exist "$to\dummy" | Should -Exist (Get-ChildItem $to).Count | Should -Be 1 } - It 'works with "-Removal" switch ($removal param)' -Skip:$isUnix { + It 'works with "-Removal" switch ($removal param)' { $test1 | Should -Exist test_extract 'Expand-7zipArchive' $test1 $true $test1 | Should -Not -Exist @@ -126,21 +129,21 @@ Describe 'Decompression function' -Tag 'Scoop', 'Decompress' { $test2 = "$working_dir\ZstdTest.tar.zst" } - It 'extract normal compressed file' -Skip:$isUnix { + It 'extract normal compressed file' { $to = test_extract 'Expand-ZstdArchive' $test1 $to | Should -Exist "$to\ZstdTest" | Should -Exist (Get-ChildItem $to).Count | Should -Be 1 } - It 'extract nested compressed file' -Skip:$isUnix { + It 'extract nested compressed file' { $to = test_extract 'Expand-ZstdArchive' $test2 $to | Should -Exist "$to\ZstdTest" | Should -Exist (Get-ChildItem $to).Count | Should -Be 1 } - It 'works with "-Removal" switch ($removal param)' -Skip:$isUnix { + It 'works with "-Removal" switch ($removal param)' { $test1 | Should -Exist test_extract 'Expand-ZstdArchive' $test1 $true $test1 | Should -Not -Exist @@ -159,7 +162,7 @@ Describe 'Decompression function' -Tag 'Scoop', 'Decompress' { $test2 = "$working_dir\MSITestNull.msi" } - It 'extract normal MSI file' -Skip:$isUnix { + It 'extract normal MSI file' { Mock get_config { $false } $to = test_extract 'Expand-MsiArchive' $test1 $to | Should -Exist @@ -167,13 +170,13 @@ Describe 'Decompression function' -Tag 'Scoop', 'Decompress' { (Get-ChildItem "$to\MSITest").Count | Should -Be 1 } - It 'extract empty MSI file using lessmsi' -Skip:$isUnix { + It 'extract empty MSI file using lessmsi' { Mock get_config { $true } $to = test_extract 'Expand-MsiArchive' $test2 $to | Should -Exist } - It 'works with "-Removal" switch ($removal param)' -Skip:$isUnix { + It 'works with "-Removal" switch ($removal param)' { Mock get_config { $false } $test1 | Should -Exist test_extract 'Expand-MsiArchive' $test1 $true @@ -192,14 +195,14 @@ Describe 'Decompression function' -Tag 'Scoop', 'Decompress' { $test = "$working_dir\InnoTest.exe" } - It 'extract Inno Setup file' -Skip:$isUnix { + It 'extract Inno Setup file' { $to = test_extract 'Expand-InnoArchive' $test $to | Should -Exist "$to\empty" | Should -Exist (Get-ChildItem $to).Count | Should -Be 1 } - It 'works with "-Removal" switch ($removal param)' -Skip:$isUnix { + It 'works with "-Removal" switch ($removal param)' { $test | Should -Exist test_extract 'Expand-InnoArchive' $test $true $test | Should -Not -Exist @@ -212,14 +215,14 @@ Describe 'Decompression function' -Tag 'Scoop', 'Decompress' { $test = "$working_dir\ZipTest.zip" } - It 'extract compressed file' -Skip:$isUnix { + It 'extract compressed file' { $to = test_extract 'Expand-ZipArchive' $test $to | Should -Exist "$to\empty" | Should -Exist (Get-ChildItem $to).Count | Should -Be 1 } - It 'works with "-Removal" switch ($removal param)' -Skip:$isUnix { + It 'works with "-Removal" switch ($removal param)' { $test | Should -Exist test_extract 'Expand-ZipArchive' $test $true $test | Should -Not -Exist diff --git a/test/Scoop-Depends.Tests.ps1 b/test/Scoop-Depends.Tests.ps1 index b465d28220..d80d7d2652 100644 --- a/test/Scoop-Depends.Tests.ps1 +++ b/test/Scoop-Depends.Tests.ps1 @@ -1,9 +1,11 @@ -. "$PSScriptRoot\Scoop-TestLib.ps1" -. "$PSScriptRoot\..\lib\core.ps1" -. "$PSScriptRoot\..\lib\depends.ps1" -. "$PSScriptRoot\..\lib\buckets.ps1" -. "$PSScriptRoot\..\lib\install.ps1" -. "$PSScriptRoot\..\lib\manifest.ps1" +BeforeAll { + . "$PSScriptRoot\Scoop-TestLib.ps1" + . "$PSScriptRoot\..\lib\core.ps1" + . "$PSScriptRoot\..\lib\depends.ps1" + . "$PSScriptRoot\..\lib\buckets.ps1" + . "$PSScriptRoot\..\lib\install.ps1" + . "$PSScriptRoot\..\lib\manifest.ps1" +} Describe 'Package Dependencies' -Tag 'Scoop' { Context 'Requirement function' { diff --git a/test/Scoop-GetOpts.Tests.ps1 b/test/Scoop-GetOpts.Tests.ps1 index 2fcbf56fe6..ef4d56a950 100644 --- a/test/Scoop-GetOpts.Tests.ps1 +++ b/test/Scoop-GetOpts.Tests.ps1 @@ -1,5 +1,7 @@ -. "$PSScriptRoot\Scoop-TestLib.ps1" -. "$PSScriptRoot\..\lib\getopt.ps1" +BeforeAll { + . "$PSScriptRoot\Scoop-TestLib.ps1" + . "$PSScriptRoot\..\lib\getopt.ps1" +} Describe 'getopt' -Tag 'Scoop' { It 'handle short option with required argument missing' { diff --git a/test/Scoop-Install.Tests.ps1 b/test/Scoop-Install.Tests.ps1 index 3559ac89e8..bafdfbe847 100644 --- a/test/Scoop-Install.Tests.ps1 +++ b/test/Scoop-Install.Tests.ps1 @@ -1,10 +1,9 @@ -. "$PSScriptRoot\Scoop-TestLib.ps1" -. "$PSScriptRoot\..\lib\core.ps1" -. "$PSScriptRoot\..\lib\manifest.ps1" -. "$PSScriptRoot\..\lib\install.ps1" -. "$PSScriptRoot\..\lib\unix.ps1" - -$isUnix = is_unix +BeforeAll { + . "$PSScriptRoot\Scoop-TestLib.ps1" + . "$PSScriptRoot\..\lib\core.ps1" + . "$PSScriptRoot\..\lib\manifest.ps1" + . "$PSScriptRoot\..\lib\install.ps1" +} Describe 'appname_from_url' -Tag 'Scoop' { It 'should extract the correct name' { @@ -34,8 +33,8 @@ Describe 'url_remote_filename' -Tag 'Scoop' { } } -Describe 'is_in_dir' -Tag 'Scoop' { - It 'should work correctly' -Skip:$isUnix { +Describe 'is_in_dir' -Tag 'Scoop', 'Windows' { + It 'should work correctly' { is_in_dir 'C:\test' 'C:\foo' | Should -BeFalse is_in_dir 'C:\test' 'C:\test\foo\baz.zip' | Should -BeTrue @@ -44,18 +43,20 @@ Describe 'is_in_dir' -Tag 'Scoop' { } } -Describe 'env add and remove path' -Tag 'Scoop' { - # test data - $manifest = @{ - 'env_add_path' = @('foo', 'bar') - } - $testdir = Join-Path $PSScriptRoot 'path-test-directory' - $global = $false +Describe 'env add and remove path' -Tag 'Scoop', 'Windows' { + BeforeAll { + # test data + $manifest = @{ + 'env_add_path' = @('foo', 'bar') + } + $testdir = Join-Path $PSScriptRoot 'path-test-directory' + $global = $false - # store the original path to prevent leakage of tests - $origPath = $env:PATH + # store the original path to prevent leakage of tests + $origPath = $env:PATH + } - It 'should concat the correct path' -Skip:$isUnix { + It 'should concat the correct path' { Mock add_first_in_path {} Mock remove_from_path {} @@ -121,37 +122,3 @@ Describe 'persist_def' -Tag 'Scoop' { $target | Should -Be 'foo' } } - -Describe 'compute_hash' -Tag 'Scoop' { - BeforeAll { - $working_dir = setup_working 'manifest' - } - - It 'computes MD5 correctly' { - compute_hash (Join-Path "$working_dir" 'invalid_wget.json') 'md5' | Should -Be 'cf229eecc201063e32b436e73b71deba' - compute_hash (Join-Path "$working_dir" 'wget.json') 'md5' | Should -Be '57c397fd5092cbd6a8b4df56be2551ab' - compute_hash (Join-Path "$working_dir" 'broken_schema.json') 'md5' | Should -Be '0427c7f4edc33d6d336db98fc160beb0' - compute_hash (Join-Path "$working_dir" 'broken_wget.json') 'md5' | Should -Be '30a7d4d3f64cb7a800d96c0f2ccec87f' - } - - It 'computes SHA-1 correctly' { - compute_hash (Join-Path "$working_dir" 'invalid_wget.json') 'sha1' | Should -Be '33ae44df8feed86cdc8f544234029fb28280c3c5' - compute_hash (Join-Path "$working_dir" 'wget.json') 'sha1' | Should -Be '98bfacb887da8cd05d3a1162f89d90173294be55' - compute_hash (Join-Path "$working_dir" 'broken_schema.json') 'sha1' | Should -Be '6dcd64f8ce7a3ae6bbc3dc2288b7cb202dbfa3c8' - compute_hash (Join-Path "$working_dir" 'broken_wget.json') 'sha1' | Should -Be '60b5b1d5bcb4193d19aeab265eab0bb9b0c46c8f' - } - - It 'computes SHA-256 correctly' { - compute_hash (Join-Path "$working_dir" 'invalid_wget.json') 'sha256' | Should -Be '1a92ef57c5f3cecba74015ae8e92fc3f2dbe141f9d171c3a06f98645a522d58c' - compute_hash (Join-Path "$working_dir" 'wget.json') 'sha256' | Should -Be '31d6d0953d4e95f0a42080acd61a8c2f92bc90cae324c0d6d2301a974c15f62f' - compute_hash (Join-Path "$working_dir" 'broken_schema.json') 'sha256' | Should -Be 'f3e5082e366006c317d9426e590623254cb1ce23d4f70165afed340b03ce333b' - compute_hash (Join-Path "$working_dir" 'broken_wget.json') 'sha256' | Should -Be 'da658987c3902658c6e754bfa6546dfd084aaa2c3ae25f1fd8aa4645bc9cae24' - } - - It 'computes SHA-512 correctly' { - compute_hash (Join-Path "$working_dir" 'invalid_wget.json') 'sha512' | Should -Be '7a7b82ec17547f5ec13dc614a8cec919e897e6c344a6ce7d71205d6f1c3aed276c7b15cbc69acac8207f72417993299cef36884e1915d56758ea09efa2259870' - compute_hash (Join-Path "$working_dir" 'wget.json') 'sha512' | Should -Be '216ebf07bb77062b51420f0f5eb6b7a94d9623d1d41d36c833436058f41e39898f2aa48d7020711c0d8765d02b87ac2e6810f3f502636a6e6f47dc4b9aa02d17' - compute_hash (Join-Path "$working_dir" 'broken_schema.json') 'sha512' | Should -Be '8d3f5617517e61c33275eafea4b166f0a245ec229c40dea436173c354786bad72e4fd9d662f6ac2b9f3dd375c00815a07f10e12975eec1b12da7ba7db10f9c14' - compute_hash (Join-Path "$working_dir" 'broken_wget.json') 'sha512' | Should -Be '7b16a714491e91cc6daa5f90e700547fac4d62e1fcec8c4b78f5a2386e04e68a8ed68f27503ece9555904a047df8050b3f12b4f779c05b1e4d0156e6e2d8fdbb' - } -} diff --git a/test/Scoop-Linting.Tests.ps1 b/test/Scoop-Linting.Tests.ps1 deleted file mode 100644 index 02cc2d93c2..0000000000 --- a/test/Scoop-Linting.Tests.ps1 +++ /dev/null @@ -1,41 +0,0 @@ -$repo_dir = (Get-Item $MyInvocation.MyCommand.Path).directory.parent.FullName - -Describe 'PSScriptAnalyzer' -Tag 'Linter' { - $scoop_modules = Get-ChildItem $repo_dir -Recurse -Include *.psd1, *.psm1, *.ps1 - $scoop_modules = $scoop_modules | Where-Object { $_.DirectoryName -notlike '*\supporting*' -and $_.DirectoryName -notlike '*\test*' } - $scoop_modules = $scoop_modules | Select-Object -ExpandProperty Directory -Unique - - Context 'Checking ScriptAnalyzer' { - It 'Invoke-ScriptAnalyzer Cmdlet should exist' { - { Get-Command Invoke-ScriptAnalyzer -ErrorAction Stop } | Should -Not -Throw - } - It 'PSScriptAnalyzerSettings.ps1 should exist' { - Test-Path "$repo_dir\PSScriptAnalyzerSettings.psd1" | Should -BeTrue - } - It 'There should be files to test' { - $scoop_modules.Count | Should -Not -Be 0 - } - } - - $linting_settings = Get-Item -Path "$repo_dir\PSScriptAnalyzerSettings.psd1" - - Context 'Linting all *.psd1, *.psm1 and *.ps1 files' { - foreach ($directory in $scoop_modules) { - $analysis = Invoke-ScriptAnalyzer -Path $directory.FullName -Settings $linting_settings.FullName - It "Should pass: $directory" { - $analysis.Count | Should -Be 0 - } - if ($analysis) { - foreach ($result in $analysis) { - switch -wildCard ($result.ScriptName) { - '*.psm1' { $type = 'Module' } - '*.ps1' { $type = 'Script' } - '*.psd1' { $type = 'Manifest' } - } - Write-Host -f Yellow " [*] $($result.Severity): $($result.Message)" - Write-Host -f Yellow " $($result.RuleName) in $type`: $directory\$($result.ScriptName):$($result.Line)" - } - } - } - } -} diff --git a/test/Scoop-Manifest.Tests.ps1 b/test/Scoop-Manifest.Tests.ps1 index 02a24162ab..232a946bbd 100644 --- a/test/Scoop-Manifest.Tests.ps1 +++ b/test/Scoop-Manifest.Tests.ps1 @@ -1,22 +1,28 @@ -. "$PSScriptRoot\Scoop-TestLib.ps1" -. "$PSScriptRoot\..\lib\json.ps1" -. "$PSScriptRoot\..\lib\manifest.ps1" +BeforeAll { + . "$PSScriptRoot\..\lib\json.ps1" + . "$PSScriptRoot\..\lib\manifest.ps1" +} -Describe 'Pretty json formating' -Tag 'Scoop' { - BeforeAll { - $format = "$PSScriptRoot\fixtures\format" - $manifests = Get-ChildItem "$format\formatted" -File -Filter '*.json' +Describe 'JSON parse and beautify' -Tag 'Scoop' { + Context 'Parse JSON' { + It 'success with valid json' { + { parse_json "$PSScriptRoot\fixtures\manifest\wget.json" } | Should -Not -Throw + } + It 'fails with invalid json' { + { parse_json "$PSScriptRoot\fixtures\manifest\broken_wget.json" } | Should -Throw + } } - - Context 'Beautify manifest' { - $manifests | ForEach-Object { - if ($PSVersionTable.PSVersion.Major -gt 5) { $_ = $_.Name } # Fix for pwsh - - It "$_" { - $pretty_json = (parse_json "$format\unformatted\$_") | ConvertToPrettyJson - $correct = (Get-Content "$format\formatted\$_") -join "`r`n" - $correct.CompareTo($pretty_json) | Should -Be 0 - } + Context 'Beautify JSON' { + BeforeDiscovery { + $manifests = (Get-ChildItem "$PSScriptRoot\fixtures\format\formatted" -File -Filter '*.json').Name + } + BeforeAll { + $format = "$PSScriptRoot\fixtures\format" + } + It '<_>' -ForEach $manifests { + $pretty_json = (parse_json "$format\unformatted\$_") | ConvertToPrettyJson + $correct = (Get-Content "$format\formatted\$_") -join "`r`n" + $correct.CompareTo($pretty_json) | Should -Be 0 } } } @@ -47,3 +53,34 @@ Describe 'Handle ARM64 and correctly fallback' -Tag 'Scoop' { Get-SupportedArchitecture $manifest3 'arm64' | Should -BeNullOrEmpty } } + +Describe 'Manifest Validator' -Tag 'Validator' { + # Could not use backslash '\' in Linux/macOS for .NET object 'Scoop.Validator' + BeforeAll { + Add-Type -Path "$PSScriptRoot\..\supporting\validator\bin\Scoop.Validator.dll" + $schema = "$PSScriptRoot/../schema.json" + } + + It 'Scoop.Validator is available' { + ([System.Management.Automation.PSTypeName]'Scoop.Validator').Type | Should -Be 'Scoop.Validator' + } + It 'fails with broken schema' { + $validator = New-Object Scoop.Validator("$PSScriptRoot/fixtures/manifest/broken_schema.json", $true) + $validator.Validate("$PSScriptRoot/fixtures/manifest/wget.json") | Should -BeFalse + $validator.Errors.Count | Should -Be 1 + $validator.Errors | Select-Object -First 1 | Should -Match 'broken_schema.*(line 6).*(position 4)' + } + It 'fails with broken manifest' { + $validator = New-Object Scoop.Validator($schema, $true) + $validator.Validate("$PSScriptRoot/fixtures/manifest/broken_wget.json") | Should -BeFalse + $validator.Errors.Count | Should -Be 1 + $validator.Errors | Select-Object -First 1 | Should -Match 'broken_wget.*(line 5).*(position 4)' + } + It 'fails with invalid manifest' { + $validator = New-Object Scoop.Validator($schema, $true) + $validator.Validate("$PSScriptRoot/fixtures/manifest/invalid_wget.json") | Should -BeFalse + $validator.Errors.Count | Should -Be 16 + $validator.Errors | Select-Object -First 1 | Should -Match "Property 'randomproperty' has not been defined and the schema does not allow additional properties\." + $validator.Errors | Select-Object -Last 1 | Should -Match 'Required properties are missing from object: version\.' + } +} diff --git a/test/Scoop-TestLib.ps1 b/test/Scoop-TestLib.ps1 index 1e58122338..2194536736 100644 --- a/test/Scoop-TestLib.ps1 +++ b/test/Scoop-TestLib.ps1 @@ -1,69 +1,3 @@ -if (!$script:run) { $script:run = 0 } -if (!$script:failed) { $script:failed = 0 } - -function filter_tests($arg) { - if (!$arg) { return } - $script:filter = $arg -join ' ' - Write-Host "filtering by '$filter'" -} -function test($desc, $assertions) { - if ($filter -and $desc -notlike "*$filter*") { return } - $script:test = $desc - $script:run++ - try { - $assertions.invoke() - } catch { - script:fail $_.exception.innerexception.message - } - $script:test = $null -} - -function assert($x, $eq = '__undefined', $ne = '__undefined') { - if ($args.length -gt 0) { - fail "unexpected arguments: $args" - } - - if ($eq -ne '__undefined') { - if ($x -ne $eq) { fail "$(fmt $x) != $(fmt $eq)" } - } elseif ($ne -ne '__undefined') { - if ($x -eq $ne) { fail "$(fmt $x) == $(fmt $ne)" } - } else { - if (!$x) { fail "$x" } - } -} - -function test_results { - $col = 'darkgreen' - $res = 'all passed' - if ($script:failed -gt 0) { - $col = 'darkred' - $res = "$script:failed failed" - } - - Write-Host "ran $script:run tests, " -NoNewline - Write-Host $res -f $col -} - -function script:fail($msg) { - $script:failed++ - $invoked = (Get-Variable -Scope 1 myinvocation).value - - $script = Split-Path $invoked.scriptname -Leaf - $line = $invoked.scriptlinenumber - - if ($script:test) { $msg = "$script:test`r`n -> $msg" } - - Write-Host "FAIL: $msg" -f darkred - Write-Host "$script line $line`:" - Write-Host (($invoked.positionmessage -split "`r`n")[1..2] -join "`r`n") -} - -function script:fmt($var) { - if ($null -eq $var) { return "`$null" } - if ($var -is [string]) { return "'$var'" } - return $var -} - # copies fixtures to a working directory function setup_working($name) { $fixtures = "$PSScriptRoot/fixtures/$name" @@ -73,11 +7,7 @@ function setup_working($name) { } # reset working dir - if ($PSVersionTable.Platform -eq 'Unix') { - $working_dir = "/tmp/ScoopTestFixtures/$name" - } else { - $working_dir = "$env:TEMP/ScoopTestFixtures/$name" - } + $working_dir = "$([IO.Path]::GetTempPath())ScoopTestFixtures/$name" if (Test-Path $working_dir) { Remove-Item -Recurse -Force $working_dir diff --git a/test/Scoop-Versions.Tests.ps1 b/test/Scoop-Versions.Tests.ps1 index 17568ddb60..536f9cc7c3 100644 --- a/test/Scoop-Versions.Tests.ps1 +++ b/test/Scoop-Versions.Tests.ps1 @@ -1,5 +1,7 @@ -. "$PSScriptRoot\Scoop-TestLib.ps1" -. "$PSScriptRoot\..\lib\versions.ps1" +BeforeAll { + . "$PSScriptRoot\Scoop-TestLib.ps1" + . "$PSScriptRoot\..\lib\versions.ps1" +} Describe 'versions comparison' -Tag 'Scoop' { Context 'semver compliant versions' { @@ -90,10 +92,21 @@ Describe 'versions comparison' -Tag 'Scoop' { } It 'handles equal versions' { + function get_config { $null } Compare-Version '12.0' '12.0' | Should -Be 0 Compare-Version '7.0.4-9' '7.0.4-9' | Should -Be 0 Compare-Version 'nightly-20190801' 'nightly' | Should -Be 0 Compare-Version 'nightly-20190801' 'nightly-20200801' | Should -Be 0 } + + It 'handles nightly versions with `update_nightly`' { + function get_config { $true } + Mock Get-Date { '20200801' } + Compare-Version 'nightly-20200801' 'nightly' | Should -Be 0 + Compare-Version 'nightly-20200730' 'nightly' | Should -Be 1 + Compare-Version 'nightly-20200730' 'nightly-20200801' | Should -Be 1 + Compare-Version 'nightly-20200802' 'nightly' | Should -Be -1 + Compare-Version 'nightly-20200802' 'nightly-20200801' | Should -Be -1 + } } } diff --git a/test/bin/init.ps1 b/test/bin/init.ps1 index a37125182f..14e32d2a6b 100644 --- a/test/bin/init.ps1 +++ b/test/bin/init.ps1 @@ -1,22 +1,21 @@ #Requires -Version 5.1 -Write-Host "PowerShell: $($PSVersionTable.PSVersion)" -Write-Host (7z.exe | Select-String -Pattern '7-Zip').ToString() -Write-Host 'Check and install testsuite dependencies ...' -if (Get-InstalledModule -Name Pester -MinimumVersion 4.0 -MaximumVersion 4.99 -ErrorAction SilentlyContinue) { - Write-Host 'Pester 4 is already installed.' +Write-Output "PowerShell: $($PSVersionTable.PSVersion)" +Write-Output 'Check and install testsuite dependencies ...' +if (Get-InstalledModule -Name Pester -MinimumVersion 5.2 -MaximumVersion 5.99 -ErrorAction SilentlyContinue) { + Write-Output 'Pester 5 is already installed.' } else { - Write-Host 'Installing Pester 4 ...' - Install-Module -Repository PSGallery -Scope CurrentUser -Force -Name Pester -MinimumVersion 4.0 -MaximumVersion 4.99 -SkipPublisherCheck + Write-Output 'Installing Pester 5 ...' + Install-Module -Repository PSGallery -Scope CurrentUser -Force -Name Pester -MinimumVersion 5.2 -MaximumVersion 5.99 -SkipPublisherCheck } if (Get-InstalledModule -Name PSScriptAnalyzer -MinimumVersion 1.17 -ErrorAction SilentlyContinue) { - Write-Host 'PSScriptAnalyzer is already installed.' + Write-Output 'PSScriptAnalyzer is already installed.' } else { - Write-Host 'Installing PSScriptAnalyzer ...' + Write-Output 'Installing PSScriptAnalyzer ...' Install-Module -Repository PSGallery -Scope CurrentUser -Force -Name PSScriptAnalyzer -SkipPublisherCheck } if (Get-InstalledModule -Name BuildHelpers -MinimumVersion 2.0 -ErrorAction SilentlyContinue) { - Write-Host 'BuildHelpers is already installed.' + Write-Output 'BuildHelpers is already installed.' } else { - Write-Host 'Installing BuildHelpers ...' + Write-Output 'Installing BuildHelpers ...' Install-Module -Repository PSGallery -Scope CurrentUser -Force -Name BuildHelpers -SkipPublisherCheck } diff --git a/test/bin/test.ps1 b/test/bin/test.ps1 index f70805aded..ffb35351a7 100644 --- a/test/bin/test.ps1 +++ b/test/bin/test.ps1 @@ -1,40 +1,41 @@ #Requires -Version 5.1 #Requires -Modules @{ ModuleName = 'BuildHelpers'; ModuleVersion = '2.0.1' } -#Requires -Modules @{ ModuleName = 'Pester'; MaximumVersion = '4.99' } +#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.2.0' } #Requires -Modules @{ ModuleName = 'PSScriptAnalyzer'; ModuleVersion = '1.17.1' } param( - [String] $TestPath = $(Convert-Path "$PSScriptRoot\..\") + [String] $TestPath = (Convert-Path "$PSScriptRoot\..") ) -$splat = @{ - Path = $TestPath - PassThru = $true +$pesterConfig = New-PesterConfiguration -Hashtable @{ + Run = @{ + Path = $TestPath + PassThru = $true + } + Output = @{ + Verbosity = 'Detailed' + } +} +$excludes = @() + +if ($IsLinux -or $IsMacOS) { + Write-Warning 'Skipping Windows-only tests on Linux/macOS' + $excludes += 'Windows' } if ($env:CI -eq $true) { Write-Host "Load 'BuildHelpers' environment variables ..." Set-BuildEnvironment -Force - $CI_WIN = (($env:RUNNER_OS -eq 'Windows') -or ($env:CI_WINDOWS -eq $true)) - - $excludes = @() - $commit = $env:BHCommitHash - $commitMessage = $env:BHCommitMessage # Check if tests are called from the Core itself, if so, adding excludes - if ($TestPath -eq $(Convert-Path "$PSScriptRoot\..\")) { - if ($commitMessage -match '!linter') { + if ($TestPath -eq (Convert-Path "$PSScriptRoot\..")) { + if ($env:BHCommitMessage -match '!linter') { Write-Warning "Skipping code linting per commit flag '!linter'" $excludes += 'Linter' } - if (!$CI_WIN) { - Write-Warning 'Skipping tests and code linting for decompress.ps1 because they only work on Windows' - $excludes += 'Decompress' - } - - $changedScripts = (Get-GitChangedFile -Include '*.ps1' -Commit $commit) + $changedScripts = (Get-GitChangedFile -Include '*.ps1', '*.psd1', '*.psm1' -Commit $env:BHCommitHash) if (!$changedScripts) { - Write-Warning "Skipping tests and code linting for *.ps1 files because they didn't change" + Write-Warning "Skipping tests and code linting for PowerShell scripts because they didn't change" $excludes += 'Linter' $excludes += 'Scoop' } @@ -44,12 +45,14 @@ if ($env:CI -eq $true) { $excludes += 'Decompress' } - if ('Decompress' -notin $excludes) { + if ('Decompress' -notin $excludes -and 'Windows' -notin $excludes) { Write-Host 'Install decompress dependencies ...' + Write-Host (7z.exe | Select-String -Pattern '7-Zip').ToString() + $env:SCOOP_HELPERS_PATH = 'C:\projects\helpers' if (!(Test-Path $env:SCOOP_HELPERS_PATH)) { - New-Item -ItemType Directory -Path $env:SCOOP_HELPERS_PATH + New-Item -ItemType Directory -Path $env:SCOOP_HELPERS_PATH | Out-Null } $env:SCOOP_LESSMSI_PATH = "$env:SCOOP_HELPERS_PATH\lessmsi\lessmsi.exe" @@ -83,13 +86,8 @@ if ($env:CI -eq $true) { } } - if ($excludes.Length -gt 0) { - $splat.ExcludeTag = $excludes - } - # Display CI environment variables - $buildVariables = ( Get-ChildItem -Path 'Env:' ).Where( { $_.Name -match '^(?:BH|CI(?:_|$)|APPVEYOR|GITHUB_|RUNNER_|SCOOP_)' } ) - $buildVariables += ( Get-Variable -Name 'CI_*' -Scope 'Script' ) + $buildVariables = (Get-ChildItem -Path 'Env:').Where({ $_.Name -match '^(?:BH|CI(?:_|$)|APPVEYOR|GITHUB_|RUNNER_|SCOOP_)' }) $details = $buildVariables | Where-Object -FilterScript { $_.Name -notmatch 'EMAIL' } | Sort-Object -Property 'Name' | @@ -97,24 +95,22 @@ if ($env:CI -eq $true) { Out-String Write-Host 'CI variables:' Write-Host $details -ForegroundColor DarkGray +} - # AppVeyor - if ($env:BHBuildSystem -eq "AppVeyor") { - $resultsXml = "$PSScriptRoot\TestResults.xml" - $splat += @{ - OutputFile = $resultsXml - OutputFormat = 'NUnitXML' - } - - Write-Host 'Invoke-Pester' @splat - $result = Invoke-Pester @splat +if ($excludes.Length -gt 0) { + $pesterConfig.Filter.ExcludeTag = $excludes +} - (New-Object Net.WebClient).UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", $resultsXml) - exit $result.FailedCount - } +if ($env:BHBuildSystem -eq 'AppVeyor') { + # AppVeyor + $resultsXml = "$PSScriptRoot\TestResults.xml" + $pesterConfig.TestResult.Enabled = $true + $pesterConfig.TestResult.OutputPath = $resultsXml + $result = Invoke-Pester -Configuration $pesterConfig + Add-TestResultToAppveyor -TestFile $resultsXml +} else { + # GitHub Actions / Local + $result = Invoke-Pester -Configuration $pesterConfig } -# GitHub Actions / Local -Write-Host 'Invoke-Pester' @splat -$result = Invoke-Pester @splat exit $result.FailedCount