From ccb15880481827a1fef78818d19f950135080e2a Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Mon, 20 May 2024 21:08:58 +0200 Subject: [PATCH 01/33] Added setup.ps1 for installation/updates on Windows --- setup.ps1 | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 setup.ps1 diff --git a/setup.ps1 b/setup.ps1 new file mode 100644 index 000000000..551331f3f --- /dev/null +++ b/setup.ps1 @@ -0,0 +1,214 @@ +# Set the console color to Matrix theme and clear the console +$host.UI.RawUI.BackgroundColor = "Black" +$host.UI.RawUI.ForegroundColor = "Green" +Clear-Host + +# Set the log file path and initialize variables +$LogFilePath = Join-Path $env:TEMP "script_log.txt" +$ProjectDir = "G:\Downloads\GitHub\freqtrade" +$RequirementFiles = @("requirements.txt", "requirements-dev.txt", "requirements-hyperopt.txt", "requirements-freqai.txt", "requirements-plot.txt") +$VenvName = ".venv" + +function Write-Log { + param ( + [string]$Message, + [string]$Level = 'INFO' + ) + switch ($Level) { + 'INFO' { Write-Host $Message -ForegroundColor Green } + 'WARNING' { Write-Host $Message -ForegroundColor Yellow } + 'ERROR' { Write-Host $Message -ForegroundColor Red } + 'PROMPT' { Write-Host $Message -ForegroundColor Cyan } + } + "${Level}: $Message" | Out-File $LogFilePath -Append +} + +function Get-UserSelection { + param ( + [string]$prompt, + [string[]]$options, + [string]$defaultChoice = 'A' + ) + + Write-Log "$prompt`n" -Level 'PROMPT' + for ($i = 0; $i -lt $options.Length; $i++) { + 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' + $inputPath = Read-Host + if ([string]::IsNullOrEmpty($inputPath)) { + $inputPath = $defaultChoice + } + + # Ensure $inputPath is treated as a string and split it by commas + $inputPath = [string]$inputPath + $selections = $inputPath.Split(',') | ForEach-Object { + $_.Trim().ToUpper() + } + + # Convert each selection from letter to index + $indices = $selections | ForEach-Object { + if ($_ -match '^[A-Z]$') { + # Ensure the input is a single uppercase letter + [int][char]$_ - [int][char]'A' + } + else { + Write-Log "Invalid input: $_. Please enter letters between A and Z." -Level 'ERROR' + continue + } + } + + return $indices +} + +function Exit-Script { + param ( + [int]$exitCode, + [bool]$isSubShell = $true + ) + + if ($OldVirtualPath) { + $env:PATH = $OldVirtualPath + } + + # Check if the script is exiting with an error and it's not a subshell + if ($exitCode -ne 0 -and $isSubShell) { + Write-Log "Script failed. Would you like to open the log file? (Y/N)" -Level 'PROMPT' + $openLog = Read-Host + if ($openLog -eq 'Y' -or $openLog -eq 'y') { + Start-Process notepad.exe -ArgumentList $LogFilePath + } + } + + Write-Log "Press any key to exit..." + $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | Out-Null + exit $exitCode +} + +# Function to handle installation and conflict resolution +function Install-And-Resolve { + param ([string]$InputPath) + if (-not $InputPath) { + Write-Log "ERROR: No input provided for installation." -Level 'ERROR' + Exit-Script -exitCode 1 + } + Write-Log "Installing $InputPath..." + $installCmd = if (Test-Path $InputPath) { $VenvPip + @('install', '-r', $InputPath) } else { $VenvPip + @('install', $InputPath) } + $output = & $installCmd[0] $installCmd[1..$installCmd.Length] 2>&1 + $output | Out-File $LogFilePath -Append + if ($LASTEXITCODE -ne 0) { + Write-Log "Conflict detected, attempting to resolve..." -Level 'ERROR' + & $VenvPip[0] $VenvPip[1..$VenvPip.Length] 'check' | Out-File "conflicts.txt" + Exit-Script -exitCode 1 + } +} + +# Function to get the installed Python version tag for wheel compatibility +function Get-PythonVersionTag { + $pythonVersion = & python -c "import sys; print(f'cp{sys.version_info.major}{sys.version_info.minor}')" + $architecture = & python -c "import platform; print('win_amd64' if platform.machine().endswith('64') else 'win32')" + return "$pythonVersion-$architecture" +} + +# Check for admin privileges and elevate if necessary +if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + Write-Log "Requesting administrative privileges..." -Level 'ERROR' + Start-Process PowerShell -ArgumentList "-File `"$PSCommandPath`"" -Verb RunAs + Exit-Script -exitCode 1 -isSubShell $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-And-Resolve $filePath + } + else { + Write-Log "Requirement file not found: $filePath" -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 +} + +# Ask if the user wants to install the UI +$uiOptions = @("Yes", "No") +$installUI = Get-UserSelection -prompt "Do you want to install the freqtrade UI?" -options $uiOptions -defaultChoice 'B' + +if ($uiOptions[$installUI] -eq "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 + } +} + +Write-Log "Update complete!" +Exit-Script -exitCode 0 From 12b5376cb6aa25688c53d5b3332776dfa6d0ff6b Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Tue, 21 May 2024 08:04:09 +0200 Subject: [PATCH 02/33] Revert "Updated gitignore file" This reverts commit 5110c14d358b5d97cdde815410be1417f66a0332. --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index 1e96fd5da..c818981ee 100644 --- a/.gitignore +++ b/.gitignore @@ -114,6 +114,3 @@ target/ !config_examples/config_full.example.json !config_examples/config_kraken.example.json !config_examples/config_freqai.example.json - -config_examples/nfi_configs/*.json -docker-compose-*.yml \ No newline at end of file From d124716196df0141d3653fc276ace9c43a56c1ac Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Tue, 21 May 2024 22:32:03 +0200 Subject: [PATCH 03/33] Added unit tests --- powershell_tests/setup.Tests.ps1 | 151 +++++++++++++++++++++++++++++++ setup.ps1 | 51 +++++++---- 2 files changed, 184 insertions(+), 18 deletions(-) create mode 100644 powershell_tests/setup.Tests.ps1 diff --git a/powershell_tests/setup.Tests.ps1 b/powershell_tests/setup.Tests.ps1 new file mode 100644 index 000000000..a34facd0d --- /dev/null +++ b/powershell_tests/setup.Tests.ps1 @@ -0,0 +1,151 @@ +# 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" + } + } +} diff --git a/setup.ps1 b/setup.ps1 index 551331f3f..cc443fb20 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -14,6 +14,7 @@ function Write-Log { [string]$Message, [string]$Level = 'INFO' ) + switch ($Level) { 'INFO' { Write-Host $Message -ForegroundColor Green } 'WARNING' { Write-Host $Message -ForegroundColor Yellow } @@ -47,15 +48,20 @@ function Get-UserSelection { $_.Trim().ToUpper() } - # Convert each selection from letter to index - $indices = $selections | ForEach-Object { - if ($_ -match '^[A-Z]$') { - # Ensure the input is a single uppercase letter - [int][char]$_ - [int][char]'A' + # Convert each selection from letter to index and validate + $indices = @() + 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) { + $indices += $index + } + else { + Write-Log "Invalid input: $selection. Please enter letters within the valid range of options." -Level 'ERROR' + } } else { - Write-Log "Invalid input: $_. Please enter letters between A and Z." -Level 'ERROR' - continue + Write-Log "Invalid input: $selection. Please enter letters between A and Z." -Level 'ERROR' } } @@ -65,14 +71,14 @@ function Get-UserSelection { function Exit-Script { param ( [int]$exitCode, - [bool]$isSubShell = $true + [bool]$isSubShell = $true, + [bool]$waitForKeypress = $true ) if ($OldVirtualPath) { $env:PATH = $OldVirtualPath } - # Check if the script is exiting with an error and it's not a subshell if ($exitCode -ne 0 -and $isSubShell) { Write-Log "Script failed. Would you like to open the log file? (Y/N)" -Level 'PROMPT' $openLog = Read-Host @@ -81,25 +87,29 @@ function Exit-Script { } } - Write-Log "Press any key to exit..." - $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | Out-Null - exit $exitCode + if ($waitForKeypress) { + Write-Log "Press any key to exit..." + $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | Out-Null + } + + return $exitCode } -# Function to handle installation and conflict resolution -function Install-And-Resolve { +# Function to handle installation +function Install { param ([string]$InputPath) + if (-not $InputPath) { - Write-Log "ERROR: No input provided for installation." -Level 'ERROR' + Write-Log "No input provided for installation." -Level 'ERROR' Exit-Script -exitCode 1 } + Write-Log "Installing $InputPath..." $installCmd = if (Test-Path $InputPath) { $VenvPip + @('install', '-r', $InputPath) } else { $VenvPip + @('install', $InputPath) } $output = & $installCmd[0] $installCmd[1..$installCmd.Length] 2>&1 $output | Out-File $LogFilePath -Append if ($LASTEXITCODE -ne 0) { - Write-Log "Conflict detected, attempting to resolve..." -Level 'ERROR' - & $VenvPip[0] $VenvPip[1..$VenvPip.Length] 'check' | Out-File "conflicts.txt" + Write-Log "Conflict detected. Exiting now..." -Level 'ERROR' Exit-Script -exitCode 1 } } @@ -111,6 +121,11 @@ function Get-PythonVersionTag { return "$pythonVersion-$architecture" } +# Exit in test environment +if ($MyInvocation.InvocationName -ne $MyInvocation.MyCommand.Name) { + exit +} + # Check for admin privileges and elevate if necessary if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Log "Requesting administrative privileges..." -Level 'ERROR' @@ -179,7 +194,7 @@ foreach ($index in $selectedIndices) { $filePath = Join-Path $ProjectDir $RequirementFiles[$index] if (Test-Path $filePath) { - Install-And-Resolve $filePath + Install $filePath } else { Write-Log "Requirement file not found: $filePath" -Level 'ERROR' From 6d261b828ed77b16c8de24087d7083e12eeadd51 Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Thu, 23 May 2024 12:07:40 +0200 Subject: [PATCH 04/33] Applied fixed from last PR review --- powershell_tests/setup.Tests.ps1 | 151 ------------- setup.ps1 | 375 +++++++++++++++++++------------ tests/setup.Tests.ps1 | 183 +++++++++++++++ 3 files changed, 411 insertions(+), 298 deletions(-) delete mode 100644 powershell_tests/setup.Tests.ps1 create mode 100644 tests/setup.Tests.ps1 diff --git a/powershell_tests/setup.Tests.ps1 b/powershell_tests/setup.Tests.ps1 deleted file mode 100644 index a34facd0d..000000000 --- a/powershell_tests/setup.Tests.ps1 +++ /dev/null @@ -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" - } - } -} diff --git a/setup.ps1 b/setup.ps1 index cc443fb20..a7154fc1f 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -1,19 +1,23 @@ -# Set the console color to Matrix theme and clear the console -$host.UI.RawUI.BackgroundColor = "Black" -$host.UI.RawUI.ForegroundColor = "Green" +Import-Module -Name ".\setupFunctions.psm1" Clear-Host # Set the log file path and initialize variables -$LogFilePath = Join-Path $env:TEMP "script_log.txt" -$ProjectDir = "G:\Downloads\GitHub\freqtrade" -$RequirementFiles = @("requirements.txt", "requirements-dev.txt", "requirements-hyperopt.txt", "requirements-freqai.txt", "requirements-plot.txt") +$timestamp = Get-Date -Format "yyyyMMdd_HHmmss" +$Global:LogFilePath = Join-Path $env:TEMP "script_log_$timestamp.txt" + +$RequirementFiles = @("requirements.txt", "requirements-dev.txt", "requirements-hyperopt.txt", "requirements-freqai.txt", "requirements-freqai-rl.txt", "requirements-plot.txt") $VenvName = ".venv" +$VenvDir = Join-Path $PSScriptRoot $VenvName function Write-Log { param ( [string]$Message, [string]$Level = 'INFO' ) + + if (-not (Test-Path -Path $LogFilePath)) { + New-Item -ItemType File -Path $LogFilePath -Force | Out-Null + } switch ($Level) { 'INFO' { Write-Host $Message -ForegroundColor Green } @@ -26,60 +30,88 @@ function Write-Log { function Get-UserSelection { param ( - [string]$prompt, - [string[]]$options, - [string]$defaultChoice = 'A' + [string]$Prompt, + [string[]]$Options, + [string]$DefaultChoice = 'A', + [bool]$AllowMultipleSelections = $true ) - Write-Log "$prompt`n" -Level 'PROMPT' - for ($i = 0; $i -lt $options.Length; $i++) { - Write-Log "$([char](65 + $i)). $($options[$i])" -Level 'PROMPT' + Write-Log "$Prompt`n" -Level 'PROMPT' + for ($i = 0; $i -lt $Options.Length; $i++) { + 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' - $inputPath = Read-Host - if ([string]::IsNullOrEmpty($inputPath)) { - $inputPath = $defaultChoice + if ($AllowMultipleSelections) { + Write-Log "`nSelect one or more options by typing the corresponding letters, separated by commas." -Level 'PROMPT' + } + 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 - $inputPath = [string]$inputPath - $selections = $inputPath.Split(',') | ForEach-Object { - $_.Trim().ToUpper() + $userInput = Read-Host + if ([string]::IsNullOrEmpty($userInput)) { + $userInput = $DefaultChoice } - # Convert each selection from letter to index and validate - $indices = @() - 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) { - $indices += $index + if ($AllowMultipleSelections) { + # Ensure $userInput is treated as a string and split it by commas + $userInput = [string]$userInput + $selections = $userInput.Split(',') | ForEach-Object { + $_.Trim().ToUpper() + } + + # 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 { - 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 { - 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 { param ( - [int]$exitCode, - [bool]$isSubShell = $true, - [bool]$waitForKeypress = $true + [int]$ExitCode, + [bool]$IsSubShell = $true, + [bool]$WaitForKeypress = $true ) - if ($OldVirtualPath) { - $env:PATH = $OldVirtualPath + if ($Global: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' $openLog = Read-Host 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..." $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | Out-Null } - return $exitCode + return $ExitCode } -# Function to handle installation -function Install { - param ([string]$InputPath) +# Function to install requirements +function Install-Requirements { + param ([string]$RequirementsPath) - if (-not $InputPath) { - Write-Log "No input provided for installation." -Level 'ERROR' - Exit-Script -exitCode 1 + if (-not $RequirementsPath) { + Write-Log "No requirements path provided for installation." -Level 'ERROR' + Exit-Script -ExitCode 1 } - Write-Log "Installing $InputPath..." - $installCmd = if (Test-Path $InputPath) { $VenvPip + @('install', '-r', $InputPath) } else { $VenvPip + @('install', $InputPath) } + Write-Log "Installing requirements from $RequirementsPath..." + $installCmd = if (Test-Path $RequirementsPath) { & $VenvPip install -r $RequirementsPath } else { & $VenvPip install $RequirementsPath } $output = & $installCmd[0] $installCmd[1..$installCmd.Length] 2>&1 $output | Out-File $LogFilePath -Append if ($LASTEXITCODE -ne 0) { 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 Get-PythonVersionTag { - $pythonVersion = & python -c "import sys; print(f'cp{sys.version_info.major}{sys.version_info.minor}')" - $architecture = & python -c "import platform; print('win_amd64' if platform.machine().endswith('64') else 'win32')" - return "$pythonVersion-$architecture" -} +function Test-PythonExecutable { + param( + [string]$PythonExecutable + ) -# Exit in test environment -if ($MyInvocation.InvocationName -ne $MyInvocation.MyCommand.Name) { - exit -} - -# Check for admin privileges and elevate if necessary -if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { - Write-Log "Requesting administrative privileges..." -Level 'ERROR' - Start-Process PowerShell -ArgumentList "-File `"$PSCommandPath`"" -Verb RunAs - Exit-Script -exitCode 1 -isSubShell $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 + $pythonCmd = Get-Command $PythonExecutable -ErrorAction SilentlyContinue + if ($pythonCmd) { + $command = "$($pythonCmd.Source) --version 2>&1" + $versionOutput = Invoke-Expression $command + if ($LASTEXITCODE -eq 0) { + $version = $versionOutput | Select-String -Pattern "Python (\d+\.\d+\.\d+)" | ForEach-Object { $_.Matches.Groups[1].Value } + Write-Log "Python version $version found using executable '$PythonExecutable'." + return $true + } + else { + Write-Log "Python executable '$PythonExecutable' not working correctly." -Level 'ERROR' + return $false + } } else { - Write-Log "Requirement file not found: $filePath" -Level 'ERROR' - Exit-Script -exitCode 1 + Write-Log "Python executable '$PythonExecutable' not found." -Level 'ERROR' + return $false } } -# 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 +function Find-PythonExecutable { + + $pythonExecutables = @("python", "python3", "python3.9", "python3.10", "python3.11", "C:\Python39\python.exe", "C:\Python310\python.exe", "C:\Python311\python.exe") + + foreach ($executable in $pythonExecutables) { + if (Test-PythonExecutable -PythonExecutable $executable) { + 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 -$uiOptions = @("Yes", "No") -$installUI = Get-UserSelection -prompt "Do you want to install the freqtrade UI?" -options $uiOptions -defaultChoice 'B' + # Check for admin privileges and elevate if necessary + if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + Write-Log "Requesting administrative privileges..." + Start-Process PowerShell -ArgumentList "-File `"$PSCommandPath`"" -Verb RunAs + } -if ($uiOptions[$installUI] -eq "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 + # Log file setup + "Admin rights confirmed" | Out-File $LogFilePath -Append + "Starting the < operations..." | Out-File $LogFilePath -Append + + # 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) { - Write-Log "Failed to install freqtrade UI." -Level 'ERROR' + Write-Log "Failed to pull updates from Git." -Level 'ERROR' 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!" -Exit-Script -exitCode 0 +# Call the Main function +Main \ No newline at end of file diff --git a/tests/setup.Tests.ps1 b/tests/setup.Tests.ps1 new file mode 100644 index 000000000..cb4a0eb05 --- /dev/null +++ b/tests/setup.Tests.ps1 @@ -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 + } + } +} From 1352240ec785a4d307e490d7905f6567757569a2 Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Thu, 23 May 2024 12:08:10 +0200 Subject: [PATCH 05/33] Formatted setup.ps1 --- setup.ps1 | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/setup.ps1 b/setup.ps1 index a7154fc1f..e997f8324 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -148,26 +148,26 @@ function Install-Requirements { function Test-PythonExecutable { param( - [string]$PythonExecutable + [string]$PythonExecutable ) $pythonCmd = Get-Command $PythonExecutable -ErrorAction SilentlyContinue if ($pythonCmd) { - $command = "$($pythonCmd.Source) --version 2>&1" - $versionOutput = Invoke-Expression $command - if ($LASTEXITCODE -eq 0) { - $version = $versionOutput | Select-String -Pattern "Python (\d+\.\d+\.\d+)" | ForEach-Object { $_.Matches.Groups[1].Value } - Write-Log "Python version $version found using executable '$PythonExecutable'." - return $true - } - else { - Write-Log "Python executable '$PythonExecutable' not working correctly." -Level 'ERROR' - return $false - } + $command = "$($pythonCmd.Source) --version 2>&1" + $versionOutput = Invoke-Expression $command + if ($LASTEXITCODE -eq 0) { + $version = $versionOutput | Select-String -Pattern "Python (\d+\.\d+\.\d+)" | ForEach-Object { $_.Matches.Groups[1].Value } + Write-Log "Python version $version found using executable '$PythonExecutable'." + return $true + } + else { + Write-Log "Python executable '$PythonExecutable' not working correctly." -Level 'ERROR' + return $false + } } else { - Write-Log "Python executable '$PythonExecutable' not found." -Level 'ERROR' - return $false + Write-Log "Python executable '$PythonExecutable' not found." -Level 'ERROR' + return $false } } @@ -176,9 +176,9 @@ function Find-PythonExecutable { $pythonExecutables = @("python", "python3", "python3.9", "python3.10", "python3.11", "C:\Python39\python.exe", "C:\Python310\python.exe", "C:\Python311\python.exe") foreach ($executable in $pythonExecutables) { - if (Test-PythonExecutable -PythonExecutable $executable) { - return $executable - } + if (Test-PythonExecutable -PythonExecutable $executable) { + return $executable + } } return $null @@ -260,12 +260,12 @@ function Main { # 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 - } + 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 From 670c5d0067a49d046c81c9b7987d2919af0a8621 Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Thu, 23 May 2024 18:16:37 +0200 Subject: [PATCH 06/33] Added powershell unit tests to CI config --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bc2ac1f5..b36813a7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,6 +71,11 @@ jobs: run: | pytest --random-order --cov=freqtrade --cov=freqtrade_client --cov-config=.coveragerc + - name: Run Pester tests with code coverage + shell: pwsh + run: | + Invoke-Pester -Path tests -EnableExit + - name: Coveralls if: (runner.os == 'Linux' && matrix.python-version == '3.10' && matrix.os == 'ubuntu-22.04') env: From e29fcb45ac2909a30d090825a7c083e97bd69f49 Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Thu, 23 May 2024 18:58:09 +0200 Subject: [PATCH 07/33] Removed admin permissions, because it seems not necessary. Improved error messages. Increase speed of requirements installation by introducing new merging strategy. --- setup.ps1 | 71 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/setup.ps1 b/setup.ps1 index e997f8324..0881202f4 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -1,4 +1,3 @@ -Import-Module -Name ".\setupFunctions.psm1" Clear-Host # Set the log file path and initialize variables @@ -103,7 +102,6 @@ function Get-UserSelection { function Exit-Script { param ( [int]$ExitCode, - [bool]$IsSubShell = $true, [bool]$WaitForKeypress = $true ) @@ -111,15 +109,14 @@ function Exit-Script { $env:PATH = $Global:OldVirtualPath } - if ($ExitCode -ne 0 -and $IsSubShell) { + if ($ExitCode -ne 0) { Write-Log "Script failed. Would you like to open the log file? (Y/N)" -Level 'PROMPT' $openLog = Read-Host if ($openLog -eq 'Y' -or $openLog -eq 'y') { Start-Process notepad.exe -ArgumentList $LogFilePath } } - - if ($WaitForKeypress) { + elseif ($WaitForKeypress) { Write-Log "Press any key to exit..." $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | Out-Null } @@ -183,6 +180,26 @@ function Find-PythonExecutable { return $null } + +# Function to get the list of requirements from a file +function Get-Requirements($file) { + $requirements = @() + $lines = Get-Content $file + foreach ($line in $lines) { + if ($line.StartsWith("-r ")) { + $nestedFile = $line.Substring(3).Trim() + $nestedFilePath = Join-Path (Split-Path $file -Parent) $nestedFile + if (Test-Path $nestedFilePath) { + $requirements += Get-Requirements $nestedFilePath + } + } + elseif (-not $line.StartsWith("#")) { + $requirements += $line + } + } + return $requirements +} + function Main { # Exit on lower versions than Python 3.9 or when Python executable not found $pythonExecutable = Find-PythonExecutable @@ -191,14 +208,6 @@ function Main { Exit 1 } - # Check for admin privileges and elevate if necessary - if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { - Write-Log "Requesting administrative privileges..." - Start-Process PowerShell -ArgumentList "-File `"$PSCommandPath`"" -Verb RunAs - } - - # Log file setup - "Admin rights confirmed" | Out-File $LogFilePath -Append "Starting the < operations..." | Out-File $LogFilePath -Append # Navigate to the project directory @@ -211,7 +220,7 @@ function Main { # 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" + & $pythonExecutable -m venv "$VenvName" if (-Not (Test-Path $VenvPython)) { Write-Log "Failed to create virtual environment." -Level 'ERROR' Exit-Script -exitCode 1 @@ -244,10 +253,10 @@ function Main { $selectedRequirementFiles = @() foreach ($index in $selectedIndices) { if ($index -lt 0 -or $index -ge $RequirementFiles.Length) { - Write-Log "Invalid selection index: $index" -Level 'ERROR' + Write-Log "Invalid selection. Please try again using the allowed letters." -Level 'ERROR' continue } - + $filePath = Join-Path $PSScriptRoot $RequirementFiles[$index] if (Test-Path $filePath) { $selectedRequirementFiles += $filePath @@ -258,14 +267,34 @@ function Main { } } - # Install the selected requirement files together + # Merge the contents of the selected requirement files if ($selectedRequirementFiles.Count -gt 0) { - Write-Log "Installing selected requirement files..." - $selectedRequirementFiles | ForEach-Object { & $VenvPython -m pip install -r $_ --quiet } + Write-Log "Merging selected requirement files..." + $mergedDependencies = @() + foreach ($file in $selectedRequirementFiles) { + $mergedDependencies += Get-Requirements $file + } + # Avoid duplicate dependencies + $mergedDependencies = $mergedDependencies | Select-Object -Unique + + # Create a temporary directory + $tempDir = Join-Path $env:TEMP "freqtrade_requirements" + New-Item -ItemType Directory -Path $tempDir -Force | Out-Null + + # Create a temporary file with the merged dependencies + $tempFile = Join-Path $tempDir "requirements.txt" + $mergedDependencies | Out-File $tempFile + + # Install the merged dependencies + Write-Log "Installing merged dependencies..." + & $VenvPython -m pip install -r $tempFile if ($LASTEXITCODE -ne 0) { - Write-Log "Failed to install selected requirement files. Exiting now..." -Level 'ERROR' + Write-Log "Failed to install merged dependencies. Exiting now..." -Level 'ERROR' Exit-Script -exitCode 1 } + + # Remove the temporary directory + Remove-Item $tempDir -Recurse -Force } # Install freqtrade from setup using the virtual environment's Python @@ -301,7 +330,7 @@ function Main { Write-Log "Invalid selection for freqtrade UI installation." -Level 'ERROR' Exit-Script -exitCode 1 } - + Write-Log "Update complete!" Exit-Script -exitCode 0 } From b9fd8d2ee7c658b2cd39f3aa967ee35f38ee5dd3 Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Thu, 23 May 2024 19:02:53 +0200 Subject: [PATCH 08/33] Fixed log level of one log statement --- setup.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.ps1 b/setup.ps1 index 0881202f4..94452beaf 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -225,7 +225,7 @@ function Main { Write-Log "Failed to create virtual environment." -Level 'ERROR' Exit-Script -exitCode 1 } - Write-Log "Virtual environment created successfully." -Level 'ERROR' + Write-Log "Virtual environment created successfully." } # Activate the virtual environment From 6174a49aa591b8167c1bc846633027bca164e4b0 Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Sun, 26 May 2024 15:54:52 +0200 Subject: [PATCH 09/33] Implemented the changes to pass the review, implemented consistent variable naming, adjusted unit tests --- .github/workflows/ci.yml | 14 ++- setup.ps1 | 232 ++++++++++++++------------------------- tests/setup.Tests.ps1 | 131 ++++++++++------------ 3 files changed, 148 insertions(+), 229 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b36813a7c..35770345d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,11 +71,6 @@ jobs: run: | pytest --random-order --cov=freqtrade --cov=freqtrade_client --cov-config=.coveragerc - - name: Run Pester tests with code coverage - shell: pwsh - run: | - Invoke-Pester -Path tests -EnableExit - - name: Coveralls if: (runner.os == 'Linux' && matrix.python-version == '3.10' && matrix.os == 'ubuntu-22.04') env: @@ -323,6 +318,15 @@ jobs: run: | mypy freqtrade scripts tests + - name: Run Pester tests (PowerShell) + run: | + Write-host $PSVersionTable.PSVersion.Major $PSVersionTable.PSRemotingProtocolVersion.Minor + Set-PSRepository psgallery -InstallationPolicy trusted + Install-Module -Name Pester -RequiredVersion 5.3.1 -Confirm:$false -Force + Invoke-Pester -Path "tests" + if ($Error[0].Fullyqualifiederrorid -eq 'PesterAssertionFailed') {exit 1} + shell: powershell + - name: Discord notification uses: rjstone/discord-webhook-notify@v1 if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) diff --git a/setup.ps1 b/setup.ps1 index 94452beaf..2431b969b 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -1,8 +1,7 @@ Clear-Host -# Set the log file path and initialize variables -$timestamp = Get-Date -Format "yyyyMMdd_HHmmss" -$Global:LogFilePath = Join-Path $env:TEMP "script_log_$timestamp.txt" +$Timestamp = Get-Date -Format "yyyyMMdd_HHmmss" +$Global:LogFilePath = Join-Path $env:TEMP "script_log_$Timestamp.txt" $RequirementFiles = @("requirements.txt", "requirements-dev.txt", "requirements-hyperopt.txt", "requirements-freqai.txt", "requirements-freqai-rl.txt", "requirements-plot.txt") $VenvName = ".venv" @@ -24,6 +23,7 @@ function Write-Log { 'ERROR' { Write-Host $Message -ForegroundColor Red } 'PROMPT' { Write-Host $Message -ForegroundColor Cyan } } + "${Level}: $Message" | Out-File $LogFilePath -Append } @@ -36,8 +36,8 @@ function Get-UserSelection { ) Write-Log "$Prompt`n" -Level 'PROMPT' - for ($i = 0; $i -lt $Options.Length; $i++) { - Write-Log "$([char](65 + $i)). $($Options[$i])" -Level 'PROMPT' + for ($I = 0; $I -lt $Options.Length; $I++) { + Write-Log "$([char](65 + $I)). $($Options[$I])" -Level 'PROMPT' } if ($AllowMultipleSelections) { @@ -47,53 +47,53 @@ function Get-UserSelection { Write-Log "`nSelect an option by typing the corresponding letter." -Level 'PROMPT' } - $userInput = Read-Host - if ([string]::IsNullOrEmpty($userInput)) { - $userInput = $DefaultChoice + [string]$UserInput = Read-Host + if ([string]::IsNullOrEmpty($UserInput)) { + $UserInput = $DefaultChoice } if ($AllowMultipleSelections) { - # Ensure $userInput is treated as a string and split it by commas - $userInput = [string]$userInput - $selections = $userInput.Split(',') | ForEach-Object { + $Selections = $UserInput.Split(',') | ForEach-Object { $_.Trim().ToUpper() } - # 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 + $ErrorMessage = "Invalid input: $Selection. Please enter letters within the valid range of options." + + # 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' + Write-Log $ErrorMessage -Level 'ERROR' return -1 } } else { - Write-Log "Invalid input: $selection. Please enter letters between A and Z." -Level 'ERROR' + Write-Log $ErrorMessage -Level 'ERROR' return -1 } } - return $selectedIndices + 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 + # 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' + Write-Log "Invalid input: $UserInput. Please enter a letter within the valid range of options." -Level 'ERROR' return -1 } } else { - Write-Log "Invalid input: $userInput. Please enter a letter between A and Z." -Level 'ERROR' + Write-Log "Invalid input: $UserInput. Please enter a letter between A and Z." -Level 'ERROR' return -1 } } @@ -105,9 +105,8 @@ function Exit-Script { [bool]$WaitForKeypress = $true ) - if ($Global:OldVirtualPath) { - $env:PATH = $Global:OldVirtualPath - } + # Disable virtual environment + deactivate if ($ExitCode -ne 0) { Write-Log "Script failed. Would you like to open the log file? (Y/N)" -Level 'PROMPT' @@ -124,37 +123,18 @@ function Exit-Script { return $ExitCode } -# Function to install requirements -function Install-Requirements { - param ([string]$RequirementsPath) - - if (-not $RequirementsPath) { - Write-Log "No requirements path provided for installation." -Level 'ERROR' - Exit-Script -ExitCode 1 - } - - Write-Log "Installing requirements from $RequirementsPath..." - $installCmd = if (Test-Path $RequirementsPath) { & $VenvPip install -r $RequirementsPath } else { & $VenvPip install $RequirementsPath } - $output = & $installCmd[0] $installCmd[1..$installCmd.Length] 2>&1 - $output | Out-File $LogFilePath -Append - if ($LASTEXITCODE -ne 0) { - Write-Log "Conflict detected. Exiting now..." -Level 'ERROR' - Exit-Script -ExitCode 1 - } -} - function Test-PythonExecutable { param( [string]$PythonExecutable ) - $pythonCmd = Get-Command $PythonExecutable -ErrorAction SilentlyContinue - if ($pythonCmd) { - $command = "$($pythonCmd.Source) --version 2>&1" - $versionOutput = Invoke-Expression $command + $PythonCmd = Get-Command $PythonExecutable -ErrorAction SilentlyContinue + if ($PythonCmd) { + $Command = "$($PythonCmd.Source) --version 2>&1" + $VersionOutput = Invoke-Expression $Command if ($LASTEXITCODE -eq 0) { - $version = $versionOutput | Select-String -Pattern "Python (\d+\.\d+\.\d+)" | ForEach-Object { $_.Matches.Groups[1].Value } - Write-Log "Python version $version found using executable '$PythonExecutable'." + $Version = $VersionOutput | Select-String -Pattern "Python (\d+\.\d+\.\d+)" | ForEach-Object { $_.Matches.Groups[1].Value } + Write-Log "Python version $Version found using executable '$PythonExecutable'." return $true } else { @@ -169,58 +149,35 @@ function Test-PythonExecutable { } function Find-PythonExecutable { + $PythonExecutables = @("python", "python3.12", "python3.11", "python3.10", "python3.9", "python3", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python312\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python311\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python310\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python39\python.exe", "C:\Python311\python.exe", "C:\Python310\python.exe", "C:\Python39\python.exe") - $pythonExecutables = @("python", "python3", "python3.9", "python3.10", "python3.11", "C:\Python39\python.exe", "C:\Python310\python.exe", "C:\Python311\python.exe") - foreach ($executable in $pythonExecutables) { - if (Test-PythonExecutable -PythonExecutable $executable) { - return $executable + foreach ($Executable in $PythonExecutables) { + if (Test-PythonExecutable -PythonExecutable $Executable) { + return $Executable } } return $null } - -# Function to get the list of requirements from a file -function Get-Requirements($file) { - $requirements = @() - $lines = Get-Content $file - foreach ($line in $lines) { - if ($line.StartsWith("-r ")) { - $nestedFile = $line.Substring(3).Trim() - $nestedFilePath = Join-Path (Split-Path $file -Parent) $nestedFile - if (Test-Path $nestedFilePath) { - $requirements += Get-Requirements $nestedFilePath - } - } - elseif (-not $line.StartsWith("#")) { - $requirements += $line - } - } - return $requirements -} - function Main { + "Starting the operations..." | Out-File $LogFilePath -Append + "Current directory: $(Get-Location)" | Out-File $LogFilePath -Append + # Exit on lower versions than Python 3.9 or when Python executable not found - $pythonExecutable = Find-PythonExecutable - if ($null -eq $pythonExecutable) { + $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 } - "Starting the < operations..." | Out-File $LogFilePath -Append - - # 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" + $VenvPython = "$VenvDir\Scripts\Activate.ps1" # 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' - & $pythonExecutable -m venv "$VenvName" + & $PythonExecutable -m venv $VenvName if (-Not (Test-Path $VenvPython)) { Write-Log "Failed to create virtual environment." -Level 'ERROR' Exit-Script -exitCode 1 @@ -228,106 +185,79 @@ function Main { Write-Log "Virtual environment created successfully." } - # 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) { - Write-Log "Failed to pull updates from Git." -Level 'ERROR' - Exit-Script -exitCode 1 + # Pull latest updates only if the repository state is not dirty + Write-Log "Checking if the repository is clean..." + $status = & "C:\Program Files\Git\cmd\git.exe" status --porcelain + if ($status) { + Write-Log "Repository is dirty. Skipping pull." } + else { + Write-Log "Pulling latest updates..." + & "C:\Program Files\Git\cmd\git.exe" 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 + } + } + 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 + python -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' + $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. Please try again using the allowed letters." -Level 'ERROR' - continue - } - - $filePath = Join-Path $PSScriptRoot $RequirementFiles[$index] - if (Test-Path $filePath) { - $selectedRequirementFiles += $filePath + $SelectedRequirementFiles = @() + $PipInstallArguments = "" + foreach ($Index in $SelectedIndices) { + $FilePath = Join-Path $PSScriptRoot $RequirementFiles[$Index] + if (Test-Path $FilePath) { + $SelectedRequirementFiles += $FilePath + $PipInstallArguments += " -r $FilePath" } else { - Write-Log "Requirement file not found: $filePath" -Level 'ERROR' + Write-Log "Requirement file not found: $FilePath" -Level 'ERROR' Exit-Script -exitCode 1 } } - - # Merge the contents of the selected requirement files - if ($selectedRequirementFiles.Count -gt 0) { - Write-Log "Merging selected requirement files..." - $mergedDependencies = @() - foreach ($file in $selectedRequirementFiles) { - $mergedDependencies += Get-Requirements $file - } - # Avoid duplicate dependencies - $mergedDependencies = $mergedDependencies | Select-Object -Unique - - # Create a temporary directory - $tempDir = Join-Path $env:TEMP "freqtrade_requirements" - New-Item -ItemType Directory -Path $tempDir -Force | Out-Null - - # Create a temporary file with the merged dependencies - $tempFile = Join-Path $tempDir "requirements.txt" - $mergedDependencies | Out-File $tempFile - - # Install the merged dependencies - Write-Log "Installing merged dependencies..." - & $VenvPython -m pip install -r $tempFile - if ($LASTEXITCODE -ne 0) { - Write-Log "Failed to install merged dependencies. Exiting now..." -Level 'ERROR' - Exit-Script -exitCode 1 - } - - # Remove the temporary directory - Remove-Item $tempDir -Recurse -Force + if ($PipInstallArguments -ne "") { + python -m pip install $PipInstallArguments } # 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 + python -m pip install -e . | 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 + $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) { + 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 + python '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) { + elseif ($InstallUi -eq 1) { # User selected "No" # Skip installing freqtrade UI Write-Log "Skipping freqtrade UI installation." } else { - # Invalid selection + # Invalid Selection # Handle the error case - Write-Log "Invalid selection for freqtrade UI installation." -Level 'ERROR' + Write-Log "Invalid Selection for freqtrade UI installation." -Level 'ERROR' Exit-Script -exitCode 1 } diff --git a/tests/setup.Tests.ps1 b/tests/setup.Tests.ps1 index cb4a0eb05..714952a53 100644 --- a/tests/setup.Tests.ps1 +++ b/tests/setup.Tests.ps1 @@ -1,8 +1,8 @@ # 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 +$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 @@ -10,19 +10,20 @@ 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" - + # Setup variables + $SetupScriptPath = Join-Path $PSScriptRoot "..\setup.ps1" + $Global:LogFilePath = Join-Path $env:TEMP "script_log.txt" + # Check if the setup script exists - if (-Not (Test-Path -Path $setupScriptPath)) { - Write-Host "Error: setup.ps1 script not found at path: $setupScriptPath" + 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 + . $SetupScriptPath } Context "Write-Log Tests" -Tag "Unit" { @@ -30,104 +31,104 @@ Describe "Setup and Tests" { 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" - } + + $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" + $Global:LogFilePath | Should -Exist - $logContent = Get-Content $Global:LogFilePath - $logContent | Should -Contain "ERROR: Test Error Message" + $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") + $Options = @("Option1", "Option2", "Option3") Mock Read-Host { return "B" } - $result = Get-UserSelection -prompt "Select an option" -options $options - $result | Should -Be 1 + $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") + $Options = @("Option1", "Option2", "Option3") Mock Read-Host { return "" } - $result = Get-UserSelection -prompt "Select an option" -options $options -defaultChoice "C" - $result | Should -Be 2 + $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") + $Options = @("Option1", "Option2", "Option3") Mock Read-Host { return "X" } - $result = Get-UserSelection -prompt "Select an option" -options $options - $result | Should -Be -1 + $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") + $Options = @("Option1", "Option2", "Option3") Mock Read-Host { return "D" } - $result = Get-UserSelection -prompt "Select an option" -options $options - $result | Should -Be -1 + $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") + $Options = @("Option1", "Option2", "Option3") Mock Read-Host { return "1" } - $result = Get-UserSelection -prompt "Select an option" -options $options - $result | Should -Be -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 + $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) + $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) + $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) + $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 + $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) + $Options = @("Option1", "Option2", "Option3") + $Indices = Get-UserSelection -prompt "Select options" -options $Options + $Indices | Should -Be @(0, 1, 2) } } } @@ -137,47 +138,31 @@ Describe "Setup and Tests" { 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 + $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 + 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' + $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 + $Result = Find-PythonExecutable + $Result | Should -Be $null } } } From bad1d83cee5390cbd0d93e7bbf1f1ec6555034ee Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Tue, 28 May 2024 13:10:56 +0200 Subject: [PATCH 10/33] Fixed some bugs, added unit tests --- setup.ps1 | 94 ++++++++++++++++++++++++++----------------- tests/setup.Tests.ps1 | 14 +++++++ 2 files changed, 70 insertions(+), 38 deletions(-) diff --git a/setup.ps1 b/setup.ps1 index 2431b969b..c72433468 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -51,15 +51,10 @@ function Get-UserSelection { if ([string]::IsNullOrEmpty($UserInput)) { $UserInput = $DefaultChoice } - - if ($AllowMultipleSelections) { - $Selections = $UserInput.Split(',') | ForEach-Object { - $_.Trim().ToUpper() - } - - $ErrorMessage = "Invalid input: $Selection. Please enter letters within the valid range of options." + $UserInput = $UserInput.ToUpper() - # Convert each Selection from letter to Index and validate + if ($AllowMultipleSelections) { + $Selections = $UserInput.Split(',') | ForEach-Object { $_.Trim() } $SelectedIndices = @() foreach ($Selection in $Selections) { if ($Selection -match '^[A-Z]$') { @@ -68,20 +63,18 @@ function Get-UserSelection { $SelectedIndices += $Index } else { - Write-Log $ErrorMessage -Level 'ERROR' + Write-Log "Invalid input: $Selection. Please enter letters within the valid range of options." -Level 'ERROR' return -1 } } else { - Write-Log $ErrorMessage -Level 'ERROR' + Write-Log "Invalid input: $Selection. Please enter a letter 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) { @@ -98,16 +91,12 @@ function Get-UserSelection { } } } - function Exit-Script { param ( [int]$ExitCode, [bool]$WaitForKeypress = $true ) - # Disable virtual environment - deactivate - if ($ExitCode -ne 0) { Write-Log "Script failed. Would you like to open the log file? (Y/N)" -Level 'PROMPT' $openLog = Read-Host @@ -128,10 +117,19 @@ function Test-PythonExecutable { [string]$PythonExecutable ) + $DeactivateVenv = Join-Path $VenvDir "Scripts\Deactivate.bat" + if (Test-Path $DeactivateVenv) { + Write-Host "Deactivating virtual environment..." + & $DeactivateVenv + Write-Host "Virtual environment deactivated." + } + else { + Write-Host "Deactivation script not found: $DeactivateVenv" + } + $PythonCmd = Get-Command $PythonExecutable -ErrorAction SilentlyContinue if ($PythonCmd) { - $Command = "$($PythonCmd.Source) --version 2>&1" - $VersionOutput = Invoke-Expression $Command + $VersionOutput = & $PythonCmd.Source --version 2>&1 if ($LASTEXITCODE -eq 0) { $Version = $VersionOutput | Select-String -Pattern "Python (\d+\.\d+\.\d+)" | ForEach-Object { $_.Matches.Groups[1].Value } Write-Log "Python version $Version found using executable '$PythonExecutable'." @@ -172,39 +170,59 @@ function Main { } # Define the path to the Python executable in the virtual environment - $VenvPython = "$VenvDir\Scripts\Activate.ps1" + $ActivateVenv = "$VenvDir\Scripts\Activate.ps1" # Check if the virtual environment exists, if not, create it - if (-Not (Test-Path $VenvPython)) { + if (-Not (Test-Path $ActivateVenv)) { Write-Log "Virtual environment not found. Creating virtual environment..." -Level 'ERROR' - & $PythonExecutable -m venv $VenvName - if (-Not (Test-Path $VenvPython)) { + & $PythonExecutable -m venv $VenvName 2>&1 | Out-File $LogFilePath -Append + if ($LASTEXITCODE -ne 0) { Write-Log "Failed to create virtual environment." -Level 'ERROR' Exit-Script -exitCode 1 } - Write-Log "Virtual environment created successfully." + else { + Write-Log "Virtual environment created." + } } + # Activate the virtual environment and check if it was successful + Write-Log "Virtual environment found. Activating virtual environment..." + & $ActivateVenv 2>&1 | Out-File $LogFilePath -Append + # Check if virtual environment is activated + if ($env:VIRTUAL_ENV) { + Write-Host "Virtual environment is activated at: $($env:VIRTUAL_ENV)" + } + else { + Write-Log "Failed to activate virtual environment." -Level 'ERROR' + Exit-Script -exitCode 1 + } + + # Ensure pip + python -m ensurepip --default-pip 2>&1 | Out-File $LogFilePath -Append + # Pull latest updates only if the repository state is not dirty Write-Log "Checking if the repository is clean..." - $status = & "C:\Program Files\Git\cmd\git.exe" status --porcelain - if ($status) { + $Status = & "C:\Program Files\Git\cmd\git.exe" status --porcelain + if ($Status) { Write-Log "Repository is dirty. Skipping pull." } else { Write-Log "Pulling latest updates..." - & "C:\Program Files\Git\cmd\git.exe" pull | Out-File $LogFilePath -Append 2>&1 + & "C:\Program Files\Git\cmd\git.exe" pull 2>&1 | Out-File $LogFilePath -Append if ($LASTEXITCODE -ne 0) { Write-Log "Failed to pull updates from Git." -Level 'ERROR' 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..." - python -m pip install --find-links=build_helpers\ --prefer-binary TA-Lib | Out-File $LogFilePath -Append 2>&1 + python -m pip install --find-links=build_helpers\ --prefer-binary TA-Lib 2>&1 | Out-File $LogFilePath -Append + if ($LASTEXITCODE -ne 0) { + Write-Log "Failed to install TA-Lib." -Level 'ERROR' + Exit-Script -exitCode 1 + } } # Present options for requirement files @@ -212,25 +230,25 @@ function Main { # Cache the selected requirement files $SelectedRequirementFiles = @() - $PipInstallArguments = "" + $PipInstallArguments = @() foreach ($Index in $SelectedIndices) { - $FilePath = Join-Path $PSScriptRoot $RequirementFiles[$Index] - if (Test-Path $FilePath) { - $SelectedRequirementFiles += $FilePath - $PipInstallArguments += " -r $FilePath" + $RelativePath = $RequirementFiles[$Index] + if (Test-Path $RelativePath) { + $SelectedRequirementFiles += $RelativePath + $PipInstallArguments += "-r", $RelativePath # Add each flag and path as separate elements } else { - Write-Log "Requirement file not found: $FilePath" -Level 'ERROR' + Write-Log "Requirement file not found: $RelativePath" -Level 'ERROR' Exit-Script -exitCode 1 } } - if ($PipInstallArguments -ne "") { - python -m pip install $PipInstallArguments + if ($PipInstallArguments.Count -ne 0) { + & pip install @PipInstallArguments # Use array splatting to pass arguments correctly } # Install freqtrade from setup using the virtual environment's Python Write-Log "Installing freqtrade from setup..." - python -m pip install -e . | Out-File $LogFilePath -Append 2>&1 + pip install -e . 2>&1 | Out-File $LogFilePath -Append if ($LASTEXITCODE -ne 0) { Write-Log "Failed to install freqtrade." -Level 'ERROR' Exit-Script -exitCode 1 @@ -243,7 +261,7 @@ function Main { # User selected "Yes" # Install freqtrade UI using the virtual environment's install-ui command Write-Log "Installing freqtrade UI..." - python 'freqtrade', 'install-ui' | Out-File $LogFilePath -Append 2>&1 + python freqtrade install-ui 2>&1 | Out-File $LogFilePath -Append if ($LASTEXITCODE -ne 0) { Write-Log "Failed to install freqtrade UI." -Level 'ERROR' Exit-Script -exitCode 1 diff --git a/tests/setup.Tests.ps1 b/tests/setup.Tests.ps1 index 714952a53..aaeefe230 100644 --- a/tests/setup.Tests.ps1 +++ b/tests/setup.Tests.ps1 @@ -57,6 +57,13 @@ Describe "Setup and Tests" { $Result | Should -Be 1 } + 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 "" } @@ -97,6 +104,13 @@ Describe "Setup and Tests" { 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 handle valid input without whitespace correctly" { Mock Read-Host { return "A,B,C" } $Options = @("Option1", "Option2", "Option3") $Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "A" From 074434e83ca1be1be76372569b0956127291088c Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Thu, 30 May 2024 10:08:41 +0200 Subject: [PATCH 11/33] Renamed freqtrade UI to freqUI --- setup.ps1 | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/setup.ps1 b/setup.ps1 index c72433468..a8ae6c47f 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -16,7 +16,7 @@ function Write-Log { if (-not (Test-Path -Path $LogFilePath)) { New-Item -ItemType File -Path $LogFilePath -Force | Out-Null } - + switch ($Level) { 'INFO' { Write-Host $Message -ForegroundColor Green } 'WARNING' { Write-Host $Message -ForegroundColor Yellow } @@ -34,19 +34,19 @@ function Get-UserSelection { [string]$DefaultChoice = 'A', [bool]$AllowMultipleSelections = $true ) - + Write-Log "$Prompt`n" -Level 'PROMPT' for ($I = 0; $I -lt $Options.Length; $I++) { Write-Log "$([char](65 + $I)). $($Options[$I])" -Level 'PROMPT' } - + if ($AllowMultipleSelections) { Write-Log "`nSelect one or more options by typing the corresponding letters, separated by commas." -Level 'PROMPT' } else { Write-Log "`nSelect an option by typing the corresponding letter." -Level 'PROMPT' } - + [string]$UserInput = Read-Host if ([string]::IsNullOrEmpty($UserInput)) { $UserInput = $DefaultChoice @@ -125,7 +125,7 @@ function Test-PythonExecutable { } else { Write-Host "Deactivation script not found: $DeactivateVenv" - } + } $PythonCmd = Get-Command $PythonExecutable -ErrorAction SilentlyContinue if ($PythonCmd) { @@ -149,7 +149,7 @@ function Test-PythonExecutable { function Find-PythonExecutable { $PythonExecutables = @("python", "python3.12", "python3.11", "python3.10", "python3.9", "python3", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python312\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python311\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python310\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python39\python.exe", "C:\Python311\python.exe", "C:\Python310\python.exe", "C:\Python39\python.exe") - + foreach ($Executable in $PythonExecutables) { if (Test-PythonExecutable -PythonExecutable $Executable) { return $Executable @@ -196,7 +196,7 @@ function Main { Write-Log "Failed to activate virtual environment." -Level 'ERROR' Exit-Script -exitCode 1 } - + # Ensure pip python -m ensurepip --default-pip 2>&1 | Out-File $LogFilePath -Append @@ -255,33 +255,33 @@ function Main { } $UiOptions = @("Yes", "No") - $InstallUi = Get-UserSelection -prompt "Do you want to install the freqtrade UI?" -options $UiOptions -defaultChoice 'B' -allowMultipleSelections $false + $InstallUi = Get-UserSelection -prompt "Do you want to install the freqUI?" -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..." + # Install freqUI using the virtual environment's install-ui command + Write-Log "Installing freqUI..." python freqtrade install-ui 2>&1 | Out-File $LogFilePath -Append if ($LASTEXITCODE -ne 0) { - Write-Log "Failed to install freqtrade UI." -Level 'ERROR' + Write-Log "Failed to install freqUI." -Level 'ERROR' Exit-Script -exitCode 1 } } elseif ($InstallUi -eq 1) { # User selected "No" - # Skip installing freqtrade UI - Write-Log "Skipping freqtrade UI installation." + # Skip installing freqUI + Write-Log "Skipping freqUI installation." } else { # Invalid Selection # Handle the error case - Write-Log "Invalid Selection for freqtrade UI installation." -Level 'ERROR' + Write-Log "Invalid Selection for freqUI installation." -Level 'ERROR' Exit-Script -exitCode 1 } - + Write-Log "Update complete!" Exit-Script -exitCode 0 } # Call the Main function -Main \ No newline at end of file +Main From c9d67999ee33896090fc5de303e3388516b36583 Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Thu, 30 May 2024 10:28:53 +0200 Subject: [PATCH 12/33] Updated Windows installation doc, refined logging --- docs/windows_installation.md | 14 ++++++++++++++ setup.ps1 | 10 +++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/docs/windows_installation.md b/docs/windows_installation.md index d513c0af5..edba2c065 100644 --- a/docs/windows_installation.md +++ b/docs/windows_installation.md @@ -5,6 +5,20 @@ We **strongly** recommend that Windows users use [Docker](docker_quickstart.md) If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work. Otherwise, please follow the instructions below. +## Install freqtrade automatically + +### Using Invoke-WebRequest + +```powershell +Invoke-WebRequest -Uri "https://raw.githubusercontent.com/freqtrade/freqtrade/stable/setup.ps1" -UseBasicParsing | Invoke-Expression +``` + +### Or using curl + +```powershell +curl -sSL "https://raw.githubusercontent.com/freqtrade/freqtrade/stable/setup.ps1" | powershell -c - +``` + ## Install freqtrade manually !!! Note "64bit Python version" diff --git a/setup.ps1 b/setup.ps1 index a8ae6c47f..aaf3a825b 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -119,12 +119,12 @@ function Test-PythonExecutable { $DeactivateVenv = Join-Path $VenvDir "Scripts\Deactivate.bat" if (Test-Path $DeactivateVenv) { - Write-Host "Deactivating virtual environment..." + Write-Host "Deactivating virtual environment..." 2>&1 | Out-File $LogFilePath -Append & $DeactivateVenv - Write-Host "Virtual environment deactivated." + Write-Host "Virtual environment deactivated." 2>&1 | Out-File $LogFilePath -Append } else { - Write-Host "Deactivation script not found: $DeactivateVenv" + Write-Host "Deactivation script not found: $DeactivateVenv" 2>&1 | Out-File $LogFilePath -Append } $PythonCmd = Get-Command $PythonExecutable -ErrorAction SilentlyContinue @@ -165,7 +165,7 @@ 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." + Write-Log "No suitable Python executable found. Please ensure that Python 3.9 or higher is installed and available in the system PATH." -Level 'ERROR' Exit 1 } @@ -190,7 +190,7 @@ function Main { & $ActivateVenv 2>&1 | Out-File $LogFilePath -Append # Check if virtual environment is activated if ($env:VIRTUAL_ENV) { - Write-Host "Virtual environment is activated at: $($env:VIRTUAL_ENV)" + Write-Log "Virtual environment is activated at: $($env:VIRTUAL_ENV)" } else { Write-Log "Failed to activate virtual environment." -Level 'ERROR' From 055293db7c42a9aabeeeb98caaa68ce26d6e9b5b Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Thu, 30 May 2024 17:06:34 +0200 Subject: [PATCH 13/33] Updated Windows installation doc --- docs/windows_installation.md | 45 ++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/docs/windows_installation.md b/docs/windows_installation.md index edba2c065..0d5c80ae5 100644 --- a/docs/windows_installation.md +++ b/docs/windows_installation.md @@ -2,22 +2,24 @@ We **strongly** recommend that Windows users use [Docker](docker_quickstart.md) as this will work much easier and smoother (also more secure). -If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work. +If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work.\ Otherwise, please follow the instructions below. +--- + +First of all, make sure you get the whole repository by running: + +git clone + +Now, choose to install freqtrade automatically (recommended) or manually and follow the next instructions. + ## Install freqtrade automatically -### Using Invoke-WebRequest +### Run these commands -```powershell -Invoke-WebRequest -Uri "https://raw.githubusercontent.com/freqtrade/freqtrade/stable/setup.ps1" -UseBasicParsing | Invoke-Expression -``` - -### Or using curl - -```powershell -curl -sSL "https://raw.githubusercontent.com/freqtrade/freqtrade/stable/setup.ps1" | powershell -c - -``` +Set-ExecutionPolicy -ExecutionPolicy Bypass +cd freqtrade +. .\setup.ps1 ## Install freqtrade manually @@ -28,13 +30,7 @@ curl -sSL "https://raw.githubusercontent.com/freqtrade/freqtrade/stable/setup.ps !!! Hint Using the [Anaconda Distribution](https://www.anaconda.com/distribution/) under Windows can greatly help with installation problems. Check out the [Anaconda installation section](installation.md#installation-with-conda) in the documentation for more information. -### 1. Clone the git repository - -```bash -git clone https://github.com/freqtrade/freqtrade.git -``` - -### 2. Install ta-lib +### Install ta-lib Install ta-lib according to the [ta-lib documentation](https://github.com/TA-Lib/ta-lib-python#windows). @@ -43,17 +39,19 @@ These Wheels are also used by CI running on windows, and are therefore tested to Other versions must be downloaded from the above link. -``` powershell + powershell cd \path\freqtrade python -m venv .venv .venv\Scripts\activate.ps1 + # optionally install ta-lib from wheel + # Eventually adjust the below filename to match the downloaded wheel + pip install --find-links build_helpers\ TA-Lib -U pip install -r requirements.txt pip install -e . freqtrade -``` !!! Note "Use Powershell" The above installation script assumes you're using powershell on a 64bit windows. @@ -61,14 +59,11 @@ freqtrade ### Error during installation on Windows -``` bash -error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools -``` + bash +error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": Unfortunately, many packages requiring compilation don't provide a pre-built wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use. You can download the Visual C++ build tools from [here](https://visualstudio.microsoft.com/visual-cpp-build-tools/) and install "Desktop development with C++" in it's default configuration. Unfortunately, this is a heavy download / dependency so you might want to consider WSL2 or [docker compose](docker_quickstart.md) first. ![Windows installation](assets/windows_install.png) - ---- From 9c7bc374bc3e250991a0202de7f78870848d6297 Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Thu, 30 May 2024 19:45:41 +0200 Subject: [PATCH 14/33] Fixed the doc --- docs/windows_installation.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/windows_installation.md b/docs/windows_installation.md index 0d5c80ae5..993fd5eb1 100644 --- a/docs/windows_installation.md +++ b/docs/windows_installation.md @@ -17,9 +17,11 @@ Now, choose to install freqtrade automatically (recommended) or manually and fol ### Run these commands +```powershell Set-ExecutionPolicy -ExecutionPolicy Bypass cd freqtrade . .\setup.ps1 +``` ## Install freqtrade manually @@ -39,19 +41,17 @@ These Wheels are also used by CI running on windows, and are therefore tested to Other versions must be downloaded from the above link. - powershell +``` powershell cd \path\freqtrade python -m venv .venv .venv\Scripts\activate.ps1 - # optionally install ta-lib from wheel - # Eventually adjust the below filename to match the downloaded wheel - pip install --find-links build_helpers\ TA-Lib -U pip install -r requirements.txt pip install -e . freqtrade +``` !!! Note "Use Powershell" The above installation script assumes you're using powershell on a 64bit windows. @@ -59,11 +59,14 @@ freqtrade ### Error during installation on Windows - bash -error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": +``` bash +error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools +``` Unfortunately, many packages requiring compilation don't provide a pre-built wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use. You can download the Visual C++ build tools from [here](https://visualstudio.microsoft.com/visual-cpp-build-tools/) and install "Desktop development with C++" in it's default configuration. Unfortunately, this is a heavy download / dependency so you might want to consider WSL2 or [docker compose](docker_quickstart.md) first. ![Windows installation](assets/windows_install.png) + +--- From 39bae749b5bc9326b0270567674c448b4d45610e Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Thu, 30 May 2024 19:52:41 +0200 Subject: [PATCH 15/33] Changed freqUI installation behaviour to auto installing --- setup.ps1 | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/setup.ps1 b/setup.ps1 index aaf3a825b..5272614e8 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -254,32 +254,14 @@ function Main { Exit-Script -exitCode 1 } - $UiOptions = @("Yes", "No") - $InstallUi = Get-UserSelection -prompt "Do you want to install the freqUI?" -options $UiOptions -defaultChoice 'B' -allowMultipleSelections $false - - if ($InstallUi -eq 0) { - # User selected "Yes" - # Install freqUI using the virtual environment's install-ui command - Write-Log "Installing freqUI..." - python freqtrade install-ui 2>&1 | Out-File $LogFilePath -Append - if ($LASTEXITCODE -ne 0) { - Write-Log "Failed to install freqUI." -Level 'ERROR' - Exit-Script -exitCode 1 - } - } - elseif ($InstallUi -eq 1) { - # User selected "No" - # Skip installing freqUI - Write-Log "Skipping freqUI installation." - } - else { - # Invalid Selection - # Handle the error case - Write-Log "Invalid Selection for freqUI installation." -Level 'ERROR' + Write-Log "Installing freqUI..." + python freqtrade install-ui 2>&1 | Out-File $LogFilePath -Append + if ($LASTEXITCODE -ne 0) { + Write-Log "Failed to install freqUI." -Level 'ERROR' Exit-Script -exitCode 1 } - Write-Log "Update complete!" + Write-Log "Installation/Update complete!" Exit-Script -exitCode 0 } From 2ff6e962553c064e54bdbcd5bba3a28d59174ac4 Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Thu, 30 May 2024 23:54:30 +0200 Subject: [PATCH 16/33] Hopefully, fixed the failing GitHub action --- tests/setup.Tests.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/setup.Tests.ps1 b/tests/setup.Tests.ps1 index aaeefe230..601a1e0e8 100644 --- a/tests/setup.Tests.ps1 +++ b/tests/setup.Tests.ps1 @@ -22,17 +22,17 @@ Describe "Setup and Tests" { # 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" } @@ -153,12 +153,12 @@ Describe "Setup and Tests" { Mock Start-Process {} Mock Read-Host { return "Y" } } - + 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 From e6a562f74a571b6305d3714847761f9cfdb15d1a Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 31 May 2024 20:31:56 +0200 Subject: [PATCH 17/33] Ensure pairlist tests use proper mode --- tests/plugins/test_pairlist.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index b1fed192b..f6c58a1e7 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -773,6 +773,7 @@ def test_VolumePairList_whitelist_gen( whitelist_result, caplog, ) -> None: + whitelist_conf["runmode"] = "backtest" whitelist_conf["pairlists"] = pairlists whitelist_conf["stake_currency"] = base_currency @@ -1270,6 +1271,7 @@ def test_ShuffleFilter_init(mocker, whitelist_conf, caplog) -> None: {"method": "StaticPairList"}, {"method": "ShuffleFilter", "seed": 43}, ] + whitelist_conf["runmode"] = "backtest" exchange = get_patched_exchange(mocker, whitelist_conf) plm = PairListManager(exchange, whitelist_conf) From 5a0e0263d88826cde93f3c7a5f02df09b3379ffa Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 31 May 2024 20:36:18 +0200 Subject: [PATCH 18/33] use StrEnum for RunMode --- freqtrade/enums/runmode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/enums/runmode.py b/freqtrade/enums/runmode.py index d5c2cf652..61409abf6 100644 --- a/freqtrade/enums/runmode.py +++ b/freqtrade/enums/runmode.py @@ -1,7 +1,7 @@ -from enum import Enum +from enum import StrEnum -class RunMode(Enum): +class RunMode(StrEnum): """ Bot running mode (backtest, hyperopt, ...) can be "live", "dry-run", "backtest", "edge", "hyperopt". From 0e44cd91d88b45058172a5ed6ab656d9174a434d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Jun 2024 08:43:04 +0200 Subject: [PATCH 19/33] StrEnum was only introduced in 3.11 . . . --- freqtrade/enums/runmode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/enums/runmode.py b/freqtrade/enums/runmode.py index 61409abf6..a24dd6e2c 100644 --- a/freqtrade/enums/runmode.py +++ b/freqtrade/enums/runmode.py @@ -1,7 +1,7 @@ -from enum import StrEnum +from enum import Enum -class RunMode(StrEnum): +class RunMode(str, Enum): """ Bot running mode (backtest, hyperopt, ...) can be "live", "dry-run", "backtest", "edge", "hyperopt". From 69faabb3b4de3d486947882c64dbef9832ab512a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Jun 2024 11:52:20 +0200 Subject: [PATCH 20/33] freqai tests mostly assume backtest runmode --- tests/freqai/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/freqai/conftest.py b/tests/freqai/conftest.py index fce01b9ee..887dfe3a4 100644 --- a/tests/freqai/conftest.py +++ b/tests/freqai/conftest.py @@ -50,6 +50,7 @@ def freqai_conf(default_conf, tmp_path): freqaiconf.update( { "datadir": Path(default_conf["datadir"]), + "runmode": "backtest", "strategy": "freqai_test_strat", "user_data_dir": tmp_path, "strategy-path": "freqtrade/tests/strategy/strats", From 93b64e7db63d3351cb5056b6d80d65f6be4cc023 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Jun 2024 08:51:17 +0200 Subject: [PATCH 21/33] Update documentation wording --- docs/windows_installation.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/windows_installation.md b/docs/windows_installation.md index 993fd5eb1..5e28d98fb 100644 --- a/docs/windows_installation.md +++ b/docs/windows_installation.md @@ -2,20 +2,26 @@ We **strongly** recommend that Windows users use [Docker](docker_quickstart.md) as this will work much easier and smoother (also more secure). -If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work.\ +If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work. Otherwise, please follow the instructions below. ---- +All instructions assume that python 3.9+ is installed and available. -First of all, make sure you get the whole repository by running: +## Clone the git repository -git clone +First of all clone the repository by running: -Now, choose to install freqtrade automatically (recommended) or manually and follow the next instructions. +``` powershell +git clone https://github.com/freqtrade/freqtrade.git +``` + +Now, choose your installation method, either automatically via script (recommended) or manually following the corresponding instructions. ## Install freqtrade automatically -### Run these commands +### Run the installation script + +The script will ask you a few questions to determine which parts should be installed. ```powershell Set-ExecutionPolicy -ExecutionPolicy Bypass From a306f5a24548071de478c0b3e827aceafc3a66b5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Jun 2024 20:10:24 +0200 Subject: [PATCH 22/33] Improve wording in setup script --- setup.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.ps1 b/setup.ps1 index 5272614e8..40148a328 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -91,6 +91,7 @@ function Get-UserSelection { } } } + function Exit-Script { param ( [int]$ExitCode, @@ -204,7 +205,7 @@ function Main { Write-Log "Checking if the repository is clean..." $Status = & "C:\Program Files\Git\cmd\git.exe" status --porcelain if ($Status) { - Write-Log "Repository is dirty. Skipping pull." + Write-Log "Changes in local git repository. Skipping git pull." } else { Write-Log "Pulling latest updates..." From 86e50b1764e70e82d266fb47b05feff70182d639 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Jun 2024 20:17:55 +0200 Subject: [PATCH 23/33] Don't take assumptions about the install location of git --- setup.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.ps1 b/setup.ps1 index 40148a328..1fed23b66 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -203,13 +203,13 @@ function Main { # Pull latest updates only if the repository state is not dirty Write-Log "Checking if the repository is clean..." - $Status = & "C:\Program Files\Git\cmd\git.exe" status --porcelain + $Status = & "git" status --porcelain if ($Status) { Write-Log "Changes in local git repository. Skipping git pull." } else { Write-Log "Pulling latest updates..." - & "C:\Program Files\Git\cmd\git.exe" pull 2>&1 | Out-File $LogFilePath -Append + & "git" pull 2>&1 | Out-File $LogFilePath -Append if ($LASTEXITCODE -ne 0) { Write-Log "Failed to pull updates from Git." -Level 'ERROR' Exit-Script -exitCode 1 From d116952fe0954b67ac4ff2fa7ae5dfe1e2ac69bb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Jun 2024 20:18:57 +0200 Subject: [PATCH 24/33] Don't use overly long lines --- setup.ps1 | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/setup.ps1 b/setup.ps1 index 1fed23b66..132e4a5e5 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -148,7 +148,21 @@ function Test-PythonExecutable { } function Find-PythonExecutable { - $PythonExecutables = @("python", "python3.12", "python3.11", "python3.10", "python3.9", "python3", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python312\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python311\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python310\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python39\python.exe", "C:\Python311\python.exe", "C:\Python310\python.exe", "C:\Python39\python.exe") + $PythonExecutables = @( + "python", + "python3.12", + "python3.11", + "python3.10", + "python3.9", + "python3", + "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python312\python.exe", + "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python311\python.exe", + "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python310\python.exe", + "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python39\python.exe", + "C:\Python311\python.exe", + "C:\Python310\python.exe", + "C:\Python39\python.exe", + ) foreach ($Executable in $PythonExecutables) { From a2d5b4b2fe3c979616b488c90edd528cbbb6086d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Jun 2024 20:19:33 +0200 Subject: [PATCH 25/33] include 3.12 in all methods --- setup.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.ps1 b/setup.ps1 index 132e4a5e5..ce9e5f2cf 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -159,6 +159,7 @@ function Find-PythonExecutable { "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python311\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python310\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python39\python.exe", + "C:\Python312\python.exe", "C:\Python311\python.exe", "C:\Python310\python.exe", "C:\Python39\python.exe", From 7b6864b991b34f09aea315541124d2a70c42d33a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 2 Jun 2024 08:53:19 +0200 Subject: [PATCH 26/33] Pester should fail "automatically" ... --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca621a4e8..e60ceab55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -324,7 +324,6 @@ jobs: Set-PSRepository psgallery -InstallationPolicy trusted Install-Module -Name Pester -RequiredVersion 5.3.1 -Confirm:$false -Force Invoke-Pester -Path "tests" - if ($Error[0].Fullyqualifiederrorid -eq 'PesterAssertionFailed') {exit 1} shell: powershell - name: Discord notification From e7559cc62c268d98f120deae34c9d508423003d9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 2 Jun 2024 09:26:15 +0200 Subject: [PATCH 27/33] Update pester command --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e60ceab55..8af26519e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -323,7 +323,8 @@ jobs: Write-host $PSVersionTable.PSVersion.Major $PSVersionTable.PSRemotingProtocolVersion.Minor Set-PSRepository psgallery -InstallationPolicy trusted Install-Module -Name Pester -RequiredVersion 5.3.1 -Confirm:$false -Force - Invoke-Pester -Path "tests" + + Invoke-Pester -Path "tests" -CI shell: powershell - name: Discord notification From f6649314a84746f480096594feefa4a76a13f14d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 2 Jun 2024 09:44:38 +0200 Subject: [PATCH 28/33] use pwsh, not powershell shell --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8af26519e..d799467e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -325,7 +325,7 @@ jobs: Install-Module -Name Pester -RequiredVersion 5.3.1 -Confirm:$false -Force Invoke-Pester -Path "tests" -CI - shell: powershell + shell: pwsh - name: Discord notification uses: rjstone/discord-webhook-notify@v1 From 49a6a18881e7b1bfb9779255e189bde1793455b7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 2 Jun 2024 11:48:14 +0200 Subject: [PATCH 29/33] Fix setup.ps1 syntax error --- setup.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.ps1 b/setup.ps1 index ce9e5f2cf..8647bea94 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -162,7 +162,7 @@ function Find-PythonExecutable { "C:\Python312\python.exe", "C:\Python311\python.exe", "C:\Python310\python.exe", - "C:\Python39\python.exe", + "C:\Python39\python.exe" ) From c324981a17a802cd749cc976d5ef9c9a1d4ac1c4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 2 Jun 2024 13:42:00 +0200 Subject: [PATCH 30/33] Simplify setup.tests, avoid error log --- tests/setup.Tests.ps1 | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/tests/setup.Tests.ps1 b/tests/setup.Tests.ps1 index 601a1e0e8..e58a4729d 100644 --- a/tests/setup.Tests.ps1 +++ b/tests/setup.Tests.ps1 @@ -1,13 +1,4 @@ -# 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 { # Setup variables @@ -28,7 +19,9 @@ Describe "Setup and Tests" { Context "Write-Log Tests" -Tag "Unit" { It "should write INFO level log" { - Remove-Item $Global:LogFilePath -ErrorAction SilentlyContinue + if (Test-Path $Global:LogFilePath){ + Remove-Item $Global:LogFilePath -ErrorAction SilentlyContinue + } Write-Log -Message "Test Info Message" -Level "INFO" $Global:LogFilePath | Should -Exist @@ -38,7 +31,9 @@ Describe "Setup and Tests" { } It "should write ERROR level log" { - Remove-Item $Global:LogFilePath -ErrorAction SilentlyContinue + if (Test-Path $Global:LogFilePath){ + Remove-Item $Global:LogFilePath -ErrorAction SilentlyContinue + } Write-Log -Message "Test Error Message" -Level "ERROR" $Global:LogFilePath | Should -Exist From e9fb645b98f162f934df876421ff3c5e51c8782e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 2 Jun 2024 13:42:51 +0200 Subject: [PATCH 31/33] Exit-1 if invoke-pester created error entries --- .github/workflows/ci.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d799467e3..b359ac458 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -320,12 +320,14 @@ jobs: - name: Run Pester tests (PowerShell) run: | - Write-host $PSVersionTable.PSVersion.Major $PSVersionTable.PSRemotingProtocolVersion.Minor + $PSVersionTable Set-PSRepository psgallery -InstallationPolicy trusted Install-Module -Name Pester -RequiredVersion 5.3.1 -Confirm:$false -Force - + $Error.clear() Invoke-Pester -Path "tests" -CI - shell: pwsh + if ($Error.Length -gt 0) {exit 1} + + shell: powershell - name: Discord notification uses: rjstone/discord-webhook-notify@v1 From a05450c547d5c9df3f174a867cf9ea136a57efa8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 2 Jun 2024 14:17:55 +0200 Subject: [PATCH 32/33] Add bottleneck dependency as per pandas recommendation https://pandas.pydata.org/docs/getting_started/install.html#performance-dependencies-recommended --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 8c31cc80e..f85157f7b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ numpy==1.26.4 pandas==2.2.2 +bottleneck==1.3.8 pandas-ta==0.3.14b ccxt==4.3.35 From 8eda43f68dc0580ef985af557b2ffe5b1f9f79be Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 2 Jun 2024 14:19:21 +0200 Subject: [PATCH 33/33] Pin numexpr - it's installed as floating dependency anyway --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index f85157f7b..8adb2f84e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ numpy==1.26.4 pandas==2.2.2 bottleneck==1.3.8 +numexpr==2.10.0 pandas-ta==0.3.14b ccxt==4.3.35