Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions cli.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

(define-enum-type resyntax-output-format (plain-text github-pull-request-review git-commit-message json))
(define-enum-type resyntax-fix-method (modify-files create-multiple-git-commits))
(define-record-type resyntax-analyze-options (targets suite output-format output-destination))
(define-record-type resyntax-analyze-options (targets suite output-format output-destination analyzer-timeout-ms))


(define-record-type resyntax-fix-options
Expand All @@ -48,7 +48,8 @@
max-fixes
max-modified-files
max-modified-lines
max-pass-count))
max-pass-count
analyzer-timeout-ms))


(define all-lines (range-set (unbounded-range #:comparator natural<=>)))
Expand All @@ -60,6 +61,7 @@
(define selected-rule #false)
(define output-format plain-text)
(define output-destination 'console)
(define analyzer-timeout-ms 10000)

(command-line
#:program "resyntax analyze"
Expand Down Expand Up @@ -105,6 +107,11 @@ changed relative to baseref are analyzed."
(define rule-sym (string->symbol rule-name))
(set! selected-rule rule-sym))

("--analyzer-timeout"
timeout-ms
"The timeout in milliseconds for expansion analyzers. Defaults to 10000 (10 seconds)."
(set! analyzer-timeout-ms (string->number timeout-ms)))

("--output-to-file"
outputpath
"Store results in a file instead of printing them to the console."
Expand All @@ -128,7 +135,8 @@ determined by the GITHUB_REPOSITORY and GITHUB_REF environment variables."
#:targets (build-vector targets)
#:suite suite
#:output-format output-format
#:output-destination output-destination))
#:output-destination output-destination
#:analyzer-timeout-ms analyzer-timeout-ms))


(define (resyntax-fix-parse-command-line)
Expand All @@ -143,6 +151,7 @@ determined by the GITHUB_REPOSITORY and GITHUB_REF environment variables."
(define max-pass-count 10)
(define max-modified-files +inf.0)
(define max-modified-lines +inf.0)
(define analyzer-timeout-ms 10000)

(command-line
#:program "resyntax fix"
Expand Down Expand Up @@ -197,6 +206,11 @@ changed relative to baseref are analyzed and fixed."
(define rule-sym (string->symbol rule-name))
(set! selected-rule rule-sym))

("--analyzer-timeout"
timeout-ms
"The timeout in milliseconds for expansion analyzers. Defaults to 10000 (10 seconds)."
(set! analyzer-timeout-ms (string->number timeout-ms)))

("--max-pass-count"
passcount
"The maximum number of times Resyntax will fix each file. By default, Resyntax runs at most 10 \
Expand Down Expand Up @@ -235,7 +249,8 @@ are needed when applying a fix unlocks further fixes."
#:max-fixes max-fixes
#:max-modified-files max-modified-files
#:max-modified-lines max-modified-lines
#:max-pass-count max-pass-count))
#:max-pass-count max-pass-count
#:analyzer-timeout-ms analyzer-timeout-ms))


(define (resyntax-run)
Expand Down Expand Up @@ -273,7 +288,8 @@ For help on these, use 'analyze --help' or 'fix --help'."
(define analysis
(resyntax-analyze-all sources
#:suite (resyntax-analyze-options-suite options)
#:max-passes 1))
#:max-passes 1
#:timeout-ms (resyntax-analyze-options-analyzer-timeout-ms options)))
(define results
(transduce (resyntax-analysis-all-results analysis)
(append-mapping in-hash-values)
Expand Down Expand Up @@ -322,7 +338,8 @@ For help on these, use 'analyze --help' or 'fix --help'."
#:max-fixes (resyntax-fix-options-max-fixes options)
#:max-passes (resyntax-fix-options-max-pass-count options)
#:max-modified-sources max-modified-files
#:max-modified-lines max-modified-lines))
#:max-modified-lines max-modified-lines
#:timeout-ms (resyntax-fix-options-analyzer-timeout-ms options)))
(match fix-method
[(== modify-files)
(resyntax-analysis-write-file-changes! analysis)]
Expand Down
34 changes: 22 additions & 12 deletions main.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@
[resyntax-analysis-write-file-changes! (-> resyntax-analysis? void?)]
[resyntax-analysis-commit-fixes! (-> resyntax-analysis? void?)]
[resyntax-analyze
(->* (source?) (#:suite refactoring-suite? #:lines range-set?) refactoring-result-set?)]
(->* (source?) (#:suite refactoring-suite? #:lines range-set? #:timeout-ms exact-nonnegative-integer?) refactoring-result-set?)]
[resyntax-analyze-all
(->* ((hash/c source? range-set? #:flat? #true))
(#:suite refactoring-suite?
#:max-fixes (or/c exact-nonnegative-integer? +inf.0)
#:max-passes exact-nonnegative-integer?
#:max-modified-sources (or/c exact-nonnegative-integer? +inf.0)
#:max-modified-lines (or/c exact-nonnegative-integer? +inf.0))
#:max-modified-lines (or/c exact-nonnegative-integer? +inf.0)
#:timeout-ms exact-nonnegative-integer?)
resyntax-analysis?)]
[reysntax-analyze-for-properties-only
(->* (source?) (#:suite refactoring-suite?) syntax-property-bundle?)]
(->* (source?) (#:suite refactoring-suite? #:timeout-ms exact-nonnegative-integer?) syntax-property-bundle?)]
[refactor! (-> (sequence/c refactoring-result?) void?)]))


Expand Down Expand Up @@ -167,7 +168,8 @@

(define/guard (resyntax-analyze source
#:suite [suite default-recommendations]
#:lines [lines (range-set (unbounded-range #:comparator natural<=>))])
#:lines [lines (range-set (unbounded-range #:comparator natural<=>))]
#:timeout-ms [timeout-ms 10000])
(define comments (with-input-from-source source read-comment-locations))
(define source-lang (source-read-language source))
(guard source-lang #:else
Expand Down Expand Up @@ -210,7 +212,8 @@
(with-handlers ([exn:fail? skip])
(define analysis (source-analyze source
#:lines lines
#:analyzers (refactoring-suite-analyzers effective-suite)))
#:analyzers (refactoring-suite-analyzers effective-suite)
#:timeout-ms timeout-ms))
(refactor-visited-forms
#:analysis analysis #:suite effective-suite #:comments comments #:lines lines)))

Expand All @@ -229,7 +232,9 @@
[else result-set]))


(define/guard (reysntax-analyze-for-properties-only source #:suite [suite default-recommendations])
(define/guard (reysntax-analyze-for-properties-only source
#:suite [suite default-recommendations]
#:timeout-ms [timeout-ms 10000])
(define comments (with-input-from-source source read-comment-locations))
(define full-source (source->string source))
(guard (string-prefix? full-source "#lang racket") #:else
Expand All @@ -251,7 +256,8 @@
[exn:fail:filesystem:missing-module? skip]
[exn:fail:contract:variable? skip])
(define analysis (source-analyze source
#:analyzers (refactoring-suite-analyzers suite)))
#:analyzers (refactoring-suite-analyzers suite)
#:timeout-ms timeout-ms))
(source-code-analysis-added-syntax-properties analysis)))


Expand All @@ -260,7 +266,8 @@
#:max-fixes [max-fixes +inf.0]
#:max-passes [max-passes 10]
#:max-modified-sources [max-modified-sources +inf.0]
#:max-modified-lines [max-modified-lines +inf.0])
#:max-modified-lines [max-modified-lines +inf.0]
#:timeout-ms [timeout-ms 10000])
(log-resyntax-info "--- analyzing code ---")
(for/fold ([pass-result-lists '()]
[sources sources]
Expand All @@ -274,7 +281,8 @@
#:suite suite
#:max-fixes max-fixes
#:max-modified-sources max-modified-sources
#:max-modified-lines max-modified-lines))
#:max-modified-lines max-modified-lines
#:timeout-ms timeout-ms))
(define pass-fix-count (count-total-results pass-results))
(define new-max-fixes (- max-fixes pass-fix-count))]
#:break (hash-empty? pass-results)
Expand All @@ -298,7 +306,8 @@
#:suite suite
#:max-fixes max-fixes
#:max-modified-sources max-modified-sources
#:max-modified-lines max-modified-lines)
#:max-modified-lines max-modified-lines
#:timeout-ms timeout-ms)
(transduce (in-hash-entries sources) ; entries with source keys and line range set values

;; The following steps perform a kind of layered shuffle: the files to refactor are
Expand Down Expand Up @@ -326,7 +335,7 @@
(append-mapping
(λ (e)
(match-define (entry source lines) e)
(define result-set (resyntax-analyze source #:suite suite #:lines lines))
(define result-set (resyntax-analyze source #:suite suite #:lines lines #:timeout-ms timeout-ms))
(refactoring-result-set-results result-set)))
(limiting max-modified-lines
#:by (λ (result)
Expand Down Expand Up @@ -524,6 +533,7 @@
;; Test with multipass analyze
(define analysis
(resyntax-analyze-all (hash test-source (range-set (unbounded-range #:comparator natural<=>)))
#:suite breaking-suite))
#:suite breaking-suite
#:timeout-ms 10000))
(check-equal? (resyntax-analysis-total-fixes analysis) 0
"Breaking suggestions should be filtered from resyntax-analyze-all")))
43 changes: 19 additions & 24 deletions private/analysis.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

(provide
(contract-out
[source-analyze (->* (source? #:analyzers (sequence/c expansion-analyzer?))
[source-analyze (->* (source? #:analyzers (sequence/c expansion-analyzer?) #:timeout-ms exact-nonnegative-integer?)
(#:lines range-set?)
source-code-analysis?)]
[source-code-analysis? (-> any/c boolean?)]
Expand Down Expand Up @@ -68,15 +68,9 @@
(syntax-ref stx path)))


;; Timeout for analyzers in seconds
(define analyzer-timeout-seconds 10)

;; Milliseconds per second (for time conversions)
(define milliseconds-per-second 1000)

;; Run an analyzer with a timeout. Returns the result or an empty syntax property bundle
;; if timeout occurred or an error was raised.
(define (run-analyzer-with-timeout analyzer expanded source-name)
(define (run-analyzer-with-timeout analyzer expanded source-name timeout-ms)
(define result-box (box #false))
(define error-box (box #false))
(define start-time (current-inexact-milliseconds))
Expand All @@ -87,8 +81,7 @@
(with-handlers ([exn:fail? (λ (e) (set-box! error-box e))])
(set-box! result-box (expansion-analyze analyzer expanded))))))

(define timeout-evt (alarm-evt (+ (current-inexact-milliseconds)
(* analyzer-timeout-seconds milliseconds-per-second))))
(define timeout-evt (alarm-evt (+ (current-inexact-milliseconds) timeout-ms)))

;; Wait for either completion or timeout
(define final-result
Expand All @@ -100,9 +93,9 @@
[(eq? final-result 'timeout)
(kill-thread analyzer-thread)
(log-resyntax-warning
"analyzer ~a timed out after ~a seconds while analyzing ~a"
"analyzer ~a timed out after ~a ms while analyzing ~a"
(object-name analyzer)
analyzer-timeout-seconds
timeout-ms
source-name)
(syntax-property-bundle)]
[else
Expand All @@ -125,7 +118,8 @@

(define (source-analyze code
#:lines [lines (range-set (unbounded-range #:comparator natural<=>))]
#:analyzers analyzers)
#:analyzers analyzers
#:timeout-ms timeout-ms)
(define ns (make-base-namespace))
(parameterize ([current-directory (or (source-directory code) (current-directory))]
[current-namespace ns])
Expand Down Expand Up @@ -245,7 +239,7 @@
(transduce analyzers
(append-mapping
(λ (analyzer)
(define result (run-analyzer-with-timeout analyzer expanded program-source-name))
(define result (run-analyzer-with-timeout analyzer expanded program-source-name timeout-ms))
(syntax-property-bundle-entries result)))
(filtering
(λ (prop-entry)
Expand Down Expand Up @@ -312,20 +306,21 @@
(define test-source (string-source "#lang racket/base (define x 1)"))

;; Test with empty analyzers list
(define analysis-empty (source-analyze test-source #:analyzers '()))
(define analysis-empty (source-analyze test-source #:analyzers '() #:timeout-ms 10000))
(check-true (source-code-analysis? analysis-empty))

;; Test with single analyzer
(define analysis-single
(source-analyze test-source #:analyzers (list identifier-usage-analyzer)))
(source-analyze test-source #:analyzers (list identifier-usage-analyzer) #:timeout-ms 10000))
(check-true (source-code-analysis? analysis-single))

;; Test with default analyzers (should match default behavior)
(define analysis-default
(source-analyze test-source
#:analyzers (list identifier-usage-analyzer
ignored-result-values-analyzer
variable-mutability-analyzer)))
variable-mutability-analyzer)
#:timeout-ms 10000))
(check-true (source-code-analysis? analysis-default)))

(test-case "source-analyze filters out properties with invalid paths"
Expand All @@ -345,7 +340,7 @@
(syntax-property-entry (syntax-path (list 0 999)) 'another-invalid-prop #true)))))

;; Run analysis with the bad analyzer - should not crash
(define analysis (source-analyze test-source #:analyzers (list bad-analyzer)))
(define analysis (source-analyze test-source #:analyzers (list bad-analyzer) #:timeout-ms 10000))

;; Check that the analysis completed successfully
(check-true (source-code-analysis? analysis))
Expand All @@ -372,13 +367,13 @@
(make-expansion-analyzer
#:name 'slow-analyzer
(λ (expanded)
;; Sleep for 11 seconds - slightly longer than the 10 second timeout
(sleep 11)
;; Sleep for 200ms - longer than a 100ms timeout
(sleep 0.2)
(syntax-property-bundle
(syntax-property-entry empty-syntax-path 'slow-prop #true)))))

;; Run analysis with the slow analyzer - should timeout and not crash
(define analysis (source-analyze test-source #:analyzers (list slow-analyzer)))
;; Run analysis with the slow analyzer and a short timeout - should timeout and not crash
(define analysis (source-analyze test-source #:analyzers (list slow-analyzer) #:timeout-ms 100))

;; Check that the analysis completed (even though the analyzer timed out)
(check-true (source-code-analysis? analysis))
Expand All @@ -403,7 +398,7 @@
(syntax-property-entry empty-syntax-path 'fast-prop #true)))))

;; Run analysis with the fast analyzer - should complete successfully
(define analysis (source-analyze test-source #:analyzers (list fast-analyzer)))
(define analysis (source-analyze test-source #:analyzers (list fast-analyzer) #:timeout-ms 10000))

;; Check that the analysis completed
(check-true (source-code-analysis? analysis))
Expand All @@ -427,7 +422,7 @@
(error "intentional test error"))))

;; Run analysis with the failing analyzer - should skip it and not crash
(define analysis (source-analyze test-source #:analyzers (list failing-analyzer)))
(define analysis (source-analyze test-source #:analyzers (list failing-analyzer) #:timeout-ms 10000))

;; Check that the analysis completed (even though the analyzer failed)
(check-true (source-code-analysis? analysis))))
16 changes: 15 additions & 1 deletion test.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,21 @@

(pattern (~seq (#:option #:lines (~and line-set (#:range-set _ ...))))
#:with (id ...) (list #'current-line-mask)
#:with (value ...) (list #'line-set)))
#:with (value ...) (list #'line-set))

(pattern (~seq (#:option #:analyzer-timeout-millis timeout-value:number))
#:with (id ...) (list #'current-analyzer-timeout-millis)
#:with (value ...) (list #`'#,(syntax-e #'timeout-value)))

(pattern (~seq (#:option #:lines (~and line-set (#:range-set _ ...)))
(#:option #:analyzer-timeout-millis timeout-value:number))
#:with (id ...) (list #'current-line-mask #'current-analyzer-timeout-millis)
#:with (value ...) (list #'line-set #`'#,(syntax-e #'timeout-value)))

(pattern (~seq (#:option #:analyzer-timeout-millis timeout-value:number)
(#:option #:lines (~and line-set (#:range-set _ ...))))
#:with (id ...) (list #'current-analyzer-timeout-millis #'current-line-mask)
#:with (value ...) (list #`'#,(syntax-e #'timeout-value) #'line-set)))

(define-splicing-syntax-class code-block-test-args
#:attributes ([check 1])
Expand Down
18 changes: 18 additions & 0 deletions test/analyzer-timeout-test.rkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#lang resyntax/test


header:
------------------------------
#lang racket/base
------------------------------


test: "timeout parameter can be customized in tests"
@analyzer-timeout-millis 5000
- (or 1 (or 2 3))
- (or 1 2 3)


no-change-test: "timeout parameter works with no-change-test"
@analyzer-timeout-millis 5000
- (define x 42)
Loading
Loading