From 8ea6678e3f77a4e153d8b09488be8887c036a65e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Nov 2025 17:06:14 +0000 Subject: [PATCH 1/3] Initial plan From 495198f1d52e7fa49518edbb99bde7d5047d3818 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Nov 2025 17:18:28 +0000 Subject: [PATCH 2/3] Add timeout parameter to source-analyze and resyntax-analyze functions Co-authored-by: jackfirth <8175575+jackfirth@users.noreply.github.com> --- main.rkt | 17 ++++++++++------ private/analysis.rkt | 43 +++++++++++++++++---------------------- test.rkt | 16 ++++++++++++++- test/private/rackunit.rkt | 14 ++++++++++--- 4 files changed, 56 insertions(+), 34 deletions(-) diff --git a/main.rkt b/main.rkt index ab6f0b24..b44a18d3 100644 --- a/main.rkt +++ b/main.rkt @@ -17,7 +17,7 @@ [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? @@ -27,7 +27,7 @@ #:max-modified-lines (or/c exact-nonnegative-integer? +inf.0)) 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?)])) @@ -167,7 +167,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 @@ -210,7 +211,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))) @@ -229,7 +231,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 @@ -251,7 +255,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))) diff --git a/private/analysis.rkt b/private/analysis.rkt index 9fc6b9d8..52ac154c 100644 --- a/private/analysis.rkt +++ b/private/analysis.rkt @@ -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?)] @@ -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)) @@ -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 @@ -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 @@ -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]) @@ -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) @@ -312,12 +306,12 @@ (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) @@ -325,7 +319,8 @@ (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" @@ -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)) @@ -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)) @@ -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)) @@ -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)))) diff --git a/test.rkt b/test.rkt index 9120001a..dad2b487 100644 --- a/test.rkt +++ b/test.rkt @@ -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:number)) + #:with (id ...) (list #'current-analyzer-timeout-millis) + #:with (value ...) (list #'timeout)) + + (pattern (~seq (#:option #:lines (~and line-set (#:range-set _ ...))) + (#:option #:analyzer-timeout-millis timeout:number)) + #:with (id ...) (list #'current-line-mask #'current-analyzer-timeout-millis) + #:with (value ...) (list #'line-set #'timeout)) + + (pattern (~seq (#:option #:analyzer-timeout-millis timeout:number) + (#:option #:lines (~and line-set (#:range-set _ ...)))) + #:with (id ...) (list #'current-analyzer-timeout-millis #'current-line-mask) + #:with (value ...) (list #'timeout #'line-set))) (define-splicing-syntax-class code-block-test-args #:attributes ([check 1]) diff --git a/test/private/rackunit.rkt b/test/private/rackunit.rkt index 76a32b8f..17a9f096 100644 --- a/test/private/rackunit.rkt +++ b/test/private/rackunit.rkt @@ -5,6 +5,7 @@ current-suite-under-test current-header current-line-mask + current-analyzer-timeout-millis clear-header! set-header! clear-suites-under-test! @@ -127,6 +128,8 @@ (define current-line-mask (make-parameter (range-set (unbounded-range #:comparator natural<=>)))) +(define current-analyzer-timeout-millis (make-parameter 100)) + (define (range-bound-add bound amount) (if (unbounded? bound) @@ -178,7 +181,8 @@ (λ () (resyntax-analyze (string-source (code-block-raw-string original-program)) #:suite suite - #:lines modified-line-mask)))) + #:lines modified-line-mask + #:timeout-ms (current-analyzer-timeout-millis))))) (with-check-info* (make-matched-rules-check-info result-set) (λ () @@ -227,7 +231,9 @@ (define result-set (call-with-logs-captured (λ () - (resyntax-analyze (string-source (code-block-raw-string original-program)) #:suite suite)))) + (resyntax-analyze (string-source (code-block-raw-string original-program)) + #:suite suite + #:timeout-ms (current-analyzer-timeout-millis))))) (define refactored-program (modified-source-contents (refactoring-result-set-updated-source result-set))) (with-check-info* (make-matched-rules-check-info result-set) @@ -264,7 +270,9 @@ (define actual-props (call-with-logs-captured - (λ () (reysntax-analyze-for-properties-only program-src #:suite suite)))) + (λ () (reysntax-analyze-for-properties-only program-src + #:suite suite + #:timeout-ms (current-analyzer-timeout-millis))))) (define target-src (string-source (string-trim (code-block-raw-string target)))) (define context-src-list From 0a9fb038777e46263897ea90b0de6297e90cf3e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Nov 2025 17:23:51 +0000 Subject: [PATCH 3/3] Add CLI support for analyzer timeout and test demonstration Co-authored-by: jackfirth <8175575+jackfirth@users.noreply.github.com> --- cli.rkt | 29 +++++++++++++++++++++++------ main.rkt | 17 +++++++++++------ test.rkt | 12 ++++++------ test/analyzer-timeout-test.rkt | 18 ++++++++++++++++++ 4 files changed, 58 insertions(+), 18 deletions(-) create mode 100644 test/analyzer-timeout-test.rkt diff --git a/cli.rkt b/cli.rkt index 3f94f241..f1917b15 100644 --- a/cli.rkt +++ b/cli.rkt @@ -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 @@ -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<=>))) @@ -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" @@ -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." @@ -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) @@ -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" @@ -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 \ @@ -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) @@ -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) @@ -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)] diff --git a/main.rkt b/main.rkt index b44a18d3..bcf381b1 100644 --- a/main.rkt +++ b/main.rkt @@ -24,7 +24,8 @@ #: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? #:timeout-ms exact-nonnegative-integer?) syntax-property-bundle?)] @@ -265,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] @@ -279,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) @@ -303,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 @@ -331,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) @@ -529,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"))) diff --git a/test.rkt b/test.rkt index dad2b487..2703e328 100644 --- a/test.rkt +++ b/test.rkt @@ -149,19 +149,19 @@ #:with (id ...) (list #'current-line-mask) #:with (value ...) (list #'line-set)) - (pattern (~seq (#:option #:analyzer-timeout-millis timeout:number)) + (pattern (~seq (#:option #:analyzer-timeout-millis timeout-value:number)) #:with (id ...) (list #'current-analyzer-timeout-millis) - #:with (value ...) (list #'timeout)) + #:with (value ...) (list #`'#,(syntax-e #'timeout-value))) (pattern (~seq (#:option #:lines (~and line-set (#:range-set _ ...))) - (#:option #:analyzer-timeout-millis timeout:number)) + (#:option #:analyzer-timeout-millis timeout-value:number)) #:with (id ...) (list #'current-line-mask #'current-analyzer-timeout-millis) - #:with (value ...) (list #'line-set #'timeout)) + #:with (value ...) (list #'line-set #`'#,(syntax-e #'timeout-value))) - (pattern (~seq (#:option #:analyzer-timeout-millis timeout:number) + (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 #'timeout #'line-set))) + #:with (value ...) (list #`'#,(syntax-e #'timeout-value) #'line-set))) (define-splicing-syntax-class code-block-test-args #:attributes ([check 1]) diff --git a/test/analyzer-timeout-test.rkt b/test/analyzer-timeout-test.rkt new file mode 100644 index 00000000..90b40630 --- /dev/null +++ b/test/analyzer-timeout-test.rkt @@ -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)