Applied fixed from last PR review
This commit is contained in:
@@ -1,151 +0,0 @@
|
|||||||
# Ensure the latest version of Pester is installed and imported
|
|
||||||
if (-not (Get-Module -ListAvailable -Name Pester | Where-Object { $_.Version -ge [version]"5.3.1" })) {
|
|
||||||
Install-Module -Name Pester -Force -Scope CurrentUser -SkipPublisherCheck
|
|
||||||
}
|
|
||||||
|
|
||||||
Import-Module -Name Pester -MinimumVersion 5.3.1
|
|
||||||
|
|
||||||
# Describe block to contain all tests and setup
|
|
||||||
Describe "Setup and Tests" {
|
|
||||||
|
|
||||||
BeforeAll {
|
|
||||||
# Construct the absolute path to setup.ps1
|
|
||||||
$setupScriptPath = Join-Path -Path (Get-Location) -ChildPath "setup.ps1"
|
|
||||||
|
|
||||||
# Check if the setup script exists
|
|
||||||
if (-Not (Test-Path -Path $setupScriptPath)) {
|
|
||||||
Write-Host "Error: setup.ps1 script not found at path: $setupScriptPath"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Load the script to test
|
|
||||||
. $setupScriptPath
|
|
||||||
}
|
|
||||||
|
|
||||||
Context "Write-Log Tests" -Tag "Unit" {
|
|
||||||
It "should write INFO level log" {
|
|
||||||
$logFilePath = Join-Path $env:TEMP "script_log.txt"
|
|
||||||
Remove-Item $logFilePath -ErrorAction SilentlyContinue
|
|
||||||
|
|
||||||
Write-Log -Message "Test Info Message" -Level "INFO"
|
|
||||||
|
|
||||||
$logContent = Get-Content $logFilePath
|
|
||||||
$logContent | Should -Contain "INFO: Test Info Message"
|
|
||||||
}
|
|
||||||
|
|
||||||
It "should write ERROR level log" {
|
|
||||||
$logFilePath = Join-Path $env:TEMP "script_log.txt"
|
|
||||||
Remove-Item $logFilePath -ErrorAction SilentlyContinue
|
|
||||||
|
|
||||||
Write-Log -Message "Test Error Message" -Level "ERROR"
|
|
||||||
|
|
||||||
$logContent = Get-Content $logFilePath
|
|
||||||
$logContent | Should -Contain "ERROR: Test Error Message"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Context "Get-UserSelection Tests" -Tag "Unit" {
|
|
||||||
It "should handle valid input correctly" {
|
|
||||||
Mock Read-Host { return "A,B,C" }
|
|
||||||
|
|
||||||
$options = @("Option1", "Option2", "Option3")
|
|
||||||
$indices = Get-UserSelection -prompt "Select options" -options $options -defaultChoice "A"
|
|
||||||
|
|
||||||
$indices | Should -Be @(0, 1, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
It "should return indices for selected options" {
|
|
||||||
Mock Read-Host { return "a,b" }
|
|
||||||
|
|
||||||
$options = @("Option1", "Option2", "Option3")
|
|
||||||
$indices = Get-UserSelection -prompt "Select options" -options $options
|
|
||||||
|
|
||||||
$indices | Should -Be @(0, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
It "should return default choice if no input" {
|
|
||||||
Mock Read-Host { return "" }
|
|
||||||
|
|
||||||
$options = @("Option1", "Option2", "Option3")
|
|
||||||
$indices = Get-UserSelection -prompt "Select options" -options $options -defaultChoice "C"
|
|
||||||
|
|
||||||
$indices | Should -Be @(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
It "should handle mixed valid and invalid input correctly" {
|
|
||||||
Mock Read-Host { return "A,X,B,Y,C,Z" }
|
|
||||||
|
|
||||||
$options = @("Option1", "Option2", "Option3")
|
|
||||||
$indices = Get-UserSelection -prompt "Select options" -options $options -defaultChoice "A"
|
|
||||||
|
|
||||||
$indices | Should -Be @(0, 1, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
It "should handle invalid input gracefully" {
|
|
||||||
Mock Read-Host { return "x,y,z" }
|
|
||||||
|
|
||||||
$options = @("Option1", "Option2", "Option3")
|
|
||||||
$indices = Get-UserSelection -prompt "Select options" -options $options -defaultChoice "A"
|
|
||||||
|
|
||||||
$indices | Should -Be @()
|
|
||||||
}
|
|
||||||
|
|
||||||
It "should handle input without whitespace" {
|
|
||||||
Mock Read-Host { return "a,b,c" }
|
|
||||||
|
|
||||||
$options = @("Option1", "Option2", "Option3")
|
|
||||||
$indices = Get-UserSelection -prompt "Select options" -options $options
|
|
||||||
|
|
||||||
$indices | Should -Be @(0, 1, 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Context "Exit-Script Tests" -Tag "Unit" {
|
|
||||||
BeforeAll {
|
|
||||||
# Set environment variables for the test
|
|
||||||
$global:OldVirtualPath = "C:\old\path"
|
|
||||||
$global:LogFilePath = "C:\path\to\logfile.log"
|
|
||||||
}
|
|
||||||
|
|
||||||
BeforeEach {
|
|
||||||
Mock Write-Log {}
|
|
||||||
Mock Start-Process {}
|
|
||||||
Mock Read-Host { return "Y" }
|
|
||||||
|
|
||||||
# Backup the original PATH
|
|
||||||
$global:OriginalPath = $env:PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
AfterEach {
|
|
||||||
# Restore the original PATH
|
|
||||||
$env:PATH = $OriginalPath
|
|
||||||
}
|
|
||||||
|
|
||||||
It "should exit with the given exit code without waiting for key press" {
|
|
||||||
$exitCode = Exit-Script -exitCode 0 -isSubShell $true -waitForKeypress $false
|
|
||||||
$exitCode | Should -Be 0
|
|
||||||
}
|
|
||||||
|
|
||||||
It "should prompt to open log file on error" {
|
|
||||||
Exit-Script -exitCode 1 -isSubShell $true -waitForKeypress $false
|
|
||||||
Assert-MockCalled Read-Host -Exactly 1
|
|
||||||
Assert-MockCalled Start-Process -Exactly 1
|
|
||||||
}
|
|
||||||
|
|
||||||
It "should restore the environment path if OldVirtualPath is set" {
|
|
||||||
# Set a different PATH to simulate the change
|
|
||||||
$env:PATH = "C:\new\path"
|
|
||||||
Exit-Script -exitCode 0 -isSubShell $true -waitForKeypress $false
|
|
||||||
$env:PATH | Should -Be "C:\old\path"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Context "Get-PythonVersionTag Tests" -Tag "Unit" {
|
|
||||||
It "should return the correct Python version tag" {
|
|
||||||
Mock Invoke-Expression { param($cmd) return "cp39-win_amd64" }
|
|
||||||
|
|
||||||
$tag = Get-PythonVersionTag
|
|
||||||
$tag | Should -Be "cp39-win_amd64"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
# Set the console color to Matrix theme and clear the console
|
Import-Module -Name ".\setupFunctions.psm1"
|
||||||
$host.UI.RawUI.BackgroundColor = "Black"
|
|
||||||
$host.UI.RawUI.ForegroundColor = "Green"
|
|
||||||
Clear-Host
|
Clear-Host
|
||||||
|
|
||||||
# Set the log file path and initialize variables
|
# Set the log file path and initialize variables
|
||||||
$LogFilePath = Join-Path $env:TEMP "script_log.txt"
|
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
|
||||||
$ProjectDir = "G:\Downloads\GitHub\freqtrade"
|
$Global:LogFilePath = Join-Path $env:TEMP "script_log_$timestamp.txt"
|
||||||
$RequirementFiles = @("requirements.txt", "requirements-dev.txt", "requirements-hyperopt.txt", "requirements-freqai.txt", "requirements-plot.txt")
|
|
||||||
|
$RequirementFiles = @("requirements.txt", "requirements-dev.txt", "requirements-hyperopt.txt", "requirements-freqai.txt", "requirements-freqai-rl.txt", "requirements-plot.txt")
|
||||||
$VenvName = ".venv"
|
$VenvName = ".venv"
|
||||||
|
$VenvDir = Join-Path $PSScriptRoot $VenvName
|
||||||
|
|
||||||
function Write-Log {
|
function Write-Log {
|
||||||
param (
|
param (
|
||||||
@@ -15,6 +15,10 @@ function Write-Log {
|
|||||||
[string]$Level = 'INFO'
|
[string]$Level = 'INFO'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (-not (Test-Path -Path $LogFilePath)) {
|
||||||
|
New-Item -ItemType File -Path $LogFilePath -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
switch ($Level) {
|
switch ($Level) {
|
||||||
'INFO' { Write-Host $Message -ForegroundColor Green }
|
'INFO' { Write-Host $Message -ForegroundColor Green }
|
||||||
'WARNING' { Write-Host $Message -ForegroundColor Yellow }
|
'WARNING' { Write-Host $Message -ForegroundColor Yellow }
|
||||||
@@ -26,60 +30,88 @@ function Write-Log {
|
|||||||
|
|
||||||
function Get-UserSelection {
|
function Get-UserSelection {
|
||||||
param (
|
param (
|
||||||
[string]$prompt,
|
[string]$Prompt,
|
||||||
[string[]]$options,
|
[string[]]$Options,
|
||||||
[string]$defaultChoice = 'A'
|
[string]$DefaultChoice = 'A',
|
||||||
|
[bool]$AllowMultipleSelections = $true
|
||||||
)
|
)
|
||||||
|
|
||||||
Write-Log "$prompt`n" -Level 'PROMPT'
|
Write-Log "$Prompt`n" -Level 'PROMPT'
|
||||||
for ($i = 0; $i -lt $options.Length; $i++) {
|
for ($i = 0; $i -lt $Options.Length; $i++) {
|
||||||
Write-Log "$([char](65 + $i)). $($options[$i])" -Level 'PROMPT'
|
Write-Log "$([char](65 + $i)). $($Options[$i])" -Level 'PROMPT'
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Log "`nSelect one or more options by typing the corresponding letters, separated by commas." -Level 'PROMPT'
|
if ($AllowMultipleSelections) {
|
||||||
$inputPath = Read-Host
|
Write-Log "`nSelect one or more options by typing the corresponding letters, separated by commas." -Level 'PROMPT'
|
||||||
if ([string]::IsNullOrEmpty($inputPath)) {
|
}
|
||||||
$inputPath = $defaultChoice
|
else {
|
||||||
|
Write-Log "`nSelect an option by typing the corresponding letter." -Level 'PROMPT'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Ensure $inputPath is treated as a string and split it by commas
|
$userInput = Read-Host
|
||||||
$inputPath = [string]$inputPath
|
if ([string]::IsNullOrEmpty($userInput)) {
|
||||||
$selections = $inputPath.Split(',') | ForEach-Object {
|
$userInput = $DefaultChoice
|
||||||
$_.Trim().ToUpper()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Convert each selection from letter to index and validate
|
if ($AllowMultipleSelections) {
|
||||||
$indices = @()
|
# Ensure $userInput is treated as a string and split it by commas
|
||||||
foreach ($selection in $selections) {
|
$userInput = [string]$userInput
|
||||||
if ($selection -match '^[A-Z]$') {
|
$selections = $userInput.Split(',') | ForEach-Object {
|
||||||
$index = [int][char]$selection - [int][char]'A'
|
$_.Trim().ToUpper()
|
||||||
if ($index -ge 0 -and $index -lt $options.Length) {
|
}
|
||||||
$indices += $index
|
|
||||||
|
# Convert each selection from letter to index and validate
|
||||||
|
$selectedIndices = @()
|
||||||
|
foreach ($selection in $selections) {
|
||||||
|
if ($selection -match '^[A-Z]$') {
|
||||||
|
$index = [int][char]$selection - [int][char]'A'
|
||||||
|
if ($index -ge 0 -and $index -lt $Options.Length) {
|
||||||
|
$selectedIndices += $index
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Invalid input: $selection. Please enter letters within the valid range of options." -Level 'ERROR'
|
||||||
|
return -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Write-Log "Invalid input: $selection. Please enter letters within the valid range of options." -Level 'ERROR'
|
Write-Log "Invalid input: $selection. Please enter letters between A and Z." -Level 'ERROR'
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $selectedIndices
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# Convert the selection from letter to index and validate
|
||||||
|
if ($userInput -match '^[A-Z]$') {
|
||||||
|
$selectedIndex = [int][char]$userInput - [int][char]'A'
|
||||||
|
if ($selectedIndex -ge 0 -and $selectedIndex -lt $Options.Length) {
|
||||||
|
return $selectedIndex
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Invalid input: $userInput. Please enter a letter within the valid range of options." -Level 'ERROR'
|
||||||
|
return -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Write-Log "Invalid input: $selection. Please enter letters between A and Z." -Level 'ERROR'
|
Write-Log "Invalid input: $userInput. Please enter a letter between A and Z." -Level 'ERROR'
|
||||||
|
return -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $indices
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Exit-Script {
|
function Exit-Script {
|
||||||
param (
|
param (
|
||||||
[int]$exitCode,
|
[int]$ExitCode,
|
||||||
[bool]$isSubShell = $true,
|
[bool]$IsSubShell = $true,
|
||||||
[bool]$waitForKeypress = $true
|
[bool]$WaitForKeypress = $true
|
||||||
)
|
)
|
||||||
|
|
||||||
if ($OldVirtualPath) {
|
if ($Global:OldVirtualPath) {
|
||||||
$env:PATH = $OldVirtualPath
|
$env:PATH = $Global:OldVirtualPath
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($exitCode -ne 0 -and $isSubShell) {
|
if ($ExitCode -ne 0 -and $IsSubShell) {
|
||||||
Write-Log "Script failed. Would you like to open the log file? (Y/N)" -Level 'PROMPT'
|
Write-Log "Script failed. Would you like to open the log file? (Y/N)" -Level 'PROMPT'
|
||||||
$openLog = Read-Host
|
$openLog = Read-Host
|
||||||
if ($openLog -eq 'Y' -or $openLog -eq 'y') {
|
if ($openLog -eq 'Y' -or $openLog -eq 'y') {
|
||||||
@@ -87,143 +119,192 @@ function Exit-Script {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($waitForKeypress) {
|
if ($WaitForKeypress) {
|
||||||
Write-Log "Press any key to exit..."
|
Write-Log "Press any key to exit..."
|
||||||
$host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | Out-Null
|
$host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
return $exitCode
|
return $ExitCode
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to handle installation
|
# Function to install requirements
|
||||||
function Install {
|
function Install-Requirements {
|
||||||
param ([string]$InputPath)
|
param ([string]$RequirementsPath)
|
||||||
|
|
||||||
if (-not $InputPath) {
|
if (-not $RequirementsPath) {
|
||||||
Write-Log "No input provided for installation." -Level 'ERROR'
|
Write-Log "No requirements path provided for installation." -Level 'ERROR'
|
||||||
Exit-Script -exitCode 1
|
Exit-Script -ExitCode 1
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Log "Installing $InputPath..."
|
Write-Log "Installing requirements from $RequirementsPath..."
|
||||||
$installCmd = if (Test-Path $InputPath) { $VenvPip + @('install', '-r', $InputPath) } else { $VenvPip + @('install', $InputPath) }
|
$installCmd = if (Test-Path $RequirementsPath) { & $VenvPip install -r $RequirementsPath } else { & $VenvPip install $RequirementsPath }
|
||||||
$output = & $installCmd[0] $installCmd[1..$installCmd.Length] 2>&1
|
$output = & $installCmd[0] $installCmd[1..$installCmd.Length] 2>&1
|
||||||
$output | Out-File $LogFilePath -Append
|
$output | Out-File $LogFilePath -Append
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
Write-Log "Conflict detected. Exiting now..." -Level 'ERROR'
|
Write-Log "Conflict detected. Exiting now..." -Level 'ERROR'
|
||||||
Exit-Script -exitCode 1
|
Exit-Script -ExitCode 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to get the installed Python version tag for wheel compatibility
|
function Test-PythonExecutable {
|
||||||
function Get-PythonVersionTag {
|
param(
|
||||||
$pythonVersion = & python -c "import sys; print(f'cp{sys.version_info.major}{sys.version_info.minor}')"
|
[string]$PythonExecutable
|
||||||
$architecture = & python -c "import platform; print('win_amd64' if platform.machine().endswith('64') else 'win32')"
|
)
|
||||||
return "$pythonVersion-$architecture"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Exit in test environment
|
$pythonCmd = Get-Command $PythonExecutable -ErrorAction SilentlyContinue
|
||||||
if ($MyInvocation.InvocationName -ne $MyInvocation.MyCommand.Name) {
|
if ($pythonCmd) {
|
||||||
exit
|
$command = "$($pythonCmd.Source) --version 2>&1"
|
||||||
}
|
$versionOutput = Invoke-Expression $command
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
# Check for admin privileges and elevate if necessary
|
$version = $versionOutput | Select-String -Pattern "Python (\d+\.\d+\.\d+)" | ForEach-Object { $_.Matches.Groups[1].Value }
|
||||||
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
Write-Log "Python version $version found using executable '$PythonExecutable'."
|
||||||
Write-Log "Requesting administrative privileges..." -Level 'ERROR'
|
return $true
|
||||||
Start-Process PowerShell -ArgumentList "-File `"$PSCommandPath`"" -Verb RunAs
|
}
|
||||||
Exit-Script -exitCode 1 -isSubShell $false
|
else {
|
||||||
}
|
Write-Log "Python executable '$PythonExecutable' not working correctly." -Level 'ERROR'
|
||||||
|
return $false
|
||||||
# Log file setup
|
}
|
||||||
"Admin rights confirmed" | Out-File $LogFilePath -Append
|
|
||||||
"Starting the script operations..." | Out-File $LogFilePath -Append
|
|
||||||
|
|
||||||
# Navigate to the project directory
|
|
||||||
Set-Location -Path $ProjectDir
|
|
||||||
"Current directory: $(Get-Location)" | Out-File $LogFilePath -Append
|
|
||||||
|
|
||||||
# Define the path to the Python executable in the virtual environment
|
|
||||||
$VenvPython = Join-Path $ProjectDir "$VenvName\Scripts\python.exe"
|
|
||||||
|
|
||||||
# Check if the virtual environment exists, if not, create it
|
|
||||||
if (-Not (Test-Path $VenvPython)) {
|
|
||||||
Write-Log "Virtual environment not found. Creating virtual environment..." -IsError $false
|
|
||||||
python -m venv "$VenvName"
|
|
||||||
if (-Not (Test-Path $VenvPython)) {
|
|
||||||
Write-Log "Failed to create virtual environment." -Level 'ERROR'
|
|
||||||
Exit-Script -exitCode 1
|
|
||||||
}
|
|
||||||
Write-Log "Virtual environment created successfully." -IsError $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Define the pip command using the Python executable
|
|
||||||
$VenvPip = @($VenvPython, '-m', 'pip')
|
|
||||||
|
|
||||||
# Activate the virtual environment
|
|
||||||
$OldVirtualPath = $env:PATH
|
|
||||||
$env:PATH = "$ProjectDir\$VenvName\Scripts;$env:PATH"
|
|
||||||
|
|
||||||
# Ensure setuptools is installed using the virtual environment's pip
|
|
||||||
Write-Log "Ensuring setuptools is installed..."
|
|
||||||
& $VenvPip[0] $VenvPip[1..$VenvPip.Length] 'install', '-v', 'setuptools' | Out-File $LogFilePath -Append 2>&1
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
|
||||||
Write-Log "Failed to install setuptools." -Level 'ERROR'
|
|
||||||
Exit-Script -exitCode 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Pull latest updates
|
|
||||||
Write-Log "Pulling latest updates..."
|
|
||||||
& git pull | Out-File $LogFilePath -Append 2>&1
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
|
||||||
Write-Log "Failed to pull updates from Git." -Level 'ERROR'
|
|
||||||
Exit-Script -exitCode 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Install TA-Lib using the virtual environment's pip
|
|
||||||
Write-Log "Installing TA-Lib using virtual environment's pip..."
|
|
||||||
& $VenvPip[0] $VenvPip[1..$VenvPip.Length] 'install', '--find-links=build_helpers\', '--prefer-binary', 'TA-Lib' | Out-File $LogFilePath -Append 2>&1
|
|
||||||
|
|
||||||
# Present options for requirement files
|
|
||||||
$selectedIndices = Get-UserSelection -prompt "Select which requirement files to install:" -options $RequirementFiles -defaultChoice 'A'
|
|
||||||
|
|
||||||
# Install selected requirement files
|
|
||||||
foreach ($index in $selectedIndices) {
|
|
||||||
if ($index -lt 0 -or $index -ge $RequirementFiles.Length) {
|
|
||||||
Write-Log "Invalid selection index: $index" -Level 'ERROR'
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
$filePath = Join-Path $ProjectDir $RequirementFiles[$index]
|
|
||||||
if (Test-Path $filePath) {
|
|
||||||
Install $filePath
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Write-Log "Requirement file not found: $filePath" -Level 'ERROR'
|
Write-Log "Python executable '$PythonExecutable' not found." -Level 'ERROR'
|
||||||
Exit-Script -exitCode 1
|
return $false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Install freqtrade from setup using the virtual environment's Python
|
function Find-PythonExecutable {
|
||||||
Write-Log "Installing freqtrade from setup..."
|
|
||||||
$setupInstallCommand = "$VenvPython -m pip install -e ."
|
$pythonExecutables = @("python", "python3", "python3.9", "python3.10", "python3.11", "C:\Python39\python.exe", "C:\Python310\python.exe", "C:\Python311\python.exe")
|
||||||
Invoke-Expression $setupInstallCommand | Out-File $LogFilePath -Append 2>&1
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
foreach ($executable in $pythonExecutables) {
|
||||||
Write-Log "Failed to install freqtrade." -Level 'ERROR'
|
if (Test-PythonExecutable -PythonExecutable $executable) {
|
||||||
Exit-Script -exitCode 1
|
return $executable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $null
|
||||||
}
|
}
|
||||||
|
function Main {
|
||||||
|
# Exit on lower versions than Python 3.9 or when Python executable not found
|
||||||
|
$pythonExecutable = Find-PythonExecutable
|
||||||
|
if ($null -eq $pythonExecutable) {
|
||||||
|
Write-Host "Error: No suitable Python executable found. Please ensure that Python 3.9 or higher is installed and available in the system PATH."
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
|
|
||||||
# Ask if the user wants to install the UI
|
# Check for admin privileges and elevate if necessary
|
||||||
$uiOptions = @("Yes", "No")
|
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||||
$installUI = Get-UserSelection -prompt "Do you want to install the freqtrade UI?" -options $uiOptions -defaultChoice 'B'
|
Write-Log "Requesting administrative privileges..."
|
||||||
|
Start-Process PowerShell -ArgumentList "-File `"$PSCommandPath`"" -Verb RunAs
|
||||||
|
}
|
||||||
|
|
||||||
if ($uiOptions[$installUI] -eq "Yes") {
|
# Log file setup
|
||||||
# Install freqtrade UI using the virtual environment's install-ui command
|
"Admin rights confirmed" | Out-File $LogFilePath -Append
|
||||||
Write-Log "Installing freqtrade UI..."
|
"Starting the < operations..." | Out-File $LogFilePath -Append
|
||||||
& $VenvPython 'freqtrade', 'install-ui' | Out-File $LogFilePath -Append 2>&1
|
|
||||||
|
# Navigate to the project directory
|
||||||
|
Set-Location -Path $PSScriptRoot
|
||||||
|
"Current directory: $(Get-Location)" | Out-File $LogFilePath -Append
|
||||||
|
|
||||||
|
# Define the path to the Python executable in the virtual environment
|
||||||
|
$VenvPython = "$VenvDir\Scripts\python.exe"
|
||||||
|
|
||||||
|
# Check if the virtual environment exists, if not, create it
|
||||||
|
if (-Not (Test-Path $VenvPython)) {
|
||||||
|
Write-Log "Virtual environment not found. Creating virtual environment..." -Level 'ERROR'
|
||||||
|
python -m venv "$VenvName"
|
||||||
|
if (-Not (Test-Path $VenvPython)) {
|
||||||
|
Write-Log "Failed to create virtual environment." -Level 'ERROR'
|
||||||
|
Exit-Script -exitCode 1
|
||||||
|
}
|
||||||
|
Write-Log "Virtual environment created successfully." -Level 'ERROR'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Activate the virtual environment
|
||||||
|
$Global:OldVirtualPath = $env:PATH
|
||||||
|
$env:PATH = "$VenvDir\Scripts;$env:PATH"
|
||||||
|
|
||||||
|
# Pull latest updates
|
||||||
|
Write-Log "Pulling latest updates..."
|
||||||
|
& "C:\Program Files\Git\cmd\git.exe" pull | Out-File $LogFilePath -Append 2>&1
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
Write-Log "Failed to install freqtrade UI." -Level 'ERROR'
|
Write-Log "Failed to pull updates from Git." -Level 'ERROR'
|
||||||
Exit-Script -exitCode 1
|
Exit-Script -exitCode 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path "$VenvDir\Lib\site-packages\talib")) {
|
||||||
|
# Install TA-Lib using the virtual environment's pip
|
||||||
|
Write-Log "Installing TA-Lib using virtual environment's pip..."
|
||||||
|
& $VenvPython -m pip install --find-links=build_helpers\ --prefer-binary TA-Lib | Out-File $LogFilePath -Append 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Present options for requirement files
|
||||||
|
$selectedIndices = Get-UserSelection -prompt "Select which requirement files to install:" -options $RequirementFiles -defaultChoice 'A'
|
||||||
|
|
||||||
|
# Cache the selected requirement files
|
||||||
|
$selectedRequirementFiles = @()
|
||||||
|
foreach ($index in $selectedIndices) {
|
||||||
|
if ($index -lt 0 -or $index -ge $RequirementFiles.Length) {
|
||||||
|
Write-Log "Invalid selection index: $index" -Level 'ERROR'
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$filePath = Join-Path $PSScriptRoot $RequirementFiles[$index]
|
||||||
|
if (Test-Path $filePath) {
|
||||||
|
$selectedRequirementFiles += $filePath
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Requirement file not found: $filePath" -Level 'ERROR'
|
||||||
|
Exit-Script -exitCode 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install the selected requirement files together
|
||||||
|
if ($selectedRequirementFiles.Count -gt 0) {
|
||||||
|
Write-Log "Installing selected requirement files..."
|
||||||
|
$selectedRequirementFiles | ForEach-Object { & $VenvPython -m pip install -r $_ --quiet}
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Log "Failed to install selected requirement files. Exiting now..." -Level 'ERROR'
|
||||||
|
Exit-Script -exitCode 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install freqtrade from setup using the virtual environment's Python
|
||||||
|
Write-Log "Installing freqtrade from setup..."
|
||||||
|
$setupInstallCommand = "$VenvPython -m pip install -e ."
|
||||||
|
Invoke-Expression $setupInstallCommand | Out-File $LogFilePath -Append 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Log "Failed to install freqtrade." -Level 'ERROR'
|
||||||
|
Exit-Script -exitCode 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$uiOptions = @("Yes", "No")
|
||||||
|
$installUI = Get-UserSelection -prompt "Do you want to install the freqtrade UI?" -options $uiOptions -defaultChoice 'B' -allowMultipleSelections $false
|
||||||
|
|
||||||
|
if ($installUI -eq 0) {
|
||||||
|
# User selected "Yes"
|
||||||
|
# Install freqtrade UI using the virtual environment's install-ui command
|
||||||
|
Write-Log "Installing freqtrade UI..."
|
||||||
|
& $VenvPython 'freqtrade', 'install-ui' | Out-File $LogFilePath -Append 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Log "Failed to install freqtrade UI." -Level 'ERROR'
|
||||||
|
Exit-Script -exitCode 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ($installUI -eq 1) {
|
||||||
|
# User selected "No"
|
||||||
|
# Skip installing freqtrade UI
|
||||||
|
Write-Log "Skipping freqtrade UI installation."
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# Invalid selection
|
||||||
|
# Handle the error case
|
||||||
|
Write-Log "Invalid selection for freqtrade UI installation." -Level 'ERROR'
|
||||||
|
Exit-Script -exitCode 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Update complete!"
|
||||||
|
Exit-Script -exitCode 0
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Log "Update complete!"
|
# Call the Main function
|
||||||
Exit-Script -exitCode 0
|
Main
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
# Ensure the specific version 5.3.1 of Pester is installed and imported
|
||||||
|
$requiredVersion = [version]"5.3.1"
|
||||||
|
$installedModule = Get-Module -ListAvailable -Name Pester
|
||||||
|
if (-not ($installedModule) -or ($installedModule.Version -lt $requiredVersion)) {
|
||||||
|
Install-Module -Name Pester -RequiredVersion $requiredVersion -Force -Scope CurrentUser -SkipPublisherCheck
|
||||||
|
}
|
||||||
|
|
||||||
|
Import-Module -Name Pester -MinimumVersion 5.3.1
|
||||||
|
|
||||||
|
# Describe block to contain all tests and setup
|
||||||
|
Describe "Setup and Tests" {
|
||||||
|
BeforeAll {
|
||||||
|
# Construct the absolute path to setup.ps1
|
||||||
|
$setupScriptPath = Join-Path $PSScriptRoot "..\setup.ps1"
|
||||||
|
|
||||||
|
# Check if the setup script exists
|
||||||
|
if (-Not (Test-Path -Path $setupScriptPath)) {
|
||||||
|
Write-Host "Error: setup.ps1 script not found at path: $setupScriptPath"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mock main to prevent it from running
|
||||||
|
Mock Main {}
|
||||||
|
|
||||||
|
. $setupScriptPath
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Write-Log Tests" -Tag "Unit" {
|
||||||
|
It "should write INFO level log" {
|
||||||
|
Remove-Item $Global:LogFilePath -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
Write-Log -Message "Test Info Message" -Level "INFO"
|
||||||
|
|
||||||
|
$Global:LogFilePath | Should -Exist
|
||||||
|
$logContent = Get-Content $Global:LogFilePath
|
||||||
|
$logContent | Should -Contain "INFO: Test Info Message"
|
||||||
|
}
|
||||||
|
|
||||||
|
It "should write ERROR level log" {
|
||||||
|
$Global:LogFilePath = Join-Path $env:TEMP "script_log.txt"
|
||||||
|
Remove-Item $Global:LogFilePath -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
Write-Log -Message "Test Error Message" -Level "ERROR"
|
||||||
|
|
||||||
|
$logContent = Get-Content $Global:LogFilePath
|
||||||
|
$logContent | Should -Contain "ERROR: Test Error Message"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Describe "Get-UserSelection Tests" {
|
||||||
|
Context "Valid input" {
|
||||||
|
It "Should return the correct index for a valid single selection" {
|
||||||
|
$options = @("Option1", "Option2", "Option3")
|
||||||
|
Mock Read-Host { return "B" }
|
||||||
|
$result = Get-UserSelection -prompt "Select an option" -options $options
|
||||||
|
$result | Should -Be 1
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should return the default choice when no input is provided" {
|
||||||
|
$options = @("Option1", "Option2", "Option3")
|
||||||
|
Mock Read-Host { return "" }
|
||||||
|
$result = Get-UserSelection -prompt "Select an option" -options $options -defaultChoice "C"
|
||||||
|
$result | Should -Be 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Invalid input" {
|
||||||
|
It "Should return -1 for an invalid letter selection" {
|
||||||
|
$options = @("Option1", "Option2", "Option3")
|
||||||
|
Mock Read-Host { return "X" }
|
||||||
|
$result = Get-UserSelection -prompt "Select an option" -options $options
|
||||||
|
$result | Should -Be -1
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should return -1 for a selection outside the valid range" {
|
||||||
|
$options = @("Option1", "Option2", "Option3")
|
||||||
|
Mock Read-Host { return "D" }
|
||||||
|
$result = Get-UserSelection -prompt "Select an option" -options $options
|
||||||
|
$result | Should -Be -1
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should return -1 for a non-letter input" {
|
||||||
|
$options = @("Option1", "Option2", "Option3")
|
||||||
|
Mock Read-Host { return "1" }
|
||||||
|
$result = Get-UserSelection -prompt "Select an option" -options $options
|
||||||
|
$result | Should -Be -1
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should return -1 for mixed valid and invalid input" {
|
||||||
|
Mock Read-Host { return "A,X,B,Y,C,Z" }
|
||||||
|
$options = @("Option1", "Option2", "Option3")
|
||||||
|
$indices = Get-UserSelection -prompt "Select options" -options $options -defaultChoice "A"
|
||||||
|
$indices | Should -Be -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Multiple selections" {
|
||||||
|
It "Should handle valid input correctly" {
|
||||||
|
Mock Read-Host { return "A,B,C" }
|
||||||
|
$options = @("Option1", "Option2", "Option3")
|
||||||
|
$indices = Get-UserSelection -prompt "Select options" -options $options -defaultChoice "A"
|
||||||
|
$indices | Should -Be @(0, 1, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should return indices for selected options" {
|
||||||
|
Mock Read-Host { return "a,b" }
|
||||||
|
$options = @("Option1", "Option2", "Option3")
|
||||||
|
$indices = Get-UserSelection -prompt "Select options" -options $options
|
||||||
|
$indices | Should -Be @(0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should return default choice if no input" {
|
||||||
|
Mock Read-Host { return "" }
|
||||||
|
$options = @("Option1", "Option2", "Option3")
|
||||||
|
$indices = Get-UserSelection -prompt "Select options" -options $options -defaultChoice "C"
|
||||||
|
$indices | Should -Be @(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should handle invalid input gracefully" {
|
||||||
|
Mock Read-Host { return "x,y,z" }
|
||||||
|
$options = @("Option1", "Option2", "Option3")
|
||||||
|
$indices = Get-UserSelection -prompt "Select options" -options $options -defaultChoice "A"
|
||||||
|
$indices | Should -Be -1
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should handle input without whitespace" {
|
||||||
|
Mock Read-Host { return "a,b,c" }
|
||||||
|
$options = @("Option1", "Option2", "Option3")
|
||||||
|
$indices = Get-UserSelection -prompt "Select options" -options $options
|
||||||
|
$indices | Should -Be @(0, 1, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Describe "Exit-Script Tests" -Tag "Unit" {
|
||||||
|
BeforeEach {
|
||||||
|
Mock Write-Log {}
|
||||||
|
Mock Start-Process {}
|
||||||
|
Mock Read-Host { return "Y" }
|
||||||
|
|
||||||
|
# Backup the original PATH
|
||||||
|
$global:OriginalPath = $env:PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
AfterEach {
|
||||||
|
# Restore the original PATH
|
||||||
|
$env:PATH = $OriginalPath
|
||||||
|
}
|
||||||
|
|
||||||
|
It "should exit with the given exit code without waiting for key press" {
|
||||||
|
$exitCode = Exit-Script -exitCode 0 -isSubShell $true -waitForKeypress $false
|
||||||
|
$exitCode | Should -Be 0
|
||||||
|
}
|
||||||
|
|
||||||
|
It "should prompt to open log file on error" {
|
||||||
|
Exit-Script -exitCode 1 -isSubShell $true -waitForKeypress $false
|
||||||
|
Assert-MockCalled Read-Host -Exactly 1
|
||||||
|
Assert-MockCalled Start-Process -Exactly 1
|
||||||
|
}
|
||||||
|
|
||||||
|
It "should restore the environment path if OldVirtualPath is set" {
|
||||||
|
# Set a different PATH to simulate the change
|
||||||
|
$env:PATH = "C:\new\path"
|
||||||
|
$Global:OldVirtualPath = $env:PATH
|
||||||
|
Exit-Script -exitCode 0 -isSubShell $true -waitForKeypress $false
|
||||||
|
$env:PATH | Should -Be "C:\new\path"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context 'Find-PythonExecutable' {
|
||||||
|
It 'Returns the first valid Python executable' {
|
||||||
|
Mock Test-PythonExecutable { $true } -ParameterFilter { $PythonExecutable -eq 'python' }
|
||||||
|
$result = Find-PythonExecutable
|
||||||
|
$result | Should -Be 'python'
|
||||||
|
}
|
||||||
|
|
||||||
|
It 'Returns null if no valid Python executable is found' {
|
||||||
|
Mock Test-PythonExecutable { $false }
|
||||||
|
$result = Find-PythonExecutable
|
||||||
|
$result | Should -Be $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user