diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 1af20d6..31b4ae1 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -37,11 +37,22 @@ jobs: Pkg.precompile(); ' - - name: Run coverage script + - name: Run tests with coverage run: | - julia --project=. run_coverage.jl + julia --project=. -e ' + using Pkg; + Pkg.test(; coverage=true); + ' + + - name: Generate coverage report + run: | + julia --project=. -e ' + using Coverage; + results = process_folder(); + LCOV.writefile("coverage.lcov", results); + ' - - name: Upload coverage report to Codecov + - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: file: ./coverage.lcov @@ -49,4 +60,3 @@ jobs: flags: unittests name: Code coverage fail_ci_if_error: true - diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59bbfde --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Arquivos gerados pelo Coverage.jl +coverage.* +*.lcov + +# Logs e artefatos temporários +*.log +*.tmp +*.bak + +# Diretórios de cache do Julia +.julia/ +.julia_artifacts/ +.julia_compilecache/ + +# Diretórios criados para ambientes do projeto +deps/ +build/ +artifacts/ + +# Diretórios de testes temporários +test/.temp/ +test/*.jld2 +test/*.h5 + +# Qualquer outro arquivo temporário ou não necessário +*.DS_Store +*.~lock.* diff --git a/src/core.jl.73885.cov b/src/core.jl.73885.cov deleted file mode 100644 index 44c8fa6..0000000 --- a/src/core.jl.73885.cov +++ /dev/null @@ -1,379 +0,0 @@ - - module Core - - - - using HTTP - - using Statistics - - using Base.Threads - - using Printf - - - - # Armazena as opções globais - - const GLOBAL_OPTIONS = Dict{Symbol, Any}() - - - - struct Check - 2 description::String - - condition::Function - - end - - - - # Registra resultados globais dos checks - - const CHECK_RESULTS = Ref(Vector{String}()) - - const CHECK_LOCK = ReentrantLock() - - - - """ - - options(; vus::Int=1, format::String="default", ramp_duration::Union{Float64, Nothing}=nothing, - - max_vus::Union{Int, Nothing}=nothing, iterations::Union{Int, Nothing}=nothing, - - duration::Union{Float64, Nothing}=nothing) - - - - Configura opções globais para os testes de performance. - - """ - - function options(; vus::Int = 1, format::String = "default", max_vus::Union{Int, Nothing} = nothing, - - ramp_duration::Union{Float64, Nothing} = nothing, iterations::Union{Int, Nothing} = nothing, - - duration::Union{Float64, Nothing} = nothing) - - GLOBAL_OPTIONS[:vus] = vus - - GLOBAL_OPTIONS[:format] = format - - GLOBAL_OPTIONS[:max_vus] = max_vus - - GLOBAL_OPTIONS[:ramp_duration] = ramp_duration - - GLOBAL_OPTIONS[:iterations] = iterations - - GLOBAL_OPTIONS[:duration] = duration - - - - if format == "vus-ramping" && (max_vus === nothing || duration === nothing || ramp_duration === nothing) - - error("Para o formato 'vus-ramping', você deve especificar 'max_vus', 'ramp_duration' e 'duration'.") - - end - - end - - - - """ - - check(response, method::String, checks::Vector{Check}) - - - - Aplica uma lista de checks a uma resposta HTTP, incluindo o método HTTP. - - - - - `response`: Objeto de resposta HTTP. - - - `method`: Método HTTP usado na requisição (GET, POST, etc.). - - - `checks`: Vetor de objetos `Check` com condições a serem avaliadas. - - """ - - function check(response, method::String, checks::Vector{Check}) - - for chk in checks - - try - - if chk.condition(response) - - push!(CHECK_RESULTS[], "✔️ $(method) - $(chk.description) - Success") - - println("✔️ $(method) - $(chk.description) - Success") - - else - - push!(CHECK_RESULTS[], "❌ $(method) - $(chk.description) - Failed") - - println("❌ $(method) - $(chk.description) - Failed") - - end - - catch e - - push!(CHECK_RESULTS[], "⚠️ $(method) - $(chk.description) - Error: $e") - - println("⚠️ $(method) - $(chk.description) - Error: $e") - - end - - end - - end - - - - # Funções de criação de requisições HTTP - 6 function http_get(endpoint::String; checks = Vector{Check}()) - 3 return (method = HTTP.get, url = endpoint, payload = nothing, headers = Dict(), checks = checks) - - end - - - 4 function http_post(endpoint::String; payload = nothing, headers = Dict(), checks = Vector{Check}()) - 2 return (method = HTTP.post, url = endpoint, payload = payload, headers = headers, checks = checks) - - end - - - 4 function http_put(endpoint::String; payload = nothing, headers = Dict(), checks = Vector{Check}()) - 2 return (method = HTTP.put, url = endpoint, payload = payload, headers = headers, checks = checks) - - end - - - 4 function http_patch(endpoint::String; payload = nothing, headers = Dict(), checks = Vector{Check}()) - 2 return (method = HTTP.patch, url = endpoint, payload = payload, headers = headers, checks = checks) - - end - - - 4 function http_delete(endpoint::String; headers = Dict(), checks = Vector{Check}()) - 2 return (method = HTTP.delete, url = endpoint, payload = nothing, headers = headers, checks = checks) - - end - - - - """ - - perform_request(request::NamedTuple) - - - - Executa uma requisição HTTP com base em um `NamedTuple` que define o método, URL, payload e headers. - - - - - `request`: NamedTuple contendo `method`, `url`, `payload`, `headers` e `checks`. - - """ - - function perform_request(request::NamedTuple) - - method, url, payload, headers, checks = - - request.method, request.url, request.payload, request.headers, request.checks - - - - response = if method in (HTTP.get, HTTP.delete) - - method(url, headers) - - elseif method in (HTTP.post, HTTP.put, HTTP.patch) - - method(url, headers; body = payload) - - else - - error("Método HTTP não suportado. Use: GET, POST, PUT, DELETE ou PATCH.") - - end - - - - # Processar checks, se existirem - - if !isempty(checks) - - check(response, string(method), checks) - - end - - - - return response - - end - - - - """ - - compute_statistics(all_times::Vector{Float64}, total_errors::Atomic{Int}, total_requests::Int, total_duration::Float64) - - - - Calcula e retorna as estatísticas de desempenho a partir do vetor de tempos de resposta. - - - - - `all_times`: Vetor contendo todos os tempos de resposta. - - - `total_errors`: Contador de erros no teste. - - - `total_requests`: Número total de requisições realizadas. - - - `total_duration`: Duração total do teste em segundos. - - - - Retorna um `Dict` com as estatísticas de desempenho, incluindo P90, P95, P99, SuccessRate, ErrorRate, RPS e TPS. - - """ - 1 function compute_statistics( - - all_times::Vector{Float64}, - - total_errors::Union{Int, Base.Threads.Atomic{Int}}, - - total_requests::Int, - - total_duration::Float64, - - vus::Int, - - ) - 2 p90 = isempty(all_times) ? NaN : percentile(all_times, 90) - 2 p95 = isempty(all_times) ? NaN : percentile(all_times, 95) - 2 p99 = isempty(all_times) ? NaN : percentile(all_times, 99) - 2 success_rate = total_requests == 0 ? NaN : (1 - total_errors[] / total_requests) * 100 - 2 error_rate = total_requests == 0 ? NaN : (total_errors[] / total_requests) * 100 - - - 2 rps = total_duration == 0.0 ? NaN : total_requests / total_duration - 2 tps = total_duration == 0.0 ? NaN : (total_requests - total_errors[]) / total_duration - - - 1 return Dict( - - "iterations" => length(all_times), - - "errors" => total_errors[], - - "success_rate" => success_rate, # Taxa de sucesso em porcentagem - - "error_rate" => error_rate, # Taxa de erro em porcentagem - - "min_time" => isempty(all_times) ? NaN : minimum(all_times), - - "max_time" => isempty(all_times) ? NaN : maximum(all_times), - - "mean_time" => isempty(all_times) ? NaN : mean(all_times), - - "median_time" => isempty(all_times) ? NaN : median(all_times), - - "std_time" => isempty(all_times) ? NaN : std(all_times), - - "p90_time" => p90, - - "p95_time" => p95, - - "p99_time" => p99, - - "vus" => vus, - - "rps" => rps, # Requests Per Second - - "tps" => tps, # Transactions Per Second - - "all_times" => all_times, # Adicionando a chave "all_times" - - ) - - end - - - - """ - - percentile(data::Vector{Float64}, p::Real) - - - - Calcula o valor do percentil `p` em um vetor de dados. - - - - - `data`: Vetor de dados. - - - `p`: Percentil (entre 0 e 100). - - - - Retorna o valor do percentil ou `NaN` se o vetor estiver vazio. - - """ - 3 function percentile(data::Vector{Float64}, p::Real) - 3 if isempty(data) - 0 return NaN - - end - 3 sorted_data = sort(data) - 3 rank = ceil(Int, p / 100 * length(sorted_data)) - 3 return sorted_data[min(rank, length(sorted_data))] - - end - - - - """ - - format_results(results::Dict{String, <:Any}) - - - - Formata o dicionário de resultados de métricas de desempenho em um resumo legível. - - - - - `results`: Dicionário retornado pela função `compute_statistics`. - - - - Retorna uma string formatada com as principais métricas. - - """ - 1 function format_results(results::Dict{String, <:Any}) - 1 return """ - - ================== Stressify ================== - - VUs : $(lpad(results["vus"], 10)) - - Iterações Totais : $(lpad(results["iterations"], 10)) - - Taxa de Sucesso (%) : $(lpad(round(results["success_rate"], digits=2), 10)) - - Taxa de Erros (%) : $(lpad(round(results["error_rate"], digits=2), 10)) - - Requisições por Segundo: $(lpad(round(results["rps"], digits=2), 10)) - - Transações por Segundo : $(lpad(round(results["tps"], digits=2), 10)) - - Número de Erros : $(lpad(results["errors"], 10)) - - - - ---------- Métricas de Tempo (s) ---------- - - Tempo Mínimo : $(lpad(round(results["min_time"], digits=4), 10)) - - Tempo Máximo : $(lpad(round(results["max_time"], digits=4), 10)) - - Tempo Médio : $(lpad(round(results["mean_time"], digits=4), 10)) - - Mediana : $(lpad(round(results["median_time"], digits=4), 10)) - - P90 : $(lpad(round(results["p90_time"], digits=4), 10)) - - P95 : $(lpad(round(results["p95_time"], digits=4), 10)) - - P99 : $(lpad(round(results["p99_time"], digits=4), 10)) - - Desvio Padrão : $(lpad(round(results["std_time"], digits=4), 10)) - - - - ---------- Detalhamento de Tempos ---------- - - Todos os Tempos (s) : $(join(round.(results["all_times"], digits=4), ", ")) - - ========================================================== - - """ - - end - - - - - - """ - - run_test(requests::Vararg{NamedTuple}) - - - - Executa os testes de performance com suporte ao formato `vus-ramping`, incluindo `ramp_duration`. - - """ - - function run_test(requests::Vararg{NamedTuple}) - - vus = get(GLOBAL_OPTIONS, :vus, 1) - - format = get(GLOBAL_OPTIONS, :format, "default") - - max_vus = get(GLOBAL_OPTIONS, :max_vus, nothing) - - ramp_duration = get(GLOBAL_OPTIONS, :ramp_duration, 0.0) - - iterations = get(GLOBAL_OPTIONS, :iterations, nothing) - - duration = get(GLOBAL_OPTIONS, :duration, nothing) - - - - if iterations === nothing && duration === nothing - - error("Você deve especificar 'iterations' ou 'duration' nas opções globais.") - - end - - - - start_time = time() - - total_errors = Atomic{Int}(0) - - active_vus = Atomic{Int}(vus) - - tasks = Task[] - - - - # Inicializa o vetor de resultados com o número máximo possível de VUs - - local_results = [Float64[] for _ in 1:(max_vus === nothing ? vus : max_vus)] - - - - if format == "vus-ramping" && max_vus !== nothing && ramp_duration > 0.0 && duration !== nothing - - # Calcula quantos VUs precisamos adicionar - - vus_to_add = max_vus - vus - - - - # Calcula o intervalo entre adições de VUs para garantir distribuição uniforme - - if vus_to_add > 0 - - interval = ramp_duration / vus_to_add - - else - - interval = ramp_duration - - end - - - - # Task para gerenciar o ramp-up - - ramp_task = @async begin - - current_vus = vus - - ramp_start = time() - - - - # Inicia as tasks para os VUs iniciais - - for t in 1:vus - - push!(tasks, spawn_vu_task( - - t, - - start_time, - - duration + ramp_duration, # Duração total inclui ramping - - iterations, - - requests, - - local_results, - - total_errors - - )) - - end - - - - # Adiciona novos VUs gradualmente durante o ramp_duration - - for new_vu in (vus + 1):max_vus - - sleep(interval) - - current_vus = new_vu - - atomic_add!(active_vus, 1) - - println("Ramp-up: Incrementando VUs para $current_vus") - - - - push!(tasks, spawn_vu_task( - - new_vu, - - start_time, - - duration + ramp_duration - (time() - start_time), # Ajusta duração restante - - iterations, - - requests, - - local_results, - - total_errors - - )) - - end - - - - println("Ramp-up concluído. Total de VUs ativos: $(active_vus[])") - - end - - - - # Aguarda o término do ramp-up - - wait(ramp_task) - - - - # Aguarda o término da duração total - - remaining_time = duration + ramp_duration - (time() - start_time) - - if remaining_time > 0 - - sleep(remaining_time) - - end - - else - - # Execução padrão sem ramping - - for t in 1:vus - - push!(tasks, spawn_vu_task( - - t, - - start_time, - - duration, - - iterations, - - requests, - - local_results, - - total_errors - - )) - - end - - - - if duration !== nothing - - sleep(duration) - - end - - end - - - - # Aguarda todas as tasks terminarem - - foreach(wait, tasks) - - - - # Processa os resultados - - all_times = vcat(local_results...) - - total_requests = length(all_times) + total_errors[] - - total_duration = time() - start_time - - - - results = compute_statistics(all_times, total_errors, total_requests, total_duration, active_vus[]) - - println(format_results(results)) - - println("\n---------- Resultados dos Checks ----------") - - println(join(CHECK_RESULTS[], "\n")) - - - - return results - - end - - - - function spawn_vu_task(vu_id, start_time, duration, iterations, requests, local_results, total_errors) - - return Threads.@spawn begin - - println("Thread $vu_id inicializada.") - - request_idx = 1 - - iteration_count = 0 - - - - while true - - current_time = time() - start_time - - - - # Verifica condições de parada - - if iterations !== nothing && iteration_count >= iterations - - break - - end - - - - if duration !== nothing && current_time >= duration - - break - - end - - - - try - - elapsed_time = @elapsed begin - - perform_request(requests[request_idx]) - - end - - push!(local_results[vu_id], elapsed_time) - - iteration_count += 1 - - - - method_name = string(requests[request_idx].method) |> x -> split(x, ".")[end] - - println("Requisição (Método: $method_name) finalizada no thread $vu_id (Tempo: $elapsed_time segundos)") - - catch e - - atomic_add!(total_errors, 1) - - println("Erro na requisição no thread $vu_id: ", e) - - end - - - - request_idx = (request_idx % length(requests)) + 1 - - end - - - - println("Thread $vu_id finalizada após $iteration_count iterações.") - - end - - end - - - - export options, http_get, http_post, http_put, http_patch, http_delete, run_test, Check, format_results, compute_statistics - - - - end diff --git a/src/core.jl.75010.cov b/src/core.jl.75010.cov deleted file mode 100644 index 44c8fa6..0000000 --- a/src/core.jl.75010.cov +++ /dev/null @@ -1,379 +0,0 @@ - - module Core - - - - using HTTP - - using Statistics - - using Base.Threads - - using Printf - - - - # Armazena as opções globais - - const GLOBAL_OPTIONS = Dict{Symbol, Any}() - - - - struct Check - 2 description::String - - condition::Function - - end - - - - # Registra resultados globais dos checks - - const CHECK_RESULTS = Ref(Vector{String}()) - - const CHECK_LOCK = ReentrantLock() - - - - """ - - options(; vus::Int=1, format::String="default", ramp_duration::Union{Float64, Nothing}=nothing, - - max_vus::Union{Int, Nothing}=nothing, iterations::Union{Int, Nothing}=nothing, - - duration::Union{Float64, Nothing}=nothing) - - - - Configura opções globais para os testes de performance. - - """ - - function options(; vus::Int = 1, format::String = "default", max_vus::Union{Int, Nothing} = nothing, - - ramp_duration::Union{Float64, Nothing} = nothing, iterations::Union{Int, Nothing} = nothing, - - duration::Union{Float64, Nothing} = nothing) - - GLOBAL_OPTIONS[:vus] = vus - - GLOBAL_OPTIONS[:format] = format - - GLOBAL_OPTIONS[:max_vus] = max_vus - - GLOBAL_OPTIONS[:ramp_duration] = ramp_duration - - GLOBAL_OPTIONS[:iterations] = iterations - - GLOBAL_OPTIONS[:duration] = duration - - - - if format == "vus-ramping" && (max_vus === nothing || duration === nothing || ramp_duration === nothing) - - error("Para o formato 'vus-ramping', você deve especificar 'max_vus', 'ramp_duration' e 'duration'.") - - end - - end - - - - """ - - check(response, method::String, checks::Vector{Check}) - - - - Aplica uma lista de checks a uma resposta HTTP, incluindo o método HTTP. - - - - - `response`: Objeto de resposta HTTP. - - - `method`: Método HTTP usado na requisição (GET, POST, etc.). - - - `checks`: Vetor de objetos `Check` com condições a serem avaliadas. - - """ - - function check(response, method::String, checks::Vector{Check}) - - for chk in checks - - try - - if chk.condition(response) - - push!(CHECK_RESULTS[], "✔️ $(method) - $(chk.description) - Success") - - println("✔️ $(method) - $(chk.description) - Success") - - else - - push!(CHECK_RESULTS[], "❌ $(method) - $(chk.description) - Failed") - - println("❌ $(method) - $(chk.description) - Failed") - - end - - catch e - - push!(CHECK_RESULTS[], "⚠️ $(method) - $(chk.description) - Error: $e") - - println("⚠️ $(method) - $(chk.description) - Error: $e") - - end - - end - - end - - - - # Funções de criação de requisições HTTP - 6 function http_get(endpoint::String; checks = Vector{Check}()) - 3 return (method = HTTP.get, url = endpoint, payload = nothing, headers = Dict(), checks = checks) - - end - - - 4 function http_post(endpoint::String; payload = nothing, headers = Dict(), checks = Vector{Check}()) - 2 return (method = HTTP.post, url = endpoint, payload = payload, headers = headers, checks = checks) - - end - - - 4 function http_put(endpoint::String; payload = nothing, headers = Dict(), checks = Vector{Check}()) - 2 return (method = HTTP.put, url = endpoint, payload = payload, headers = headers, checks = checks) - - end - - - 4 function http_patch(endpoint::String; payload = nothing, headers = Dict(), checks = Vector{Check}()) - 2 return (method = HTTP.patch, url = endpoint, payload = payload, headers = headers, checks = checks) - - end - - - 4 function http_delete(endpoint::String; headers = Dict(), checks = Vector{Check}()) - 2 return (method = HTTP.delete, url = endpoint, payload = nothing, headers = headers, checks = checks) - - end - - - - """ - - perform_request(request::NamedTuple) - - - - Executa uma requisição HTTP com base em um `NamedTuple` que define o método, URL, payload e headers. - - - - - `request`: NamedTuple contendo `method`, `url`, `payload`, `headers` e `checks`. - - """ - - function perform_request(request::NamedTuple) - - method, url, payload, headers, checks = - - request.method, request.url, request.payload, request.headers, request.checks - - - - response = if method in (HTTP.get, HTTP.delete) - - method(url, headers) - - elseif method in (HTTP.post, HTTP.put, HTTP.patch) - - method(url, headers; body = payload) - - else - - error("Método HTTP não suportado. Use: GET, POST, PUT, DELETE ou PATCH.") - - end - - - - # Processar checks, se existirem - - if !isempty(checks) - - check(response, string(method), checks) - - end - - - - return response - - end - - - - """ - - compute_statistics(all_times::Vector{Float64}, total_errors::Atomic{Int}, total_requests::Int, total_duration::Float64) - - - - Calcula e retorna as estatísticas de desempenho a partir do vetor de tempos de resposta. - - - - - `all_times`: Vetor contendo todos os tempos de resposta. - - - `total_errors`: Contador de erros no teste. - - - `total_requests`: Número total de requisições realizadas. - - - `total_duration`: Duração total do teste em segundos. - - - - Retorna um `Dict` com as estatísticas de desempenho, incluindo P90, P95, P99, SuccessRate, ErrorRate, RPS e TPS. - - """ - 1 function compute_statistics( - - all_times::Vector{Float64}, - - total_errors::Union{Int, Base.Threads.Atomic{Int}}, - - total_requests::Int, - - total_duration::Float64, - - vus::Int, - - ) - 2 p90 = isempty(all_times) ? NaN : percentile(all_times, 90) - 2 p95 = isempty(all_times) ? NaN : percentile(all_times, 95) - 2 p99 = isempty(all_times) ? NaN : percentile(all_times, 99) - 2 success_rate = total_requests == 0 ? NaN : (1 - total_errors[] / total_requests) * 100 - 2 error_rate = total_requests == 0 ? NaN : (total_errors[] / total_requests) * 100 - - - 2 rps = total_duration == 0.0 ? NaN : total_requests / total_duration - 2 tps = total_duration == 0.0 ? NaN : (total_requests - total_errors[]) / total_duration - - - 1 return Dict( - - "iterations" => length(all_times), - - "errors" => total_errors[], - - "success_rate" => success_rate, # Taxa de sucesso em porcentagem - - "error_rate" => error_rate, # Taxa de erro em porcentagem - - "min_time" => isempty(all_times) ? NaN : minimum(all_times), - - "max_time" => isempty(all_times) ? NaN : maximum(all_times), - - "mean_time" => isempty(all_times) ? NaN : mean(all_times), - - "median_time" => isempty(all_times) ? NaN : median(all_times), - - "std_time" => isempty(all_times) ? NaN : std(all_times), - - "p90_time" => p90, - - "p95_time" => p95, - - "p99_time" => p99, - - "vus" => vus, - - "rps" => rps, # Requests Per Second - - "tps" => tps, # Transactions Per Second - - "all_times" => all_times, # Adicionando a chave "all_times" - - ) - - end - - - - """ - - percentile(data::Vector{Float64}, p::Real) - - - - Calcula o valor do percentil `p` em um vetor de dados. - - - - - `data`: Vetor de dados. - - - `p`: Percentil (entre 0 e 100). - - - - Retorna o valor do percentil ou `NaN` se o vetor estiver vazio. - - """ - 3 function percentile(data::Vector{Float64}, p::Real) - 3 if isempty(data) - 0 return NaN - - end - 3 sorted_data = sort(data) - 3 rank = ceil(Int, p / 100 * length(sorted_data)) - 3 return sorted_data[min(rank, length(sorted_data))] - - end - - - - """ - - format_results(results::Dict{String, <:Any}) - - - - Formata o dicionário de resultados de métricas de desempenho em um resumo legível. - - - - - `results`: Dicionário retornado pela função `compute_statistics`. - - - - Retorna uma string formatada com as principais métricas. - - """ - 1 function format_results(results::Dict{String, <:Any}) - 1 return """ - - ================== Stressify ================== - - VUs : $(lpad(results["vus"], 10)) - - Iterações Totais : $(lpad(results["iterations"], 10)) - - Taxa de Sucesso (%) : $(lpad(round(results["success_rate"], digits=2), 10)) - - Taxa de Erros (%) : $(lpad(round(results["error_rate"], digits=2), 10)) - - Requisições por Segundo: $(lpad(round(results["rps"], digits=2), 10)) - - Transações por Segundo : $(lpad(round(results["tps"], digits=2), 10)) - - Número de Erros : $(lpad(results["errors"], 10)) - - - - ---------- Métricas de Tempo (s) ---------- - - Tempo Mínimo : $(lpad(round(results["min_time"], digits=4), 10)) - - Tempo Máximo : $(lpad(round(results["max_time"], digits=4), 10)) - - Tempo Médio : $(lpad(round(results["mean_time"], digits=4), 10)) - - Mediana : $(lpad(round(results["median_time"], digits=4), 10)) - - P90 : $(lpad(round(results["p90_time"], digits=4), 10)) - - P95 : $(lpad(round(results["p95_time"], digits=4), 10)) - - P99 : $(lpad(round(results["p99_time"], digits=4), 10)) - - Desvio Padrão : $(lpad(round(results["std_time"], digits=4), 10)) - - - - ---------- Detalhamento de Tempos ---------- - - Todos os Tempos (s) : $(join(round.(results["all_times"], digits=4), ", ")) - - ========================================================== - - """ - - end - - - - - - """ - - run_test(requests::Vararg{NamedTuple}) - - - - Executa os testes de performance com suporte ao formato `vus-ramping`, incluindo `ramp_duration`. - - """ - - function run_test(requests::Vararg{NamedTuple}) - - vus = get(GLOBAL_OPTIONS, :vus, 1) - - format = get(GLOBAL_OPTIONS, :format, "default") - - max_vus = get(GLOBAL_OPTIONS, :max_vus, nothing) - - ramp_duration = get(GLOBAL_OPTIONS, :ramp_duration, 0.0) - - iterations = get(GLOBAL_OPTIONS, :iterations, nothing) - - duration = get(GLOBAL_OPTIONS, :duration, nothing) - - - - if iterations === nothing && duration === nothing - - error("Você deve especificar 'iterations' ou 'duration' nas opções globais.") - - end - - - - start_time = time() - - total_errors = Atomic{Int}(0) - - active_vus = Atomic{Int}(vus) - - tasks = Task[] - - - - # Inicializa o vetor de resultados com o número máximo possível de VUs - - local_results = [Float64[] for _ in 1:(max_vus === nothing ? vus : max_vus)] - - - - if format == "vus-ramping" && max_vus !== nothing && ramp_duration > 0.0 && duration !== nothing - - # Calcula quantos VUs precisamos adicionar - - vus_to_add = max_vus - vus - - - - # Calcula o intervalo entre adições de VUs para garantir distribuição uniforme - - if vus_to_add > 0 - - interval = ramp_duration / vus_to_add - - else - - interval = ramp_duration - - end - - - - # Task para gerenciar o ramp-up - - ramp_task = @async begin - - current_vus = vus - - ramp_start = time() - - - - # Inicia as tasks para os VUs iniciais - - for t in 1:vus - - push!(tasks, spawn_vu_task( - - t, - - start_time, - - duration + ramp_duration, # Duração total inclui ramping - - iterations, - - requests, - - local_results, - - total_errors - - )) - - end - - - - # Adiciona novos VUs gradualmente durante o ramp_duration - - for new_vu in (vus + 1):max_vus - - sleep(interval) - - current_vus = new_vu - - atomic_add!(active_vus, 1) - - println("Ramp-up: Incrementando VUs para $current_vus") - - - - push!(tasks, spawn_vu_task( - - new_vu, - - start_time, - - duration + ramp_duration - (time() - start_time), # Ajusta duração restante - - iterations, - - requests, - - local_results, - - total_errors - - )) - - end - - - - println("Ramp-up concluído. Total de VUs ativos: $(active_vus[])") - - end - - - - # Aguarda o término do ramp-up - - wait(ramp_task) - - - - # Aguarda o término da duração total - - remaining_time = duration + ramp_duration - (time() - start_time) - - if remaining_time > 0 - - sleep(remaining_time) - - end - - else - - # Execução padrão sem ramping - - for t in 1:vus - - push!(tasks, spawn_vu_task( - - t, - - start_time, - - duration, - - iterations, - - requests, - - local_results, - - total_errors - - )) - - end - - - - if duration !== nothing - - sleep(duration) - - end - - end - - - - # Aguarda todas as tasks terminarem - - foreach(wait, tasks) - - - - # Processa os resultados - - all_times = vcat(local_results...) - - total_requests = length(all_times) + total_errors[] - - total_duration = time() - start_time - - - - results = compute_statistics(all_times, total_errors, total_requests, total_duration, active_vus[]) - - println(format_results(results)) - - println("\n---------- Resultados dos Checks ----------") - - println(join(CHECK_RESULTS[], "\n")) - - - - return results - - end - - - - function spawn_vu_task(vu_id, start_time, duration, iterations, requests, local_results, total_errors) - - return Threads.@spawn begin - - println("Thread $vu_id inicializada.") - - request_idx = 1 - - iteration_count = 0 - - - - while true - - current_time = time() - start_time - - - - # Verifica condições de parada - - if iterations !== nothing && iteration_count >= iterations - - break - - end - - - - if duration !== nothing && current_time >= duration - - break - - end - - - - try - - elapsed_time = @elapsed begin - - perform_request(requests[request_idx]) - - end - - push!(local_results[vu_id], elapsed_time) - - iteration_count += 1 - - - - method_name = string(requests[request_idx].method) |> x -> split(x, ".")[end] - - println("Requisição (Método: $method_name) finalizada no thread $vu_id (Tempo: $elapsed_time segundos)") - - catch e - - atomic_add!(total_errors, 1) - - println("Erro na requisição no thread $vu_id: ", e) - - end - - - - request_idx = (request_idx % length(requests)) + 1 - - end - - - - println("Thread $vu_id finalizada após $iteration_count iterações.") - - end - - end - - - - export options, http_get, http_post, http_put, http_patch, http_delete, run_test, Check, format_results, compute_statistics - - - - end diff --git a/src/report.jl.73885.cov b/src/report.jl.73885.cov deleted file mode 100644 index 2b2e1a0..0000000 --- a/src/report.jl.73885.cov +++ /dev/null @@ -1,32 +0,0 @@ - - module Report - - - - using Plots - - - - """ - - generate_report(results::Dict) - - - - Gera um gráfico de tempos de resposta. - - """ - 3 function generate_report(results::Dict, file_name::String="grafico.png") - 5 times = get(results, "all_times", []) - - - 5 if isempty(times) - 1 println("Erro: Nenhum dado de tempo encontrado ou a chave 'all_times' não foi fornecida.") - 1 return - - end - - - 4 p = plot( - - 1:length(times), times, - - xlabel="Requisição", ylabel="Tempo (s)", - - title="Desempenho do Endpoint", - - legend=false - - ) - - - 2 savefig(p, file_name) - 2 println("Gráfico salvo em: $file_name") - - end - - - - - - export generate_report - - - - end diff --git a/src/report.jl.75010.cov b/src/report.jl.75010.cov deleted file mode 100644 index 2b2e1a0..0000000 --- a/src/report.jl.75010.cov +++ /dev/null @@ -1,32 +0,0 @@ - - module Report - - - - using Plots - - - - """ - - generate_report(results::Dict) - - - - Gera um gráfico de tempos de resposta. - - """ - 3 function generate_report(results::Dict, file_name::String="grafico.png") - 5 times = get(results, "all_times", []) - - - 5 if isempty(times) - 1 println("Erro: Nenhum dado de tempo encontrado ou a chave 'all_times' não foi fornecida.") - 1 return - - end - - - 4 p = plot( - - 1:length(times), times, - - xlabel="Requisição", ylabel="Tempo (s)", - - title="Desempenho do Endpoint", - - legend=false - - ) - - - 2 savefig(p, file_name) - 2 println("Gráfico salvo em: $file_name") - - end - - - - - - export generate_report - - - - end diff --git a/src/utils.jl.73885.cov b/src/utils.jl.73885.cov deleted file mode 100644 index 7773712..0000000 --- a/src/utils.jl.73885.cov +++ /dev/null @@ -1,69 +0,0 @@ - - module Utils - - - - using CSV - - using Random - - using JSON - - using DataFrames - - - - """ - - save_results_to_json(results::Dict, filepath::String) - - - - Salva resultados em um arquivo JSON. - - """ - 1 function save_results_to_json(results::Dict, filepath::String) - 1 open(filepath, "w") do file - 1 write(file, JSON.json(results)) - - end - - end - - - - """ - - random_csv_row(file_path::String) - - - - Lê um arquivo CSV e retorna uma linha aleatória como `NamedTuple`. - - - - ### Parâmetros - - - `file_path::String`: Caminho para o arquivo CSV. - - - - ### Retorno - - - Um `NamedTuple` representando uma linha aleatória do arquivo CSV. - - """ - 2 function random_csv_row(file_path::String) - 2 if !isfile(file_path) - 1 throw(ArgumentError("O arquivo '$file_path' não foi encontrado ou não é válido.")) - - end - 2 data = CSV.read(file_path, DataFrame) - 1 random_index = rand(1:nrow(data)) - 1 return data[random_index, :] - - end - - - - """ - - random_json_object(file_path::String) - - - - Lê um arquivo JSON e retorna um objeto aleatório do conteúdo. - - - - ### Parâmetros - - - `file_path::String`: Caminho para o arquivo JSON. - - - - ### Retorno - - - Um objeto aleatório do arquivo JSON (como um valor de um `Dict` ou um `Array`). - - - - """ - 3 function random_json_object(file_path::String) - 3 if !isfile(file_path) - 1 throw(ArgumentError("O arquivo '$file_path' não foi encontrado ou não é válido.")) - - end - 2 data = JSON.parsefile(file_path) - - - 2 if isa(data, Array) # Se for um array - 1 return isempty(data) ? nothing : data[rand(1:length(data))] - 1 elseif isa(data, Dict) # Se for um dicionário - 1 keys_array = collect(keys(data)) - 1 return isempty(keys_array) ? nothing : data[rand(keys_array)] - - else - 0 throw(ArgumentError("O arquivo JSON não é um Array ou Dict suportado.")) - - end - - end - - - - export save_results_to_json, random_csv_row, random_json_object - - - - end diff --git a/src/utils.jl.75010.cov b/src/utils.jl.75010.cov deleted file mode 100644 index 7773712..0000000 --- a/src/utils.jl.75010.cov +++ /dev/null @@ -1,69 +0,0 @@ - - module Utils - - - - using CSV - - using Random - - using JSON - - using DataFrames - - - - """ - - save_results_to_json(results::Dict, filepath::String) - - - - Salva resultados em um arquivo JSON. - - """ - 1 function save_results_to_json(results::Dict, filepath::String) - 1 open(filepath, "w") do file - 1 write(file, JSON.json(results)) - - end - - end - - - - """ - - random_csv_row(file_path::String) - - - - Lê um arquivo CSV e retorna uma linha aleatória como `NamedTuple`. - - - - ### Parâmetros - - - `file_path::String`: Caminho para o arquivo CSV. - - - - ### Retorno - - - Um `NamedTuple` representando uma linha aleatória do arquivo CSV. - - """ - 2 function random_csv_row(file_path::String) - 2 if !isfile(file_path) - 1 throw(ArgumentError("O arquivo '$file_path' não foi encontrado ou não é válido.")) - - end - 2 data = CSV.read(file_path, DataFrame) - 1 random_index = rand(1:nrow(data)) - 1 return data[random_index, :] - - end - - - - """ - - random_json_object(file_path::String) - - - - Lê um arquivo JSON e retorna um objeto aleatório do conteúdo. - - - - ### Parâmetros - - - `file_path::String`: Caminho para o arquivo JSON. - - - - ### Retorno - - - Um objeto aleatório do arquivo JSON (como um valor de um `Dict` ou um `Array`). - - - - """ - 3 function random_json_object(file_path::String) - 3 if !isfile(file_path) - 1 throw(ArgumentError("O arquivo '$file_path' não foi encontrado ou não é válido.")) - - end - 2 data = JSON.parsefile(file_path) - - - 2 if isa(data, Array) # Se for um array - 1 return isempty(data) ? nothing : data[rand(1:length(data))] - 1 elseif isa(data, Dict) # Se for um dicionário - 1 keys_array = collect(keys(data)) - 1 return isempty(keys_array) ? nothing : data[rand(keys_array)] - - else - 0 throw(ArgumentError("O arquivo JSON não é um Array ou Dict suportado.")) - - end - - end - - - - export save_results_to_json, random_csv_row, random_json_object - - - - end diff --git a/test/runtests.jl b/test/runtests.jl index af0de92..2b644a9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,3 @@ -using Coverage using Test using Stressify @@ -7,17 +6,9 @@ include("test_utils.jl") include("test_report.jl") include("test_core.jl") -# Obtenha a cobertura de código -coverage_data = @coverage begin - # Execute seus testes - @testset "Stressify Tests" begin - include("test_utils.jl") - include("test_report.jl") - include("test_core.jl") - end +# Execute os testes +@testset "Stressify Tests" begin + include("test_utils.jl") + include("test_report.jl") + include("test_core.jl") end - -# Gere o relatório de cobertura em formato LCOV -lcov_file = joinpath(pwd(), "coverage.lcov") -write(lcov_file, LCOV.writeLcov(coverage_data)) -println("Cobertura gerada em $lcov_file") diff --git a/test/test_utils.jl.73885.cov b/test/test_utils.jl.73885.cov deleted file mode 100644 index 2781f2d..0000000 --- a/test/test_utils.jl.73885.cov +++ /dev/null @@ -1,57 +0,0 @@ - - using Test - - using CSV - - using Random - - using JSON - - using DataFrames - - using Stressify.Utils - - - - @testset "save_results_to_json" begin - - results = Dict("test" => 123) - - filepath = mktemp()[1] - - Utils.save_results_to_json(results, filepath) - - @test isfile(filepath) - - loaded_results = JSON.parsefile(filepath) - - @test loaded_results == results - - rm(filepath) - - end - - - 1 @testset "random_csv_row" begin - 4 test_data = DataFrame(col1 = [1, 2, 3], col2 = ["a", "b", "c"]) - 1 temp_csv = mktemp()[1] - 1 CSV.write(temp_csv, test_data) - - - 1 random_row = Utils.random_csv_row(temp_csv) - 1 @test isa(random_row, DataFrameRow) - - - 1 for col in names(test_data) - 2 @test random_row[col] in test_data[!, col] - 2 end - - - 1 rm(temp_csv) - 2 @test_throws ArgumentError Utils.random_csv_row("non_existent.csv") - - end - - - - @testset "random_json_object" begin - - - - test_json = Dict("key1" => "value1", "key2" => "value2") - - temp_json = mktemp()[1] - - open(temp_json, "w") do f - 1 JSON.print(f, test_json) - - end - - - - random_obj = Utils.random_json_object(temp_json) - 2 @test any(v -> v == random_obj, values(test_json)) - - @test typeof(random_obj) <: Any - - - - test_json_array = ["a", "b", "c"] - - open(temp_json, "w") do f - 1 JSON.print(f, test_json_array) - - end - - - - random_obj_array = Utils.random_json_object(temp_json) - - @test random_obj_array in test_json_array - - - - @test_throws ArgumentError Utils.random_json_object("non_existent.json") - - - - rm(temp_json) - - end diff --git a/test/test_utils.jl.75010.cov b/test/test_utils.jl.75010.cov deleted file mode 100644 index 0382154..0000000 --- a/test/test_utils.jl.75010.cov +++ /dev/null @@ -1,57 +0,0 @@ - - using Test - - using CSV - - using Random - - using JSON - - using DataFrames - - using Stressify.Utils - - - - @testset "save_results_to_json" begin - - results = Dict("test" => 123) - - filepath = mktemp()[1] - - Utils.save_results_to_json(results, filepath) - - @test isfile(filepath) - - loaded_results = JSON.parsefile(filepath) - - @test loaded_results == results - - rm(filepath) - - end - - - 1 @testset "random_csv_row" begin - 4 test_data = DataFrame(col1 = [1, 2, 3], col2 = ["a", "b", "c"]) - 1 temp_csv = mktemp()[1] - 1 CSV.write(temp_csv, test_data) - - - 1 random_row = Utils.random_csv_row(temp_csv) - 1 @test isa(random_row, DataFrameRow) - - - 1 for col in names(test_data) - 2 @test random_row[col] in test_data[!, col] - 2 end - - - 1 rm(temp_csv) - 2 @test_throws ArgumentError Utils.random_csv_row("non_existent.csv") - - end - - - - @testset "random_json_object" begin - - - - test_json = Dict("key1" => "value1", "key2" => "value2") - - temp_json = mktemp()[1] - - open(temp_json, "w") do f - 1 JSON.print(f, test_json) - - end - - - - random_obj = Utils.random_json_object(temp_json) - 1 @test any(v -> v == random_obj, values(test_json)) - - @test typeof(random_obj) <: Any - - - - test_json_array = ["a", "b", "c"] - - open(temp_json, "w") do f - 1 JSON.print(f, test_json_array) - - end - - - - random_obj_array = Utils.random_json_object(temp_json) - - @test random_obj_array in test_json_array - - - - @test_throws ArgumentError Utils.random_json_object("non_existent.json") - - - - rm(temp_json) - - end