diff options
900 files changed, 24407 insertions, 8845 deletions
diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index bdb9011f25..16d1e4a85a 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -25,7 +25,7 @@ jobs: bundler: none - run: echo "make=make -sj$((1 + $(nproc --all)))" >> $GITHUB_ENV - run: sudo apt-get install build-essential autoconf bison - - run: autoconf + - run: ./autogen.sh - run: ./configure --disable-install-doc - run: $make update-unicode - run: $make common-srcs diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index c878caddeb..26d2ca473d 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -17,14 +17,14 @@ jobs: if: "contains(matrix.os, 'ubuntu')" - name: Install libraries run: | - brew upgrade + brew update brew install gdbm gmp libffi openssl@1.1 zlib autoconf automake libtool readline if: "contains(matrix.os, 'macos')" - name: git config run: | git config --global advice.detachedHead 0 - uses: actions/checkout@v2 - - run: autoconf + - run: ./autogen.sh - name: Run configure run: ./configure -C --disable-install-doc --disable-rubygems --with-gcc 'optflags=-O0' 'debugflags=-save-temps=obj -g' - run: make all golf diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 2f4bbc7093..576341bf88 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -3,7 +3,7 @@ on: [push, pull_request] jobs: checks: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - name: Check if C-sources are US-ASCII diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9fbabd35c6..2dc47c0683 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,8 +9,8 @@ on: jobs: CodeQL-Build: - # CodeQL runs on ubuntu-latest and windows-latest - runs-on: ubuntu-latest + # CodeQL runs on ubuntu-20.04 and windows-latest + runs-on: ubuntu-20.04 steps: - name: Install libraries @@ -31,13 +31,13 @@ jobs: run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: cpp config-file: ./.github/codeql/codeql-config.yml - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index 532e4589e2..47b73fb907 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -1,17 +1,31 @@ name: Compilations -on: [push, pull_request] +on: + push: + paths-ignore: + - 'doc/**' + - '**.md' + - '**.rdoc' + pull_request: + paths-ignore: + - 'doc/**' + - '**.md' + - '**.rdoc' + +concurrency: + group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} + cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} # Github actions does not support YAML anchors. This creative use of -# environment variables (plus the "echo ::set-env" hack) is to reroute that +# environment variables (plus the "echo $GITHUB_ENV" hack) is to reroute that # restriction. env: - default_cc: clang-11 + default_cc: clang-14 append_cc: '' crosshost: '' # -O1 is faster than -O3 in our tests... Majority of time are consumed trying - # to optimize binaries. Also Github Actions runs on a relatively modern CPU + # to optimize binaries. Also Github Actions run on relatively modern CPUs # compared to, say, GCC 4 or Clang 3. We don't specify `-march=native` # because compilers tend not understand what the CPU is. optflags: '-O1' @@ -47,37 +61,52 @@ jobs: fail-fast: false matrix: entry: - - { key: default_cc, name: gcc-10, value: gcc-10 } -# - { key: default_cc, name: gcc-9, value: gcc-9 } -# - { key: default_cc, name: gcc-8, value: gcc-8 } -# - { key: default_cc, name: gcc-7, value: gcc-7 } -# - { key: default_cc, name: gcc-6, value: gcc-6 } -# - { key: default_cc, name: gcc-5, value: gcc-5 } - - { key: default_cc, name: gcc-4.8, value: gcc-4.8 } - - { key: default_cc, name: clang-12, value: clang-12 } -# - { key: default_cc, name: clang-11, value: clang-11 } -# - { key: default_cc, name: clang-10, value: clang-10 } -# - { key: default_cc, name: clang-9, value: clang-9 } -# - { key: default_cc, name: clang-8, value: clang-8 } -# - { key: default_cc, name: clang-7, value: clang-7 } -# - { key: default_cc, name: clang-6.0, value: clang-6.0 } -# - { key: default_cc, name: clang-5.0, value: clang-5.0 } -# - { key: default_cc, name: clang-4.0, value: clang-4.0 } - - { key: default_cc, name: clang-3.9, value: clang-3.9 } - - - { key: crosshost, name: aarch64-linux-gnu, value: aarch64-linux-gnu } + - { key: default_cc, name: gcc-11, value: gcc-11, container: gcc-11 } + - { key: default_cc, name: gcc-10, value: gcc-10, container: gcc-10 } + - { key: default_cc, name: gcc-9, value: gcc-9, container: gcc-9 } + - { key: default_cc, name: gcc-8, value: gcc-8, container: gcc-8 } + - { key: default_cc, name: gcc-7, value: gcc-7, container: gcc-7 } + - { key: default_cc, name: gcc-6, value: gcc-6, container: gcc-6 } + - { key: default_cc, name: gcc-5, value: gcc-5, container: gcc-5 } + - { key: default_cc, name: gcc-4.8, value: gcc-4.8, container: gcc-4.8 } + - key: default_cc + name: 'gcc-11 LTO' + value: 'gcc-11 -O2 -flto=auto -ffat-lto-objects' + container: gcc-11 + shared: '--disable-shared' + # check: true + - { key: default_cc, name: clang-14, value: clang-14, container: clang-14 } + - { key: default_cc, name: clang-13, value: clang-13, container: clang-13 } + - { key: default_cc, name: clang-12, value: clang-12, container: clang-12 } + - { key: default_cc, name: clang-11, value: clang-11, container: clang-11 } + - { key: default_cc, name: clang-10, value: clang-10, container: clang-10 } + - { key: default_cc, name: clang-9, value: clang-9, container: clang-9 } + - { key: default_cc, name: clang-8, value: clang-8, container: clang-8 } + - { key: default_cc, name: clang-7, value: clang-7, container: clang-7 } + - { key: default_cc, name: clang-6.0, value: clang-6.0, container: clang-6.0 } + - { key: default_cc, name: clang-5.0, value: clang-5.0, container: clang-5.0 } + - { key: default_cc, name: clang-4.0, value: clang-4.0, container: clang-4.0 } + - { key: default_cc, name: clang-3.9, value: clang-3.9, container: clang-3.9 } + - key: default_cc + name: 'clang-14 LTO' + value: 'clang-14 -O2 -flto=auto' + container: clang-14 + shared: '--disable-shared' + # check: true + + - { key: crosshost, name: aarch64-linux-gnu, value: aarch64-linux-gnu, container: crossbuild-essential-arm64 } # - { key: crosshost, name: arm-linux-gnueabi, value: arm-linux-gnueabi } # - { key: crosshost, name: arm-linux-gnueabihf, value: arm-linux-gnueabihf } # - { key: crosshost, name: i686-w64-mingw32, value: i686-w64-mingw32 } # - { key: crosshost, name: powerpc-linux-gnu, value: powerpc-linux-gnu } - - { key: crosshost, name: powerpc64le-linux-gnu, value: powerpc64le-linux-gnu } - - { key: crosshost, name: s390x-linux-gnu, value: s390x-linux-gnu } - - { key: crosshost, name: x86_64-w64-mingw32, value: x86_64-w64-mingw32 } + - { key: crosshost, name: powerpc64le-linux-gnu, value: powerpc64le-linux-gnu, container: crossbuild-essential-ppc64el } + - { key: crosshost, name: s390x-linux-gnu, value: s390x-linux-gnu, container: crossbuild-essential-s390x } + - { key: crosshost, name: x86_64-w64-mingw32, value: x86_64-w64-mingw32, container: mingw-w64 } - { key: append_cc, name: c99, value: '-std=c99 -Werror=pedantic -pedantic-errors' } # - { key: append_cc, name: c11, value: '-std=c11 -Werror=pedantic -pedantic-errors' } # - { key: append_cc, name: c17, value: '-std=c17 -Werror=pedantic -pedantic-errors' } - - { key: append_cc, name: c2x, value: '-std=c2x -Werror=pedantic -pedantic-errors' } +# - { key: append_cc, name: c2x, value: '-std=c2x -Werror=pedantic -pedantic-errors' } - { key: CXXFLAGS, name: c++98, value: '-std=c++98 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } # - { key: CXXFLAGS, name: c++11, value: '-std=c++11 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } # - { key: CXXFLAGS, name: c++14, value: '-std=c++14 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } @@ -85,7 +114,7 @@ jobs: - { key: CXXFLAGS, name: c++2a, value: '-std=c++2a -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } - { key: optflags, name: '-O0', value: '-O0 -march=x86-64 -mtune=generic' } -# - { key: optflags, name: '-O3', value: '-O3 -march=x86-64 -mtune=generic' } +# - { key: optflags, name: '-O3', value: '-O3 -march=x86-64 -mtune=generic', check: true } - { key: append_configure, name: gmp, value: '--with-gmp' } - { key: append_configure, name: jemalloc, value: '--with-jemalloc' } @@ -130,7 +159,7 @@ jobs: - { key: cppflags, name: DEBUG_FIND_TIME_NUMGUESS, value: '-DDEBUG_FIND_TIME_NUMGUESS' } - { key: cppflags, name: DEBUG_INTEGER_PACK, value: '-DDEBUG_INTEGER_PACK' } - - { key: cppflags, name: ENABLE_PATH_CHECK, value: '-DENABLE_PATH_CHECK' } +# - { key: cppflags, name: ENABLE_PATH_CHECK, value: '-DENABLE_PATH_CHECK' } - { key: cppflags, name: GC_DEBUG_STRESS_TO_CLASS, value: '-DGC_DEBUG_STRESS_TO_CLASS' } # - { key: cppflags, name: GC_ENABLE_LAZY_SWEEP=0, value: '-DGC_ENABLE_LAZY_SWEEP=0' } @@ -152,50 +181,50 @@ jobs: # - { key: cppflags, name: VM_DEBUG_VERIFY_METHOD_CACHE, value: '-DVM_DEBUG_VERIFY_METHOD_CACHE' } - { key: cppflags, name: MJIT_FORCE_ENABLE, value: '-DMJIT_FORCE_ENABLE' } + - { key: cppflags, name: YJIT_FORCE_ENABLE, value: '-DYJIT_FORCE_ENABLE' } name: ${{ matrix.entry.name }} runs-on: ubuntu-latest - container: ghcr.io/ruby/ruby-ci-image:latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" + container: + image: ghcr.io/ruby/ruby-ci-image:${{ matrix.entry.container || 'clang-14' }} + options: --user root + if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: + - run: id + working-directory: - run: mkdir build working-directory: - name: setenv run: | echo "${{ matrix.entry.key }}=${{ matrix.entry.value }}" >> $GITHUB_ENV - echo "make=make -sj$((1 + $(nproc --all)))" >> $GITHUB_ENV - - uses: actions/checkout@v2 + echo "GNUMAKEFLAGS=-sj$((1 + $(nproc --all)))" >> $GITHUB_ENV + - uses: actions/checkout@v3 with: path: src - - run: autoconf + - uses: actions/cache@v3 + with: + path: src/.downloaded-cache + key: downloaded-cache + - run: ./autogen.sh working-directory: src - name: Run configure - run: | - if [ -n "${crosshost}" ]; then - ../src/configure -C \ - ${default_configure} \ - ${append_configure} \ - --host="${crosshost}" - else - ../src/configure -C \ - ${default_configure} \ - ${append_configure} \ - --with-gcc="${default_cc} ${append_cc}" - fi - - run: $make extract-extlibs - - run: $make incs - - run: $make - - run: $make test - - run: $make install - if: "matrix.entry.name == '-O3'" - - run: /usr/local/bin/gem install --no-doc timezone tzinfo - if: "matrix.entry.name == '-O3'" - - run: $make test-tool - if: "matrix.entry.name == '-O3'" - - run: $make test-all TESTS='-- ruby -ext-' - if: "matrix.entry.name == '-O3'" - - run: $make test-spec - if: "matrix.entry.name == '-O3'" + run: > + ../src/configure -C ${default_configure} ${append_configure} + ${{ matrix.entry.key == 'crosshost' && '--host="${crosshost}"' || '--with-gcc="${default_cc} ${append_cc}"' }} + ${{ matrix.entry.shared || '--enable-shared' }} + - run: make extract-extlibs + - run: make incs + - run: make +# - run: make leaked-globals + - run: make test + - run: make install + if: ${{ matrix.entry.check }} + - run: make test-tool + if: ${{ matrix.entry.check }} + - run: make test-all TESTS='-- ruby -ext-' + if: ${{ matrix.entry.check }} + - run: make test-spec + if: ${{ matrix.entry.check }} - uses: k0kubun/action-slack@v2.0.0 with: @@ -209,7 +238,7 @@ jobs: } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: failure() && github.event_name == 'push' + if: ${{ failure() && github.event_name == 'push' }} defaults: run: diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index f0355258f1..1da94630fe 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -5,7 +5,7 @@ jobs: runs-on: macos-latest strategy: matrix: - test_task: [ "check", "test-bundler-parallel", "test-bundled-gems", "leaked-globals" ] + test_task: [ "check" ] #, "test-bundler-parallel", "test-bundled-gems" ] matrix tests has unknown issues fail-fast: false env: GITPULLOPTIONS: --no-tags origin ${{github.ref}} @@ -26,18 +26,20 @@ jobs: - name: Install libraries run: | export WAITS='5 60' - tool/travis_retry.sh brew upgrade + tool/travis_retry.sh brew update tool/travis_retry.sh brew install gdbm gmp libffi openssl@1.1 zlib autoconf automake libtool readline working-directory: src - name: Set ENV run: | echo "JOBS=-j$((1 + $(sysctl -n hw.activecpu)))" >> $GITHUB_ENV - - run: autoconf + - run: ./autogen.sh working-directory: src - name: Run configure run: ../src/configure -C --disable-install-doc --with-openssl-dir=$(brew --prefix openssl@1.1) --with-readline-dir=$(brew --prefix readline) - run: make $JOBS incs - run: make $JOBS + - run: make leaked-globals + if: matrix.test_task == 'check' - run: make prepare-gems if: matrix.test_task == 'check' - run: make $JOBS -s ${{ matrix.test_task }} diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index c0366f2942..a5d64c7b2d 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -6,7 +6,8 @@ on: [push, pull_request] # jobs: make: - runs-on: windows-2019 + runs-on: windows-2022 + name: ${{ github.workflow }} (${{ matrix.msystem }}) env: MSYSTEM: MINGW64 MSYSTEM_PREFIX: /mingw64 @@ -35,7 +36,7 @@ jobs: with: path: src - name: Set up Ruby & MSYS2 - uses: MSP-Greg/setup-ruby-pkgs@v1 + uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 mingw: _upgrade_ gdbm gmp libffi libyaml openssl ragel readline @@ -50,13 +51,12 @@ jobs: if ($rslt.contains($e)) { Write-Host $rslt } else { Write-Host "`nCan't find $e" } } - - name: misc setup, autoreconf + + - name: autogen run: | - mkdir install - mkdir temp - cd src - sh -c "autoreconf -fi" - working-directory: + ./autogen.sh + working-directory: src + shell: sh - name: configure run: | @@ -66,7 +66,7 @@ jobs: [Console]::InputEncoding = [System.Text.Encoding]::GetEncoding("IBM437") $config_args = "--build=$env:CHOST --host=$env:CHOST --target=$env:CHOST" Write-Host $config_args - sh -c "../src/configure --disable-install-doc --prefix=/install $config_args" + sh -c "../src/configure --disable-install-doc --prefix=/. $config_args" # Write-Host "-------------------------------------- config.log" # Get-Content ./config.log | foreach {Write-Output $_} @@ -92,7 +92,7 @@ jobs: $PSDefaultParameterValues['*:Encoding'] = 'utf8' [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("IBM437") [Console]::InputEncoding = [System.Text.Encoding]::GetEncoding("IBM437") - make DESTDIR=.. install-nodoc + make DESTDIR=../install install-nodoc - name: test timeout-minutes: 5 @@ -127,7 +127,7 @@ jobs: payload: | { "ci": "GitHub Actions", - "env": "${{ github.workflow }} / ${{ matrix.test_task }}", + "env": "${{ github.workflow }} ${{ matrix.msystem }} / ${{ matrix.test_task }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", "branch": "${{ github.ref }}".split('/').reverse()[0] diff --git a/.github/workflows/mjit.yml b/.github/workflows/mjit.yml index 68889ecf3d..8dcbcf7760 100644 --- a/.github/workflows/mjit.yml +++ b/.github/workflows/mjit.yml @@ -4,10 +4,10 @@ jobs: make: strategy: matrix: - test_task: [ "check" ] # to make job names consistent - jit_opts: [ "--jit", "--jit-wait" ] + test_task: [check] # to make job names consistent + jit_opts: [--jit-wait] fail-fast: false - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 if: "!contains(github.event.head_commit.message, '[ci skip]')" env: TESTOPTS: '-q --tty=no' @@ -34,29 +34,29 @@ jobs: sudo bash -c 'IFS=:; for d in '"$PATH"'; do chmod -v go-w $d; done' || : - name: Set ENV run: | - echo "JOBS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV - - run: autoconf + echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV + - run: ./autogen.sh working-directory: src - name: Run configure - run: ../src/configure -C --disable-install-doc - - run: make $JOBS incs - - run: make $JOBS - - run: sudo make $JOBS -s install + run: ../src/configure -C --disable-install-doc cppflags=-DVM_CHECK_MODE + - run: make incs + - run: make + - run: sudo make -s install - run: sudo apt-get install gdb # used by test / test-all failure - name: Run test run: | ulimit -c unlimited - make $JOBS -s test RUN_OPTS="$RUN_OPTS" - timeout-minutes: 60 - - name: Run test-all - run: | - ulimit -c unlimited - make $JOBS -s test-all RUN_OPTS="$RUN_OPTS" + make -s test RUN_OPTS="$RUN_OPTS" timeout-minutes: 60 + # - name: Run test-all + # run: | + # ulimit -c unlimited + # make -s test-all RUN_OPTS="$RUN_OPTS" + # timeout-minutes: 60 - name: Run test-spec run: | ulimit -c unlimited - make $JOBS -s test-spec RUN_OPTS="$RUN_OPTS" + make -s test-spec RUN_OPTS="$RUN_OPTS" timeout-minutes: 60 - uses: k0kubun/action-slack@v2.0.0 with: diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index fe517d2750..93187699be 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -4,7 +4,7 @@ jobs: make: strategy: matrix: - test_task: [ "check", "test-bundler-parallel", "test-bundled-gems", "test-all TESTS=--repeat-count=2", "leaked-globals" ] + test_task: [ "check", "test-bundler-parallel", "test-bundled-gems", "test-all TESTS=--repeat-count=2" ] os: - ubuntu-20.04 # - ubuntu-18.04 @@ -17,14 +17,10 @@ jobs: os: ubuntu-16.04 - test_task: "test-all TESTS=--repeat-count=2" os: ubuntu-16.04 - - test_task: leaked-globals - os: ubuntu-16.04 - os: ubuntu-16.04 debug: -DRUBY_DEBUG - test_task: "test-all TESTS=--repeat-count=2" debug: -DRUBY_DEBUG - - test_task: leaked-globals - debug: -DRUBY_DEBUG fail-fast: false env: GITPULLOPTIONS: --no-tags origin ${{github.ref}} @@ -53,12 +49,14 @@ jobs: - name: Set ENV run: | echo "JOBS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV - - run: autoconf + - run: ./autogen.sh working-directory: src - name: Run configure run: ../src/configure -C --disable-install-doc cppflags=${{ matrix.debug }} - run: make $JOBS incs - run: make $JOBS + - run: make leaked-globals + if: matrix.test_task == 'check' - run: make prepare-gems if: matrix.test_task == 'check' - name: Create dummy files in build dir diff --git a/README.ja.md b/README.ja.md index bee6433c62..6663b94680 100644 --- a/README.ja.md +++ b/README.ja.md @@ -52,11 +52,11 @@ Rubyリãƒã‚¸ãƒˆãƒªã®æœ¬æ¥ã®master㯠https://git.ruby-lang.org/ruby.git ã«ã ### Subversion -å¤ã„Rubyã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®ã‚½ãƒ¼ã‚¹ã‚³ãƒ¼ãƒ‰ã¯æ¬¡ã®ã‚³ãƒžãƒ³ãƒ‰ã§å–å¾—ã§ãã¾ã™ï¼Ž +å¤ã„Rubyã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®ã‚½ãƒ¼ã‚¹ã‚³ãƒ¼ãƒ‰ã¯æ¬¡ã®ã‚³ãƒžãƒ³ãƒ‰ã§ã‚‚å–å¾—ã§ãã¾ã™ï¼Ž $ svn co https://svn.ruby-lang.org/repos/ruby/branches/ruby_2_6/ ruby -ä»–ã«é–‹ç™ºä¸ã®ãƒ–ランãƒã®ä¸€è¦§ã¯æ¬¡ã®ã‚³ãƒžãƒ³ãƒ‰ã§è¦‹ã‚‰ã‚Œã¾ã™ï¼Ž +ä»–ã®ãƒ–ランãƒã®ä¸€è¦§ã¯æ¬¡ã®ã‚³ãƒžãƒ³ãƒ‰ã§è¦‹ã‚‰ã‚Œã¾ã™ï¼Ž $ svn ls https://svn.ruby-lang.org/repos/ruby/branches/ @@ -71,31 +71,26 @@ https://www.ruby-lang.org/ ## メーリングリスト -Rubyã®ãƒ¡ãƒ¼ãƒªãƒ³ã‚°ãƒªã‚¹ãƒˆãŒã‚ã‚Šã¾ã™ï¼Žå‚åŠ å¸Œæœ›ã®æ–¹ã¯ - -mailto:ruby-list-request@ruby-lang.org - -ã¾ã§æœ¬æ–‡ã« +Rubyã®ãƒ¡ãƒ¼ãƒªãƒ³ã‚°ãƒªã‚¹ãƒˆãŒã‚ã‚Šã¾ã™ï¼Žå‚åŠ å¸Œæœ›ã®æ–¹ã¯ [ruby-list-request@ruby-lang.org] ã¾ã§æœ¬æ–‡ã« subscribe ã¨æ›¸ã„ã¦é€ã£ã¦ä¸‹ã•ã„. -Ruby開発者å‘ã‘メーリングリストもã‚ã‚Šã¾ã™ï¼Žã“ã¡ã‚‰ã§ã¯rubyã®ãƒã‚°ï¼Œå°†æ¥ã®ä»•æ§˜æ‹¡å¼µãªã©å®Ÿè£…上ã®å•é¡Œã«ã¤ã„ã¦è°è«–ã•ã‚Œã¦ã„ã¾ã™ï¼Ž å‚åŠ å¸Œæœ›ã®æ–¹ã¯ - -mailto:ruby-dev-request@ruby-lang.org - -ã¾ã§ruby-listã¨åŒæ§˜ã®æ–¹æ³•ã§ãƒ¡ãƒ¼ãƒ«ã—ã¦ãã ã•ã„. +Ruby開発者å‘ã‘メーリングリストもã‚ã‚Šã¾ã™ï¼Žã“ã¡ã‚‰ã§ã¯rubyã®ãƒã‚°ï¼Œå°†æ¥ã®ä»•æ§˜æ‹¡å¼µãªã©å®Ÿè£…上ã®å•é¡Œã«ã¤ã„ã¦è°è«–ã•ã‚Œã¦ã„ã¾ã™ï¼Ž +å‚åŠ å¸Œæœ›ã®æ–¹ã¯ [ruby-dev-request@ruby-lang.org] ã¾ã§ruby-listã¨åŒæ§˜ã®æ–¹æ³•ã§ãƒ¡ãƒ¼ãƒ«ã—ã¦ãã ã•ã„. Ruby拡張モジュールã«ã¤ã„ã¦è©±ã—åˆã†ruby-extメーリングリストã¨æ•°å¦é–¢ä¿‚ã®è©±é¡Œã«ã¤ã„ã¦è©±ã—åˆã†ruby-mathメーリングリスト㨠英語ã§rubyã«ã¤ã„ã¦è©±ã—åˆã†ruby-talkメーリングリストもã‚ã‚Šã¾ã™ï¼Žå‚åŠ æ–¹æ³•ã¯ã©ã‚Œã‚‚åŒã˜ã§ã™ï¼Ž +[ruby-list-request@ruby-lang.org]: mailto:ruby-list-request@ruby-lang.org?subject=Join%20Ruby%20Mailing%20List&body=subscribe +[ruby-dev-request@ruby-lang.org]: mailto:ruby-dev-request@ruby-lang.org?subject=Join%20Ruby%20Mailing%20List&body=subscribe + ## コンパイル・インストール 以下ã®æ‰‹é †ã§è¡Œã£ã¦ãã ã•ã„. -1. ã‚‚ã— `configure` ファイルãŒè¦‹ã¤ã‹ã‚‰ãªã„,もã—ã㯠`configure.ac` よりå¤ã„よã†ãªã‚‰ï¼Œ `autoconf` を実行ã—㦠- æ–°ã—ã `configure` を生æˆã™ã‚‹ +1. (Gitリãƒã‚¸ãƒˆãƒªã‹ã‚‰å–å¾—ã—ãŸã‚½ãƒ¼ã‚¹ã‚’ビルドã™ã‚‹å ´åˆ) `./autogen.sh` を実行ã—ã¦æ–°ã—ã `configure` を生æˆã™ã‚‹ 2. `configure` を実行ã—㦠`Makefile` ãªã©ã‚’生æˆã™ã‚‹ @@ -51,7 +51,8 @@ if you are a committer. ### Subversion -Stable branches for older Ruby versions can be checked out with the following command: +Stable branches for older Ruby versions can be checked out with also the +following command: $ svn co https://svn.ruby-lang.org/repos/ruby/branches/ruby_2_6/ ruby @@ -71,16 +72,17 @@ send the following phrase: subscribe -in the mail body (not subject) to the address -[ruby-talk-request@ruby-lang.org](mailto:ruby-talk-request@ruby-lang.org?subject=Join%20Ruby%20Mailing%20List&body=subscribe). +in the mail body (not subject) to the address [ruby-talk-request@ruby-lang.org]. + +[ruby-talk-request@ruby-lang.org]: mailto:ruby-talk-request@ruby-lang.org?subject=Join%20Ruby%20Mailing%20List&body=subscribe ## How to compile and install 1. If you want to use Microsoft Visual C++ to compile Ruby, read [win32/README.win32](win32/README.win32) instead of this document. -2. If `./configure` does not exist or is older than `configure.ac`, run - `autoconf` to (re)generate configure. +2. Run `./autogen.sh` to generate configure, when you build the source checked + out from the Git repository. 3. Run `./configure`, which will generate `config.h` and `Makefile`. diff --git a/aclocal.m4 b/aclocal.m4 index 940d91e83f..e69de29bb2 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -1,48 +0,0 @@ -# generated automatically by aclocal 1.16.2 -*- Autoconf -*- - -# Copyright (C) 1996-2020 Free Software Foundation, Inc. - -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY, to the extent permitted by law; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A -# PARTICULAR PURPOSE. - -m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) -m4_include([tool/m4/_colorize_result_prepare.m4]) -m4_include([tool/m4/ac_msg_result.m4]) -m4_include([tool/m4/colorize_result.m4]) -m4_include([tool/m4/ruby_append_option.m4]) -m4_include([tool/m4/ruby_append_options.m4]) -m4_include([tool/m4/ruby_check_builtin_func.m4]) -m4_include([tool/m4/ruby_check_builtin_setjmp.m4]) -m4_include([tool/m4/ruby_check_printf_prefix.m4]) -m4_include([tool/m4/ruby_check_setjmp.m4]) -m4_include([tool/m4/ruby_check_signedness.m4]) -m4_include([tool/m4/ruby_check_sizeof.m4]) -m4_include([tool/m4/ruby_check_sysconf.m4]) -m4_include([tool/m4/ruby_cppoutfile.m4]) -m4_include([tool/m4/ruby_decl_attribute.m4]) -m4_include([tool/m4/ruby_default_arch.m4]) -m4_include([tool/m4/ruby_define_if.m4]) -m4_include([tool/m4/ruby_defint.m4]) -m4_include([tool/m4/ruby_dtrace_available.m4]) -m4_include([tool/m4/ruby_dtrace_postprocess.m4]) -m4_include([tool/m4/ruby_func_attribute.m4]) -m4_include([tool/m4/ruby_mingw32.m4]) -m4_include([tool/m4/ruby_prepend_option.m4]) -m4_include([tool/m4/ruby_prog_gnu_ld.m4]) -m4_include([tool/m4/ruby_replace_funcs.m4]) -m4_include([tool/m4/ruby_replace_type.m4]) -m4_include([tool/m4/ruby_rm_recursive.m4]) -m4_include([tool/m4/ruby_setjmp_type.m4]) -m4_include([tool/m4/ruby_stack_grow_direction.m4]) -m4_include([tool/m4/ruby_try_cflags.m4]) -m4_include([tool/m4/ruby_try_cxxflags.m4]) -m4_include([tool/m4/ruby_try_ldflags.m4]) -m4_include([tool/m4/ruby_type_attribute.m4]) -m4_include([tool/m4/ruby_universal_arch.m4]) -m4_include([tool/m4/ruby_werror_flag.m4]) diff --git a/addr2line.c b/addr2line.c index 9669427d36..0029cffbca 100644 --- a/addr2line.c +++ b/addr2line.c @@ -437,7 +437,8 @@ parse_debug_line_cu(int num_traces, void **traces, char **debug_line, addr += a; break; case DW_LNS_fixed_advance_pc: - a = *(unsigned char *)p++; + a = *(uint16_t *)p; + p += sizeof(uint16_t); addr += a; break; case DW_LNS_set_prologue_end: @@ -1625,6 +1626,7 @@ debug_info_read(DebugInfoReader *reader, int num_traces, void **traces, static unsigned long uncompress_debug_section(ElfW(Shdr) *shdr, char *file, char **ptr) { + *ptr = NULL; #ifdef SUPPORT_COMPRESSED_DEBUG_LINE ElfW(Chdr) *chdr = (ElfW(Chdr) *)(file + shdr->sh_offset); unsigned long destsize = chdr->ch_size; @@ -1645,6 +1647,7 @@ uncompress_debug_section(ElfW(Shdr) *shdr, char *file, char **ptr) fail: free(*ptr); + *ptr = NULL; #endif return 0; } diff --git a/appveyor.yml b/appveyor.yml index d31d4b2df9..78e3102008 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,12 +8,11 @@ platform: - x64 environment: ruby_version: "24-%Platform%" - zlib_version: "1.2.11" matrix: - build: vs vs: 120 ssl: OpenSSL - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 GEMS_FOR_TEST: "" - build: vs vs: 140 @@ -22,6 +21,8 @@ environment: GEMS_FOR_TEST: "" RELINE_TEST_ENCODING: "Windows-31J" UPDATE_UNICODE: "UNICODE_FILES=. UNICODE_PROPERTY_FILES=. UNICODE_AUXILIARY_FILES=. UNICODE_EMOJI_FILES=." +cache: + - c:\Tools\vcpkg\installed\ for: - matrix: @@ -33,6 +34,12 @@ for: - SET BITS=%Platform:x86=32% - SET BITS=%BITS:x=% - SET OPENSSL_DIR=C:\%ssl%-Win%BITS% + - cd C:\Tools\vcpkg + - git pull -q + - .\bootstrap-vcpkg.bat + - cd %APPVEYOR_BUILD_FOLDER% + - vcpkg --triplet %Platform%-windows install libffi libyaml zlib + - set PATH=c:\Tools\vcpkg\installed\%Platform%-windows\bin;%PATH% - CALL SET vcvars=%%^VS%VS%COMNTOOLS^%%..\..\VC\vcvarsall.bat - SET vcvars - '"%vcvars%" %Platform:x64=amd64%' @@ -53,16 +60,17 @@ for: - mkdir \usr\local\bin - mkdir \usr\local\include - mkdir \usr\local\lib - - SET ZLIB_ZIP=.downloaded-cache\zlib%zlib_version:.=%.zip - - if not exist %ZLIB_ZIP% curl -fsSL -o %ZLIB_ZIP% --retry 10 https://zlib.net/zlib%zlib_version:.=%.zip - - 7z x -aos -o%APPVEYOR_BUILD_FOLDER%\ext\zlib %ZLIB_ZIP% - for %%I in (%OPENSSL_DIR%\*.dll) do mklink /h \usr\local\bin\%%~nxI %%I - attrib +r /s /d - mkdir %Platform%-mswin_%vs% build_script: - cd %APPVEYOR_BUILD_FOLDER% - cd %Platform%-mswin_%vs% - - ..\win32\configure.bat --without-ext=+,dbm,gdbm,readline --with-opt-dir=/usr/local --with-openssl-dir=%OPENSSL_DIR:\=/% + - >- + ..\win32\configure.bat + --without-ext=+,dbm,gdbm,readline + --with-opt-dir="/usr/local;c:/Tools/vcpkg/installed/%Platform%-windows" + --with-openssl-dir=%OPENSSL_DIR:\=/% - nmake -l - nmake install-nodoc - \usr\bin\ruby -v -e "p :locale => Encoding.find('locale'), :filesystem => Encoding.find('filesystem')" @@ -3838,6 +3838,7 @@ select_bang_ensure(VALUE a) if (i2 < len && i2 < i1) { long tail = 0; + rb_ary_modify(ary); if (i1 < len) { tail = len - i1; RARRAY_PTR_USE_TRANSIENT(ary, ptr, { @@ -4071,7 +4072,7 @@ ary_slice_bang_by_rb_ary_splice(VALUE ary, long pos, long len) else if (orig_len < pos) { return Qnil; } - else if (orig_len < pos + len) { + if (orig_len < pos + len) { len = orig_len - pos; } if (len == 0) { @@ -4820,6 +4821,7 @@ ary_append(VALUE x, VALUE y) if (n > 0) { rb_ary_splice(x, RARRAY_LEN(x), 0, RARRAY_CONST_PTR_TRANSIENT(y), n); } + RB_GC_GUARD(y); return x; } @@ -8120,7 +8122,7 @@ Init_Array(void) rb_define_method(rb_cArray, "each_index", rb_ary_each_index, 0); rb_define_method(rb_cArray, "reverse_each", rb_ary_reverse_each, 0); rb_define_method(rb_cArray, "length", rb_ary_length, 0); - rb_define_alias(rb_cArray, "size", "length"); + rb_define_method(rb_cArray, "size", rb_ary_length, 0); rb_define_method(rb_cArray, "empty?", rb_ary_empty_p, 0); rb_define_method(rb_cArray, "find_index", rb_ary_index, -1); rb_define_method(rb_cArray, "index", rb_ary_index, -1); diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000000..44a1922cb0 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +PWD= +case "$0" in +*/*) srcdir=`dirname $0`;; +*) srcdir="";; +esac + +exec ${AUTORECONF:-autoreconf} --install --symlink "$@" ${srcdir:+"$srcdir"} diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 1c29a2b799..7d920c31b5 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -8,6 +8,24 @@ assert_equal 'Ractor', %q{ Ractor.new{}.class } +# Ractor.allocate is not supported +assert_equal "[:ok, :ok]", %q{ + rs = [] + begin + Ractor.allocate + rescue => e + rs << :ok if e.message == 'allocator undefined for Ractor' + end + + begin + Ractor.new{}.dup + rescue + rs << :ok if e.message == 'allocator undefined for Ractor' + end + + rs +} + # A Ractor can have a name assert_equal 'test-name', %q{ r = Ractor.new name: 'test-name' do @@ -158,6 +176,39 @@ assert_equal '[[:e1, 1], [:e2, 2]]', %q{ a # } +# dtoa race condition +assert_equal '[:ok, :ok, :ok]', %q{ + n = 3 + n.times.map{ + Ractor.new{ + 10_000.times{ rand.to_s } + :ok + } + }.map(&:take) +} + +# Ractor.make_shareable issue for locals in proc [Bug #18023] +assert_equal '[:a, :b, :c, :d, :e]', %q{ + v1, v2, v3, v4, v5 = :a, :b, :c, :d, :e + closure = Proc.new { [v1, v2, v3, v4, v5] } + + Ractor.make_shareable(closure).call +} + +# Ractor.make_shareable issue for locals in proc [Bug #18023] +assert_equal '[:a, :b, :c, :d, :e, :f, :g]', %q{ + a = :a + closure = -> { + b, c, d = :b, :c, :d + -> { + e, f, g = :e, :f, :g + -> { [a, b, c, d, e, f, g] } + }.call + }.call + + Ractor.make_shareable(closure).call +} + ### ### # Ractor still has several memory corruption so skip huge number of tests @@ -709,6 +760,31 @@ assert_equal 'hello', %q{ end } +# yield/move should not make moved object when the yield is not succeeded +assert_equal '"str"', %q{ + R = Ractor.new{} + M = Ractor.current + r = Ractor.new do + s = 'str' + selected_r, v = Ractor.select R, yield_value: s, move: true + raise if selected_r != R # taken from R + M.send s.inspect # s should not be a moved object + end + + Ractor.receive +} + +# yield/move can fail +assert_equal "allocator undefined for Thread", %q{ + r = Ractor.new do + obj = Thread.new{} + Ractor.yield obj + rescue => e + e.message + end + r.take +} + # Access to global-variables are prohibited assert_equal 'can not access global variables $gv from non-main Ractors', %q{ $gv = 1 @@ -751,6 +827,18 @@ assert_equal 'ok', %q{ 'ok' } +# $stdin,out,err belong to Ractor +assert_equal 'ok', %q{ + r = Ractor.new do + $stdin.itself + $stdout.itself + $stderr.itself + 'ok' + end + + r.take +} + # $DEBUG, $VERBOSE are Ractor local assert_equal 'true', %q{ $DEBUG = true @@ -1338,4 +1426,19 @@ assert_equal "ok", %q{ end } +# Can yield back values while GC is sweeping [Bug #18117] +assert_equal "ok", %q{ + workers = (0...8).map do + Ractor.new do + loop do + 10_000.times.map { Object.new } + Ractor.yield Time.now + end + end + end + + 1_000.times { idle_worker, tmp_reporter = Ractor.select(*workers) } + "ok" +} + end # if !ENV['GITHUB_WORKFLOW'] @@ -26,6 +26,7 @@ #include "ruby/internal/config.h" #include <ctype.h> +#include "gc.h" #include "constant.h" #include "id_table.h" #include "internal.h" @@ -973,17 +974,22 @@ rb_include_module(VALUE klass, VALUE module) int do_include = 1; while (iclass) { VALUE check_class = iclass->klass; - while (check_class) { - if (RB_TYPE_P(check_class, T_ICLASS) && - (RBASIC(check_class)->klass == module)) { - do_include = 0; + /* During lazy sweeping, iclass->klass could be a dead object that + * has not yet been swept. */ + if (!rb_objspace_garbage_object_p(check_class)) { + while (check_class) { + if (RB_TYPE_P(check_class, T_ICLASS) && + (RBASIC(check_class)->klass == module)) { + do_include = 0; + } + check_class = RCLASS_SUPER(check_class); } - check_class = RCLASS_SUPER(check_class); - } - if (do_include) { - include_modules_at(iclass->klass, RCLASS_ORIGIN(iclass->klass), module, TRUE); + if (do_include) { + include_modules_at(iclass->klass, RCLASS_ORIGIN(iclass->klass), module, TRUE); + } } + iclass = iclass->next; } } @@ -1134,10 +1140,12 @@ cache_clear_refined_method(ID key, VALUE value, void *data) { rb_method_entry_t *me = (rb_method_entry_t *) value; - if (me->def->type == VM_METHOD_TYPE_REFINED) { + if (me->def->type == VM_METHOD_TYPE_REFINED && me->def->body.refined.orig_me) { VALUE klass = (VALUE)data; rb_clear_method_cache(klass, me->called_id); } + // Refined method entries without an orig_me is going to stay in the method + // table of klass, like before the move, so no need to clear the cache. return ID_TABLE_CONTINUE; } @@ -1178,17 +1186,22 @@ rb_prepend_module(VALUE klass, VALUE module) struct rb_id_table *klass_m_tbl = RCLASS_M_TBL(klass); struct rb_id_table *klass_origin_m_tbl = RCLASS_M_TBL(klass_origin); while (iclass) { - if (klass_had_no_origin && klass_origin_m_tbl == RCLASS_M_TBL(iclass->klass)) { - // backfill an origin iclass to handle refinements and future prepends - rb_id_table_foreach(RCLASS_M_TBL(iclass->klass), clear_module_cache_i, (void *)iclass->klass); - RCLASS_M_TBL(iclass->klass) = klass_m_tbl; - VALUE origin = rb_include_class_new(klass_origin, RCLASS_SUPER(iclass->klass)); - RCLASS_SET_SUPER(iclass->klass, origin); - RCLASS_SET_INCLUDER(origin, RCLASS_INCLUDER(iclass->klass)); - RCLASS_SET_ORIGIN(iclass->klass, origin); - RICLASS_SET_ORIGIN_SHARED_MTBL(origin); + /* During lazy sweeping, iclass->klass could be a dead object that + * has not yet been swept. */ + if (!rb_objspace_garbage_object_p(iclass->klass)) { + if (klass_had_no_origin && klass_origin_m_tbl == RCLASS_M_TBL(iclass->klass)) { + // backfill an origin iclass to handle refinements and future prepends + rb_id_table_foreach(RCLASS_M_TBL(iclass->klass), clear_module_cache_i, (void *)iclass->klass); + RCLASS_M_TBL(iclass->klass) = klass_m_tbl; + VALUE origin = rb_include_class_new(klass_origin, RCLASS_SUPER(iclass->klass)); + RCLASS_SET_SUPER(iclass->klass, origin); + RCLASS_SET_INCLUDER(origin, RCLASS_INCLUDER(iclass->klass)); + RCLASS_SET_ORIGIN(iclass->klass, origin); + RICLASS_SET_ORIGIN_SHARED_MTBL(origin); + } + include_modules_at(iclass->klass, iclass->klass, module, FALSE); } - include_modules_at(iclass->klass, iclass->klass, module, FALSE); + iclass = iclass->next; } } @@ -1344,7 +1344,7 @@ test-bundled-gems-prepare: $(TEST_RUNNABLE)-test-bundled-gems-prepare no-test-bundled-gems-prepare: no-test-bundled-gems-precheck yes-test-bundled-gems-prepare: yes-test-bundled-gems-precheck $(XRUBY) -C "$(srcdir)" bin/gem install --no-document \ - --install-dir .bundle --conservative "bundler" "minitest:~> 5" "test-unit" "rake" "hoe" "yard" "pry" "packnga" "rexml" "json-schema" "rbs" + --install-dir .bundle --conservative "bundler" "minitest:~> 5" "test-unit" "rake" "hoe" "rexml" "json-schema" "rbs:~> 1.6.2" PREPARE_BUNDLED_GEMS = test-bundled-gems-prepare test-bundled-gems: $(TEST_RUNNABLE)-test-bundled-gems @@ -2454,6 +2454,7 @@ class.$(OBJEXT): {$(VPATH)}config.h class.$(OBJEXT): {$(VPATH)}constant.h class.$(OBJEXT): {$(VPATH)}defines.h class.$(OBJEXT): {$(VPATH)}encoding.h +class.$(OBJEXT): {$(VPATH)}gc.h class.$(OBJEXT): {$(VPATH)}id.h class.$(OBJEXT): {$(VPATH)}id_table.h class.$(OBJEXT): {$(VPATH)}intern.h @@ -2803,6 +2804,7 @@ compile.$(OBJEXT): $(top_srcdir)/internal/hash.h compile.$(OBJEXT): $(top_srcdir)/internal/imemo.h compile.$(OBJEXT): $(top_srcdir)/internal/numeric.h compile.$(OBJEXT): $(top_srcdir)/internal/object.h +compile.$(OBJEXT): $(top_srcdir)/internal/rational.h compile.$(OBJEXT): $(top_srcdir)/internal/re.h compile.$(OBJEXT): $(top_srcdir)/internal/serial.h compile.$(OBJEXT): $(top_srcdir)/internal/static_assert.h @@ -8428,6 +8430,7 @@ mjit.$(OBJEXT): $(hdrdir)/ruby/ruby.h mjit.$(OBJEXT): $(hdrdir)/ruby/version.h mjit.$(OBJEXT): $(top_srcdir)/internal/array.h mjit.$(OBJEXT): $(top_srcdir)/internal/class.h +mjit.$(OBJEXT): $(top_srcdir)/internal/compile.h mjit.$(OBJEXT): $(top_srcdir)/internal/compilers.h mjit.$(OBJEXT): $(top_srcdir)/internal/cont.h mjit.$(OBJEXT): $(top_srcdir)/internal/file.h @@ -8449,6 +8452,7 @@ mjit.$(OBJEXT): {$(VPATH)}backward/2/limits.h mjit.$(OBJEXT): {$(VPATH)}backward/2/long_long.h mjit.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h mjit.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h +mjit.$(OBJEXT): {$(VPATH)}builtin.h mjit.$(OBJEXT): {$(VPATH)}config.h mjit.$(OBJEXT): {$(VPATH)}constant.h mjit.$(OBJEXT): {$(VPATH)}debug.h @@ -8459,6 +8463,9 @@ mjit.$(OBJEXT): {$(VPATH)}encoding.h mjit.$(OBJEXT): {$(VPATH)}gc.h mjit.$(OBJEXT): {$(VPATH)}id.h mjit.$(OBJEXT): {$(VPATH)}id_table.h +mjit.$(OBJEXT): {$(VPATH)}insns.def +mjit.$(OBJEXT): {$(VPATH)}insns.inc +mjit.$(OBJEXT): {$(VPATH)}insns_info.inc mjit.$(OBJEXT): {$(VPATH)}intern.h mjit.$(OBJEXT): {$(VPATH)}internal.h mjit.$(OBJEXT): {$(VPATH)}internal/anyargs.h @@ -8601,6 +8608,7 @@ mjit.$(OBJEXT): {$(VPATH)}internal/value_type.h mjit.$(OBJEXT): {$(VPATH)}internal/variable.h mjit.$(OBJEXT): {$(VPATH)}internal/warning_push.h mjit.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +mjit.$(OBJEXT): {$(VPATH)}iseq.h mjit.$(OBJEXT): {$(VPATH)}method.h mjit.$(OBJEXT): {$(VPATH)}missing.h mjit.$(OBJEXT): {$(VPATH)}mjit.c @@ -14800,6 +14808,7 @@ util.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h util.$(OBJEXT): $(top_srcdir)/internal/util.h util.$(OBJEXT): $(top_srcdir)/internal/warnings.h util.$(OBJEXT): {$(VPATH)}assert.h +util.$(OBJEXT): {$(VPATH)}atomic.h util.$(OBJEXT): {$(VPATH)}backward/2/assume.h util.$(OBJEXT): {$(VPATH)}backward/2/attributes.h util.$(OBJEXT): {$(VPATH)}backward/2/bool.h @@ -14955,6 +14964,7 @@ util.$(OBJEXT): {$(VPATH)}internal/variable.h util.$(OBJEXT): {$(VPATH)}internal/warning_push.h util.$(OBJEXT): {$(VPATH)}internal/xmalloc.h util.$(OBJEXT): {$(VPATH)}missing.h +util.$(OBJEXT): {$(VPATH)}ruby_atomic.h util.$(OBJEXT): {$(VPATH)}st.h util.$(OBJEXT): {$(VPATH)}subst.h util.$(OBJEXT): {$(VPATH)}util.c @@ -28,6 +28,7 @@ #include "internal/hash.h" #include "internal/numeric.h" #include "internal/object.h" +#include "internal/rational.h" #include "internal/re.h" #include "internal/symbol.h" #include "internal/thread.h" @@ -1985,6 +1986,16 @@ cdhash_cmp(VALUE val, VALUE lit) else if (tlit == T_FLOAT) { return rb_float_cmp(lit, val); } + else if (tlit == T_RATIONAL) { + const struct RRational *rat1 = RRATIONAL(val); + const struct RRational *rat2 = RRATIONAL(lit); + return cdhash_cmp(rat1->num, rat2->num) || cdhash_cmp(rat1->den, rat2->den); + } + else if (tlit == T_COMPLEX) { + const struct RComplex *comp1 = RCOMPLEX(val); + const struct RComplex *comp2 = RCOMPLEX(lit); + return cdhash_cmp(comp1->real, comp2->real) || cdhash_cmp(comp1->imag, comp2->imag); + } else { UNREACHABLE_RETURN(-1); } @@ -2003,6 +2014,10 @@ cdhash_hash(VALUE a) return FIX2LONG(rb_big_hash(a)); case T_FLOAT: return rb_dbl_long_hash(RFLOAT_VALUE(a)); + case T_RATIONAL: + return rb_rational_hash(a); + case T_COMPLEX: + return rb_complex_hash(a); default: UNREACHABLE_RETURN(0); } @@ -2884,7 +2899,8 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal } else if (iobj != diobj && IS_INSN(&diobj->link) && IS_INSN_ID(diobj, jump) && - OPERAND_AT(iobj, 0) != OPERAND_AT(diobj, 0)) { + OPERAND_AT(iobj, 0) != OPERAND_AT(diobj, 0) && + diobj->insn_info.events == 0) { /* * useless jump elimination: * jump LABEL1 @@ -9901,7 +9917,7 @@ typedef unsigned int ibf_offset_t; #define IBF_MAJOR_VERSION ISEQ_MAJOR_VERSION #if RUBY_DEVEL -#define IBF_DEVEL_VERSION 2 +#define IBF_DEVEL_VERSION 3 #define IBF_MINOR_VERSION (ISEQ_MINOR_VERSION * 10000 + IBF_DEVEL_VERSION) #else #define IBF_MINOR_VERSION ISEQ_MINOR_VERSION @@ -10735,6 +10751,33 @@ ibf_dump_ci_entries(struct ibf_dump *dump, const rb_iseq_t *iseq) return offset; } +static enum rb_id_table_iterator_result +dump_outer_variable(ID id, VALUE val, void *dump) +{ + ibf_dump_write_small_value(dump, ibf_dump_id(dump, id)); + ibf_dump_write_small_value(dump, val); + + return ID_TABLE_CONTINUE; +} + +static ibf_offset_t +ibf_dump_outer_variables(struct ibf_dump *dump, const rb_iseq_t *iseq) +{ + struct rb_id_table * ovs = iseq->body->outer_variables; + + ibf_offset_t offset = ibf_dump_pos(dump); + + if (ovs) { + ibf_dump_write_small_value(dump, (VALUE)rb_id_table_size(ovs)); + rb_id_table_foreach(ovs, dump_outer_variable, (void *)dump); + } + else { + ibf_dump_write_small_value(dump, (VALUE)0); + } + + return offset; +} + /* note that we dump out rb_call_info but load back rb_call_data */ static void ibf_load_ci_entries(const struct ibf_load *load, @@ -10779,6 +10822,28 @@ ibf_load_ci_entries(const struct ibf_load *load, } } +static struct rb_id_table * +ibf_load_outer_variables(const struct ibf_load * load, ibf_offset_t outer_variables_offset) +{ + ibf_offset_t reading_pos = outer_variables_offset; + + struct rb_id_table *tbl = NULL; + + size_t table_size = (size_t)ibf_load_small_value(load, &reading_pos); + + if (table_size > 0) { + tbl = rb_id_table_create(table_size); + } + + for (size_t i = 0; i < table_size; i++) { + ID key = ibf_load_id(load, (ID)ibf_load_small_value(load, &reading_pos)); + VALUE value = ibf_load_small_value(load, &reading_pos); + rb_id_table_insert(tbl, key, value); + } + + return tbl; +} + static ibf_offset_t ibf_dump_iseq_each(struct ibf_dump *dump, const rb_iseq_t *iseq) { @@ -10818,6 +10883,7 @@ ibf_dump_iseq_each(struct ibf_dump *dump, const rb_iseq_t *iseq) const int parent_iseq_index = ibf_dump_iseq(dump, iseq->body->parent_iseq); const int local_iseq_index = ibf_dump_iseq(dump, iseq->body->local_iseq); const ibf_offset_t ci_entries_offset = ibf_dump_ci_entries(dump, iseq); + const ibf_offset_t outer_variables_offset = ibf_dump_outer_variables(dump, iseq); #if IBF_ISEQ_ENABLE_LOCAL_BUFFER ibf_offset_t local_obj_list_offset; @@ -10879,6 +10945,7 @@ ibf_dump_iseq_each(struct ibf_dump *dump, const rb_iseq_t *iseq) ibf_dump_write_small_value(dump, parent_iseq_index); ibf_dump_write_small_value(dump, local_iseq_index); ibf_dump_write_small_value(dump, IBF_BODY_OFFSET(ci_entries_offset)); + ibf_dump_write_small_value(dump, IBF_BODY_OFFSET(outer_variables_offset)); ibf_dump_write_small_value(dump, body->variable.flip_count); ibf_dump_write_small_value(dump, body->local_table_size); ibf_dump_write_small_value(dump, body->is_size); @@ -10985,6 +11052,7 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset) const int parent_iseq_index = (int)ibf_load_small_value(load, &reading_pos); const int local_iseq_index = (int)ibf_load_small_value(load, &reading_pos); const ibf_offset_t ci_entries_offset = (ibf_offset_t)IBF_BODY_OFFSET(ibf_load_small_value(load, &reading_pos)); + const ibf_offset_t outer_variables_offset = (ibf_offset_t)IBF_BODY_OFFSET(ibf_load_small_value(load, &reading_pos)); const rb_snum_t variable_flip_count = (rb_snum_t)ibf_load_small_value(load, &reading_pos); const unsigned int local_table_size = (unsigned int)ibf_load_small_value(load, &reading_pos); const unsigned int is_size = (unsigned int)ibf_load_small_value(load, &reading_pos); @@ -11034,6 +11102,7 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset) load_body->is_entries = ZALLOC_N(union iseq_inline_storage_entry, is_size); ibf_load_ci_entries(load, ci_entries_offset, ci_size, &load_body->call_data); + load_body->outer_variables = ibf_load_outer_variables(load, outer_variables_offset); load_body->param.opt_table = ibf_load_param_opt_table(load, param_opt_table_offset, param_opt_num); load_body->param.keyword = ibf_load_param_keyword(load, param_keyword_offset); load_body->param.flags.has_kw = (param_flags >> 4) & 1; @@ -1328,8 +1328,8 @@ nucomp_numerator(VALUE self) } /* :nodoc: */ -static VALUE -nucomp_hash(VALUE self) +st_index_t +rb_complex_hash(VALUE self) { st_index_t v, h[2]; VALUE n; @@ -1340,7 +1340,13 @@ nucomp_hash(VALUE self) n = rb_hash(dat->imag); h[1] = NUM2LONG(n); v = rb_memhash(h, sizeof(h)); - return ST2FIX(v); + return v; +} + +static VALUE +nucomp_hash(VALUE self) +{ + return ST2FIX(rb_complex_hash(self)); } /* :nodoc: */ diff --git a/configure.ac b/configure.ac index c143c23d3a..28fdfefbbc 100644 --- a/configure.ac +++ b/configure.ac @@ -1,22 +1,48 @@ dnl Process this file with autoconf to produce a configure script. -AC_INIT() +AC_INIT { AC_CONFIG_AUX_DIR(tool) -AC_CONFIG_MACRO_DIRS(tool/m4) AC_PREREQ(2.67) tooldir="$srcdir/tool" -dnl override AC_CHECKING -dnl placed here due to aclocal(1)'s -dnl ignoring this definition in separate files -AC_DEFUN([AC_CHECKING],[dnl -AC_REQUIRE([_COLORIZE_RESULT_PREPARE])dnl -AS_MESSAGE([checking ${msg_checking}$1${msg_reset}...])])dnl - AC_DISABLE_OPTION_CHECKING +m4_include([tool/m4/_colorize_result_prepare.m4]) +m4_include([tool/m4/ac_msg_result.m4]) +m4_include([tool/m4/colorize_result.m4]) +m4_include([tool/m4/ruby_append_option.m4]) +m4_include([tool/m4/ruby_append_options.m4]) +m4_include([tool/m4/ruby_check_builtin_func.m4]) +m4_include([tool/m4/ruby_check_builtin_setjmp.m4]) +m4_include([tool/m4/ruby_check_printf_prefix.m4]) +m4_include([tool/m4/ruby_check_setjmp.m4]) +m4_include([tool/m4/ruby_check_signedness.m4]) +m4_include([tool/m4/ruby_check_sizeof.m4]) +m4_include([tool/m4/ruby_check_sysconf.m4]) +m4_include([tool/m4/ruby_cppoutfile.m4]) +m4_include([tool/m4/ruby_decl_attribute.m4]) +m4_include([tool/m4/ruby_default_arch.m4]) +m4_include([tool/m4/ruby_define_if.m4]) +m4_include([tool/m4/ruby_defint.m4]) +m4_include([tool/m4/ruby_dtrace_available.m4]) +m4_include([tool/m4/ruby_dtrace_postprocess.m4]) +m4_include([tool/m4/ruby_func_attribute.m4]) +m4_include([tool/m4/ruby_mingw32.m4]) +m4_include([tool/m4/ruby_prepend_option.m4]) +m4_include([tool/m4/ruby_prog_gnu_ld.m4]) +m4_include([tool/m4/ruby_replace_funcs.m4]) +m4_include([tool/m4/ruby_replace_type.m4]) +m4_include([tool/m4/ruby_rm_recursive.m4]) +m4_include([tool/m4/ruby_setjmp_type.m4]) +m4_include([tool/m4/ruby_stack_grow_direction.m4]) +m4_include([tool/m4/ruby_try_cflags.m4]) +m4_include([tool/m4/ruby_try_cxxflags.m4]) +m4_include([tool/m4/ruby_try_ldflags.m4]) +m4_include([tool/m4/ruby_universal_arch.m4]) +m4_include([tool/m4/ruby_werror_flag.m4]) + AC_ARG_VAR([cflags], [additional CFLAGS (ignored when CFLAGS is given)]) AC_ARG_VAR([cppflags], [additional CPPFLAGS (ignored when CPPFLAGS is given)]) AC_ARG_VAR([cxxflags], [additional CXXFLAGS (ignored when CXXFLAGS is given)]) @@ -175,7 +201,10 @@ rb_test_CFLAGS=${CFLAGS+yes} rb_test_CXXFLAGS=${CXXFLAGS+yes} # BSD's ports and MacPorts prefix GNU binutils with 'g' -AC_PROG_CC_C99 + +dnl Seems necessarily in order to add -std=gnu99 option for gcc 4.9. +m4_version_prereq([2.70], [], [AC_PROG_CC_C99]) + AC_PROG_CXX AC_PROG_CPP AC_PROG_RANLIB @@ -244,6 +273,9 @@ AC_ARG_ENABLE(load-relative, AS_HELP_STRING([--enable-load-relative], [resolve load paths at run time]), [load_relative=$enableval]) +# checks for UNIX variants that set C preprocessor variables +AC_USE_SYSTEM_EXTENSIONS + dnl Checks for programs. cflagspat= @@ -283,12 +315,12 @@ AS_CASE(["$host_os:$build_os"], AS_CASE(["$target_os"], [darwin*], [ AC_MSG_CHECKING(if minimum required OS X version is supported) - AC_TRY_CPP([@%:@include <AvailabilityMacros.h> + AC_PREPROC_IFELSE([AC_LANG_SOURCE([[@%:@include <AvailabilityMacros.h> @%:@if MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_5 @%:@error pre OS X 10.5 [!<===== pre OS X 10.5 =====>] @%:@endif - ], + ]])], [macosx_min_required=yes], [AC_MSG_RESULT(no) AC_MSG_ERROR([Unsupported OS X version is required])]) @@ -316,6 +348,14 @@ AS_IF([test "$GCC" = yes -a "$gcc_major" -lt 3 ], [ AC_MSG_ERROR([too old GCC]) ]) + +AC_CACHE_CHECK([if thread-local storage is supported], [rb_cv_tls_supported], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[int __thread conftest;]])], + [rb_cv_tls_supported=yes], + [rb_cv_tls_supported=no])]) +AS_IF([test x"$rb_cv_tls_supported" != xyes], + [AC_DEFINE(RB_THREAD_LOCAL_SPECIFIER_IS_UNSUPPORTED)]) + RUBY_PROG_GNU_LD RUBY_CPPOUTFILE @@ -399,8 +439,8 @@ AS_CASE(["$target_os"], [mingw*], [ test "$rb_cv_msvcrt" = "" && unset rb_cv_msvcrt AC_CACHE_CHECK(for mingw32 runtime DLL, rb_cv_msvcrt, [ - AC_TRY_LINK([@%:@include <stdio.h>], - [FILE* volatile f = stdin; return 0;], + AC_LINK_IFELSE([AC_LANG_PROGRAM([[@%:@include <stdio.h>]], + [[FILE* volatile f = stdin; return 0;]])], [rb_cv_msvcrt=`$OBJDUMP -p conftest$ac_exeext | tr A-Z a-z | sed -n '/^[[ ]]*dll name: \(msvc.*\)\.dll$/{s//\1/p;q;}'`], @@ -444,15 +484,12 @@ for prog in ${ac_tool_prefix:+${ac_tool_prefix}pkg-config} pkg-config; do test -z "${PKG_CONFIG}" || break done -# checks for UNIX variants that set C preprocessor variables -AC_USE_SYSTEM_EXTENSIONS - AC_MSG_CHECKING([whether it is Android]) -AC_TRY_COMPILE([ +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @%:@ifdef __ANDROID__ @%:@error android @%:@endif -], [], +]], [[]])], [AC_MSG_RESULT(no)], [ AC_MSG_RESULT(yes) @@ -499,7 +536,7 @@ AC_SUBST(CHDIR) : "compiler section" && { RUBY_WERROR_FLAG([ AC_MSG_CHECKING([whether CFLAGS is valid]) - AC_TRY_COMPILE([], [], + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[]])], [AC_MSG_RESULT(yes)], [ AC_MSG_RESULT(no) @@ -515,7 +552,7 @@ RUBY_WERROR_FLAG([ echo '<?xml?><plist><dict><key>CFBundleIdentifier</key><string></string></dict></plist>' > Info.plist && : } || AC_MSG_ERROR([failed to make temporary directory]) - AC_TRY_LINK([], [], + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])], [AC_MSG_RESULT(yes)], [ cd .. && rm -fr tmp.$$.try_link @@ -720,13 +757,13 @@ AS_IF([test "$GCC" = yes], [ ], [ CFLAGS="$CFLAGS -Werror -Wuninitialized" ]) - AC_TRY_COMPILE([@%:@include <math.h> + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include <math.h> int foo(double x) { int exp; frexp(x, &exp); return exp; - }], [if (foo(0.0)) return 1;], + }]], [[if (foo(0.0)) return 1;]])], [rb_cv_mingw64_broken_frexp_modf=no], [rb_cv_mingw64_broken_frexp_modf=yes]) CFLAGS="$save_CFLAGS" @@ -816,13 +853,13 @@ AS_IF([test "$GCC" = yes], [ AS_CASE(["$target_cpu"], [[i[3-6]86*]], [ AC_CACHE_CHECK([for __sync_val_compare_and_swap], [rb_cv_gcc_compiler_cas], [ - AC_TRY_LINK([unsigned long atomic_var;], - [__sync_val_compare_and_swap(&atomic_var, 0, 1);], + AC_LINK_IFELSE([AC_LANG_PROGRAM([[unsigned long atomic_var;]], + [[__sync_val_compare_and_swap(&atomic_var, 0, 1);]])], [rb_cv_gcc_compiler_cas=yes], [ save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -march=i486" - AC_TRY_LINK([unsigned long atomic_var;], - [__sync_val_compare_and_swap(&atomic_var, 0, 1);], + AC_LINK_IFELSE([AC_LANG_PROGRAM([[unsigned long atomic_var;]], + [[__sync_val_compare_and_swap(&atomic_var, 0, 1);]])], [rb_cv_gcc_compiler_cas=i486], [rb_cv_gcc_compiler_cas=no]) CFLAGS="$save_CFLAGS" @@ -849,7 +886,7 @@ test -z "${ac_env_CXXFLAGS_set}" -a -n "${cxxflags+set}" && eval CXXFLAGS="\"$cx AC_CACHE_CHECK([whether compiler has statement and declarations in expressions], rb_cv_have_stmt_and_decl_in_expr, - [AC_TRY_COMPILE([],[ __extension__ ({ int a = 0; a; }); ], + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]],[[ __extension__ ({ int a = 0; a; }); ]])], [rb_cv_have_stmt_and_decl_in_expr=yes], [rb_cv_have_stmt_and_decl_in_expr=no])]) AS_IF([test "$rb_cv_have_stmt_and_decl_in_expr" = yes], [ @@ -870,12 +907,12 @@ AS_CASE(["$target_os"], [freebsd*], [ AC_CACHE_CHECK([whether pthread should be enabled by default], rb_cv_enable_pthread_default, - [AC_TRY_CPP([ + [AC_PREPROC_IFELSE([AC_LANG_SOURCE([[ #include <osreldate.h> #if __FreeBSD_version < 502102 #error pthread should be disabled on this platform #endif - ], + ]])], rb_cv_enable_pthread_default=yes, rb_cv_enable_pthread_default=no)]) enable_pthread=$rb_cv_enable_pthread_default @@ -903,8 +940,8 @@ AS_CASE(["$target_os"], RUBY_APPEND_OPTIONS(CPPFLAGS, -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT) AC_CACHE_CHECK([whether syscall(2) is deprecated], rb_cv_syscall_deprecated, [RUBY_WERROR_FLAG([ - AC_TRY_COMPILE([@%:@include <unistd.h>], - [if (syscall(0)) return 1;], + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include <unistd.h>]], + [[if (syscall(0)) return 1;]])], [rb_cv_syscall_deprecated=no], [rb_cv_syscall_deprecated=yes])])]) AS_IF([test $rb_cv_syscall_deprecated = yes], [ @@ -931,7 +968,7 @@ AS_CASE(["$target_os"], ]) with_setjmp_type=sigsetjmp # to hijack SIGCHLD handler AC_CACHE_CHECK(for broken crypt with 8bit chars, rb_cv_broken_crypt, - [AC_TRY_RUN([ + [AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include <stdio.h> #include <unistd.h> #include <string.h> @@ -968,7 +1005,7 @@ main() } return 0; } -], +]])], rb_cv_broken_crypt=no, rb_cv_broken_crypt=yes, rb_cv_broken_crypt=yes)]) @@ -997,11 +1034,11 @@ main() [solaris*], [ LIBS="-lm $LIBS" ac_cv_func_vfork=no AC_MSG_CHECKING(whether _XOPEN_SOURCE is already given) - AC_TRY_COMPILE([#include <unistd.h> + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <unistd.h> #ifndef _XOPEN_SOURCE #error _XOPEN_SOURCE is not defined #endif - ], [], + ]], [[]])], [given_xopen_source=yes], [given_xopen_source=no]) AC_MSG_RESULT($given_xopen_source) AS_IF([test $given_xopen_source = no], [ @@ -1011,13 +1048,13 @@ main() AS_IF([test x"$define_xopen_source" != x], [ break ]) - RUBY_WERROR_FLAG([AC_TRY_COMPILE([ + RUBY_WERROR_FLAG([AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #define _XOPEN_SOURCE ${tmp_xpg}00 #include <unistd.h> #ifndef _XPG${tmp_xpg} #error _XPG${tmp_xpg} should be defined by _XOPEN_SOURCE=${tmp_xpg}00 #endif - ], [], + ]], [[]])], [define_xopen_source=${tmp_xpg}00], []) ]) done @@ -1135,7 +1172,8 @@ AS_IF([test -n "${rb_there_is_in_fact_no_gplusplus_but_autoconf_is_cheating_us}" RUBY_WERROR_FLAG([ AC_MSG_CHECKING([whether CXXFLAGS is valid]) AC_LANG_PUSH(C++) - AC_TRY_COMPILE([@%:@include <cstdio>], [], [AC_MSG_RESULT(yes)], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include <cstdio>]], [[]])], + [AC_MSG_RESULT(yes)],[ AC_MSG_RESULT(no) # The message mentions CXXFLAGS, but CPPFLAGS might also affects. AC_MSG_WARN([something wrong with CXXFLAGS="$CXXFLAGS"]) @@ -1333,8 +1371,8 @@ AC_CACHE_CHECK(packed struct attribute, rb_cv_packed_struct, "__pragma(pack(push, 1)) x __pragma(pack(pop))" \ "x __attribute__((packed))" \ ; do - AC_TRY_COMPILE([@%:@define PACKED_STRUCT(x) $mac - PACKED_STRUCT(struct { int a; });], [], + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@define PACKED_STRUCT(x) $mac + PACKED_STRUCT(struct { int a; });]], [[]])], [rb_cv_packed_struct=$mac; break]) done]) AS_IF([test "$rb_cv_packed_struct" != no], [ @@ -1377,10 +1415,10 @@ RUBY_REPLACE_TYPE(clockid_t, [], CLOCKID, [@%:@ifdef HAVE_TIME_H # 2006). The check below is redundant and should always success. Remain not # deleted for backward compat. AC_CACHE_CHECK(for variable length macro, rb_cv_va_args_macro, - [AC_TRY_COMPILE([ + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ int foo(const char*); @%:@define FOO(...) foo(@%:@__VA_ARGS__) -], [FOO(1);FOO(1,2);FOO(1,2,3);], +]], [[FOO(1);FOO(1,2);FOO(1,2,3);]])], rb_cv_va_args_macro=yes, rb_cv_va_args_macro=no)]) AS_IF([test "$rb_cv_va_args_macro" = yes], [ @@ -1398,7 +1436,7 @@ AS_IF([test "$rb_cv_va_args_macro" = yes], [ AC_CACHE_CHECK([if _Alignof() works], rb_cv_have__alignof,[ rb_cv_have__alignof=no RUBY_WERROR_FLAG([ - AC_TRY_COMPILE([ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @%:@ifdef HAVE_STDALIGN_H @%:@include <stdalign.h> @%:@endif @@ -1408,7 +1446,7 @@ AC_CACHE_CHECK([if _Alignof() works], rb_cv_have__alignof,[ @%:@ifndef __GNUC__ @%:@define __extension__ @%:@endif - ], [ + ]], [[ typedef struct conftest_tag { char _; double d; @@ -1418,9 +1456,9 @@ AC_CACHE_CHECK([if _Alignof() works], rb_cv_have__alignof,[ ? 1 : -1 @:>@; return conftest_ary@<:@0@:>@; - ], [ + ]])],[ rb_cv_have__alignof=yes - ]) + ],[]) ]) ]) AS_IF([test "$rb_cv_have__alignof" != no], [ @@ -1483,8 +1521,8 @@ AS_IF([test "$GCC" = yes], [ AC_CACHE_CHECK([for function alias], [rb_cv_gcc_function_alias], [rb_cv_gcc_function_alias=no for a in alias weak,alias; do - AC_TRY_LINK([void foo(void) {} - void bar(void) __attribute__(($a("foo")));], [bar()], + AC_LINK_IFELSE([AC_LANG_PROGRAM([[void foo(void) {} + void bar(void) __attribute__(($a("foo")));]], [[bar()]])], [rb_cv_gcc_function_alias=$a; break]) done]) AS_IF([test "$rb_cv_gcc_function_alias" != no], [ @@ -1496,14 +1534,14 @@ AS_IF([test "$GCC" = yes], [ ]) AC_CACHE_CHECK([for __atomic builtins], [rb_cv_gcc_atomic_builtins], [ - AC_TRY_LINK([unsigned int atomic_var;], - [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[unsigned int atomic_var;]], + [[ __atomic_exchange_n(&atomic_var, 0, __ATOMIC_SEQ_CST); __atomic_exchange_n(&atomic_var, 1, __ATOMIC_SEQ_CST); __atomic_fetch_add(&atomic_var, 1, __ATOMIC_SEQ_CST); __atomic_fetch_sub(&atomic_var, 1, __ATOMIC_SEQ_CST); __atomic_or_fetch(&atomic_var, 1, __ATOMIC_SEQ_CST); - ], + ]])], [rb_cv_gcc_atomic_builtins=yes], [rb_cv_gcc_atomic_builtins=no])]) AS_IF([test "$rb_cv_gcc_atomic_builtins" = yes], [ @@ -1511,15 +1549,15 @@ AS_IF([test "$GCC" = yes], [ ]) AC_CACHE_CHECK([for __sync builtins], [rb_cv_gcc_sync_builtins], [ - AC_TRY_LINK([unsigned int atomic_var;], - [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[unsigned int atomic_var;]], + [[ __sync_lock_test_and_set(&atomic_var, 0); __sync_lock_test_and_set(&atomic_var, 1); __sync_fetch_and_add(&atomic_var, 1); __sync_fetch_and_sub(&atomic_var, 1); __sync_or_and_fetch(&atomic_var, 1); __sync_val_compare_and_swap(&atomic_var, 0, 1); - ], + ]])], [rb_cv_gcc_sync_builtins=yes], [rb_cv_gcc_sync_builtins=no])]) AS_IF([test "$rb_cv_gcc_sync_builtins" = yes], [ @@ -1529,8 +1567,8 @@ AS_IF([test "$GCC" = yes], [ AC_CACHE_CHECK(for __builtin_unreachable, rb_cv_func___builtin_unreachable, [RUBY_WERROR_FLAG( - [AC_TRY_LINK([volatile int zero;], - [if (zero) __builtin_unreachable();], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[volatile int zero;]], + [[if (zero) __builtin_unreachable();]])], [rb_cv_func___builtin_unreachable=yes], [rb_cv_func___builtin_unreachable=no]) ]) @@ -1543,8 +1581,8 @@ AC_CACHE_CHECK(for exported function attribute, rb_cv_func_exported, [ rb_cv_func_exported=no RUBY_WERROR_FLAG([ for mac in '__attribute__ ((__visibility__("default")))' '__declspec(dllexport)'; do - AC_TRY_COMPILE([@%:@define RUBY_FUNC_EXPORTED $mac extern - RUBY_FUNC_EXPORTED void conftest_attribute_check(void);], [], + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@define RUBY_FUNC_EXPORTED $mac extern + RUBY_FUNC_EXPORTED void conftest_attribute_check(void);]], [[]])], [rb_cv_func_exported="$mac"; break]) done ])]) @@ -1571,8 +1609,8 @@ AC_CACHE_CHECK(for function name string predefined identifier, rb_cv_function_name_string=no RUBY_WERROR_FLAG([ for func in __func__ __FUNCTION__; do - AC_TRY_LINK([@%:@include <stdio.h>], - [puts($func);], + AC_LINK_IFELSE([AC_LANG_PROGRAM([[@%:@include <stdio.h>]], + [[puts($func);]])], [rb_cv_function_name_string=$func break]) done @@ -1729,9 +1767,9 @@ AS_IF([test "x$rb_cv_type_int64_t" != xno], [ AC_CACHE_CHECK(for stack end address, rb_cv_stack_end_address, [rb_cv_stack_end_address=no - AC_TRY_LINK( - [extern void *__libc_stack_end;], - [if (!__libc_stack_end) return 1;], + AC_LINK_IFELSE([AC_LANG_PROGRAM( + [[extern void *__libc_stack_end;]], + [[if (!__libc_stack_end) return 1;]])], [rb_cv_stack_end_address="__libc_stack_end"]) ]) AS_IF([test $rb_cv_stack_end_address != no], [ @@ -1759,14 +1797,14 @@ AS_CASE(["${target_cpu}-${target_os}:${target_archs}"], AS_IF([test "x$ALLOCA" = "x"], [ AC_CACHE_CHECK([for dynamic size alloca], rb_cv_dynamic_alloca, [ for chk in ok __chkstk; do - AC_TRY_LINK([ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ @%:@ifdef HAVE_ALLOCA_H @%:@include <alloca.h> @%:@endif void $chk() {} int dynamic_alloca_test; - int dynamic_alloca_result;], - [dynamic_alloca_result = alloca(dynamic_alloca_test) != 0;], + int dynamic_alloca_result;]], + [[dynamic_alloca_result = alloca(dynamic_alloca_test) != 0;]])], [rb_cv_dynamic_alloca=$chk; break]) done]) AS_IF([test "x$rb_cv_dynamic_alloca" = "x__chkstk"], [ @@ -1818,9 +1856,9 @@ AC_CHECK_HEADERS(sys/pstat.h) AC_CACHE_CHECK(for signbit, rb_cv_have_signbit, - [AC_TRY_LINK([ + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include <math.h> -], [int v = signbit(-0.0);], +]], [[int v = signbit(-0.0);]])], rb_cv_have_signbit=yes, rb_cv_have_signbit=no)]) AS_IF([test "$rb_cv_have_signbit" = yes], [ @@ -1978,7 +2016,7 @@ AS_CASE(["$ac_cv_func_memset_s:$ac_cv_func_qsort_s"], [*yes*], AS_IF([test "$ac_cv_func_getcwd" = yes], [ AC_CACHE_CHECK(if getcwd allocates buffer if NULL is given, [rb_cv_getcwd_malloc], - [AC_TRY_RUN([ + [AC_RUN_IFELSE([AC_LANG_SOURCE([[ @%:@include <stddef.h> @%:@include <stdio.h> @%:@ifdef HAVE_UNISTD_H @@ -1997,7 +2035,7 @@ main(int argc, char **argv) if (!getcwd(NULL, 0)) return EXIT_FAILURE; return EXIT_SUCCESS; } -], +]])], rb_cv_getcwd_malloc=yes, rb_cv_getcwd_malloc=no, AS_CASE($target_os, @@ -2045,21 +2083,21 @@ RUBY_CHECK_BUILTIN_FUNC(__builtin_trap, [__builtin_trap()]) AS_IF([test "$ac_cv_func_qsort_r" != no], [ AC_CACHE_CHECK(whether qsort_r is GNU version, rb_cv_gnu_qsort_r, - [AC_TRY_COMPILE([ + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @%:@include <stdlib.h> void (qsort_r)(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, void *), void *arg); -],[ ], +]], [[ ]])], [rb_cv_gnu_qsort_r=yes], [rb_cv_gnu_qsort_r=no]) ]) AC_CACHE_CHECK(whether qsort_r is BSD version, rb_cv_bsd_qsort_r, - [AC_TRY_COMPILE([ + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @%:@include <stdlib.h> void (qsort_r)(void *base, size_t nmemb, size_t size, void *arg, int (*compar)(void *, const void *, const void *)); -],[ ], +]], [[ ]])], [rb_cv_bsd_qsort_r=yes], [rb_cv_bsd_qsort_r=no]) ]) @@ -2074,7 +2112,7 @@ void (qsort_r)(void *base, size_t nmemb, size_t size, AC_CACHE_CHECK(whether atan2 handles Inf as C99, rb_cv_atan2_inf_c99, [ AS_IF([test $ac_cv_func_atan2f:$ac_cv_func_atan2l = yes:yes], [ - AC_TRY_RUN([ + AC_RUN_IFELSE([AC_LANG_SOURCE([[ @%:@include <math.h> @%:@ifdef HAVE_UNISTD_H @%:@include <unistd.h> @@ -2092,7 +2130,7 @@ main(int argc, char **argv) if (fabs(atan2(INFINITY, INFINITY) - M_PI_4) <= 0.01) return EXIT_SUCCESS; return EXIT_FAILURE; } -], +]])], [rb_cv_atan2_inf_c99=yes], [rb_cv_atan2_inf_c99=no], [AS_CASE($target_os, [mingw*|mswin*], [rb_cv_atan2_inf_c99=no], [rb_cv_atan2_inf_c99=yes])] @@ -2121,9 +2159,9 @@ AS_IF([test x"$ac_cv_lib_rt_timer_settime" = xyes], [ ]) AC_CACHE_CHECK(for unsetenv returns a value, rb_cv_unsetenv_return_value, - [AC_TRY_COMPILE([ + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include <stdlib.h> -], [int v = unsetenv("foo");], +]], [[int v = unsetenv("foo");]])], rb_cv_unsetenv_return_value=yes, rb_cv_unsetenv_return_value=no)]) AS_IF([test "$rb_cv_unsetenv_return_value" = no], [ @@ -2141,21 +2179,21 @@ AS_IF([test "$use_setreuid" = yes], [ ]) AC_STRUCT_TIMEZONE AC_CACHE_CHECK(for struct tm.tm_gmtoff, rb_cv_member_struct_tm_tm_gmtoff, - [AC_TRY_COMPILE([ + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @%:@define _BSD_SOURCE @%:@define _DEFAULT_SOURCE @%:@include <time.h> - ], - [struct tm t; t.tm_gmtoff = 3600;], + ]], + [[struct tm t; t.tm_gmtoff = 3600;]])], [rb_cv_member_struct_tm_tm_gmtoff=yes], [rb_cv_member_struct_tm_tm_gmtoff=no])]) AS_IF([test "$rb_cv_member_struct_tm_tm_gmtoff" = yes], [ AC_DEFINE(HAVE_STRUCT_TM_TM_GMTOFF) ]) AC_CACHE_CHECK(for external int daylight, rb_cv_have_daylight, - [AC_TRY_LINK([#include <time.h> - int i;], - [i = daylight;], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <time.h> + int i;]], + [[i = daylight;]])], rb_cv_have_daylight=yes, rb_cv_have_daylight=no)]) AS_IF([test "$rb_cv_have_daylight" = yes], [ @@ -2163,7 +2201,7 @@ AS_IF([test "$rb_cv_have_daylight" = yes], [ ]) AC_CACHE_CHECK(for negative time_t for gmtime(3), rb_cv_negative_time_t, - [AC_TRY_RUN([ + [AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include <stdlib.h> #include <time.h> @@ -2193,7 +2231,7 @@ main() check(gmtime(&t), 1, 12, 13, 20, 52); return 0; } -], +]])], rb_cv_negative_time_t=yes, rb_cv_negative_time_t=no, rb_cv_negative_time_t=yes)]) @@ -2204,7 +2242,7 @@ AS_IF([test "$rb_cv_negative_time_t" = yes], [ # [ruby-dev:40910] overflow of time on FreeBSD # http://www.freebsd.org/cgi/query-pr.cgi?pr=145341 AC_CACHE_CHECK(for localtime(3) overflow correctly, rb_cv_localtime_overflow, - [AC_TRY_RUN([ + [AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include <stdlib.h> #include <time.h> @@ -2236,7 +2274,7 @@ main() check(t); return 0; } -], +]])], rb_cv_localtime_overflow=yes, rb_cv_localtime_overflow=no, rb_cv_localtime_overflow=no)]) @@ -2249,7 +2287,7 @@ AS_IF([test "$ac_cv_func_sigprocmask" = yes && test "$ac_cv_func_sigaction" = ye ], [ AC_CHECK_FUNCS(sigsetmask) AC_CACHE_CHECK(for BSD signal semantics, rb_cv_bsd_signal, - [AC_TRY_RUN([ + [AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include <stdio.h> #include <signal.h> @@ -2267,7 +2305,7 @@ main() kill(getpid(), SIGINT); return 0; } -], +]])], rb_cv_bsd_signal=yes, rb_cv_bsd_signal=no, rb_cv_bsd_signal=$ac_cv_func_sigsetmask)]) @@ -2313,7 +2351,7 @@ AS_IF([test "$rb_cv_rshift_sign" = yes], [ AS_IF([test "$ac_cv_func_copy_file_range" = no], [ AC_CACHE_CHECK([for copy_file_range], rb_cv_use_copy_file_range, - [AC_TRY_RUN([ + [AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include <sys/types.h> #include <sys/stat.h> #include <sys/syscall.h> @@ -2340,7 +2378,7 @@ main() return 1; #endif } - ], + ]])], [rb_cv_use_copy_file_range=yes], [rb_cv_use_copy_file_range=no], [rb_cv_use_copy_file_range=no])]) @@ -2481,13 +2519,13 @@ AS_IF([test x"$enable_pthread" = xyes], [ AC_MSG_WARN("Don't know how to find pthread library on your system -- thread support disabled") ]) AC_CACHE_CHECK([whether pthread_t is scalar type], [rb_cv_scalar_pthread_t], [ - AC_TRY_COMPILE([ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @%:@include <pthread.h> - ], [ + ]], [[ pthread_t thread_id; thread_id = 0; if (!thread_id) return 0; - ], [rb_cv_scalar_pthread_t=yes], [rb_cv_scalar_pthread_t=no]) + ]])],[rb_cv_scalar_pthread_t=yes],[rb_cv_scalar_pthread_t=no]) ]) AS_IF([test x"$rb_cv_scalar_pthread_t" = xyes], [ : # RUBY_CHECK_SIZEOF(pthread_t, [void* int long], [], [@%:@include <pthread.h>]) @@ -2513,14 +2551,14 @@ AS_IF([test x"$enable_pthread" = xyes], [ "(pthread_self(), \"%s\", name)" \ "(name)" \ ; do - AC_TRY_COMPILE([ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @%:@include <pthread.h> @%:@ifdef HAVE_PTHREAD_NP_H @%:@include <pthread_np.h> @%:@endif @%:@define SET_THREAD_NAME(name) pthread_setname_np${mac} - ], - [if (SET_THREAD_NAME("conftest")) return 1;], + ]], + [[if (SET_THREAD_NAME("conftest")) return 1;]])], [rb_cv_func_pthread_setname_np_arguments="${mac}" break]) done @@ -2544,8 +2582,8 @@ AS_IF([test x"$enable_pthread" = xyes], [ AS_IF([test x"$ac_cv_header_ucontext_h" = xno], [ AC_CACHE_CHECK([if signal.h defines ucontext_t], [rb_cv_ucontext_in_signal_h], - [AC_TRY_COMPILE([@%:@include <signal.h>], - [size_t size = sizeof(ucontext_t);], + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include <signal.h>]], + [[size_t size = sizeof(ucontext_t);]])], [rb_cv_ucontext_in_signal_h=yes], [rb_cv_ucontext_in_signal_h=no])]) AS_IF([test x"$rb_cv_ucontext_in_signal_h" = xyes], [ AC_DEFINE_UNQUOTED(UCONTEXT_IN_SIGNAL_H, 1) @@ -2553,14 +2591,14 @@ AS_IF([test x"$ac_cv_header_ucontext_h" = xno], [ ]) AS_IF([test x"$ac_cv_header_ucontext_h" = xyes -o x"$rb_cv_ucontext_in_signal_h" = xyes], [ AC_CACHE_CHECK([if mcontext_t is a pointer], [rb_cv_mcontext_t_ptr], - [AC_TRY_COMPILE([ + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @%:@include <signal.h> @%:@ifdef HAVE_UCONTEXT_H @%:@include <ucontext.h> @%:@endif mcontext_t test(mcontext_t mc) {return mc+1;} - ], - [test(0);], + ]], + [[test(0);]])], [rb_cv_mcontext_t_ptr=yes], [rb_cv_mcontext_t_ptr=no])]) AS_IF([test x"$rb_cv_mcontext_t_ptr" = xyes], [ AC_DEFINE_UNQUOTED(DEFINE_MCONTEXT_PTR(mc, uc), mcontext_t mc = (uc)->uc_mcontext) @@ -2574,7 +2612,7 @@ AS_IF([test x"$ac_cv_header_ucontext_h" = xyes -o x"$rb_cv_ucontext_in_signal_h" AS_IF([test "$ac_cv_func_fork_works" = "yes" -a "$rb_with_pthread" = "yes"], [ AC_CACHE_CHECK([if fork works with pthread], rb_cv_fork_with_pthread, - [AC_TRY_RUN([ + [AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include <stdlib.h> #include <unistd.h> #include <pthread.h> @@ -2630,7 +2668,7 @@ main(int argc, char *argv[]) } return EXIT_SUCCESS; -}], +}]])], rb_cv_fork_with_pthread=yes, rb_cv_fork_with_pthread=no, rb_cv_fork_with_pthread=yes)]) @@ -2653,7 +2691,7 @@ AC_ARG_WITH(dln-a-out, with_dln_a_out=no])], [with_dln_a_out=no]) AC_CACHE_CHECK(whether ELF binaries are produced, rb_cv_binary_elf, -[AC_TRY_LINK([],[], [ +[AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])],[ AS_CASE(["`head -1 conftest$EXEEXT | tr -dc '\177ELF' | tr '\177' .`"], [.ELF*], [rb_cv_binary_elf=yes], [rb_cv_binary_elf=no])], rb_cv_binary_elf=no)]) @@ -2810,6 +2848,14 @@ AS_IF([test "$with_dln_a_out" != yes], [ : ${LDFLAGS=""} : ${LIBPATHENV=DYLD_FALLBACK_LIBRARY_PATH} : ${PRELOADENV=DYLD_INSERT_LIBRARIES} + AS_IF([test x"$enable_shared" = xyes], [ + # Resolve symbols from libruby.dylib when --enable-shared + EXTDLDFLAGS='$(LIBRUBYARG_SHARED)' + ], [test "x$EXTSTATIC" = x], [ + # When building exts as bundles, a mach-o bundle needs to know its loader + # program to bind symbols from the ruby executable + EXTDLDFLAGS="-bundle_loader '\$(BUILTRUBY)'" + ]) rb_cv_dlopen=yes], [aix*], [ : ${LDSHARED='$(CC)'} AS_IF([test "$GCC" = yes], [ @@ -2846,15 +2892,26 @@ AS_IF([test "$with_dln_a_out" != yes], [ AS_IF([test "$rb_cv_dlopen" = yes], [ AS_CASE(["$target_os"], [darwin*], [ + AC_SUBST(ADDITIONAL_DLDFLAGS, "") for flag in \ - "-undefined dynamic_lookup" \ "-multiply_defined suppress" \ + "-undefined dynamic_lookup" \ ; do - test "x${linker_flag}" = x || flag="${linker_flag}`echo ${flag} | tr ' ' ,`" - RUBY_TRY_LDFLAGS([$flag], [], [flag=]) - AS_IF([test "x$flag" != x], [ - RUBY_APPEND_OPTIONS(DLDFLAGS, [$flag]) - ]) + test "x${linker_flag}" = x || flag="${linker_flag}`echo ${flag} | tr ' ' ,`" + RUBY_TRY_LDFLAGS([$flag], [], [flag=]) + AS_IF([test x"$flag" = x], [continue]) + + AC_MSG_CHECKING([whether $flag is accepted for bundle]) + : > conftest.c + AS_IF([${LDSHARED/'$(CC)'/$CC} -o conftest.bundle $flag conftest.c >/dev/null 2>conftest.err && + test ! -s conftest.err], [ + AC_MSG_RESULT([yes]) + RUBY_APPEND_OPTIONS(DLDFLAGS, [$flag]) + ], [ + AC_MSG_RESULT([no]) + RUBY_APPEND_OPTIONS(ADDITIONAL_DLDFLAGS, [$flag]) + ]) + rm -fr conftest.* done ]) ]) @@ -2930,7 +2987,7 @@ AC_CHECK_FUNCS(backtrace) AS_IF([test "x$ac_cv_func_backtrace" = xyes], [ AC_CACHE_CHECK(for broken backtrace, rb_cv_broken_backtrace, - [AC_TRY_RUN([ + [AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include <unistd.h> #include <stdio.h> #include <stdlib.h> @@ -2979,7 +3036,7 @@ main(void) a[0] = 1; return EXIT_SUCCESS; } -], +]])], rb_cv_broken_backtrace=no, rb_cv_broken_backtrace=yes, rb_cv_broken_backtrace=no)]) @@ -2999,11 +3056,10 @@ AS_IF([test "$ac_cv_header_a_out_h" = yes], [ AS_IF([test "$with_dln_a_out" = yes || test "$rb_cv_dlopen" = unknown], [ cat confdefs.h > config.h AC_CACHE_CHECK(whether matz's dln works, rb_cv_dln_a_out, - [AC_TRY_COMPILE([ + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #define USE_DLN_A_OUT #include "dln.c" -], - [], +]], [[]])], rb_cv_dln_a_out=yes, rb_cv_dln_a_out=no)]) AS_IF([test "$rb_cv_dln_a_out" = yes], [ @@ -3076,10 +3132,10 @@ AS_IF([test "$with_dln_a_out" = yes], [ AC_ARG_WITH(ext, - AC_HELP_STRING([--with-ext=EXTS], + AS_HELP_STRING([--with-ext=EXTS], [pass to --with-ext option of extmk.rb])) AC_ARG_WITH(out-ext, - AC_HELP_STRING([--with-out-ext=EXTS], + AS_HELP_STRING([--with-out-ext=EXTS], [pass to --without-ext option of extmk.rb])) EXTSTATIC= AC_SUBST(EXTSTATIC)dnl @@ -3263,6 +3319,11 @@ AS_CASE("$enable_shared", [yes], [ AC_DEFINE_UNQUOTED(LIBDIR_BASENAME, ["${libdir_basename}"]) libdir_basename="${libdir_basename}"${multiarch+'/${arch}'} + RUBY_TRY_LDFLAGS([${linker_flag}--no-as-needed], [no_as_needed=yes], [no_as_needed=no]) + AS_IF([test "$no_as_needed" = yes], [ + RUBY_APPEND_OPTIONS(LDFLAGS, [${linker_flag}--no-as-needed]) + ]) + AS_CASE(["$target_os"], [freebsd*|dragonfly*], [], [ @@ -3541,7 +3602,7 @@ AS_IF([test "$rb_with_pthread" = "yes"], [ THREAD_MODEL=pthread ]) AC_CACHE_CHECK([for prefix of external symbols], rb_cv_symbol_prefix, [ - AC_TRY_COMPILE([extern void conftest_external(void) {}], [], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[extern void conftest_external(void) {}]], [[]])],[ rb_cv_symbol_prefix=`$NM conftest.$ac_objext | sed -n ['/.*T[ ]\([^ ]*\)conftest_external.*/!d;s//\1/p;q']` ], @@ -3552,7 +3613,7 @@ SYMBOL_PREFIX="$rb_cv_symbol_prefix" test "x$SYMBOL_PREFIX" = xNONE && SYMBOL_PREFIX='' DLNOBJ=dln.o AC_ARG_ENABLE(dln, - AC_HELP_STRING([--disable-dln], [disable dynamic link feature]), + AS_HELP_STRING([--disable-dln], [disable dynamic link feature]), [test "$enableval" = yes || DLNOBJ=dmydln.o]) AC_SUBST(DLNOBJ) MINIDLNOBJ=dmydln.o @@ -3684,13 +3745,12 @@ AS_IF([test "${universal_binary-no}" = yes ], [ AC_CACHE_CHECK([for architecture macros], rb_cv_architecture_macros, [ mv confdefs.h confdefs1.h : > confdefs.h - AC_TRY_COMPILE([@%:@if defined __`echo ${universal_archnames} | + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@if defined __`echo ${universal_archnames} | sed 's/=[^ ]*//g;s/ /__ || defined __/g'`__ @%:@else @%:@error >>>>>><<<<<< -@%:@endif], [], -[ +@%:@endif]], [[]])],[ rb_cv_architecture_macros=yes mv -f confdefs1.h confdefs.h ], [ @@ -3703,16 +3763,17 @@ AS_IF([test "${universal_binary-no}" = yes ], [ CFLAGS="$new_cflags -arch $archs" archs="__${archs}__" AC_MSG_CHECKING([for macro ${archs} on ${cpu}]) - AC_TRY_COMPILE([@%:@ifndef ${archs} + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@ifndef ${archs} @%:@error -@%:@endif], [], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no])]) +@%:@endif]], [[]])], + [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no])]) done mv -f confdefs1.h confdefs.h AC_MSG_ERROR([failed]) ])]) AC_CACHE_CHECK(whether __ARCHITECTURE__ is available, rb_cv_architecture_available, - AC_TRY_COMPILE([@%:@include <stdio.h> - const char arch[[]] = __ARCHITECTURE__;], [puts(arch);], + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include <stdio.h> + const char arch[[]] = __ARCHITECTURE__;]], [[puts(arch);]])], [rb_cv_architecture_available=yes], [rb_cv_architecture_available=no])) ]) @@ -4115,7 +4176,7 @@ AC_CONFIG_FILES(Makefile:template/Makefile.in, [ [EXEEXT='$EXEEXT' MAKE='${MAKE-make}' gnumake='$gnumake' GIT='$GIT']) AC_ARG_WITH([ruby-pc], - AC_HELP_STRING([--with-ruby-pc=FILENAME], [pc file basename]), + AS_HELP_STRING([--with-ruby-pc=FILENAME], [pc file basename]), [ruby_pc="$withval"], [ruby_pc="${RUBY_BASE_NAME}-${MAJOR}.${MINOR}.pc"]) AC_SUBST(ruby_pc) @@ -1155,6 +1155,11 @@ VALUE rb_fiberptr_self(struct rb_fiber_struct *fiber) return fiber->cont.self; } +unsigned int rb_fiberptr_blocking(struct rb_fiber_struct *fiber) +{ + return fiber->blocking; +} + // This is used for root_fiber because other fibers call cont_init_mjit_cont through cont_new. void rb_fiber_init_mjit_cont(struct rb_fiber_struct *fiber) @@ -2003,7 +2008,7 @@ rb_fiber_set_scheduler(VALUE klass, VALUE scheduler) return rb_scheduler_set(scheduler); } -static void rb_fiber_terminate(rb_fiber_t *fiber, int need_interrupt); +NORETURN(static void rb_fiber_terminate(rb_fiber_t *fiber, int need_interrupt)); void rb_fiber_start(void) @@ -2379,6 +2384,7 @@ rb_fiber_terminate(rb_fiber_t *fiber, int need_interrupt) next_fiber = return_fiber(true); if (need_interrupt) RUBY_VM_SET_INTERRUPT(&next_fiber->cont.saved_ec); fiber_switch(next_fiber, 1, &value, RB_NO_KEYWORDS, Qfalse, false); + ruby_stop(0); } VALUE diff --git a/debug_counter.h b/debug_counter.h index 6dc66ef988..3c20821db6 100644 --- a/debug_counter.h +++ b/debug_counter.h @@ -62,6 +62,9 @@ RB_DEBUG_COUNTER(ccs_not_found) // count for not found corresponding ccs on met // vm_eval.c RB_DEBUG_COUNTER(call0_public) RB_DEBUG_COUNTER(call0_other) +RB_DEBUG_COUNTER(gccct_hit) +RB_DEBUG_COUNTER(gccct_miss) +RB_DEBUG_COUNTER(gccct_null) // iseq RB_DEBUG_COUNTER(iseq_num) // number of total created iseq @@ -2273,6 +2273,8 @@ glob_helper( int escape = !(flags & FNM_NOESCAPE); size_t pathlen = baselen + namelen; + rb_check_stack_overflow(); + for (cur = beg; cur < end; ++cur) { struct glob_pattern *p = *cur; if (p->type == RECURSIVE) { diff --git a/enc/Makefile.in b/enc/Makefile.in index 9203874386..3854f66de6 100644 --- a/enc/Makefile.in +++ b/enc/Makefile.in @@ -21,6 +21,7 @@ TRANSSODIR = $(ENCSODIR)/trans DLEXT = @DLEXT@ OBJEXT = @OBJEXT@ LIBEXT = @LIBEXT@ +EXEEXT = @EXEEXT@ TIMESTAMPDIR = $(EXTOUT)/.timestamp ENC_TRANS_D = $(TIMESTAMPDIR)/.enc-trans.time ENC_TRANS_SO_D = $(TIMESTAMPDIR)/.enc-trans.so.time @@ -34,6 +35,7 @@ RUBY_SO_NAME = @RUBY_SO_NAME@ LIBRUBY = @LIBRUBY@ LIBRUBYARG_SHARED = @LIBRUBYARG_SHARED@ LIBRUBYARG_STATIC = $(LIBRUBYARG_SHARED) +BUILTRUBY = $(topdir)/miniruby$(EXEEXT) empty = AR = @AR@ diff --git a/encoding.c b/encoding.c index 330be29f4b..32d5a349eb 100644 --- a/encoding.c +++ b/encoding.c @@ -101,8 +101,6 @@ static rb_encoding *global_enc_ascii, #define ENCODING_NAMELEN_MAX 63 #define valid_encoding_name_p(name) ((name) && strlen(name) <= ENCODING_NAMELEN_MAX) -#define enc_autoload_p(enc) (!rb_enc_mbmaxlen(enc)) - static const rb_data_type_t encoding_data_type = { "encoding", {0, 0, 0,}, @@ -207,16 +205,14 @@ rb_enc_dummy_p(rb_encoding *enc) return ENC_DUMMY_P(enc) != 0; } -static int enc_autoload(rb_encoding *); - static int check_encoding(rb_encoding *enc) { int index = rb_enc_to_index(enc); if (rb_enc_from_index(index) != enc) return -1; - if (enc_autoload_p(enc)) { - index = enc_autoload(enc); + if (rb_enc_autoload_p(enc)) { + index = rb_enc_autoload(enc); } return index; } @@ -260,7 +256,7 @@ must_encindex(int index) rb_raise(rb_eEncodingError, "wrong encoding index %d for %s (expected %d)", index, rb_enc_name(enc), ENC_TO_ENCINDEX(enc)); } - if (enc_autoload_p(enc) && enc_autoload(enc) == -1) { + if (rb_enc_autoload_p(enc) && rb_enc_autoload(enc) == -1) { rb_loaderror("failed to load encoding (%s)", rb_enc_name(enc)); } @@ -444,7 +440,7 @@ rb_enc_register(const char *name, rb_encoding *encoding) if (STRCASECMP(name, rb_enc_name(oldenc))) { index = enc_register(enc_table, name, encoding); } - else if (enc_autoload_p(oldenc) || !ENC_DUMMY_P(oldenc)) { + else if (rb_enc_autoload_p(oldenc) || !ENC_DUMMY_P(oldenc)) { enc_register_at(enc_table, index, name, encoding); } else { @@ -834,7 +830,7 @@ load_encoding(const char *name) else if ((idx = enc_registered(enc_table, name)) < 0) { idx = -1; } - else if (enc_autoload_p(enc_table->list[idx].enc)) { + else if (rb_enc_autoload_p(enc_table->list[idx].enc)) { idx = -1; } } @@ -853,8 +849,8 @@ enc_autoload_body(struct enc_table *enc_table, rb_encoding *enc) do { if (i >= enc_table->count) return -1; } while (enc_table->list[i].enc != base && (++i, 1)); - if (enc_autoload_p(base)) { - if (enc_autoload(base) < 0) return -1; + if (rb_enc_autoload_p(base)) { + if (rb_enc_autoload(base) < 0) return -1; } i = enc->ruby_encoding_index; enc_register_at(enc_table, i & ENC_INDEX_MASK, rb_enc_name(enc), base); @@ -867,8 +863,8 @@ enc_autoload_body(struct enc_table *enc_table, rb_encoding *enc) } } -static int -enc_autoload(rb_encoding *enc) +int +rb_enc_autoload(rb_encoding *enc) { int i; GLOBAL_ENC_TABLE_EVAL(enc_table, i = enc_autoload_body(enc_table, enc)); @@ -895,8 +891,8 @@ rb_enc_find_index(const char *name) rb_raise(rb_eArgError, "encoding %s is not registered", name); } } - else if (enc_autoload_p(enc)) { - if (enc_autoload(enc) < 0) { + else if (rb_enc_autoload_p(enc)) { + if (rb_enc_autoload(enc) < 0) { rb_warn("failed to load encoding (%s); use ASCII-8BIT instead", name); return 0; @@ -1340,7 +1336,7 @@ enc_inspect(VALUE self) "#<%"PRIsVALUE":%s%s%s>", rb_obj_class(self), rb_enc_name(enc), (ENC_DUMMY_P(enc) ? " (dummy)" : ""), - enc_autoload_p(enc) ? " (autoload)" : ""); + rb_enc_autoload_p(enc) ? " (autoload)" : ""); } /* @@ -682,7 +682,7 @@ enum_to_a(int argc, VALUE *argv, VALUE obj) { VALUE ary = rb_ary_new(); - rb_block_call(obj, id_each, argc, argv, collect_all, ary); + rb_block_call_kw(obj, id_each, argc, argv, collect_all, ary, RB_PASS_CALLED_KEYWORDS); return ary; } @@ -805,7 +805,7 @@ ary_inject_op(VALUE ary, VALUE init, VALUE op) if (FIXNUM_P(e)) { n += FIX2LONG(e); /* should not overflow long type */ if (!FIXABLE(n)) { - v = rb_big_plus(ULONG2NUM(n), v); + v = rb_big_plus(LONG2NUM(n), v); n = 0; } } diff --git a/enumerator.c b/enumerator.c index b4a7cb588e..90d2ec433c 100644 --- a/enumerator.c +++ b/enumerator.c @@ -2678,8 +2678,13 @@ lazy_with_index_proc(VALUE proc_entry, struct MEMO* result, VALUE memos, long me return result; } +static VALUE +lazy_with_index_size(VALUE proc, VALUE receiver) { + return receiver; +} + static const lazyenum_funcs lazy_with_index_funcs = { - lazy_with_index_proc, 0, + lazy_with_index_proc, lazy_with_index_size, }; /* @@ -306,7 +306,8 @@ rb_warning_warn(VALUE mod, VALUE str) static int rb_warning_warn_arity(void) { - return rb_method_entry_arity(rb_method_entry(rb_singleton_class(rb_mWarning), id_warn)); + const rb_method_entry_t *me = rb_method_entry(rb_singleton_class(rb_mWarning), id_warn); + return me ? rb_method_entry_arity(me) : 1; } static VALUE @@ -1950,8 +1951,10 @@ name_err_mesg_to_str(VALUE obj) d = rb_protect(name_err_mesg_receiver_name, obj, &state); if (state || d == Qundef || d == Qnil) d = rb_protect(rb_inspect, obj, &state); - if (state) + if (state) { rb_set_errinfo(Qnil); + } + d = rb_check_string_type(d); if (NIL_P(d)) { d = rb_any_to_s(obj); } @@ -1021,7 +1021,7 @@ rb_vrescue2(VALUE (* b_proc) (VALUE), VALUE data1, else if (result) { /* escape from r_proc */ if (state == TAG_RETRY) { - state = 0; + state = TAG_NONE; ec->errinfo = Qnil; result = Qfalse; goto retry_entry; @@ -1033,17 +1033,21 @@ rb_vrescue2(VALUE (* b_proc) (VALUE), VALUE data1, if (state == TAG_RAISE) { int handle = FALSE; VALUE eclass; + va_list ap; - while ((eclass = va_arg(args, VALUE)) != 0) { + result = Qnil; + /* reuses args when raised again after retrying in r_proc */ + va_copy(ap, args); + while ((eclass = va_arg(ap, VALUE)) != 0) { if (rb_obj_is_kind_of(ec->errinfo, eclass)) { handle = TRUE; break; } } + va_end(ap); if (handle) { - result = Qnil; - state = 0; + state = TAG_NONE; if (r_proc) { result = (*r_proc) (data2, ec->errinfo); } diff --git a/eval_intern.h b/eval_intern.h index 34489777a2..9fa9031189 100644 --- a/eval_intern.h +++ b/eval_intern.h @@ -302,7 +302,16 @@ VALUE rb_ec_backtrace_location_ary(const rb_execution_context_t *ec, long lev, l #ifndef CharNext /* defined as CharNext[AW] on Windows. */ # ifdef HAVE_MBLEN -# define CharNext(p) ((p) + mblen((p), RUBY_MBCHAR_MAXSIZE)) +# define CharNext(p) rb_char_next(p) +static inline const char * +rb_char_next(const char *p) +{ + if (p) { + int len = mblen(p, RUBY_MBCHAR_MAXSIZE); + p += len > 0 ? len : 1; + } + return p; +} # else # define CharNext(p) ((p) + 1) # endif diff --git a/ext/-test-/array/concat/depend b/ext/-test-/array/concat/depend new file mode 100644 index 0000000000..4f2ba01f71 --- /dev/null +++ b/ext/-test-/array/concat/depend @@ -0,0 +1,322 @@ +# AUTOGENERATED DEPENDENCIES START +resize.o: $(RUBY_EXTCONF_H) +resize.o: $(arch_hdrdir)/ruby/config.h +resize.o: $(hdrdir)/ruby/assert.h +resize.o: $(hdrdir)/ruby/backward.h +resize.o: $(hdrdir)/ruby/backward/2/assume.h +resize.o: $(hdrdir)/ruby/backward/2/attributes.h +resize.o: $(hdrdir)/ruby/backward/2/bool.h +resize.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h +resize.o: $(hdrdir)/ruby/backward/2/inttypes.h +resize.o: $(hdrdir)/ruby/backward/2/limits.h +resize.o: $(hdrdir)/ruby/backward/2/long_long.h +resize.o: $(hdrdir)/ruby/backward/2/stdalign.h +resize.o: $(hdrdir)/ruby/backward/2/stdarg.h +resize.o: $(hdrdir)/ruby/defines.h +resize.o: $(hdrdir)/ruby/intern.h +resize.o: $(hdrdir)/ruby/internal/anyargs.h +resize.o: $(hdrdir)/ruby/internal/arithmetic.h +resize.o: $(hdrdir)/ruby/internal/arithmetic/char.h +resize.o: $(hdrdir)/ruby/internal/arithmetic/double.h +resize.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h +resize.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h +resize.o: $(hdrdir)/ruby/internal/arithmetic/int.h +resize.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h +resize.o: $(hdrdir)/ruby/internal/arithmetic/long.h +resize.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h +resize.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h +resize.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h +resize.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h +resize.o: $(hdrdir)/ruby/internal/arithmetic/short.h +resize.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h +resize.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h +resize.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h +resize.o: $(hdrdir)/ruby/internal/assume.h +resize.o: $(hdrdir)/ruby/internal/attr/alloc_size.h +resize.o: $(hdrdir)/ruby/internal/attr/artificial.h +resize.o: $(hdrdir)/ruby/internal/attr/cold.h +resize.o: $(hdrdir)/ruby/internal/attr/const.h +resize.o: $(hdrdir)/ruby/internal/attr/constexpr.h +resize.o: $(hdrdir)/ruby/internal/attr/deprecated.h +resize.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h +resize.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h +resize.o: $(hdrdir)/ruby/internal/attr/error.h +resize.o: $(hdrdir)/ruby/internal/attr/flag_enum.h +resize.o: $(hdrdir)/ruby/internal/attr/forceinline.h +resize.o: $(hdrdir)/ruby/internal/attr/format.h +resize.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h +resize.o: $(hdrdir)/ruby/internal/attr/noalias.h +resize.o: $(hdrdir)/ruby/internal/attr/nodiscard.h +resize.o: $(hdrdir)/ruby/internal/attr/noexcept.h +resize.o: $(hdrdir)/ruby/internal/attr/noinline.h +resize.o: $(hdrdir)/ruby/internal/attr/nonnull.h +resize.o: $(hdrdir)/ruby/internal/attr/noreturn.h +resize.o: $(hdrdir)/ruby/internal/attr/pure.h +resize.o: $(hdrdir)/ruby/internal/attr/restrict.h +resize.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h +resize.o: $(hdrdir)/ruby/internal/attr/warning.h +resize.o: $(hdrdir)/ruby/internal/attr/weakref.h +resize.o: $(hdrdir)/ruby/internal/cast.h +resize.o: $(hdrdir)/ruby/internal/compiler_is.h +resize.o: $(hdrdir)/ruby/internal/compiler_is/apple.h +resize.o: $(hdrdir)/ruby/internal/compiler_is/clang.h +resize.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h +resize.o: $(hdrdir)/ruby/internal/compiler_is/intel.h +resize.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h +resize.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h +resize.o: $(hdrdir)/ruby/internal/compiler_since.h +resize.o: $(hdrdir)/ruby/internal/config.h +resize.o: $(hdrdir)/ruby/internal/constant_p.h +resize.o: $(hdrdir)/ruby/internal/core.h +resize.o: $(hdrdir)/ruby/internal/core/rarray.h +resize.o: $(hdrdir)/ruby/internal/core/rbasic.h +resize.o: $(hdrdir)/ruby/internal/core/rbignum.h +resize.o: $(hdrdir)/ruby/internal/core/rclass.h +resize.o: $(hdrdir)/ruby/internal/core/rdata.h +resize.o: $(hdrdir)/ruby/internal/core/rfile.h +resize.o: $(hdrdir)/ruby/internal/core/rhash.h +resize.o: $(hdrdir)/ruby/internal/core/robject.h +resize.o: $(hdrdir)/ruby/internal/core/rregexp.h +resize.o: $(hdrdir)/ruby/internal/core/rstring.h +resize.o: $(hdrdir)/ruby/internal/core/rstruct.h +resize.o: $(hdrdir)/ruby/internal/core/rtypeddata.h +resize.o: $(hdrdir)/ruby/internal/ctype.h +resize.o: $(hdrdir)/ruby/internal/dllexport.h +resize.o: $(hdrdir)/ruby/internal/dosish.h +resize.o: $(hdrdir)/ruby/internal/error.h +resize.o: $(hdrdir)/ruby/internal/eval.h +resize.o: $(hdrdir)/ruby/internal/event.h +resize.o: $(hdrdir)/ruby/internal/fl_type.h +resize.o: $(hdrdir)/ruby/internal/gc.h +resize.o: $(hdrdir)/ruby/internal/glob.h +resize.o: $(hdrdir)/ruby/internal/globals.h +resize.o: $(hdrdir)/ruby/internal/has/attribute.h +resize.o: $(hdrdir)/ruby/internal/has/builtin.h +resize.o: $(hdrdir)/ruby/internal/has/c_attribute.h +resize.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h +resize.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h +resize.o: $(hdrdir)/ruby/internal/has/extension.h +resize.o: $(hdrdir)/ruby/internal/has/feature.h +resize.o: $(hdrdir)/ruby/internal/has/warning.h +resize.o: $(hdrdir)/ruby/internal/intern/array.h +resize.o: $(hdrdir)/ruby/internal/intern/bignum.h +resize.o: $(hdrdir)/ruby/internal/intern/class.h +resize.o: $(hdrdir)/ruby/internal/intern/compar.h +resize.o: $(hdrdir)/ruby/internal/intern/complex.h +resize.o: $(hdrdir)/ruby/internal/intern/cont.h +resize.o: $(hdrdir)/ruby/internal/intern/dir.h +resize.o: $(hdrdir)/ruby/internal/intern/enum.h +resize.o: $(hdrdir)/ruby/internal/intern/enumerator.h +resize.o: $(hdrdir)/ruby/internal/intern/error.h +resize.o: $(hdrdir)/ruby/internal/intern/eval.h +resize.o: $(hdrdir)/ruby/internal/intern/file.h +resize.o: $(hdrdir)/ruby/internal/intern/gc.h +resize.o: $(hdrdir)/ruby/internal/intern/hash.h +resize.o: $(hdrdir)/ruby/internal/intern/io.h +resize.o: $(hdrdir)/ruby/internal/intern/load.h +resize.o: $(hdrdir)/ruby/internal/intern/marshal.h +resize.o: $(hdrdir)/ruby/internal/intern/numeric.h +resize.o: $(hdrdir)/ruby/internal/intern/object.h +resize.o: $(hdrdir)/ruby/internal/intern/parse.h +resize.o: $(hdrdir)/ruby/internal/intern/proc.h +resize.o: $(hdrdir)/ruby/internal/intern/process.h +resize.o: $(hdrdir)/ruby/internal/intern/random.h +resize.o: $(hdrdir)/ruby/internal/intern/range.h +resize.o: $(hdrdir)/ruby/internal/intern/rational.h +resize.o: $(hdrdir)/ruby/internal/intern/re.h +resize.o: $(hdrdir)/ruby/internal/intern/ruby.h +resize.o: $(hdrdir)/ruby/internal/intern/select.h +resize.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +resize.o: $(hdrdir)/ruby/internal/intern/signal.h +resize.o: $(hdrdir)/ruby/internal/intern/sprintf.h +resize.o: $(hdrdir)/ruby/internal/intern/string.h +resize.o: $(hdrdir)/ruby/internal/intern/struct.h +resize.o: $(hdrdir)/ruby/internal/intern/thread.h +resize.o: $(hdrdir)/ruby/internal/intern/time.h +resize.o: $(hdrdir)/ruby/internal/intern/variable.h +resize.o: $(hdrdir)/ruby/internal/intern/vm.h +resize.o: $(hdrdir)/ruby/internal/interpreter.h +resize.o: $(hdrdir)/ruby/internal/iterator.h +resize.o: $(hdrdir)/ruby/internal/memory.h +resize.o: $(hdrdir)/ruby/internal/method.h +resize.o: $(hdrdir)/ruby/internal/module.h +resize.o: $(hdrdir)/ruby/internal/newobj.h +resize.o: $(hdrdir)/ruby/internal/rgengc.h +resize.o: $(hdrdir)/ruby/internal/scan_args.h +resize.o: $(hdrdir)/ruby/internal/special_consts.h +resize.o: $(hdrdir)/ruby/internal/static_assert.h +resize.o: $(hdrdir)/ruby/internal/stdalign.h +resize.o: $(hdrdir)/ruby/internal/stdbool.h +resize.o: $(hdrdir)/ruby/internal/symbol.h +resize.o: $(hdrdir)/ruby/internal/value.h +resize.o: $(hdrdir)/ruby/internal/value_type.h +resize.o: $(hdrdir)/ruby/internal/variable.h +resize.o: $(hdrdir)/ruby/internal/warning_push.h +resize.o: $(hdrdir)/ruby/internal/xmalloc.h +resize.o: $(hdrdir)/ruby/missing.h +resize.o: $(hdrdir)/ruby/ruby.h +resize.o: $(hdrdir)/ruby/st.h +resize.o: $(hdrdir)/ruby/subst.h +resize.o: resize.c +to_ary_conact.o: $(RUBY_EXTCONF_H) +to_ary_conact.o: $(arch_hdrdir)/ruby/config.h +to_ary_conact.o: $(hdrdir)/ruby.h +to_ary_conact.o: $(hdrdir)/ruby/assert.h +to_ary_conact.o: $(hdrdir)/ruby/backward.h +to_ary_conact.o: $(hdrdir)/ruby/backward/2/assume.h +to_ary_conact.o: $(hdrdir)/ruby/backward/2/attributes.h +to_ary_conact.o: $(hdrdir)/ruby/backward/2/bool.h +to_ary_conact.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h +to_ary_conact.o: $(hdrdir)/ruby/backward/2/inttypes.h +to_ary_conact.o: $(hdrdir)/ruby/backward/2/limits.h +to_ary_conact.o: $(hdrdir)/ruby/backward/2/long_long.h +to_ary_conact.o: $(hdrdir)/ruby/backward/2/stdalign.h +to_ary_conact.o: $(hdrdir)/ruby/backward/2/stdarg.h +to_ary_conact.o: $(hdrdir)/ruby/defines.h +to_ary_conact.o: $(hdrdir)/ruby/intern.h +to_ary_conact.o: $(hdrdir)/ruby/internal/anyargs.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic/char.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic/double.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic/int.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic/long.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic/short.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h +to_ary_conact.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h +to_ary_conact.o: $(hdrdir)/ruby/internal/assume.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/alloc_size.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/artificial.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/cold.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/const.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/constexpr.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/deprecated.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/error.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/flag_enum.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/forceinline.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/format.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/noalias.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/nodiscard.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/noexcept.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/noinline.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/nonnull.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/noreturn.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/pure.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/restrict.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/warning.h +to_ary_conact.o: $(hdrdir)/ruby/internal/attr/weakref.h +to_ary_conact.o: $(hdrdir)/ruby/internal/cast.h +to_ary_conact.o: $(hdrdir)/ruby/internal/compiler_is.h +to_ary_conact.o: $(hdrdir)/ruby/internal/compiler_is/apple.h +to_ary_conact.o: $(hdrdir)/ruby/internal/compiler_is/clang.h +to_ary_conact.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h +to_ary_conact.o: $(hdrdir)/ruby/internal/compiler_is/intel.h +to_ary_conact.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h +to_ary_conact.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h +to_ary_conact.o: $(hdrdir)/ruby/internal/compiler_since.h +to_ary_conact.o: $(hdrdir)/ruby/internal/config.h +to_ary_conact.o: $(hdrdir)/ruby/internal/constant_p.h +to_ary_conact.o: $(hdrdir)/ruby/internal/core.h +to_ary_conact.o: $(hdrdir)/ruby/internal/core/rarray.h +to_ary_conact.o: $(hdrdir)/ruby/internal/core/rbasic.h +to_ary_conact.o: $(hdrdir)/ruby/internal/core/rbignum.h +to_ary_conact.o: $(hdrdir)/ruby/internal/core/rclass.h +to_ary_conact.o: $(hdrdir)/ruby/internal/core/rdata.h +to_ary_conact.o: $(hdrdir)/ruby/internal/core/rfile.h +to_ary_conact.o: $(hdrdir)/ruby/internal/core/rhash.h +to_ary_conact.o: $(hdrdir)/ruby/internal/core/robject.h +to_ary_conact.o: $(hdrdir)/ruby/internal/core/rregexp.h +to_ary_conact.o: $(hdrdir)/ruby/internal/core/rstring.h +to_ary_conact.o: $(hdrdir)/ruby/internal/core/rstruct.h +to_ary_conact.o: $(hdrdir)/ruby/internal/core/rtypeddata.h +to_ary_conact.o: $(hdrdir)/ruby/internal/ctype.h +to_ary_conact.o: $(hdrdir)/ruby/internal/dllexport.h +to_ary_conact.o: $(hdrdir)/ruby/internal/dosish.h +to_ary_conact.o: $(hdrdir)/ruby/internal/error.h +to_ary_conact.o: $(hdrdir)/ruby/internal/eval.h +to_ary_conact.o: $(hdrdir)/ruby/internal/event.h +to_ary_conact.o: $(hdrdir)/ruby/internal/fl_type.h +to_ary_conact.o: $(hdrdir)/ruby/internal/gc.h +to_ary_conact.o: $(hdrdir)/ruby/internal/glob.h +to_ary_conact.o: $(hdrdir)/ruby/internal/globals.h +to_ary_conact.o: $(hdrdir)/ruby/internal/has/attribute.h +to_ary_conact.o: $(hdrdir)/ruby/internal/has/builtin.h +to_ary_conact.o: $(hdrdir)/ruby/internal/has/c_attribute.h +to_ary_conact.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h +to_ary_conact.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h +to_ary_conact.o: $(hdrdir)/ruby/internal/has/extension.h +to_ary_conact.o: $(hdrdir)/ruby/internal/has/feature.h +to_ary_conact.o: $(hdrdir)/ruby/internal/has/warning.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/array.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/bignum.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/class.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/compar.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/complex.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/cont.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/dir.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/enum.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/enumerator.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/error.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/eval.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/file.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/gc.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/hash.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/io.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/load.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/marshal.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/numeric.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/object.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/parse.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/proc.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/process.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/random.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/range.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/rational.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/re.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/ruby.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/select.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/signal.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/sprintf.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/string.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/struct.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/thread.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/time.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/variable.h +to_ary_conact.o: $(hdrdir)/ruby/internal/intern/vm.h +to_ary_conact.o: $(hdrdir)/ruby/internal/interpreter.h +to_ary_conact.o: $(hdrdir)/ruby/internal/iterator.h +to_ary_conact.o: $(hdrdir)/ruby/internal/memory.h +to_ary_conact.o: $(hdrdir)/ruby/internal/method.h +to_ary_conact.o: $(hdrdir)/ruby/internal/module.h +to_ary_conact.o: $(hdrdir)/ruby/internal/newobj.h +to_ary_conact.o: $(hdrdir)/ruby/internal/rgengc.h +to_ary_conact.o: $(hdrdir)/ruby/internal/scan_args.h +to_ary_conact.o: $(hdrdir)/ruby/internal/special_consts.h +to_ary_conact.o: $(hdrdir)/ruby/internal/static_assert.h +to_ary_conact.o: $(hdrdir)/ruby/internal/stdalign.h +to_ary_conact.o: $(hdrdir)/ruby/internal/stdbool.h +to_ary_conact.o: $(hdrdir)/ruby/internal/symbol.h +to_ary_conact.o: $(hdrdir)/ruby/internal/token_paste.h +to_ary_conact.o: $(hdrdir)/ruby/internal/value.h +to_ary_conact.o: $(hdrdir)/ruby/internal/value_type.h +to_ary_conact.o: $(hdrdir)/ruby/internal/variable.h +to_ary_conact.o: $(hdrdir)/ruby/internal/warning_push.h +to_ary_conact.o: $(hdrdir)/ruby/internal/xmalloc.h +to_ary_conact.o: $(hdrdir)/ruby/missing.h +to_ary_conact.o: $(hdrdir)/ruby/ruby.h +to_ary_conact.o: $(hdrdir)/ruby/st.h +to_ary_conact.o: $(hdrdir)/ruby/subst.h +to_ary_conact.o: to_ary_conact.c +# AUTOGENERATED DEPENDENCIES END diff --git a/ext/-test-/array/concat/extconf.rb b/ext/-test-/array/concat/extconf.rb new file mode 100644 index 0000000000..cdd79126c9 --- /dev/null +++ b/ext/-test-/array/concat/extconf.rb @@ -0,0 +1,2 @@ +# frozen_string_literal: false +create_makefile("-test-/array/to_ary_concat") diff --git a/ext/-test-/array/concat/to_ary_conact.c b/ext/-test-/array/concat/to_ary_conact.c new file mode 100644 index 0000000000..ec1fd321ce --- /dev/null +++ b/ext/-test-/array/concat/to_ary_conact.c @@ -0,0 +1,34 @@ +#include "ruby.h" + +// Bar + +typedef struct { + int dummy; +} Bar; + +static rb_data_type_t Bar_type = { + "Bar", + {NULL, RUBY_TYPED_DEFAULT_FREE, NULL }, +}; + +static VALUE +Bar_alloc(VALUE klass) +{ + return TypedData_Wrap_Struct(klass, &Bar_type, NULL); +} + +VALUE Bar_to_ary(VALUE _self) { + VALUE ary = rb_ary_new2(2); + VALUE foo = rb_ary_new2(0); + rb_ary_push(ary, foo); + rb_ary_push(ary, foo); + rb_ary_push(ary, foo); + return ary; +} + +void Init_to_ary_concat() { + VALUE mBug = rb_define_module("Bug"); + VALUE bar = rb_define_class_under(mBug, "Bar", rb_cObject); + rb_define_alloc_func(bar, Bar_alloc); + rb_define_method(bar, "to_ary", Bar_to_ary, 0); +} diff --git a/ext/-test-/postponed_job/postponed_job.c b/ext/-test-/postponed_job/postponed_job.c index d8684d475a..fa57bef6f5 100644 --- a/ext/-test-/postponed_job/postponed_job.c +++ b/ext/-test-/postponed_job/postponed_job.c @@ -58,6 +58,34 @@ pjob_call_direct(VALUE self, VALUE obj) return self; } +#ifdef HAVE_PTHREAD_H +#include <pthread.h> + +static void * +pjob_register_in_c_thread_i(void *obj) +{ + rb_postponed_job_register_one(0, pjob_one_callback, (void *)obj); + rb_postponed_job_register_one(0, pjob_one_callback, (void *)obj); + rb_postponed_job_register_one(0, pjob_one_callback, (void *)obj); + return NULL; +} + +static VALUE +pjob_register_in_c_thread(VALUE self, VALUE obj) +{ + pthread_t thread; + if (pthread_create(&thread, NULL, pjob_register_in_c_thread_i, (void *)obj)) { + return Qfalse; + } + + if (pthread_join(thread, NULL)) { + return Qfalse; + } + + return Qtrue; +} +#endif + void Init_postponed_job(VALUE self) { @@ -65,5 +93,8 @@ Init_postponed_job(VALUE self) rb_define_module_function(mBug, "postponed_job_register", pjob_register, 1); rb_define_module_function(mBug, "postponed_job_register_one", pjob_register_one, 1); rb_define_module_function(mBug, "postponed_job_call_direct", pjob_call_direct, 1); +#ifdef HAVE_PTHREAD_H + rb_define_module_function(mBug, "postponed_job_register_in_c_thread", pjob_register_in_c_thread, 1); +#endif } diff --git a/ext/-test-/string/depend b/ext/-test-/string/depend index 67dfd2289f..7db4465bf9 100644 --- a/ext/-test-/string/depend +++ b/ext/-test-/string/depend @@ -1000,6 +1000,7 @@ fstring.o: $(hdrdir)/ruby/backward/2/long_long.h fstring.o: $(hdrdir)/ruby/backward/2/stdalign.h fstring.o: $(hdrdir)/ruby/backward/2/stdarg.h fstring.o: $(hdrdir)/ruby/defines.h +fstring.o: $(hdrdir)/ruby/encoding.h fstring.o: $(hdrdir)/ruby/intern.h fstring.o: $(hdrdir)/ruby/internal/anyargs.h fstring.o: $(hdrdir)/ruby/internal/arithmetic.h @@ -1142,6 +1143,8 @@ fstring.o: $(hdrdir)/ruby/internal/variable.h fstring.o: $(hdrdir)/ruby/internal/warning_push.h fstring.o: $(hdrdir)/ruby/internal/xmalloc.h fstring.o: $(hdrdir)/ruby/missing.h +fstring.o: $(hdrdir)/ruby/onigmo.h +fstring.o: $(hdrdir)/ruby/oniguruma.h fstring.o: $(hdrdir)/ruby/ruby.h fstring.o: $(hdrdir)/ruby/st.h fstring.o: $(hdrdir)/ruby/subst.h diff --git a/ext/-test-/string/enc_str_buf_cat.c b/ext/-test-/string/enc_str_buf_cat.c index 9ac4a298be..4c1b262e1e 100644 --- a/ext/-test-/string/enc_str_buf_cat.c +++ b/ext/-test-/string/enc_str_buf_cat.c @@ -7,8 +7,22 @@ enc_str_buf_cat(VALUE str, VALUE str2) return rb_enc_str_buf_cat(str, RSTRING_PTR(str2), RSTRING_LEN(str2), rb_enc_get(str2)); } +static VALUE +str_conv_enc_opts(VALUE str, VALUE from, VALUE to, VALUE ecflags, VALUE ecopts) +{ + rb_encoding *from_enc = NIL_P(from) ? NULL : rb_to_encoding(from); + rb_encoding *to_enc = NIL_P(to) ? NULL : rb_to_encoding(to); + int flags = NUM2INT(ecflags); + if (!NIL_P(ecopts)) { + Check_Type(ecopts, T_HASH); + OBJ_FREEZE(ecopts); + } + return rb_str_conv_enc_opts(str, from_enc, to_enc, flags, ecopts); +} + void Init_string_enc_str_buf_cat(VALUE klass) { rb_define_method(klass, "enc_str_buf_cat", enc_str_buf_cat, 1); + rb_define_method(klass, "str_conv_enc_opts", str_conv_enc_opts, 4); } diff --git a/ext/-test-/string/fstring.c b/ext/-test-/string/fstring.c index 30120b42f6..2374319fe3 100644 --- a/ext/-test-/string/fstring.c +++ b/ext/-test-/string/fstring.c @@ -1,4 +1,5 @@ #include "ruby.h" +#include "ruby/encoding.h" VALUE rb_fstring(VALUE str); @@ -8,8 +9,22 @@ bug_s_fstring(VALUE self, VALUE str) return rb_fstring(str); } +VALUE +bug_s_rb_enc_interned_str(VALUE self, VALUE encoding) +{ + return rb_enc_interned_str("foo", 3, RDATA(encoding)->data); +} + +VALUE +bug_s_rb_enc_str_new(VALUE self, VALUE encoding) +{ + return rb_enc_str_new("foo", 3, RDATA(encoding)->data); +} + void Init_string_fstring(VALUE klass) { rb_define_singleton_method(klass, "fstring", bug_s_fstring, 1); + rb_define_singleton_method(klass, "rb_enc_interned_str", bug_s_rb_enc_interned_str, 1); + rb_define_singleton_method(klass, "rb_enc_str_new", bug_s_rb_enc_str_new, 1); } diff --git a/ext/cgi/escape/escape.c b/ext/cgi/escape/escape.c index 77627e2f03..809f95ef4c 100644 --- a/ext/cgi/escape/escape.c +++ b/ext/cgi/escape/escape.c @@ -36,7 +36,8 @@ static VALUE optimized_escape_html(VALUE str) { VALUE vbuf; - char *buf = ALLOCV_N(char, vbuf, RSTRING_LEN(str) * HTML_ESCAPE_MAX_LEN); + typedef char escape_buf[HTML_ESCAPE_MAX_LEN]; + char *buf = *ALLOCV_N(escape_buf, vbuf, RSTRING_LEN(str)); const char *cstr = RSTRING_PTR(str); const char *end = cstr + RSTRING_LEN(str); @@ -388,7 +389,7 @@ cgiesc_unescape(int argc, VALUE *argv, VALUE self) void Init_escape(void) { -#if HAVE_RB_EXT_RACTOR_SAFE +#ifdef HAVE_RB_EXT_RACTOR_SAFE rb_ext_ractor_safe(true); #endif diff --git a/ext/date/date.gemspec b/ext/date/date.gemspec index 44282759c6..cf07696976 100644 --- a/ext/date/date.gemspec +++ b/ext/date/date.gemspec @@ -1,7 +1,12 @@ # frozen_string_literal: true + +version = File.foreach(File.expand_path("../lib/date.rb", __FILE__)).find do |line| + /^\s*VERSION\s*=\s*["'](.*)["']/ =~ line and break $1 +end + Gem::Specification.new do |s| s.name = "date" - s.version = '3.1.0' + s.version = version s.summary = "A subclass of Object includes Comparable module for handling dates." s.description = "A subclass of Object includes Comparable module for handling dates." diff --git a/ext/date/date_core.c b/ext/date/date_core.c index 7e9bf16a07..66ec0aa92a 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -4328,12 +4328,40 @@ date_s_strptime(int argc, VALUE *argv, VALUE klass) VALUE date__parse(VALUE str, VALUE comp); +static size_t +get_limit(VALUE opt) +{ + if (!NIL_P(opt)) { + VALUE limit = rb_hash_aref(opt, ID2SYM(rb_intern("limit"))); + if (NIL_P(limit)) return SIZE_MAX; + return NUM2SIZET(limit); + } + return 128; +} + +static void +check_limit(VALUE str, VALUE opt) +{ + if (NIL_P(str)) return; + if (SYMBOL_P(str)) str = rb_sym2str(str); + + StringValue(str); + size_t slen = RSTRING_LEN(str); + size_t limit = get_limit(opt); + if (slen > limit) { + rb_raise(rb_eArgError, + "string length (%"PRI_SIZE_PREFIX"u) exceeds the limit %"PRI_SIZE_PREFIX"u", slen, limit); + } +} + static VALUE date_s__parse_internal(int argc, VALUE *argv, VALUE klass) { - VALUE vstr, vcomp, hash; + VALUE vstr, vcomp, hash, opt; - rb_scan_args(argc, argv, "11", &vstr, &vcomp); + rb_scan_args(argc, argv, "11:", &vstr, &vcomp, &opt); + if (!NIL_P(opt)) argc--; + check_limit(vstr, opt); StringValue(vstr); if (!rb_enc_str_asciicompat_p(vstr)) rb_raise(rb_eArgError, @@ -4348,7 +4376,7 @@ date_s__parse_internal(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * Date._parse(string[, comp=true]) -> hash + * Date._parse(string[, comp=true], limit: 128) -> hash * * Parses the given representation of date and time, and returns a * hash of parsed elements. @@ -4363,6 +4391,10 @@ date_s__parse_internal(int argc, VALUE *argv, VALUE klass) * it full. * * Date._parse('2001-02-03') #=> {:year=>2001, :mon=>2, :mday=>3} + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s__parse(int argc, VALUE *argv, VALUE klass) @@ -4372,7 +4404,7 @@ date_s__parse(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * Date.parse(string='-4712-01-01'[, comp=true[, start=Date::ITALY]]) -> date + * Date.parse(string='-4712-01-01'[, comp=true[, start=Date::ITALY]], limit: 128) -> date * * Parses the given representation of date and time, and creates a * date object. @@ -4389,13 +4421,18 @@ date_s__parse(int argc, VALUE *argv, VALUE klass) * Date.parse('2001-02-03') #=> #<Date: 2001-02-03 ...> * Date.parse('20010203') #=> #<Date: 2001-02-03 ...> * Date.parse('3rd Feb 2001') #=> #<Date: 2001-02-03 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s_parse(int argc, VALUE *argv, VALUE klass) { - VALUE str, comp, sg; + VALUE str, comp, sg, opt; - rb_scan_args(argc, argv, "03", &str, &comp, &sg); + rb_scan_args(argc, argv, "03:", &str, &comp, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -4407,11 +4444,12 @@ date_s_parse(int argc, VALUE *argv, VALUE klass) } { - VALUE argv2[2], hash; - - argv2[0] = str; - argv2[1] = comp; - hash = date_s__parse(2, argv2, klass); + int argc2 = 2; + VALUE argv2[3]; + argv2[0] = str; + argv2[1] = comp; + if (!NIL_P(opt)) argv2[argc2++] = opt; + VALUE hash = date_s__parse(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } @@ -4425,19 +4463,28 @@ VALUE date__jisx0301(VALUE); /* * call-seq: - * Date._iso8601(string) -> hash + * Date._iso8601(string, limit: 128) -> hash * * Returns a hash of parsed elements. + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE -date_s__iso8601(VALUE klass, VALUE str) +date_s__iso8601(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + return date__iso8601(str); } /* * call-seq: - * Date.iso8601(string='-4712-01-01'[, start=Date::ITALY]) -> date + * Date.iso8601(string='-4712-01-01'[, start=Date::ITALY], limit: 128) -> date * * Creates a new Date object by parsing from a string according to * some typical ISO 8601 formats. @@ -4445,13 +4492,18 @@ date_s__iso8601(VALUE klass, VALUE str) * Date.iso8601('2001-02-03') #=> #<Date: 2001-02-03 ...> * Date.iso8601('20010203') #=> #<Date: 2001-02-03 ...> * Date.iso8601('2001-W05-6') #=> #<Date: 2001-02-03 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s_iso8601(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -4461,38 +4513,56 @@ date_s_iso8601(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__iso8601(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + VALUE hash = date_s__iso8601(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } /* * call-seq: - * Date._rfc3339(string) -> hash + * Date._rfc3339(string, limit: 128) -> hash * * Returns a hash of parsed elements. + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE -date_s__rfc3339(VALUE klass, VALUE str) +date_s__rfc3339(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + return date__rfc3339(str); } /* * call-seq: - * Date.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY]) -> date + * Date.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> date * * Creates a new Date object by parsing from a string according to * some typical RFC 3339 formats. * * Date.rfc3339('2001-02-03T04:05:06+07:00') #=> #<Date: 2001-02-03 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s_rfc3339(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -4502,38 +4572,56 @@ date_s_rfc3339(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__rfc3339(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + VALUE hash = date_s__rfc3339(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } /* * call-seq: - * Date._xmlschema(string) -> hash + * Date._xmlschema(string, limit: 128) -> hash * * Returns a hash of parsed elements. + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE -date_s__xmlschema(VALUE klass, VALUE str) +date_s__xmlschema(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + return date__xmlschema(str); } /* * call-seq: - * Date.xmlschema(string='-4712-01-01'[, start=Date::ITALY]) -> date + * Date.xmlschema(string='-4712-01-01'[, start=Date::ITALY], limit: 128) -> date * * Creates a new Date object by parsing from a string according to * some typical XML Schema formats. * * Date.xmlschema('2001-02-03') #=> #<Date: 2001-02-03 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s_xmlschema(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -4543,41 +4631,58 @@ date_s_xmlschema(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__xmlschema(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + VALUE hash = date_s__xmlschema(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } /* * call-seq: - * Date._rfc2822(string) -> hash - * Date._rfc822(string) -> hash + * Date._rfc2822(string, limit: 128) -> hash + * Date._rfc822(string, limit: 128) -> hash * * Returns a hash of parsed elements. + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE -date_s__rfc2822(VALUE klass, VALUE str) +date_s__rfc2822(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + return date__rfc2822(str); } /* * call-seq: - * Date.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY]) -> date - * Date.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY]) -> date + * Date.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> date + * Date.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> date * * Creates a new Date object by parsing from a string according to * some typical RFC 2822 formats. * * Date.rfc2822('Sat, 3 Feb 2001 00:00:00 +0000') * #=> #<Date: 2001-02-03 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s_rfc2822(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: @@ -4587,39 +4692,56 @@ date_s_rfc2822(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__rfc2822(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + VALUE hash = date_s__rfc2822(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } /* * call-seq: - * Date._httpdate(string) -> hash + * Date._httpdate(string, limit: 128) -> hash * * Returns a hash of parsed elements. + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE -date_s__httpdate(VALUE klass, VALUE str) +date_s__httpdate(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + return date__httpdate(str); } /* * call-seq: - * Date.httpdate(string='Mon, 01 Jan -4712 00:00:00 GMT'[, start=Date::ITALY]) -> date + * Date.httpdate(string='Mon, 01 Jan -4712 00:00:00 GMT'[, start=Date::ITALY], limit: 128) -> date * * Creates a new Date object by parsing from a string according to * some RFC 2616 format. * * Date.httpdate('Sat, 03 Feb 2001 00:00:00 GMT') * #=> #<Date: 2001-02-03 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s_httpdate(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: @@ -4629,26 +4751,39 @@ date_s_httpdate(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__httpdate(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + VALUE hash = date_s__httpdate(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } /* * call-seq: - * Date._jisx0301(string) -> hash + * Date._jisx0301(string, limit: 128) -> hash * * Returns a hash of parsed elements. + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE -date_s__jisx0301(VALUE klass, VALUE str) +date_s__jisx0301(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + return date__jisx0301(str); } /* * call-seq: - * Date.jisx0301(string='-4712-01-01'[, start=Date::ITALY]) -> date + * Date.jisx0301(string='-4712-01-01'[, start=Date::ITALY], limit: 128) -> date * * Creates a new Date object by parsing from a string according to * some typical JIS X 0301 formats. @@ -4658,13 +4793,18 @@ date_s__jisx0301(VALUE klass, VALUE str) * For no-era year, legacy format, Heisei is assumed. * * Date.jisx0301('13.02.03') #=> #<Date: 2001-02-03 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE date_s_jisx0301(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -4674,7 +4814,11 @@ date_s_jisx0301(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__jisx0301(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + VALUE hash = date_s__jisx0301(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } @@ -8013,7 +8157,7 @@ datetime_s_strptime(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * DateTime.parse(string='-4712-01-01T00:00:00+00:00'[, comp=true[, start=Date::ITALY]]) -> datetime + * DateTime.parse(string='-4712-01-01T00:00:00+00:00'[, comp=true[, start=Date::ITALY]], limit: 128) -> datetime * * Parses the given representation of date and time, and creates a * DateTime object. @@ -8032,13 +8176,18 @@ datetime_s_strptime(int argc, VALUE *argv, VALUE klass) * #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...> * DateTime.parse('3rd Feb 2001 04:05:06 PM') * #=> #<DateTime: 2001-02-03T16:05:06+00:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE datetime_s_parse(int argc, VALUE *argv, VALUE klass) { - VALUE str, comp, sg; + VALUE str, comp, sg, opt; - rb_scan_args(argc, argv, "03", &str, &comp, &sg); + rb_scan_args(argc, argv, "03:", &str, &comp, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -8050,18 +8199,20 @@ datetime_s_parse(int argc, VALUE *argv, VALUE klass) } { - VALUE argv2[2], hash; - - argv2[0] = str; - argv2[1] = comp; - hash = date_s__parse(2, argv2, klass); + int argc2 = 2; + VALUE argv2[3]; + argv2[0] = str; + argv2[1] = comp; + argv2[2] = opt; + if (!NIL_P(opt)) argc2++; + VALUE hash = date_s__parse(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } /* * call-seq: - * DateTime.iso8601(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY]) -> datetime + * DateTime.iso8601(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime * * Creates a new DateTime object by parsing from a string according to * some typical ISO 8601 formats. @@ -8072,13 +8223,18 @@ datetime_s_parse(int argc, VALUE *argv, VALUE klass) * #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...> * DateTime.iso8601('2001-W05-6T04:05:06+07:00') * #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE datetime_s_iso8601(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -8088,27 +8244,37 @@ datetime_s_iso8601(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__iso8601(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2--; + VALUE hash = date_s__iso8601(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } /* * call-seq: - * DateTime.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY]) -> datetime + * DateTime.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime * * Creates a new DateTime object by parsing from a string according to * some typical RFC 3339 formats. * * DateTime.rfc3339('2001-02-03T04:05:06+07:00') * #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE datetime_s_rfc3339(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -8118,27 +8284,37 @@ datetime_s_rfc3339(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__rfc3339(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + VALUE hash = date_s__rfc3339(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } /* * call-seq: - * DateTime.xmlschema(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY]) -> datetime + * DateTime.xmlschema(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime * * Creates a new DateTime object by parsing from a string according to * some typical XML Schema formats. * * DateTime.xmlschema('2001-02-03T04:05:06+07:00') * #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE datetime_s_xmlschema(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -8148,28 +8324,38 @@ datetime_s_xmlschema(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__xmlschema(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + VALUE hash = date_s__xmlschema(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } /* * call-seq: - * DateTime.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY]) -> datetime - * DateTime.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY]) -> datetime + * DateTime.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> datetime + * DateTime.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> datetime * * Creates a new DateTime object by parsing from a string according to * some typical RFC 2822 formats. * * DateTime.rfc2822('Sat, 3 Feb 2001 04:05:06 +0700') * #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE datetime_s_rfc2822(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -8179,7 +8365,12 @@ datetime_s_rfc2822(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__rfc2822(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + VALUE hash = date_s__rfc2822(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } @@ -8193,13 +8384,18 @@ datetime_s_rfc2822(int argc, VALUE *argv, VALUE klass) * * DateTime.httpdate('Sat, 03 Feb 2001 04:05:06 GMT') * #=> #<DateTime: 2001-02-03T04:05:06+00:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE datetime_s_httpdate(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -8209,14 +8405,19 @@ datetime_s_httpdate(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__httpdate(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + VALUE hash = date_s__httpdate(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } /* * call-seq: - * DateTime.jisx0301(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY]) -> datetime + * DateTime.jisx0301(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime * * Creates a new DateTime object by parsing from a string according to * some typical JIS X 0301 formats. @@ -8228,13 +8429,18 @@ datetime_s_httpdate(int argc, VALUE *argv, VALUE klass) * * DateTime.jisx0301('13.02.03T04:05:06+07:00') * #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing `limit: nil`, but note that + * it may take a long time to parse. */ static VALUE datetime_s_jisx0301(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + if (!NIL_P(opt)) argc--; switch (argc) { case 0: @@ -8244,7 +8450,12 @@ datetime_s_jisx0301(int argc, VALUE *argv, VALUE klass) } { - VALUE hash = date_s__jisx0301(klass, str); + int argc2 = 1; + VALUE argv2[2]; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + VALUE hash = date_s__jisx0301(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } @@ -9403,19 +9614,19 @@ Init_date_core(void) rb_define_singleton_method(cDate, "strptime", date_s_strptime, -1); rb_define_singleton_method(cDate, "_parse", date_s__parse, -1); rb_define_singleton_method(cDate, "parse", date_s_parse, -1); - rb_define_singleton_method(cDate, "_iso8601", date_s__iso8601, 1); + rb_define_singleton_method(cDate, "_iso8601", date_s__iso8601, -1); rb_define_singleton_method(cDate, "iso8601", date_s_iso8601, -1); - rb_define_singleton_method(cDate, "_rfc3339", date_s__rfc3339, 1); + rb_define_singleton_method(cDate, "_rfc3339", date_s__rfc3339, -1); rb_define_singleton_method(cDate, "rfc3339", date_s_rfc3339, -1); - rb_define_singleton_method(cDate, "_xmlschema", date_s__xmlschema, 1); + rb_define_singleton_method(cDate, "_xmlschema", date_s__xmlschema, -1); rb_define_singleton_method(cDate, "xmlschema", date_s_xmlschema, -1); - rb_define_singleton_method(cDate, "_rfc2822", date_s__rfc2822, 1); - rb_define_singleton_method(cDate, "_rfc822", date_s__rfc2822, 1); + rb_define_singleton_method(cDate, "_rfc2822", date_s__rfc2822, -1); + rb_define_singleton_method(cDate, "_rfc822", date_s__rfc2822, -1); rb_define_singleton_method(cDate, "rfc2822", date_s_rfc2822, -1); rb_define_singleton_method(cDate, "rfc822", date_s_rfc2822, -1); - rb_define_singleton_method(cDate, "_httpdate", date_s__httpdate, 1); + rb_define_singleton_method(cDate, "_httpdate", date_s__httpdate, -1); rb_define_singleton_method(cDate, "httpdate", date_s_httpdate, -1); - rb_define_singleton_method(cDate, "_jisx0301", date_s__jisx0301, 1); + rb_define_singleton_method(cDate, "_jisx0301", date_s__jisx0301, -1); rb_define_singleton_method(cDate, "jisx0301", date_s_jisx0301, -1); rb_define_method(cDate, "initialize", date_initialize, -1); diff --git a/ext/date/lib/date.rb b/ext/date/lib/date.rb index 65c34ace49..0a49076ab6 100644 --- a/ext/date/lib/date.rb +++ b/ext/date/lib/date.rb @@ -4,6 +4,7 @@ require 'date_core' class Date + VERSION = '3.1.3' # :nodoc: def infinite? false diff --git a/ext/etc/etc.c b/ext/etc/etc.c index 477423c9ed..737d295abc 100644 --- a/ext/etc/etc.c +++ b/ext/etc/etc.c @@ -52,7 +52,7 @@ char *getenv(); #endif char *getlogin(); -#define RUBY_ETC_VERSION "1.2.0" +#define RUBY_ETC_VERSION "1.3.0" #ifdef HAVE_RB_DEPRECATE_CONSTANT void rb_deprecate_constant(VALUE mod, const char *name); @@ -68,6 +68,15 @@ void rb_deprecate_constant(VALUE mod, const char *name); typedef int rb_atomic_t; # define RUBY_ATOMIC_CAS(var, oldval, newval) \ ((var) == (oldval) ? ((var) = (newval), (oldval)) : (var)) +# define RUBY_ATOMIC_EXCHANGE(var, newval) \ + atomic_exchange(&var, newval) +static inline rb_atomic_t +atomic_exchange(volatile rb_atomic_t *var, rb_atomic_t newval) +{ + rb_atomic_t oldval = *var; + *var = newval; + return oldval; +} #endif /* call-seq: @@ -253,7 +262,9 @@ static VALUE passwd_ensure(VALUE _) { endpwent(); - passwd_blocking = 0; + if (RUBY_ATOMIC_EXCHANGE(passwd_blocking, 0) != 1) { + rb_raise(rb_eRuntimeError, "unexpected passwd_blocking"); + } return Qnil; } @@ -495,7 +506,9 @@ static VALUE group_ensure(VALUE _) { endgrent(); - group_blocking = 0; + if (RUBY_ATOMIC_EXCHANGE(group_blocking, 0) != 1) { + rb_raise(rb_eRuntimeError, "unexpected group_blocking"); + } return Qnil; } @@ -944,11 +957,13 @@ io_pathconf(VALUE io, VALUE arg) static int etc_nprocessors_affin(void) { - cpu_set_t *cpuset; + cpu_set_t *cpuset, cpuset_buff[1024 / sizeof(cpu_set_t)]; size_t size; int ret; int n; + CPU_ZERO_S(sizeof(cpuset_buff), cpuset_buff); + /* * XXX: * man page says CPU_ALLOC takes number of cpus. But it is not accurate @@ -967,13 +982,12 @@ etc_nprocessors_affin(void) */ for (n=64; n <= 16384; n *= 2) { size = CPU_ALLOC_SIZE(n); - if (size >= 1024) { + if (size >= sizeof(cpuset_buff)) { cpuset = xcalloc(1, size); if (!cpuset) return -1; } else { - cpuset = alloca(size); - CPU_ZERO_S(size, cpuset); + cpuset = cpuset_buff; } ret = sched_getaffinity(0, size, cpuset); @@ -982,10 +996,10 @@ etc_nprocessors_affin(void) ret = CPU_COUNT_S(size, cpuset); } - if (size >= 1024) { + if (size >= sizeof(cpuset_buff)) { xfree(cpuset); } - if (ret > 0) { + if (ret > 0 || errno != EINVAL) { return ret; } } diff --git a/ext/etc/extconf.rb b/ext/etc/extconf.rb index b6ae7700da..6e7810a5e8 100644 --- a/ext/etc/extconf.rb +++ b/ext/etc/extconf.rb @@ -47,10 +47,7 @@ if !File.exist?("#{srcdir}/depend") %x[#{RbConfig.ruby} #{srcdir}/mkconstants.rb -o #{srcdir}/constdefs.h] end -decl = [ - "void rb_deprecate_constant(VALUE, const char *);", -] -have_func('rb_deprecate_constant(Qnil, "None")', [decl]) +have_func('rb_deprecate_constant(Qnil, "None")') $distcleanfiles << "constdefs.h" diff --git a/ext/extmk.rb b/ext/extmk.rb index 80a0a1208d..97f1ad9c39 100755 --- a/ext/extmk.rb +++ b/ext/extmk.rb @@ -408,8 +408,10 @@ if CROSS_COMPILING $ruby = $mflags.defined?("MINIRUBY") || CONFIG['MINIRUBY'] elsif sep = config_string('BUILD_FILE_SEPARATOR') $ruby = "$(topdir:/=#{sep})#{sep}miniruby" + EXEEXT -else +elsif CONFIG['EXTSTATIC'] $ruby = '$(topdir)/miniruby' + EXEEXT +else + $ruby = '$(topdir)/ruby' + EXEEXT end $ruby = [$ruby] $ruby << "-I'$(topdir)'" @@ -421,6 +423,7 @@ end topruby = $ruby $ruby = topruby.join(' ') $mflags << "ruby=#$ruby" +$builtruby = '$(topdir)/miniruby' + EXEEXT # Must be an executable path MTIMES = [__FILE__, 'rbconfig.rb', srcdir+'/lib/mkmf.rb'].collect {|f| File.mtime(f)} diff --git a/ext/fcntl/fcntl.gemspec b/ext/fcntl/fcntl.gemspec index 645c507a4d..048e101aa5 100644 --- a/ext/fcntl/fcntl.gemspec +++ b/ext/fcntl/fcntl.gemspec @@ -3,7 +3,7 @@ Gem::Specification.new do |spec| spec.name = "fcntl" - spec.version = "1.0.0" + spec.version = "1.0.1" spec.authors = ["Yukihiro Matsumoto"] spec.email = ["matz@ruby-lang.org"] diff --git a/ext/fiddle/closure.c b/ext/fiddle/closure.c index 40cee55e9a..3679e5c9ad 100644 --- a/ext/fiddle/closure.c +++ b/ext/fiddle/closure.c @@ -130,6 +130,10 @@ with_gvl_callback(void *ptr) rb_ary_push(params, ULL2NUM(*(unsigned LONG_LONG *)x->args[i])); break; #endif + case TYPE_CONST_STRING: + rb_ary_push(params, + rb_str_new_cstr(*((const char **)(x->args[i])))); + break; default: rb_raise(rb_eRuntimeError, "closure args: %d", type); } @@ -175,6 +179,10 @@ with_gvl_callback(void *ptr) *(unsigned LONG_LONG *)x->resp = NUM2ULL(ret); break; #endif + case TYPE_CONST_STRING: + /* Dangerous. Callback must keep reference of the String. */ + *((const char **)(x->resp)) = StringValueCStr(ret); + break; default: rb_raise(rb_eRuntimeError, "closure retval: %d", type); } @@ -221,6 +229,7 @@ initialize(int rbargc, VALUE argv[], VALUE self) { VALUE ret; VALUE args; + VALUE normalized_args; VALUE abi; fiddle_closure * cl; ffi_cif * cif; @@ -239,21 +248,26 @@ initialize(int rbargc, VALUE argv[], VALUE self) cl->argv = (ffi_type **)xcalloc(argc + 1, sizeof(ffi_type *)); + normalized_args = rb_ary_new_capa(argc); for (i = 0; i < argc; i++) { - int type = NUM2INT(RARRAY_AREF(args, i)); - cl->argv[i] = INT2FFI_TYPE(type); + VALUE arg = rb_fiddle_type_ensure(RARRAY_AREF(args, i)); + rb_ary_push(normalized_args, arg); + cl->argv[i] = rb_fiddle_int_to_ffi_type(NUM2INT(arg)); } cl->argv[argc] = NULL; + ret = rb_fiddle_type_ensure(ret); rb_iv_set(self, "@ctype", ret); - rb_iv_set(self, "@args", args); + rb_iv_set(self, "@args", normalized_args); cif = &cl->cif; pcl = cl->pcl; - result = ffi_prep_cif(cif, NUM2INT(abi), argc, - INT2FFI_TYPE(NUM2INT(ret)), - cl->argv); + result = ffi_prep_cif(cif, + NUM2INT(abi), + argc, + rb_fiddle_int_to_ffi_type(NUM2INT(ret)), + cl->argv); if (FFI_OK != result) rb_raise(rb_eRuntimeError, "error prepping CIF %d", result); diff --git a/ext/fiddle/conversions.h b/ext/fiddle/conversions.h index 1de956e90c..c7c12a9234 100644 --- a/ext/fiddle/conversions.h +++ b/ext/fiddle/conversions.h @@ -24,7 +24,6 @@ typedef union void * pointer; /* ffi_type_pointer */ } fiddle_generic; -/* Deprecated. Use rb_fiddle_*() version. */ VALUE rb_fiddle_type_ensure(VALUE type); ffi_type * rb_fiddle_int_to_ffi_type(int type); void rb_fiddle_value_to_generic(int type, VALUE *src, fiddle_generic *dst); diff --git a/ext/fiddle/extconf.rb b/ext/fiddle/extconf.rb index 6d1d5104a1..6ca685317e 100644 --- a/ext/fiddle/extconf.rb +++ b/ext/fiddle/extconf.rb @@ -3,6 +3,47 @@ require 'mkmf' # :stopdoc: +def gcc? + RbConfig::CONFIG["GCC"] == "yes" +end + +def disable_optimization_build_flag(flags) + if gcc? + expanded_flags = RbConfig.expand(flags.dup) + optimization_option_pattern = /(^|\s)?-O\d(\s|$)?/ + if optimization_option_pattern.match?(expanded_flags) + expanded_flags.gsub(optimization_option_pattern, '\\1-Og\\2') + else + flags + " -Og" + end + else + flags + end +end + +def enable_debug_build_flag(flags) + if gcc? + expanded_flags = RbConfig.expand(flags.dup) + debug_option_pattern = /(^|\s)-g(?:gdb)?\d?(\s|$)/ + if debug_option_pattern.match?(expanded_flags) + expanded_flags.gsub(debug_option_pattern, '\\1-ggdb3\\2') + else + flags + " -ggdb3" + end + else + flags + end +end + +checking_for(checking_message("--enable-debug-build option")) do + enable_debug_build = enable_config("debug-build", false) + if enable_debug_build + $CFLAGS = disable_optimization_build_flag($CFLAGS) + $CFLAGS = enable_debug_build_flag($CFLAGS) + end + enable_debug_build +end + libffi_version = nil have_libffi = false bundle = enable_config('bundled-libffi') @@ -159,6 +200,8 @@ elsif have_header "windows.h" %w{ LoadLibrary FreeLibrary GetProcAddress }.each do |func| abort "missing function #{func}" unless have_func(func) end + + have_library "ws2_32" end have_const('FFI_STDCALL', ffi_header) diff --git a/ext/fiddle/function.c b/ext/fiddle/function.c index 1d82bc8a3e..d15a54bfa6 100644 --- a/ext/fiddle/function.c +++ b/ext/fiddle/function.c @@ -375,10 +375,17 @@ function_call(int argc, VALUE argv[], VALUE self) (void)rb_thread_call_without_gvl(nogvl_ffi_call, &args, 0, 0); } - rb_funcall(mFiddle, rb_intern("last_error="), 1, INT2NUM(errno)); + { + int errno_keep = errno; #if defined(_WIN32) - rb_funcall(mFiddle, rb_intern("win32_last_error="), 1, INT2NUM(errno)); + int socket_error = WSAGetLastError(); + rb_funcall(mFiddle, rb_intern("win32_last_error="), 1, + INT2NUM(errno_keep)); + rb_funcall(mFiddle, rb_intern("win32_last_socket_error="), 1, + INT2NUM(socket_error)); #endif + rb_funcall(mFiddle, rb_intern("last_error="), 1, INT2NUM(errno_keep)); + } ALLOCV_END(alloc_buffer); diff --git a/ext/fiddle/lib/fiddle.rb b/ext/fiddle/lib/fiddle.rb index 3fdf525b4c..4512989310 100644 --- a/ext/fiddle/lib/fiddle.rb +++ b/ext/fiddle/lib/fiddle.rb @@ -17,6 +17,18 @@ module Fiddle def self.win32_last_error= error Thread.current[:__FIDDLE_WIN32_LAST_ERROR__] = error end + + # Returns the last win32 socket +Error+ of the current executing + # +Thread+ or nil if none + def self.win32_last_socket_error + Thread.current[:__FIDDLE_WIN32_LAST_SOCKET_ERROR__] + end + + # Sets the last win32 socket +Error+ of the current executing + # +Thread+ to +error+ + def self.win32_last_socket_error= error + Thread.current[:__FIDDLE_WIN32_LAST_SOCKET_ERROR__] = error + end end # Returns the last +Error+ of the current executing +Thread+ or nil if none diff --git a/ext/fiddle/lib/fiddle/cparser.rb b/ext/fiddle/lib/fiddle/cparser.rb index 8a269393c6..93a05513c9 100644 --- a/ext/fiddle/lib/fiddle/cparser.rb +++ b/ext/fiddle/lib/fiddle/cparser.rb @@ -148,9 +148,11 @@ module Fiddle # def parse_ctype(ty, tymap=nil) tymap ||= {} - case ty - when Array + if ty.is_a?(Array) return [parse_ctype(ty[0], tymap), ty[1]] + end + ty = ty.gsub(/\Aconst\s+/, "") + case ty when 'void' return TYPE_VOID when /\A(?:(?:signed\s+)?long\s+long(?:\s+int\s+)?|int64_t)(?:\s+\w+)?\z/ diff --git a/ext/fiddle/lib/fiddle/types.rb b/ext/fiddle/lib/fiddle/types.rb index 8dc811d3e4..7baf31ec9e 100644 --- a/ext/fiddle/lib/fiddle/types.rb +++ b/ext/fiddle/lib/fiddle/types.rb @@ -27,28 +27,29 @@ module Fiddle # * WORD module Win32Types def included(m) # :nodoc: + # https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types m.module_eval{ - typealias "DWORD", "unsigned long" - typealias "PDWORD", "unsigned long *" - typealias "DWORD32", "unsigned long" - typealias "DWORD64", "unsigned long long" - typealias "WORD", "unsigned short" - typealias "PWORD", "unsigned short *" + typealias "ATOM", "WORD" typealias "BOOL", "int" - typealias "ATOM", "int" typealias "BYTE", "unsigned char" - typealias "PBYTE", "unsigned char *" + typealias "DWORD", "unsigned long" + typealias "DWORD32", "uint32_t" + typealias "DWORD64", "uint64_t" + typealias "HANDLE", "PVOID" + typealias "HDC", "HANDLE" + typealias "HINSTANCE", "HANDLE" + typealias "HWND", "HANDLE" + typealias "LPCSTR", "const char *" + typealias "LPSTR", "char *" + typealias "PBYTE", "BYTE *" + typealias "PDWORD", "DWORD *" + typealias "PHANDLE", "HANDLE *" + typealias "PVOID", "void *" + typealias "PWORD", "WORD *" + typealias "UCHAR", "unsigned char" typealias "UINT", "unsigned int" typealias "ULONG", "unsigned long" - typealias "UCHAR", "unsigned char" - typealias "HANDLE", "uintptr_t" - typealias "PHANDLE", "void*" - typealias "PVOID", "void*" - typealias "LPCSTR", "char*" - typealias "LPSTR", "char*" - typealias "HINSTANCE", "unsigned int" - typealias "HDC", "unsigned int" - typealias "HWND", "unsigned int" + typealias "WORD", "unsigned short" } end module_function :included diff --git a/ext/fiddle/lib/fiddle/version.rb b/ext/fiddle/lib/fiddle/version.rb index d80cb7078f..a699371ee4 100644 --- a/ext/fiddle/lib/fiddle/version.rb +++ b/ext/fiddle/lib/fiddle/version.rb @@ -1,3 +1,3 @@ module Fiddle - VERSION = "1.0.6" + VERSION = "1.0.8" end diff --git a/ext/io/console/console.c b/ext/io/console/console.c index ff4df73693..2e2467036d 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -77,7 +77,7 @@ getattr(int fd, conmode *t) static ID id_getc, id_console, id_close, id_min, id_time, id_intr; #if ENABLE_IO_GETPASS -static ID id_gets; +static ID id_gets, id_chomp_bang; #endif #ifdef HAVE_RB_SCHEDULER_TIMEOUT @@ -1223,8 +1223,8 @@ console_key_pressed_p(VALUE io, VALUE k) } #else struct query_args { - const char *qstr; - int opt; + char qstr[6]; + unsigned char opt; }; static int @@ -1562,7 +1562,7 @@ static VALUE str_chomp(VALUE str) { if (!NIL_P(str)) { - str = rb_funcallv(str, rb_intern("chomp!"), 0, 0); + rb_funcallv(str, id_chomp_bang, 0, 0); } return str; } @@ -1574,6 +1574,10 @@ str_chomp(VALUE str) * Reads and returns a line without echo back. * Prints +prompt+ unless it is +nil+. * + * The newline character that terminates the + * read line is removed from the returned string, + * see String#chomp!. + * * You must require 'io/console' to use this method. */ static VALUE @@ -1618,6 +1622,7 @@ Init_console(void) id_getc = rb_intern("getc"); #if ENABLE_IO_GETPASS id_gets = rb_intern("gets"); + id_chomp_bang = rb_intern("chomp!"); #endif id_console = rb_intern("console"); id_close = rb_intern("close"); diff --git a/ext/io/console/io-console.gemspec b/ext/io/console/io-console.gemspec index 743e5e965d..8a0df83b2c 100644 --- a/ext/io/console/io-console.gemspec +++ b/ext/io/console/io-console.gemspec @@ -1,5 +1,5 @@ # -*- ruby -*- -_VERSION = "0.5.6" +_VERSION = "0.5.7" Gem::Specification.new do |s| s.name = "io-console" diff --git a/ext/io/wait/io-wait.gemspec b/ext/io/wait/io-wait.gemspec index 1c6c2d5705..ec23699def 100644 --- a/ext/io/wait/io-wait.gemspec +++ b/ext/io/wait/io-wait.gemspec @@ -1,6 +1,8 @@ +_VERSION = "0.2.0" + Gem::Specification.new do |spec| spec.name = "io-wait" - spec.version = "0.1.0" + spec.version = _VERSION spec.authors = ["Nobu Nakada"] spec.email = ["nobu@ruby-lang.org"] @@ -8,7 +10,7 @@ Gem::Specification.new do |spec| spec.description = %q{Waits until IO is readable or writable without blocking.} spec.homepage = "https://github.com/ruby/io-wait" spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0") + spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0") spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage @@ -20,6 +22,6 @@ Gem::Specification.new do |spec| end spec.extensions = %w[ext/io/wait/extconf.rb] spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.executables = [] spec.require_paths = ["lib"] end diff --git a/ext/io/wait/wait.c b/ext/io/wait/wait.c index 73bc77a294..512e4f6a80 100644 --- a/ext/io/wait/wait.c +++ b/ext/io/wait/wait.c @@ -211,7 +211,7 @@ wait_mode_sym(VALUE mode) /* * call-seq: * io.wait(events, timeout) -> event mask or false. - * io.wait(timeout = nil, mode = :read) -> event mask or false (deprecated) + * io.wait(timeout = nil, mode = :read) -> event mask or false. * * Waits until the IO becomes ready for the specified events and returns the * subset of events that become ready, or +false+ when times out. @@ -222,34 +222,32 @@ wait_mode_sym(VALUE mode) * Returns +true+ immediately when buffered data is available. * * Optional parameter +mode+ is one of +:read+, +:write+, or - * +:read_write+ (deprecated). + * +:read_write+. */ static VALUE io_wait(int argc, VALUE *argv, VALUE io) { - VALUE timeout = Qnil; + VALUE timeout = Qundef; rb_io_event_t events = 0; - if (argc < 2 || (argc >= 2 && RB_SYMBOL_P(argv[1]))) { - if (argc > 0) { - timeout = argv[0]; - } - - for (int i = 1; i < argc; i += 1) { - events |= wait_mode_sym(argv[i]); + if (argc != 2 || (RB_SYMBOL_P(argv[0]) || RB_SYMBOL_P(argv[1]))) { + for (int i = 0; i < argc; i += 1) { + if (RB_SYMBOL_P(argv[i])) { + events |= wait_mode_sym(argv[i]); + } + else if (timeout == Qundef) { + rb_time_interval(timeout = argv[i]); + } + else { + rb_raise(rb_eArgError, "timeout given more than once"); + } } + if (timeout == Qundef) timeout = Qnil; } - else if (argc == 2) { + else /* argc == 2 */ { events = RB_NUM2UINT(argv[0]); - - if (argv[1] != Qnil) { - timeout = argv[1]; - } - } - else { - // TODO error - return Qnil; + timeout = argv[1]; } if (events == 0) { @@ -275,6 +273,10 @@ io_wait(int argc, VALUE *argv, VALUE io) void Init_wait(void) { +#ifdef HAVE_RB_EXT_RACTOR_SAFE + RB_EXT_RACTOR_SAFE(true); +#endif + rb_define_method(rb_cIO, "nread", io_nread, 0); rb_define_method(rb_cIO, "ready?", io_ready_p, 0); diff --git a/ext/monitor/monitor.c b/ext/monitor/monitor.c index 627d8211d6..a3efe96bb3 100644 --- a/ext/monitor/monitor.c +++ b/ext/monitor/monitor.c @@ -53,7 +53,7 @@ monitor_ptr(VALUE monitor) static int mc_owner_p(struct rb_monitor *mc) { - return mc->owner == rb_thread_current(); + return mc->owner == rb_fiber_current(); } static VALUE @@ -65,7 +65,7 @@ monitor_try_enter(VALUE monitor) if (!rb_mutex_trylock(mc->mutex)) { return Qfalse; } - RB_OBJ_WRITE(monitor, &mc->owner, rb_thread_current()); + RB_OBJ_WRITE(monitor, &mc->owner, rb_fiber_current()); mc->count = 0; } mc->count += 1; @@ -78,7 +78,7 @@ monitor_enter(VALUE monitor) struct rb_monitor *mc = monitor_ptr(monitor); if (!mc_owner_p(mc)) { rb_mutex_lock(mc->mutex); - RB_OBJ_WRITE(monitor, &mc->owner, rb_thread_current()); + RB_OBJ_WRITE(monitor, &mc->owner, rb_fiber_current()); mc->count = 0; } mc->count++; @@ -90,7 +90,7 @@ monitor_check_owner(VALUE monitor) { struct rb_monitor *mc = monitor_ptr(monitor); if (!mc_owner_p(mc)) { - rb_raise(rb_eThreadError, "current thread not owner"); + rb_raise(rb_eThreadError, "current fiber not owner"); } return Qnil; } @@ -161,7 +161,7 @@ monitor_enter_for_cond(VALUE v) struct wait_for_cond_data *data = (struct wait_for_cond_data *)v; struct rb_monitor *mc = monitor_ptr(data->monitor); - RB_OBJ_WRITE(data->monitor, &mc->owner, rb_thread_current()); + RB_OBJ_WRITE(data->monitor, &mc->owner, rb_fiber_current()); mc->count = NUM2LONG(data->count); return Qnil; } @@ -203,7 +203,7 @@ monitor_synchronize(VALUE monitor) void Init_monitor(void) { -#if HAVE_RB_EXT_RACTOR_SAFE +#ifdef HAVE_RB_EXT_RACTOR_SAFE rb_ext_ractor_safe(true); #endif diff --git a/ext/objspace/object_tracing.c b/ext/objspace/object_tracing.c index 4973a7535b..66d6baa491 100644 --- a/ext/objspace/object_tracing.c +++ b/ext/objspace/object_tracing.c @@ -208,7 +208,8 @@ allocation_info_tracer_compact(void *ptr) { struct traceobj_arg *trace_arg = (struct traceobj_arg *)ptr; - if (st_foreach_with_replace(trace_arg->object_table, hash_foreach_should_replace_key, hash_replace_key, 0)) { + if (trace_arg->object_table && + st_foreach_with_replace(trace_arg->object_table, hash_foreach_should_replace_key, hash_replace_key, 0)) { rb_raise(rb_eRuntimeError, "hash modified during iteration"); } } diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index 7afdfc1f6b..3fa4fd279b 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -996,6 +996,7 @@ Init_objspace(void) * You can use the #type method to check the type of the internal object. */ rb_cInternalObjectWrapper = rb_define_class_under(rb_mObjSpace, "InternalObjectWrapper", rb_cObject); + rb_undef_alloc_func(rb_cInternalObjectWrapper); rb_define_method(rb_cInternalObjectWrapper, "type", iow_type, 0); rb_define_method(rb_cInternalObjectWrapper, "inspect", iow_inspect, 0); rb_define_method(rb_cInternalObjectWrapper, "internal_object_id", iow_internal_object_id, 0); diff --git a/ext/openssl/History.md b/ext/openssl/History.md index a4a82a146c..60b9dd8825 100644 --- a/ext/openssl/History.md +++ b/ext/openssl/History.md @@ -1,3 +1,26 @@ +Version 2.2.2 +============= + +Merged changes in 2.1.4. + + +Version 2.2.1 +============= + +Merged changes in 2.1.3. Additionally, the following issues are fixed by this +release. + +Bug fixes +--------- + +* Fix crash in `OpenSSL::Timestamp::{Request,Response,TokenInfo}.new` when + invalid arguments are given. + [[GitHub #407]](https://github.com/ruby/openssl/pull/407) +* Fix `OpenSSL::Timestamp::Factory#create_timestamp` with LibreSSL on platforms + where `time_t` has a different size from `long`. + [[GitHub #454]](https://github.com/ruby/openssl/pull/454) + + Version 2.2.0 ============= @@ -75,6 +98,52 @@ Notable changes [[GitHub #297]](https://github.com/ruby/openssl/pull/297) +Version 2.1.4 +============= + +Bug fixes +--------- + +* Do not use pkg-config if --with-openssl-dir option is specified. + [[GitHub #486]](https://github.com/ruby/openssl/pull/486) + + +Version 2.1.3 +============= + +Bug fixes +--------- + +* Fix deprecation warnings on Ruby 3.0. +* Add ".include" directive support in `OpenSSL::Config`. + [[GitHub #216]](https://github.com/ruby/openssl/pull/216) +* Fix handling of IPv6 address SANs. + [[GitHub #185]](https://github.com/ruby/openssl/pull/185) +* Hostname verification failure with `OpenSSL::SSL::SSLContext#verify_hostname=` + sets a proper error code. + [[GitHub #350]](https://github.com/ruby/openssl/pull/350) +* Fix crash with `OpenSSL::BN.new(nil, 2)`. + [[Bug #15760]](https://bugs.ruby-lang.org/issues/15760) +* `OpenSSL::SSL::SSLSocket#sys{read,write}` prevent internal string buffers from + being modified by another thread. + [[GitHub #453]](https://github.com/ruby/openssl/pull/453) +* Fix misuse of input record separator in `OpenSSL::Buffering` where it was + for output. +* Fix wrong interger casting in `OpenSSL::PKey::EC#dsa_verify_asn1`. + [[GitHub #460]](https://github.com/ruby/openssl/pull/460) +* `extconf.rb` explicitly checks that OpenSSL's version number is 1.0.1 or + newer but also less than 3.0. Ruby/OpenSSL v2.1.x and v2.2.x will not support + OpenSSL 3.0 API. + [[GitHub #458]](https://github.com/ruby/openssl/pull/458) +* Activate `digest` gem correctly. `digest` library could go into an + inconsistent state if there are multiple versions of `digest` is installed + and `openssl` is `require`d before `digest`. + [[GitHub #463]](https://github.com/ruby/openssl/pull/463) +* Fix GC.compact compatibility. + [[GitHub #464]](https://github.com/ruby/openssl/issues/464) + [[GitHub #465]](https://github.com/ruby/openssl/pull/465) + + Version 2.1.2 ============= diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 693e55cd97..0dc1a5eb43 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -13,7 +13,7 @@ require "mkmf" -dir_config("openssl") +dir_config_given = dir_config("openssl").any? dir_config("kerberos") Logging::message "=== OpenSSL for Ruby configurator ===\n" @@ -33,9 +33,6 @@ if $mswin || $mingw have_library("ws2_32") end -Logging::message "=== Checking for required stuff... ===\n" -result = pkg_config("openssl") && have_header("openssl/ssl.h") - if $mingw append_cflags '-D_FORTIFY_SOURCE=2' append_ldflags '-fstack-protector' @@ -92,19 +89,33 @@ def find_openssl_library return false end -unless result - unless find_openssl_library - Logging::message "=== Checking for required stuff failed. ===\n" - Logging::message "Makefile wasn't created. Fix the errors above.\n" - raise "OpenSSL library could not be found. You might want to use " \ - "--with-openssl-dir=<dir> option to specify the prefix where OpenSSL " \ - "is installed." - end +Logging::message "=== Checking for required stuff... ===\n" +pkg_config_found = !dir_config_given && pkg_config("openssl") && have_header("openssl/ssl.h") + +if !pkg_config_found && !find_openssl_library + Logging::message "=== Checking for required stuff failed. ===\n" + Logging::message "Makefile wasn't created. Fix the errors above.\n" + raise "OpenSSL library could not be found. You might want to use " \ + "--with-openssl-dir=<dir> option to specify the prefix where OpenSSL " \ + "is installed." end -unless checking_for("OpenSSL version is 1.0.1 or later") { - try_static_assert("OPENSSL_VERSION_NUMBER >= 0x10001000L", "openssl/opensslv.h") } - raise "OpenSSL >= 1.0.1 or LibreSSL is required" +version_ok = if have_macro("LIBRESSL_VERSION_NUMBER", "openssl/opensslv.h") + is_libressl = true + checking_for("LibreSSL version >= 2.5.0") { + try_static_assert("LIBRESSL_VERSION_NUMBER >= 0x20500000L", "openssl/opensslv.h") } +else + checking_for("OpenSSL version >= 1.0.1 and < 3.0.0") { + try_static_assert("OPENSSL_VERSION_NUMBER >= 0x10001000L", "openssl/opensslv.h") && + !try_static_assert("OPENSSL_VERSION_MAJOR >= 3", "openssl/opensslv.h") } +end +unless version_ok + raise "OpenSSL >= 1.0.1, < 3.0.0 or LibreSSL >= 2.5.0 is required" +end + +# Prevent wincrypt.h from being included, which defines conflicting macro with openssl/x509.h +if is_libressl && ($mswin || $mingw) + $defs.push("-DNOCRYPT") end Logging::message "=== Checking for OpenSSL features... ===\n" @@ -116,10 +127,6 @@ engines.each { |name| have_func("ENGINE_load_#{name}()", "openssl/engine.h") } -if ($mswin || $mingw) && have_macro("LIBRESSL_VERSION_NUMBER", "openssl/opensslv.h") - $defs.push("-DNOCRYPT") -end - # added in 1.0.2 have_func("EC_curve_nist2nid") have_func("X509_REVOKED_dup") diff --git a/ext/openssl/lib/openssl/version.rb b/ext/openssl/lib/openssl/version.rb index 9c7515ba0f..89e052069e 100644 --- a/ext/openssl/lib/openssl/version.rb +++ b/ext/openssl/lib/openssl/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module OpenSSL - VERSION = "2.2.0" + VERSION = "2.2.2" end diff --git a/ext/openssl/openssl.gemspec b/ext/openssl/openssl.gemspec index 471a3c4265..c8e3cc3fc3 100644 --- a/ext/openssl/openssl.gemspec +++ b/ext/openssl/openssl.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "openssl" - spec.version = "2.2.0" + spec.version = "2.2.2" spec.authors = ["Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"] spec.email = ["ruby-core@ruby-lang.org"] spec.summary = %q{OpenSSL provides SSL, TLS and general purpose cryptography.} @@ -17,7 +17,8 @@ Gem::Specification.new do |spec| spec.required_ruby_version = ">= 2.3.0" - spec.add_development_dependency "rake" + spec.add_runtime_dependency "ipaddr" + spec.add_development_dependency "rake", ">= 11.2.0" spec.add_development_dependency "rake-compiler" spec.add_development_dependency "test-unit", "~> 3.0" spec.add_development_dependency "rdoc" diff --git a/ext/openssl/ossl_bn.c b/ext/openssl/ossl_bn.c index d94b8e375c..bec37299f7 100644 --- a/ext/openssl/ossl_bn.c +++ b/ext/openssl/ossl_bn.c @@ -453,7 +453,7 @@ ossl_bn_is_negative(VALUE self) if (!(result = BN_new())) { \ ossl_raise(eBNError, NULL); \ } \ - if (!BN_##func(result, bn, ossl_bn_ctx)) { \ + if (BN_##func(result, bn, ossl_bn_ctx) <= 0) { \ BN_free(result); \ ossl_raise(eBNError, NULL); \ } \ @@ -479,7 +479,7 @@ BIGNUM_1c(sqr) if (!(result = BN_new())) { \ ossl_raise(eBNError, NULL); \ } \ - if (!BN_##func(result, bn1, bn2)) { \ + if (BN_##func(result, bn1, bn2) <= 0) { \ BN_free(result); \ ossl_raise(eBNError, NULL); \ } \ @@ -512,7 +512,7 @@ BIGNUM_2(sub) if (!(result = BN_new())) { \ ossl_raise(eBNError, NULL); \ } \ - if (!BN_##func(result, bn1, bn2, ossl_bn_ctx)) { \ + if (BN_##func(result, bn1, bn2, ossl_bn_ctx) <= 0) { \ BN_free(result); \ ossl_raise(eBNError, NULL); \ } \ @@ -556,11 +556,21 @@ BIGNUM_2c(gcd) BIGNUM_2c(mod_sqr) /* - * Document-method: OpenSSL::BN#mod_inverse * call-seq: - * bn.mod_inverse(bn2) => aBN + * bn.mod_inverse(bn2) => aBN */ -BIGNUM_2c(mod_inverse) +static VALUE +ossl_bn_mod_inverse(VALUE self, VALUE other) +{ + BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; + VALUE obj; + GetBN(self, bn1); + obj = NewBN(rb_obj_class(self)); + if (!(result = BN_mod_inverse(NULL, bn1, bn2, ossl_bn_ctx))) + ossl_raise(eBNError, "BN_mod_inverse"); + SetBN(obj, result); + return obj; +} /* * call-seq: @@ -609,7 +619,7 @@ ossl_bn_div(VALUE self, VALUE other) if (!(result = BN_new())) { \ ossl_raise(eBNError, NULL); \ } \ - if (!BN_##func(result, bn1, bn2, bn3, ossl_bn_ctx)) { \ + if (BN_##func(result, bn1, bn2, bn3, ossl_bn_ctx) <= 0) { \ BN_free(result); \ ossl_raise(eBNError, NULL); \ } \ @@ -651,7 +661,7 @@ BIGNUM_3c(mod_exp) { \ BIGNUM *bn; \ GetBN(self, bn); \ - if (!BN_##func(bn, NUM2INT(bit))) { \ + if (BN_##func(bn, NUM2INT(bit)) <= 0) { \ ossl_raise(eBNError, NULL); \ } \ return self; \ @@ -711,7 +721,7 @@ ossl_bn_is_bit_set(VALUE self, VALUE bit) if (!(result = BN_new())) { \ ossl_raise(eBNError, NULL); \ } \ - if (!BN_##func(result, bn, b)) { \ + if (BN_##func(result, bn, b) <= 0) { \ BN_free(result); \ ossl_raise(eBNError, NULL); \ } \ @@ -741,7 +751,7 @@ BIGNUM_SHIFT(rshift) int b; \ b = NUM2INT(bits); \ GetBN(self, bn); \ - if (!BN_##func(bn, bn, b)) \ + if (BN_##func(bn, bn, b) <= 0) \ ossl_raise(eBNError, NULL); \ return self; \ } @@ -780,7 +790,7 @@ BIGNUM_SELF_SHIFT(rshift) if (!(result = BN_new())) { \ ossl_raise(eBNError, NULL); \ } \ - if (!BN_##func(result, b, top, bottom)) { \ + if (BN_##func(result, b, top, bottom) <= 0) { \ BN_free(result); \ ossl_raise(eBNError, NULL); \ } \ @@ -809,7 +819,7 @@ BIGNUM_RAND(pseudo_rand) if (!(result = BN_new())) { \ ossl_raise(eBNError, NULL); \ } \ - if (!BN_##func##_range(result, bn)) { \ + if (BN_##func##_range(result, bn) <= 0) { \ BN_free(result); \ ossl_raise(eBNError, NULL); \ } \ diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index 0b78f40b72..5b92fc39f0 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -814,6 +814,31 @@ ossl_cipher_block_size(VALUE self) } /* + * call-seq: + * cipher.ccm_data_len = integer -> integer + * + * Sets the length of the plaintext / ciphertext message that will be + * processed in CCM mode. Make sure to call this method after #key= and + * #iv= have been set, and before #auth_data=. + * + * Only call this method after calling Cipher#encrypt or Cipher#decrypt. + */ +static VALUE +ossl_cipher_set_ccm_data_len(VALUE self, VALUE data_len) +{ + int in_len, out_len; + EVP_CIPHER_CTX *ctx; + + in_len = NUM2INT(data_len); + + GetCipher(self, ctx); + if (EVP_CipherUpdate(ctx, NULL, &out_len, NULL, in_len) != 1) + ossl_raise(eCipherError, NULL); + + return data_len; +} + +/* * INIT */ void @@ -1043,6 +1068,7 @@ Init_ossl_cipher(void) rb_define_method(cCipher, "iv_len", ossl_cipher_iv_length, 0); rb_define_method(cCipher, "block_size", ossl_cipher_block_size, 0); rb_define_method(cCipher, "padding=", ossl_cipher_set_padding, 1); + rb_define_method(cCipher, "ccm_data_len=", ossl_cipher_set_ccm_data_len, 1); id_auth_tag_len = rb_intern_const("auth_tag_len"); id_key_set = rb_intern_const("key_set"); diff --git a/ext/openssl/ossl_digest.c b/ext/openssl/ossl_digest.c index e2157cb02f..6294fa2acf 100644 --- a/ext/openssl/ossl_digest.c +++ b/ext/openssl/ossl_digest.c @@ -313,8 +313,6 @@ ossl_digest_block_length(VALUE self) void Init_ossl_digest(void) { - rb_require("digest"); - #if 0 mOSSL = rb_define_module("OpenSSL"); eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); @@ -398,6 +396,12 @@ Init_ossl_digest(void) * digest2 = sha256.digest(data2) * */ + + /* + * Digest::Class is defined by the digest library. rb_require() cannot be + * used here because it bypasses RubyGems. + */ + rb_funcall(Qnil, rb_intern_const("require"), 1, rb_str_new_cstr("digest")); cDigest = rb_define_class_under(mOSSL, "Digest", rb_path2class("Digest::Class")); /* Document-class: OpenSSL::Digest::DigestError * diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index fc2bc6c815..1d105abd35 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -653,15 +653,15 @@ static VALUE ossl_ec_key_dsa_verify_asn1(VALUE self, VALUE data, VALUE sig) StringValue(data); StringValue(sig); - switch (ECDSA_verify(0, (unsigned char *) RSTRING_PTR(data), RSTRING_LENINT(data), (unsigned char *) RSTRING_PTR(sig), (int)RSTRING_LEN(sig), ec)) { - case 1: return Qtrue; - case 0: return Qfalse; - default: break; + switch (ECDSA_verify(0, (unsigned char *)RSTRING_PTR(data), RSTRING_LENINT(data), + (unsigned char *)RSTRING_PTR(sig), RSTRING_LENINT(sig), ec)) { + case 1: + return Qtrue; + case 0: + return Qfalse; + default: + ossl_raise(eECError, "ECDSA_verify"); } - - ossl_raise(eECError, "ECDSA_verify"); - - UNREACHABLE; } /* diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 4b7efa39f5..7654be12ee 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -13,6 +13,12 @@ #define numberof(ary) (int)(sizeof(ary)/sizeof((ary)[0])) +#if !defined(TLS1_3_VERSION) && \ + defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER >= 0x3020000fL +# define TLS1_3_VERSION 0x0304 +#endif + #ifdef _WIN32 # define TO_SOCKET(s) _get_osfhandle(s) #else @@ -33,7 +39,7 @@ static VALUE eSSLErrorWaitReadable; static VALUE eSSLErrorWaitWritable; static ID id_call, ID_callback_state, id_tmp_dh_callback, id_tmp_ecdh_callback, - id_npn_protocols_encoded; + id_npn_protocols_encoded, id_each; static VALUE sym_exception, sym_wait_readable, sym_wait_writable; static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode, @@ -54,6 +60,13 @@ static int ossl_sslctx_ex_store_p; #endif static void +ossl_sslctx_mark(void *ptr) +{ + SSL_CTX *ctx = ptr; + rb_gc_mark((VALUE)SSL_CTX_get_ex_data(ctx, ossl_sslctx_ex_ptr_idx)); +} + +static void ossl_sslctx_free(void *ptr) { SSL_CTX *ctx = ptr; @@ -67,7 +80,7 @@ ossl_sslctx_free(void *ptr) static const rb_data_type_t ossl_sslctx_type = { "OpenSSL/SSL/CTX", { - 0, ossl_sslctx_free, + ossl_sslctx_mark, ossl_sslctx_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, }; @@ -616,7 +629,7 @@ static VALUE ssl_encode_npn_protocols(VALUE protocols) { VALUE encoded = rb_str_new(NULL, 0); - rb_iterate(rb_each, protocols, ssl_npn_encode_protocol_i, encoded); + rb_block_call(protocols, id_each, 0, 0, ssl_npn_encode_protocol_i, encoded); return encoded; } @@ -686,7 +699,7 @@ static int ssl_npn_advertise_cb(SSL *ssl, const unsigned char **out, unsigned int *outlen, void *arg) { - VALUE protocols = (VALUE)arg; + VALUE protocols = rb_attr_get((VALUE)arg, id_npn_protocols_encoded); *out = (const unsigned char *) RSTRING_PTR(protocols); *outlen = RSTRING_LENINT(protocols); @@ -908,7 +921,7 @@ ossl_sslctx_setup(VALUE self) if (!NIL_P(val)) { VALUE encoded = ssl_encode_npn_protocols(val); rb_ivar_set(self, id_npn_protocols_encoded, encoded); - SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_npn_advertise_cb, (void *)encoded); + SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_npn_advertise_cb, (void *)self); OSSL_Debug("SSL NPN advertise callback added"); } if (RTEST(rb_attr_get(self, id_i_npn_select_cb))) { @@ -1527,6 +1540,14 @@ ssl_started(SSL *ssl) } static void +ossl_ssl_mark(void *ptr) +{ + SSL *ssl = ptr; + rb_gc_mark((VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx)); + rb_gc_mark((VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_vcb_idx)); +} + +static void ossl_ssl_free(void *ssl) { SSL_free(ssl); @@ -1535,7 +1556,7 @@ ossl_ssl_free(void *ssl) const rb_data_type_t ossl_ssl_type = { "OpenSSL/SSL", { - 0, ossl_ssl_free, + ossl_ssl_mark, ossl_ssl_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, }; @@ -1691,6 +1712,11 @@ ossl_start_ssl(VALUE self, int (*func)(), const char *funcname, VALUE opts) rb_io_wait_readable(fptr->fd); continue; case SSL_ERROR_SYSCALL: +#ifdef __APPLE__ + /* See ossl_ssl_write_internal() */ + if (errno == EPROTOTYPE) + continue; +#endif if (errno) rb_sys_fail(funcname); ossl_raise(eSSLError, "%s SYSCALL returned=%d errno=%d state=%s", funcname, ret2, errno, SSL_state_string_long(ssl)); #if defined(SSL_R_CERTIFICATE_VERIFY_FAILED) @@ -1847,26 +1873,36 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) io = rb_attr_get(self, id_i_io); GetOpenFile(io, fptr); if (ssl_started(ssl)) { - for (;;){ + rb_str_locktmp(str); + for (;;) { nread = SSL_read(ssl, RSTRING_PTR(str), ilen); switch(ssl_get_error(ssl, nread)){ case SSL_ERROR_NONE: + rb_str_unlocktmp(str); goto end; case SSL_ERROR_ZERO_RETURN: + rb_str_unlocktmp(str); if (no_exception_p(opts)) { return Qnil; } rb_eof_error(); case SSL_ERROR_WANT_WRITE: - if (no_exception_p(opts)) { return sym_wait_writable; } - write_would_block(nonblock); + if (nonblock) { + rb_str_unlocktmp(str); + if (no_exception_p(opts)) { return sym_wait_writable; } + write_would_block(nonblock); + } rb_io_wait_writable(fptr->fd); continue; case SSL_ERROR_WANT_READ: - if (no_exception_p(opts)) { return sym_wait_readable; } - read_would_block(nonblock); + if (nonblock) { + rb_str_unlocktmp(str); + if (no_exception_p(opts)) { return sym_wait_readable; } + read_would_block(nonblock); + } rb_io_wait_readable(fptr->fd); continue; case SSL_ERROR_SYSCALL: if (!ERR_peek_error()) { + rb_str_unlocktmp(str); if (errno) rb_sys_fail(0); else { @@ -1883,6 +1919,7 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) } /* fall through */ default: + rb_str_unlocktmp(str); ossl_raise(eSSLError, "SSL_read"); } } @@ -1953,21 +1990,21 @@ ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts) int nwrite = 0; rb_io_t *fptr; int nonblock = opts != Qfalse; - VALUE io; + VALUE tmp, io; - StringValue(str); + tmp = rb_str_new_frozen(StringValue(str)); GetSSL(self, ssl); io = rb_attr_get(self, id_i_io); GetOpenFile(io, fptr); if (ssl_started(ssl)) { - for (;;){ - int num = RSTRING_LENINT(str); + for (;;) { + int num = RSTRING_LENINT(tmp); /* SSL_write(3ssl) manpage states num == 0 is undefined */ if (num == 0) goto end; - nwrite = SSL_write(ssl, RSTRING_PTR(str), num); + nwrite = SSL_write(ssl, RSTRING_PTR(tmp), num); switch(ssl_get_error(ssl, nwrite)){ case SSL_ERROR_NONE: goto end; @@ -1982,6 +2019,16 @@ ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts) rb_io_wait_readable(fptr->fd); continue; case SSL_ERROR_SYSCALL: +#ifdef __APPLE__ + /* + * It appears that send syscall can return EPROTOTYPE if the + * socket is being torn down. Retry to get a proper errno to + * make the error handling in line with the socket library. + * [Bug #14713] https://bugs.ruby-lang.org/issues/14713 + */ + if (errno == EPROTOTYPE) + continue; +#endif if (errno) rb_sys_fail(0); default: ossl_raise(eSSLError, "SSL_write"); @@ -2997,6 +3044,7 @@ Init_ossl_ssl(void) id_tmp_dh_callback = rb_intern_const("tmp_dh_callback"); id_tmp_ecdh_callback = rb_intern_const("tmp_ecdh_callback"); id_npn_protocols_encoded = rb_intern_const("npn_protocols_encoded"); + id_each = rb_intern_const("each"); #define DefIVarID(name) do \ id_i_##name = rb_intern_const("@"#name); while (0) diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c index d3209c3d40..cff9b7bfff 100644 --- a/ext/openssl/ossl_ts.c +++ b/ext/openssl/ossl_ts.c @@ -68,9 +68,9 @@ static VALUE cTimestampRequest; static VALUE cTimestampResponse; static VALUE cTimestampTokenInfo; static VALUE cTimestampFactory; -static ID sBAD_ALG, sBAD_REQUEST, sBAD_DATA_FORMAT, sTIME_NOT_AVAILABLE; -static ID sUNACCEPTED_POLICY, sUNACCEPTED_EXTENSION, sADD_INFO_NOT_AVAILABLE; -static ID sSYSTEM_FAILURE; +static VALUE sBAD_ALG, sBAD_REQUEST, sBAD_DATA_FORMAT, sTIME_NOT_AVAILABLE; +static VALUE sUNACCEPTED_POLICY, sUNACCEPTED_EXTENSION, sADD_INFO_NOT_AVAILABLE; +static VALUE sSYSTEM_FAILURE; static void ossl_ts_req_free(void *ptr) @@ -205,8 +205,10 @@ ossl_ts_req_initialize(int argc, VALUE *argv, VALUE self) in = ossl_obj2bio(&arg); ts_req = d2i_TS_REQ_bio(in, &ts_req); BIO_free(in); - if (!ts_req) + if (!ts_req) { + DATA_PTR(self) = NULL; ossl_raise(eTimestampError, "Error when decoding the timestamp request"); + } DATA_PTR(self) = ts_req; return self; @@ -529,8 +531,10 @@ ossl_ts_resp_initialize(VALUE self, VALUE der) in = ossl_obj2bio(&der); ts_resp = d2i_TS_RESP_bio(in, &ts_resp); BIO_free(in); - if (!ts_resp) + if (!ts_resp) { + DATA_PTR(self) = NULL; ossl_raise(eTimestampError, "Error when decoding the timestamp response"); + } DATA_PTR(self) = ts_resp; return self; @@ -871,8 +875,10 @@ ossl_ts_token_info_initialize(VALUE self, VALUE der) in = ossl_obj2bio(&der); info = d2i_TS_TST_INFO_bio(in, &info); BIO_free(in); - if (!info) + if (!info) { + DATA_PTR(self) = NULL; ossl_raise(eTimestampError, "Error when decoding the timestamp token info"); + } DATA_PTR(self) = info; return self; @@ -1074,7 +1080,11 @@ ossl_tsfac_serial_cb(struct TS_resp_ctx *ctx, void *data) } static int +#if !defined(LIBRESSL_VERSION_NUMBER) ossl_tsfac_time_cb(struct TS_resp_ctx *ctx, void *data, long *sec, long *usec) +#else +ossl_tsfac_time_cb(struct TS_resp_ctx *ctx, void *data, time_t *sec, long *usec) +#endif { *sec = *((long *)data); *usec = 0; @@ -1247,24 +1257,24 @@ Init_ossl_ts(void) * timestamp server rejects the message imprint algorithm used in the * +Request+ */ - sBAD_ALG = rb_intern_const("BAD_ALG"); + sBAD_ALG = ID2SYM(rb_intern_const("BAD_ALG")); /* * Possible return value for +Response#failure_info+. Indicates that the * timestamp server was not able to process the +Request+ properly. */ - sBAD_REQUEST = rb_intern_const("BAD_REQUEST"); + sBAD_REQUEST = ID2SYM(rb_intern_const("BAD_REQUEST")); /* * Possible return value for +Response#failure_info+. Indicates that the * timestamp server was not able to parse certain data in the +Request+. */ - sBAD_DATA_FORMAT = rb_intern_const("BAD_DATA_FORMAT"); + sBAD_DATA_FORMAT = ID2SYM(rb_intern_const("BAD_DATA_FORMAT")); - sTIME_NOT_AVAILABLE = rb_intern_const("TIME_NOT_AVAILABLE"); - sUNACCEPTED_POLICY = rb_intern_const("UNACCEPTED_POLICY"); - sUNACCEPTED_EXTENSION = rb_intern_const("UNACCEPTED_EXTENSION"); - sADD_INFO_NOT_AVAILABLE = rb_intern_const("ADD_INFO_NOT_AVAILABLE"); - sSYSTEM_FAILURE = rb_intern_const("SYSTEM_FAILURE"); + sTIME_NOT_AVAILABLE = ID2SYM(rb_intern_const("TIME_NOT_AVAILABLE")); + sUNACCEPTED_POLICY = ID2SYM(rb_intern_const("UNACCEPTED_POLICY")); + sUNACCEPTED_EXTENSION = ID2SYM(rb_intern_const("UNACCEPTED_EXTENSION")); + sADD_INFO_NOT_AVAILABLE = ID2SYM(rb_intern_const("ADD_INFO_NOT_AVAILABLE")); + sSYSTEM_FAILURE = ID2SYM(rb_intern_const("SYSTEM_FAILURE")); /* Document-class: OpenSSL::Timestamp * Provides classes and methods to request, create and validate diff --git a/ext/openssl/ossl_x509store.c b/ext/openssl/ossl_x509store.c index 61543d44f6..9035a70aa9 100644 --- a/ext/openssl/ossl_x509store.c +++ b/ext/openssl/ossl_x509store.c @@ -106,6 +106,13 @@ VALUE cX509StoreContext; VALUE eX509StoreError; static void +ossl_x509store_mark(void *ptr) +{ + X509_STORE *store = ptr; + rb_gc_mark((VALUE)X509_STORE_get_ex_data(store, store_ex_verify_cb_idx)); +} + +static void ossl_x509store_free(void *ptr) { X509_STORE_free(ptr); @@ -114,7 +121,7 @@ ossl_x509store_free(void *ptr) static const rb_data_type_t ossl_x509store_type = { "OpenSSL/X509/STORE", { - 0, ossl_x509store_free, + ossl_x509store_mark, ossl_x509store_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, }; @@ -457,23 +464,16 @@ ossl_x509store_verify(int argc, VALUE *argv, VALUE self) } /* - * Public Functions - */ -static void ossl_x509stctx_free(void*); - - -static const rb_data_type_t ossl_x509stctx_type = { - "OpenSSL/X509/STORE_CTX", - { - 0, ossl_x509stctx_free, - }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, -}; - -/* * Private functions */ static void +ossl_x509stctx_mark(void *ptr) +{ + X509_STORE_CTX *ctx = ptr; + rb_gc_mark((VALUE)X509_STORE_CTX_get_ex_data(ctx, stctx_ex_verify_cb_idx)); +} + +static void ossl_x509stctx_free(void *ptr) { X509_STORE_CTX *ctx = ptr; @@ -484,6 +484,14 @@ ossl_x509stctx_free(void *ptr) X509_STORE_CTX_free(ctx); } +static const rb_data_type_t ossl_x509stctx_type = { + "OpenSSL/X509/STORE_CTX", + { + ossl_x509stctx_mark, ossl_x509stctx_free, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, +}; + static VALUE ossl_x509stctx_alloc(VALUE klass) { @@ -517,7 +525,9 @@ static VALUE ossl_x509stctx_set_time(VALUE, VALUE); /* * call-seq: - * StoreContext.new(store, cert = nil, chain = nil) + * StoreContext.new(store, cert = nil, untrusted = nil) + * + * Sets up a StoreContext for a verification of the X.509 certificate _cert_. */ static VALUE ossl_x509stctx_initialize(int argc, VALUE *argv, VALUE self) @@ -527,15 +537,24 @@ ossl_x509stctx_initialize(int argc, VALUE *argv, VALUE self) X509_STORE *x509st; X509 *x509 = NULL; STACK_OF(X509) *x509s = NULL; + int state; rb_scan_args(argc, argv, "12", &store, &cert, &chain); GetX509StCtx(self, ctx); GetX509Store(store, x509st); - if(!NIL_P(cert)) x509 = DupX509CertPtr(cert); /* NEED TO DUP */ - if(!NIL_P(chain)) x509s = ossl_x509_ary2sk(chain); - if(X509_STORE_CTX_init(ctx, x509st, x509, x509s) != 1){ + if (!NIL_P(cert)) + x509 = DupX509CertPtr(cert); /* NEED TO DUP */ + if (!NIL_P(chain)) { + x509s = ossl_protect_x509_ary2sk(chain, &state); + if (state) { + X509_free(x509); + rb_jump_tag(state); + } + } + if (X509_STORE_CTX_init(ctx, x509st, x509, x509s) != 1){ + X509_free(x509); sk_X509_pop_free(x509s, X509_free); - ossl_raise(eX509StoreError, NULL); + ossl_raise(eX509StoreError, "X509_STORE_CTX_init"); } if (!NIL_P(t = rb_iv_get(store, "@time"))) ossl_x509stctx_set_time(self, t); diff --git a/ext/psych/lib/psych.rb b/ext/psych/lib/psych.rb index cedf0a4ad6..34d2218549 100644 --- a/ext/psych/lib/psych.rb +++ b/ext/psych/lib/psych.rb @@ -271,7 +271,7 @@ module Psych # YAML documents that are supplied via user input. Instead, please use the # safe_load method. # - def self.load yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: false, symbolize_names: false, freeze: false + def self.unsafe_load yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: false, symbolize_names: false, freeze: false if legacy_filename != NOT_GIVEN warn_with_uplevel 'Passing filename with the 2nd argument of Psych.load is deprecated. Use keyword argument like Psych.load(yaml, filename: ...) instead.', uplevel: 1 if $VERBOSE filename = legacy_filename @@ -281,6 +281,7 @@ module Psych return fallback unless result result.to_ruby(symbolize_names: symbolize_names, freeze: freeze) end + class << self; alias :load :unsafe_load; end ### # Safely load the yaml string in +yaml+. By default, only the following @@ -577,11 +578,12 @@ module Psych # NOTE: This method *should not* be used to parse untrusted documents, such as # YAML documents that are supplied via user input. Instead, please use the # safe_load_file method. - def self.load_file filename, **kwargs + def self.unsafe_load_file filename, **kwargs File.open(filename, 'r:bom|utf-8') { |f| - self.load f, filename: filename, **kwargs + self.unsafe_load f, filename: filename, **kwargs } end + class << self; alias :load_file :unsafe_load_file; end ### # Safely loads the document contained in +filename+. Returns the yaml contained in diff --git a/ext/psych/lib/psych/handler.rb b/ext/psych/lib/psych/handler.rb index 8f23e366fa..ad7249ff77 100644 --- a/ext/psych/lib/psych/handler.rb +++ b/ext/psych/lib/psych/handler.rb @@ -119,7 +119,7 @@ module Psych # +tag+ is an associated tag or nil # +plain+ is a boolean value # +quoted+ is a boolean value - # +style+ is an integer idicating the string style + # +style+ is an integer indicating the string style # # See the constants in Psych::Nodes::Scalar for the possible values of # +style+ diff --git a/ext/psych/lib/psych/nodes/scalar.rb b/ext/psych/lib/psych/nodes/scalar.rb index e2616b6a84..5550b616a3 100644 --- a/ext/psych/lib/psych/nodes/scalar.rb +++ b/ext/psych/lib/psych/nodes/scalar.rb @@ -50,7 +50,7 @@ module Psych # +tag+ is an associated tag or nil # +plain+ is a boolean value # +quoted+ is a boolean value - # +style+ is an integer idicating the string style + # +style+ is an integer indicating the string style # # == See Also # diff --git a/ext/psych/lib/psych/versions.rb b/ext/psych/lib/psych/versions.rb index 488f86e0b4..acb21336c4 100644 --- a/ext/psych/lib/psych/versions.rb +++ b/ext/psych/lib/psych/versions.rb @@ -1,10 +1,10 @@ - # frozen_string_literal: true + module Psych # The version of Psych you are using - VERSION = '3.3.0' + VERSION = '3.3.2' if RUBY_ENGINE == 'jruby' - DEFAULT_SNAKEYAML_VERSION = '1.26'.freeze + DEFAULT_SNAKEYAML_VERSION = '1.28'.freeze end end diff --git a/ext/psych/lib/psych/visitors/to_ruby.rb b/ext/psych/lib/psych/visitors/to_ruby.rb index ec80701917..4de7f80d33 100644 --- a/ext/psych/lib/psych/visitors/to_ruby.rb +++ b/ext/psych/lib/psych/visitors/to_ruby.rb @@ -24,6 +24,7 @@ module Psych super() @st = {} @ss = ss + @load_tags = Psych.load_tags @domain_types = Psych.domain_types @class_loader = class_loader @symbolize_names = symbolize_names @@ -48,7 +49,7 @@ module Psych end def deserialize o - if klass = resolve_class(Psych.load_tags[o.tag]) + if klass = resolve_class(@load_tags[o.tag]) instance = klass.allocate if instance.respond_to?(:init_with) @@ -128,7 +129,7 @@ module Psych end def visit_Psych_Nodes_Sequence o - if klass = resolve_class(Psych.load_tags[o.tag]) + if klass = resolve_class(@load_tags[o.tag]) instance = klass.allocate if instance.respond_to?(:init_with) @@ -160,8 +161,8 @@ module Psych end def visit_Psych_Nodes_Mapping o - if Psych.load_tags[o.tag] - return revive(resolve_class(Psych.load_tags[o.tag]), o) + if @load_tags[o.tag] + return revive(resolve_class(@load_tags[o.tag]), o) end return revive_hash(register(o, {}), o) unless o.tag @@ -326,6 +327,7 @@ module Psych end private + def register node, object @st[node.anchor] = object if node.anchor object @@ -337,7 +339,7 @@ module Psych list end - def revive_hash hash, o + def revive_hash hash, o, tagged= false o.children.each_slice(2) { |k,v| key = accept(k) val = accept(v) @@ -364,7 +366,7 @@ module Psych hash[key] = val end else - if @symbolize_names + if !tagged && @symbolize_names && key.is_a?(String) key = key.to_sym elsif !@freeze key = deduplicate(key) @@ -402,7 +404,7 @@ module Psych def revive klass, node s = register(node, klass.allocate) - init_with(s, revive_hash({}, node), node) + init_with(s, revive_hash({}, node, true), node) end def init_with o, h, node diff --git a/ext/psych/lib/psych/visitors/visitor.rb b/ext/psych/lib/psych/visitors/visitor.rb index e2585c0c77..21052aa66f 100644 --- a/ext/psych/lib/psych/visitors/visitor.rb +++ b/ext/psych/lib/psych/visitors/visitor.rb @@ -17,7 +17,7 @@ module Psych if defined?(Ractor) def dispatch - Ractor.current[:Psych_Visitors_Visitor] ||= Visitor.dispatch_cache + @dispatch_cache ||= (Ractor.current[:Psych_Visitors_Visitor] ||= Visitor.dispatch_cache) end else DISPATCH = dispatch_cache diff --git a/ext/psych/lib/psych/visitors/yaml_tree.rb b/ext/psych/lib/psych/visitors/yaml_tree.rb index ac6777aeb5..bf7c0bb8ca 100644 --- a/ext/psych/lib/psych/visitors/yaml_tree.rb +++ b/ext/psych/lib/psych/visitors/yaml_tree.rb @@ -509,9 +509,9 @@ module Psych def emit_coder c, o case c.type when :scalar - @emitter.scalar c.scalar, nil, c.tag, c.tag.nil?, false, Nodes::Scalar::ANY + @emitter.scalar c.scalar, nil, c.tag, c.tag.nil?, false, c.style when :seq - @emitter.start_sequence nil, c.tag, c.tag.nil?, Nodes::Sequence::BLOCK + @emitter.start_sequence nil, c.tag, c.tag.nil?, c.style c.seq.each do |thing| accept thing end diff --git a/ext/psych/yaml/loader.c b/ext/psych/yaml/loader.c index 78b87e6f6b..bcf3aee8cb 100644 --- a/ext/psych/yaml/loader.c +++ b/ext/psych/yaml/loader.c @@ -541,4 +541,4 @@ yaml_parser_load_mapping_end(yaml_parser_t *parser, yaml_event_t *event, (void)POP(parser, *ctx); return 1; -}
\ No newline at end of file +} diff --git a/ext/psych/yaml/scanner.c b/ext/psych/yaml/scanner.c index 6acee7d465..bb5d201274 100644 --- a/ext/psych/yaml/scanner.c +++ b/ext/psych/yaml/scanner.c @@ -273,7 +273,7 @@ * The tokens BLOCK-SEQUENCE-START and BLOCK-MAPPING-START denote indentation * increase that precedes a block collection (cf. the INDENT token in Python). * The token BLOCK-END denote indentation decrease that ends a block collection - * (cf. the DEDENT token in Python). However YAML has some syntax pecularities + * (cf. the DEDENT token in Python). However YAML has some syntax peculiarities * that makes detections of these tokens more complex. * * The tokens BLOCK-ENTRY, KEY, and VALUE are used to represent the indicators @@ -3287,7 +3287,7 @@ yaml_parser_scan_flow_scalar(yaml_parser_t *parser, yaml_token_t *token, /* Check if we are at the end of the scalar. */ - /* Fix for crash unitialized value crash + /* Fix for crash uninitialized value crash * Credit for the bug and input is to OSS Fuzz * Credit for the fix to Alex Gaynor */ diff --git a/ext/psych/yaml/yaml.h b/ext/psych/yaml/yaml.h index 4f84957d8f..f1b7bfde20 100644 --- a/ext/psych/yaml/yaml.h +++ b/ext/psych/yaml/yaml.h @@ -1095,7 +1095,7 @@ typedef struct yaml_parser_s { yaml_error_type_t error; /** Error description. */ const char *problem; - /** The byte about which the problem occured. */ + /** The byte about which the problem occurred. */ size_t problem_offset; /** The problematic value (@c -1 is none). */ int problem_value; @@ -1335,7 +1335,7 @@ yaml_parser_delete(yaml_parser_t *parser); * Set a string input. * * Note that the @a input pointer must be valid while the @a parser object - * exists. The application is responsible for destroing @a input after + * exists. The application is responsible for destroying @a input after * destroying the @a parser. * * @param[in,out] parser A parser object. @@ -1734,7 +1734,7 @@ typedef struct yaml_emitter_s { size_t length; /** Does the scalar contain line breaks? */ int multiline; - /** Can the scalar be expessed in the flow plain style? */ + /** Can the scalar be expressed in the flow plain style? */ int flow_plain_allowed; /** Can the scalar be expressed in the block plain style? */ int block_plain_allowed; @@ -1950,7 +1950,7 @@ yaml_emitter_close(yaml_emitter_t *emitter); /** * Emit a YAML document. * - * The documen object may be generated using the yaml_parser_load() function + * The document object may be generated using the yaml_parser_load() function * or the yaml_document_initialize() function. The emitter takes the * responsibility for the document object and destroys its content after * it is emitted. The document object is destroyed even if the function fails. diff --git a/ext/psych/yaml/yaml_private.h b/ext/psych/yaml/yaml_private.h index 6c674de147..266a6bd3a7 100644 --- a/ext/psych/yaml/yaml_private.h +++ b/ext/psych/yaml/yaml_private.h @@ -2,7 +2,7 @@ #include RUBY_EXTCONF_H #endif -#if HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H #include "config.h" #endif diff --git a/ext/racc/cparse/cparse.c b/ext/racc/cparse/cparse.c index 8614c10e09..f71ed2bba9 100644 --- a/ext/racc/cparse/cparse.c +++ b/ext/racc/cparse/cparse.c @@ -819,7 +819,7 @@ reduce0(RB_BLOCK_CALL_FUNC_ARGLIST(_, data)) void Init_cparse(void) { -#if HAVE_RB_EXT_RACTOR_SAFE +#ifdef HAVE_RB_EXT_RACTOR_SAFE rb_ext_ractor_safe(true); #endif diff --git a/ext/ripper/lib/ripper/lexer.rb b/ext/ripper/lib/ripper/lexer.rb index 06db264497..1df1c022c7 100644 --- a/ext/ripper/lib/ripper/lexer.rb +++ b/ext/ripper/lib/ripper/lexer.rb @@ -136,7 +136,7 @@ class Ripper end @buf.flatten! unless (result = @buf).empty? - result.concat(@buf) until (@buf = []; super(); @buf.empty?) + result.concat(@buf) until (@buf = []; super(); @buf.flatten!; @buf.empty?) end result end diff --git a/ext/socket/ifaddr.c b/ext/socket/ifaddr.c index da013256cb..1da259bd6f 100644 --- a/ext/socket/ifaddr.c +++ b/ext/socket/ifaddr.c @@ -460,6 +460,7 @@ rsock_init_sockifaddr(void) * Socket::Ifaddr represents a result of getifaddrs() function. */ rb_cSockIfaddr = rb_define_class_under(rb_cSocket, "Ifaddr", rb_cObject); + rb_undef_alloc_func(rb_cSockIfaddr); rb_define_method(rb_cSockIfaddr, "inspect", ifaddr_inspect, 0); rb_define_method(rb_cSockIfaddr, "name", ifaddr_name, 0); rb_define_method(rb_cSockIfaddr, "ifindex", ifaddr_ifindex, 0); diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 6c86e8964d..12930b3575 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -1,3 +1,4 @@ +/* -*- mode: c; indent-tabs-mode: t -*- */ /********************************************************************** stringio.c - @@ -11,7 +12,7 @@ **********************************************************************/ -#define STRINGIO_VERSION "3.0.0" +#define STRINGIO_VERSION "3.0.1.1" #include "ruby.h" #include "ruby/io.h" @@ -64,7 +65,7 @@ strio_extract_modeenc(VALUE *vmode_p, VALUE *vperm_p, VALUE opthash, n = strchr(n, '|'); } e = strchr(++n, ':'); - len = e ? e - n : strlen(n); + len = e ? e - n : (long)strlen(n); if (len > 0 && len <= ENCODING_MAXNAMELEN) { if (e) { memcpy(encname, n, len); @@ -599,6 +600,14 @@ strio_closed_write(VALUE self) return Qtrue; } +static struct StringIO * +strio_to_read(VALUE self) +{ + struct StringIO *ptr = readable(self); + if (ptr->pos < RSTRING_LEN(ptr->string)) return ptr; + return NULL; +} + /* * call-seq: * strio.eof -> true or false @@ -610,8 +619,7 @@ strio_closed_write(VALUE self) static VALUE strio_eof(VALUE self) { - struct StringIO *ptr = readable(self); - if (ptr->pos < RSTRING_LEN(ptr->string)) return Qfalse; + if (strio_to_read(self)) return Qfalse; return Qtrue; } @@ -821,11 +829,11 @@ strio_get_sync(VALUE self) static VALUE strio_each_byte(VALUE self) { - struct StringIO *ptr = readable(self); + struct StringIO *ptr; RETURN_ENUMERATOR(self, 0, 0); - while (ptr->pos < RSTRING_LEN(ptr->string)) { + while ((ptr = strio_to_read(self)) != NULL) { char c = RSTRING_PTR(ptr->string)[ptr->pos++]; rb_yield(CHR2FIX(c)); } @@ -976,7 +984,7 @@ strio_unget_bytes(struct StringIO *ptr, const char *cp, long cl) len = RSTRING_LEN(str); rest = pos - len; if (cl > pos) { - long ex = (rest < 0 ? cl-pos : cl+rest); + long ex = cl - (rest < 0 ? pos : len); rb_str_modify_expand(str, ex); rb_str_set_len(str, len + ex); s = RSTRING_PTR(str); @@ -1064,11 +1072,7 @@ strio_each_codepoint(VALUE self) ptr = readable(self); enc = get_enc(ptr); - for (;;) { - if (ptr->pos >= RSTRING_LEN(ptr->string)) { - return self; - } - + while ((ptr = strio_to_read(self)) != NULL) { c = rb_enc_codepoint_len(RSTRING_PTR(ptr->string)+ptr->pos, RSTRING_END(ptr->string), &n, enc); ptr->pos += n; diff --git a/ext/stringio/stringio.gemspec b/ext/stringio/stringio.gemspec index d12c648a7d..524d976cfb 100644 --- a/ext/stringio/stringio.gemspec +++ b/ext/stringio/stringio.gemspec @@ -28,7 +28,4 @@ Gem::Specification.new do |s| # s.cert_chain = %w[certs/nobu.pem] # s.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") if $0 =~ /gem\z/ - - s.add_development_dependency 'rake-compiler' - s.add_development_dependency 'test-unit' end diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index 88074a0a26..e1426380b4 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -22,7 +22,7 @@ extern size_t onig_region_memsize(const struct re_registers *regs); #include <stdbool.h> -#define STRSCAN_VERSION "3.0.0" +#define STRSCAN_VERSION "3.0.1" /* ======================================================================= Data Type Definitions @@ -445,13 +445,10 @@ static VALUE strscan_get_charpos(VALUE self) { struct strscanner *p; - VALUE substr; GET_SCANNER(self, p); - substr = rb_funcall(p->str, id_byteslice, 2, INT2FIX(0), LONG2NUM(p->curr)); - - return rb_str_length(substr); + return LONG2NUM(rb_enc_strlen(S_PBEG(p), CURPTR(p), rb_enc_get(p->str))); } /* @@ -984,7 +981,7 @@ strscan_unscan(VALUE self) } /* - * Returns +true+ iff the scan pointer is at the beginning of the line. + * Returns +true+ if and only if the scan pointer is at the beginning of the line. * * s = StringScanner.new("test\ntest\n") * s.bol? # => true @@ -1037,7 +1034,7 @@ strscan_empty_p(VALUE self) } /* - * Returns true iff there is more data in the string. See #eos?. + * Returns true if and only if there is more data in the string. See #eos?. * This method is obsolete; use #eos? instead. * * s = StringScanner.new('test string') @@ -1054,7 +1051,7 @@ strscan_rest_p(VALUE self) } /* - * Returns +true+ iff the last match was successful. + * Returns +true+ if and only if the last match was successful. * * s = StringScanner.new('test string') * s.match?(/\w+/) # => 4 @@ -1537,7 +1534,7 @@ strscan_fixed_anchor_p(VALUE self) * * === Finding Where we Are * - * - #beginning_of_line? (#bol?) + * - #beginning_of_line? (<tt>#bol?</tt>) * - #eos? * - #rest? * - #rest_size @@ -1554,13 +1551,13 @@ strscan_fixed_anchor_p(VALUE self) * - #matched * - #matched? * - #matched_size - * - [] + * - <tt>#[]</tt> * - #pre_match * - #post_match * * === Miscellaneous * - * - << + * - <tt><<</tt> * - #concat * - #string * - #string= diff --git a/ext/strscan/strscan.gemspec b/ext/strscan/strscan.gemspec index fa9b895a9c..5d8119ea4c 100644 --- a/ext/strscan/strscan.gemspec +++ b/ext/strscan/strscan.gemspec @@ -25,8 +25,4 @@ Gem::Specification.new do |s| s.email = [nil, "kou@cozmixng.org"] s.homepage = "https://github.com/ruby/strscan" s.licenses = ["Ruby", "BSD-2-Clause"] - - s.add_development_dependency "rake-compiler" - s.add_development_dependency "benchmark-driver" - s.add_development_dependency "test-unit" end diff --git a/ext/zlib/extlibs b/ext/zlib/extlibs index a64b37ba5f..556d1f4a6f 100644 --- a/ext/zlib/extlibs +++ b/ext/zlib/extlibs @@ -1,8 +1,6 @@ -ver = 1.2.11 +ver = 1.2.13 pkg = zlib-$(ver) -https://zlib.net/$(pkg).tar.gz \ - md5:1c9f62f0778697a09d36121ead88e08e \ - sha512:73fd3fff4adeccd4894084c15ddac89890cd10ef105dd5e1835e1e9bbb6a49ff229713bd197d203edfa17c2727700fce65a2a235f07568212d820dca88b528ae \ +https://github.com/madler/zlib/releases/download/v$(ver)/$(pkg).tar.gz \ + sha256:b3a24de97a8fdbc835b9833169501030b8977031bcb54b3b3ac13740f846ab30 \ # - win32/$(pkg)-mswin.patch -p0 diff --git a/ext/zlib/win32/zlib-1.2.11-mswin.patch b/ext/zlib/win32/zlib-1.2.11-mswin.patch deleted file mode 100644 index 8810b4403c..0000000000 --- a/ext/zlib/win32/zlib-1.2.11-mswin.patch +++ /dev/null @@ -1,95 +0,0 @@ -diff -ru zlib-1.2.11/gzread.c zlib-1.2.11/gzread.c ---- zlib-1.2.11/gzread.c 2016-12-31 23:37:10.000000000 +0900 -+++ zlib-1.2.11/gzread.c 2020-11-23 19:35:00.550987184 +0900 -@@ -316,7 +316,7 @@ - /* set n to the maximum amount of len that fits in an unsigned int */ - n = -1; - if (n > len) -- n = len; -+ n = (unsigned)len; - - /* first just try copying data from the output buffer */ - if (state->x.have) { -@@ -397,7 +397,7 @@ - } - - /* read len or fewer bytes to buf */ -- len = gz_read(state, buf, len); -+ len = (unsigned)gz_read(state, buf, len); - - /* check for an error */ - if (len == 0 && state->err != Z_OK && state->err != Z_BUF_ERROR) -@@ -469,7 +469,7 @@ - } - - /* nothing there -- try gz_read() */ -- ret = gz_read(state, buf, 1); -+ ret = (int)gz_read(state, buf, 1); - return ret < 1 ? -1 : buf[0]; - } - -diff -ru zlib-1.2.11/gzwrite.c zlib-1.2.11/gzwrite.c ---- zlib-1.2.11/gzwrite.c 2017-01-15 09:29:40.000000000 +0900 -+++ zlib-1.2.11/gzwrite.c 2020-11-23 19:35:41.530494030 +0900 -@@ -209,7 +209,7 @@ - state->in); - copy = state->size - have; - if (copy > len) -- copy = len; -+ copy = (unsigned)len; - memcpy(state->in + have, buf, copy); - state->strm.avail_in += copy; - state->x.pos += copy; -@@ -229,7 +229,7 @@ - do { - unsigned n = (unsigned)-1; - if (n > len) -- n = len; -+ n = (unsigned)len; - state->strm.avail_in = n; - state->x.pos += n; - if (gz_comp(state, Z_NO_FLUSH) == -1) -@@ -368,7 +368,7 @@ - - /* write string */ - len = strlen(str); -- ret = gz_write(state, str, len); -+ ret = (int)gz_write(state, str, len); - return ret == 0 && len != 0 ? -1 : ret; - } - -diff -ru zlib-1.2.11/win32/Makefile.msc zlib-1.2.11/win32/Makefile.msc ---- zlib-1.2.11/win32/Makefile.msc 2017-01-15 09:07:08.000000000 +0900 -+++ zlib-1.2.11/win32/Makefile.msc 2020-11-23 22:37:19.746500208 +0900 -@@ -37,6 +37,22 @@ - gzwrite.obj infback.obj inflate.obj inftrees.obj inffast.obj trees.obj uncompr.obj zutil.obj - OBJA = - -+!ifdef USE_ASM -+LOC = -DASMV -DASMINF -+!if "$(ARCH)" == "i386" -+OBJA = inffas32.obj match686.obj -+!else if "$(ARCH)" == "x64" -+AS = ml64 -+LOC = $(LOC) -I. -+OBJA = inffasx64.obj gvmat64.obj inffas8664.obj -+!endif -+!endif -+ -+!if "$(ARCH)" == "x64" -+ZBASE = 0x5A4C000000 -+!else -+ZBASE = 0x5A4C0000 -+!endif - - # targets - all: $(STATICLIB) $(SHAREDLIB) $(IMPLIB) \ -@@ -49,7 +65,7 @@ - - $(SHAREDLIB): $(TOP)/win32/zlib.def $(OBJS) $(OBJA) zlib1.res - $(LD) $(LDFLAGS) -def:$(TOP)/win32/zlib.def -dll -implib:$(IMPLIB) \ -- -out:$@ -base:0x5A4C0000 $(OBJS) $(OBJA) zlib1.res -+ -out:$@ -base:$(ZBASE) $(OBJS) $(OBJA) zlib1.res - if exist $@.manifest \ - mt -nologo -manifest $@.manifest -outputresource:$@;2 - diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 90fa5a61ef..5dbeba6943 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -25,7 +25,7 @@ # define VALGRIND_MAKE_MEM_UNDEFINED(p, n) 0 #endif -#define RUBY_ZLIB_VERSION "1.1.0" +#define RUBY_ZLIB_VERSION "2.0.0" #ifndef RB_PASS_CALLED_KEYWORDS # define rb_class_new_instance_kw(argc, argv, klass, kw_splat) rb_class_new_instance(argc, argv, klass) @@ -288,6 +288,7 @@ static VALUE rb_gzreader_readlines(int, VALUE*, VALUE); * - Zlib::MemError * - Zlib::BufError * - Zlib::VersionError + * - Zlib::InProgressError * * (if you have GZIP_SUPPORT) * - Zlib::GzipReader @@ -304,7 +305,7 @@ void Init_zlib(void); /*--------- Exceptions --------*/ static VALUE cZError, cStreamEnd, cNeedDict; -static VALUE cStreamError, cDataError, cMemError, cBufError, cVersionError; +static VALUE cStreamError, cDataError, cMemError, cBufError, cVersionError, cInProgressError; static void raise_zlib_error(int err, const char *msg) @@ -546,6 +547,7 @@ struct zstream { unsigned long flags; VALUE buf; VALUE input; + VALUE mutex; z_stream stream; const struct zstream_funcs { int (*reset)(z_streamp); @@ -554,14 +556,15 @@ struct zstream { } *func; }; -#define ZSTREAM_FLAG_READY 0x1 -#define ZSTREAM_FLAG_IN_STREAM 0x2 -#define ZSTREAM_FLAG_FINISHED 0x4 -#define ZSTREAM_FLAG_CLOSING 0x8 -#define ZSTREAM_FLAG_GZFILE 0x10 /* disallows yield from expand_buffer for +#define ZSTREAM_FLAG_READY (1 << 0) +#define ZSTREAM_FLAG_IN_STREAM (1 << 1) +#define ZSTREAM_FLAG_FINISHED (1 << 2) +#define ZSTREAM_FLAG_CLOSING (1 << 3) +#define ZSTREAM_FLAG_GZFILE (1 << 4) /* disallows yield from expand_buffer for gzip*/ -#define ZSTREAM_REUSE_BUFFER 0x20 -#define ZSTREAM_FLAG_UNUSED 0x40 +#define ZSTREAM_REUSE_BUFFER (1 << 5) +#define ZSTREAM_IN_PROGRESS (1 << 6) +#define ZSTREAM_FLAG_UNUSED (1 << 7) #define ZSTREAM_READY(z) ((z)->flags |= ZSTREAM_FLAG_READY) #define ZSTREAM_IS_READY(z) ((z)->flags & ZSTREAM_FLAG_READY) @@ -590,7 +593,9 @@ static const struct zstream_funcs inflate_funcs = { }; struct zstream_run_args { - struct zstream * z; + struct zstream *const z; + Bytef *src; + long len; int flush; /* stream flush value for inflate() or deflate() */ int interrupt; /* stop processing the stream and return to ruby */ int jump_state; /* for buffer expansion block break or exception */ @@ -621,6 +626,7 @@ zstream_init(struct zstream *z, const struct zstream_funcs *func) z->flags = 0; z->buf = Qnil; z->input = Qnil; + z->mutex = rb_mutex_new(); z->stream.zalloc = zlib_mem_alloc; z->stream.zfree = zlib_mem_free; z->stream.opaque = Z_NULL; @@ -652,7 +658,9 @@ zstream_expand_buffer(struct zstream *z) rb_obj_reveal(z->buf, rb_cString); } + rb_mutex_unlock(z->mutex); rb_protect(rb_yield, z->buf, &state); + rb_mutex_lock(z->mutex); if (ZSTREAM_REUSE_BUFFER_P(z)) { rb_str_modify(z->buf); @@ -1053,19 +1061,18 @@ zstream_unblock_func(void *ptr) args->interrupt = 1; } -static void -zstream_run(struct zstream *z, Bytef *src, long len, int flush) +static VALUE +zstream_run_try(VALUE value_arg) { - struct zstream_run_args args; + struct zstream_run_args *args = (struct zstream_run_args *)value_arg; + struct zstream *z = args->z; + Bytef *src = args->src; + long len = args->len; + int flush = args->flush; + int err; VALUE old_input = Qnil; - args.z = z; - args.flush = flush; - args.interrupt = 0; - args.jump_state = 0; - args.stream_output = !ZSTREAM_IS_GZFILE(z) && rb_block_given_p(); - if (NIL_P(z->input) && len == 0) { z->stream.next_in = (Bytef*)""; z->stream.avail_in = 0; @@ -1087,14 +1094,20 @@ zstream_run(struct zstream *z, Bytef *src, long len, int flush) loop: #ifndef RB_NOGVL_UBF_ASYNC_SAFE - err = (int)(VALUE)rb_thread_call_without_gvl(zstream_run_func, (void *)&args, - zstream_unblock_func, (void *)&args); + err = (int)(VALUE)rb_thread_call_without_gvl(zstream_run_func, (void *)args, + zstream_unblock_func, (void *)args); #else - err = (int)(VALUE)rb_nogvl(zstream_run_func, (void *)&args, - zstream_unblock_func, (void *)&args, + err = (int)(VALUE)rb_nogvl(zstream_run_func, (void *)args, + zstream_unblock_func, (void *)args, RB_NOGVL_UBF_ASYNC_SAFE); #endif + /* retry if no exception is thrown */ + if (err == Z_OK && args->interrupt) { + args->interrupt = 0; + goto loop; + } + if (flush != Z_FINISH && err == Z_BUF_ERROR && z->stream.avail_out > 0) { z->flags |= ZSTREAM_FLAG_IN_STREAM; @@ -1128,8 +1141,52 @@ loop: rb_gc_force_recycle(old_input); } - if (args.jump_state) - rb_jump_tag(args.jump_state); + if (args->jump_state) + rb_jump_tag(args->jump_state); + + return Qnil; +} + +static VALUE +zstream_run_ensure(VALUE value_arg) +{ + struct zstream_run_args *args = (struct zstream_run_args *)value_arg; + + /* Remove ZSTREAM_IN_PROGRESS flag to signal that this zstream is not in use. */ + args->z->flags &= ~ZSTREAM_IN_PROGRESS; + + return Qnil; +} + +static VALUE +zstream_run_synchronized(VALUE value_arg) +{ + struct zstream_run_args *args = (struct zstream_run_args *)value_arg; + + /* Cannot start zstream while it is in progress. */ + if (args->z->flags & ZSTREAM_IN_PROGRESS) { + rb_raise(cInProgressError, "zlib stream is in progress"); + } + args->z->flags |= ZSTREAM_IN_PROGRESS; + + rb_ensure(zstream_run_try, value_arg, zstream_run_ensure, value_arg); + + return Qnil; +} + +static void +zstream_run(struct zstream *z, Bytef *src, long len, int flush) +{ + struct zstream_run_args args = { + .z = z, + .src = src, + .len = len, + .flush = flush, + .interrupt = 0, + .jump_state = 0, + .stream_output = !ZSTREAM_IS_GZFILE(z) && rb_block_given_p(), + }; + rb_mutex_synchronize(z->mutex, zstream_run_synchronized, (VALUE)&args); } static VALUE @@ -1177,6 +1234,7 @@ zstream_mark(void *p) struct zstream *z = p; rb_gc_mark(z->buf); rb_gc_mark(z->input); + rb_gc_mark(z->mutex); } static void @@ -3514,6 +3572,16 @@ rb_gzfile_path(VALUE obj) return gz->path; } +static VALUE +gzfile_initialize_path_partial(VALUE obj) +{ + struct gzfile* gz; + TypedData_Get_Struct(obj, struct gzfile, &gzfile_data_type, gz); + gz->path = rb_funcall(gz->io, id_path, 0); + rb_define_singleton_method(obj, "path", rb_gzfile_path, 0); + return Qnil; +} + static void rb_gzfile_ecopts(struct gzfile *gz, VALUE opts) { @@ -3622,8 +3690,8 @@ rb_gzwriter_initialize(int argc, VALUE *argv, VALUE obj) rb_gzfile_ecopts(gz, opt); if (rb_respond_to(io, id_path)) { - gz->path = rb_funcall(gz->io, id_path, 0); - rb_define_singleton_method(obj, "path", rb_gzfile_path, 0); + /* File#path may raise IOError in case when a path is unavailable */ + rb_rescue2(gzfile_initialize_path_partial, obj, NULL, Qnil, rb_eIOError, (VALUE)0); } return obj; @@ -3884,8 +3952,8 @@ rb_gzreader_initialize(int argc, VALUE *argv, VALUE obj) rb_gzfile_ecopts(gz, opt); if (rb_respond_to(io, id_path)) { - gz->path = rb_funcall(gz->io, id_path, 0); - rb_define_singleton_method(obj, "path", rb_gzfile_path, 0); + /* File#path may raise IOError in case when a path is unavailable */ + rb_rescue2(gzfile_initialize_path_partial, obj, NULL, Qnil, rb_eIOError, (VALUE)0); } return obj; @@ -4548,7 +4616,7 @@ zlib_gunzip_run(VALUE arg) void Init_zlib(void) { -#if HAVE_RB_EXT_RACTOR_SAFE +#ifdef HAVE_RB_EXT_RACTOR_SAFE rb_ext_ractor_safe(true); #endif @@ -4570,6 +4638,7 @@ Init_zlib(void) cMemError = rb_define_class_under(mZlib, "MemError", cZError); cBufError = rb_define_class_under(mZlib, "BufError", cZError); cVersionError = rb_define_class_under(mZlib, "VersionError", cZError); + cInProgressError = rb_define_class_under(mZlib, "InProgressError", cZError); rb_define_module_function(mZlib, "zlib_version", rb_zlib_version, 0); rb_define_module_function(mZlib, "adler32", rb_zlib_adler32, -1); @@ -4877,6 +4946,7 @@ Init_zlib(void) * - Zlib::MemError * - Zlib::BufError * - Zlib::VersionError + * - Zlib::InProgressError * */ @@ -4952,6 +5022,20 @@ Init_zlib(void) */ /* + * Document-class: Zlib::InProgressError + * + * Subclass of Zlib::Error. This error is raised when the zlib + * stream is currently in progress. + * + * For example: + * + * inflater = Zlib::Inflate.new + * inflater.inflate(compressed) do + * inflater.inflate(compressed) # Raises Zlib::InProgressError + * end + */ + +/* * Document-class: Zlib::GzipFile::Error * * Base class of errors that occur when processing GZIP files. diff --git a/ext/zlib/zlib.gemspec b/ext/zlib/zlib.gemspec index ae1f24143d..1d35065921 100644 --- a/ext/zlib/zlib.gemspec +++ b/ext/zlib/zlib.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |spec| spec.homepage = "https://github.com/ruby/zlib" spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.files = [".gitignore", ".travis.yml", "Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin/console", "bin/setup", "ext/zlib/extconf.rb", "ext/zlib/zlib.c", "zlib.gemspec"] + spec.files = [".gitignore", "Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin/console", "bin/setup", "ext/zlib/extconf.rb", "ext/zlib/zlib.c", "zlib.gemspec"] spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] @@ -268,6 +268,46 @@ rb_str_encode_ospath(VALUE path) #ifdef __APPLE__ # define NORMALIZE_UTF8PATH 1 + +# ifdef HAVE_WORKING_FORK +static void +rb_CFString_class_initialize_before_fork(void) +{ + /* + * Since macOS 13, CFString family API used in + * rb_str_append_normalized_ospath may internally use Objective-C classes + * (NSTaggedPointerString and NSPlaceholderMutableString) for small strings. + * + * On the other hand, Objective-C classes should not be used for the first + * time in a fork()'ed but not exec()'ed process. Violations for this rule + * can result deadlock during class initialization, so Objective-C runtime + * conservatively crashes on such cases by default. + * + * Therefore, we need to use CFString API to initialize Objective-C classes + * used internally *before* fork(). + * + * For future changes, please note that this initialization process cannot + * be done in ctor because NSTaggedPointerString in CoreFoundation is enabled + * after CFStringInitializeTaggedStrings(), which is called during loading + * Objective-C runtime after ctor. + * For more details, see https://bugs.ruby-lang.org/issues/18912 + */ + + /* Enough small but non-empty ASCII string to fit in NSTaggedPointerString. */ + const char small_str[] = "/"; + long len = sizeof(small_str) - 1; + + const CFAllocatorRef alloc = kCFAllocatorDefault; + CFStringRef s = CFStringCreateWithBytesNoCopy(alloc, + (const UInt8 *)small_str, + len, kCFStringEncodingUTF8, + FALSE, kCFAllocatorNull); + CFMutableStringRef m = CFStringCreateMutableCopy(alloc, len, s); + CFRelease(m); + CFRelease(s); +} +# endif + static VALUE rb_str_append_normalized_ospath(VALUE str, const char *ptr, long len) { @@ -1253,7 +1293,7 @@ statx_birthtime(const struct statx *stx, VALUE fname) /* birthtime is not supported on the filesystem */ statx_notimplement("birthtime"); } - return rb_time_nano_new(stx->stx_btime.tv_sec, stx->stx_btime.tv_nsec); + return rb_time_nano_new((time_t)stx->stx_btime.tv_sec, stx->stx_btime.tv_nsec); } typedef struct statx statx_data; @@ -2173,7 +2213,7 @@ rb_file_sticky_p(VALUE obj, VALUE fname) #ifdef S_ISVTX return check3rdbyte(fname, S_ISVTX); #else - return Qnil; + return Qfalse; #endif } @@ -6462,6 +6502,10 @@ const char ruby_null_device[] = void Init_File(void) { +#if defined(__APPLE__) && defined(HAVE_WORKING_FORK) + rb_CFString_class_initialize_before_fork(); +#endif + VALUE separator; rb_mFileTest = rb_define_module("FileTest"); @@ -679,7 +679,7 @@ typedef struct rb_objspace { unsigned int dont_gc : 1; unsigned int dont_incremental : 1; unsigned int during_gc : 1; - unsigned int during_compacting : 2; + unsigned int during_compacting : 1; unsigned int gc_stressful: 1; unsigned int has_hook: 1; unsigned int during_minor_gc : 1; @@ -1202,6 +1202,14 @@ tick(void) #define MEASURE_LINE(expr) expr #endif /* USE_TICK_T */ +static inline void * +asan_unpoison_object_temporary(VALUE obj) +{ + void *ptr = asan_poisoned_object_p(obj); + asan_unpoison_object(obj, false); + return ptr; +} + #define FL_CHECK2(name, x, pred) \ ((RGENGC_CHECK_MODE && SPECIAL_CONST_P(x)) ? \ (rb_bug(name": SPECIAL_CONST (%p)", (void *)(x)), 0) : (pred)) @@ -1239,99 +1247,102 @@ RVALUE_FLAGS_AGE(VALUE flags) static int check_rvalue_consistency_force(const VALUE obj, int terminate) { - rb_objspace_t *objspace = &rb_objspace; int err = 0; + rb_objspace_t *objspace = &rb_objspace; - if (SPECIAL_CONST_P(obj)) { - fprintf(stderr, "check_rvalue_consistency: %p is a special const.\n", (void *)obj); - err++; - } - else if (!is_pointer_to_heap(objspace, (void *)obj)) { - /* check if it is in tomb_pages */ - struct heap_page *page = NULL; - list_for_each(&heap_tomb->pages, page, page_node) { - if (&page->start[0] <= (RVALUE *)obj && - (RVALUE *)obj < &page->start[page->total_slots]) { - fprintf(stderr, "check_rvalue_consistency: %p is in a tomb_heap (%p).\n", - (void *)obj, (void *)page); - err++; - goto skip; - } - } - bp(); - fprintf(stderr, "check_rvalue_consistency: %p is not a Ruby object.\n", (void *)obj); - err++; - skip: - ; - } - else { - const int wb_unprotected_bit = RVALUE_WB_UNPROTECTED_BITMAP(obj) != 0; - const int uncollectible_bit = RVALUE_UNCOLLECTIBLE_BITMAP(obj) != 0; - const int mark_bit = RVALUE_MARK_BITMAP(obj) != 0; - const int marking_bit = RVALUE_MARKING_BITMAP(obj) != 0, remembered_bit = marking_bit; - const int age = RVALUE_FLAGS_AGE(RBASIC(obj)->flags); - - if (GET_HEAP_PAGE(obj)->flags.in_tomb) { - fprintf(stderr, "check_rvalue_consistency: %s is in tomb page.\n", obj_info(obj)); - err++; - } - if (BUILTIN_TYPE(obj) == T_NONE) { - fprintf(stderr, "check_rvalue_consistency: %s is T_NONE.\n", obj_info(obj)); - err++; - } - if (BUILTIN_TYPE(obj) == T_ZOMBIE) { - fprintf(stderr, "check_rvalue_consistency: %s is T_ZOMBIE.\n", obj_info(obj)); + RB_VM_LOCK_ENTER_NO_BARRIER(); + { + if (SPECIAL_CONST_P(obj)) { + fprintf(stderr, "check_rvalue_consistency: %p is a special const.\n", (void *)obj); err++; } - - obj_memsize_of((VALUE)obj, FALSE); - - /* check generation - * - * OLD == age == 3 && old-bitmap && mark-bit (except incremental marking) - */ - if (age > 0 && wb_unprotected_bit) { - fprintf(stderr, "check_rvalue_consistency: %s is not WB protected, but age is %d > 0.\n", obj_info(obj), age); + else if (!is_pointer_to_heap(objspace, (void *)obj)) { + /* check if it is in tomb_pages */ + struct heap_page *page = NULL; + list_for_each(&heap_tomb->pages, page, page_node) { + if (&page->start[0] <= (RVALUE *)obj && + (RVALUE *)obj < &page->start[page->total_slots]) { + fprintf(stderr, "check_rvalue_consistency: %p is in a tomb_heap (%p).\n", + (void *)obj, (void *)page); + err++; + goto skip; + } + } + bp(); + fprintf(stderr, "check_rvalue_consistency: %p is not a Ruby object.\n", (void *)obj); err++; + skip: + ; } + else { + const int wb_unprotected_bit = RVALUE_WB_UNPROTECTED_BITMAP(obj) != 0; + const int uncollectible_bit = RVALUE_UNCOLLECTIBLE_BITMAP(obj) != 0; + const int mark_bit = RVALUE_MARK_BITMAP(obj) != 0; + const int marking_bit = RVALUE_MARKING_BITMAP(obj) != 0, remembered_bit = marking_bit; + const int age = RVALUE_FLAGS_AGE(RBASIC(obj)->flags); + + if (GET_HEAP_PAGE(obj)->flags.in_tomb) { + fprintf(stderr, "check_rvalue_consistency: %s is in tomb page.\n", obj_info(obj)); + err++; + } + if (BUILTIN_TYPE(obj) == T_NONE) { + fprintf(stderr, "check_rvalue_consistency: %s is T_NONE.\n", obj_info(obj)); + err++; + } + if (BUILTIN_TYPE(obj) == T_ZOMBIE) { + fprintf(stderr, "check_rvalue_consistency: %s is T_ZOMBIE.\n", obj_info(obj)); + err++; + } - if (!is_marking(objspace) && uncollectible_bit && !mark_bit) { - fprintf(stderr, "check_rvalue_consistency: %s is uncollectible, but is not marked while !gc.\n", obj_info(obj)); - err++; - } + obj_memsize_of((VALUE)obj, FALSE); - if (!is_full_marking(objspace)) { - if (uncollectible_bit && age != RVALUE_OLD_AGE && !wb_unprotected_bit) { - fprintf(stderr, "check_rvalue_consistency: %s is uncollectible, but not old (age: %d) and not WB unprotected.\n", - obj_info(obj), age); + /* check generation + * + * OLD == age == 3 && old-bitmap && mark-bit (except incremental marking) + */ + if (age > 0 && wb_unprotected_bit) { + fprintf(stderr, "check_rvalue_consistency: %s is not WB protected, but age is %d > 0.\n", obj_info(obj), age); err++; } - if (remembered_bit && age != RVALUE_OLD_AGE) { - fprintf(stderr, "check_rvalue_consistency: %s is remembered, but not old (age: %d).\n", - obj_info(obj), age); + + if (!is_marking(objspace) && uncollectible_bit && !mark_bit) { + fprintf(stderr, "check_rvalue_consistency: %s is uncollectible, but is not marked while !gc.\n", obj_info(obj)); err++; } - } - /* - * check coloring - * - * marking:false marking:true - * marked:false white *invalid* - * marked:true black grey - */ - if (is_incremental_marking(objspace) && marking_bit) { - if (!is_marking(objspace) && !mark_bit) { - fprintf(stderr, "check_rvalue_consistency: %s is marking, but not marked.\n", obj_info(obj)); - err++; + if (!is_full_marking(objspace)) { + if (uncollectible_bit && age != RVALUE_OLD_AGE && !wb_unprotected_bit) { + fprintf(stderr, "check_rvalue_consistency: %s is uncollectible, but not old (age: %d) and not WB unprotected.\n", + obj_info(obj), age); + err++; + } + if (remembered_bit && age != RVALUE_OLD_AGE) { + fprintf(stderr, "check_rvalue_consistency: %s is remembered, but not old (age: %d).\n", + obj_info(obj), age); + err++; + } + } + + /* + * check coloring + * + * marking:false marking:true + * marked:false white *invalid* + * marked:true black grey + */ + if (is_incremental_marking(objspace) && marking_bit) { + if (!is_marking(objspace) && !mark_bit) { + fprintf(stderr, "check_rvalue_consistency: %s is marking, but not marked.\n", obj_info(obj)); + err++; + } } } } + RB_VM_LOCK_LEAVE_NO_BARRIER(); if (err > 0 && terminate) { rb_bug("check_rvalue_consistency_force: there is %d errors.", err); } - return err; } @@ -1710,45 +1721,34 @@ heap_page_add_freeobj(rb_objspace_t *objspace, struct heap_page *page, VALUE obj gc_report(3, objspace, "heap_page_add_freeobj: add %p to freelist\n", (void *)obj); } -static inline bool +static inline void heap_add_freepage(rb_heap_t *heap, struct heap_page *page) { asan_unpoison_memory_region(&page->freelist, sizeof(RVALUE*), false); GC_ASSERT(page->free_slots != 0); + GC_ASSERT(page->freelist != NULL); - if (page->freelist) { - page->free_next = heap->free_pages; - heap->free_pages = page; + page->free_next = heap->free_pages; + heap->free_pages = page; - RUBY_DEBUG_LOG("page:%p freelist:%p", page, page->freelist); + RUBY_DEBUG_LOG("page:%p freelist:%p", page, page->freelist); - asan_poison_memory_region(&page->freelist, sizeof(RVALUE*)); - return true; - } - else { - asan_poison_memory_region(&page->freelist, sizeof(RVALUE*)); - return false; - } + asan_poison_memory_region(&page->freelist, sizeof(RVALUE*)); } #if GC_ENABLE_INCREMENTAL_MARK -static inline int +static inline void heap_add_poolpage(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *page) { asan_unpoison_memory_region(&page->freelist, sizeof(RVALUE*), false); - if (page->freelist) { - page->free_next = heap->pooled_pages; - heap->pooled_pages = page; - objspace->rincgc.pooled_slots += page->free_slots; - asan_poison_memory_region(&page->freelist, sizeof(RVALUE*)); + GC_ASSERT(page->free_slots != 0); + GC_ASSERT(page->freelist != NULL); - return TRUE; - } - else { - asan_poison_memory_region(&page->freelist, sizeof(RVALUE*)); + page->free_next = heap->pooled_pages; + heap->pooled_pages = page; + objspace->rincgc.pooled_slots += page->free_slots; - return FALSE; - } + asan_poison_memory_region(&page->freelist, sizeof(RVALUE*)); } #endif @@ -2071,12 +2071,15 @@ gc_event_hook_body(rb_execution_context_t *ec, rb_objspace_t *objspace, const rb #define gc_event_hook_available_p(objspace) ((objspace)->flags.has_hook) #define gc_event_hook_needed_p(objspace, event) ((objspace)->hook_events & (event)) -#define gc_event_hook(objspace, event, data) do { \ +#define gc_event_hook_prep(objspace, event, data, prep) do { \ if (UNLIKELY(gc_event_hook_needed_p(objspace, event))) { \ + prep; \ gc_event_hook_body(GET_EC(), (objspace), (event), (data)); \ } \ } while (0) +#define gc_event_hook(objspace, event, data) gc_event_hook_prep(objspace, event, data, (void)0) + static inline VALUE newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace, VALUE obj) { @@ -2220,6 +2223,16 @@ ractor_cache_slots(rb_objspace_t *objspace, rb_ractor_t *cr) GC_ASSERT(RB_TYPE_P((VALUE)cr->newobj_cache.freelist, T_NONE)); } +static inline VALUE +newobj_fill(VALUE obj, VALUE v1, VALUE v2, VALUE v3) +{ + RVALUE *p = (RVALUE *)obj; + p->as.values.v1 = v1; + p->as.values.v2 = v2; + p->as.values.v3 = v3; + return obj; +} + ALWAYS_INLINE(static VALUE newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_t *cr, int wb_protected)); static inline VALUE @@ -2250,7 +2263,7 @@ newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_t * } GC_ASSERT(obj != 0); newobj_init(klass, flags, wb_protected, objspace, obj); - gc_event_hook(objspace, RUBY_INTERNAL_EVENT_NEWOBJ, obj); + gc_event_hook_prep(objspace, RUBY_INTERNAL_EVENT_NEWOBJ, obj, newobj_fill(obj, 0, 0, 0)); } RB_VM_LOCK_LEAVE_CR_LEV(cr, &lev); @@ -2312,16 +2325,6 @@ newobj_of0(VALUE klass, VALUE flags, int wb_protected, rb_ractor_t *cr) } static inline VALUE -newobj_fill(VALUE obj, VALUE v1, VALUE v2, VALUE v3) -{ - RVALUE *p = (RVALUE *)obj; - p->as.values.v1 = v1; - p->as.values.v2 = v2; - p->as.values.v3 = v3; - return obj; -} - -static inline VALUE newobj_of(VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, int wb_protected) { VALUE obj = newobj_of0(klass, flags, wb_protected, GET_RACTOR()); @@ -3858,16 +3861,6 @@ rb_objspace_call_finalizer(rb_objspace_t *objspace) ATOMIC_SET(finalizing, 0); } -PUREFUNC(static inline int is_id_value(rb_objspace_t *objspace, VALUE ptr)); -static inline int -is_id_value(rb_objspace_t *objspace, VALUE ptr) -{ - if (!is_pointer_to_heap(objspace, (void *)ptr)) return FALSE; - if (BUILTIN_TYPE(ptr) > T_FIXNUM) return FALSE; - if (BUILTIN_TYPE(ptr) == T_ICLASS) return FALSE; - return TRUE; -} - static inline int heap_is_swept_object(rb_objspace_t *objspace, rb_heap_t *heap, VALUE ptr) { @@ -4484,11 +4477,6 @@ static VALUE gc_move(rb_objspace_t *objspace, VALUE scan, VALUE free); static void lock_page_body(rb_objspace_t *objspace, struct heap_page_body *body) { - /* If this is an explicit compaction (GC.compact), we don't need a read - * barrier, so just return early. */ - if (objspace->flags.during_compacting >> 1) { - return; - } #if defined(_WIN32) DWORD old_protect; @@ -4505,11 +4493,6 @@ lock_page_body(rb_objspace_t *objspace, struct heap_page_body *body) static void unlock_page_body(rb_objspace_t *objspace, struct heap_page_body *body) { - /* If this is an explicit compaction (GC.compact), we don't need a read - * barrier, so just return early. */ - if (objspace->flags.during_compacting >> 1) { - return; - } #if defined(_WIN32) DWORD old_protect; @@ -4641,7 +4624,7 @@ static void read_barrier_handler(intptr_t address) } #if defined(_WIN32) -LPTOP_LEVEL_EXCEPTION_FILTER old_handler; +static LPTOP_LEVEL_EXCEPTION_FILTER old_handler; typedef void (*signal_handler)(int); static signal_handler old_sigsegv_handler; @@ -4660,13 +4643,15 @@ static LONG WINAPI read_barrier_signal(EXCEPTION_POINTERS * info) } } -static void uninstall_handlers(void) +static void +uninstall_handlers(void) { signal(SIGSEGV, old_sigsegv_handler); SetUnhandledExceptionFilter(old_handler); } -static void install_handlers(void) +static void +install_handlers(void) { /* Remove SEGV handler so that the Unhandled Exception Filter handles it */ old_sigsegv_handler = signal(SIGSEGV, NULL); @@ -4702,13 +4687,15 @@ read_barrier_signal(int sig, siginfo_t * info, void * data) sigprocmask(SIG_SETMASK, &prev_set, NULL); } -static void uninstall_handlers(void) +static void +uninstall_handlers(void) { sigaction(SIGBUS, &old_sigbus_handler, NULL); sigaction(SIGSEGV, &old_sigsegv_handler, NULL); } -static void install_handlers(void) +static void +install_handlers(void) { struct sigaction action; memset(&action, 0, sizeof(struct sigaction)); @@ -4747,12 +4734,8 @@ gc_compact_finish(rb_objspace_t *objspace, rb_heap_t *heap) { GC_ASSERT(heap->sweeping_page == heap->compact_cursor); - /* If this is an explicit compaction (GC.compact), no read barrier was set - * so we don't need to unprotect pages or uninstall the SEGV handler */ - if (!(objspace->flags.during_compacting >> 1)) { - gc_unprotect_pages(objspace, heap); - uninstall_handlers(); - } + gc_unprotect_pages(objspace, heap); + uninstall_handlers(); /* The mutator is allowed to run during incremental sweeping. T_MOVED * objects can get pushed on the stack and when the compaction process @@ -5054,20 +5037,7 @@ gc_sweep_start_heap(rb_objspace_t *objspace, rb_heap_t *heap) rb_ractor_t *r = NULL; list_for_each(&GET_VM()->ractor.set, r, vmlr_node) { - struct heap_page *page = r->newobj_cache.using_page; - RVALUE *freelist = r->newobj_cache.freelist; - RUBY_DEBUG_LOG("ractor using_page:%p freelist:%p", page, freelist); - - if (page && freelist) { - RVALUE **p = &page->freelist; - while (*p) { - p = &(*p)->as.free.next; - } - *p = freelist; - } - - r->newobj_cache.using_page = NULL; - r->newobj_cache.freelist = NULL; + rb_gc_ractor_newobj_cache_clear(&r->newobj_cache); } } @@ -5140,21 +5110,18 @@ gc_sweep_step(rb_objspace_t *objspace, rb_heap_t *heap) else if (free_slots > 0) { #if GC_ENABLE_INCREMENTAL_MARK if (need_pool) { - if (heap_add_poolpage(objspace, heap, sweep_page)) { - need_pool = FALSE; - } + heap_add_poolpage(objspace, heap, sweep_page); + need_pool = FALSE; } else { - if (heap_add_freepage(heap, sweep_page)) { - swept_slots += free_slots; - if (swept_slots > 2048) { - break; - } + heap_add_freepage(heap, sweep_page); + swept_slots += free_slots; + if (swept_slots > 2048) { + break; } } #else - heap_add_freepage(heap, sweep_page); - break; + heap_add_freepage(heap, sweep_page); #endif } else { @@ -5272,12 +5239,6 @@ gc_compact_start(rb_objspace_t *objspace, rb_heap_t *heap) memset(objspace->rcompactor.considered_count_table, 0, T_MASK * sizeof(size_t)); memset(objspace->rcompactor.moved_count_table, 0, T_MASK * sizeof(size_t)); - /* If this is an explicit compaction (GC.compact), we don't need a read - * barrier, so just return early. */ - if (objspace->flags.during_compacting >> 1) { - return; - } - /* Set up read barrier for pages containing MOVED objects */ install_handlers(); } @@ -6181,12 +6142,16 @@ gc_mark_imemo(rb_objspace_t *objspace, VALUE obj) case imemo_env: { const rb_env_t *env = (const rb_env_t *)obj; - GC_ASSERT(env->ep[VM_ENV_DATA_INDEX_ENV] == obj); - GC_ASSERT(VM_ENV_ESCAPED_P(env->ep)); - gc_mark_values(objspace, (long)env->env_size, env->env); - VM_ENV_FLAGS_SET(env->ep, VM_ENV_FLAG_WB_REQUIRED); - gc_mark(objspace, (VALUE)rb_vm_env_prev_env(env)); - gc_mark(objspace, (VALUE)env->iseq); + + if (LIKELY(env->ep)) { + // just after newobj() can be NULL here. + GC_ASSERT(env->ep[VM_ENV_DATA_INDEX_ENV] == obj); + GC_ASSERT(VM_ENV_ESCAPED_P(env->ep)); + gc_mark_values(objspace, (long)env->env_size, env->env); + VM_ENV_FLAGS_SET(env->ep, VM_ENV_FLAG_WB_REQUIRED); + gc_mark(objspace, (VALUE)rb_vm_env_prev_env(env)); + gc_mark(objspace, (VALUE)env->iseq); + } } return; case imemo_cref: @@ -7969,6 +7934,37 @@ rb_obj_gc_flags(VALUE obj, ID* flags, size_t max) /* GC */ void +rb_gc_ractor_newobj_cache_clear(rb_ractor_newobj_cache_t *newobj_cache) +{ + struct heap_page *page = newobj_cache->using_page; + RVALUE *freelist = newobj_cache->freelist; + RUBY_DEBUG_LOG("ractor using_page:%p freelist:%p", page, freelist); + + if (page && freelist) { + asan_unpoison_memory_region(&page->freelist, sizeof(RVALUE*), false); + if (page->freelist) { + RVALUE *p = page->freelist; + asan_unpoison_object((VALUE)p, false); + while (p->as.free.next) { + RVALUE *prev = p; + p = p->as.free.next; + asan_poison_object((VALUE)prev); + asan_unpoison_object((VALUE)p, false); + } + p->as.free.next = freelist; + asan_poison_object((VALUE)p); + } + else { + page->freelist = freelist; + } + asan_poison_memory_region(&page->freelist, sizeof(RVALUE*)); + } + + newobj_cache->using_page = NULL; + newobj_cache->freelist = NULL; +} + +void rb_gc_force_recycle(VALUE obj) { rb_objspace_t *objspace = &rb_objspace; @@ -7997,7 +7993,7 @@ rb_gc_force_recycle(VALUE obj) } else { #endif - if (is_old || !GET_HEAP_PAGE(obj)->flags.before_sweep) { + if (is_old || GET_HEAP_PAGE(obj)->flags.before_sweep) { CLEAR_IN_BITMAP(GET_HEAP_MARK_BITS(obj), obj); } CLEAR_IN_BITMAP(GET_HEAP_MARKING_BITS(obj), obj); @@ -8218,7 +8214,7 @@ gc_start(rb_objspace_t *objspace, int reason) objspace->flags.immediate_sweep = !!((unsigned)reason & GPR_FLAG_IMMEDIATE_SWEEP); /* Explicitly enable compaction (GC.compact) */ - objspace->flags.during_compacting = (!!((unsigned)reason & GPR_FLAG_COMPACT) << 1); + objspace->flags.during_compacting = !!(reason & GPR_FLAG_COMPACT); if (!heap_allocated_pages) return FALSE; /* heap is not ready */ if (!(reason & GPR_FLAG_METHOD) && !ready_to_gc(objspace)) return TRUE; /* GC is not allowed */ @@ -8457,6 +8453,9 @@ gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_ RB_VM_LOCK_ENTER_LEV(lock_lev); switch (event) { + case gc_enter_event_rest: + if (!is_marking(objspace)) break; + // fall through case gc_enter_event_start: case gc_enter_event_mark_continue: // stop other ractors @@ -9451,11 +9450,7 @@ heap_check_moved_i(void *vstart, void *vend, size_t stride, void *data) static VALUE gc_compact(rb_execution_context_t *ec, VALUE self) { - /* Clear the heap. */ - gc_start_internal(ec, self, Qtrue, Qtrue, Qtrue, Qfalse); - - /* At this point, all references are live and the mutator is not allowed - * to run, so we don't need a read barrier. */ + /* Run GC with compaction enabled */ gc_start_internal(ec, self, Qtrue, Qtrue, Qtrue, Qtrue); return gc_compact_stats(ec, self); @@ -11181,9 +11176,20 @@ wmap_allocate(VALUE klass) static int wmap_live_p(rb_objspace_t *objspace, VALUE obj) { - if (!FL_ABLE(obj)) return TRUE; - if (!is_id_value(objspace, obj)) return FALSE; - if (!is_live_object(objspace, obj)) return FALSE; + if (SPECIAL_CONST_P(obj)) return TRUE; + if (is_pointer_to_heap(objspace, (void *)obj)) { + void *poisoned = asan_unpoison_object_temporary(obj); + + enum ruby_value_type t = BUILTIN_TYPE(obj); + int ret = (!(t == T_NONE || t >= T_FIXNUM || t == T_ICLASS) && + is_live_object(objspace, obj)); + + if (poisoned) { + asan_poison_object(obj); + } + + return ret; + } return TRUE; } @@ -11249,10 +11255,26 @@ struct wmap_iter_arg { VALUE value; }; +static VALUE +wmap_inspect_append(rb_objspace_t *objspace, VALUE str, VALUE obj) +{ + if (SPECIAL_CONST_P(obj)) { + return rb_str_append(str, rb_inspect(obj)); + } + else if (wmap_live_p(objspace, obj)) { + return rb_str_append(str, rb_any_to_s(obj)); + } + else { + return rb_str_catf(str, "#<collected:%p>", (void*)obj); + } +} + static int wmap_inspect_i(st_data_t key, st_data_t val, st_data_t arg) { - VALUE str = (VALUE)arg; + struct wmap_iter_arg *argp = (struct wmap_iter_arg *)arg; + rb_objspace_t *objspace = argp->objspace; + VALUE str = argp->value; VALUE k = (VALUE)key, v = (VALUE)val; if (RSTRING_PTR(str)[0] == '#') { @@ -11262,11 +11284,9 @@ wmap_inspect_i(st_data_t key, st_data_t val, st_data_t arg) rb_str_cat2(str, ": "); RSTRING_PTR(str)[0] = '#'; } - k = SPECIAL_CONST_P(k) ? rb_inspect(k) : rb_any_to_s(k); - rb_str_append(str, k); + wmap_inspect_append(objspace, str, k); rb_str_cat2(str, " => "); - v = SPECIAL_CONST_P(v) ? rb_inspect(v) : rb_any_to_s(v); - rb_str_append(str, v); + wmap_inspect_append(objspace, str, v); return ST_CONTINUE; } @@ -11277,11 +11297,14 @@ wmap_inspect(VALUE self) VALUE str; VALUE c = rb_class_name(CLASS_OF(self)); struct weakmap *w; + struct wmap_iter_arg args; TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); str = rb_sprintf("-<%"PRIsVALUE":%p", c, (void *)self); if (w->wmap2obj) { - st_foreach(w->wmap2obj, wmap_inspect_i, str); + args.objspace = &rb_objspace; + args.value = str; + st_foreach(w->wmap2obj, wmap_inspect_i, (st_data_t)&args); } RSTRING_PTR(str)[0] = '#'; rb_str_cat2(str, ">"); diff --git a/gems/bundled_gems b/gems/bundled_gems index f8fe130552..445d876df8 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -1,9 +1,9 @@ # gem-name version-to-bundle repository-url [optional-commit-hash-to-test-or-defaults-to-v-version] minitest 5.14.2 https://github.com/seattlerb/minitest -power_assert 1.2.0 https://github.com/ruby/power_assert +power_assert 1.2.1 https://github.com/ruby/power_assert rake 13.0.3 https://github.com/ruby/rake test-unit 3.3.7 https://github.com/test-unit/test-unit 3.3.7 -rexml 3.2.4 https://github.com/ruby/rexml +rexml 3.2.5 https://github.com/ruby/rexml rss 0.2.9 https://github.com/ruby/rss 0.2.9 -rbs 1.0.4 https://github.com/ruby/rbs -typeprof 0.12.0 https://github.com/ruby/typeprof +typeprof 0.15.2 https://github.com/ruby/typeprof +rbs 1.4.0 https://github.com/ruby/rbs @@ -2476,6 +2476,7 @@ static int delete_if_i(VALUE key, VALUE value, VALUE hash) { if (RTEST(rb_yield_values(2, key, value))) { + rb_hash_modify(hash); return ST_DELETE; } return ST_CONTINUE; @@ -2753,6 +2754,7 @@ static int keep_if_i(VALUE key, VALUE value, VALUE hash) { if (!RTEST(rb_yield_values(2, key, value))) { + rb_hash_modify(hash); return ST_DELETE; } return ST_CONTINUE; @@ -3249,7 +3251,7 @@ rb_hash_transform_keys(int argc, VALUE *argv, VALUE hash) return result; } -static VALUE rb_hash_flatten(int argc, VALUE *argv, VALUE hash); +static int flatten_i(VALUE key, VALUE val, VALUE ary); /* * call-seq: @@ -3278,8 +3280,9 @@ rb_hash_transform_keys_bang(int argc, VALUE *argv, VALUE hash) rb_hash_modify_check(hash); if (!RHASH_TABLE_EMPTY_P(hash)) { long i; - VALUE pairs = rb_hash_flatten(0, NULL, hash); - rb_hash_clear(hash); + VALUE new_keys = hash_alloc(0); + VALUE pairs = rb_ary_tmp_new(RHASH_SIZE(hash) * 2); + rb_hash_foreach(hash, flatten_i, pairs); for (i = 0; i < RARRAY_LEN(pairs); i += 2) { VALUE key = RARRAY_AREF(pairs, i), new_key, val; @@ -3296,8 +3299,14 @@ rb_hash_transform_keys_bang(int argc, VALUE *argv, VALUE hash) new_key = key; } val = RARRAY_AREF(pairs, i+1); + if (!hash_stlike_lookup(new_keys, key, NULL)) { + rb_hash_stlike_delete(hash, &key, NULL); + } rb_hash_aset(hash, new_key, val); + rb_hash_aset(new_keys, new_key, Qnil); } + rb_ary_clear(pairs); + rb_hash_clear(new_keys); } return hash; } @@ -3313,6 +3322,7 @@ transform_values_foreach_replace(st_data_t *key, st_data_t *value, st_data_t arg { VALUE new_value = rb_yield((VALUE)*value); VALUE hash = (VALUE)argp; + rb_hash_modify(hash); RB_OBJ_WRITE(hash, value, new_value); return ST_CONTINUE; } diff --git a/include/ruby/internal/intern/select/posix.h b/include/ruby/internal/intern/select/posix.h index 6c1092b39d..0bf68ae204 100644 --- a/include/ruby/internal/intern/select/posix.h +++ b/include/ruby/internal/intern/select/posix.h @@ -55,7 +55,7 @@ rb_fd_copy(rb_fdset_t *dst, const fd_set *src, int n) } static inline void -rb_fd_dup(rb_fdset_t *dst, const fd_set *src, int n) +rb_fd_dup(rb_fdset_t *dst, const fd_set *src) { *dst = *src; } diff --git a/include/ruby/internal/memory.h b/include/ruby/internal/memory.h index 7d24df4945..46dfcadd10 100644 --- a/include/ruby/internal/memory.h +++ b/include/ruby/internal/memory.h @@ -110,18 +110,18 @@ extern void *alloca(); ((var) = RBIMPL_CAST((type *)ruby_xrealloc2((void *)(var), (n), sizeof(type)))) #define ALLOCA_N(type,n) \ - RBIMPL_CAST((type *)alloca(rbimpl_size_mul_or_raise(sizeof(type), (n)))) + RBIMPL_CAST((type *)(!(n) ? NULL : alloca(rbimpl_size_mul_or_raise(sizeof(type), (n))))) /* allocates _n_ bytes temporary buffer and stores VALUE including it * in _v_. _n_ may be evaluated twice. */ #define RB_ALLOCV(v, n) \ ((n) < RUBY_ALLOCV_LIMIT ? \ - ((v) = 0, alloca(n)) : \ + ((v) = 0, !(n) ? NULL : alloca(n)) : \ rb_alloc_tmp_buffer(&(v), (n))) #define RB_ALLOCV_N(type, v, n) \ RBIMPL_CAST((type *) \ (((size_t)(n) < RUBY_ALLOCV_LIMIT / sizeof(type)) ? \ - ((v) = 0, alloca((n) * sizeof(type))) : \ + ((v) = 0, !(n) ? NULL : alloca((n) * sizeof(type))) : \ rb_alloc_tmp_buffer2(&(v), (n), sizeof(type)))) #define RB_ALLOCV_END(v) rb_free_tmp_buffer(&(v)) @@ -256,6 +256,7 @@ rb_alloc_tmp_buffer2(volatile VALUE *store, long count, size_t elsize) } #ifndef __MINGW32__ +RBIMPL_SYMBOL_EXPORT_BEGIN() RBIMPL_ATTR_NOALIAS() RBIMPL_ATTR_NONNULL((1)) RBIMPL_ATTR_RETURNS_NONNULL() @@ -272,6 +273,7 @@ ruby_nonempty_memcpy(void *dest, const void *src, size_t n) return dest; } } +RBIMPL_SYMBOL_EXPORT_END() #undef memcpy #define memcpy ruby_nonempty_memcpy #endif diff --git a/internal/bits.h b/internal/bits.h index a7ddcaeb78..746947bfc2 100644 --- a/internal/bits.h +++ b/internal/bits.h @@ -284,7 +284,7 @@ nlz_int64(uint64_t x) } else { /* :FIXME: Is there a way to make this branch a compile-time error? */ - __builtin_unreachable(); + UNREACHABLE_RETURN(~0); } #else @@ -419,7 +419,7 @@ rb_popcount64(uint64_t x) } else { /* :FIXME: Is there a way to make this branch a compile-time error? */ - __builtin_unreachable(); + UNREACHABLE_RETURN(~0); } #else @@ -492,7 +492,7 @@ ntz_int64(uint64_t x) } else { /* :FIXME: Is there a way to make this branch a compile-time error? */ - __builtin_unreachable(); + UNREACHABLE_RETURN(~0); } #else diff --git a/internal/complex.h b/internal/complex.h index 6f63a9ebeb..9eae804ddb 100644 --- a/internal/complex.h +++ b/internal/complex.h @@ -25,5 +25,6 @@ struct RComplex { /* complex.c */ VALUE rb_dbl_complex_new_polar_pi(double abs, double ang); +st_index_t rb_complex_hash(VALUE comp); #endif /* INTERNAL_COMPLEX_H */ diff --git a/internal/cont.h b/internal/cont.h index a365cbe978..9e49dd3c8e 100644 --- a/internal/cont.h +++ b/internal/cont.h @@ -21,5 +21,6 @@ void ruby_register_rollback_func_for_ensure(VALUE (*ensure_func)(VALUE), VALUE ( void rb_fiber_init_mjit_cont(struct rb_fiber_struct *fiber); VALUE rb_fiberptr_self(struct rb_fiber_struct *fiber); +unsigned int rb_fiberptr_blocking(struct rb_fiber_struct *fiber); #endif /* INTERNAL_CONT_H */ diff --git a/internal/encoding.h b/internal/encoding.h index af236daeaf..c0cf061bd4 100644 --- a/internal/encoding.h +++ b/internal/encoding.h @@ -12,12 +12,15 @@ #include "ruby/ruby.h" /* for ID */ #include "ruby/encoding.h" /* for rb_encoding */ +#define rb_enc_autoload_p(enc) (!rb_enc_mbmaxlen(enc)) + /* encoding.c */ ID rb_id_encoding(void); rb_encoding *rb_enc_get_from_index(int index); rb_encoding *rb_enc_check_str(VALUE str1, VALUE str2); int rb_encdb_replicate(const char *alias, const char *orig); int rb_encdb_alias(const char *alias, const char *orig); +int rb_enc_autoload(rb_encoding *enc); int rb_encdb_dummy(const char *name); void rb_encdb_declare(const char *name); void rb_enc_set_base(const char *name, const char *orig); diff --git a/internal/gc.h b/internal/gc.h index a602f0c9b3..d439f6d8f6 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -61,6 +61,11 @@ struct rb_objspace; /* in vm_core.h */ rb_obj_write((VALUE)(a), UNALIGNED_MEMBER_ACCESS((VALUE *)(slot)), \ (VALUE)(b), __FILE__, __LINE__) +typedef struct ractor_newobj_cache { + struct RVALUE *freelist; + struct heap_page *using_page; +} rb_ractor_newobj_cache_t; + /* gc.c */ extern VALUE *ruby_initial_gc_stress_ptr; extern int ruby_disable_gc; @@ -100,6 +105,7 @@ void rb_gc_mark_vm_stack_values(long n, const VALUE *values); void *ruby_sized_xrealloc(void *ptr, size_t new_size, size_t old_size) RUBY_ATTR_RETURNS_NONNULL RUBY_ATTR_ALLOC_SIZE((2)); void *ruby_sized_xrealloc2(void *ptr, size_t new_count, size_t element_size, size_t old_count) RUBY_ATTR_RETURNS_NONNULL RUBY_ATTR_ALLOC_SIZE((2, 3)); void ruby_sized_xfree(void *x, size_t size); +void rb_gc_ractor_newobj_cache_clear(rb_ractor_newobj_cache_t *newobj_cache); RUBY_SYMBOL_EXPORT_END MJIT_SYMBOL_EXPORT_BEGIN diff --git a/internal/rational.h b/internal/rational.h index e53ee7b499..6bbd2a9810 100644 --- a/internal/rational.h +++ b/internal/rational.h @@ -33,6 +33,7 @@ VALUE rb_rational_div(VALUE self, VALUE other); VALUE rb_lcm(VALUE x, VALUE y); VALUE rb_rational_reciprocal(VALUE x); VALUE rb_cstr_to_rat(const char *, int); +VALUE rb_rational_hash(VALUE self); VALUE rb_rational_abs(VALUE self); VALUE rb_rational_cmp(VALUE self, VALUE other); VALUE rb_rational_pow(VALUE self, VALUE other); diff --git a/internal/string.h b/internal/string.h index 8907a1a6e6..adc8385cb6 100644 --- a/internal/string.h +++ b/internal/string.h @@ -43,6 +43,7 @@ size_t rb_str_memsize(VALUE); char *rb_str_to_cstr(VALUE str); const char *ruby_escaped_char(int c); void rb_str_make_independent(VALUE str); +int rb_enc_str_coderange_scan(VALUE str, rb_encoding *enc); static inline bool STR_EMBED_P(VALUE str); static inline bool STR_SHARED_P(VALUE str); diff --git a/internal/vm.h b/internal/vm.h index 874eb2d881..689b4fa61f 100644 --- a/internal/vm.h +++ b/internal/vm.h @@ -80,12 +80,7 @@ VALUE rb_yield_force_blockarg(VALUE values); VALUE rb_lambda_call(VALUE obj, ID mid, int argc, const VALUE *argv, rb_block_call_func_t bl_proc, int min_argc, int max_argc, VALUE data2); - -MJIT_SYMBOL_EXPORT_BEGIN -VALUE rb_vm_call0(struct rb_execution_context_struct *ec, VALUE recv, ID id, int argc, const VALUE *argv, const struct rb_callable_method_entry_struct *me, int kw_splat); -VALUE rb_vm_call_kw(struct rb_execution_context_struct *ec, VALUE recv, VALUE id, int argc, const VALUE *argv, const struct rb_callable_method_entry_struct *me, int kw_splat); -VALUE rb_make_no_method_exception(VALUE exc, VALUE format, VALUE obj, int argc, const VALUE *argv, int priv); -MJIT_SYMBOL_EXPORT_END +void rb_check_stack_overflow(void); /* vm_insnhelper.c */ VALUE rb_equal_opt(VALUE obj1, VALUE obj2); @@ -1306,71 +1306,77 @@ rb_io_from_fd(int fd) int rb_io_wait_readable(int f) { - VALUE scheduler = rb_scheduler_current(); - if (scheduler != Qnil) { - return RTEST( - rb_scheduler_io_wait_readable(scheduler, rb_io_from_fd(f)) - ); - } + VALUE scheduler; io_fd_check_closed(f); + + scheduler = rb_scheduler_current(); switch (errno) { case EINTR: #if defined(ERESTART) case ERESTART: #endif - rb_thread_check_ints(); - return TRUE; + rb_thread_check_ints(); + return TRUE; case EAGAIN: #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif - rb_thread_wait_fd(f); - return TRUE; + if (scheduler != Qnil) { + return RTEST( + rb_scheduler_io_wait_readable(scheduler, rb_io_from_fd(f)) + ); + } else { + rb_thread_wait_fd(f); + } + return TRUE; default: - return FALSE; + return FALSE; } } int rb_io_wait_writable(int f) { - VALUE scheduler = rb_scheduler_current(); - if (scheduler != Qnil) { - return RTEST( - rb_scheduler_io_wait_writable(scheduler, rb_io_from_fd(f)) - ); - } + VALUE scheduler; io_fd_check_closed(f); + + scheduler = rb_scheduler_current(); switch (errno) { case EINTR: #if defined(ERESTART) case ERESTART: #endif - /* - * In old Linux, several special files under /proc and /sys don't handle - * select properly. Thus we need avoid to call if don't use O_NONBLOCK. - * Otherwise, we face nasty hang up. Sigh. - * e.g. http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=31b07093c44a7a442394d44423e21d783f5523b8 - * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=31b07093c44a7a442394d44423e21d783f5523b8 - * In EINTR case, we only need to call RUBY_VM_CHECK_INTS_BLOCKING(). - * Then rb_thread_check_ints() is enough. - */ - rb_thread_check_ints(); - return TRUE; + /* + * In old Linux, several special files under /proc and /sys don't handle + * select properly. Thus we need avoid to call if don't use O_NONBLOCK. + * Otherwise, we face nasty hang up. Sigh. + * e.g. http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=31b07093c44a7a442394d44423e21d783f5523b8 + * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=31b07093c44a7a442394d44423e21d783f5523b8 + * In EINTR case, we only need to call RUBY_VM_CHECK_INTS_BLOCKING(). + * Then rb_thread_check_ints() is enough. + */ + rb_thread_check_ints(); + return TRUE; case EAGAIN: #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif - rb_thread_fd_writable(f); - return TRUE; + if (scheduler != Qnil) { + return RTEST( + rb_scheduler_io_wait_writable(scheduler, rb_io_from_fd(f)) + ); + } else { + rb_thread_fd_writable(f); + } + return TRUE; default: - return FALSE; + return FALSE; } } @@ -2976,8 +2982,10 @@ io_getpartial(int argc, VALUE *argv, VALUE io, int no_exception, int nonblock) GetOpenFile(io, fptr); rb_io_check_byte_readable(fptr); - if (len == 0) + if (len == 0) { + io_set_read_length(str, 0, shrinkable); return str; + } if (!nonblock) READ_CHECK(fptr); @@ -3120,8 +3128,10 @@ io_read_nonblock(rb_execution_context_t *ec, VALUE io, VALUE length, VALUE str, GetOpenFile(io, fptr); rb_io_check_byte_readable(fptr); - if (len == 0) + if (len == 0) { + io_set_read_length(str, 0, shrinkable); return str; + } n = read_buffered_data(RSTRING_PTR(str), len, fptr); if (n <= 0) { @@ -3988,9 +3998,9 @@ rb_io_each_byte(VALUE io) char *p = fptr->rbuf.ptr + fptr->rbuf.off++; fptr->rbuf.len--; rb_yield(INT2FIX(*p & 0xff)); + rb_io_check_byte_readable(fptr); errno = 0; } - rb_io_check_byte_readable(fptr); READ_CHECK(fptr); } while (io_fillbuf(fptr) >= 0); return io; @@ -4209,6 +4219,7 @@ rb_io_each_codepoint(VALUE io) fptr->cbuf.off += n; fptr->cbuf.len -= n; rb_yield(UINT2NUM(c)); + rb_io_check_byte_readable(fptr); } } NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr); @@ -4246,6 +4257,7 @@ rb_io_each_codepoint(VALUE io) else { continue; } + rb_io_check_byte_readable(fptr); } return io; @@ -11238,6 +11250,13 @@ nogvl_fcopyfile(struct copy_stream_struct *stp) return 0; if (lseek(stp->dst_fd, 0, SEEK_CUR) > (off_t)0) /* if dst IO was already written */ return 0; + if (fcntl(stp->dst_fd, F_GETFL) & O_APPEND) { + /* fcopyfile(3) appends src IO to dst IO and then truncates + * dst IO to src IO's original size. */ + off_t end = lseek(stp->dst_fd, 0, SEEK_END); + lseek(stp->dst_fd, 0, SEEK_SET); + if (end > (off_t)0) return 0; + } if (src_offset > (off_t)0) { off_t r; @@ -359,8 +359,12 @@ rb_iseq_mark(const rb_iseq_t *iseq) rb_gc_mark_movable((VALUE)ci); } if (cc && vm_cc_markable(cc)) { - rb_gc_mark_movable((VALUE)cc); - // TODO: check enable + if (!vm_cc_invalidated_p(cc)) { + rb_gc_mark_movable((VALUE)cc); + } + else { + cds[i].cc = rb_vm_empty_cc(); + } } } } diff --git a/lib/bundler.rb b/lib/bundler.rb index d370d8a53a..1ab87ac299 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -37,12 +37,13 @@ module Bundler environment_preserver = EnvironmentPreserver.from_env ORIGINAL_ENV = environment_preserver.restore environment_preserver.replace_with_backup - SUDO_MUTEX = Mutex.new + SUDO_MUTEX = Thread::Mutex.new autoload :Definition, File.expand_path("bundler/definition", __dir__) autoload :Dependency, File.expand_path("bundler/dependency", __dir__) autoload :DepProxy, File.expand_path("bundler/dep_proxy", __dir__) autoload :Deprecate, File.expand_path("bundler/deprecate", __dir__) + autoload :Digest, File.expand_path("bundler/digest", __dir__) autoload :Dsl, File.expand_path("bundler/dsl", __dir__) autoload :EndpointSpecification, File.expand_path("bundler/endpoint_specification", __dir__) autoload :Env, File.expand_path("bundler/env", __dir__) @@ -69,6 +70,7 @@ module Bundler autoload :SharedHelpers, File.expand_path("bundler/shared_helpers", __dir__) autoload :Source, File.expand_path("bundler/source", __dir__) autoload :SourceList, File.expand_path("bundler/source_list", __dir__) + autoload :SourceMap, File.expand_path("bundler/source_map", __dir__) autoload :SpecSet, File.expand_path("bundler/spec_set", __dir__) autoload :StubSpecification, File.expand_path("bundler/stub_specification", __dir__) autoload :UI, File.expand_path("bundler/ui", __dir__) @@ -197,7 +199,7 @@ module Bundler def frozen_bundle? frozen = settings[:deployment] - frozen ||= settings[:frozen] unless feature_flag.deployment_means_frozen? + frozen ||= settings[:frozen] frozen end @@ -235,8 +237,9 @@ module Bundler end if warning - user_home = tmp_home_path(warning) - Bundler.ui.warn "#{warning}\nBundler will use `#{user_home}' as your home directory temporarily.\n" + Bundler.ui.warn "#{warning}\n" + user_home = tmp_home_path + Bundler.ui.warn "Bundler will use `#{user_home}' as your home directory temporarily.\n" user_home else Pathname.new(home) @@ -634,6 +637,12 @@ EOF @rubygems = nil end + def configure_gem_home_and_path(path = bundle_path) + configure_gem_path + configure_gem_home(path) + Bundler.rubygems.clear_paths + end + private def eval_yaml_gemspec(path, contents) @@ -651,47 +660,29 @@ EOF rescue ScriptError, StandardError => e msg = "There was an error while loading `#{path.basename}`: #{e.message}" - if e.is_a?(LoadError) - msg += "\nDoes it try to require a relative path? That's been removed in Ruby 1.9" - end - raise GemspecError, Dsl::DSLError.new(msg, path, e.backtrace, contents) end - def configure_gem_home_and_path - configure_gem_path - configure_gem_home - bundle_path - end - - def configure_gem_path(env = ENV) - blank_home = env["GEM_HOME"].nil? || env["GEM_HOME"].empty? - if !use_system_gems? + def configure_gem_path + unless use_system_gems? # this needs to be empty string to cause # PathSupport.split_gem_path to only load up the # Bundler --path setting as the GEM_PATH. - env["GEM_PATH"] = "" - elsif blank_home - possibles = [Bundler.rubygems.gem_dir, Bundler.rubygems.gem_path] - paths = possibles.flatten.compact.uniq.reject(&:empty?) - env["GEM_PATH"] = paths.join(File::PATH_SEPARATOR) + Bundler::SharedHelpers.set_env "GEM_PATH", "" end end - def configure_gem_home - Bundler::SharedHelpers.set_env "GEM_HOME", File.expand_path(bundle_path, root) - Bundler.rubygems.clear_paths + def configure_gem_home(path) + Bundler::SharedHelpers.set_env "GEM_HOME", path.to_s end - def tmp_home_path(warning) + def tmp_home_path Kernel.send(:require, "tmpdir") SharedHelpers.filesystem_access(Dir.tmpdir) do path = Bundler.tmp at_exit { Bundler.rm_rf(path) } path end - rescue RuntimeError => e - raise e.exception("#{warning}\nBundler also failed to create a temporary home directory':\n#{e}") end # @param env [Hash] diff --git a/lib/bundler/bundler.gemspec b/lib/bundler/bundler.gemspec index 48410e76f4..38c533b0c1 100644 --- a/lib/bundler/bundler.gemspec +++ b/lib/bundler/bundler.gemspec @@ -34,9 +34,10 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 2.3.0" s.required_rubygems_version = ">= 2.5.2" - s.files = (Dir.glob("lib/bundler/**/*", File::FNM_DOTMATCH) + Dir.glob("man/bundle*") + Dir.glob("man/gemfile*") + Dir.glob("libexec/bundle*")).reject {|f| File.directory?(f) } + s.files = Dir.glob("lib/bundler{.rb,/**/*}", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } - s.files += ["lib/bundler.rb"] + # include the gemspec itself because warbler breaks w/o it + s.files += %w[lib/bundler/bundler.gemspec] s.bindir = "libexec" s.executables = %w[bundle bundler] diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 421f42cb52..d271086b25 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -14,6 +14,7 @@ module Bundler COMMAND_ALIASES = { "check" => "c", "install" => "i", + "plugin" => "", "list" => "ls", "exec" => ["e", "ex", "exe"], "cache" => ["package", "pack"], @@ -72,14 +73,6 @@ module Bundler Bundler.ui = UI::Shell.new(options) Bundler.ui.level = "debug" if options["verbose"] unprinted_warnings.each {|w| Bundler.ui.warn(w) } - - if ENV["RUBYGEMS_GEMDEPS"] && !ENV["RUBYGEMS_GEMDEPS"].empty? - Bundler.ui.warn( - "The RUBYGEMS_GEMDEPS environment variable is set. This enables RubyGems' " \ - "experimental Gemfile mode, which may conflict with Bundler and cause unexpected errors. " \ - "To remove this warning, unset RUBYGEMS_GEMDEPS.", :wrap => true - ) - end end check_unknown_options!(:except => [:config, :exec]) @@ -191,6 +184,7 @@ module Bundler method_option "install", :type => :boolean, :banner => "Runs 'bundle install' after removing the gems from the Gemfile" def remove(*gems) + SharedHelpers.major_deprecation(2, "The `--install` flag has been deprecated. `bundle install` is triggered by default.") if ARGV.include?("--install") require_relative "cli/remove" Remove.new(gems, options).run end @@ -308,39 +302,19 @@ module Bundler end end - unless Bundler.feature_flag.bundler_3_mode? - desc "show GEM [OPTIONS]", "Shows all gems that are part of the bundle, or the path to a given gem" - long_desc <<-D - Show lists the names and versions of all gems that are required by your Gemfile. - Calling show with [GEM] will list the exact location of that gem on your machine. - D - method_option "paths", :type => :boolean, - :banner => "List the paths of all gems that are required by your Gemfile." - method_option "outdated", :type => :boolean, - :banner => "Show verbose output including whether gems are outdated." - def show(gem_name = nil) - if ARGV[0] == "show" - rest = ARGV[1..-1] - - if flag = rest.find{|arg| ["--verbose", "--outdated"].include?(arg) } - Bundler::SharedHelpers.major_deprecation(2, "the `#{flag}` flag to `bundle show` was undocumented and will be removed without replacement") - else - new_command = rest.find {|arg| !arg.start_with?("--") } ? "info" : "list" - - new_arguments = rest.map do |arg| - next arg if arg != "--paths" - next "--path" if new_command == "info" - end - - old_argv = ARGV.join(" ") - new_argv = [new_command, *new_arguments.compact].join(" ") - - Bundler::SharedHelpers.major_deprecation(2, "use `bundle #{new_argv}` instead of `bundle #{old_argv}`") - end - end - require_relative "cli/show" - Show.new(options, gem_name).run - end + desc "show GEM [OPTIONS]", "Shows all gems that are part of the bundle, or the path to a given gem" + long_desc <<-D + Show lists the names and versions of all gems that are required by your Gemfile. + Calling show with [GEM] will list the exact location of that gem on your machine. + D + method_option "paths", :type => :boolean, + :banner => "List the paths of all gems that are required by your Gemfile." + method_option "outdated", :type => :boolean, + :banner => "Show verbose output including whether gems are outdated." + def show(gem_name = nil) + SharedHelpers.major_deprecation(2, "the `--outdated` flag to `bundle show` was undocumented and will be removed without replacement") if ARGV.include?("--outdated") + require_relative "cli/show" + Show.new(options, gem_name).run end desc "list", "List all gems in the bundle" @@ -357,6 +331,7 @@ module Bundler desc "info GEM [OPTIONS]", "Show information for the given gem" method_option "path", :type => :boolean, :banner => "Print full path to gem" + method_option "version", :type => :boolean, :banner => "Print gem version" def info(gem_name) require_relative "cli/info" Info.new(options, gem_name).run @@ -475,6 +450,12 @@ module Bundler "do in future versions. Instead please use `bundle config set cache_all true`, " \ "and stop using this flag" if ARGV.include?("--all") + SharedHelpers.major_deprecation 2, + "The `--path` flag is deprecated because its semantics are unclear. " \ + "Use `bundle config cache_path` to configure the path of your cache of gems, " \ + "and `bundle config path` to configure the path where your gems are installed, " \ + "and stop using this flag" if ARGV.include?("--path") + require_relative "cli/cache" Cache.new(options).run end @@ -482,7 +463,7 @@ module Bundler map aliases_for("cache") desc "exec [OPTIONS]", "Run the command in context of the bundle" - method_option :keep_file_descriptors, :type => :boolean, :default => false + method_option :keep_file_descriptors, :type => :boolean, :default => true method_option :gemfile, :type => :string, :required => false long_desc <<-D Exec runs a command, providing it access to the gems in the bundle. While using @@ -490,6 +471,10 @@ module Bundler into the system wide RubyGems repository. D def exec(*args) + if ARGV.include?("--no-keep-file-descriptors") + SharedHelpers.major_deprecation(2, "The `--no-keep-file-descriptors` has been deprecated. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to") + end + require_relative "cli/exec" Exec.new(options, args).run end @@ -504,8 +489,8 @@ module Bundler By default, setting a configuration value sets it for all projects on the machine. - If a global setting is superceded by local configuration, this command - will show the current value, as well as any superceded values and + If a global setting is superseded by local configuration, this command + will show the current value, as well as any superseded values and where they were specified. D require_relative "cli/config" @@ -568,7 +553,7 @@ module Bundler method_option :version, :type => :boolean, :default => false, :aliases => "-v", :desc => "Set to show each gem version." method_option :without, :type => :array, :default => [], :aliases => "-W", :banner => "GROUP[ GROUP...]", :desc => "Exclude gems that are part of the specified named group." def viz - SharedHelpers.major_deprecation 2, "The `viz` command has been moved to the `bundle-viz` gem, see https://github.com/bundler/bundler-viz" + SharedHelpers.major_deprecation 2, "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph" require_relative "cli/viz" Viz.new(options.dup).run end @@ -591,6 +576,9 @@ module Bundler :desc => "Generate a test directory for your library, either rspec, minitest or test-unit. Set a default with `bundle config set --global gem.test (rspec|minitest|test-unit)`." method_option :ci, :type => :string, :lazy_default => Bundler.settings["gem.ci"] || "", :desc => "Generate CI configuration, either GitHub Actions, Travis CI, GitLab CI or CircleCI. Set a default with `bundle config set --global gem.ci (github|travis|gitlab|circle)`" + method_option :linter, :type => :string, :lazy_default => Bundler.settings["gem.linter"] || "", + :desc => "Add a linter and code formatter, either RuboCop or Standard. Set a default with `bundle config set --global gem.linter (rubocop|standard)`" + method_option :github_username, :type => :string, :default => Bundler.settings["gem.github_username"], :banner => "Set your username on GitHub", :desc => "Fill in GitHub username on README so that you don't have to do it manually. Set a default with `bundle config set --global gem.github_username <your_username>`." def gem(name) end diff --git a/lib/bundler/cli/cache.rb b/lib/bundler/cli/cache.rb index 9cd6133879..c8698ed7e3 100644 --- a/lib/bundler/cli/cache.rb +++ b/lib/bundler/cli/cache.rb @@ -9,7 +9,7 @@ module Bundler end def run - Bundler.ui.level = "error" if options[:quiet] + Bundler.ui.level = "warn" if options[:quiet] Bundler.settings.set_command_option_if_given :path, options[:path] Bundler.settings.set_command_option_if_given :cache_path, options["cache-path"] diff --git a/lib/bundler/cli/check.rb b/lib/bundler/cli/check.rb index 19c0aaea06..65c51337d2 100644 --- a/lib/bundler/cli/check.rb +++ b/lib/bundler/cli/check.rb @@ -11,9 +11,11 @@ module Bundler def run Bundler.settings.set_command_option_if_given :path, options[:path] + definition = Bundler.definition + definition.validate_runtime! + begin - definition = Bundler.definition - definition.validate_runtime! + definition.resolve_only_locally! not_installed = definition.missing_specs rescue GemNotFound, VersionConflict Bundler.ui.error "Bundler can't satisfy your Gemfile's dependencies." diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb index 23ac78a103..ba259143b7 100644 --- a/lib/bundler/cli/common.rb +++ b/lib/bundler/cli/common.rb @@ -36,10 +36,15 @@ module Bundler def self.without_groups_message(command) command_in_past_tense = command == :install ? "installed" : "updated" groups = Bundler.settings[:without] + "Gems in the #{verbalize_groups(groups)} were not #{command_in_past_tense}." + end + + def self.verbalize_groups(groups) + groups.map!{|g| "'#{g}'" } group_list = [groups[0...-1].join(", "), groups[-1..-1]]. reject {|s| s.to_s.empty? }.join(" and ") group_str = groups.size == 1 ? "group" : "groups" - "Gems in the #{group_str} #{group_list} were not #{command_in_past_tense}." + "#{group_str} #{group_list}" end def self.select_spec(name, regex_match = nil) @@ -53,7 +58,13 @@ module Bundler case specs.count when 0 - raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies) + dep_in_other_group = Bundler.definition.current_dependencies.find {|dep|dep.name == name } + + if dep_in_other_group + raise GemNotFound, "Could not find gem '#{name}', because it's in the #{verbalize_groups(dep_in_other_group.groups)}, configured to be ignored." + else + raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies) + end when 1 specs.first else @@ -83,6 +94,8 @@ module Bundler end def self.ensure_all_gems_in_lockfile!(names, locked_gems = Bundler.locked_gems) + return unless locked_gems + locked_names = locked_gems.specs.map(&:name).uniq names.-(locked_names).each do |g| raise GemNotFound, gem_not_found_message(g, locked_names) diff --git a/lib/bundler/cli/doctor.rb b/lib/bundler/cli/doctor.rb index 2986ddbc99..43f1ca92e2 100644 --- a/lib/bundler/cli/doctor.rb +++ b/lib/bundler/cli/doctor.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "rbconfig" +require "shellwords" module Bundler class CLI::Doctor @@ -22,14 +23,14 @@ module Bundler end def dylibs_darwin(path) - output = `/usr/bin/otool -L "#{path}"`.chomp + output = `/usr/bin/otool -L #{path.shellescape}`.chomp dylibs = output.split("\n")[1..-1].map {|l| l.match(DARWIN_REGEX).captures[0] }.uniq # ignore @rpath and friends dylibs.reject {|dylib| dylib.start_with? "@" } end def dylibs_ldd(path) - output = `/usr/bin/ldd "#{path}"`.chomp + output = `/usr/bin/ldd #{path.shellescape}`.chomp output.split("\n").map do |l| match = l.match(LDD_REGEX) next if match.nil? @@ -61,7 +62,7 @@ module Bundler end def run - Bundler.ui.level = "error" if options[:quiet] + Bundler.ui.level = "warn" if options[:quiet] Bundler.settings.validate! check! @@ -100,8 +101,11 @@ module Bundler files_not_readable_or_writable = [] files_not_rw_and_owned_by_different_user = [] files_not_owned_by_current_user_but_still_rw = [] + broken_symlinks = [] Find.find(Bundler.bundle_path.to_s).each do |f| - if !File.writable?(f) || !File.readable?(f) + if !File.exist?(f) + broken_symlinks << f + elsif !File.writable?(f) || !File.readable?(f) if File.stat(f).uid != Process.uid files_not_rw_and_owned_by_different_user << f else @@ -113,6 +117,13 @@ module Bundler end ok = true + + if broken_symlinks.any? + Bundler.ui.warn "Broken links exist in the Bundler home. Please report them to the offending gem's upstream repo. These files are:\n - #{broken_symlinks.join("\n - ")}" + + ok = false + end + if files_not_owned_by_current_user_but_still_rw.any? Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \ "user, but are still readable/writable. These files are:\n - #{files_not_owned_by_current_user_but_still_rw.join("\n - ")}" diff --git a/lib/bundler/cli/exec.rb b/lib/bundler/cli/exec.rb index 318d57fb06..42b602a055 100644 --- a/lib/bundler/cli/exec.rb +++ b/lib/bundler/cli/exec.rb @@ -12,12 +12,7 @@ module Bundler @options = options @cmd = args.shift @args = args - - if !Bundler.current_ruby.jruby? - @args << { :close_others => !options.keep_file_descriptors? } - elsif options.keep_file_descriptors? - Bundler.ui.warn "Ruby version #{RUBY_VERSION} defaults to keeping non-standard file descriptors on Kernel#exec." - end + @args << { :close_others => !options.keep_file_descriptors? } unless Bundler.current_ruby.jruby? end def run diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 0d773579e5..2a74325fde 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -39,11 +39,19 @@ module Bundler constant_name = name.gsub(/-[_-]*(?![_-]|$)/) { "::" }.gsub(/([_-]+|(::)|^)(.|$)/) { $2.to_s + $3.upcase } constant_array = constant_name.split("::") - git_installed = Bundler.git_present? + use_git = Bundler.git_present? && options[:git] - git_author_name = git_installed ? `git config user.name`.chomp : "" - github_username = git_installed ? `git config github.user`.chomp : "" - git_user_email = git_installed ? `git config user.email`.chomp : "" + git_author_name = use_git ? `git config user.name`.chomp : "" + git_username = use_git ? `git config github.user`.chomp : "" + git_user_email = use_git ? `git config user.email`.chomp : "" + + github_username = if options[:github_username].nil? + git_username + elsif options[:github_username] == false + "" + else + options[:github_username] + end config = { :name => name, @@ -58,8 +66,9 @@ module Bundler :ext => options[:ext], :exe => options[:exe], :bundler_version => bundler_dependency_version, + :git => use_git, :github_username => github_username.empty? ? "[USERNAME]" : github_username, - :required_ruby_version => Gem.ruby_version < Gem::Version.new("2.4.a") ? "2.3.0" : "2.4.0", + :required_ruby_version => required_ruby_version, } ensure_safe_gem_name(name, constant_array) @@ -67,6 +76,7 @@ module Bundler "#{Bundler.preferred_gemfile_name}.tt" => Bundler.preferred_gemfile_name, "lib/newgem.rb.tt" => "lib/#{namespaced_path}.rb", "lib/newgem/version.rb.tt" => "lib/#{namespaced_path}/version.rb", + "sig/newgem.rbs.tt" => "sig/#{namespaced_path}.rbs", "newgem.gemspec.tt" => "#{name}.gemspec", "Rakefile.tt" => "Rakefile", "README.md.tt" => "README.md", @@ -79,7 +89,7 @@ module Bundler bin/setup ] - templates.merge!("gitignore.tt" => ".gitignore") if Bundler.git_present? + templates.merge!("gitignore.tt" => ".gitignore") if use_git if test_framework = ask_and_set_test_framework config[:test] = test_framework @@ -154,15 +164,16 @@ module Bundler templates.merge!("CHANGELOG.md.tt" => "CHANGELOG.md") end - if ask_and_set(:rubocop, "Do you want to add rubocop as a dependency for gems you generate?", - "RuboCop is a static code analyzer that has out-of-the-box rules for many " \ - "of the guidelines in the community style guide. " \ - "For more information, see the RuboCop docs (https://docs.rubocop.org/en/stable/) " \ - "and the Ruby Style Guides (https://github.com/rubocop-hq/ruby-style-guide).") - config[:rubocop] = true - config[:rubocop_version] = Gem.ruby_version < Gem::Version.new("2.4.a") ? "0.81.0" : "1.7" + config[:linter] = ask_and_set_linter + case config[:linter] + when "rubocop" + config[:linter_version] = rubocop_version Bundler.ui.info "RuboCop enabled in config" templates.merge!("rubocop.yml.tt" => ".rubocop.yml") + when "standard" + config[:linter_version] = standard_version + Bundler.ui.info "Standard enabled in config" + templates.merge!("standard.yml.tt" => ".standard.yml") end templates.merge!("exe/newgem.tt" => "exe/#{name}") if config[:exe] @@ -175,24 +186,32 @@ module Bundler ) end + if target.exist? && !target.directory? + Bundler.ui.error "Couldn't create a new gem named `#{gem_name}` because there's an existing file named `#{gem_name}`." + exit Bundler::BundlerError.all_errors[Bundler::GenericSystemCallError] + end + + if use_git + Bundler.ui.info "Initializing git repo in #{target}" + require "shellwords" + `git init #{target.to_s.shellescape}` + + config[:git_default_branch] = File.read("#{target}/.git/HEAD").split("/").last.chomp + end + templates.each do |src, dst| destination = target.join(dst) - SharedHelpers.filesystem_access(destination) do - thor.template("newgem/#{src}", destination, config) - end + thor.template("newgem/#{src}", destination, config) end executables.each do |file| - SharedHelpers.filesystem_access(target.join(file)) do |path| - executable = (path.stat.mode | 0o111) - path.chmod(executable) - end + path = target.join(file) + executable = (path.stat.mode | 0o111) + path.chmod(executable) end - if Bundler.git_present? && options[:git] - Bundler.ui.info "Initializing git repo in #{target}" + if use_git Dir.chdir(target) do - `git init` `git add .` end end @@ -202,8 +221,6 @@ module Bundler Bundler.ui.info "Gem '#{name}' was successfully created. " \ "For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html" - rescue Errno::EEXIST => e - raise GenericSystemCallError.new(e, "There was a conflict while creating the new gem.") end private @@ -302,6 +319,58 @@ module Bundler ci_template end + def ask_and_set_linter + linter_template = options[:linter] || Bundler.settings["gem.linter"] + linter_template = deprecated_rubocop_option if linter_template.nil? + + if linter_template.to_s.empty? + Bundler.ui.confirm "Do you want to add a code linter and formatter to your gem? " \ + "Supported Linters:\n" \ + "* RuboCop: https://rubocop.org\n" \ + "* Standard: https://github.com/testdouble/standard\n" \ + "\n" + Bundler.ui.info hint_text("linter") + + result = Bundler.ui.ask "Enter a linter. rubocop/standard/(none):" + if result =~ /rubocop|standard/ + linter_template = result + else + linter_template = false + end + end + + if Bundler.settings["gem.linter"].nil? + Bundler.settings.set_global("gem.linter", linter_template) + end + + # Once gem.linter safely set, unset the deprecated gem.rubocop + unless Bundler.settings["gem.rubocop"].nil? + Bundler.settings.set_global("gem.rubocop", nil) + end + + if options[:linter] == Bundler.settings["gem.linter"] + Bundler.ui.info "#{options[:linter]} is already configured, ignoring --linter flag." + end + + linter_template + end + + def deprecated_rubocop_option + if !options[:rubocop].nil? + if options[:rubocop] + Bundler::SharedHelpers.major_deprecation 2, "--rubocop is deprecated, use --linter=rubocop" + "rubocop" + else + Bundler::SharedHelpers.major_deprecation 2, "--no-rubocop is deprecated, use --linter" + false + end + elsif !Bundler.settings["gem.rubocop"].nil? + Bundler::SharedHelpers.major_deprecation 2, + "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead" + Bundler.settings["gem.rubocop"] ? "rubocop" : false + end + end + def bundler_dependency_version v = Gem::Version.new(Bundler::VERSION) req = v.segments[0..1] @@ -335,5 +404,30 @@ module Bundler def open_editor(editor, file) thor.run(%(#{editor} "#{file}")) end + + def required_ruby_version + if Gem.ruby_version < Gem::Version.new("2.4.a") then "2.3.0" + elsif Gem.ruby_version < Gem::Version.new("2.5.a") then "2.4.0" + elsif Gem.ruby_version < Gem::Version.new("2.6.a") then "2.5.0" + else + "2.6.0" + end + end + + def rubocop_version + if Gem.ruby_version < Gem::Version.new("2.4.a") then "0.81.0" + elsif Gem.ruby_version < Gem::Version.new("2.5.a") then "1.12" + else + "1.21" + end + end + + def standard_version + if Gem.ruby_version < Gem::Version.new("2.4.a") then "0.2.5" + elsif Gem.ruby_version < Gem::Version.new("2.5.a") then "1.0" + else + "1.3" + end + end end end diff --git a/lib/bundler/cli/info.rb b/lib/bundler/cli/info.rb index 3111b64a33..76c8cf60c0 100644 --- a/lib/bundler/cli/info.rb +++ b/lib/bundler/cli/info.rb @@ -18,6 +18,7 @@ module Bundler if spec return print_gem_path(spec) if @options[:path] + return print_gem_version(spec) if @options[:version] print_gem_info(spec) end end @@ -39,13 +40,18 @@ module Bundler raise GemNotFound, Bundler::CLI::Common.gem_not_found_message(gem_name, Bundler.definition.dependencies) end + def print_gem_version(spec) + Bundler.ui.info spec.version.to_s + end + def print_gem_path(spec) - if spec.name == "bundler" + name = spec.name + if name == "bundler" path = File.expand_path("../../../..", __FILE__) else path = spec.full_gem_path - unless File.directory?(path) - return Bundler.ui.warn "The gem #{gem_name} has been deleted. It was installed at: #{path}" + if spec.deleted_gem? + return Bundler.ui.warn "The gem #{name} has been deleted. It was installed at: #{path}" end end @@ -54,8 +60,9 @@ module Bundler def print_gem_info(spec) metadata = spec.metadata + name = spec.name gem_info = String.new - gem_info << " * #{spec.name} (#{spec.version}#{spec.git_version})\n" + gem_info << " * #{name} (#{spec.version}#{spec.git_version})\n" gem_info << "\tSummary: #{spec.summary}\n" if spec.summary gem_info << "\tHomepage: #{spec.homepage}\n" if spec.homepage gem_info << "\tDocumentation: #{metadata["documentation_uri"]}\n" if metadata.key?("documentation_uri") @@ -67,6 +74,11 @@ module Bundler gem_info << "\tMailing List: #{metadata["mailing_list_uri"]}\n" if metadata.key?("mailing_list_uri") gem_info << "\tPath: #{spec.full_gem_path}\n" gem_info << "\tDefault Gem: yes" if spec.respond_to?(:default_gem?) && spec.default_gem? + + if name != "bundler" && spec.deleted_gem? + return Bundler.ui.warn "The gem #{name} has been deleted. Gemspec information is still available though:\n#{gem_info}" + end + Bundler.ui.info gem_info end end diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index c702eb14d1..4c1915fea6 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -8,7 +8,7 @@ module Bundler end def run - Bundler.ui.level = "error" if options[:quiet] + Bundler.ui.level = "warn" if options[:quiet] warn_if_root @@ -33,12 +33,8 @@ module Bundler options[:local] = true if Bundler.app_cache.exist? - if Bundler.feature_flag.deployment_means_frozen? - Bundler.settings.set_command_option :deployment, true - else - Bundler.settings.set_command_option :deployment, true if options[:deployment] - Bundler.settings.set_command_option :frozen, true if options[:frozen] - end + Bundler.settings.set_command_option :deployment, true if options[:deployment] + Bundler.settings.set_command_option :frozen, true if options[:frozen] end # When install is called with --no-deployment, disable deployment mode @@ -62,7 +58,10 @@ module Bundler definition.validate_runtime! installer = Installer.install(Bundler.root, definition, options) - Bundler.load.cache if Bundler.app_cache.exist? && !options["no-cache"] && !Bundler.frozen_bundle? + + Bundler.settings.temporary(:cache_all_platforms => options[:local] ? false : Bundler.settings[:cache_all_platforms]) do + Bundler.load.cache(nil, options[:local]) if Bundler.app_cache.exist? && !options["no-cache"] && !Bundler.frozen_bundle? + end Bundler.ui.confirm "Bundle complete! #{dependencies_count_for(definition)}, #{gems_installed_for(definition)}." Bundler::CLI::Common.output_without_groups_message(:install) @@ -84,28 +83,15 @@ module Bundler end Bundler::CLI::Common.output_fund_metadata_summary - rescue GemNotFound, VersionConflict => e - if options[:local] && Bundler.app_cache.exist? - Bundler.ui.warn "Some gems seem to be missing from your #{Bundler.settings.app_cache_path} directory." - end - - unless Bundler.definition.has_rubygems_remotes? - Bundler.ui.warn <<-WARN, :wrap => true - Your Gemfile has no gem server sources. If you need gems that are \ - not already on your machine, add a line like this to your Gemfile: - source 'https://rubygems.org' - WARN - end - raise e - rescue Gem::InvalidSpecificationException => e + rescue Gem::InvalidSpecificationException Bundler.ui.warn "You have one or more invalid gemspecs that need to be fixed." - raise e + raise end private def warn_if_root - return if Bundler.settings[:silence_root_warning] || Bundler::WINDOWS || !Process.uid.zero? + return if Bundler.settings[:silence_root_warning] || Gem.win_platform? || !Process.uid.zero? Bundler.ui.warn "Don't run Bundler as root. Bundler can ask for sudo " \ "if it is needed, and installing your bundle as root will break this " \ "application for all non-root users on this machine.", :wrap => true diff --git a/lib/bundler/cli/issue.rb b/lib/bundler/cli/issue.rb index f4cd5ac4df..b891ecb1d2 100644 --- a/lib/bundler/cli/issue.rb +++ b/lib/bundler/cli/issue.rb @@ -20,9 +20,10 @@ module Bundler Hopefully the troubleshooting steps above resolved your problem! If things still aren't working the way you expect them to, please let us know so - that we can diagnose and help fix the problem you're having. Please - view the Filing Issues guide for more information: - https://github.com/rubygems/rubygems/blob/master/bundler/doc/contributing/ISSUES.md + that we can diagnose and help fix the problem you're having, by filling + in the new issue form located at + https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md, + and copy and pasting the information below. EOS diff --git a/lib/bundler/cli/list.rb b/lib/bundler/cli/list.rb index 66abd32650..f56bf5b86a 100644 --- a/lib/bundler/cli/list.rb +++ b/lib/bundler/cli/list.rb @@ -16,7 +16,13 @@ module Bundler specs = if @only_group.any? || @without_group.any? filtered_specs_by_groups else - Bundler.load.specs + begin + Bundler.load.specs + rescue GemNotFound => e + Bundler.ui.error e.message + Bundler.ui.warn "Install missing gems with `bundle install`." + exit 1 + end end.reject {|s| s.name == "bundler" }.sort_by(&:name) return Bundler.ui.info "No gems in the Gemfile" if specs.empty? diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb index 7dd078b1ef..7d613a6644 100644 --- a/lib/bundler/cli/lock.rb +++ b/lib/bundler/cli/lock.rb @@ -21,9 +21,13 @@ module Bundler Bundler::Fetcher.disable_endpoint = options["full-index"] update = options[:update] + conservative = options[:conservative] + if update.is_a?(Array) # unlocking specific gems Bundler::CLI::Common.ensure_all_gems_in_lockfile!(update) - update = { :gems => update, :lock_shared_dependencies => options[:conservative] } + update = { :gems => update, :conservative => conservative } + elsif update + update = { :conservative => conservative } if conservative end definition = Bundler.definition(update) diff --git a/lib/bundler/cli/open.rb b/lib/bundler/cli/open.rb index df32e2f38b..ea504344f3 100644 --- a/lib/bundler/cli/open.rb +++ b/lib/bundler/cli/open.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "shellwords" - module Bundler class CLI::Open attr_reader :options, :name @@ -19,6 +17,7 @@ module Bundler else path = spec.full_gem_path Dir.chdir(path) do + require "shellwords" command = Shellwords.split(editor) + [path] Bundler.with_original_env do system(*command) diff --git a/lib/bundler/cli/outdated.rb b/lib/bundler/cli/outdated.rb index 6a1789e235..d5183b060b 100644 --- a/lib/bundler/cli/outdated.rb +++ b/lib/bundler/cli/outdated.rb @@ -72,7 +72,7 @@ module Bundler gemfile_specs + dependency_specs end - specs.sort_by(&:name).each do |current_spec| + specs.sort_by(&:name).uniq(&:name).each do |current_spec| next unless gems.empty? || gems.include?(current_spec.name) active_spec = retrieve_active_spec(definition, current_spec) @@ -146,17 +146,16 @@ module Bundler end def retrieve_active_spec(definition, current_spec) - if strict - active_spec = definition.find_resolved_spec(current_spec) - else - active_specs = definition.find_indexed_specs(current_spec) - if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1 - active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? } - end - active_spec = active_specs.last - end + active_spec = definition.resolve.find_by_name_and_platform(current_spec.name, current_spec.platform) + return unless active_spec - active_spec + return active_spec if strict + + active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version) + if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1 + active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? } + end + active_specs.last end def print_gems(gems_list) diff --git a/lib/bundler/cli/remove.rb b/lib/bundler/cli/remove.rb index cd6a2cec28..44a4d891dd 100644 --- a/lib/bundler/cli/remove.rb +++ b/lib/bundler/cli/remove.rb @@ -11,8 +11,7 @@ module Bundler raise InvalidOption, "Please specify gems to remove." if @gems.empty? Injector.remove(@gems, {}) - - Installer.install(Bundler.root, Bundler.definition) if @options["install"] + Installer.install(Bundler.root, Bundler.definition) end end end diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb index 94699484d4..95a8886ea5 100644 --- a/lib/bundler/cli/update.rb +++ b/lib/bundler/cli/update.rb @@ -9,7 +9,7 @@ module Bundler end def run - Bundler.ui.level = "error" if options[:quiet] + Bundler.ui.level = "warn" if options[:quiet] Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins? @@ -27,9 +27,14 @@ module Bundler raise InvalidOption, "Cannot specify --all along with specific options." end + conservative = options[:conservative] + if full_update - # We're doing a full update - Bundler.definition(true) + if conservative + Bundler.definition(:conservative => conservative) + else + Bundler.definition(true) + end else unless Bundler.default_lockfile.exist? raise GemfileLockNotFound, "This Bundle hasn't been installed yet. " \ @@ -43,7 +48,7 @@ module Bundler end Bundler.definition(:gems => gems, :sources => sources, :ruby => options[:ruby], - :lock_shared_dependencies => options[:conservative], + :conservative => conservative, :bundler => options[:bundler]) end @@ -61,7 +66,7 @@ module Bundler if locked_gems = Bundler.definition.locked_gems previous_locked_info = locked_gems.specs.reduce({}) do |h, s| - h[s.name] = { :spec => s, :version => s.version, :source => s.source.to_s } + h[s.name] = { :spec => s, :version => s.version, :source => s.source.identifier } h end end @@ -90,7 +95,7 @@ module Bundler end locked_source = locked_info[:source] - new_source = new_spec.source.to_s + new_source = new_spec.source.identifier next if locked_source != new_source new_version = new_spec.version diff --git a/lib/bundler/compact_index_client.rb b/lib/bundler/compact_index_client.rb index cf67f0e7a0..d5dbeb3b10 100644 --- a/lib/bundler/compact_index_client.rb +++ b/lib/bundler/compact_index_client.rb @@ -5,7 +5,7 @@ require "set" module Bundler class CompactIndexClient - DEBUG_MUTEX = Mutex.new + DEBUG_MUTEX = Thread::Mutex.new def self.debug return unless ENV["DEBUG_COMPACT_INDEX"] DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") } @@ -25,7 +25,7 @@ module Bundler @endpoints = Set.new @info_checksums_by_name = {} @parsed_checksums = false - @mutex = Mutex.new + @mutex = Thread::Mutex.new end def execution_mode=(block) diff --git a/lib/bundler/compact_index_client/updater.rb b/lib/bundler/compact_index_client/updater.rb index 7959e5c089..d9b9cec0d4 100644 --- a/lib/bundler/compact_index_client/updater.rb +++ b/lib/bundler/compact_index_client/updater.rb @@ -50,16 +50,20 @@ module Bundler content = response.body - SharedHelpers.filesystem_access(local_temp_path) do + etag = (response["ETag"] || "").gsub(%r{\AW/}, "") + correct_response = SharedHelpers.filesystem_access(local_temp_path) do if response.is_a?(Net::HTTPPartialContent) && local_temp_path.size.nonzero? local_temp_path.open("a") {|f| f << slice_body(content, 1..-1) } + + etag_for(local_temp_path) == etag else - local_temp_path.open("w") {|f| f << content } + local_temp_path.open("wb") {|f| f << content } + + etag.length.zero? || etag_for(local_temp_path) == etag end end - etag = (response["ETag"] || "").gsub(%r{\AW/}, "") - if etag.length.zero? || etag_for(local_temp_path) == etag + if correct_response SharedHelpers.filesystem_access(local_path) do FileUtils.mv(local_temp_path, local_path) end @@ -72,11 +76,6 @@ module Bundler update(local_path, remote_path, :retrying) end - rescue Errno::EACCES - raise Bundler::PermissionError, - "Bundler does not have write access to create a temp directory " \ - "within #{Dir.tmpdir}. Bundler must have write access to your " \ - "systems temp directory to function properly. " rescue Zlib::GzipFile::Error raise Bundler::HTTPError end @@ -92,11 +91,11 @@ module Bundler def checksum_for_file(path) return nil unless path.file? - # This must use IO.read instead of Digest.file().hexdigest + # This must use File.read instead of Digest.file().hexdigest # because we need to preserve \n line endings on windows when calculating # the checksum SharedHelpers.filesystem_access(path, :read) do - SharedHelpers.digest(:MD5).hexdigest(IO.read(path)) + SharedHelpers.digest(:MD5).hexdigest(File.read(path)) end end end diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb index c132e8ecc0..f84d68e262 100644 --- a/lib/bundler/current_ruby.rb +++ b/lib/bundler/current_ruby.rb @@ -20,6 +20,7 @@ module Bundler 2.5 2.6 2.7 + 3.0 ].freeze KNOWN_MAJOR_VERSIONS = KNOWN_MINOR_VERSIONS.map {|v| v.split(".", 2).first }.uniq.freeze @@ -64,19 +65,19 @@ module Bundler end def mswin? - Bundler::WINDOWS + Gem.win_platform? end def mswin64? - Bundler::WINDOWS && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mswin64" && Bundler.local_platform.cpu == "x64" + Gem.win_platform? && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mswin64" && Bundler.local_platform.cpu == "x64" end def mingw? - Bundler::WINDOWS && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu != "x64" + Gem.win_platform? && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu != "x64" end def x64_mingw? - Bundler::WINDOWS && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu == "x64" + Gem.win_platform? && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu == "x64" end (KNOWN_MINOR_VERSIONS + KNOWN_MAJOR_VERSIONS).each do |version| diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 3c25149d33..584f379256 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -56,10 +56,8 @@ module Bundler @unlocking_bundler = false @unlocking = unlock else - unlock = unlock.dup @unlocking_bundler = unlock.delete(:bundler) - unlock.delete_if {|_k, v| Array(v).empty? } - @unlocking = !unlock.empty? + @unlocking = unlock.any? {|_k, v| !Array(v).empty? } end @dependencies = dependencies @@ -75,7 +73,6 @@ module Bundler @lockfile_contents = String.new @locked_bundler_version = nil @locked_ruby_version = nil - @locked_specs_incomplete_for_platform = false @new_platform = nil if lockfile && File.exist?(lockfile) @@ -106,7 +103,19 @@ module Bundler @locked_platforms = [] end - @unlock[:gems] ||= [] + locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) } + @multisource_allowed = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes? && Bundler.frozen_bundle? + + if @multisource_allowed + unless sources.aggregate_global_source? + msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." + + Bundler::SharedHelpers.major_deprecation 2, msg + end + + @sources.merged_gem_lockfile_sections!(locked_gem_sources.first) + end + @unlock[:sources] ||= [] @unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object @ruby_version.diff(locked_ruby_version_object) @@ -119,14 +128,18 @@ module Bundler @path_changes = converge_paths @source_changes = converge_sources - unless @unlock[:lock_shared_dependencies] - eager_unlock = expand_dependencies(@unlock[:gems], true) - @unlock[:gems] = @locked_specs.for(eager_unlock, [], false, false, false).map(&:name) + if @unlock[:conservative] + @unlock[:gems] ||= @dependencies.map(&:name) + else + eager_unlock = expand_dependencies(@unlock[:gems] || [], true) + @unlock[:gems] = @locked_specs.for(eager_unlock, false, false).map(&:name) end @dependency_changes = converge_dependencies @local_changes = converge_locals + @locked_specs_incomplete_for_platform = !@locked_specs.for(requested_dependencies & expand_dependencies(locked_dependencies), true, true) + @requires = compute_requires end @@ -145,17 +158,21 @@ module Bundler end end + def resolve_only_locally! + @remote = false + sources.local_only! + resolve + end + def resolve_with_cache! - raise "Specs already loaded" if @specs sources.cached! - specs + resolve end def resolve_remotely! - return if @specs @remote = true sources.remote! - specs + resolve end # For given dependency list returns a SpecSet with Gemspec of all the required @@ -165,25 +182,7 @@ module Bundler # # @return [Bundler::SpecSet] def specs - @specs ||= begin - begin - specs = resolve.materialize(requested_dependencies) - rescue GemNotFound => e # Handle yanked gem - gem_name, gem_version = extract_gem_info(e) - locked_gem = @locked_specs[gem_name].last - raise if locked_gem.nil? || locked_gem.version.to_s != gem_version || !@remote - raise GemNotFound, "Your bundle is locked to #{locked_gem}, but that version could not " \ - "be found in any of the sources listed in your Gemfile. If you haven't changed sources, " \ - "that means the author of #{locked_gem} has removed it. You'll need to update your bundle " \ - "to a version other than #{locked_gem} that hasn't been removed in order to install." - end - unless specs["bundler"].any? - bundler = sources.metadata_source.specs.search(Gem::Dependency.new("bundler", VERSION)).last - specs["bundler"] = bundler - end - - specs - end + @specs ||= materialize(requested_dependencies) end def new_specs @@ -195,9 +194,7 @@ module Bundler end def missing_specs - missing = [] - resolve.materialize(requested_dependencies, missing) - missing + resolve.materialize(requested_dependencies).missing_specs end def missing_specs? @@ -206,7 +203,6 @@ module Bundler Bundler.ui.debug "The definition is missing #{missing.map(&:full_name)}" true rescue BundlerError => e - @index = nil @resolve = nil @specs = nil @gem_version_promoter = nil @@ -216,17 +212,11 @@ module Bundler end def requested_specs - @requested_specs ||= begin - groups = requested_groups - groups.map!(&:to_sym) - specs_for(groups) - end + specs_for(requested_groups) end def requested_dependencies - groups = requested_groups - groups.map!(&:to_sym) - dependencies_for(groups) + dependencies_for(requested_groups) end def current_dependencies @@ -235,15 +225,22 @@ module Bundler end end + def locked_dependencies + @locked_deps.values + end + def specs_for(groups) + return specs if groups.empty? deps = dependencies_for(groups) - specs.for(expand_dependencies(deps)) + materialize(deps) end def dependencies_for(groups) - current_dependencies.reject do |d| + groups.map!(&:to_sym) + deps = current_dependencies.reject do |d| (d.groups & groups).empty? end + expand_dependencies(deps) end # Resolve all the dependencies specified in Gemfile. It ensures that @@ -264,59 +261,11 @@ module Bundler # Run a resolve against the locally available gems Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}") expanded_dependencies = expand_dependencies(dependencies + metadata_dependencies, @remote) - Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms) + Resolver.resolve(expanded_dependencies, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms) end end end - def index - @index ||= Index.build do |idx| - dependency_names = @dependencies.map(&:name) - - sources.all_sources.each do |source| - source.dependency_names = dependency_names - pinned_spec_names(source) - idx.add_source source.specs - dependency_names.concat(source.unmet_deps).uniq! - end - - double_check_for_index(idx, dependency_names) - end - end - - # Suppose the gem Foo depends on the gem Bar. Foo exists in Source A. Bar has some versions that exist in both - # sources A and B. At this point, the API request will have found all the versions of Bar in source A, - # but will not have found any versions of Bar from source B, which is a problem if the requested version - # of Foo specifically depends on a version of Bar that is only found in source B. This ensures that for - # each spec we found, we add all possible versions from all sources to the index. - def double_check_for_index(idx, dependency_names) - pinned_names = pinned_spec_names - loop do - idxcount = idx.size - - names = :names # do this so we only have to traverse to get dependency_names from the index once - unmet_dependency_names = lambda do - return names unless names == :names - new_names = sources.all_sources.map(&:dependency_names_to_double_check) - return names = nil if new_names.compact! - names = new_names.flatten(1).concat(dependency_names) - names.uniq! - names -= pinned_names - names - end - - sources.all_sources.each do |source| - source.double_check_for(unmet_dependency_names) - end - - break if idxcount == idx.size - end - end - private :double_check_for_index - - def has_rubygems_remotes? - sources.rubygems_sources.any? {|s| s.remotes.any? } - end - def spec_git_paths sources.git_sources.map {|s| File.realpath(s.path) if File.exist?(s.path) }.compact end @@ -415,44 +364,26 @@ module Bundler added.concat new_platforms.map {|p| "* platform: #{p}" } deleted.concat deleted_platforms.map {|p| "* platform: #{p}" } - gemfile_sources = sources.lock_sources - - new_sources = gemfile_sources - @locked_sources - deleted_sources = @locked_sources - gemfile_sources - - new_deps = @dependencies - @locked_deps.values - deleted_deps = @locked_deps.values - @dependencies - - # Check if it is possible that the source is only changed thing - if (new_deps.empty? && deleted_deps.empty?) && (!new_sources.empty? && !deleted_sources.empty?) - new_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) } - deleted_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) } - end - - if @locked_sources != gemfile_sources - if new_sources.any? - added.concat new_sources.map {|source| "* source: #{source}" } - end - - if deleted_sources.any? - deleted.concat deleted_sources.map {|source| "* source: #{source}" } - end - end + new_deps = @dependencies - locked_dependencies + deleted_deps = locked_dependencies - @dependencies added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any? - if deleted_deps.any? - deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" } - end + deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" } if deleted_deps.any? both_sources = Hash.new {|h, k| h[k] = [] } @dependencies.each {|d| both_sources[d.name][0] = d } - @locked_deps.each {|name, d| both_sources[name][1] = d.source } + locked_dependencies.each {|d| both_sources[d.name][1] = d } + + both_sources.each do |name, (dep, lock_dep)| + next if dep.nil? || lock_dep.nil? + + gemfile_source = dep.source || sources.default_source + lock_source = lock_dep.source || sources.default_source + next if lock_source.include?(gemfile_source) - both_sources.each do |name, (dep, lock_source)| - next if lock_source.nil? || (dep && lock_source.can_lock?(dep)) - gemfile_source_name = (dep && dep.source) || "no specified source" - lockfile_source_name = lock_source - changed << "* #{name} from `#{gemfile_source_name}` to `#{lockfile_source_name}`" + gemfile_source_name = dep.source ? gemfile_source.identifier : "no specified source" + lockfile_source_name = lock_dep.source ? lock_source.identifier : "no specified source" + changed << "* #{name} from `#{lockfile_source_name}` to `#{gemfile_source_name}`" end reason = change_reason @@ -519,14 +450,6 @@ module Bundler end end - def find_resolved_spec(current_spec) - specs.find_by_name_and_platform(current_spec.name, current_spec.platform) - end - - def find_indexed_specs(current_spec) - index[current_spec.name].select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version) - end - attr_reader :sources private :sources @@ -540,6 +463,35 @@ module Bundler private + def materialize(dependencies) + specs = resolve.materialize(dependencies) + missing_specs = specs.missing_specs + + if missing_specs.any? + missing_specs.each do |s| + locked_gem = @locked_specs[s.name].last + next if locked_gem.nil? || locked_gem.version != s.version || !@remote + raise GemNotFound, "Your bundle is locked to #{locked_gem} from #{locked_gem.source}, but that version can " \ + "no longer be found in that source. That means the author of #{locked_gem} has removed it. " \ + "You'll need to update your bundle to a version other than #{locked_gem} that hasn't been " \ + "removed in order to install." + end + + raise GemNotFound, "Could not find #{missing_specs.map(&:full_name).join(", ")} in any of the sources" + end + + unless specs["bundler"].any? + bundler = sources.metadata_source.specs.search(Gem::Dependency.new("bundler", VERSION)).last + specs["bundler"] = bundler + end + + specs + end + + def precompute_source_requirements_for_indirect_dependencies? + @remote && sources.non_global_rubygems_sources.all?(&:dependency_api_available?) && !sources.aggregate_global_source? + end + def current_ruby_platform_locked? return false unless generic_local_platform == Gem::Platform::RUBY @@ -592,9 +544,9 @@ module Bundler def dependencies_for_source_changed?(source, locked_source = source) deps_for_source = @dependencies.select {|s| s.source == source } - locked_deps_for_source = @locked_deps.values.select {|dep| dep.source == locked_source } + locked_deps_for_source = locked_dependencies.select {|dep| dep.source == locked_source } - deps_for_source.sort != locked_deps_for_source.sort + deps_for_source.uniq.sort != locked_deps_for_source.sort end def specs_for_source_changed?(source) @@ -653,36 +605,11 @@ module Bundler end end - def converge_rubygems_sources - return false if Bundler.feature_flag.disable_multisource? - - changes = false - - # Get the RubyGems sources from the Gemfile.lock - locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) } - # Get the RubyGems remotes from the Gemfile - actual_remotes = sources.rubygems_remotes - - # If there is a RubyGems source in both - if !locked_gem_sources.empty? && !actual_remotes.empty? - locked_gem_sources.each do |locked_gem| - # Merge the remotes from the Gemfile into the Gemfile.lock - changes |= locked_gem.replace_remotes(actual_remotes, Bundler.settings[:allow_deployment_source_credential_changes]) - end - end - - changes - end - def converge_sources - changes = false - - changes |= converge_rubygems_sources - # Replace the sources from the Gemfile with the sources from the Gemfile.lock, # if they exist in the Gemfile.lock and are `==`. If you can't find an equivalent # source in the Gemfile.lock, use the one from the Gemfile. - changes |= sources.replace_sources!(@locked_sources) + changes = sources.replace_sources!(@locked_sources) sources.all_sources.each do |source| # If the source is unlockable and the current command allows an unlock of @@ -700,25 +627,14 @@ module Bundler end def converge_dependencies - frozen = Bundler.frozen_bundle? - (@dependencies + @locked_deps.values).each do |dep| - locked_source = @locked_deps[dep.name] - # This is to make sure that if bundler is installing in deployment mode and - # after locked_source and sources don't match, we still use locked_source. - if frozen && !locked_source.nil? && - locked_source.respond_to?(:source) && locked_source.source.instance_of?(Source::Path) && locked_source.source.path.exist? - dep.source = locked_source.source - elsif dep.source + changes = false + + @dependencies.each do |dep| + if dep.source dep.source = sources.get(dep.source) end - end - changes = false - # We want to know if all match, but don't want to check all entries - # This means we need to return false if any dependency doesn't match - # the lock or doesn't exist in the lock. - @dependencies.each do |dependency| - unless locked_dep = @locked_deps[dependency.name] + unless locked_dep = @locked_deps[dep.name] changes = true next end @@ -729,11 +645,11 @@ module Bundler # directive, the lockfile dependencies and resolved dependencies end up # with a mismatch on #type. Work around that by setting the type on the # dep from the lockfile. - locked_dep.instance_variable_set(:@type, dependency.type) + locked_dep.instance_variable_set(:@type, dep.type) # We already know the name matches from the hash lookup # so we only need to check the requirement now - changes ||= dependency.requirement != locked_dep.requirement + changes ||= dep.requirement != locked_dep.requirement end changes @@ -743,47 +659,37 @@ module Bundler # commonly happen if the Gemfile has changed since the lockfile was last # generated def converge_locked_specs - deps = [] - - # Build a list of dependencies that are the same in the Gemfile - # and Gemfile.lock. If the Gemfile modified a dependency, but - # the gem in the Gemfile.lock still satisfies it, this is fine - # too. - @dependencies.each do |dep| - locked_dep = @locked_deps[dep.name] + resolve = converge_specs(@locked_specs) - # If the locked_dep doesn't match the dependency we're looking for then we ignore the locked_dep - locked_dep = nil unless locked_dep == dep + diff = nil - if in_locked_deps?(dep, locked_dep) || satisfies_locked_spec?(dep) - deps << dep - elsif dep.source.is_a?(Source::Path) && dep.current_platform? && (!locked_dep || dep.source != locked_dep.source) - @locked_specs.each do |s| - @unlock[:gems] << s.name if s.source == dep.source - end + # Now, we unlock any sources that do not have anymore gems pinned to it + sources.all_sources.each do |source| + next unless source.respond_to?(:unlock!) - dep.source.unlock! if dep.source.respond_to?(:unlock!) - dep.source.specs.each {|s| @unlock[:gems] << s.name } + unless resolve.any? {|s| s.source == source } + diff ||= @locked_specs.to_a - resolve.to_a + source.unlock! if diff.any? {|s| s.source == source } end end - unlock_source_unlocks_spec = Bundler.feature_flag.unlock_source_unlocks_spec? + resolve + end + def converge_specs(specs) + deps = [] converged = [] - @locked_specs.each do |s| + specs.each do |s| # Replace the locked dependency's source with the equivalent source from the Gemfile dep = @dependencies.find {|d| s.satisfies?(d) } - s.source = (dep && dep.source) || sources.get(s.source) - # Don't add a spec to the list if its source is expired. For example, - # if you change a Git gem to RubyGems. - next if s.source.nil? - next if @unlock[:sources].include?(s.source.name) + if dep && (!dep.source || s.source.include?(dep.source)) + deps << dep + end + + s.source = (dep && dep.source) || sources.get(s.source) || sources.default_source unless Bundler.frozen_bundle? - # XXX This is a backwards-compatibility fix to preserve the ability to - # unlock a single gem by passing its name via `--source`. See issue #3759 - # TODO: delete in Bundler 2 - next if unlock_source_unlocks_spec && @unlock[:sources].include?(s.name) + next if @unlock[:sources].include?(s.source.name) # If the spec is from a path source and it doesn't exist anymore # then we unlock it. @@ -795,8 +701,8 @@ module Bundler rescue PathError, GitError # if we won't need the source (according to the lockfile), # don't error if the path/git source isn't available - next if @locked_specs. - for(requested_dependencies, [], false, true, false). + next if specs. + for(requested_dependencies, false, true). none? {|locked_spec| locked_spec.source == s.source } raise @@ -811,36 +717,15 @@ module Bundler s.dependencies.replace(new_spec.dependencies) end - converged << s - end - - resolve = SpecSet.new(converged) - @locked_specs_incomplete_for_platform = !resolve.for(expand_dependencies(requested_dependencies & deps), @unlock[:gems], true, true) - resolve = resolve.for(expand_dependencies(deps, true), @unlock[:gems], false, false, false) - diff = nil - - # Now, we unlock any sources that do not have anymore gems pinned to it - sources.all_sources.each do |source| - next unless source.respond_to?(:unlock!) - - unless resolve.any? {|s| s.source == source } - diff ||= @locked_specs.to_a - resolve.to_a - source.unlock! if diff.any? {|s| s.source == source } + if dep.nil? && requested_dependencies.find {|d| s.name == d.name } + @unlock[:gems] << s.name + else + converged << s end end - resolve - end - - def in_locked_deps?(dep, locked_dep) - # Because the lockfile can't link a dep to a specific remote, we need to - # treat sources as equivalent anytime the locked dep has all the remotes - # that the Gemfile dep does. - locked_dep && locked_dep.source && dep.source && locked_dep.source.include?(dep.source) - end - - def satisfies_locked_spec?(dep) - @locked_specs[dep].any? {|s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) } + resolve = SpecSet.new(converged) + SpecSet.new(resolve.for(expand_dependencies(deps, true), false, false).reject{|s| @unlock[:gems].include?(s.name) }) end def metadata_dependencies @@ -887,38 +772,22 @@ module Bundler end def source_requirements - # Load all specs from remote sources - index - # Record the specs available in each gem's source, so that those # specs will be available later when the resolver knows where to # look for that gemspec (or its dependencies) - default = sources.default_source - source_requirements = { :default => default } - default = nil unless Bundler.feature_flag.disable_multisource? - dependencies.each do |dep| - next unless source = dep.source || default - source_requirements[dep.name] = source + source_requirements = if precompute_source_requirements_for_indirect_dependencies? + { :default => sources.default_source }.merge(source_map.all_requirements) + else + { :default => Source::RubygemsAggregate.new(sources, source_map) }.merge(source_map.direct_requirements) end metadata_dependencies.each do |dep| source_requirements[dep.name] = sources.metadata_source end - source_requirements[:default_bundler] = source_requirements["bundler"] || source_requirements[:default] + source_requirements[:default_bundler] = source_requirements["bundler"] || sources.default_source source_requirements["bundler"] = sources.metadata_source # needs to come last to override source_requirements end - def pinned_spec_names(skip = nil) - pinned_names = [] - default = Bundler.feature_flag.disable_multisource? && sources.default_source - @dependencies.each do |dep| - next unless dep_source = dep.source || default - next if dep_source == skip - pinned_names << dep.name - end - pinned_names - end - def requested_groups groups - Bundler.settings[:without] - @optional_groups + Bundler.settings[:with] end @@ -936,12 +805,6 @@ module Bundler current == proposed end - def extract_gem_info(error) - # This method will extract the error message like "Could not find foo-1.2.3 in any of the sources" - # to an array. The first element will be the gem name (e.g. foo), the second will be the version number. - error.message.scan(/Could not find (\w+)-(\d+(?:\.\d+)+)/).flatten - end - def compute_requires dependencies.reduce({}) do |requires, dep| next requires unless dep.should_include? @@ -954,24 +817,16 @@ module Bundler end def additional_base_requirements_for_resolve - return [] unless @locked_gems && Bundler.feature_flag.only_update_to_newer_versions? - dependencies_by_name = dependencies.inject({}) {|memo, dep| memo.update(dep.name => dep) } - @locked_gems.specs.reduce({}) do |requirements, locked_spec| + return [] unless @locked_gems && unlocking? && !sources.expired_sources?(@locked_gems.sources) + converge_specs(@locked_gems.specs).map do |locked_spec| name = locked_spec.name - dependency = dependencies_by_name[name] - next requirements unless dependency - next requirements if @locked_gems.dependencies[name] != dependency - next requirements if dependency.source.is_a?(Source::Path) dep = Gem::Dependency.new(name, ">= #{locked_spec.version}") - requirements[name] = DepProxy.get_proxy(dep, locked_spec.platform) - requirements - end.values + DepProxy.get_proxy(dep, locked_spec.platform) + end end - def equivalent_rubygems_remotes?(source) - return false unless source.is_a?(Source::Rubygems) - - Bundler.settings[:allow_deployment_source_credential_changes] && source.equivalent_remotes?(sources.rubygems_remotes) + def source_map + @source_map ||= SourceMap.new(sources, dependencies) end end end diff --git a/lib/bundler/digest.rb b/lib/bundler/digest.rb new file mode 100644 index 0000000000..759f609416 --- /dev/null +++ b/lib/bundler/digest.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +# This code was extracted from https://github.com/Solistra/ruby-digest which is under public domain +module Bundler + module Digest + # The initial constant values for the 32-bit constant words A, B, C, D, and + # E, respectively. + SHA1_WORDS = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0].freeze + + # The 8-bit field used for bitwise `AND` masking. Defaults to `0xFFFFFFFF`. + SHA1_MASK = 0xFFFFFFFF + + class << self + def sha1(string) + unless string.is_a?(String) + raise TypeError, "can't convert #{string.class.inspect} into String" + end + + buffer = string.b + + words = SHA1_WORDS.dup + generate_split_buffer(buffer) do |chunk| + w = [] + chunk.each_slice(4) do |a, b, c, d| + w << (((a << 8 | b) << 8 | c) << 8 | d) + end + a, b, c, d, e = *words + (16..79).each do |i| + w[i] = SHA1_MASK & rotate((w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]), 1) + end + 0.upto(79) do |i| + case i + when 0..19 + f = ((b & c) | (~b & d)) + k = 0x5A827999 + when 20..39 + f = (b ^ c ^ d) + k = 0x6ED9EBA1 + when 40..59 + f = ((b & c) | (b & d) | (c & d)) + k = 0x8F1BBCDC + when 60..79 + f = (b ^ c ^ d) + k = 0xCA62C1D6 + end + t = SHA1_MASK & (SHA1_MASK & rotate(a, 5) + f + e + k + w[i]) + a, b, c, d, e = t, a, SHA1_MASK & rotate(b, 30), c, d # rubocop:disable Style/ParallelAssignment + end + mutated = [a, b, c, d, e] + words.map!.with_index {|word, index| SHA1_MASK & (word + mutated[index]) } + end + + words.pack("N*").unpack("H*").first + end + + private + + def generate_split_buffer(string, &block) + size = string.bytesize * 8 + buffer = string.bytes << 128 + buffer << 0 while buffer.size % 64 != 56 + buffer.concat([size].pack("Q>").bytes) + buffer.each_slice(64, &block) + end + + def rotate(value, spaces) + value << spaces | value >> (32 - spaces) + end + end + end +end diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index 1cc7908b8a..1108fc3b78 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -18,6 +18,8 @@ module Bundler VALID_KEYS = %w[group groups git path glob name branch ref tag require submodules platform platforms type source install_if gemfile].freeze + GITHUB_PULL_REQUEST_URL = %r{\Ahttps://github\.com/([A-Za-z0-9_\-\.]+/[A-Za-z0-9_\-\.]+)/pull/(\d+)\z}.freeze + attr_reader :gemspecs attr_accessor :dependencies @@ -103,8 +105,8 @@ module Bundler if current = @dependencies.find {|d| d.name == dep.name } deleted_dep = @dependencies.delete(current) if current.type == :development - if current.requirement != dep.requirement - unless deleted_dep + unless deleted_dep + if current.requirement != dep.requirement return if dep.type == :development update_prompt = "" @@ -122,17 +124,14 @@ module Bundler raise GemfileError, "You cannot specify the same gem twice with different version requirements.\n" \ "You specified: #{current.name} (#{current.requirement}) and #{dep.name} (#{dep.requirement})" \ "#{update_prompt}" + else + Bundler.ui.warn "Your Gemfile lists the gem #{current.name} (#{current.requirement}) more than once.\n" \ + "You should probably keep only one of them.\n" \ + "Remove any duplicate entries and specify the gem only once.\n" \ + "While it's not a problem now, it could cause errors if you change the version of one of them later." end - else - Bundler.ui.warn "Your Gemfile lists the gem #{current.name} (#{current.requirement}) more than once.\n" \ - "You should probably keep only one of them.\n" \ - "Remove any duplicate entries and specify the gem only once.\n" \ - "While it's not a problem now, it could cause errors if you change the version of one of them later." - end - - if current.source != dep.source - unless deleted_dep + if current.source != dep.source return if dep.type == :development raise GemfileError, "You cannot specify the same gem twice coming from different sources.\n" \ "You specified that #{dep.name} (#{dep.requirement}) should come from " \ @@ -164,8 +163,7 @@ module Bundler elsif block_given? with_source(@sources.add_rubygems_source("remotes" => source), &blk) else - check_primary_source_safety(@sources) - @sources.global_rubygems_source = source + @sources.add_global_rubygems_remote(source) end end @@ -183,24 +181,14 @@ module Bundler end def path(path, options = {}, &blk) - unless block_given? - msg = "You can no longer specify a path source by itself. Instead, \n" \ - "either use the :path option on a gem, or specify the gems that \n" \ - "bundler should find in the path source by passing a block to \n" \ - "the path method, like: \n\n" \ - " path 'dir/containing/rails' do\n" \ - " gem 'rails'\n" \ - " end\n\n" - - raise DeprecatedError, msg if Bundler.feature_flag.disable_multisource? - SharedHelpers.major_deprecation(2, msg.strip) - end - source_options = normalize_hash(options).merge( "path" => Pathname.new(path), "root_path" => gemfile_root, "gemspec" => gemspecs.find {|g| g.name == options["name"] } ) + + source_options["global"] = true unless block_given? + source = @sources.add_path_source(source_options) with_source(source, &blk) end @@ -229,6 +217,7 @@ module Bundler end def to_definition(lockfile, unlock) + check_primary_source_safety Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles) end @@ -279,6 +268,11 @@ module Bundler raise GemfileError, "Undefined local variable or method `#{name}' for Gemfile" end + def check_primary_source_safety + check_path_source_safety + check_rubygems_source_safety + end + private def add_git_sources @@ -286,8 +280,17 @@ module Bundler warn_deprecated_git_source(:github, <<-'RUBY'.strip, 'Change any "reponame" :github sources to "username/reponame".') "https://github.com/#{repo_name}.git" RUBY - repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") - "https://github.com/#{repo_name}.git" + if repo_name =~ GITHUB_PULL_REQUEST_URL + { + "git" => "https://github.com/#{$1}.git", + "branch" => "refs/pull/#{$2}/head", + "ref" => nil, + "tag" => nil, + } + else + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") + "https://github.com/#{repo_name}.git" + end end git_source(:gist) do |repo_name| @@ -373,7 +376,11 @@ repo_name ||= user_name git_name = (git_names & opts.keys).last if @git_sources[git_name] - opts["git"] = @git_sources[git_name].call(opts[git_name]) + git_opts = @git_sources[git_name].call(opts[git_name]) + git_opts = { "git" => git_opts } if git_opts.is_a?(String) + opts.merge!(git_opts) do |key, _gemfile_value, _git_source_value| + raise GemfileError, %(The :#{key} option can't be used with `#{git_name}: #{opts[git_name].inspect}`) + end end %w[git path].each do |type| @@ -440,25 +447,46 @@ repo_name ||= user_name end end - def check_primary_source_safety(source_list) - return if source_list.rubygems_primary_remotes.empty? && source_list.global_rubygems_source.nil? + def check_path_source_safety + return if @sources.global_path_source.nil? + + msg = "You can no longer specify a path source by itself. Instead, \n" \ + "either use the :path option on a gem, or specify the gems that \n" \ + "bundler should find in the path source by passing a block to \n" \ + "the path method, like: \n\n" \ + " path 'dir/containing/rails' do\n" \ + " gem 'rails'\n" \ + " end\n\n" + + SharedHelpers.major_deprecation(2, msg.strip) + end + + def check_rubygems_source_safety + if @sources.implicit_global_source? + implicit_global_source_warning + elsif @sources.aggregate_global_source? + multiple_global_source_warning + end + end + + def implicit_global_source_warning + Bundler::SharedHelpers.major_deprecation 2, "This Gemfile does not include an explicit global source. " \ + "Not using an explicit global source may result in a different lockfile being generated depending on " \ + "the gems you have installed locally before bundler is run. " \ + "Instead, define a global source in your Gemfile like this: source \"https://rubygems.org\"." + end - if Bundler.feature_flag.disable_multisource? + def multiple_global_source_warning + if Bundler.feature_flag.bundler_3_mode? msg = "This Gemfile contains multiple primary sources. " \ "Each source after the first must include a block to indicate which gems " \ "should come from that source" - unless Bundler.feature_flag.bundler_2_mode? - msg += ". To downgrade this error to a warning, run " \ - "`bundle config unset disable_multisource`" - end raise GemfileEvalError, msg else Bundler::SharedHelpers.major_deprecation 2, "Your Gemfile contains multiple primary sources. " \ "Using `source` more than once without a block is a security risk, and " \ "may result in installing unexpected gems. To resolve this warning, use " \ - "a block to indicate which gems should come from the secondary source. " \ - "To upgrade this warning to an error, run `bundle config set --local " \ - "disable_multisource true`." + "a block to indicate which gems should come from the secondary source." end end diff --git a/lib/bundler/environment_preserver.rb b/lib/bundler/environment_preserver.rb index a77f7e0816..0f08e049d8 100644 --- a/lib/bundler/environment_preserver.rb +++ b/lib/bundler/environment_preserver.rb @@ -38,7 +38,10 @@ module Bundler # Replaces `ENV` with the bundler environment variables backed up def replace_with_backup - ENV.replace(backup) unless Gem.win_platform? + unless Gem.win_platform? + ENV.replace(backup) + return + end # Fallback logic for Windows below to workaround # https://bugs.ruby-lang.org/issues/16798. Can be dropped once all diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb index 11763b4e88..9ad7460e58 100644 --- a/lib/bundler/errors.rb +++ b/lib/bundler/errors.rb @@ -75,10 +75,26 @@ module Bundler end end + def permission_type + case @permission_type + when :create + "executable permissions for all parent directories and write permissions for `#{parent_folder}`" + when :delete + permissions = "executable permissions for all parent directories and write permissions for `#{parent_folder}`" + permissions += ", and the same thing for all subdirectories inside #{@path}" if File.directory?(@path) + permissions + else + "#{@permission_type} permissions for that path" + end + end + + def parent_folder + File.dirname(@path) + end + def message "There was an error while trying to #{action} `#{@path}`. " \ - "It is likely that you need to grant #{@permission_type} permissions " \ - "for that path." + "It is likely that you need to grant #{permission_type}." end status_code(23) @@ -122,7 +138,7 @@ module Bundler class VirtualProtocolError < BundlerError def message - "There was an error relating to virtualization and file access." \ + "There was an error relating to virtualization and file access. " \ "It is likely that you need to grant access to or mount some file system correctly." end diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index a1b443b042..e441b941c2 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -31,17 +31,13 @@ module Bundler settings_flag(:auto_clean_without_path) { bundler_3_mode? } settings_flag(:cache_all) { bundler_3_mode? } settings_flag(:default_install_uses_path) { bundler_3_mode? } - settings_flag(:deployment_means_frozen) { bundler_3_mode? } - settings_flag(:disable_multisource) { bundler_3_mode? } settings_flag(:forget_cli_options) { bundler_3_mode? } settings_flag(:global_gem_cache) { bundler_3_mode? } - settings_flag(:only_update_to_newer_versions) { bundler_3_mode? } settings_flag(:path_relative_to_cwd) { bundler_3_mode? } settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") } settings_flag(:print_only_version_number) { bundler_3_mode? } settings_flag(:setup_makes_kernel_gem_public) { !bundler_3_mode? } settings_flag(:suppress_install_using_messages) { bundler_3_mode? } - settings_flag(:unlock_source_unlocks_spec) { !bundler_3_mode? } settings_flag(:update_requires_all_flag) { bundler_4_mode? } settings_flag(:use_gem_version_promoter_for_major_updates) { bundler_3_mode? } diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index 0c81c54740..e3253a942f 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -28,7 +28,8 @@ module Bundler " is a chance you are experiencing a man-in-the-middle attack, but" \ " most likely your system doesn't have the CA certificates needed" \ " for verification. For information about OpenSSL certificates, see" \ - " http://bit.ly/ruby-ssl. To connect without using SSL, edit your Gemfile" \ + " https://railsapps.github.io/openssl-certificate-verify-failed.html." \ + " To connect without using SSL, edit your Gemfile" \ " sources and change 'https' to 'http'." end end @@ -47,7 +48,8 @@ module Bundler remote_uri = filter_uri(remote_uri) super "Authentication is required for #{remote_uri}.\n" \ "Please supply credentials for this source. You can do this by running:\n" \ - " bundle config set --global #{remote_uri} username:password" + "`bundle config set --global #{remote_uri} username:password`\n" \ + "or by storing the credentials in the `#{Settings.key_for(remote_uri)}` environment variable" end end # This error is raised if HTTP authentication is provided, but incorrect. diff --git a/lib/bundler/fetcher/compact_index.rb b/lib/bundler/fetcher/compact_index.rb index 0304155bdd..bc69b884ec 100644 --- a/lib/bundler/fetcher/compact_index.rb +++ b/lib/bundler/fetcher/compact_index.rb @@ -111,7 +111,7 @@ module Bundler def bundle_worker(func = nil) @bundle_worker ||= begin worker_name = "Compact Index (#{display_uri.host})" - Bundler::Worker.new(Bundler.current_ruby.rbx? ? 1 : 25, worker_name, func) + Bundler::Worker.new(Bundler.settings.processor_count, worker_name, func) end @bundle_worker.tap do |worker| worker.instance_variable_set(:@func, func) if func diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb index a2289aaaae..f2aad3a500 100644 --- a/lib/bundler/fetcher/downloader.rb +++ b/lib/bundler/fetcher/downloader.rb @@ -14,8 +14,10 @@ module Bundler def fetch(uri, headers = {}, counter = 0) raise HTTPError, "Too many redirects" if counter >= redirect_limit + filtered_uri = URICredentialsFilter.credential_filtered_uri(uri) + response = request(uri, headers) - Bundler.ui.debug("HTTP #{response.code} #{response.message} #{uri}") + Bundler.ui.debug("HTTP #{response.code} #{response.message} #{filtered_uri}") case response when Net::HTTPSuccess, Net::HTTPNotModified @@ -40,7 +42,7 @@ module Bundler raise BadAuthenticationError, uri.host if uri.userinfo raise AuthenticationRequiredError, uri.host when Net::HTTPNotFound - raise FallbackError, "Net::HTTPNotFound: #{URICredentialsFilter.credential_filtered_uri(uri)}" + raise FallbackError, "Net::HTTPNotFound: #{filtered_uri}" else raise HTTPError, "#{response.class}#{": #{response.body}" unless response.body.empty?}" end @@ -49,7 +51,9 @@ module Bundler def request(uri, headers) validate_uri_scheme!(uri) - Bundler.ui.debug "HTTP GET #{uri}" + filtered_uri = URICredentialsFilter.credential_filtered_uri(uri) + + Bundler.ui.debug "HTTP GET #{filtered_uri}" req = Net::HTTP::Get.new uri.request_uri, headers if uri.user user = CGI.unescape(uri.user) @@ -64,12 +68,11 @@ module Bundler raise CertificateFailureError.new(uri) rescue *HTTP_ERRORS => e Bundler.ui.trace e - case e.message - when /host down:/, /getaddrinfo: nodename nor servname provided/ + if e.is_a?(SocketError) || e.message =~ /host down:/ raise NetworkDownError, "Could not reach host #{uri.host}. Check your network " \ "connection and try again." else - raise HTTPError, "Network error while fetching #{URICredentialsFilter.credential_filtered_uri(uri)}" \ + raise HTTPError, "Network error while fetching #{filtered_uri}" \ " (#{e})" end end diff --git a/lib/bundler/fetcher/index.rb b/lib/bundler/fetcher/index.rb index 08b041897e..0d14c47aa7 100644 --- a/lib/bundler/fetcher/index.rb +++ b/lib/bundler/fetcher/index.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require_relative "base" -require "rubygems/remote_fetcher" module Bundler class Fetcher diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb index 5d0bb905bc..cc615db60c 100644 --- a/lib/bundler/friendly_errors.rb +++ b/lib/bundler/friendly_errors.rb @@ -49,8 +49,6 @@ module Bundler "Alternatively, you can increase the amount of memory the JVM is able to use by running Bundler with jruby -J-Xmx1024m -S bundle (JRuby defaults to 500MB)." else request_issue_report_for(error) end - rescue StandardError - raise error end def exit_status(error) @@ -65,34 +63,6 @@ module Bundler def request_issue_report_for(e) Bundler.ui.error <<-EOS.gsub(/^ {8}/, ""), nil, nil --- ERROR REPORT TEMPLATE ------------------------------------------------------- - # Error Report - - ## Questions - - Please fill out answers to these questions, it'll help us figure out - why things are going wrong. - - - **What did you do?** - - I ran the command `#{$PROGRAM_NAME} #{ARGV.join(" ")}` - - - **What did you expect to happen?** - - I expected Bundler to... - - - **What happened instead?** - - Instead, what happened was... - - - **Have you tried any solutions posted on similar issues in our issue tracker, stack overflow, or google?** - - I tried... - - - **Have you read our issues document, https://github.com/rubygems/rubygems/blob/master/bundler/doc/contributing/ISSUES.md?** - - ... - - ## Backtrace ``` #{e.class}: #{e.message} @@ -111,8 +81,7 @@ module Bundler First, try this link to see if there are any existing issue reports for this error: #{issues_url(e)} - If there aren't any reports for this error yet, please create copy and paste the report template above into a new issue. Don't forget to anonymize any private data! The new issue form is located at: - https://github.com/rubygems/rubygems/issues/new?labels=Bundler + If there aren't any reports for this error yet, please fill in the new issue form located at #{new_issue_url}, and copy and paste the report template above in there. EOS end @@ -123,6 +92,10 @@ module Bundler "https://github.com/rubygems/rubygems/search?q=" \ "#{CGI.escape(message)}&type=Issues" end + + def new_issue_url + "https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md" + end end def self.with_friendly_errors diff --git a/lib/bundler/gem_helper.rb b/lib/bundler/gem_helper.rb index d3e30124f9..034f2e5960 100644 --- a/lib/bundler/gem_helper.rb +++ b/lib/bundler/gem_helper.rb @@ -47,6 +47,11 @@ module Bundler built_gem_path = build_gem end + desc "Generate SHA512 checksum if #{name}-#{version}.gem into the checksums directory." + task "build:checksum" => "build" do + build_checksum(built_gem_path) + end + desc "Build and install #{name}-#{version}.gem into system gems." task "install" => "build" do install_gem(built_gem_path) @@ -71,7 +76,7 @@ module Bundler tag_version { git_push(args[:remote]) } unless already_tagged? end - task "release:rubygem_push" do + task "release:rubygem_push" => "build" do rubygem_push(built_gem_path) if gem_push? end @@ -93,13 +98,21 @@ module Bundler built_gem_path ||= build_gem cmd = [*gem_command, "install", built_gem_path.to_s] cmd << "--local" if local - _, status = sh_with_status(cmd) - unless status.success? - raise "Couldn't install gem, run `gem install #{built_gem_path}' for more detailed output" - end + sh(cmd) Bundler.ui.confirm "#{name} (#{version}) installed." end + def build_checksum(built_gem_path = nil) + built_gem_path ||= build_gem + SharedHelpers.filesystem_access(File.join(base, "checksums")) {|p| FileUtils.mkdir_p(p) } + file_name = "#{File.basename(built_gem_path)}.sha512" + require "digest/sha2" + checksum = ::Digest::SHA512.new.hexdigest(built_gem_path.to_s) + target = File.join(base, "checksums", file_name) + File.write(target, checksum) + Bundler.ui.confirm "#{name} #{version} checksum written to checksums/#{file_name}." + end + protected def rubygem_push(path) @@ -116,8 +129,8 @@ module Bundler def git_push(remote = nil) remote ||= default_remote - perform_git_push "#{remote} refs/heads/#{current_branch}" - perform_git_push "#{remote} refs/tags/#{version_tag}" + sh("git push #{remote} refs/heads/#{current_branch}".shellsplit) + sh("git push #{remote} refs/tags/#{version_tag}".shellsplit) Bundler.ui.confirm "Pushed git commits and release tag." end @@ -145,13 +158,6 @@ module Bundler allowed_push_host || env_rubygems_host || "rubygems.org" end - def perform_git_push(options = "") - cmd = "git push #{options}" - out, status = sh_with_status(cmd.shellsplit) - return if status.success? - raise "Couldn't git push. `#{cmd}' failed with the following output:\n\n#{out}\n" - end - def already_tagged? return false unless sh(%w[git tag]).split(/\n/).include?(version_tag) Bundler.ui.confirm "Tag #{version_tag} has already been created." @@ -202,8 +208,7 @@ module Bundler def sh(cmd, &block) out, status = sh_with_status(cmd, &block) unless status.success? - cmd = cmd.shelljoin if cmd.respond_to?(:shelljoin) - raise(out.empty? ? "Running `#{cmd}` failed. Run this command directly for more detailed output." : out) + raise("Running `#{cmd.shelljoin}` failed with the following output:\n\n#{out}\n") end out end diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb index f945176037..8930fca6d0 100644 --- a/lib/bundler/index.rb +++ b/lib/bundler/index.rb @@ -122,10 +122,9 @@ module Bundler names end - # returns a list of the dependencies def unmet_dependency_names dependency_names.select do |name| - name != "bundler" && search(name).empty? + search(name).empty? end end @@ -196,11 +195,7 @@ module Bundler if base # allow all platforms when searching from a lockfile dependency.matches_spec?(spec) else - if Gem::Platform.respond_to? :match_spec? - dependency.matches_spec?(spec) && Gem::Platform.match_spec?(spec) - else - dependency.matches_spec?(spec) && Gem::Platform.match(spec.platform) - end + dependency.matches_spec?(spec) && Gem::Platform.match_spec?(spec) end end diff --git a/lib/bundler/injector.rb b/lib/bundler/injector.rb index e9aa13a357..613bda4f84 100644 --- a/lib/bundler/injector.rb +++ b/lib/bundler/injector.rb @@ -128,7 +128,7 @@ module Bundler # evaluates a gemfile to remove the specified gem # from it. def remove_deps(gemfile_path) - initial_gemfile = IO.readlines(gemfile_path) + initial_gemfile = File.readlines(gemfile_path) Bundler.ui.info "Removing gems from #{gemfile_path}" @@ -181,7 +181,7 @@ module Bundler patterns = /gem\s+(['"])#{Regexp.union(gems)}\1|gem\s*\((['"])#{Regexp.union(gems)}\2\)/ new_gemfile = [] multiline_removal = false - IO.readlines(gemfile_path).each do |line| + File.readlines(gemfile_path).each do |line| match_data = line.match(patterns) if match_data && is_not_within_comment?(line, match_data) multiline_removal = line.rstrip.end_with?(",") diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb index 59211193d4..a718418fce 100644 --- a/lib/bundler/inline.rb +++ b/lib/bundler/inline.rb @@ -50,8 +50,9 @@ def gemfile(install = false, options = {}, &gemfile) Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins? builder = Bundler::Dsl.new builder.instance_eval(&gemfile) + builder.check_primary_source_safety - Bundler.settings.temporary(:frozen => false) do + Bundler.settings.temporary(:deployment => false, :frozen => false) do definition = builder.to_definition(nil, true) def definition.lock(*); end definition.validate_runtime! diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index 8d55784006..63e02ba496 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "rubygems/dependency_installer" require_relative "worker" require_relative "installer/parallel_installer" require_relative "installer/standalone" @@ -89,6 +88,8 @@ module Bundler end install(options) + Gem::Specification.reset # invalidate gem specification cache so that installed gems are immediately available + lock unless Bundler.frozen_bundle? Standalone.new(options[:standalone], @definition).generate if options[:standalone] end @@ -133,7 +134,7 @@ module Bundler next end - mode = Bundler::WINDOWS ? "wb:UTF-8" : "w" + mode = Gem.win_platform? ? "wb:UTF-8" : "w" require "erb" content = if RUBY_VERSION >= "2.6" ERB.new(template, :trim_mode => "-").result(binding) @@ -142,7 +143,7 @@ module Bundler end File.write(binstub_path, content, :mode => mode, :perm => 0o777 & ~File.umask) - if Bundler::WINDOWS || options[:all_platforms] + if Gem.win_platform? || options[:all_platforms] prefix = "@ruby -x \"%~f0\" %*\n@exit /b %ERRORLEVEL%\n\n" File.write("#{binstub_path}.cmd", prefix + content, :mode => mode) end @@ -180,7 +181,7 @@ module Bundler executable_path = Pathname(spec.full_gem_path).join(spec.bindir, executable).relative_path_from(bin_path) executable_path = executable_path - mode = Bundler::WINDOWS ? "wb:UTF-8" : "w" + mode = Gem.win_platform? ? "wb:UTF-8" : "w" require "erb" content = if RUBY_VERSION >= "2.6" ERB.new(template, :trim_mode => "-").result(binding) @@ -189,7 +190,7 @@ module Bundler end File.write("#{bin_path}/#{executable}", content, :mode => mode, :perm => 0o755) - if Bundler::WINDOWS || options[:all_platforms] + if Gem.win_platform? || options[:all_platforms] prefix = "@ruby -x \"%~f0\" %*\n@exit /b %ERRORLEVEL%\n\n" File.write("#{bin_path}/#{executable}.cmd", prefix + content, :mode => mode) end @@ -220,14 +221,7 @@ module Bundler # Parallelization has some issues on Windows, so it's not yet the default return 1 if Gem.win_platform? - processor_count - end - - def processor_count - require "etc" - Etc.nprocessors - rescue StandardError - 1 + Bundler.settings.processor_count end def load_plugins diff --git a/lib/bundler/installer/gem_installer.rb b/lib/bundler/installer/gem_installer.rb index 507fd1802c..1df86ccfbc 100644 --- a/lib/bundler/installer/gem_installer.rb +++ b/lib/bundler/installer/gem_installer.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "shellwords" - module Bundler class GemInstaller attr_reader :spec, :standalone, :worker, :force, :installer @@ -31,34 +29,23 @@ module Bundler def specific_failure_message(e) message = "#{e.class}: #{e.message}\n" - message += " " + e.backtrace.join("\n ") + "\n\n" if Bundler.ui.debug? + message += " " + e.backtrace.join("\n ") + "\n\n" message = message.lines.first + Bundler.ui.add_color(message.lines.drop(1).join, :clear) message + Bundler.ui.add_color(failure_message, :red) end def failure_message - return install_error_message if spec.source.options["git"] - "#{install_error_message}\n#{gem_install_message}" + install_error_message end def install_error_message "An error occurred while installing #{spec.name} (#{spec.version}), and Bundler cannot continue." end - def gem_install_message - source = spec.source - return unless source.respond_to?(:remotes) - - if source.remotes.size == 1 - "Make sure that `gem install #{spec.name} -v '#{spec.version}' --source '#{source.remotes.first}'` succeeds before bundling." - else - "Make sure that `gem install #{spec.name} -v '#{spec.version}'` succeeds before bundling." - end - end - def spec_settings # Fetch the build settings, if there are any if settings = Bundler.settings["build.#{spec.name}"] + require "shellwords" Shellwords.shellsplit(settings) end end diff --git a/lib/bundler/installer/parallel_installer.rb b/lib/bundler/installer/parallel_installer.rb index a6d1de29e6..5b6680e5e1 100644 --- a/lib/bundler/installer/parallel_installer.rb +++ b/lib/bundler/installer/parallel_installer.rb @@ -6,10 +6,11 @@ require_relative "gem_installer" module Bundler class ParallelInstaller class SpecInstallation - attr_accessor :spec, :name, :post_install_message, :state, :error + attr_accessor :spec, :name, :full_name, :post_install_message, :state, :error def initialize(spec) @spec = spec @name = spec.name + @full_name = spec.full_name @state = :none @post_install_message = "" @error = nil @@ -27,13 +28,8 @@ module Bundler state == :failed end - def installation_attempted? - installed? || failed? - end - - # Only true when spec in neither installed nor already enqueued def ready_to_enqueue? - !enqueued? && !installation_attempted? + state == :none end def has_post_install_message? @@ -54,14 +50,11 @@ module Bundler # Represents only the non-development dependencies, the ones that are # itself and are in the total list. def dependencies - @dependencies ||= begin - all_dependencies.reject {|dep| ignorable_dependency? dep } - end + @dependencies ||= all_dependencies.reject {|dep| ignorable_dependency? dep } end def missing_lockfile_dependencies(all_spec_names) - deps = all_dependencies.reject {|dep| ignorable_dependency? dep } - deps.reject {|dep| all_spec_names.include? dep.name } + dependencies.reject {|dep| all_spec_names.include? dep.name } end # Represents all dependencies @@ -70,7 +63,7 @@ module Bundler end def to_s - "#<#{self.class} #{@spec.full_name} (#{state})>" + "#<#{self.class} #{full_name} (#{state})>" end end @@ -93,18 +86,48 @@ module Bundler def call check_for_corrupt_lockfile + if @rake + do_install(@rake, 0) + Gem::Specification.reset + end + if @size > 1 install_with_worker else install_serially end + check_for_unmet_dependencies + handle_error if failed_specs.any? @specs ensure worker_pool && worker_pool.stop end + def check_for_unmet_dependencies + unmet_dependencies = @specs.map do |s| + [ + s, + s.dependencies.reject {|dep| @specs.any? {|spec| dep.matches_spec?(spec.spec) } }, + ] + end.reject {|a| a.last.empty? } + return if unmet_dependencies.empty? + + warning = [] + warning << "Your lockfile doesn't include a valid resolution." + warning << "You can fix this by regenerating your lockfile or trying to manually editing the bad locked gems to a version that satisfies all dependencies." + warning << "The unmet dependencies are:" + + unmet_dependencies.each do |spec, unmet_spec_dependencies| + unmet_spec_dependencies.each do |unmet_spec_dependency| + warning << "* #{unmet_spec_dependency}, depended upon #{spec.full_name}, unsatisfied by #{@specs.find {|s| s.name == unmet_spec_dependency.name && !unmet_spec_dependency.matches_spec?(s.spec) }.full_name}" + end + end + + Bundler.ui.warn(warning.join("\n")) + end + def check_for_corrupt_lockfile missing_dependencies = @specs.map do |s| [ @@ -217,8 +240,6 @@ module Bundler # are installed. def enqueue_specs @specs.select(&:ready_to_enqueue?).each do |spec| - next if @rake && !@rake.installed? && spec.name != @rake.name - if spec.dependencies_installed? @specs spec.state = :enqueued worker_pool.enq spec diff --git a/lib/bundler/installer/standalone.rb b/lib/bundler/installer/standalone.rb index 2a3f5cfe35..e8494b4bcd 100644 --- a/lib/bundler/installer/standalone.rb +++ b/lib/bundler/installer/standalone.rb @@ -3,7 +3,7 @@ module Bundler class Standalone def initialize(groups, definition) - @specs = groups.empty? ? definition.requested_specs : definition.specs_for(groups.map(&:to_sym)) + @specs = definition.specs_for(groups) end def generate @@ -12,12 +12,13 @@ module Bundler end File.open File.join(bundler_path, "setup.rb"), "w" do |file| file.puts "require 'rbconfig'" - file.puts "ruby_engine = RUBY_ENGINE" - file.puts "ruby_version = RbConfig::CONFIG[\"ruby_version\"]" - file.puts "path = File.expand_path('..', __FILE__)" file.puts reverse_rubygems_kernel_mixin paths.each do |path| - file.puts %($:.unshift File.expand_path("\#{path}/#{path}")) + if Pathname.new(path).absolute? + file.puts %($:.unshift "#{path}") + else + file.puts %($:.unshift File.expand_path("\#{__dir__}/#{path}")) + end end end end @@ -28,14 +29,14 @@ module Bundler @specs.map do |spec| next if spec.name == "bundler" Array(spec.require_paths).map do |path| - gem_path(path, spec).sub(version_dir, '#{ruby_engine}/#{ruby_version}') + gem_path(path, spec).sub(version_dir, '#{RUBY_ENGINE}/#{RbConfig::CONFIG["ruby_version"]}') # This is a static string intentionally. It's interpolated at a later time. end - end.flatten + end.flatten.compact end def version_dir - "#{Bundler::RubyVersion.system.engine}/#{RbConfig::CONFIG["ruby_version"]}" + "#{RUBY_ENGINE}/#{RbConfig::CONFIG["ruby_version"]}" end def bundler_path @@ -44,7 +45,11 @@ module Bundler def gem_path(path, spec) full_path = Pathname.new(path).absolute? ? path : File.join(spec.full_gem_path, path) - Pathname.new(full_path).relative_path_from(Bundler.root.join(bundler_path)).to_s + if spec.source.instance_of?(Source::Path) + full_path + else + Pathname.new(full_path).relative_path_from(Bundler.root.join(bundler_path)).to_s + end rescue TypeError error_message = "#{spec.name} #{spec.version} has an invalid gemspec" raise Gem::InvalidSpecificationException.new(error_message) diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb index 04ba2a2364..4eb228f314 100644 --- a/lib/bundler/lazy_specification.rb +++ b/lib/bundler/lazy_specification.rb @@ -38,8 +38,24 @@ module Bundler identifier.hash end + ## + # Does this locked specification satisfy +dependency+? + # + # NOTE: Rubygems default requirement is ">= 0", which doesn't match + # prereleases of 0 versions, like "0.0.0.dev" or "0.0.0.SNAPSHOT". However, + # bundler users expect those to work. We need to make sure that Gemfile + # dependencies without explicit requirements (which use ">= 0" under the + # hood by default) are still valid for locked specs using this kind of + # versions. The method implements an ad-hoc fix for that. A better solution + # might be to change default rubygems requirement of dependencies to be ">= + # 0.A" but that's a major refactoring likely to break things. Hopefully we + # can attempt it in the future. + # + def satisfies?(dependency) - @name == dependency.name && dependency.requirement.satisfied_by?(Gem::Version.new(@version)) + effective_requirement = dependency.requirement == Gem::Requirement.default ? Gem::Requirement.new(">= 0.A") : dependency.requirement + + @name == dependency.name && effective_requirement.satisfied_by?(Gem::Version.new(@version)) end def to_lock @@ -73,7 +89,12 @@ module Bundler same_platform_candidates = candidates.select do |spec| MatchPlatform.platforms_match?(spec.platform, platform_object) end - search = same_platform_candidates.last || candidates.last + installable_candidates = same_platform_candidates.select do |spec| + !spec.is_a?(EndpointSpecification) || + (spec.required_ruby_version.satisfied_by?(Gem.ruby_version) && + spec.required_rubygems_version.satisfied_by?(Gem.rubygems_version)) + end + search = installable_candidates.last || same_platform_candidates.last search.dependencies = dependencies if search && (search.is_a?(RemoteSpecification) || search.is_a?(EndpointSpecification)) search end diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb index f836737621..6ff4910a36 100644 --- a/lib/bundler/lockfile_parser.rb +++ b/lib/bundler/lockfile_parser.rb @@ -1,16 +1,5 @@ # frozen_string_literal: true -#-- -# Some versions of the Bundler 1.1 RC series introduced corrupted -# lockfiles. There were two major problems: -# -# * multiple copies of the same GIT section appeared in the lockfile -# * when this happened, those sections got multiple copies of gems -# in those sections. -# -# As a result, Bundler 1.1 contains code that fixes the earlier -# corruption. We will remove this fix-up code in Bundler 1.2. - module Bundler class LockfileParser attr_reader :sources, :dependencies, :specs, :platforms, :bundler_version, :ruby_version @@ -64,8 +53,6 @@ module Bundler @state = nil @specs = {} - @rubygems_aggregate = Source::Rubygems.new - if lockfile.match(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/) raise LockfileError, "Your #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} contains merge conflicts.\n" \ "Run `git checkout HEAD -- #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}` first to get a clean lock." @@ -89,7 +76,6 @@ module Bundler send("parse_#{@state}", line) end end - @sources << @rubygems_aggregate unless Bundler.feature_flag.disable_multisource? @specs = @specs.values.sort_by(&:identifier) warn_for_outdated_bundler_version rescue ArgumentError => e @@ -100,6 +86,7 @@ module Bundler def warn_for_outdated_bundler_version return unless bundler_version + return if bundler_version.segments.last == "dev" prerelease_text = bundler_version.prerelease? ? " --pre" : "" current_version = Gem::Version.create(Bundler::VERSION) return unless current_version < bundler_version @@ -127,23 +114,11 @@ module Bundler @sources << @current_source when GIT @current_source = TYPES[@type].from_lock(@opts) - # Strip out duplicate GIT sections - if @sources.include?(@current_source) - @current_source = @sources.find {|s| s == @current_source } - else - @sources << @current_source - end + @sources << @current_source when GEM - if Bundler.feature_flag.disable_multisource? - @opts["remotes"] = @opts.delete("remote") - @current_source = TYPES[@type].from_lock(@opts) - @sources << @current_source - else - Array(@opts["remote"]).each do |url| - @rubygems_aggregate.add_remote(url) - end - @current_source = @rubygems_aggregate - end + @opts["remotes"] = Array(@opts.delete("remote")).reverse + @current_source = TYPES[@type].from_lock(@opts) + @sources << @current_source when PLUGIN @current_source = Plugin.source_from_lock(@opts) @sources << @current_source @@ -221,10 +196,9 @@ module Bundler platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY @current_spec = LazySpecification.new(name, version, platform) @current_spec.source = @current_source + @current_source.add_dependency_names(name) - # Avoid introducing multiple copies of the same spec (caused by - # duplicate GIT sections) - @specs[@current_spec.identifier] ||= @current_spec + @specs[@current_spec.identifier] = @current_spec elsif spaces.size == 6 version = version.split(",").map(&:strip) if version dep = Gem::Dependency.new(name, version) diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1 index b44295b2d3..a94467e25f 100644 --- a/lib/bundler/man/bundle-add.1 +++ b/lib/bundler/man/bundle-add.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-ADD" "1" "January 2021" "" "" +.TH "BUNDLE\-ADD" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install diff --git a/lib/bundler/man/bundle-binstubs.1 b/lib/bundler/man/bundle-binstubs.1 index 99876d023e..6d1b1d4247 100644 --- a/lib/bundler/man/bundle-binstubs.1 +++ b/lib/bundler/man/bundle-binstubs.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-BINSTUBS" "1" "January 2021" "" "" +.TH "BUNDLE\-BINSTUBS" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-binstubs\fR \- Install the binstubs of the listed gems diff --git a/lib/bundler/man/bundle-cache.1 b/lib/bundler/man/bundle-cache.1 index 3b3c6f9734..acbdae0df2 100644 --- a/lib/bundler/man/bundle-cache.1 +++ b/lib/bundler/man/bundle-cache.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-CACHE" "1" "January 2021" "" "" +.TH "BUNDLE\-CACHE" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application diff --git a/lib/bundler/man/bundle-check.1 b/lib/bundler/man/bundle-check.1 index 4436c3e971..e555c9b399 100644 --- a/lib/bundler/man/bundle-check.1 +++ b/lib/bundler/man/bundle-check.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-CHECK" "1" "January 2021" "" "" +.TH "BUNDLE\-CHECK" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems diff --git a/lib/bundler/man/bundle-clean.1 b/lib/bundler/man/bundle-clean.1 index b7e882ecbf..d403247524 100644 --- a/lib/bundler/man/bundle-clean.1 +++ b/lib/bundler/man/bundle-clean.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-CLEAN" "1" "January 2021" "" "" +.TH "BUNDLE\-CLEAN" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 7a4a16c43e..976dae3aec 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-CONFIG" "1" "January 2021" "" "" +.TH "BUNDLE\-CONFIG" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-config\fR \- Set bundler configuration options @@ -56,9 +56,6 @@ Executing \fBbundle config unset \-\-local <name> <value>\fR will delete the con .P Executing bundle with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\. . -.P -Executing \fBbundle config set \-\-local disable_multisource true\fR upgrades the warning about the Gemfile containing multiple primary sources to an error\. Executing \fBbundle config unset disable_multisource\fR downgrades this error to a warning\. -. .SH "REMEMBERING OPTIONS" Flags passed to \fBbundle install\fR or the Bundler runtime, such as \fB\-\-path foo\fR or \fB\-\-without production\fR, are remembered between commands and saved to your local application\'s configuration (normally, \fB\./\.bundle/config\fR)\. . @@ -184,9 +181,6 @@ The following is a list of all configuration keys and their purpose\. You can le \fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR): Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\. . .IP "\(bu" 4 -\fBdisable_multisource\fR (\fBBUNDLE_DISABLE_MULTISOURCE\fR): When set, Gemfiles containing multiple sources will produce errors instead of warnings\. Use \fBbundle config unset disable_multisource\fR to unset\. -. -.IP "\(bu" 4 \fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR): Stop Bundler from accessing gems installed to RubyGems\' normal location\. . .IP "\(bu" 4 @@ -199,6 +193,9 @@ The following is a list of all configuration keys and their purpose\. You can le \fBfrozen\fR (\fBBUNDLE_FROZEN\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\. Defaults to \fBtrue\fR when \fB\-\-deployment\fR is used\. . .IP "\(bu" 4 +\fBgem\.github_username\fR (\fBBUNDLE_GEM__GITHUB_USERNAME\fR): Sets a GitHub username or organization to be used in \fBREADME\fR file when you create a new gem via \fBbundle gem\fR command\. It can be overridden by passing an explicit \fB\-\-github\-username\fR flag to \fBbundle gem\fR\. +. +.IP "\(bu" 4 \fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR): Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\. . .IP "\(bu" 4 @@ -211,10 +208,10 @@ The following is a list of all configuration keys and their purpose\. You can le \fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR): When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\. . .IP "\(bu" 4 -\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR) Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\. +\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR): Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\. . .IP "\(bu" 4 -\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of gems Bundler can install in parallel\. Defaults to 1\. +\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of gems Bundler can install in parallel\. Defaults to 1 on Windows, and to the the number of processors on other platforms\. . .IP "\(bu" 4 \fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR): Whether \fBbundle package\fR should skip installing gems\. @@ -223,9 +220,6 @@ The following is a list of all configuration keys and their purpose\. You can le \fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR): Whether Bundler should leave outdated gems unpruned when caching\. . .IP "\(bu" 4 -\fBonly_update_to_newer_versions\fR (\fBBUNDLE_ONLY_UPDATE_TO_NEWER_VERSIONS\fR): During \fBbundle update\fR, only resolve to newer versions of the gems in the lockfile\. -. -.IP "\(bu" 4 \fBpath\fR (\fBBUNDLE_PATH\fR): The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. Defaults to \fBGem\.dir\fR\. When \-\-deployment is used, defaults to vendor/bundle\. . .IP "\(bu" 4 @@ -241,7 +235,7 @@ The following is a list of all configuration keys and their purpose\. You can le \fBprefer_patch\fR (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\. . .IP "\(bu" 4 -\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR) Print only version number from \fBbundler \-\-version\fR\. +\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR): Print only version number from \fBbundler \-\-version\fR\. . .IP "\(bu" 4 \fBredirect\fR (\fBBUNDLE_REDIRECT\fR): The number of redirects allowed for network requests\. Defaults to \fB5\fR\. @@ -280,10 +274,7 @@ The following is a list of all configuration keys and their purpose\. You can le \fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR): The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\. . .IP "\(bu" 4 -\fBunlock_source_unlocks_spec\fR (\fBBUNDLE_UNLOCK_SOURCE_UNLOCKS_SPEC\fR): Whether running \fBbundle update \-\-source NAME\fR unlocks a gem with the given name\. Defaults to \fBtrue\fR\. -. -.IP "\(bu" 4 -\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR) Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\. +\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR): Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\. . .IP "\(bu" 4 \fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR): The custom user agent fragment Bundler includes in API requests\. @@ -470,6 +461,23 @@ export BUNDLE_GITHUB__COM=abcd0123generatedtoken:x\-oauth\-basic . .IP "" 0 . +.P +Note that any configured credentials will be redacted by informative commands such as \fBbundle config list\fR or \fBbundle config get\fR, unless you use the \fB\-\-parseable\fR flag\. This is to avoid unintentionally leaking credentials when copy\-pasting bundler output\. +. +.P +Also note that to guarantee a sane mapping between valid environment variable names and valid host names, bundler makes the following transformations: +. +.IP "\(bu" 4 +Any \fB\-\fR characters in a host name are mapped to a triple dash (\fB___\fR) in the corresponding environment variable\. +. +.IP "\(bu" 4 +Any \fB\.\fR characters in a host name are mapped to a double dash (\fB__\fR) in the corresponding environment variable\. +. +.IP "" 0 +. +.P +This means that if you have a gem server named \fBmy\.gem\-host\.com\fR, you\'ll need to use the \fBBUNDLE_MY__GEM___HOST__COM\fR variable to configure credentials for it through ENV\. +. .SH "CONFIGURE BUNDLER DIRECTORIES" Bundler\'s home, config, cache and plugin directories are able to be configured through environment variables\. The default location for Bundler\'s home directory is \fB~/\.bundle\fR, which all directories inherit from by default\. The following outlines the available environment variables and their default values . diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index b3c4e59a78..f412ee3aec 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -47,10 +47,6 @@ configuration only from the local application. Executing bundle with the `BUNDLE_IGNORE_CONFIG` environment variable set will cause it to ignore all configuration. -Executing `bundle config set --local disable_multisource true` upgrades the warning about -the Gemfile containing multiple primary sources to an error. Executing `bundle -config unset disable_multisource` downgrades this error to a warning. - ## REMEMBERING OPTIONS Flags passed to `bundle install` or the Bundler runtime, such as `--path foo` or @@ -178,10 +174,6 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). * `disable_local_revision_check` (`BUNDLE_DISABLE_LOCAL_REVISION_CHECK`): Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository. -* `disable_multisource` (`BUNDLE_DISABLE_MULTISOURCE`): - When set, Gemfiles containing multiple sources will produce errors - instead of warnings. - Use `bundle config unset disable_multisource` to unset. * `disable_shared_gems` (`BUNDLE_DISABLE_SHARED_GEMS`): Stop Bundler from accessing gems installed to RubyGems' normal location. * `disable_version_check` (`BUNDLE_DISABLE_VERSION_CHECK`): @@ -194,6 +186,10 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). Disallow changes to the `Gemfile`. When the `Gemfile` is changed and the lockfile has not been updated, running Bundler commands will be blocked. Defaults to `true` when `--deployment` is used. +* `gem.github_username` (`BUNDLE_GEM__GITHUB_USERNAME`): + Sets a GitHub username or organization to be used in `README` file when you + create a new gem via `bundle gem` command. It can be overridden by passing an + explicit `--github-username` flag to `bundle gem`. * `gem.push_key` (`BUNDLE_GEM__PUSH_KEY`): Sets the `--key` parameter for `gem push` when using the `rake release` command with a private gemstash server. @@ -206,20 +202,18 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). * `global_gem_cache` (`BUNDLE_GLOBAL_GEM_CACHE`): Whether Bundler should cache all gems globally, rather than locally to the installing Ruby installation. -* `ignore_messages` (`BUNDLE_IGNORE_MESSAGES`): When set, no post install - messages will be printed. To silence a single gem, use dot notation like - `ignore_messages.httparty true`. -* `init_gems_rb` (`BUNDLE_INIT_GEMS_RB`) +* `ignore_messages` (`BUNDLE_IGNORE_MESSAGES`): + When set, no post install messages will be printed. To silence a single gem, + use dot notation like `ignore_messages.httparty true`. +* `init_gems_rb` (`BUNDLE_INIT_GEMS_RB`): Generate a `gems.rb` instead of a `Gemfile` when running `bundle init`. * `jobs` (`BUNDLE_JOBS`): - The number of gems Bundler can install in parallel. Defaults to 1. + The number of gems Bundler can install in parallel. Defaults to 1 on Windows, + and to the the number of processors on other platforms. * `no_install` (`BUNDLE_NO_INSTALL`): Whether `bundle package` should skip installing gems. * `no_prune` (`BUNDLE_NO_PRUNE`): Whether Bundler should leave outdated gems unpruned when caching. -* `only_update_to_newer_versions` (`BUNDLE_ONLY_UPDATE_TO_NEWER_VERSIONS`): - During `bundle update`, only resolve to newer versions of the gems in the - lockfile. * `path` (`BUNDLE_PATH`): The location on disk where all gems in your bundle will be located regardless of `$GEM_HOME` or `$GEM_PATH` values. Bundle gems not found in this location @@ -233,7 +227,7 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). Enable Bundler's experimental plugin system. * `prefer_patch` (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates. Makes `bundle update` calls equivalent to `bundler update --patch`. -* `print_only_version_number` (`BUNDLE_PRINT_ONLY_VERSION_NUMBER`) +* `print_only_version_number` (`BUNDLE_PRINT_ONLY_VERSION_NUMBER`): Print only version number from `bundler --version`. * `redirect` (`BUNDLE_REDIRECT`): The number of redirects allowed for network requests. Defaults to `5`. @@ -266,10 +260,7 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). The location where RubyGems installs binstubs. Defaults to `Gem.bindir`. * `timeout` (`BUNDLE_TIMEOUT`): The seconds allowed before timing out for network requests. Defaults to `10`. -* `unlock_source_unlocks_spec` (`BUNDLE_UNLOCK_SOURCE_UNLOCKS_SPEC`): - Whether running `bundle update --source NAME` unlocks a gem with the given - name. Defaults to `true`. -* `update_requires_all_flag` (`BUNDLE_UPDATE_REQUIRES_ALL_FLAG`) +* `update_requires_all_flag` (`BUNDLE_UPDATE_REQUIRES_ALL_FLAG`): Require passing `--all` to `bundle update` when everything should be updated, and disallow passing no options to `bundle update`. * `user_agent` (`BUNDLE_USER_AGENT`): @@ -374,6 +365,23 @@ where you can use personal OAuth tokens: export BUNDLE_GITHUB__COM=abcd0123generatedtoken:x-oauth-basic +Note that any configured credentials will be redacted by informative commands +such as `bundle config list` or `bundle config get`, unless you use the +`--parseable` flag. This is to avoid unintentionally leaking credentials when +copy-pasting bundler output. + +Also note that to guarantee a sane mapping between valid environment variable +names and valid host names, bundler makes the following transformations: + +* Any `-` characters in a host name are mapped to a triple dash (`___`) in the + corresponding environment variable. + +* Any `.` characters in a host name are mapped to a double dash (`__`) in the + corresponding environment variable. + +This means that if you have a gem server named `my.gem-host.com`, you'll need to +use the `BUNDLE_MY__GEM___HOST__COM` variable to configure credentials for it +through ENV. ## CONFIGURE BUNDLER DIRECTORIES diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1 index 2923517608..87a7fe5f2f 100644 --- a/lib/bundler/man/bundle-doctor.1 +++ b/lib/bundler/man/bundle-doctor.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-DOCTOR" "1" "January 2021" "" "" +.TH "BUNDLE\-DOCTOR" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-doctor\fR \- Checks the bundle for common problems diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1 index aa0c509278..69adfa7c92 100644 --- a/lib/bundler/man/bundle-exec.1 +++ b/lib/bundler/man/bundle-exec.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-EXEC" "1" "January 2021" "" "" +.TH "BUNDLE\-EXEC" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-exec\fR \- Execute a command in the context of the bundle diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1 index c1abf90f5c..fae5c34e7e 100644 --- a/lib/bundler/man/bundle-gem.1 +++ b/lib/bundler/man/bundle-gem.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-GEM" "1" "January 2021" "" "" +.TH "BUNDLE\-GEM" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem @@ -90,6 +90,19 @@ When Bundler is configured to not generate CI files, an interactive prompt will When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler\'s global config for future \fBbundle gem\fR use\. . .TP +\fB\-\-linter\fR, \fB\-\-linter=rubocop\fR, \fB\-\-linter=standard\fR +Specify the linter and code formatter that Bundler should add to the project\'s development dependencies\. Acceptable values are \fBrubocop\fR and \fBstandard\fR\. A configuration file will be generated in the project directory\. Given no option is specified: +. +.IP +When Bundler is configured to add a linter, this defaults to Bundler\'s global config setting \fBgem\.linter\fR\. +. +.IP +When Bundler is configured not to add a linter, an interactive prompt will be displayed and the answer will be used for the current rubygem project\. +. +.IP +When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler\'s global config for future \fBbundle gem\fR use\. +. +.TP \fB\-e\fR, \fB\-\-edit[=EDITOR]\fR Open the resulting GEM_NAME\.gemspec in EDITOR, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\. . diff --git a/lib/bundler/man/bundle-gem.1.ronn b/lib/bundler/man/bundle-gem.1.ronn index a997cb907a..61c741fb24 100644 --- a/lib/bundler/man/bundle-gem.1.ronn +++ b/lib/bundler/man/bundle-gem.1.ronn @@ -92,6 +92,22 @@ configuration file using the following names: the answer will be saved in Bundler's global config for future `bundle gem` use. +* `--linter`, `--linter=rubocop`, `--linter=standard`: + Specify the linter and code formatter that Bundler should add to the + project's development dependencies. Acceptable values are `rubocop` and + `standard`. A configuration file will be generated in the project directory. + Given no option is specified: + + When Bundler is configured to add a linter, this defaults to Bundler's + global config setting `gem.linter`. + + When Bundler is configured not to add a linter, an interactive prompt + will be displayed and the answer will be used for the current rubygem project. + + When Bundler is unconfigured, an interactive prompt will be displayed and + the answer will be saved in Bundler's global config for future `bundle gem` + use. + * `-e`, `--edit[=EDITOR]`: Open the resulting GEM_NAME.gemspec in EDITOR, or the default editor if not specified. The default is `$BUNDLER_EDITOR`, `$VISUAL`, or `$EDITOR`. diff --git a/lib/bundler/man/bundle-info.1 b/lib/bundler/man/bundle-info.1 index c715af4063..9e1400ec56 100644 --- a/lib/bundler/man/bundle-info.1 +++ b/lib/bundler/man/bundle-info.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-INFO" "1" "January 2021" "" "" +.TH "BUNDLE\-INFO" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-info\fR \- Show information for the given gem in your bundle diff --git a/lib/bundler/man/bundle-init.1 b/lib/bundler/man/bundle-init.1 index 6c8f441bdd..612d16031c 100644 --- a/lib/bundler/man/bundle-init.1 +++ b/lib/bundler/man/bundle-init.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-INIT" "1" "January 2021" "" "" +.TH "BUNDLE\-INIT" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-init\fR \- Generates a Gemfile into the current working directory diff --git a/lib/bundler/man/bundle-inject.1 b/lib/bundler/man/bundle-inject.1 index 2342b44716..ded4d6d64b 100644 --- a/lib/bundler/man/bundle-inject.1 +++ b/lib/bundler/man/bundle-inject.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-INJECT" "1" "January 2021" "" "" +.TH "BUNDLE\-INJECT" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1 index c223185b6f..6ccf100b4e 100644 --- a/lib/bundler/man/bundle-install.1 +++ b/lib/bundler/man/bundle-install.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-INSTALL" "1" "January 2021" "" "" +.TH "BUNDLE\-INSTALL" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile diff --git a/lib/bundler/man/bundle-list.1 b/lib/bundler/man/bundle-list.1 index 929b0f79f8..a697173af9 100644 --- a/lib/bundler/man/bundle-list.1 +++ b/lib/bundler/man/bundle-list.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-LIST" "1" "January 2021" "" "" +.TH "BUNDLE\-LIST" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-list\fR \- List all the gems in the bundle diff --git a/lib/bundler/man/bundle-lock.1 b/lib/bundler/man/bundle-lock.1 index bcf588a3cf..ef515b0337 100644 --- a/lib/bundler/man/bundle-lock.1 +++ b/lib/bundler/man/bundle-lock.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-LOCK" "1" "January 2021" "" "" +.TH "BUNDLE\-LOCK" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-lock\fR \- Creates / Updates a lockfile without installing diff --git a/lib/bundler/man/bundle-open.1 b/lib/bundler/man/bundle-open.1 index 27308ff624..dd28566bdb 100644 --- a/lib/bundler/man/bundle-open.1 +++ b/lib/bundler/man/bundle-open.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-OPEN" "1" "January 2021" "" "" +.TH "BUNDLE\-OPEN" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-open\fR \- Opens the source directory for a gem in your bundle diff --git a/lib/bundler/man/bundle-outdated.1 b/lib/bundler/man/bundle-outdated.1 index 40b23568d5..b9d50a1c71 100644 --- a/lib/bundler/man/bundle-outdated.1 +++ b/lib/bundler/man/bundle-outdated.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-OUTDATED" "1" "January 2021" "" "" +.TH "BUNDLE\-OUTDATED" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-outdated\fR \- List installed gems with newer versions available diff --git a/lib/bundler/man/bundle-platform.1 b/lib/bundler/man/bundle-platform.1 index d0ec5c643c..b1c859f64b 100644 --- a/lib/bundler/man/bundle-platform.1 +++ b/lib/bundler/man/bundle-platform.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-PLATFORM" "1" "January 2021" "" "" +.TH "BUNDLE\-PLATFORM" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-platform\fR \- Displays platform compatibility information diff --git a/lib/bundler/man/bundle-pristine.1 b/lib/bundler/man/bundle-pristine.1 index 9fd42d04a4..6e4a028666 100644 --- a/lib/bundler/man/bundle-pristine.1 +++ b/lib/bundler/man/bundle-pristine.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-PRISTINE" "1" "January 2021" "" "" +.TH "BUNDLE\-PRISTINE" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-pristine\fR \- Restores installed gems to their pristine condition diff --git a/lib/bundler/man/bundle-remove.1 b/lib/bundler/man/bundle-remove.1 index 366e5f4be2..0b4edd1414 100644 --- a/lib/bundler/man/bundle-remove.1 +++ b/lib/bundler/man/bundle-remove.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-REMOVE" "1" "January 2021" "" "" +.TH "BUNDLE\-REMOVE" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-remove\fR \- Removes gems from the Gemfile diff --git a/lib/bundler/man/bundle-show.1 b/lib/bundler/man/bundle-show.1 index e2de241438..375699ddf0 100644 --- a/lib/bundler/man/bundle-show.1 +++ b/lib/bundler/man/bundle-show.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-SHOW" "1" "January 2021" "" "" +.TH "BUNDLE\-SHOW" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem diff --git a/lib/bundler/man/bundle-update.1 b/lib/bundler/man/bundle-update.1 index a78633bbf8..c08bc66ef0 100644 --- a/lib/bundler/man/bundle-update.1 +++ b/lib/bundler/man/bundle-update.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-UPDATE" "1" "January 2021" "" "" +.TH "BUNDLE\-UPDATE" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-update\fR \- Update your gems to the latest available versions @@ -79,7 +79,7 @@ Do not allow any gem to be updated past latest \fB\-\-patch\fR | \fB\-\-minor\fR . .TP \fB\-\-conservative\fR -Use bundle install conservative update behavior and do not allow shared dependencies to be updated\. +Use bundle install conservative update behavior and do not allow indirect dependencies to be updated\. . .SH "UPDATING ALL GEMS" If you run \fBbundle update \-\-all\fR, bundler will ignore any previously installed gems and resolve all dependencies again based on the latest versions of all gems available in the sources\. @@ -208,13 +208,13 @@ In this case, the two gems have their own set of dependencies, but they share \f In short, by default, when you update a gem using \fBbundle update\fR, bundler will update all dependencies of that gem, including those that are also dependencies of another gem\. . .P -To prevent updating shared dependencies, prior to version 1\.14 the only option was the \fBCONSERVATIVE UPDATING\fR behavior in bundle install(1) \fIbundle\-install\.1\.html\fR: +To prevent updating indirect dependencies, prior to version 1\.14 the only option was the \fBCONSERVATIVE UPDATING\fR behavior in bundle install(1) \fIbundle\-install\.1\.html\fR: . .P In this scenario, updating the \fBthin\fR version manually in the Gemfile(5), and then running bundle install(1) \fIbundle\-install\.1\.html\fR will only update \fBdaemons\fR and \fBeventmachine\fR, but not \fBrack\fR\. For more information, see the \fBCONSERVATIVE UPDATING\fR section of bundle install(1) \fIbundle\-install\.1\.html\fR\. . .P -Starting with 1\.14, specifying the \fB\-\-conservative\fR option will also prevent shared dependencies from being updated\. +Starting with 1\.14, specifying the \fB\-\-conservative\fR option will also prevent indirect dependencies from being updated\. . .SH "PATCH LEVEL OPTIONS" Version 1\.14 introduced 4 patch\-level options that will influence how gem versions are resolved\. One of the following options can be used: \fB\-\-patch\fR, \fB\-\-minor\fR or \fB\-\-major\fR\. \fB\-\-strict\fR can be added to further influence resolution\. diff --git a/lib/bundler/man/bundle-update.1.ronn b/lib/bundler/man/bundle-update.1.ronn index 397fecadcb..3a16f29149 100644 --- a/lib/bundler/man/bundle-update.1.ronn +++ b/lib/bundler/man/bundle-update.1.ronn @@ -80,7 +80,7 @@ gem. Do not allow any gem to be updated past latest `--patch` | `--minor` | `--major`. * `--conservative`: - Use bundle install conservative update behavior and do not allow shared dependencies to be updated. + Use bundle install conservative update behavior and do not allow indirect dependencies to be updated. ## UPDATING ALL GEMS @@ -195,7 +195,7 @@ In short, by default, when you update a gem using `bundle update`, bundler will update all dependencies of that gem, including those that are also dependencies of another gem. -To prevent updating shared dependencies, prior to version 1.14 the only option +To prevent updating indirect dependencies, prior to version 1.14 the only option was the `CONSERVATIVE UPDATING` behavior in [bundle install(1)](bundle-install.1.html): In this scenario, updating the `thin` version manually in the Gemfile(5), @@ -203,7 +203,7 @@ and then running [bundle install(1)](bundle-install.1.html) will only update `da but not `rack`. For more information, see the `CONSERVATIVE UPDATING` section of [bundle install(1)](bundle-install.1.html). -Starting with 1.14, specifying the `--conservative` option will also prevent shared +Starting with 1.14, specifying the `--conservative` option will also prevent indirect dependencies from being updated. ## PATCH LEVEL OPTIONS diff --git a/lib/bundler/man/bundle-viz.1 b/lib/bundler/man/bundle-viz.1 index fead09e6ad..f792aa6346 100644 --- a/lib/bundler/man/bundle-viz.1 +++ b/lib/bundler/man/bundle-viz.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-VIZ" "1" "January 2021" "" "" +.TH "BUNDLE\-VIZ" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1 index 50aa6e17c7..b1458bf57b 100644 --- a/lib/bundler/man/bundle.1 +++ b/lib/bundler/man/bundle.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE" "1" "January 2021" "" "" +.TH "BUNDLE" "1" "December 2021" "" "" . .SH "NAME" \fBbundle\fR \- Ruby Dependency Management diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index 3942806f75..2e423522b7 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "GEMFILE" "5" "January 2021" "" "" +.TH "GEMFILE" "5" "December 2021" "" "" . .SH "NAME" \fBGemfile\fR \- A format for describing gem dependencies for Ruby programs @@ -506,6 +506,32 @@ gem "rails", :git => "git://github\.com/rails/rails\.git" .P Since the \fBgithub\fR method is a specialization of \fBgit_source\fR, it accepts a \fB:branch\fR named argument\. . +.P +You can also directly pass a pull request URL: +. +.IP "" 4 +. +.nf + +gem "rails", :github => "https://github\.com/rails/rails/pull/43753" +. +.fi +. +.IP "" 0 +. +.P +Which is equivalent to: +. +.IP "" 4 +. +.nf + +gem "rails", :github => "rails/rails", branch: "refs/pull/43753/head" +. +.fi +. +.IP "" 0 +. .SS "GIST" If the git repository you want to use is hosted as a Github Gist and is public, you can use the :gist shorthand to specify the gist identifier (without the trailing "\.git")\. . diff --git a/lib/bundler/man/gemfile.5.ronn b/lib/bundler/man/gemfile.5.ronn index 994f0d66bd..20ce3242ae 100644 --- a/lib/bundler/man/gemfile.5.ronn +++ b/lib/bundler/man/gemfile.5.ronn @@ -372,6 +372,14 @@ Are both equivalent to Since the `github` method is a specialization of `git_source`, it accepts a `:branch` named argument. +You can also directly pass a pull request URL: + + gem "rails", :github => "https://github.com/rails/rails/pull/43753" + +Which is equivalent to: + + gem "rails", :github => "rails/rails", branch: "refs/pull/43753/head" + ### GIST If the git repository you want to use is hosted as a Github Gist and is public, you can use diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb index da3f468da5..158c69e1a1 100644 --- a/lib/bundler/plugin.rb +++ b/lib/bundler/plugin.rb @@ -13,6 +13,7 @@ module Bundler class MalformattedPlugin < PluginError; end class UndefinedCommandError < PluginError; end class UnknownSourceError < PluginError; end + class PluginInstallError < PluginError; end PLUGIN_FILE_NAME = "plugins.rb".freeze @@ -38,12 +39,11 @@ module Bundler specs = Installer.new.install(names, options) save_plugins names, specs - rescue PluginError => e + rescue PluginError specs_to_delete = specs.select {|k, _v| names.include?(k) && !index.commands.values.include?(k) } specs_to_delete.each_value {|spec| Bundler.rm_rf(spec.full_gem_path) } - names_list = names.map {|name| "`#{name}`" }.join(", ") - Bundler.ui.error "Failed to install the following plugins: #{names_list}. The underlying error was: #{e.message}.\n #{e.backtrace.join("\n ")}" + raise end # Uninstalls plugins by the given names @@ -105,6 +105,7 @@ module Bundler else builder.eval_gemfile(gemfile) end + builder.check_primary_source_safety definition = builder.to_definition(nil, true) return if definition.dependencies.empty? @@ -163,7 +164,7 @@ module Bundler end # To be called from Cli class to pass the command and argument to - # approriate plugin class + # appropriate plugin class def exec_command(command, args) raise UndefinedCommandError, "Command `#{command}` not found" unless command? command @@ -182,7 +183,7 @@ module Bundler !index.source_plugin(name.to_s).nil? end - # @return [Class] that handles the source. The calss includes API::Source + # @return [Class] that handles the source. The class includes API::Source def source(name) raise UnknownSourceError, "Source #{name} not found" unless source? name @@ -244,10 +245,11 @@ module Bundler # @param [Array<String>] names of inferred source plugins that can be ignored def save_plugins(plugins, specs, optional_plugins = []) plugins.each do |name| + next if index.installed?(name) + spec = specs[name] - validate_plugin! Pathname.new(spec.full_gem_path) - installed = register_plugin(name, spec, optional_plugins.include?(name)) - Bundler.ui.info "Installed plugin #{name}" if installed + + save_plugin(name, spec, optional_plugins.include?(name)) end end @@ -262,6 +264,22 @@ module Bundler raise MalformattedPlugin, "#{PLUGIN_FILE_NAME} was not found in the plugin." unless plugin_file.file? end + # Validates and registers a plugin. + # + # @param [String] name the name of the plugin + # @param [Specification] spec of installed plugin + # @param [Boolean] optional_plugin, removed if there is conflict with any + # other plugin (used for default source plugins) + # + # @raise [PluginInstallError] if validation or registration raises any error + def save_plugin(name, spec, optional_plugin = false) + validate_plugin! Pathname.new(spec.full_gem_path) + installed = register_plugin(name, spec, optional_plugin) + Bundler.ui.info "Installed plugin #{name}" if installed + rescue PluginError => e + raise PluginInstallError, "Failed to install plugin `#{spec.name}`, due to #{e.class} (#{e.message})" + end + # Runs the plugins.rb file in an isolated namespace, records the plugin # actions it registers for and then passes the data to index to be stored. # @@ -308,6 +326,8 @@ module Bundler # # @param [String] name of the plugin def load_plugin(name) + return unless name && !name.empty? + # Need to ensure before this that plugin root where the rest of gems # are installed to be on load path to support plugin deps. Currently not # done to avoid conflicts diff --git a/lib/bundler/plugin/api/source.rb b/lib/bundler/plugin/api/source.rb index e1f0826874..32b1d0ee38 100644 --- a/lib/bundler/plugin/api/source.rb +++ b/lib/bundler/plugin/api/source.rb @@ -140,6 +140,13 @@ module Bundler end end + # Set internal representation to fetch the gems/specs locally. + # + # When this is called, the source should try to fetch the specs and + # install from the local system. + def local! + end + # Set internal representation to fetch the gems/specs from remote. # # When this is called, the source should try to fetch the specs and @@ -237,6 +244,20 @@ module Bundler specs.unmet_dependency_names end + # Used by definition. + # + # Note: Do not override if you don't know what you are doing. + def spec_names + specs.spec_names + end + + # Used by definition. + # + # Note: Do not override if you don't know what you are doing. + def add_dependency_names(names) + @dependencies |= Array(names) + end + # Note: Do not override if you don't know what you are doing. def can_lock?(spec) spec.source == self @@ -262,6 +283,7 @@ module Bundler def to_s "plugin source for #{@type} with uri #{@uri}" end + alias_method :identifier, :to_s # Note: Do not override if you don't know what you are doing. def include?(other) diff --git a/lib/bundler/plugin/index.rb b/lib/bundler/plugin/index.rb index 819bc60588..29d33be718 100644 --- a/lib/bundler/plugin/index.rb +++ b/lib/bundler/plugin/index.rb @@ -74,7 +74,10 @@ module Bundler def unregister_plugin(name) @commands.delete_if {|_, v| v == name } @sources.delete_if {|_, v| v == name } - @hooks.each {|_, plugin_names| plugin_names.delete(name) } + @hooks.each do |hook, names| + names.delete(name) + @hooks.delete(hook) if names.empty? + end @plugin_paths.delete(name) @load_paths.delete(name) save_index diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb index 26cec4f18c..d7411fff45 100644 --- a/lib/bundler/plugin/installer.rb +++ b/lib/bundler/plugin/installer.rb @@ -16,15 +16,13 @@ module Bundler version = options[:version] || [">= 0"] - Bundler.settings.temporary(:disable_multisource => false) do - if options[:git] - install_git(names, version, options) - elsif options[:local_git] - install_local_git(names, version, options) - else - sources = options[:source] || Bundler.rubygems.sources - install_rubygems(names, version, sources) - end + if options[:git] + install_git(names, version, options) + elsif options[:local_git] + install_local_git(names, version, options) + else + sources = options[:source] || Bundler.rubygems.sources + install_rubygems(names, version, sources) end end @@ -79,10 +77,12 @@ module Bundler source_list = SourceList.new source_list.add_git_source(git_source_options) if git_source_options - source_list.add_rubygems_source("remotes" => rubygems_source) if rubygems_source + Array(rubygems_source).each {|remote| source_list.add_global_rubygems_remote(remote) } if rubygems_source deps = names.map {|name| Dependency.new name, version } + Bundler.configure_gem_home_and_path(Plugin.root) + definition = Definition.new(nil, deps, source_list, true) install_definition(definition) end diff --git a/lib/bundler/plugin/source_list.rb b/lib/bundler/plugin/source_list.rb index b90a331d28..547661cf2f 100644 --- a/lib/bundler/plugin/source_list.rb +++ b/lib/bundler/plugin/source_list.rb @@ -17,6 +17,10 @@ module Bundler path_sources + git_sources + rubygems_sources + [metadata_source] end + def default_source + git_sources.first || global_rubygems_source + end + private def rubygems_aggregate_class diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index 0f17ba3afb..5eb17a3921 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -17,15 +17,14 @@ module Bundler # ==== Returns # <GemBundle>,nil:: If the list of dependencies can be resolved, a # collection of gemspecs is returned. Otherwise, nil is returned. - def self.resolve(requirements, index, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil) + def self.resolve(requirements, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil) base = SpecSet.new(base) unless base.is_a?(SpecSet) - resolver = new(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) + resolver = new(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) result = resolver.start(requirements) - SpecSet.new(result) + SpecSet.new(SpecSet.new(result).for(requirements.reject{|dep| dep.name.end_with?("\0") })) end - def initialize(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) - @index = index + def initialize(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) @source_requirements = source_requirements @base = base @resolver = Molinillo::Resolver.new(self, self) @@ -36,14 +35,10 @@ module Bundler @base_dg.add_vertex(ls.name, DepProxy.get_proxy(dep, ls.platform), true) end additional_base_requirements.each {|d| @base_dg.add_vertex(d.name, d) } - @platforms = platforms + @platforms = platforms.reject {|p| p != Gem::Platform::RUBY && (platforms - [p]).any? {|pl| generic(pl) == p } } @resolving_only_for_ruby = platforms == [Gem::Platform::RUBY] @gem_version_promoter = gem_version_promoter @use_gvp = Bundler.feature_flag.use_gem_version_promoter_for_major_updates? || !@gem_version_promoter.major? - @lockfile_uses_separate_rubygems_sources = Bundler.feature_flag.disable_multisource? - - @variant_specific_names = [] - @generic_names = [] end def start(requirements) @@ -53,7 +48,6 @@ module Bundler verify_gemfile_dependencies_are_found!(requirements) dg = @resolver.resolve(requirements, @base_dg) dg. - tap {|resolved| validate_resolved_specs!(resolved) }. map(&:payload). reject {|sg| sg.name.end_with?("\0") }. map(&:to_specs). @@ -107,26 +101,15 @@ module Bundler include Molinillo::SpecificationProvider def dependencies_for(specification) - all_dependencies = specification.dependencies_for_activated_platforms - - if @variant_specific_names.include?(specification.name) - @variant_specific_names |= all_dependencies.map(&:name) - @generic_names - else - generic_names, variant_specific_names = specification.partitioned_dependency_names_for_activated_platforms - @variant_specific_names |= variant_specific_names - @generic_names - @generic_names |= generic_names - end - - all_dependencies + specification.dependencies_for_activated_platforms end def search_for(dependency_proxy) platform = dependency_proxy.__platform dependency = dependency_proxy.dep name = dependency.name - search_result = @search_for[dependency_proxy] ||= begin - index = index_for(dependency) - results = index.search(dependency, @base[name]) + @search_for[dependency_proxy] ||= begin + results = results_for(dependency, @base[name]) if vertex = @base_dg.vertex_named(name) locked_requirement = vertex.payload.requirement @@ -177,39 +160,18 @@ module Bundler @gem_version_promoter.sort_versions(dependency, spec_groups) end end + end - unless search_result.empty? - specific_dependency = @variant_specific_names.include?(name) - return search_result unless specific_dependency - - search_result.each do |sg| - if @generic_names.include?(name) - @variant_specific_names -= [name] - sg.activate_all_platforms! - else - sg.activate_platform!(platform) - end - end - end + def index_for(dependency) + source_for(dependency.name).specs + end - search_result + def source_for(name) + @source_requirements[name] || @source_requirements[:default] end - def index_for(dependency) - source = @source_requirements[dependency.name] - if source - source.specs - elsif @lockfile_uses_separate_rubygems_sources - Index.build do |idx| - if dependency.all_sources - dependency.all_sources.each {|s| idx.add_source(s.specs) if s } - else - idx.add_source @source_requirements[:default].specs - end - end - else - @index - end + def results_for(dependency, base) + index_for(dependency).search(dependency, base) end def name_for(dependency) @@ -236,19 +198,8 @@ module Bundler dependencies.map(&:dep) == other_dependencies.map(&:dep) end - def relevant_sources_for_vertex(vertex) - if vertex.root? - [@source_requirements[vertex.name]] - elsif @lockfile_uses_separate_rubygems_sources - vertex.recursive_predecessors.map do |v| - @source_requirements[v.name] - end << @source_requirements[:default] - end - end - def sort_dependencies(dependencies, activated, conflicts) dependencies.sort_by do |dependency| - dependency.all_sources = relevant_sources_for_vertex(activated.vertex_named(dependency.name)) name = name_for(dependency) vertex = activated.vertex_named(name) [ @@ -302,12 +253,6 @@ module Bundler next if name == "bundler" next unless search_for(requirement).empty? - cache_message = begin - " or in gems cached in #{Bundler.settings.app_cache_path}" if Bundler.app_cache.exist? - rescue GemfileNotFound - nil - end - if (base = @base[name]) && !base.empty? version = base.first.version message = "You have requested:\n" \ @@ -316,18 +261,17 @@ module Bundler "Try running `bundle update #{name}`\n\n" \ "If you are updating multiple gems in your Gemfile at once,\n" \ "try passing them all to `bundle update`" - elsif source = @source_requirements[name] - specs = source.specs[name] + else + source = source_for(name) + specs = source.specs.search(name) versions_with_platforms = specs.map {|s| [s.version, s.platform] } + cache_message = begin + " or in gems cached in #{Bundler.settings.app_cache_path}" if Bundler.app_cache.exist? + rescue GemfileNotFound + nil + end message = String.new("Could not find gem '#{SharedHelpers.pretty_dependency(requirement)}' in #{source}#{cache_message}.\n") - message << if versions_with_platforms.any? - "The source contains the following versions of '#{name}': #{formatted_versions_with_platforms(versions_with_platforms)}" - else - "The source does not contain any versions of '#{name}'" - end - else - message = "Could not find gem '#{requirement}' in any of the gem sources " \ - "listed in your Gemfile#{cache_message}." + message << "The source contains the following versions of '#{name}': #{formatted_versions_with_platforms(versions_with_platforms)}" if versions_with_platforms.any? end raise GemNotFound, message end @@ -392,7 +336,7 @@ module Bundler if other_bundler_required o << "\n\n" - candidate_specs = @source_requirements[:default_bundler].specs.search(conflict_dependency) + candidate_specs = source_for(:default_bundler).specs.search(conflict_dependency) if candidate_specs.any? target_version = candidate_specs.last.version new_command = [File.basename($PROGRAM_NAME), "_#{target_version}_", *ARGV].join(" ") @@ -409,17 +353,7 @@ module Bundler elsif !conflict.existing o << "\n" - relevant_sources = if conflict.requirement.source - [conflict.requirement.source] - elsif conflict.requirement.all_sources - conflict.requirement.all_sources - elsif @lockfile_uses_separate_rubygems_sources - # every conflict should have an explicit group of sources when we - # enforce strict pinning - raise "no source set for #{conflict}" - else - [] - end.compact.map(&:to_s).uniq.sort + relevant_source = conflict.requirement.source || source_for(name) metadata_requirement = name.end_with?("\0") @@ -432,12 +366,10 @@ module Bundler end o << " " - o << if relevant_sources.empty? - "in any of the sources.\n" - elsif metadata_requirement - "is not available in #{relevant_sources.join(" or ")}" + o << if metadata_requirement + "is not available in #{relevant_source}" else - "in any of the relevant sources:\n #{relevant_sources * "\n "}\n" + "in #{relevant_source}.\n" end end end, @@ -451,29 +383,5 @@ module Bundler end ) end - - def validate_resolved_specs!(resolved_specs) - resolved_specs.each do |v| - name = v.name - next unless sources = relevant_sources_for_vertex(v) - sources.compact! - if default_index = sources.index(@source_requirements[:default]) - sources.delete_at(default_index) - end - sources.reject! {|s| s.specs[name].empty? } - sources.uniq! - next if sources.size <= 1 - - multisource_disabled = Bundler.feature_flag.disable_multisource? - - msg = ["The gem '#{name}' was found in multiple relevant sources."] - msg.concat sources.map {|s| " * #{s}" }.sort - msg << "You #{multisource_disabled ? :must : :should} add this gem to the source block for the source you wish it to be installed from." - msg = msg.join("\n") - - raise SecurityError, msg if multisource_disabled - Bundler.ui.warn "Warning: #{msg}" - end - end end end diff --git a/lib/bundler/resolver/spec_group.rb b/lib/bundler/resolver/spec_group.rb index 73ffec5838..8f4fd18c46 100644 --- a/lib/bundler/resolver/spec_group.rb +++ b/lib/bundler/resolver/spec_group.rb @@ -21,14 +21,10 @@ module Bundler @version = exemplary_spec.version @source = exemplary_spec.source - @all_platforms = relevant_platforms @activated_platforms = relevant_platforms @dependencies = Hash.new do |dependencies, platforms| dependencies[platforms] = dependencies_for(platforms) end - @partitioned_dependency_names = Hash.new do |partitioned_dependency_names, platforms| - partitioned_dependency_names[platforms] = partitioned_dependency_names_for(platforms) - end @specs = specs end @@ -45,14 +41,6 @@ module Bundler end.flatten.compact.uniq end - def activate_platform!(platform) - self.activated_platforms = [platform] - end - - def activate_all_platforms! - self.activated_platforms = @all_platforms - end - def to_s activated_platforms_string = sorted_activated_platforms.join(", ") "#{name} (#{version}) (#{activated_platforms_string})" @@ -62,10 +50,6 @@ module Bundler @dependencies[activated_platforms] end - def partitioned_dependency_names_for_activated_platforms - @partitioned_dependency_names[activated_platforms] - end - def ==(other) return unless other.is_a?(SpecGroup) name == other.name && @@ -100,14 +84,6 @@ module Bundler end.flatten end - def partitioned_dependency_names_for(platforms) - return @dependencies[platforms].map(&:name), [] if platforms.size == 1 - - @dependencies[platforms].partition do |dep_proxy| - @dependencies[platforms].count {|dp| dp.dep == dep_proxy.dep } == platforms.size - end.map {|deps| deps.map(&:name) } - end - def __dependencies(platform) dependencies = [] @specs[platform].first.dependencies.each do |dep| diff --git a/lib/bundler/retry.rb b/lib/bundler/retry.rb index e95f15f7fb..2415ade200 100644 --- a/lib/bundler/retry.rb +++ b/lib/bundler/retry.rb @@ -49,7 +49,7 @@ module Bundler raise e end return true unless name - Bundler.ui.info "" unless Bundler.ui.debug? # Add new line incase dots preceded this + Bundler.ui.info "" unless Bundler.ui.debug? # Add new line in case dots preceded this Bundler.ui.warn "Retrying #{name} due to error (#{current_run.next}/#{total_runs}): #{e.class} #{e.message}", Bundler.ui.debug? end diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index 2bd2dcb451..a3efff1e86 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -85,6 +85,10 @@ module Gem dependencies - development_dependencies end + def deleted_gem? + !default_gem? && !File.directory?(full_gem_path) + end + private def dependencies_to_gemfile(dependencies, group = nil) @@ -105,7 +109,7 @@ module Gem end class Dependency - attr_accessor :source, :groups, :all_sources + attr_accessor :source, :groups alias_method :eql?, :== @@ -116,7 +120,7 @@ module Gem end def to_yaml_properties - instance_variables.reject {|p| ["@source", "@groups", "@all_sources"].include?(p.to_s) } + instance_variables.reject {|p| ["@source", "@groups"].include?(p.to_s) } end def to_lock @@ -174,20 +178,36 @@ module Gem end end + require "rubygems/platform" + class Platform JAVA = Gem::Platform.new("java") unless defined?(JAVA) MSWIN = Gem::Platform.new("mswin32") unless defined?(MSWIN) MSWIN64 = Gem::Platform.new("mswin64") unless defined?(MSWIN64) MINGW = Gem::Platform.new("x86-mingw32") unless defined?(MINGW) X64_MINGW = Gem::Platform.new("x64-mingw32") unless defined?(X64_MINGW) + end - undef_method :hash if method_defined? :hash - def hash - @cpu.hash ^ @os.hash ^ @version.hash - end + Platform.singleton_class.module_eval do + unless Platform.singleton_methods.include?(:match_spec?) + def match_spec?(spec) + match_gem?(spec.platform, spec.name) + end - undef_method :eql? if method_defined? :eql? - alias_method :eql?, :== + def match_gem?(platform, gem_name) + match_platforms?(platform, Gem.platforms) + end + + private + + def match_platforms?(platform, platforms) + platforms.any? do |local_platform| + platform.nil? || + local_platform == platform || + (local_platform != Gem::Platform::RUBY && local_platform =~ platform) + end + end + end end require "rubygems/util" diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb index cd5eb152f9..bb9f1cb3f5 100644 --- a/lib/bundler/rubygems_gem_installer.rb +++ b/lib/bundler/rubygems_gem_installer.rb @@ -8,13 +8,68 @@ module Bundler # Bundler needs to install gems regardless of binstub overwriting end + def install + pre_install_checks + + run_pre_install_hooks + + spec.loaded_from = spec_file + + # Completely remove any previous gem files + strict_rm_rf gem_dir + strict_rm_rf spec.extension_dir + + SharedHelpers.filesystem_access(gem_dir, :create) do + FileUtils.mkdir_p gem_dir, :mode => 0o755 + end + + extract_files + + build_extensions + write_build_info_file + run_post_build_hooks + + generate_bin + generate_plugins + + write_spec + + SharedHelpers.filesystem_access("#{gem_home}/cache", :write) do + write_cache_file + end + + say spec.post_install_message unless spec.post_install_message.nil? + + run_post_install_hooks + + spec + end + + def generate_plugins + return unless Gem::Installer.instance_methods(false).include?(:generate_plugins) + + latest = Gem::Specification.stubs_for(spec.name).first + return if latest && latest.version > spec.version + + ensure_writable_dir @plugins_dir + + if spec.plugins.empty? + remove_plugins_for(spec, @plugins_dir) + else + regenerate_plugins_for(spec, @plugins_dir) + end + end + def pre_install_checks super && validate_bundler_checksum(options[:bundler_expected_checksum]) end def build_extensions extension_cache_path = options[:bundler_extension_cache_path] - return super unless extension_cache_path && extension_dir = spec.extension_dir + unless extension_cache_path && extension_dir = spec.extension_dir + require "shellwords" # compensate missing require in rubygems before version 3.2.25 + return super + end extension_dir = Pathname.new(extension_dir) build_complete = SharedHelpers.filesystem_access(extension_cache_path.join("gem.build_complete"), :read, &:file?) @@ -24,6 +79,7 @@ module Bundler FileUtils.cp_r extension_cache_path, spec.extension_dir end else + require "shellwords" # compensate missing require in rubygems before version 3.2.25 super if extension_dir.directory? # not made for gems without extensions SharedHelpers.filesystem_access(extension_cache_path.parent, &:mkpath) @@ -36,6 +92,17 @@ module Bundler private + def strict_rm_rf(dir) + # FileUtils.rm_rf should probably rise in case of permission issues like + # `rm -rf` does. However, it fails to delete the folder silently due to + # https://github.com/ruby/fileutils/issues/57. It should probably be fixed + # inside `fileutils` but for now I`m checking whether the folder was + # removed after it completes, and raising otherwise. + FileUtils.rm_rf dir + + raise PermissionError.new(dir, :delete) if File.directory?(dir) + end + def validate_bundler_checksum(checksum) return true if Bundler.settings[:disable_checksum_validation] return true unless checksum diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb index d060e21f50..f6d59baf15 100644 --- a/lib/bundler/rubygems_integration.rb +++ b/lib/bundler/rubygems_integration.rb @@ -34,10 +34,12 @@ module Bundler end def build_args + require "rubygems/command" Gem::Command.build_args end def build_args=(args) + require "rubygems/command" Gem::Command.build_args = args end @@ -84,16 +86,12 @@ module Bundler def spec_missing_extensions?(spec, default = true) return spec.missing_extensions? if spec.respond_to?(:missing_extensions?) - return false if spec_default_gem?(spec) + return false if spec.default_gem? return false if spec.extensions.empty? default end - def spec_default_gem?(spec) - spec.respond_to?(:default_gem?) && spec.default_gem? - end - def spec_matches_for_glob(spec, glob) return spec.matches_for_glob(glob) if spec.respond_to?(:matches_for_glob) @@ -502,14 +500,15 @@ module Bundler end def fetch_specs(remote, name) + require "rubygems/remote_fetcher" path = remote.uri.to_s + "#{name}.#{Gem.marshal_version}.gz" fetcher = gem_remote_fetcher fetcher.headers = { "X-Gemfile-Source" => remote.original_uri.to_s } if remote.original_uri string = fetcher.fetch_path(path) Bundler.load_marshal(string) - rescue Gem::RemoteFetcher::FetchError => e + rescue Gem::RemoteFetcher::FetchError # it's okay for prerelease to fail - raise e unless name == "prerelease_specs" + raise unless name == "prerelease_specs" end def fetch_all_remote_specs(remote) @@ -519,20 +518,41 @@ module Bundler specs.concat(pres) end - def download_gem(spec, uri, path) + def download_gem(spec, uri, cache_dir) + require "rubygems/remote_fetcher" uri = Bundler.settings.mirror_for(uri) fetcher = gem_remote_fetcher fetcher.headers = { "X-Gemfile-Source" => spec.remote.original_uri.to_s } if spec.remote.original_uri Bundler::Retry.new("download gem from #{uri}").attempts do - fetcher.download(spec, uri, path) + gem_file_name = spec.file_name + local_gem_path = File.join cache_dir, gem_file_name + return if File.exist? local_gem_path + + begin + remote_gem_path = uri + "gems/#{gem_file_name}" + remote_gem_path = remote_gem_path.to_s if provides?("< 3.2.0.rc.1") + + SharedHelpers.filesystem_access(local_gem_path) do + fetcher.cache_update_path remote_gem_path, local_gem_path + end + rescue Gem::RemoteFetcher::FetchError + raise if spec.original_platform == spec.platform + + original_gem_file_name = "#{spec.original_name}.gem" + raise if gem_file_name == original_gem_file_name + + gem_file_name = original_gem_file_name + retry + end end + rescue Gem::RemoteFetcher::FetchError => e + raise Bundler::HTTPError, "Could not download gem from #{uri} due to underlying error <#{e.message}>" end def gem_remote_fetcher - require "resolv" + require "rubygems/remote_fetcher" proxy = configuration[:http_proxy] - dns = Resolv::DNS.new - Gem::RemoteFetcher.new(proxy, dns) + Gem::RemoteFetcher.new(proxy) end def gem_from_path(path, policy = nil) diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index e259b590bf..c7276b0e25 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -12,22 +12,16 @@ module Bundler def setup(*groups) @definition.ensure_equivalent_gemfile_and_lockfile if Bundler.frozen_bundle? - groups.map!(&:to_sym) - # Has to happen first clean_load_path - specs = groups.any? ? @definition.specs_for(groups) : requested_specs + specs = @definition.specs_for(groups) SharedHelpers.set_bundle_environment Bundler.rubygems.replace_entrypoints(specs) # Activate the specs load_paths = specs.map do |spec| - unless spec.loaded_from - raise GemNotFound, "#{spec.full_name} is missing. Run `bundle install` to get it." - end - check_for_activated_spec!(spec) Bundler.rubygems.mark_loaded(spec) @@ -106,7 +100,7 @@ module Bundler alias_method :gems, :specs - def cache(custom_path = nil) + def cache(custom_path = nil, local = false) cache_path = Bundler.app_cache(custom_path) SharedHelpers.filesystem_access(cache_path) do |p| FileUtils.mkdir_p(p) @@ -114,7 +108,20 @@ module Bundler Bundler.ui.info "Updating files in #{Bundler.settings.app_cache_path}" - specs_to_cache = Bundler.settings[:cache_all_platforms] ? @definition.resolve.materialized_for_all_platforms : specs + specs_to_cache = if Bundler.settings[:cache_all_platforms] + @definition.resolve.materialized_for_all_platforms + else + begin + specs + rescue GemNotFound + if local + Bundler.ui.warn "Some gems seem to be missing from your #{Bundler.settings.app_cache_path} directory." + end + + raise + end + end + specs_to_cache.each do |spec| next if spec.name == "bundler" next if spec.source.is_a?(Source::Gemspec) @@ -258,7 +265,7 @@ module Bundler return if manuals.empty? Bundler::SharedHelpers.set_env "MANPATH", manuals.concat( - ENV["MANPATH"].to_s.split(File::PATH_SEPARATOR) + ENV["MANPATH"] ? ENV["MANPATH"].to_s.split(File::PATH_SEPARATOR) : [""] ).uniq.join(File::PATH_SEPARATOR) end @@ -284,7 +291,7 @@ module Bundler return unless activated_spec = Bundler.rubygems.loaded_specs(spec.name) return if activated_spec.version == spec.version - suggestion = if Bundler.rubygems.spec_default_gem?(activated_spec) + suggestion = if activated_spec.default_gem? "Since #{spec.name} is a default gem, you can either remove your dependency on it" \ " or try updating to a newer version of bundler that supports #{spec.name} as a default gem." else diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index 749e4eb60e..1ced590b6f 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -13,27 +13,28 @@ module Bundler auto_install cache_all cache_all_platforms + clean default_install_uses_path deployment - deployment_means_frozen disable_checksum_validation disable_exec_load disable_local_branch_check disable_local_revision_check - disable_multisource disable_shared_gems disable_version_check force_ruby_platform forget_cli_options frozen + gem.changelog gem.coc gem.mit + git.allow_insecure global_gem_cache ignore_messages init_gems_rb + inline no_install no_prune - only_update_to_newer_versions path_relative_to_cwd path.system plugins @@ -43,7 +44,6 @@ module Bundler silence_deprecations silence_root_warning suppress_install_using_messages - unlock_source_unlocks_spec update_requires_all_flag use_gem_version_promoter_for_major_updates ].freeze @@ -61,6 +61,22 @@ module Bundler without ].freeze + STRING_KEYS = %w[ + bin + cache_path + console + gem.ci + gem.github_username + gem.linter + gem.rubocop + gem.test + gemfile + path + shebang + system_bindir + trust-policy + ].freeze + DEFAULT_CONFIG = { "BUNDLE_SILENCE_DEPRECATIONS" => false, "BUNDLE_DISABLE_VERSION_CHECK" => true, @@ -126,8 +142,8 @@ module Bundler keys = @temporary.keys | @global_config.keys | @local_config.keys | @env_config.keys keys.map do |key| - key.sub(/^BUNDLE_/, "").gsub(/__/, ".").downcase - end + key.sub(/^BUNDLE_/, "").gsub(/___/, "-").gsub(/__/, ".").downcase + end.sort end def local_overrides @@ -173,25 +189,32 @@ module Bundler locations = [] if value = @temporary[key] - locations << "Set for the current command: #{converted_value(value, exposed_key).inspect}" + locations << "Set for the current command: #{printable_value(value, exposed_key).inspect}" end if value = @local_config[key] - locations << "Set for your local app (#{local_config_file}): #{converted_value(value, exposed_key).inspect}" + locations << "Set for your local app (#{local_config_file}): #{printable_value(value, exposed_key).inspect}" end if value = @env_config[key] - locations << "Set via #{key}: #{converted_value(value, exposed_key).inspect}" + locations << "Set via #{key}: #{printable_value(value, exposed_key).inspect}" end if value = @global_config[key] - locations << "Set for the current user (#{global_config_file}): #{converted_value(value, exposed_key).inspect}" + locations << "Set for the current user (#{global_config_file}): #{printable_value(value, exposed_key).inspect}" end return ["You have not configured a value for `#{exposed_key}`"] if locations.empty? locations end + def processor_count + require "etc" + Etc.nprocessors + rescue StandardError + 1 + end + # for legacy reasons, in Bundler 2, we do not respect :disable_shared_gems def path configs.each do |_level, settings| @@ -277,9 +300,7 @@ module Bundler end def key_for(key) - key = Settings.normalize_uri(key).to_s if key.is_a?(String) && /https?:/ =~ key - key = key.to_s.gsub(".", "__").upcase - "BUNDLE_#{key}" + self.class.key_for(key) end private @@ -314,6 +335,10 @@ module Bundler BOOL_KEYS.include?(name.to_s) || BOOL_KEYS.include?(parent_setting_for(name.to_s)) end + def is_string(name) + STRING_KEYS.include?(name.to_s) || name.to_s.start_with?("local.") || name.to_s.start_with?("mirror.") || name.to_s.start_with?("build.") + end + def to_bool(value) case value when nil, /\A(false|f|no|n|0|)\z/i, false @@ -331,6 +356,14 @@ module Bundler ARRAY_KEYS.include?(key.to_s) end + def is_credential(key) + key == "gem.push_key" + end + + def is_userinfo(value) + value.include?(":") + end + def to_array(value) return [] unless value value.split(":").map(&:to_sym) @@ -377,15 +410,38 @@ module Bundler end end + def printable_value(value, key) + converted = converted_value(value, key) + return converted unless converted.is_a?(String) + + if is_string(key) + converted + elsif is_credential(key) + "[REDACTED]" + elsif is_userinfo(converted) + username, pass = converted.split(":", 2) + + if pass == "x-oauth-basic" + username = "[REDACTED]" + else + pass = "[REDACTED]" + end + + [username, pass].join(":") + else + converted + end + end + def global_config_file if ENV["BUNDLE_CONFIG"] && !ENV["BUNDLE_CONFIG"].empty? Pathname.new(ENV["BUNDLE_CONFIG"]) - else - begin - Bundler.user_bundle_path("config") - rescue PermissionError, GenericSystemCallError - nil - end + elsif ENV["BUNDLE_USER_CONFIG"] && !ENV["BUNDLE_USER_CONFIG"].empty? + Pathname.new(ENV["BUNDLE_USER_CONFIG"]) + elsif ENV["BUNDLE_USER_HOME"] && !ENV["BUNDLE_USER_HOME"].empty? + Pathname.new(ENV["BUNDLE_USER_HOME"]).join("config") + elsif Bundler.rubygems.user_home && !Bundler.rubygems.user_home.empty? + Pathname.new(Bundler.rubygems.user_home).join(".bundle/config") end end @@ -399,7 +455,20 @@ module Bundler valid_file = file.exist? && !file.size.zero? return {} unless valid_file require_relative "yaml_serializer" - YAMLSerializer.load file.read + YAMLSerializer.load(file.read).inject({}) do |config, (k, v)| + new_k = k + + if k.include?("-") + Bundler.ui.warn "Your #{file} config includes `#{k}`, which contains the dash character (`-`).\n" \ + "This is deprecated, because configuration through `ENV` should be possible, but `ENV` keys cannot include dashes.\n" \ + "Please edit #{file} and replace any dashes in configuration keys with a triple underscore (`___`)." + + new_k = k.gsub("-", "___") + end + + config[new_k] = v + config + end end end @@ -416,6 +485,12 @@ module Bundler \z /ix.freeze + def self.key_for(key) + key = normalize_uri(key).to_s if key.is_a?(String) && /https?:/ =~ key + key = key.to_s.gsub(".", "__").gsub("-", "___").upcase + "BUNDLE_#{key}" + end + # TODO: duplicates Rubygems#normalize_uri # TODO: is this the correct place to validate mirror URIs? def self.normalize_uri(uri) diff --git a/lib/bundler/setup.rb b/lib/bundler/setup.rb index 27911dc1ad..32e9b2d7c0 100644 --- a/lib/bundler/setup.rb +++ b/lib/bundler/setup.rb @@ -9,10 +9,10 @@ if Bundler::SharedHelpers.in_bundle? begin Bundler.ui.silence { Bundler.setup } rescue Bundler::BundlerError => e - Bundler.ui.warn "\e[31m#{e.message}\e[0m" + Bundler.ui.error e.message Bundler.ui.warn e.backtrace.join("\n") if ENV["DEBUG"] if e.is_a?(Bundler::GemNotFound) - Bundler.ui.warn "\e[33mRun `bundle install` to install missing gems.\e[0m" + Bundler.ui.warn "Run `bundle install` to install missing gems." end exit e.status_code end diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 09b79acbf9..df1c136c56 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -152,13 +152,6 @@ module Bundler Bundler.ui.warn message end - def trap(signal, override = false, &block) - prior = Signal.trap(signal) do - block.call - prior.call unless override - end - end - def ensure_same_dependencies(spec, old_deps, new_deps) new_deps = new_deps.reject {|d| d.type == :development } old_deps = old_deps.reject {|d| d.type == :development } @@ -327,12 +320,11 @@ module Bundler end def clean_load_path - bundler_lib = bundler_ruby_lib - loaded_gem_paths = Bundler.rubygems.loaded_gem_paths $LOAD_PATH.reject! do |p| - next if resolve_path(p).start_with?(bundler_lib) + resolved_path = resolve_path(p) + next if $LOADED_FEATURES.any? {|lf| lf.start_with?(resolved_path) } loaded_gem_paths.delete(p) end $LOAD_PATH.uniq! diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb index be00143f5a..2a2b332cff 100644 --- a/lib/bundler/source.rb +++ b/lib/bundler/source.rb @@ -7,6 +7,7 @@ module Bundler autoload :Metadata, File.expand_path("source/metadata", __dir__) autoload :Path, File.expand_path("source/path", __dir__) autoload :Rubygems, File.expand_path("source/rubygems", __dir__) + autoload :RubygemsAggregate, File.expand_path("source/rubygems_aggregate", __dir__) attr_accessor :dependency_names @@ -33,6 +34,18 @@ module Bundler spec.source == self end + def local!; end + + def local_only!; end + + def cached!; end + + def remote!; end + + def add_dependency_names(names) + @dependency_names = Array(dependency_names) | Array(names) + end + # it's possible that gems from one source depend on gems from some # other source, so now we download gemspecs and iterate over those # dependencies, looking for gems we don't have info on yet. @@ -42,6 +55,10 @@ module Bundler specs.dependency_names end + def spec_names + specs.spec_names + end + def include?(other) other == self end @@ -50,6 +67,10 @@ module Bundler "#<#{self.class}:0x#{object_id} #{self}>" end + def identifier + to_s + end + def path? instance_of?(Bundler::Source::Path) end diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index fb13ca0578..a41a2f23e9 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -42,7 +42,7 @@ module Bundler %w[ref branch tag submodules].each do |opt| out << " #{opt}: #{options[opt]}\n" if options[opt] end - out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB + out << " glob: #{@glob}\n" unless default_glob? out << " specs:\n" end @@ -75,12 +75,20 @@ module Bundler git_proxy.branch end - rev = " (at #{at}@#{shortref_for_display(revision)})" + rev = "at #{at}@#{shortref_for_display(revision)}" rescue GitError "" end - "#{@safe_uri}#{rev}" + specifiers = [rev, glob_for_display].compact + suffix = + if specifiers.any? + " (#{specifiers.join(", ")})" + else + "" + end + + "#{@safe_uri}#{suffix}" end def name @@ -282,6 +290,14 @@ module Bundler ref[0..11] end + def glob_for_display + default_glob? ? nil : "glob: #{@glob}" + end + + def default_glob? + @glob == DEFAULT_GLOB + end + def uri_hash if uri =~ %r{^\w+://(\w+@)?} # Downcase the domain component of the URI @@ -291,7 +307,9 @@ module Bundler # If there is no URI scheme, assume it is an ssh/git URI input = uri end - SharedHelpers.digest(:SHA1).hexdigest(input) + # We use SHA1 here for historical reason and to preserve backward compatibility. + # But a transition to a simpler mangling algorithm would be welcome. + Bundler::Digest.sha1(input) end def cached_revision diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb index ae21770306..745a7fe118 100644 --- a/lib/bundler/source/git/git_proxy.rb +++ b/lib/bundler/source/git/git_proxy.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "shellwords" - module Bundler class Source class Git @@ -58,7 +56,6 @@ module Bundler @ref = ref @revision = revision @git = git - raise GitNotInstalledError.new if allow? && !Bundler.git_present? end def revision @@ -98,12 +95,12 @@ module Bundler SharedHelpers.filesystem_access(path.dirname) do |p| FileUtils.mkdir_p(p) end - git_retry "clone", configured_uri, path.to_s, "--bare", "--no-hardlinks", "--quiet" + git_retry "clone", "--bare", "--no-hardlinks", "--quiet", "--", configured_uri, path.to_s return unless extra_ref end with_path do - git_retry(*["fetch", "--force", "--quiet", "--tags", configured_uri, "refs/heads/*:refs/heads/*", extra_ref].compact, :dir => path) + git_retry(*["fetch", "--force", "--quiet", "--tags", "--", configured_uri, "refs/heads/*:refs/heads/*", extra_ref].compact, :dir => path) end end @@ -210,7 +207,11 @@ module Bundler end def allow? - @git ? @git.allow_git_ops? : true + allowed = @git ? @git.allow_git_ops? : true + + raise GitNotInstalledError.new if allowed && !Bundler.git_present? + + allowed end def with_path(&blk) @@ -224,6 +225,7 @@ module Bundler end def check_allowed(command) + require "shellwords" command_with_no_credentials = URICredentialsFilter.credential_filtered_string("git #{command.shelljoin}", uri) raise GitNotAllowedError.new(command_with_no_credentials) unless allow? command_with_no_credentials diff --git a/lib/bundler/source/metadata.rb b/lib/bundler/source/metadata.rb index 0867879861..50b65ce0ea 100644 --- a/lib/bundler/source/metadata.rb +++ b/lib/bundler/source/metadata.rb @@ -33,10 +33,6 @@ module Bundler end end - def cached!; end - - def remote!; end - def options {} end diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb index 2c2e9023b3..01f89b204d 100644 --- a/lib/bundler/source/path.rb +++ b/lib/bundler/source/path.rb @@ -82,7 +82,9 @@ module Bundler end def install(spec, options = {}) - print_using_message "Using #{version_message(spec)} from #{self}" + using_message = "Using #{version_message(spec)} from #{self}" + using_message += " and installing its executables" unless spec.executables.empty? + print_using_message using_message generate_bin(spec, :disable_extensions => true) nil # no post-install message end diff --git a/lib/bundler/source/path/installer.rb b/lib/bundler/source/path/installer.rb index 72bfbb4836..a70973bde7 100644 --- a/lib/bundler/source/path/installer.rb +++ b/lib/bundler/source/path/installer.rb @@ -35,7 +35,7 @@ module Bundler run_hooks(:post_build) end - generate_bin unless spec.executables.nil? || spec.executables.empty? + generate_bin unless spec.executables.empty? run_hooks(:post_install) ensure diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 5b89b1645d..8bc3aa17e9 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -20,18 +20,38 @@ module Bundler @dependency_names = [] @allow_remote = false @allow_cached = false + @allow_local = options["allow_local"] || false @caches = [cache_path, *Bundler.rubygems.gem_cache] - Array(options["remotes"] || []).reverse_each {|r| add_remote(r) } + Array(options["remotes"]).reverse_each {|r| add_remote(r) } + end + + def local_only! + @specs = nil + @allow_local = true + @allow_cached = false + @allow_remote = false + end + + def local! + return if @allow_local + + @specs = nil + @allow_local = true end def remote! + return if @allow_remote + @specs = nil @allow_remote = true end def cached! + return if @allow_cached + @specs = nil + @allow_local = true @allow_cached = true end @@ -49,9 +69,17 @@ module Bundler o.is_a?(Rubygems) && (o.credless_remotes - credless_remotes).empty? end + def multiple_remotes? + @remotes.size > 1 + end + + def no_remotes? + @remotes.size == 0 + end + def can_lock?(spec) - return super if Bundler.feature_flag.disable_multisource? - spec.source.is_a?(Rubygems) + return super unless multiple_remotes? + include?(spec.source) end def options @@ -73,12 +101,27 @@ module Bundler def to_s if remotes.empty? "locally installed gems" - else - remote_names = remotes.map(&:to_s).join(", ") + elsif @allow_remote && @allow_cached && @allow_local + "rubygems repository #{remote_names}, cached gems or installed locally" + elsif @allow_remote && @allow_local "rubygems repository #{remote_names} or installed locally" + elsif @allow_remote + "rubygems repository #{remote_names}" + elsif @allow_cached && @allow_local + "cached gems or installed locally" + else + "locally installed gems" end end - alias_method :name, :to_s + + def identifier + if remotes.empty? + "locally installed gems" + else + "rubygems repository #{remote_names}" + end + end + alias_method :name, :identifier def specs @specs ||= begin @@ -87,7 +130,7 @@ module Bundler # small_idx.use large_idx. idx = @allow_remote ? remote_specs.dup : Index.new idx.use(cached_specs, :override_dupes) if @allow_cached || @allow_remote - idx.use(installed_specs, :override_dupes) + idx.use(installed_specs, :override_dupes) if @allow_local idx end end @@ -96,7 +139,7 @@ module Bundler force = opts[:force] ensure_builtin_gems_cached = opts[:ensure_builtin_gems_cached] - if ensure_builtin_gems_cached && builtin_gem?(spec) + if ensure_builtin_gems_cached && spec.default_gem? if !cached_path(spec) cached_built_in_gem(spec) unless spec.remote force = true @@ -105,7 +148,7 @@ module Bundler end end - if (installed?(spec) || Plugin.installed?(spec.name)) && !force + if installed?(spec) && !force print_using_message "Using #{version_message(spec)}" return nil # no post-install message end @@ -123,7 +166,7 @@ module Bundler begin s = Bundler.rubygems.spec_from_gem(path, Bundler.settings["trust-policy"]) spec.__swap__(s) - rescue StandardError + rescue Gem::Package::FormatError Bundler.rm_rf(path) raise end @@ -135,6 +178,7 @@ module Bundler Bundler.ui.confirm message path = cached_gem(spec) + raise GemNotFound, "Could not find #{spec.file_name} for installation" unless path if requires_sudo? install_path = Bundler.tmp(spec.full_name) bin_path = install_path.join("bin") @@ -194,12 +238,8 @@ module Bundler end def cache(spec, custom_path = nil) - if builtin_gem?(spec) - cached_path = cached_built_in_gem(spec) - else - cached_path = cached_gem(spec) - end - raise GemNotFound, "Missing gem file '#{spec.full_name}.gem'." unless cached_path + cached_path = cached_gem(spec) + raise GemNotFound, "Missing gem file '#{spec.file_name}'." unless cached_path return if File.dirname(cached_path) == Bundler.app_cache.to_s Bundler.ui.info " * #{File.basename(cached_path)}" FileUtils.cp(cached_path, Bundler.app_cache(custom_path)) @@ -226,25 +266,16 @@ module Bundler @remotes.unshift(uri) unless @remotes.include?(uri) end - def equivalent_remotes?(other_remotes) - other_remotes.map(&method(:remove_auth)) == @remotes.map(&method(:remove_auth)) - end - - def replace_remotes(other_remotes, allow_equivalent = false) - return false if other_remotes == @remotes - - equivalent = allow_equivalent && equivalent_remotes?(other_remotes) - - @remotes = [] - other_remotes.reverse_each do |r| - add_remote r.to_s + def spec_names + if @allow_remote && dependency_api_available? + remote_specs.spec_names + else + [] end - - !equivalent end def unmet_deps - if @allow_remote && api_fetchers.any? + if @allow_remote && dependency_api_available? remote_specs.unmet_dependency_names else [] @@ -260,7 +291,7 @@ module Bundler def double_check_for(unmet_dependency_names) return unless @allow_remote - return unless api_fetchers.any? + return unless dependency_api_available? unmet_dependency_names = unmet_dependency_names.call unless unmet_dependency_names.nil? @@ -282,21 +313,32 @@ module Bundler remote_specs.each do |spec| case spec when EndpointSpecification, Gem::Specification, StubSpecification, LazySpecification - names.concat(spec.runtime_dependencies) + names.concat(spec.runtime_dependencies.map(&:name)) when RemoteSpecification # from the full index return nil else raise "unhandled spec type (#{spec.inspect})" end end - names.map!(&:name) if names names end + def dependency_api_available? + api_fetchers.any? + end + protected + def remote_names + remotes.map(&:to_s).join(", ") + end + def credless_remotes - remotes.map(&method(:suppress_configured_credentials)) + if Bundler.settings[:allow_deployment_source_credential_changes] + remotes.map(&method(:remove_auth)) + else + remotes.map(&method(:suppress_configured_credentials)) + end end def remotes_for_spec(spec) @@ -311,14 +353,17 @@ module Bundler end def cached_gem(spec) - cached_gem = cached_path(spec) - unless cached_gem - raise Bundler::GemNotFound, "Could not find #{spec.file_name} for installation" + if spec.default_gem? + cached_built_in_gem(spec) + else + cached_path(spec) end - cached_gem end def cached_path(spec) + global_cache_path = download_cache_path(spec) + @caches << global_cache_path if global_cache_path + possibilities = @caches.map {|p| "#{p}/#{spec.file_name}" } possibilities.find {|p| File.exist?(p) } end @@ -365,16 +410,12 @@ module Bundler def cached_specs @cached_specs ||= begin - idx = installed_specs.dup + idx = @allow_local ? installed_specs.dup : Index.new Dir["#{cache_path}/*.gem"].each do |gemfile| next if gemfile =~ /^bundler\-[\d\.]+?\.gem/ s ||= Bundler.rubygems.spec_from_gem(gemfile) s.source = self - if Bundler.rubygems.spec_missing_extensions?(s, false) - Bundler.ui.debug "Source #{self} is ignoring #{s} because it is missing extensions" - next - end idx << s end @@ -407,11 +448,11 @@ module Bundler def fetch_names(fetchers, dependency_names, index, override_dupes) fetchers.each do |f| if dependency_names - Bundler.ui.info "Fetching gem metadata from #{f.uri}", Bundler.ui.debug? + Bundler.ui.info "Fetching gem metadata from #{URICredentialsFilter.credential_filtered_uri(f.uri)}", Bundler.ui.debug? index.use f.specs_with_retry(dependency_names, self), override_dupes Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over else - Bundler.ui.info "Fetching source index from #{f.uri}" + Bundler.ui.info "Fetching source index from #{URICredentialsFilter.credential_filtered_uri(f.uri)}" index.use f.specs_with_retry(nil, self), override_dupes end end @@ -422,19 +463,26 @@ module Bundler spec.fetch_platform - download_path = requires_sudo? ? Bundler.tmp(spec.full_name) : rubygems_dir - gem_path = "#{rubygems_dir}/cache/#{spec.full_name}.gem" + cache_path = download_cache_path(spec) || default_cache_path_for(rubygems_dir) + gem_path = "#{cache_path}/#{spec.file_name}" - SharedHelpers.filesystem_access("#{download_path}/cache") do |p| + if requires_sudo? + download_path = Bundler.tmp(spec.full_name) + download_cache_path = default_cache_path_for(download_path) + else + download_cache_path = cache_path + end + + SharedHelpers.filesystem_access(download_cache_path) do |p| FileUtils.mkdir_p(p) end - download_gem(spec, download_path) + download_gem(spec, download_cache_path) if requires_sudo? - SharedHelpers.filesystem_access("#{rubygems_dir}/cache") do |p| + SharedHelpers.filesystem_access(cache_path) do |p| Bundler.mkdir_p(p) end - Bundler.sudo "mv #{download_path}/cache/#{spec.full_name}.gem #{gem_path}" + Bundler.sudo "mv #{download_cache_path}/#{spec.file_name} #{gem_path}" end gem_path @@ -442,16 +490,8 @@ module Bundler Bundler.rm_rf(download_path) if requires_sudo? end - def builtin_gem?(spec) - # Ruby 2.1, where all included gems have this summary - return true if spec.summary =~ /is bundled with Ruby/ - - # Ruby 2.0, where gemspecs are stored in specifications/default/ - spec.loaded_from && spec.loaded_from.include?("specifications/default/") - end - def installed?(spec) - installed_specs[spec].any? + installed_specs[spec].any? && !spec.deleted_gem? end def requires_sudo? @@ -462,6 +502,10 @@ module Bundler Bundler.rubygems.gem_dir end + def default_cache_path_for(dir) + "#{dir}/cache" + end + def cache_path Bundler.app_cache end @@ -474,52 +518,13 @@ module Bundler # @param [Specification] spec # the spec we want to download or retrieve from the cache. # - # @param [String] download_path + # @param [String] download_cache_path # the local directory the .gem will end up in. # - def download_gem(spec, download_path) - local_path = File.join(download_path, "cache/#{spec.full_name}.gem") - - if (cache_path = download_cache_path(spec)) && cache_path.file? - SharedHelpers.filesystem_access(local_path) do - FileUtils.cp(cache_path, local_path) - end - else - uri = spec.remote.uri - Bundler.ui.confirm("Fetching #{version_message(spec)}") - rubygems_local_path = Bundler.rubygems.download_gem(spec, uri, download_path) - - # older rubygems return varying file:// variants depending on version - rubygems_local_path = rubygems_local_path.gsub(/\Afile:/, "") unless Bundler.rubygems.provides?(">= 3.2.0.rc.2") - rubygems_local_path = rubygems_local_path.gsub(%r{\A//}, "") if Bundler.rubygems.provides?("< 3.1.0") - - if rubygems_local_path != local_path - SharedHelpers.filesystem_access(local_path) do - FileUtils.mv(rubygems_local_path, local_path) - end - end - cache_globally(spec, local_path) - end - end - - # Checks if the requested spec exists in the global cache. If it does - # not, we create the relevant global cache subdirectory if it does not - # exist and copy the spec from the local cache to the global cache. - # - # @param [Specification] spec - # the spec we want to copy to the global cache. - # - # @param [String] local_cache_path - # the local directory from which we want to copy the .gem. - # - def cache_globally(spec, local_cache_path) - return unless cache_path = download_cache_path(spec) - return if cache_path.exist? - - SharedHelpers.filesystem_access(cache_path.dirname, &:mkpath) - SharedHelpers.filesystem_access(cache_path) do - FileUtils.cp(local_cache_path, cache_path) - end + def download_gem(spec, download_cache_path) + uri = spec.remote.uri + Bundler.ui.confirm("Fetching #{version_message(spec)}") + Bundler.rubygems.download_gem(spec, uri, download_cache_path) end # Returns the global cache path of the calling Rubygems::Source object. @@ -538,7 +543,7 @@ module Bundler return unless remote = spec.remote return unless cache_slug = remote.cache_slug - Bundler.user_cache.join("gems", cache_slug, spec.file_name) + Bundler.user_cache.join("gems", cache_slug) end def extension_cache_slug(spec) diff --git a/lib/bundler/source/rubygems_aggregate.rb b/lib/bundler/source/rubygems_aggregate.rb new file mode 100644 index 0000000000..99ef81ad54 --- /dev/null +++ b/lib/bundler/source/rubygems_aggregate.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module Bundler + class Source + class RubygemsAggregate + attr_reader :source_map, :sources + + def initialize(sources, source_map) + @sources = sources + @source_map = source_map + + @index = build_index + end + + def specs + @index + end + + def identifier + to_s + end + + def to_s + "any of the sources" + end + + private + + def build_index + Index.build do |idx| + dependency_names = source_map.pinned_spec_names + + sources.all_sources.each do |source| + source.dependency_names = dependency_names - source_map.pinned_spec_names(source) + idx.add_source source.specs + dependency_names.concat(source.unmet_deps).uniq! + end + + double_check_for_index(idx, dependency_names) + end + end + + # Suppose the gem Foo depends on the gem Bar. Foo exists in Source A. Bar has some versions that exist in both + # sources A and B. At this point, the API request will have found all the versions of Bar in source A, + # but will not have found any versions of Bar from source B, which is a problem if the requested version + # of Foo specifically depends on a version of Bar that is only found in source B. This ensures that for + # each spec we found, we add all possible versions from all sources to the index. + def double_check_for_index(idx, dependency_names) + pinned_names = source_map.pinned_spec_names + + names = :names # do this so we only have to traverse to get dependency_names from the index once + unmet_dependency_names = lambda do + return names unless names == :names + new_names = sources.all_sources.map(&:dependency_names_to_double_check) + return names = nil if new_names.compact! + names = new_names.flatten(1).concat(dependency_names) + names.uniq! + names -= pinned_names + names + end + + sources.all_sources.each do |source| + source.double_check_for(unmet_dependency_names) + end + end + end + end +end diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb index 436891256d..a4773397c7 100644 --- a/lib/bundler/source_list.rb +++ b/lib/bundler/source_list.rb @@ -5,24 +5,49 @@ module Bundler attr_reader :path_sources, :git_sources, :plugin_sources, - :global_rubygems_source, + :global_path_source, :metadata_source + def global_rubygems_source + @global_rubygems_source ||= rubygems_aggregate_class.new("allow_local" => true) + end + def initialize @path_sources = [] @git_sources = [] @plugin_sources = [] @global_rubygems_source = nil - @rubygems_aggregate = rubygems_aggregate_class.new + @global_path_source = nil @rubygems_sources = [] @metadata_source = Source::Metadata.new + + @merged_gem_lockfile_sections = false + end + + def merged_gem_lockfile_sections? + @merged_gem_lockfile_sections + end + + def merged_gem_lockfile_sections!(replacement_source) + @merged_gem_lockfile_sections = true + @global_rubygems_source = replacement_source + end + + def aggregate_global_source? + global_rubygems_source.multiple_remotes? + end + + def implicit_global_source? + global_rubygems_source.no_remotes? end def add_path_source(options = {}) if options["gemspec"] add_source_to_list Source::Gemspec.new(options), path_sources else - add_source_to_list Source::Path.new(options), path_sources + path_source = add_source_to_list Source::Path.new(options), path_sources + @global_path_source ||= path_source if options["global"] + path_source end end @@ -33,32 +58,31 @@ module Bundler end def add_rubygems_source(options = {}) - add_source_to_list Source::Rubygems.new(options), @rubygems_sources + new_source = Source::Rubygems.new(options) + return @global_rubygems_source if @global_rubygems_source == new_source + + add_source_to_list new_source, @rubygems_sources end def add_plugin_source(source, options = {}) add_source_to_list Plugin.source(source).new(options), @plugin_sources end - def global_rubygems_source=(uri) - if Bundler.feature_flag.disable_multisource? - @global_rubygems_source ||= rubygems_aggregate_class.new("remotes" => uri) - end - add_rubygems_remote(uri) - end - - def add_rubygems_remote(uri) - return if Bundler.feature_flag.disable_multisource? - @rubygems_aggregate.add_remote(uri) - @rubygems_aggregate + def add_global_rubygems_remote(uri) + global_rubygems_source.add_remote(uri) + global_rubygems_source end def default_source - global_rubygems_source || @rubygems_aggregate + global_path_source || global_rubygems_source end def rubygems_sources - @rubygems_sources + [default_source] + non_global_rubygems_sources + [global_rubygems_source] + end + + def non_global_rubygems_sources + @rubygems_sources end def rubygems_remotes @@ -69,37 +93,51 @@ module Bundler path_sources + git_sources + plugin_sources + rubygems_sources + [metadata_source] end + def non_default_explicit_sources + all_sources - [default_source, metadata_source] + end + def get(source) - source_list_for(source).find {|s| equal_source?(source, s) || equivalent_source?(source, s) } + source_list_for(source).find {|s| equivalent_source?(source, s) } end def lock_sources - lock_sources = (path_sources + git_sources + plugin_sources).sort_by(&:to_s) - if Bundler.feature_flag.disable_multisource? - lock_sources + rubygems_sources.sort_by(&:to_s) + lock_other_sources + lock_rubygems_sources + end + + def lock_other_sources + (path_sources + git_sources + plugin_sources).sort_by(&:identifier) + end + + def lock_rubygems_sources + if merged_gem_lockfile_sections? + [combine_rubygems_sources] else - lock_sources << combine_rubygems_sources + rubygems_sources.sort_by(&:identifier) end end # Returns true if there are changes def replace_sources!(replacement_sources) - return true if replacement_sources.empty? + return false if replacement_sources.empty? - [path_sources, git_sources, plugin_sources].each do |source_list| - source_list.map! do |source| - replacement_sources.find {|s| s == source } || source - end - end + @rubygems_sources, @path_sources, @git_sources, @plugin_sources = map_sources(replacement_sources) + @global_rubygems_source = global_replacement_source(replacement_sources) + + different_sources?(lock_sources, replacement_sources) + end - replacement_rubygems = !Bundler.feature_flag.disable_multisource? && - replacement_sources.detect {|s| s.is_a?(Source::Rubygems) } - @rubygems_aggregate = replacement_rubygems if replacement_rubygems + # Returns true if there are changes + def expired_sources?(replacement_sources) + return false if replacement_sources.empty? + + lock_sources = dup_with_replaced_sources(replacement_sources).lock_sources - return true if !equal_sources?(lock_sources, replacement_sources) && !equivalent_sources?(lock_sources, replacement_sources) - return true if replacement_rubygems && rubygems_remotes.sort_by(&:to_s) != replacement_rubygems.remotes.sort_by(&:to_s) + different_sources?(lock_sources, replacement_sources) + end - false + def local_only! + all_sources.each(&:local_only!) end def cached! @@ -110,11 +148,33 @@ module Bundler all_sources.each(&:remote!) end - def rubygems_primary_remotes - @rubygems_aggregate.remotes + private + + def dup_with_replaced_sources(replacement_sources) + new_source_list = dup + new_source_list.replace_sources!(replacement_sources) + new_source_list end - private + def map_sources(replacement_sources) + [@rubygems_sources, @path_sources, @git_sources, @plugin_sources].map do |sources| + sources.map do |source| + replacement_sources.find {|s| s == source } || source + end + end + end + + def global_replacement_source(replacement_sources) + replacement_source = replacement_sources.find {|s| s == global_rubygems_source } + return global_rubygems_source unless replacement_source + + replacement_source.local! + replacement_source + end + + def different_sources?(lock_sources, replacement_sources) + !equivalent_sources?(lock_sources, replacement_sources) + end def rubygems_aggregate_class Source::Rubygems @@ -150,32 +210,12 @@ module Bundler end end - def equal_sources?(lock_sources, replacement_sources) - lock_sources.sort_by(&:to_s) == replacement_sources.sort_by(&:to_s) - end - - def equal_source?(source, other_source) - source == other_source - end - - def equivalent_source?(source, other_source) - return false unless Bundler.settings[:allow_deployment_source_credential_changes] && source.is_a?(Source::Rubygems) - - equivalent_rubygems_sources?([source], [other_source]) - end - def equivalent_sources?(lock_sources, replacement_sources) - return false unless Bundler.settings[:allow_deployment_source_credential_changes] - - lock_rubygems_sources, lock_other_sources = lock_sources.partition {|s| s.is_a?(Source::Rubygems) } - replacement_rubygems_sources, replacement_other_sources = replacement_sources.partition {|s| s.is_a?(Source::Rubygems) } - - equivalent_rubygems_sources?(lock_rubygems_sources, replacement_rubygems_sources) && equal_sources?(lock_other_sources, replacement_other_sources) + lock_sources.sort_by(&:identifier) == replacement_sources.sort_by(&:identifier) end - def equivalent_rubygems_sources?(lock_sources, replacement_sources) - actual_remotes = replacement_sources.map(&:remotes).flatten.uniq - lock_sources.all? {|s| s.equivalent_remotes?(actual_remotes) } + def equivalent_source?(source, other_source) + source == other_source end end end diff --git a/lib/bundler/source_map.rb b/lib/bundler/source_map.rb new file mode 100644 index 0000000000..a554f26f76 --- /dev/null +++ b/lib/bundler/source_map.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Bundler + class SourceMap + attr_reader :sources, :dependencies + + def initialize(sources, dependencies) + @sources = sources + @dependencies = dependencies + end + + def pinned_spec_names(skip = nil) + direct_requirements.reject {|_, source| source == skip }.keys + end + + def all_requirements + requirements = direct_requirements.dup + + unmet_deps = sources.non_default_explicit_sources.map do |source| + (source.spec_names - pinned_spec_names).each do |indirect_dependency_name| + previous_source = requirements[indirect_dependency_name] + if previous_source.nil? + requirements[indirect_dependency_name] = source + else + no_ambiguous_sources = Bundler.feature_flag.bundler_3_mode? + + msg = ["The gem '#{indirect_dependency_name}' was found in multiple relevant sources."] + msg.concat [previous_source, source].map {|s| " * #{s}" }.sort + msg << "You #{no_ambiguous_sources ? :must : :should} add this gem to the source block for the source you wish it to be installed from." + msg = msg.join("\n") + + raise SecurityError, msg if no_ambiguous_sources + Bundler.ui.warn "Warning: #{msg}" + end + end + + source.unmet_deps + end + + sources.default_source.add_dependency_names(unmet_deps.flatten - requirements.keys) + + requirements + end + + def direct_requirements + @direct_requirements ||= begin + requirements = {} + default = sources.default_source + dependencies.each do |dep| + dep_source = dep.source || default + dep_source.add_dependency_names(dep.name) + requirements[dep.name] = dep_source + end + requirements + end + end + end +end diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index 399c91fea5..a19d18388a 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "tsort" +require_relative "vendored_tsort" module Bundler class SpecSet @@ -11,21 +11,20 @@ module Bundler @specs = specs end - def for(dependencies, skip = [], check = false, match_current_platform = false, raise_on_missing = true) + def for(dependencies, check = false, match_current_platform = false) handled = [] deps = dependencies.dup specs = [] - skip += ["bundler"] loop do break unless dep = deps.shift - next if handled.include?(dep) || skip.include?(dep.name) + next if handled.any?{|d| d.name == dep.name && (match_current_platform || d.__platform == dep.__platform) } || dep.name == "bundler" handled << dep specs_for_dep = spec_for_dependency(dep, match_current_platform) if specs_for_dep.any? - specs += specs_for_dep + match_current_platform ? specs += specs_for_dep : specs |= specs_for_dep specs_for_dep.first.dependencies.each do |d| next if d.type == :development @@ -34,11 +33,6 @@ module Bundler end elsif check return false - elsif raise_on_missing - others = lookup[dep.name] if match_current_platform - message = "Unable to find a spec satisfying #{dep} in the set. Perhaps the lockfile is corrupted?" - message += " Found #{others.join(", ")} that did not match the current platform." if others && !others.empty? - raise GemNotFound, message end end @@ -46,11 +40,7 @@ module Bundler specs << spec end - check ? true : SpecSet.new(specs) - end - - def valid_for?(deps) - self.for(deps, [], true) + check ? true : specs end def [](key) @@ -76,32 +66,24 @@ module Bundler lookup.dup end - def materialize(deps, missing_specs = nil) - materialized = self.for(deps, [], false, true, !missing_specs).to_a - deps = materialized.map(&:name).uniq + def materialize(deps) + materialized = self.for(deps, false, true) + materialized.map! do |s| next s unless s.is_a?(LazySpecification) - s.source.dependency_names = deps if s.source.respond_to?(:dependency_names=) - spec = s.__materialize__ - unless spec - unless missing_specs - raise GemNotFound, "Could not find #{s.full_name} in any of the sources" - end - missing_specs << s - end - spec + s.source.local! + s.__materialize__ || s end - SpecSet.new(missing_specs ? materialized.compact : materialized) + SpecSet.new(materialized) end # Materialize for all the specs in the spec set, regardless of what platform they're for # This is in contrast to how for does platform filtering (and specifically different from how `materialize` calls `for` only for the current platform) # @return [Array<Gem::Specification>] def materialized_for_all_platforms - names = @specs.map(&:name).uniq @specs.map do |s| next s unless s.is_a?(LazySpecification) - s.source.dependency_names = names if s.source.respond_to?(:dependency_names=) + s.source.local! s.source.remote! spec = s.__materialize__ raise GemNotFound, "Could not find #{s.full_name} in any of the sources" unless spec @@ -109,6 +91,10 @@ module Bundler end end + def missing_specs + @specs.select {|s| s.is_a?(LazySpecification) } + end + def merge(set) arr = sorted.dup set.each do |set_spec| @@ -186,7 +172,7 @@ module Bundler def spec_for_dependency(dep, match_current_platform) specs_for_platforms = lookup[dep.name] if match_current_platform - GemHelpers.select_best_platform_match(specs_for_platforms, Bundler.local_platform) + GemHelpers.select_best_platform_match(specs_for_platforms.select{|s| Gem::Platform.match_spec?(s) }, Bundler.local_platform) else GemHelpers.select_best_platform_match(specs_for_platforms, dep.__platform) end diff --git a/lib/bundler/stub_specification.rb b/lib/bundler/stub_specification.rb index 2456d268da..fa071901e5 100644 --- a/lib/bundler/stub_specification.rb +++ b/lib/bundler/stub_specification.rb @@ -26,11 +26,19 @@ module Bundler # @!group Stub Delegates + def manually_installed? + # This is for manually installed gems which are gems that were fixed in place after a + # failed installation. Once the issue was resolved, the user then manually created + # the gem specification using the instructions provided by `gem help install` + installed_by_version == Gem::Version.new(0) + end + # This is defined directly to avoid having to loading the full spec def missing_extensions? return false if default_gem? return false if extensions.empty? return false if File.exist? gem_build_complete_path + return false if manually_installed? true end diff --git a/lib/bundler/templates/Executable.bundler b/lib/bundler/templates/Executable.bundler index 69f26bb9c0..8009412ea2 100644 --- a/lib/bundler/templates/Executable.bundler +++ b/lib/bundler/templates/Executable.bundler @@ -60,16 +60,16 @@ m = Module.new do Regexp.last_match(1) end - def bundler_version - @bundler_version ||= + def bundler_requirement + @bundler_requirement ||= env_var_version || cli_arg_version || - lockfile_version + bundler_requirement_for(lockfile_version) end - def bundler_requirement - return "#{Gem::Requirement.default}.a" unless bundler_version + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version - bundler_gem_version = Gem::Version.new(bundler_version) + bundler_gem_version = Gem::Version.new(version) requirement = bundler_gem_version.approximate_recommendation diff --git a/lib/bundler/templates/Gemfile b/lib/bundler/templates/Gemfile index 1afd2cce67..d41f2719b4 100644 --- a/lib/bundler/templates/Gemfile +++ b/lib/bundler/templates/Gemfile @@ -2,6 +2,6 @@ source "https://rubygems.org" -git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } # gem "rails" diff --git a/lib/bundler/templates/gems.rb b/lib/bundler/templates/gems.rb index 547cd6e8d9..56a62a7a82 100644 --- a/lib/bundler/templates/gems.rb +++ b/lib/bundler/templates/gems.rb @@ -3,6 +3,6 @@ # A sample gems.rb source "https://rubygems.org" -git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } # gem "rails" diff --git a/lib/bundler/templates/newgem/Gemfile.tt b/lib/bundler/templates/newgem/Gemfile.tt index b09ccfff15..de82a63c5f 100644 --- a/lib/bundler/templates/newgem/Gemfile.tt +++ b/lib/bundler/templates/newgem/Gemfile.tt @@ -14,7 +14,10 @@ gem "rake-compiler" gem "<%= config[:test] %>", "~> <%= config[:test_framework_version] %>" <%- end -%> -<%- if config[:rubocop] -%> +<%- if config[:linter] == "rubocop" -%> -gem "rubocop", "~> <%= config[:rubocop_version] %>" +gem "rubocop", "~> <%= config[:linter_version] %>" +<%- elsif config[:linter] == "standard" -%> + +gem "standard", "~> <%= config[:linter_version] %>" <%- end -%> diff --git a/lib/bundler/templates/newgem/README.md.tt b/lib/bundler/templates/newgem/README.md.tt index 315bc745a3..8fd87abe9a 100644 --- a/lib/bundler/templates/newgem/README.md.tt +++ b/lib/bundler/templates/newgem/README.md.tt @@ -29,19 +29,21 @@ TODO: Write usage instructions here After checking out the repo, run `bin/setup` to install dependencies.<% if config[:test] %> Then, run `rake <%= config[:test].sub('mini', '').sub('rspec', 'spec') %>` to run the tests.<% end %> You can also run `bin/console` for an interactive prompt that will allow you to experiment.<% if config[:bin] %> Run `bundle exec <%= config[:name] %>` to use the gem in this directory, ignoring other installed copies of this gem.<% end %> To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). +<% if config[:git] -%> ## Contributing -Bug reports and pull requests are welcome on GitHub at https://github.com/<%= config[:github_username] %>/<%= config[:name] %>.<% if config[:coc] %> This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/<%= config[:github_username] %>/<%= config[:name] %>/blob/master/CODE_OF_CONDUCT.md).<% end %> +Bug reports and pull requests are welcome on GitHub at https://github.com/<%= config[:github_username] %>/<%= config[:name] %>.<% if config[:coc] %> This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/<%= config[:github_username] %>/<%= config[:name] %>/blob/<%= config[:git_default_branch] %>/CODE_OF_CONDUCT.md).<% end %> +<% end -%> <% if config[:mit] -%> ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). <% end -%> -<% if config[:coc] -%> +<% if config[:git] && config[:coc] -%> ## Code of Conduct -Everyone interacting in the <%= config[:constant_name] %> project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/<%= config[:github_username] %>/<%= config[:name] %>/blob/master/CODE_OF_CONDUCT.md). +Everyone interacting in the <%= config[:constant_name] %> project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/<%= config[:github_username] %>/<%= config[:name] %>/blob/<%= config[:git_default_branch] %>/CODE_OF_CONDUCT.md). <% end -%> diff --git a/lib/bundler/templates/newgem/Rakefile.tt b/lib/bundler/templates/newgem/Rakefile.tt index 5393eb4e22..00b91c8464 100644 --- a/lib/bundler/templates/newgem/Rakefile.tt +++ b/lib/bundler/templates/newgem/Rakefile.tt @@ -18,12 +18,16 @@ require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) <% end -%> -<% if config[:rubocop] -%> +<% if config[:linter] == "rubocop" -%> <% default_task_names << :rubocop -%> require "rubocop/rake_task" RuboCop::RakeTask.new +<% elsif config[:linter] == "standard" -%> +<% default_task_names << :standard -%> +require "standard/rake" + <% end -%> <% if config[:ext] -%> <% default_task_names.unshift(:clobber, :compile) -%> diff --git a/lib/bundler/templates/newgem/github/workflows/main.yml.tt b/lib/bundler/templates/newgem/github/workflows/main.yml.tt index 807c8dd79a..6570d177af 100644 --- a/lib/bundler/templates/newgem/github/workflows/main.yml.tt +++ b/lib/bundler/templates/newgem/github/workflows/main.yml.tt @@ -1,18 +1,27 @@ name: Ruby -on: [push,pull_request] +on: + push: + branches: + - <%= config[:git_default_branch] %> + + pull_request: jobs: build: runs-on: ubuntu-latest + name: Ruby ${{ matrix.ruby }} + strategy: + matrix: + ruby: + - '<%= RUBY_VERSION %>' + steps: - uses: actions/checkout@v2 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: <%= RUBY_VERSION %> + ruby-version: ${{ matrix.ruby }} + bundler-cache: true - name: Run the default task - run: | - gem install bundler -v <%= Bundler::VERSION %> - bundle install - bundle exec rake + run: bundle exec rake diff --git a/lib/bundler/templates/newgem/newgem.gemspec.tt b/lib/bundler/templates/newgem/newgem.gemspec.tt index 632b24dda9..82281ab674 100644 --- a/lib/bundler/templates/newgem/newgem.gemspec.tt +++ b/lib/bundler/templates/newgem/newgem.gemspec.tt @@ -3,20 +3,20 @@ require_relative "lib/<%=config[:namespaced_path]%>/version" Gem::Specification.new do |spec| - spec.name = <%= config[:name].inspect %> - spec.version = <%= config[:constant_name] %>::VERSION - spec.authors = [<%= config[:author].inspect %>] - spec.email = [<%= config[:email].inspect %>] - - spec.summary = "TODO: Write a short summary, because RubyGems requires one." - spec.description = "TODO: Write a longer description or delete this line." - spec.homepage = "TODO: Put your gem's website or public repo URL here." + spec.name = <%= config[:name].inspect %> + spec.version = <%= config[:constant_name] %>::VERSION + spec.authors = [<%= config[:author].inspect %>] + spec.email = [<%= config[:email].inspect %>] + + spec.summary = "TODO: Write a short summary, because RubyGems requires one." + spec.description = "TODO: Write a longer description or delete this line." + spec.homepage = "TODO: Put your gem's website or public repo URL here." <%- if config[:mit] -%> - spec.license = "MIT" + spec.license = "MIT" <%- end -%> - spec.required_ruby_version = Gem::Requirement.new(">= <%= config[:required_ruby_version] %>") + spec.required_ruby_version = ">= <%= config[:required_ruby_version] %>" - spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" + spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'" spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here." @@ -25,13 +25,15 @@ Gem::Specification.new do |spec| # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. spec.files = Dir.chdir(File.expand_path(__dir__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) } + `git ls-files -z`.split("\x0").reject do |f| + (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)}) + end end - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] <%- if config[:ext] -%> - spec.extensions = ["ext/<%= config[:underscored_name] %>/extconf.rb"] + spec.extensions = ["ext/<%= config[:underscored_name] %>/extconf.rb"] <%- end -%> # Uncomment to register a new dependency of your gem diff --git a/lib/bundler/templates/newgem/sig/newgem.rbs.tt b/lib/bundler/templates/newgem/sig/newgem.rbs.tt new file mode 100644 index 0000000000..eb7b380bbb --- /dev/null +++ b/lib/bundler/templates/newgem/sig/newgem.rbs.tt @@ -0,0 +1,8 @@ +<%- config[:constant_array].each_with_index do |c, i| -%> +<%= " " * i %>module <%= c %> +<%- end -%> +<%= " " * config[:constant_array].size %>VERSION: String +<%= " " * config[:constant_array].size %># See the writing guide of rbs: https://github.com/ruby/rbs#guides +<%- (config[:constant_array].size-1).downto(0) do |i| -%> +<%= " " * i %>end +<%- end -%> diff --git a/lib/bundler/templates/newgem/standard.yml.tt b/lib/bundler/templates/newgem/standard.yml.tt new file mode 100644 index 0000000000..9e88fbbe8b --- /dev/null +++ b/lib/bundler/templates/newgem/standard.yml.tt @@ -0,0 +1,2 @@ +# For available configuration options, see: +# https://github.com/testdouble/standard diff --git a/lib/bundler/vendor/.document b/lib/bundler/vendor/.document new file mode 100644 index 0000000000..0c43bbd6b3 --- /dev/null +++ b/lib/bundler/vendor/.document @@ -0,0 +1 @@ +# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/connection_pool/LICENSE b/lib/bundler/vendor/connection_pool/LICENSE new file mode 100644 index 0000000000..7673cbfb7a --- /dev/null +++ b/lib/bundler/vendor/connection_pool/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2011 Mike Perham + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb index fbcd26c765..984c1c3dcb 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb @@ -1,14 +1,18 @@ -require_relative 'connection_pool/version' -require_relative 'connection_pool/timed_stack' +require "timeout" +require_relative "connection_pool/version" +class Bundler::ConnectionPool + class Error < ::RuntimeError; end + class PoolShuttingDownError < ::Bundler::ConnectionPool::Error; end + class TimeoutError < ::Timeout::Error; end +end -# Generic connection pool class for e.g. sharing a limited number of network connections -# among many threads. Note: Connections are lazily created. +# Generic connection pool class for sharing a limited number of objects or network connections +# among many threads. Note: pool elements are lazily created. # # Example usage with block (faster): # # @pool = Bundler::ConnectionPool.new { Redis.new } -# # @pool.with do |redis| # redis.lpop('my-list') if redis.llen('my-list') > 0 # end @@ -34,29 +38,23 @@ require_relative 'connection_pool/timed_stack' class Bundler::ConnectionPool DEFAULTS = {size: 5, timeout: 5} - class Error < RuntimeError - end - def self.wrap(options, &block) Wrapper.new(options, &block) end def initialize(options = {}, &block) - raise ArgumentError, 'Connection pool requires a block' unless block + raise ArgumentError, "Connection pool requires a block" unless block options = DEFAULTS.merge(options) - @size = options.fetch(:size) + @size = Integer(options.fetch(:size)) @timeout = options.fetch(:timeout) @available = TimedStack.new(@size, &block) - @key = :"current-#{@available.object_id}" - @key_count = :"current-#{@available.object_id}-count" + @key = :"pool-#{@available.object_id}" + @key_count = :"pool-#{@available.object_id}-count" end -if Thread.respond_to?(:handle_interrupt) - - # MRI def with(options = {}) Thread.handle_interrupt(Exception => :never) do conn = checkout(options) @@ -69,28 +67,15 @@ if Thread.respond_to?(:handle_interrupt) end end end - -else - - # jruby 1.7.x - def with(options = {}) - conn = checkout(options) - begin - yield conn - ensure - checkin - end - end - -end + alias then with def checkout(options = {}) if ::Thread.current[@key] - ::Thread.current[@key_count]+= 1 + ::Thread.current[@key_count] += 1 ::Thread.current[@key] else - ::Thread.current[@key_count]= 1 - ::Thread.current[@key]= @available.pop(options[:timeout] || @timeout) + ::Thread.current[@key_count] = 1 + ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout) end end @@ -98,64 +83,44 @@ end if ::Thread.current[@key] if ::Thread.current[@key_count] == 1 @available.push(::Thread.current[@key]) - ::Thread.current[@key]= nil + ::Thread.current[@key] = nil + ::Thread.current[@key_count] = nil else - ::Thread.current[@key_count]-= 1 + ::Thread.current[@key_count] -= 1 end else - raise Bundler::ConnectionPool::Error, 'no connections are checked out' + raise Bundler::ConnectionPool::Error, "no connections are checked out" end nil end + ## + # Shuts down the Bundler::ConnectionPool by passing each connection to +block+ and + # then removing it from the pool. Attempting to checkout a connection after + # shutdown will raise +Bundler::ConnectionPool::PoolShuttingDownError+. + def shutdown(&block) @available.shutdown(&block) end - # Size of this connection pool - def size - @size + ## + # Reloads the Bundler::ConnectionPool by passing each connection to +block+ and then + # removing it the pool. Subsequent checkouts will create new connections as + # needed. + + def reload(&block) + @available.shutdown(reload: true, &block) end + # Size of this connection pool + attr_reader :size + # Number of pool entries available for checkout at this instant. def available @available.length end - - private - - class Wrapper < ::BasicObject - METHODS = [:with, :pool_shutdown] - - def initialize(options = {}, &block) - @pool = options.fetch(:pool) { ::Bundler::ConnectionPool.new(options, &block) } - end - - def with(&block) - @pool.with(&block) - end - - def pool_shutdown(&block) - @pool.shutdown(&block) - end - - def pool_size - @pool.size - end - - def pool_available - @pool.available - end - - def respond_to?(id, *args) - METHODS.include?(id) || with { |c| c.respond_to?(id, *args) } - end - - def method_missing(name, *args, &block) - with do |connection| - connection.send(name, *args, &block) - end - end - end end + +require_relative "connection_pool/timed_stack" +require_relative "connection_pool/wrapper" diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/monotonic_time.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/monotonic_time.rb deleted file mode 100644 index 5a9c4a27bb..0000000000 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool/monotonic_time.rb +++ /dev/null @@ -1,66 +0,0 @@ -# Global monotonic clock from Concurrent Ruby 1.0. -# Copyright (c) Jerry D'Antonio -- released under the MIT license. -# Slightly modified; used with permission. -# https://github.com/ruby-concurrency/concurrent-ruby - -require 'thread' - -class Bundler::ConnectionPool - - class_definition = Class.new do - - if defined?(Process::CLOCK_MONOTONIC) - - # @!visibility private - def get_time - Process.clock_gettime(Process::CLOCK_MONOTONIC) - end - - elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' - - # @!visibility private - def get_time - java.lang.System.nanoTime() / 1_000_000_000.0 - end - - else - - # @!visibility private - def initialize - @mutex = Mutex.new - @last_time = Time.now.to_f - end - - # @!visibility private - def get_time - @mutex.synchronize do - now = Time.now.to_f - if @last_time < now - @last_time = now - else # clock has moved back in time - @last_time += 0.000_001 - end - end - end - end - end - - ## - # Clock that cannot be set and represents monotonic time since - # some unspecified starting point. - # - # @!visibility private - GLOBAL_MONOTONIC_CLOCK = class_definition.new - private_constant :GLOBAL_MONOTONIC_CLOCK - - class << self - ## - # Returns the current time a tracked by the application monotonic clock. - # - # @return [Float] The current monotonic time when `since` not given else - # the elapsed monotonic time between `since` and the current time - def monotonic_time - GLOBAL_MONOTONIC_CLOCK.get_time - end - end -end diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb index f3fe1e04ad..a7b1cf06a8 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb @@ -1,13 +1,3 @@ -require 'thread' -require 'timeout' -require_relative 'monotonic_time' - -## -# Raised when you attempt to retrieve a connection from a pool that has been -# shut down. - -class Bundler::ConnectionPool::PoolShuttingDownError < RuntimeError; end - ## # The TimedStack manages a pool of homogeneous connections (or any resource # you wish to manage). Connections are created lazily up to a given maximum @@ -25,7 +15,7 @@ class Bundler::ConnectionPool::PoolShuttingDownError < RuntimeError; end # # conn = ts.pop # ts.pop timeout: 5 -# #=> raises Timeout::Error after 5 seconds +# #=> raises Bundler::ConnectionPool::TimeoutError after 5 seconds class Bundler::ConnectionPool::TimedStack attr_reader :max @@ -39,8 +29,8 @@ class Bundler::ConnectionPool::TimedStack @created = 0 @que = [] @max = size - @mutex = Mutex.new - @resource = ConditionVariable.new + @mutex = Thread::Mutex.new + @resource = Thread::ConditionVariable.new @shutdown_block = nil end @@ -59,12 +49,12 @@ class Bundler::ConnectionPool::TimedStack @resource.broadcast end end - alias_method :<<, :push + alias << push ## # Retrieves a connection from the stack. If a connection is available it is # immediately returned. If no connection is available within the given - # timeout a Timeout::Error is raised. + # timeout a Bundler::ConnectionPool::TimeoutError is raised. # # +:timeout+ is the only checked entry in +options+ and is preferred over # the +timeout+ argument (which will be removed in a future release). Other @@ -74,7 +64,7 @@ class Bundler::ConnectionPool::TimedStack options, timeout = timeout, 0.5 if Hash === timeout timeout = options.fetch :timeout, timeout - deadline = Bundler::ConnectionPool.monotonic_time + timeout + deadline = current_time + timeout @mutex.synchronize do loop do raise Bundler::ConnectionPool::PoolShuttingDownError if @shutdown_block @@ -83,18 +73,20 @@ class Bundler::ConnectionPool::TimedStack connection = try_create(options) return connection if connection - to_wait = deadline - Bundler::ConnectionPool.monotonic_time - raise Timeout::Error, "Waited #{timeout} sec" if to_wait <= 0 + to_wait = deadline - current_time + raise Bundler::ConnectionPool::TimeoutError, "Waited #{timeout} sec" if to_wait <= 0 @resource.wait(@mutex, to_wait) end end end ## - # Shuts down the TimedStack which prevents connections from being checked - # out. The +block+ is called once for each connection on the stack. + # Shuts down the TimedStack by passing each connection to +block+ and then + # removing it from the pool. Attempting to checkout a connection after + # shutdown will raise +Bundler::ConnectionPool::PoolShuttingDownError+ unless + # +:reload+ is +true+. - def shutdown(&block) + def shutdown(reload: false, &block) raise ArgumentError, "shutdown must receive a block" unless block_given? @mutex.synchronize do @@ -102,6 +94,7 @@ class Bundler::ConnectionPool::TimedStack @resource.broadcast shutdown_connections + @shutdown_block = nil if reload end end @@ -121,6 +114,10 @@ class Bundler::ConnectionPool::TimedStack private + def current_time + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + ## # This is an extension point for TimedStack and is called with a mutex. # @@ -149,6 +146,7 @@ class Bundler::ConnectionPool::TimedStack conn = fetch_connection(options) @shutdown_block.call(conn) end + @created = 0 end ## diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb index b149c0e242..56ebf69902 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb @@ -1,3 +1,3 @@ class Bundler::ConnectionPool - VERSION = "2.2.2" + VERSION = "2.3.0" end diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb new file mode 100644 index 0000000000..880170c06b --- /dev/null +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb @@ -0,0 +1,57 @@ +class Bundler::ConnectionPool + class Wrapper < ::BasicObject + METHODS = [:with, :pool_shutdown, :wrapped_pool] + + def initialize(options = {}, &block) + @pool = options.fetch(:pool) { ::Bundler::ConnectionPool.new(options, &block) } + end + + def wrapped_pool + @pool + end + + def with(&block) + @pool.with(&block) + end + + def pool_shutdown(&block) + @pool.shutdown(&block) + end + + def pool_size + @pool.size + end + + def pool_available + @pool.available + end + + def respond_to?(id, *args) + METHODS.include?(id) || with { |c| c.respond_to?(id, *args) } + end + + # rubocop:disable Style/MethodMissingSuper + # rubocop:disable Style/MissingRespondToMissing + if ::RUBY_VERSION >= "3.0.0" + def method_missing(name, *args, **kwargs, &block) + with do |connection| + connection.send(name, *args, **kwargs, &block) + end + end + elsif ::RUBY_VERSION >= "2.7.0" + ruby2_keywords def method_missing(name, *args, &block) + with do |connection| + connection.send(name, *args, &block) + end + end + else + def method_missing(name, *args, &block) + with do |connection| + connection.send(name, *args, &block) + end + end + end + # rubocop:enable Style/MethodMissingSuper + # rubocop:enable Style/MissingRespondToMissing + end +end diff --git a/lib/bundler/vendor/fileutils/LICENSE.txt b/lib/bundler/vendor/fileutils/LICENSE.txt new file mode 100644 index 0000000000..a009caefea --- /dev/null +++ b/lib/bundler/vendor/fileutils/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/lib/bundler/vendor/molinillo/LICENSE b/lib/bundler/vendor/molinillo/LICENSE new file mode 100644 index 0000000000..01feffa088 --- /dev/null +++ b/lib/bundler/vendor/molinillo/LICENSE @@ -0,0 +1,9 @@ +This project is licensed under the MIT license. + +Copyright (c) 2014 Samuel E. Giddins segiddins@segiddins.me + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb index 936399ed2f..10a25d2f08 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'tsort' +require_relative '../../../../vendored_tsort' require_relative 'dependency_graph/log' require_relative 'dependency_graph/vertex' @@ -17,7 +17,7 @@ module Bundler::Molinillo vertices.values.each { |v| yield v } end - include TSort + include Bundler::TSort # @!visibility private alias tsort_each_node each diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb b/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb index edf2366b7b..eeae79af3c 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Bundler::Molinillo - # Provides information about specifcations and dependencies to the resolver, + # Provides information about specifications and dependencies to the resolver, # allowing the {Resolver} class to remain generic while still providing power # and flexibility. # diff --git a/lib/bundler/vendor/net-http-persistent/README.rdoc b/lib/bundler/vendor/net-http-persistent/README.rdoc new file mode 100644 index 0000000000..4f95ad33ba --- /dev/null +++ b/lib/bundler/vendor/net-http-persistent/README.rdoc @@ -0,0 +1,82 @@ += net-http-persistent + +home :: https://github.com/drbrain/net-http-persistent +rdoc :: http://docs.seattlerb.org/net-http-persistent + +== DESCRIPTION: + +Manages persistent connections using Net::HTTP including a thread pool for +connecting to multiple hosts. + +Using persistent HTTP connections can dramatically increase the speed of HTTP. +Creating a new HTTP connection for every request involves an extra TCP +round-trip and causes TCP congestion avoidance negotiation to start over. + +Net::HTTP supports persistent connections with some API methods but does not +make setting up a single persistent connection or managing multiple +connections easy. Net::HTTP::Persistent wraps Net::HTTP and allows you to +focus on how to make HTTP requests. + +== FEATURES/PROBLEMS: + +* Supports TLS with secure defaults +* Thread-safe +* Pure ruby + +== SYNOPSIS + +The following example will make two requests to the same server. The +connection is kept alive between requests: + + require 'net/http/persistent' + + uri = URI 'http://example.com/awesome/web/service' + + http = Net::HTTP::Persistent.new name: 'my_app_name' + + # perform a GET + response = http.request uri + + # create a POST + post_uri = uri + 'create' + post = Net::HTTP::Post.new post_uri.path + post.set_form_data 'some' => 'cool data' + + # perform the POST, the URI is always required + response = http.request post_uri, post + + # if you are done making http requests, or won't make requests for several + # minutes + http.shutdown + +Please see the documentation on Net::HTTP::Persistent for more information, +including SSL connection verification, header handling and tunable options. + +== INSTALL: + + gem install net-http-persistent + +== LICENSE: + +(The MIT License) + +Copyright (c) Eric Hodel, Aaron Patterson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/bundler/vendor/thor/LICENSE.md b/lib/bundler/vendor/thor/LICENSE.md new file mode 100644 index 0000000000..ef80540b2a --- /dev/null +++ b/lib/bundler/vendor/thor/LICENSE.md @@ -0,0 +1,20 @@ +Copyright (c) 2008 Yehuda Katz, Eric Hodel, et al. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb index 62c82b3dba..90a8d2e847 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb @@ -252,7 +252,7 @@ class Bundler::Thor # flag<Regexp|String>:: the regexp or string to be replaced # replacement<String>:: the replacement, can be also given as a block # config<Hash>:: give :verbose => false to not log the status, and - # :force => true, to force the replacement regardles of runner behavior. + # :force => true, to force the replacement regardless of runner behavior. # # ==== Example # diff --git a/lib/bundler/vendor/tmpdir/lib/tmpdir.rb b/lib/bundler/vendor/tmpdir/lib/tmpdir.rb index a00496687c..70d43e0c6b 100644 --- a/lib/bundler/vendor/tmpdir/lib/tmpdir.rb +++ b/lib/bundler/vendor/tmpdir/lib/tmpdir.rb @@ -115,7 +115,7 @@ class Bundler::Dir < Dir Bundler::Dir.tmpdir end - UNUSABLE_CHARS = [File::SEPARATOR, File::ALT_SEPARATOR, File::PATH_SEPARATOR, ":"].uniq.join("").freeze + UNUSABLE_CHARS = "^,-.0-9A-Z_a-z~" class << (RANDOM = Random.new) MAX = 36**6 # < 0x100000000 diff --git a/lib/bundler/vendor/tsort/LICENSE.txt b/lib/bundler/vendor/tsort/LICENSE.txt new file mode 100644 index 0000000000..a009caefea --- /dev/null +++ b/lib/bundler/vendor/tsort/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/lib/bundler/vendor/tsort/lib/tsort.rb b/lib/bundler/vendor/tsort/lib/tsort.rb new file mode 100644 index 0000000000..8454583295 --- /dev/null +++ b/lib/bundler/vendor/tsort/lib/tsort.rb @@ -0,0 +1,453 @@ +# frozen_string_literal: true + +#-- +# tsort.rb - provides a module for topological sorting and strongly connected components. +#++ +# + +# +# TSort implements topological sorting using Tarjan's algorithm for +# strongly connected components. +# +# TSort is designed to be able to be used with any object which can be +# interpreted as a directed graph. +# +# TSort requires two methods to interpret an object as a graph, +# tsort_each_node and tsort_each_child. +# +# * tsort_each_node is used to iterate for all nodes over a graph. +# * tsort_each_child is used to iterate for child nodes of a given node. +# +# The equality of nodes are defined by eql? and hash since +# TSort uses Hash internally. +# +# == A Simple Example +# +# The following example demonstrates how to mix the TSort module into an +# existing class (in this case, Hash). Here, we're treating each key in +# the hash as a node in the graph, and so we simply alias the required +# #tsort_each_node method to Hash's #each_key method. For each key in the +# hash, the associated value is an array of the node's child nodes. This +# choice in turn leads to our implementation of the required #tsort_each_child +# method, which fetches the array of child nodes and then iterates over that +# array using the user-supplied block. +# +# require 'tsort' +# +# class Hash +# include TSort +# alias tsort_each_node each_key +# def tsort_each_child(node, &block) +# fetch(node).each(&block) +# end +# end +# +# {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort +# #=> [3, 2, 1, 4] +# +# {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components +# #=> [[4], [2, 3], [1]] +# +# == A More Realistic Example +# +# A very simple `make' like tool can be implemented as follows: +# +# require 'tsort' +# +# class Make +# def initialize +# @dep = {} +# @dep.default = [] +# end +# +# def rule(outputs, inputs=[], &block) +# triple = [outputs, inputs, block] +# outputs.each {|f| @dep[f] = [triple]} +# @dep[triple] = inputs +# end +# +# def build(target) +# each_strongly_connected_component_from(target) {|ns| +# if ns.length != 1 +# fs = ns.delete_if {|n| Array === n} +# raise TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}") +# end +# n = ns.first +# if Array === n +# outputs, inputs, block = n +# inputs_time = inputs.map {|f| File.mtime f}.max +# begin +# outputs_time = outputs.map {|f| File.mtime f}.min +# rescue Errno::ENOENT +# outputs_time = nil +# end +# if outputs_time == nil || +# inputs_time != nil && outputs_time <= inputs_time +# sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i +# block.call +# end +# end +# } +# end +# +# def tsort_each_child(node, &block) +# @dep[node].each(&block) +# end +# include TSort +# end +# +# def command(arg) +# print arg, "\n" +# system arg +# end +# +# m = Make.new +# m.rule(%w[t1]) { command 'date > t1' } +# m.rule(%w[t2]) { command 'date > t2' } +# m.rule(%w[t3]) { command 'date > t3' } +# m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' } +# m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' } +# m.build('t5') +# +# == Bugs +# +# * 'tsort.rb' is wrong name because this library uses +# Tarjan's algorithm for strongly connected components. +# Although 'strongly_connected_components.rb' is correct but too long. +# +# == References +# +# R. E. Tarjan, "Depth First Search and Linear Graph Algorithms", +# <em>SIAM Journal on Computing</em>, Vol. 1, No. 2, pp. 146-160, June 1972. +# +module Bundler + module TSort + class Cyclic < StandardError + end + + # Returns a topologically sorted array of nodes. + # The array is sorted from children to parents, i.e. + # the first element has no child and the last node has no parent. + # + # If there is a cycle, TSort::Cyclic is raised. + # + # class G + # include TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # p graph.tsort #=> [4, 2, 3, 1] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # p graph.tsort # raises TSort::Cyclic + # + def tsort + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + TSort.tsort(each_node, each_child) + end + + # Returns a topologically sorted array of nodes. + # The array is sorted from children to parents, i.e. + # the first element has no child and the last node has no parent. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # If there is a cycle, TSort::Cyclic is raised. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1] + # + # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p TSort.tsort(each_node, each_child) # raises TSort::Cyclic + # + def TSort.tsort(each_node, each_child) + TSort.tsort_each(each_node, each_child).to_a + end + + # The iterator version of the #tsort method. + # <tt><em>obj</em>.tsort_each</tt> is similar to <tt><em>obj</em>.tsort.each</tt>, but + # modification of _obj_ during the iteration may lead to unexpected results. + # + # #tsort_each returns +nil+. + # If there is a cycle, TSort::Cyclic is raised. + # + # class G + # include TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # graph.tsort_each {|n| p n } + # #=> 4 + # # 2 + # # 3 + # # 1 + # + def tsort_each(&block) # :yields: node + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + TSort.tsort_each(each_node, each_child, &block) + end + + # The iterator version of the TSort.tsort method. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # TSort.tsort_each(each_node, each_child) {|n| p n } + # #=> 4 + # # 2 + # # 3 + # # 1 + # + def TSort.tsort_each(each_node, each_child) # :yields: node + return to_enum(__method__, each_node, each_child) unless block_given? + + TSort.each_strongly_connected_component(each_node, each_child) {|component| + if component.size == 1 + yield component.first + else + raise Cyclic.new("topological sort failed: #{component.inspect}") + end + } + end + + # Returns strongly connected components as an array of arrays of nodes. + # The array is sorted from children to parents. + # Each elements of the array represents a strongly connected component. + # + # class G + # include TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # p graph.strongly_connected_components #=> [[4], [2], [3], [1]] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # p graph.strongly_connected_components #=> [[4], [2, 3], [1]] + # + def strongly_connected_components + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + TSort.strongly_connected_components(each_node, each_child) + end + + # Returns strongly connected components as an array of arrays of nodes. + # The array is sorted from children to parents. + # Each elements of the array represents a strongly connected component. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p TSort.strongly_connected_components(each_node, each_child) + # #=> [[4], [2], [3], [1]] + # + # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p TSort.strongly_connected_components(each_node, each_child) + # #=> [[4], [2, 3], [1]] + # + def TSort.strongly_connected_components(each_node, each_child) + TSort.each_strongly_connected_component(each_node, each_child).to_a + end + + # The iterator version of the #strongly_connected_components method. + # <tt><em>obj</em>.each_strongly_connected_component</tt> is similar to + # <tt><em>obj</em>.strongly_connected_components.each</tt>, but + # modification of _obj_ during the iteration may lead to unexpected results. + # + # #each_strongly_connected_component returns +nil+. + # + # class G + # include TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # graph.each_strongly_connected_component {|scc| p scc } + # #=> [4] + # # [2] + # # [3] + # # [1] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # graph.each_strongly_connected_component {|scc| p scc } + # #=> [4] + # # [2, 3] + # # [1] + # + def each_strongly_connected_component(&block) # :yields: nodes + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + TSort.each_strongly_connected_component(each_node, each_child, &block) + end + + # The iterator version of the TSort.strongly_connected_components method. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } + # #=> [4] + # # [2] + # # [3] + # # [1] + # + # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } + # #=> [4] + # # [2, 3] + # # [1] + # + def TSort.each_strongly_connected_component(each_node, each_child) # :yields: nodes + return to_enum(__method__, each_node, each_child) unless block_given? + + id_map = {} + stack = [] + each_node.call {|node| + unless id_map.include? node + TSort.each_strongly_connected_component_from(node, each_child, id_map, stack) {|c| + yield c + } + end + } + nil + end + + # Iterates over strongly connected component in the subgraph reachable from + # _node_. + # + # Return value is unspecified. + # + # #each_strongly_connected_component_from doesn't call #tsort_each_node. + # + # class G + # include TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # graph.each_strongly_connected_component_from(2) {|scc| p scc } + # #=> [4] + # # [2] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # graph.each_strongly_connected_component_from(2) {|scc| p scc } + # #=> [4] + # # [2, 3] + # + def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes + TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block) + end + + # Iterates over strongly connected components in a graph. + # The graph is represented by _node_ and _each_child_. + # + # _node_ is the first node. + # _each_child_ should have +call+ method which takes a node argument + # and yields for each child node. + # + # Return value is unspecified. + # + # #TSort.each_strongly_connected_component_from is a class method and + # it doesn't need a class to represent a graph which includes TSort. + # + # graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_child = lambda {|n, &b| graph[n].each(&b) } + # TSort.each_strongly_connected_component_from(1, each_child) {|scc| + # p scc + # } + # #=> [4] + # # [2, 3] + # # [1] + # + def TSort.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes + return to_enum(__method__, node, each_child, id_map, stack) unless block_given? + + minimum_id = node_id = id_map[node] = id_map.size + stack_length = stack.length + stack << node + + each_child.call(node) {|child| + if id_map.include? child + child_id = id_map[child] + minimum_id = child_id if child_id && child_id < minimum_id + else + sub_minimum_id = + TSort.each_strongly_connected_component_from(child, each_child, id_map, stack) {|c| + yield c + } + minimum_id = sub_minimum_id if sub_minimum_id < minimum_id + end + } + + if node_id == minimum_id + component = stack.slice!(stack_length .. -1) + component.each {|n| id_map[n] = nil} + yield component + end + + minimum_id + end + + # Should be implemented by a extended class. + # + # #tsort_each_node is used to iterate for all nodes over a graph. + # + def tsort_each_node # :yields: node + raise NotImplementedError.new + end + + # Should be implemented by a extended class. + # + # #tsort_each_child is used to iterate for child nodes of _node_. + # + def tsort_each_child(node) # :yields: child + raise NotImplementedError.new + end + end +end diff --git a/lib/bundler/vendor/uri/LICENSE.txt b/lib/bundler/vendor/uri/LICENSE.txt new file mode 100644 index 0000000000..a009caefea --- /dev/null +++ b/lib/bundler/vendor/uri/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb b/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb index a0d62ede64..7634e16958 100644 --- a/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb +++ b/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb @@ -504,8 +504,8 @@ module Bundler::URI ret = {} # for Bundler::URI::split - ret[:ABS_URI] = Regexp.new('\A\s*' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED) - ret[:REL_URI] = Regexp.new('\A\s*' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED) + ret[:ABS_URI] = Regexp.new('\A\s*+' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED) + ret[:REL_URI] = Regexp.new('\A\s*+' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED) # for Bundler::URI::extract ret[:URI_REF] = Regexp.new(pattern[:URI_REF]) diff --git a/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb b/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb index 07ef4391c0..3d0c7b91f0 100644 --- a/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb +++ b/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb @@ -3,8 +3,8 @@ module Bundler::URI class RFC3986_Parser # :nodoc: # Bundler::URI defined in RFC3986 # this regexp is modified not to host is not empty string - RFC3986_URI = /\A(?<Bundler::URI>(?<scheme>[A-Za-z][+\-.0-9A-Za-z]*):(?<hier-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])+))?(?::(?<port>\d*))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g<segment>)*)?)|(?<path-rootless>\g<segment-nz>(?:\/\g<segment>)*)|(?<path-empty>))(?:\?(?<query>[^#]*))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/ - RFC3986_relative_ref = /\A(?<relative-ref>(?<relative-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?<host>(?<IP-literal>\[(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:){,1}\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+)\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])+))?(?::(?<port>\d*))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g<segment>)*)?)|(?<path-noscheme>(?<segment-nz-nc>(?:%\h\h|[!$&-.0-9;=@-Z_a-z~])+)(?:\/\g<segment>)*)|(?<path-empty>))(?:\?(?<query>[^#]*))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/ + RFC3986_URI = /\A(?<Bundler::URI>(?<scheme>[A-Za-z][+\-.0-9A-Za-z]*+):(?<hier-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*+)@)?(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h++\.[!$&-.0-;=A-Z_a-z~]++))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])++))?(?::(?<port>\d*+))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*+))*+)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])++)(?:\/\g<segment>)*+)?)|(?<path-rootless>\g<segment-nz>(?:\/\g<segment>)*+)|(?<path-empty>))(?:\?(?<query>[^#]*+))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*+))?)\z/ + RFC3986_relative_ref = /\A(?<relative-ref>(?<relative-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*+)@)?(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:){,1}\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h++\.[!$&-.0-;=A-Z_a-z~]++))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])++))?(?::(?<port>\d*+))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*+))*+)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])++)(?:\/\g<segment>)*+)?)|(?<path-noscheme>(?<segment-nz-nc>(?:%\h\h|[!$&-.0-9;=@-Z_a-z~])++)(?:\/\g<segment>)*+)|(?<path-empty>))(?:\?(?<query>[^#]*+))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*+))?)\z/ attr_reader :regexp def initialize @@ -106,7 +106,7 @@ module Bundler::URI QUERY: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*\z/, FRAGMENT: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*\z/, OPAQUE: /\A(?:[^\/].*)?\z/, - PORT: /\A[\x09\x0a\x0c\x0d ]*\d*[\x09\x0a\x0c\x0d ]*\z/, + PORT: /\A[\x09\x0a\x0c\x0d ]*+\d*[\x09\x0a\x0c\x0d ]*\z/, } end diff --git a/lib/bundler/vendor/uri/lib/uri/version.rb b/lib/bundler/vendor/uri/lib/uri/version.rb index 56177ef194..02079b26d4 100644 --- a/lib/bundler/vendor/uri/lib/uri/version.rb +++ b/lib/bundler/vendor/uri/lib/uri/version.rb @@ -1,6 +1,6 @@ module Bundler::URI # :stopdoc: - VERSION_CODE = '001000'.freeze + VERSION_CODE = '00100003'.freeze VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze # :startdoc: end diff --git a/lib/bundler/vendored_tsort.rb b/lib/bundler/vendored_tsort.rb new file mode 100644 index 0000000000..38aed0b5de --- /dev/null +++ b/lib/bundler/vendored_tsort.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +module Bundler; end +require_relative "vendor/tsort/lib/tsort" diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index f9569c8f78..863eeaae5f 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "2.2.9".freeze + VERSION = "2.2.33".freeze def self.bundler_major_version @bundler_major_version ||= VERSION.split(".").first.to_i diff --git a/lib/bundler/worker.rb b/lib/bundler/worker.rb index 10139ed25b..5e4ee21c51 100644 --- a/lib/bundler/worker.rb +++ b/lib/bundler/worker.rb @@ -21,12 +21,12 @@ module Bundler # @param func [Proc] job to run in inside the worker pool def initialize(size, name, func) @name = name - @request_queue = Queue.new - @response_queue = Queue.new + @request_queue = Thread::Queue.new + @response_queue = Thread::Queue.new @func = func @size = size @threads = nil - SharedHelpers.trap("INT") { abort_threads } + @previous_interrupt_handler = nil end # Enqueue a request to be executed in the worker pool @@ -68,13 +68,16 @@ module Bundler # so as worker threads after retrieving it, shut themselves down def stop_threads return unless @threads + @threads.each { @request_queue.enq POISON } @threads.each(&:join) + + remove_interrupt_handler + @threads = nil end def abort_threads - return unless @threads Bundler.ui.debug("\n#{caller.join("\n")}") @threads.each(&:exit) exit 1 @@ -94,11 +97,23 @@ module Bundler end end.compact + add_interrupt_handler unless @threads.empty? + return if creation_errors.empty? message = "Failed to create threads for the #{name} worker: #{creation_errors.map(&:to_s).uniq.join(", ")}" raise ThreadCreationError, message if @threads.empty? Bundler.ui.info message end + + def add_interrupt_handler + @previous_interrupt_handler = trap("INT") { abort_threads } + end + + def remove_interrupt_handler + return unless @previous_interrupt_handler + + trap "INT", @previous_interrupt_handler + end end end diff --git a/lib/cgi.rb b/lib/cgi.rb index 3b53d27a2e..6feeaf4624 100644 --- a/lib/cgi.rb +++ b/lib/cgi.rb @@ -288,7 +288,7 @@ # class CGI - VERSION = "0.2.0" + VERSION = "0.2.2" end require 'cgi/core' diff --git a/lib/cgi/cookie.rb b/lib/cgi/cookie.rb index ae9ab58ede..1a9c1a82c1 100644 --- a/lib/cgi/cookie.rb +++ b/lib/cgi/cookie.rb @@ -40,6 +40,10 @@ class CGI class Cookie < Array @@accept_charset="UTF-8" unless defined?(@@accept_charset) + TOKEN_RE = %r"\A[[!-~]&&[^()<>@,;:\\\"/?=\[\]{}]]+\z" + PATH_VALUE_RE = %r"\A[[ -~]&&[^;]]*\z" + DOMAIN_VALUE_RE = %r"\A(?<label>(?!-)[-A-Za-z0-9]+(?<!-))(?:\.\g<label>)*\z" + # Create a new CGI::Cookie object. # # :call-seq: @@ -72,8 +76,8 @@ class CGI @domain = nil @expires = nil if name.kind_of?(String) - @name = name - @path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") + self.name = name + self.path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") @secure = false @httponly = false return super(value) @@ -84,11 +88,11 @@ class CGI raise ArgumentError, "`name' required" end - @name = options["name"] + self.name = options["name"] value = Array(options["value"]) # simple support for IE - @path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") - @domain = options["domain"] + self.path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") + self.domain = options["domain"] @expires = options["expires"] @secure = options["secure"] == true @httponly = options["httponly"] == true @@ -97,11 +101,35 @@ class CGI end # Name of this cookie, as a +String+ - attr_accessor :name + attr_reader :name + # Set name of this cookie + def name=(str) + if str and !TOKEN_RE.match?(str) + raise ArgumentError, "invalid name: #{str.dump}" + end + @name = str + end + # Path for which this cookie applies, as a +String+ - attr_accessor :path + attr_reader :path + # Set path for which this cookie applies + def path=(str) + if str and !PATH_VALUE_RE.match?(str) + raise ArgumentError, "invalid path: #{str.dump}" + end + @path = str + end + # Domain for which this cookie applies, as a +String+ - attr_accessor :domain + attr_reader :domain + # Set domain for which this cookie applies + def domain=(str) + if str and ((str = str.b).bytesize > 255 or !DOMAIN_VALUE_RE.match?(str)) + raise ArgumentError, "invalid domain: #{str.dump}" + end + @domain = str + end + # Time at which this cookie expires, as a +Time+ attr_accessor :expires # True if this cookie is secure; false otherwise @@ -159,7 +187,6 @@ class CGI raw_cookie.split(/;\s?/).each do |pairs| name, values = pairs.split('=',2) next unless name and values - name = CGI.unescape(name) values ||= "" values = values.split('&').collect{|v| CGI.unescape(v,@@accept_charset) } if cookies.has_key?(name) diff --git a/lib/cgi/core.rb b/lib/cgi/core.rb index bec76e0749..62e606837a 100644 --- a/lib/cgi/core.rb +++ b/lib/cgi/core.rb @@ -188,17 +188,28 @@ class CGI # Using #header with the HTML5 tag maker will create a <header> element. alias :header :http_header + def _no_crlf_check(str) + if str + str = str.to_s + raise "A HTTP status or header field must not include CR and LF" if str =~ /[\r\n]/ + str + else + nil + end + end + private :_no_crlf_check + def _header_for_string(content_type) #:nodoc: buf = ''.dup if nph?() - buf << "#{$CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0'} 200 OK#{EOL}" + buf << "#{_no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'} 200 OK#{EOL}" buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}" - buf << "Server: #{$CGI_ENV['SERVER_SOFTWARE']}#{EOL}" + buf << "Server: #{_no_crlf_check($CGI_ENV['SERVER_SOFTWARE'])}#{EOL}" buf << "Connection: close#{EOL}" end - buf << "Content-Type: #{content_type}#{EOL}" + buf << "Content-Type: #{_no_crlf_check(content_type)}#{EOL}" if @output_cookies - @output_cookies.each {|cookie| buf << "Set-Cookie: #{cookie}#{EOL}" } + @output_cookies.each {|cookie| buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}" } end return buf end # _header_for_string @@ -213,9 +224,9 @@ class CGI ## NPH options.delete('nph') if defined?(MOD_RUBY) if options.delete('nph') || nph?() - protocol = $CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0' + protocol = _no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0' status = options.delete('status') - status = HTTP_STATUS[status] || status || '200 OK' + status = HTTP_STATUS[status] || _no_crlf_check(status) || '200 OK' buf << "#{protocol} #{status}#{EOL}" buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}" options['server'] ||= $CGI_ENV['SERVER_SOFTWARE'] || '' @@ -223,38 +234,38 @@ class CGI end ## common headers status = options.delete('status') - buf << "Status: #{HTTP_STATUS[status] || status}#{EOL}" if status + buf << "Status: #{HTTP_STATUS[status] || _no_crlf_check(status)}#{EOL}" if status server = options.delete('server') - buf << "Server: #{server}#{EOL}" if server + buf << "Server: #{_no_crlf_check(server)}#{EOL}" if server connection = options.delete('connection') - buf << "Connection: #{connection}#{EOL}" if connection + buf << "Connection: #{_no_crlf_check(connection)}#{EOL}" if connection type = options.delete('type') - buf << "Content-Type: #{type}#{EOL}" #if type + buf << "Content-Type: #{_no_crlf_check(type)}#{EOL}" #if type length = options.delete('length') - buf << "Content-Length: #{length}#{EOL}" if length + buf << "Content-Length: #{_no_crlf_check(length)}#{EOL}" if length language = options.delete('language') - buf << "Content-Language: #{language}#{EOL}" if language + buf << "Content-Language: #{_no_crlf_check(language)}#{EOL}" if language expires = options.delete('expires') buf << "Expires: #{CGI.rfc1123_date(expires)}#{EOL}" if expires ## cookie if cookie = options.delete('cookie') case cookie when String, Cookie - buf << "Set-Cookie: #{cookie}#{EOL}" + buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}" when Array arr = cookie - arr.each {|c| buf << "Set-Cookie: #{c}#{EOL}" } + arr.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" } when Hash hash = cookie - hash.each_value {|c| buf << "Set-Cookie: #{c}#{EOL}" } + hash.each_value {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" } end end if @output_cookies - @output_cookies.each {|c| buf << "Set-Cookie: #{c}#{EOL}" } + @output_cookies.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" } end ## other headers options.each do |key, value| - buf << "#{key}: #{value}#{EOL}" + buf << "#{_no_crlf_check(key)}: #{_no_crlf_check(value)}#{EOL}" end return buf end # _header_for_hash diff --git a/lib/debug.gemspec b/lib/debug.gemspec index 71f93f7409..0c414d4827 100644 --- a/lib/debug.gemspec +++ b/lib/debug.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "debug" - spec.version = "0.1.0" + spec.version = "0.2.1" spec.authors = ["Yukihiro Matsumoto"] spec.email = ["matz@ruby-lang.org"] @@ -17,6 +17,6 @@ Gem::Specification.new do |spec| `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.executables = [] spec.require_paths = ["lib"] end diff --git a/lib/debug.rb b/lib/debug.rb index bf53eb80a4..bf63ccf34d 100644 --- a/lib/debug.rb +++ b/lib/debug.rb @@ -3,7 +3,10 @@ # Copyright (C) 2000 Information-technology Promotion Agency, Japan # Copyright (C) 2000-2003 NAKAMURA, Hiroshi <nahi@ruby-lang.org> -require 'continuation' +if $SAFE > 0 + STDERR.print "-r debug.rb is not available in safe mode\n" + exit 1 +end require 'tracer' require 'pp' @@ -178,6 +181,9 @@ SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__ # :nodoc: class DEBUGGER__ MUTEX = Thread::Mutex.new # :nodoc: + CONTINUATIONS_SUPPORTED = RUBY_ENGINE == 'ruby' + + require 'continuation' if CONTINUATIONS_SUPPORTED class Context # :nodoc: DEBUG_LAST_CMD = [] @@ -375,8 +381,10 @@ class DEBUGGER__ def debug_command(file, line, id, binding) MUTEX.lock - unless defined?($debugger_restart) and $debugger_restart - callcc{|c| $debugger_restart = c} + if CONTINUATIONS_SUPPORTED + unless defined?($debugger_restart) and $debugger_restart + callcc{|c| $debugger_restart = c} + end end set_last_thread(Thread.current) frame_pos = 0 @@ -648,7 +656,11 @@ class DEBUGGER__ stdout.printf "%s\n", debug_eval($', binding).inspect when /^\s*r(?:estart)?$/ - $debugger_restart.call + if CONTINUATIONS_SUPPORTED + $debugger_restart.call + else + stdout.print "Restart requires continuations.\n" + end when /^\s*h(?:elp)?$/ debug_print_help() @@ -1097,9 +1109,11 @@ EOHELP stdout.printf "Debug.rb\n" stdout.printf "Emacs support available.\n\n" - RubyVM::InstructionSequence.compile_option = { - trace_instruction: true - } + if defined?(RubyVM::InstructionSequence) + RubyVM::InstructionSequence.compile_option = { + trace_instruction: true + } + end set_trace_func proc { |event, file, line, id, binding, klass, *rest| DEBUGGER__.context.trace_func event, file, line, id, binding, klass } diff --git a/lib/drb/version.rb b/lib/drb/version.rb index ffba81d48e..e1f33cc65e 100644 --- a/lib/drb/version.rb +++ b/lib/drb/version.rb @@ -1,3 +1,3 @@ module DRb - VERSION = "2.0.4" + VERSION = "2.0.5" end diff --git a/lib/irb.rb b/lib/irb.rb index 7f99974f28..93c4d25c92 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -60,7 +60,11 @@ require_relative "irb/easter-egg" # -E enc Same as `ruby -E` # -w Same as `ruby -w` # -W[level=2] Same as `ruby -W` -# --inspect Use `inspect' for output (default except for bc mode) +# --context-mode n Set n[0-4] to method to create Binding Object, +# when new workspace was created +# --echo Show result(default) +# --noecho Don't show result +# --inspect Use `inspect' for output # --noinspect Don't use inspect for output # --multiline Use multiline editor module # --nomultiline Don't use multiline editor module @@ -68,19 +72,24 @@ require_relative "irb/easter-egg" # --nosingleline Don't use singleline editor module # --colorize Use colorization # --nocolorize Don't use colorization -# --prompt prompt-mode -# --prompt-mode prompt-mode +# --prompt prompt-mode/--prompt-mode prompt-mode # Switch prompt mode. Pre-defined prompt modes are # `default', `simple', `xmp' and `inf-ruby' # --inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs. # Suppresses --multiline and --singleline. -# --simple-prompt Simple prompt mode +# --sample-book-mode/--simple-prompt +# Simple prompt mode # --noprompt No prompt mode +# --single-irb Share self with sub-irb. # --tracer Display trace for each execution of commands. # --back-trace-limit n # Display backtrace top n and tail n. The default # value is 16. -# -v, --version Print the version of irb +# --verbose Show details +# --noverbose Don't show details +# -v, --version Print the version of irb +# -h, --help Print help +# -- Separate options of irb from the list of command-line args # # == Configuration # @@ -463,7 +472,7 @@ module IRB conf[:IRB_RC].call(context) if conf[:IRB_RC] conf[:MAIN_CONTEXT] = context - trap("SIGINT") do + prev_trap = trap("SIGINT") do signal_handle end @@ -472,6 +481,7 @@ module IRB eval_input end ensure + trap("SIGINT", prev_trap) conf[:AT_EXIT].each{|hook| hook.call} end end diff --git a/lib/irb/cmd/ls.rb b/lib/irb/cmd/ls.rb new file mode 100644 index 0000000000..f163f4f9e6 --- /dev/null +++ b/lib/irb/cmd/ls.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require "reline" +require_relative "nop" +require_relative "../color" + +# :stopdoc: +module IRB + module ExtendCommand + class Ls < Nop + def execute(*arg, grep: nil) + o = Output.new(grep: grep) + + obj = arg.empty? ? irb_context.workspace.main : arg.first + locals = arg.empty? ? irb_context.workspace.binding.local_variables : [] + klass = (obj.class == Class || obj.class == Module ? obj : obj.class) + + o.dump("constants", obj.constants) if obj.respond_to?(:constants) + o.dump("#{klass}.methods", obj.singleton_methods(false)) + o.dump("#{klass}#methods", klass.public_instance_methods(false)) + o.dump("instance variables", obj.instance_variables) + o.dump("class variables", klass.class_variables) + o.dump("locals", locals) + end + + class Output + MARGIN = " " + + def initialize(grep: nil) + @grep = grep + @line_width = screen_width - MARGIN.length # right padding + end + + def dump(name, strs) + strs = strs.grep(@grep) if @grep + strs = strs.sort + return if strs.empty? + + # Attempt a single line + print "#{Color.colorize(name, [:BOLD, :BLUE])}: " + if fits_on_line?(strs, cols: strs.size, offset: "#{name}: ".length) + puts strs.join(MARGIN) + return + end + puts + + # Dump with the largest # of columns that fits on a line + cols = strs.size + until fits_on_line?(strs, cols: cols, offset: MARGIN.length) || cols == 1 + cols -= 1 + end + widths = col_widths(strs, cols: cols) + strs.each_slice(cols) do |ss| + puts ss.map.with_index { |s, i| "#{MARGIN}%-#{widths[i]}s" % s }.join + end + end + + private + + def fits_on_line?(strs, cols:, offset: 0) + width = col_widths(strs, cols: cols).sum + MARGIN.length * (cols - 1) + width <= @line_width - offset + end + + def col_widths(strs, cols:) + cols.times.map do |col| + (col...strs.size).step(cols).map do |i| + strs[i].length + end.max + end + end + + def screen_width + Reline.get_screen_size.last + rescue Errno::EINVAL # in `winsize': Invalid argument - <STDIN> + 80 + end + end + private_constant :Output + end + end +end +# :startdoc: diff --git a/lib/irb/cmd/nop.rb b/lib/irb/cmd/nop.rb index fa3c011b5f..d6f7a611a6 100644 --- a/lib/irb/cmd/nop.rb +++ b/lib/irb/cmd/nop.rb @@ -14,10 +14,16 @@ module IRB module ExtendCommand class Nop - - def self.execute(conf, *opts, &block) - command = new(conf) - command.execute(*opts, &block) + if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.7.0" + def self.execute(conf, *opts, **kwargs, &block) + command = new(conf) + command.execute(*opts, **kwargs, &block) + end + else + def self.execute(conf, *opts, &block) + command = new(conf) + command.execute(*opts, &block) + end end def initialize(conf) diff --git a/lib/irb/cmd/show_source.rb b/lib/irb/cmd/show_source.rb new file mode 100644 index 0000000000..0bd40b7d4e --- /dev/null +++ b/lib/irb/cmd/show_source.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require_relative "nop" +require_relative "../color" +require_relative "../ruby-lex" + +# :stopdoc: +module IRB + module ExtendCommand + class ShowSource < Nop + def execute(str = nil) + unless str.is_a?(String) + puts "Error: Expected a string but got #{str.inspect}" + return + end + source = find_source(str) + if source && File.exist?(source.file) + show_source(source) + else + puts "Error: Couldn't locate a definition for #{str}" + end + nil + end + + private + + # @param [IRB::ExtendCommand::ShowSource::Source] source + def show_source(source) + puts + puts "#{bold("From")}: #{source.file}:#{source.first_line}" + puts + code = IRB::Color.colorize_code(File.read(source.file)) + puts code.lines[(source.first_line - 1)...source.last_line].join + puts + end + + def find_source(str) + case str + when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name + eval(str, irb_context.workspace.binding) # trigger autoload + base = irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object } + file, line = base.const_source_location(str) if base.respond_to?(:const_source_location) # Ruby 2.7+ + when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method + owner = eval(Regexp.last_match[:owner], irb_context.workspace.binding) + method = Regexp.last_match[:method] + if owner.respond_to?(:instance_method) && owner.instance_methods.include?(method.to_sym) + file, line = owner.instance_method(method).source_location + end + when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method + receiver = eval(Regexp.last_match[:receiver] || 'self', irb_context.workspace.binding) + method = Regexp.last_match[:method] + file, line = receiver.method(method).source_location if receiver.respond_to?(method) + end + if file && line + Source.new(file: file, first_line: line, last_line: find_end(file, line)) + end + end + + def find_end(file, first_line) + return first_line unless File.exist?(file) + lex = RubyLex.new + code = +"" + File.read(file).lines[(first_line - 1)..-1].each_with_index do |line, i| + _ltype, _indent, continue, code_block_open = lex.check_state(code << line) + if !continue && !code_block_open + return first_line + i + end + end + first_line + end + + def bold(str) + Color.colorize(str, [:BOLD]) + end + + Source = Struct.new( + :file, # @param [String] - file name + :first_line, # @param [String] - first line + :last_line, # @param [String] - last line + keyword_init: true, + ) + private_constant :Source + end + end +end +# :startdoc: diff --git a/lib/irb/cmd/whereami.rb b/lib/irb/cmd/whereami.rb new file mode 100644 index 0000000000..b3def11b93 --- /dev/null +++ b/lib/irb/cmd/whereami.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require_relative "nop" + +# :stopdoc: +module IRB + module ExtendCommand + class Whereami < Nop + def execute(*) + code = irb_context.workspace.code_around_binding + if code + puts code + else + puts "The current context doesn't have code." + end + end + end + end +end +# :startdoc: diff --git a/lib/irb/color.rb b/lib/irb/color.rb index a054bb20f8..cfbb3cc668 100644 --- a/lib/irb/color.rb +++ b/lib/irb/color.rb @@ -64,6 +64,7 @@ module IRB # :nodoc: on_alias_error: [[RED, REVERSE], ALL], on_class_name_error:[[RED, REVERSE], ALL], on_param_error: [[RED, REVERSE], ALL], + on___end__: [[GREEN], ALL], } rescue NameError # Give up highlighting Ripper-incompatible older Ruby @@ -120,6 +121,7 @@ module IRB # :nodoc: symbol_state = SymbolState.new colored = +'' length = 0 + end_seen = false scan(code, allow_last_error: !complete) do |token, str, expr| # IRB::ColorPrinter skips colorizing fragments with any invalid token @@ -138,10 +140,11 @@ module IRB # :nodoc: end end length += str.bytesize + end_seen = true if token == :on___end__ end # give up colorizing incomplete Ripper tokens - if length != code.bytesize + unless end_seen or length == code.bytesize return Reline::Unicode.escape_for_print(code) end diff --git a/lib/irb/color_printer.rb b/lib/irb/color_printer.rb index 92afea51cd..30c6825750 100644 --- a/lib/irb/color_printer.rb +++ b/lib/irb/color_printer.rb @@ -21,6 +21,15 @@ module IRB end end + def pp(obj) + if obj.is_a?(String) + # Avoid calling Ruby 2.4+ String#pretty_print that splits a string by "\n" + text(obj.inspect) + else + super + end + end + def text(str, width = nil) unless str.is_a?(String) str = str.inspect diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 22a1ad1d3d..d1bb82122e 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -7,7 +7,7 @@ # From Original Idea of shugo@ruby-lang.org # -autoload :RDoc, "rdoc" +require_relative 'ruby-lex' module IRB module InputCompletor # :nodoc: @@ -38,8 +38,69 @@ module IRB BASIC_WORD_BREAK_CHARACTERS = " \t\n`><=;|&{(" - CompletionProc = proc { |input| - retrieve_completion_data(input).compact.map{ |i| i.encode(Encoding.default_external) } + def self.retrieve_files_to_require_from_load_path + @@files_from_load_path ||= $LOAD_PATH.flat_map { |path| + begin + Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: path) + rescue Errno::ENOENT + [] + end + }.uniq.map { |path| + path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') + } + end + + def self.retrieve_files_to_require_relative_from_current_dir + @@files_from_current_dir ||= Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: '.').map { |path| + path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') + } + end + + CompletionRequireProc = lambda { |target, preposing = nil, postposing = nil| + if target =~ /\A(['"])([^'"]+)\Z/ + quote = $1 + actual_target = $2 + else + return nil # It's not String literal + end + tokens = RubyLex.ripper_lex_without_warning(preposing.gsub(/\s*\z/, '')) + tok = nil + tokens.reverse_each do |t| + unless [:on_lparen, :on_sp, :on_ignored_sp, :on_nl, :on_ignored_nl, :on_comment].include?(t.event) + tok = t + break + end + end + result = [] + if tok && tok.event == :on_ident && tok.state == Ripper::EXPR_CMDARG + case tok.tok + when 'require' + result = retrieve_files_to_require_from_load_path.select { |path| + path.start_with?(actual_target) + }.map { |path| + quote + path + } + when 'require_relative' + result = retrieve_files_to_require_relative_from_current_dir.select { |path| + path.start_with?(actual_target) + }.map { |path| + quote + path + } + end + end + result + } + + CompletionProc = lambda { |target, preposing = nil, postposing = nil| + if preposing && postposing + result = CompletionRequireProc.(target, preposing, postposing) + unless result + result = retrieve_completion_data(target).compact.map{ |i| i.encode(Encoding.default_external) } + end + result + else + retrieve_completion_data(target).compact.map{ |i| i.encode(Encoding.default_external) } + end } def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace.binding, doc_namespace: false) @@ -266,13 +327,22 @@ module IRB end PerfectMatchedProc = ->(matched, bind: IRB.conf[:MAIN_CONTEXT].workspace.binding) { + begin + require 'rdoc' + rescue LoadError + return + end + RDocRIDriver ||= RDoc::RI::Driver.new + if matched =~ /\A(?:::)?RubyVM/ and not ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER'] IRB.__send__(:easter_egg) return end + namespace = retrieve_completion_data(matched, bind: bind, doc_namespace: true) return unless namespace + if namespace.is_a?(Array) out = RDoc::Markup::Document.new namespace.each do |m| diff --git a/lib/irb/ext/save-history.rb b/lib/irb/ext/save-history.rb index ac358c8ccb..7acaebe36a 100644 --- a/lib/irb/ext/save-history.rb +++ b/lib/irb/ext/save-history.rb @@ -81,6 +81,8 @@ module IRB end } end + @loaded_history_lines = history.size + @loaded_history_mtime = File.mtime(history_file) end end @@ -105,12 +107,20 @@ module IRB raise end - open(history_file, "w:#{IRB.conf[:LC_MESSAGES].encoding}", 0600) do |f| + if File.exist?(history_file) && @loaded_history_mtime && + File.mtime(history_file) != @loaded_history_mtime + history = history[@loaded_history_lines..-1] + append_history = true + end + + open(history_file, "#{append_history ? 'a' : 'w'}:#{IRB.conf[:LC_MESSAGES].encoding}", 0600) do |f| hist = history.map{ |l| l.split("\n").join("\\\n") } - begin - hist = hist.last(num) if hist.size > num and num > 0 - rescue RangeError # bignum too big to convert into `long' - # Do nothing because the bignum should be treated as inifinity + unless append_history + begin + hist = hist.last(num) if hist.size > num and num > 0 + rescue RangeError # bignum too big to convert into `long' + # Do nothing because the bignum should be treated as inifinity + end end f.puts(hist) end diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb index 347323247e..339e9e6084 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -126,7 +126,23 @@ module IRB # :nodoc: ], [ - :measure, :Measure, "irb/cmd/measure" + :irb_ls, :Ls, "irb/cmd/ls", + [:ls, NO_OVERRIDE], + ], + + [ + :irb_measure, :Measure, "irb/cmd/measure", + [:measure, NO_OVERRIDE], + ], + + [ + :irb_show_source, :ShowSource, "irb/cmd/show_source", + [:show_source, NO_OVERRIDE], + ], + + [ + :irb_whereami, :Whereami, "irb/cmd/whereami", + [:whereami, NO_OVERRIDE], ], ] @@ -168,12 +184,13 @@ module IRB # :nodoc: end if load_file + kwargs = ", **kwargs" if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.7.0" line = __LINE__; eval %[ - def #{cmd_name}(*opts, &b) + def #{cmd_name}(*opts#{kwargs}, &b) require "#{load_file}" arity = ExtendCommand::#{cmd_class}.instance_method(:execute).arity args = (1..(arity < 0 ? ~arity : arity)).map {|i| "arg" + i.to_s } - args << "*opts" if arity < 0 + args << "*opts#{kwargs}" if arity < 0 args << "&block" args = args.join(", ") line = __LINE__; eval %[ @@ -184,7 +201,7 @@ module IRB # :nodoc: end end ], nil, __FILE__, line - __send__ :#{cmd_name}_, *opts, &b + __send__ :#{cmd_name}_, *opts#{kwargs}, &b end ], nil, __FILE__, line else diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index e223672985..1854567a2d 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -280,6 +280,7 @@ module IRB Reline.basic_word_break_characters = IRB::InputCompletor::BASIC_WORD_BREAK_CHARACTERS end Reline.completion_append_character = nil + Reline.completer_quote_characters = '' Reline.completion_proc = IRB::InputCompletor::CompletionProc Reline.output_modifier_proc = if IRB.conf[:USE_COLORIZE] diff --git a/lib/irb/irb.gemspec b/lib/irb/irb.gemspec index 9842b4bce1..38fee9d9fb 100644 --- a/lib/irb/irb.gemspec +++ b/lib/irb/irb.gemspec @@ -28,53 +28,8 @@ Gem::Specification.new do |spec| "doc/irb/irb.rd.ja", "exe/irb", "irb.gemspec", - "lib/irb.rb", - "lib/irb/cmd/chws.rb", - "lib/irb/cmd/fork.rb", - "lib/irb/cmd/help.rb", - "lib/irb/cmd/info.rb", - "lib/irb/cmd/load.rb", - "lib/irb/cmd/measure.rb", - "lib/irb/cmd/nop.rb", - "lib/irb/cmd/pushws.rb", - "lib/irb/cmd/subirb.rb", - "lib/irb/color.rb", - "lib/irb/color_printer.rb", - "lib/irb/completion.rb", - "lib/irb/context.rb", - "lib/irb/easter-egg.rb", - "lib/irb/ext/change-ws.rb", - "lib/irb/ext/history.rb", - "lib/irb/ext/loader.rb", - "lib/irb/ext/multi-irb.rb", - "lib/irb/ext/save-history.rb", - "lib/irb/ext/tracer.rb", - "lib/irb/ext/use-loader.rb", - "lib/irb/ext/workspaces.rb", - "lib/irb/extend-command.rb", - "lib/irb/frame.rb", - "lib/irb/help.rb", - "lib/irb/init.rb", - "lib/irb/input-method.rb", - "lib/irb/inspector.rb", - "lib/irb/lc/error.rb", - "lib/irb/lc/help-message", - "lib/irb/lc/ja/encoding_aliases.rb", - "lib/irb/lc/ja/error.rb", - "lib/irb/lc/ja/help-message", - "lib/irb/locale.rb", - "lib/irb/magic-file.rb", - "lib/irb/notifier.rb", - "lib/irb/output-method.rb", - "lib/irb/ruby-lex.rb", - "lib/irb/ruby_logo.aa", - "lib/irb/src_encoding.rb", - "lib/irb/version.rb", - "lib/irb/workspace.rb", - "lib/irb/ws-for-case-2.rb", - "lib/irb/xmp.rb", "man/irb.1", - ] + ] + Dir.glob("lib/**/*") spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] diff --git a/lib/irb/lc/help-message b/lib/irb/lc/help-message index a80facc9c5..9c3ea859ad 100644 --- a/lib/irb/lc/help-message +++ b/lib/irb/lc/help-message @@ -10,7 +10,7 @@ # # Usage: irb.rb [options] [programfile] [arguments] - -f Suppress read of ~/.irbrc + -f Suppress read of ~/.irbrc -d Set $DEBUG to true (same as `ruby -d') -r load-module Same as `ruby -r' -I path Specify $LOAD_PATH directory @@ -18,7 +18,7 @@ Usage: irb.rb [options] [programfile] [arguments] -E enc Same as `ruby -E` -w Same as `ruby -w` -W[level=2] Same as `ruby -W` - --context-mode n Set n[0-3] to method to create Binding Object, + --context-mode n Set n[0-4] to method to create Binding Object, when new workspace was created --echo Show result(default) --noecho Don't show result @@ -31,8 +31,8 @@ Usage: irb.rb [options] [programfile] [arguments] --colorize Use colorization --nocolorize Don't use colorization --prompt prompt-mode/--prompt-mode prompt-mode - Switch prompt mode. Pre-defined prompt modes are - `default', `simple', `xmp' and `inf-ruby' + Switch prompt mode. Pre-defined prompt modes are + `default', `simple', `xmp' and `inf-ruby' --inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs. Suppresses --multiline and --singleline. --sample-book-mode/--simple-prompt @@ -41,8 +41,8 @@ Usage: irb.rb [options] [programfile] [arguments] --single-irb Share self with sub-irb. --tracer Display trace for each execution of commands. --back-trace-limit n - Display backtrace top n and tail n. The default - value is 16. + Display backtrace top n and tail n. The default + value is 16. --verbose Show details --noverbose Don't show details -v, --version Print the version of irb diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index ce94797dad..82df06da2b 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -47,12 +47,26 @@ class RubyLex @io = io if @io.respond_to?(:check_termination) @io.check_termination do |code| - code.gsub!(/\s*\z/, '').concat("\n") - ltype, indent, continue, code_block_open = check_state(code) - if ltype or indent > 0 or continue or code_block_open - false + if Reline::IOGate.in_pasting? + lex = RubyLex.new + rest = lex.check_termination_in_prev_line(code) + if rest + Reline.delete_text + rest.bytes.reverse_each do |c| + Reline.ungetc(c) + end + true + else + false + end else - true + code.gsub!(/\s*\z/, '').concat("\n") + ltype, indent, continue, code_block_open = check_state(code) + if ltype or indent > 0 or continue or code_block_open + false + else + true + end end end end @@ -60,7 +74,7 @@ class RubyLex @io.dynamic_prompt do |lines| lines << '' if lines.empty? result = [] - tokens = ripper_lex_without_warning(lines.map{ |l| l + "\n" }.join) + tokens = self.class.ripper_lex_without_warning(lines.map{ |l| l + "\n" }.join) code = String.new partial_tokens = [] unprocessed_tokens = [] @@ -115,10 +129,10 @@ class RubyLex :on_param_error ] - def ripper_lex_without_warning(code) + def self.ripper_lex_without_warning(code) verbose, $VERBOSE = $VERBOSE, nil tokens = nil - self.class.compile_with_errors_suppressed(code) do |inner_code, line_no| + compile_with_errors_suppressed(code) do |inner_code, line_no| lexer = Ripper::Lexer.new(inner_code, '-', line_no) if lexer.respond_to?(:scan) # Ruby 2.7+ tokens = [] @@ -168,7 +182,7 @@ class RubyLex if @io.respond_to?(:auto_indent) and context.auto_indent_mode @io.auto_indent do |lines, line_index, byte_pointer, is_newline| if is_newline - @tokens = ripper_lex_without_warning(lines[0..line_index].join("\n")) + @tokens = self.class.ripper_lex_without_warning(lines[0..line_index].join("\n")) prev_spaces = find_prev_spaces(line_index) depth_difference = check_newline_depth_difference depth_difference = 0 if depth_difference < 0 @@ -177,7 +191,7 @@ class RubyLex code = line_index.zero? ? '' : lines[0..(line_index - 1)].map{ |l| l + "\n" }.join last_line = lines[line_index]&.byteslice(0, byte_pointer) code += last_line if last_line - @tokens = ripper_lex_without_warning(code) + @tokens = self.class.ripper_lex_without_warning(code) corresponding_token_depth = check_corresponding_token_depth if corresponding_token_depth corresponding_token_depth @@ -190,7 +204,7 @@ class RubyLex end def check_state(code, tokens = nil) - tokens = ripper_lex_without_warning(code) unless tokens + tokens = self.class.ripper_lex_without_warning(code) unless tokens ltype = process_literal_type(tokens) indent = process_nesting_level(tokens) continue = process_continue(tokens) @@ -256,7 +270,7 @@ class RubyLex end code = @line + (line.nil? ? '' : line) code.gsub!(/\s*\z/, '').concat("\n") - @tokens = ripper_lex_without_warning(code) + @tokens = self.class.ripper_lex_without_warning(code) @continue = process_continue @code_block_open = check_code_block(code) @indent = process_nesting_level @@ -277,8 +291,9 @@ class RubyLex return true elsif tokens.size >= 1 and tokens[-1][1] == :on_heredoc_end # "EOH\n" return false - elsif tokens.size >= 2 and defined?(Ripper::EXPR_BEG) and tokens[-2][3].anybits?(Ripper::EXPR_BEG | Ripper::EXPR_FNAME) + elsif tokens.size >= 2 and defined?(Ripper::EXPR_BEG) and tokens[-2][3].anybits?(Ripper::EXPR_BEG | Ripper::EXPR_FNAME) and tokens[-2][2] !~ /\A\.\.\.?\z/ # end of literal except for regexp + # endless range at end of line is not a continue return true end false @@ -738,5 +753,50 @@ class RubyLex nil end end + + def check_termination_in_prev_line(code) + tokens = self.class.ripper_lex_without_warning(code) + past_first_newline = false + index = tokens.rindex do |t| + # traverse first token before last line + if past_first_newline + if t.tok.include?("\n") + true + end + elsif t.tok.include?("\n") + past_first_newline = true + false + else + false + end + end + if index + first_token = nil + last_line_tokens = tokens[(index + 1)..(tokens.size - 1)] + last_line_tokens.each do |t| + unless [:on_sp, :on_ignored_sp, :on_comment].include?(t.event) + first_token = t + break + end + end + if first_token.nil? + return false + elsif first_token && first_token.state == Ripper::EXPR_DOT + return false + else + tokens_without_last_line = tokens[0..index] + ltype = process_literal_type(tokens_without_last_line) + indent = process_nesting_level(tokens_without_last_line) + continue = process_continue(tokens_without_last_line) + code_block_open = check_code_block(tokens_without_last_line.map(&:tok).join(''), tokens_without_last_line) + if ltype or indent > 0 or continue or code_block_open + return false + else + return last_line_tokens.map(&:tok).join('') + end + end + end + false + end end # :startdoc: diff --git a/lib/irb/version.rb b/lib/irb/version.rb index 0a4a1bb922..0014bdda74 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -11,7 +11,7 @@ # module IRB # :nodoc: - VERSION = "1.3.4" + VERSION = "1.3.5" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2021-02-25" + @LAST_UPDATE_DATE = "2021-04-03" end diff --git a/lib/mkmf.rb b/lib/mkmf.rb index 672c5448fa..66f2960cb8 100644 --- a/lib/mkmf.rb +++ b/lib/mkmf.rb @@ -207,8 +207,8 @@ module MakeMakefile ['RUBYCOMMONDIR', '$(vendordir)$(target_prefix)'], ['RUBYLIBDIR', '$(vendorlibdir)$(target_prefix)'], ['RUBYARCHDIR', '$(vendorarchdir)$(target_prefix)'], - ['HDRDIR', '$(rubyhdrdir)/ruby$(target_prefix)'], - ['ARCHHDRDIR', '$(rubyhdrdir)/$(arch)/ruby$(target_prefix)'], + ['HDRDIR', '$(vendorhdrdir)$(target_prefix)'], + ['ARCHHDRDIR', '$(vendorarchhdrdir)$(target_prefix)'], ] else dirs = [ @@ -216,8 +216,8 @@ module MakeMakefile ['RUBYCOMMONDIR', '$(sitedir)$(target_prefix)'], ['RUBYLIBDIR', '$(sitelibdir)$(target_prefix)'], ['RUBYARCHDIR', '$(sitearchdir)$(target_prefix)'], - ['HDRDIR', '$(rubyhdrdir)/ruby$(target_prefix)'], - ['ARCHHDRDIR', '$(rubyhdrdir)/$(arch)/ruby$(target_prefix)'], + ['HDRDIR', '$(sitehdrdir)$(target_prefix)'], + ['ARCHHDRDIR', '$(sitearchhdrdir)$(target_prefix)'], ] end dirs << ['target_prefix', (target_prefix ? "/#{target_prefix}" : "")] @@ -1942,7 +1942,7 @@ NULLCMD = #{CONFIG['NULLCMD']} srcdir = #{srcdir.gsub(/\$\((srcdir)\)|\$\{(srcdir)\}/) {mkintpath(CONFIG[$1||$2]).unspace}} topdir = #{mkintpath(topdir = $extmk ? CONFIG["topdir"] : $topdir).unspace} hdrdir = #{(hdrdir = CONFIG["hdrdir"]) == topdir ? "$(topdir)" : mkintpath(hdrdir).unspace} -arch_hdrdir = #{$arch_hdrdir.quote} +arch_hdrdir = #{mkintpath($arch_hdrdir).unspace} PATH_SEPARATOR = #{CONFIG['PATH_SEPARATOR']} VPATH = #{vpath.join(CONFIG['PATH_SEPARATOR'])} } @@ -2034,6 +2034,11 @@ sitearch = #{CONFIG['sitearch']} ruby_version = #{RbConfig::CONFIG['ruby_version']} ruby = #{$ruby.sub(%r[\A#{Regexp.quote(RbConfig::CONFIG['bindir'])}(?=/|\z)]) {'$(bindir)'}} RUBY = $(ruby#{sep}) +BUILTRUBY = #{if defined?($builtruby) && $builtruby + $builtruby + else + File.join('$(bindir)', CONFIG["RUBY_INSTALL_NAME"] + CONFIG['EXEEXT']) + end} ruby_headers = #{headers.join(' ')} RM = #{config_string('RM', &possible_command) || '$(RUBY) -run -e rm -- -f'} @@ -2552,6 +2557,7 @@ site-install-rb: install-rb $INCFLAGS << " -I$(hdrdir)/ruby/backward" unless $extmk $INCFLAGS << " -I$(hdrdir) -I$(srcdir)" $DLDFLAGS = with_config("dldflags", arg_config("DLDFLAGS", config["DLDFLAGS"])).dup + config_string("ADDITIONAL_DLDFLAGS") {|flags| $DLDFLAGS << " " << flags} unless $extmk $LIBEXT = config['LIBEXT'].dup $OBJEXT = config["OBJEXT"].dup $EXEEXT = config["EXEEXT"].dup diff --git a/lib/net/ftp.rb b/lib/net/ftp.rb index 88e8655c1c..2161d30d7c 100644 --- a/lib/net/ftp.rb +++ b/lib/net/ftp.rb @@ -85,7 +85,7 @@ module Net end # :stopdoc: - VERSION = "0.1.1" + VERSION = "0.1.2" FTP_PORT = 21 CRLF = "\r\n" DEFAULT_BLOCKSIZE = BufferedIO::BUFSIZE @@ -98,6 +98,10 @@ module Net # When +true+, the connection is in passive mode. Default: +true+. attr_accessor :passive + # When +true+, use the IP address in PASV responses. Otherwise, it uses + # the same IP address for the control connection. Default: +false+. + attr_accessor :use_pasv_ip + # When +true+, all traffic to and from the server is written # to +$stdout+. Default: +false+. attr_accessor :debug_mode @@ -206,6 +210,9 @@ module Net # handshake. # See Net::FTP#ssl_handshake_timeout for # details. Default: +nil+. + # use_pasv_ip:: When +true+, use the IP address in PASV responses. + # Otherwise, it uses the same IP address for the control + # connection. Default: +false+. # debug_mode:: When +true+, all traffic to and from the server is # written to +$stdout+. Default: +false+. # @@ -266,6 +273,7 @@ module Net @open_timeout = options[:open_timeout] @ssl_handshake_timeout = options[:ssl_handshake_timeout] @read_timeout = options[:read_timeout] || 60 + @use_pasv_ip = options[:use_pasv_ip] || false if host connect(host, options[:port] || FTP_PORT) if options[:username] @@ -330,14 +338,19 @@ module Net # SOCKS_SERVER, then a SOCKSSocket is returned, else a Socket is # returned. def open_socket(host, port) # :nodoc: - return Timeout.timeout(@open_timeout, OpenTimeout) { - if defined? SOCKSSocket and ENV["SOCKS_SERVER"] - @passive = true + if defined? SOCKSSocket and ENV["SOCKS_SERVER"] + @passive = true + Timeout.timeout(@open_timeout, OpenTimeout) do SOCKSSocket.open(host, port) - else - Socket.tcp(host, port) end - } + else + begin + Socket.tcp host, port, nil, nil, connect_timeout: @open_timeout + rescue Errno::ETIMEDOUT #raise Net:OpenTimeout instead for compatibility with previous versions + raise Net::OpenTimeout, "Timeout to open TCP connection to "\ + "#{host}:#{port} (exceeds #{@open_timeout} seconds)" + end + end end private :open_socket @@ -542,18 +555,22 @@ module Net def transfercmd(cmd, rest_offset = nil) # :nodoc: if @passive host, port = makepasv - conn = open_socket(host, port) - if @resume and rest_offset - resp = sendcmd("REST " + rest_offset.to_s) - if !resp.start_with?("3") + begin + conn = open_socket(host, port) + if @resume and rest_offset + resp = sendcmd("REST " + rest_offset.to_s) + if !resp.start_with?("3") + raise FTPReplyError, resp + end + end + resp = sendcmd(cmd) + # skip 2XX for some ftp servers + resp = getresp if resp.start_with?("2") + if !resp.start_with?("1") raise FTPReplyError, resp end - end - resp = sendcmd(cmd) - # skip 2XX for some ftp servers - resp = getresp if resp.start_with?("2") - if !resp.start_with?("1") - raise FTPReplyError, resp + ensure + conn.close if conn && $! end else sock = makeport @@ -1045,10 +1062,11 @@ module Net TIME_PARSER = ->(value, local = false) { unless /\A(?<year>\d{4})(?<month>\d{2})(?<day>\d{2}) (?<hour>\d{2})(?<min>\d{2})(?<sec>\d{2}) - (?:\.(?<fractions>\d+))?/x =~ value + (?:\.(?<fractions>\d{1,17}))?/x =~ value + value = value[0, 97] + "..." if value.size > 100 raise FTPProtoError, "invalid time-val: #{value}" end - usec = fractions.to_i * 10 ** (6 - fractions.to_s.size) + usec = ".#{fractions}".to_r * 1_000_000 if fractions Time.public_send(local ? :local : :utc, year, month, day, hour, min, sec, usec) } FACT_PARSERS = Hash.new(CASE_DEPENDENT_PARSER) @@ -1356,7 +1374,7 @@ module Net end # - # Returns +true+ iff the connection is closed. + # Returns +true+ if and only if the connection is closed. # def closed? @sock == nil or @sock.closed? @@ -1371,7 +1389,12 @@ module Net raise FTPReplyError, resp end if m = /\((?<host>\d+(?:,\d+){3}),(?<port>\d+,\d+)\)/.match(resp) - return parse_pasv_ipv4_host(m["host"]), parse_pasv_port(m["port"]) + if @use_pasv_ip + host = parse_pasv_ipv4_host(m["host"]) + else + host = @bare_sock.remote_address.ip_address + end + return host, parse_pasv_port(m["port"]) else raise FTPProtoError, resp end diff --git a/lib/net/http/header.rb b/lib/net/http/header.rb index a8901e79cb..495425d148 100644 --- a/lib/net/http/header.rb +++ b/lib/net/http/header.rb @@ -9,6 +9,8 @@ # convenient formats. # module Net::HTTPHeader + MAX_KEY_LENGTH = 1024 + MAX_FIELD_LENGTH = 65536 def initialize_http_header(initheader) @header = {} @@ -19,6 +21,12 @@ module Net::HTTPHeader warn "net/http: nil HTTP header: #{key}", uplevel: 3 if $VERBOSE else value = value.strip # raise error for invalid byte sequences + if key.to_s.bytesize > MAX_KEY_LENGTH + raise ArgumentError, "too long (#{key.bytesize} bytes) header: #{key[0, 30].inspect}..." + end + if value.to_s.bytesize > MAX_FIELD_LENGTH + raise ArgumentError, "header #{key} has too long field vallue: #{value.bytesize}" + end if value.count("\r\n") > 0 raise ArgumentError, "header #{key} has field value #{value.inspect}, this cannot include CR/LF" end diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 505b4c8950..d45304f289 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -1218,12 +1218,14 @@ module Net end resp = @tagged_responses.delete(tag) case resp.name + when /\A(?:OK)\z/ni + return resp when /\A(?:NO)\z/ni raise NoResponseError, resp when /\A(?:BAD)\z/ni raise BadResponseError, resp else - return resp + raise UnknownResponseError, resp end end @@ -3719,6 +3721,10 @@ module Net class ByeResponseError < ResponseError end + # Error raised upon an unknown response from the server. + class UnknownResponseError < ResponseError + end + RESPONSE_ERRORS = Hash.new(ResponseError) RESPONSE_ERRORS["NO"] = NoResponseError RESPONSE_ERRORS["BAD"] = BadResponseError diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb index cba92613c7..edf2ed5a00 100644 --- a/lib/net/protocol.rb +++ b/lib/net/protocol.rb @@ -26,7 +26,7 @@ require 'io/wait' module Net # :nodoc: class Protocol #:nodoc: internal use only - VERSION = "0.1.0" + VERSION = "0.1.1" private def Protocol.protocol_param(name, val) diff --git a/lib/optparse.rb b/lib/optparse.rb index bc0e821460..598ebd12bd 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -72,10 +72,10 @@ # require 'optparse' # # options = {} -# OptionParser.new do |opts| -# opts.banner = "Usage: example.rb [options]" +# OptionParser.new do |parser| +# parser.banner = "Usage: example.rb [options]" # -# opts.on("-v", "--[no-]verbose", "Run verbosely") do |v| +# parser.on("-v", "--[no-]verbose", "Run verbosely") do |v| # options[:verbose] = v # end # end.parse! @@ -96,15 +96,15 @@ # def self.parse(options) # args = Options.new("world") # -# opt_parser = OptionParser.new do |opts| -# opts.banner = "Usage: example.rb [options]" +# opt_parser = OptionParser.new do |parser| +# parser.banner = "Usage: example.rb [options]" # -# opts.on("-nNAME", "--name=NAME", "Name to say hello to") do |n| +# parser.on("-nNAME", "--name=NAME", "Name to say hello to") do |n| # args.name = n # end # -# opts.on("-h", "--help", "Prints this help") do -# puts opts +# parser.on("-h", "--help", "Prints this help") do +# puts parser # exit # end # end @@ -241,10 +241,10 @@ # require 'optparse' # # params = {} -# OptionParser.new do |opts| -# opts.on('-a') -# opts.on('-b NUM', Integer) -# opts.on('-v', '--verbose') +# OptionParser.new do |parser| +# parser.on('-a') +# parser.on('-b NUM', Integer) +# parser.on('-v', '--verbose') # end.parse!(into: params) # # p params @@ -419,7 +419,7 @@ # have any questions, file a ticket at http://bugs.ruby-lang.org. # class OptionParser - OptionParser::Version = "0.1.0" + OptionParser::Version = "0.1.1" # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze @@ -1091,6 +1091,7 @@ XXX @summary_width = width @summary_indent = indent @default_argv = ARGV + @require_exact = false add_officious yield self if block_given? end @@ -1164,6 +1165,10 @@ XXX # Strings to be parsed in default. attr_accessor :default_argv + # Whether to require that options match exactly (disallows providing + # abbreviated long option as short option). + attr_accessor :require_exact + # # Heading banner preceding summary. # @@ -1305,13 +1310,16 @@ XXX private :notwice SPLAT_PROC = proc {|*a| a.length <= 1 ? a.first : a} # :nodoc: + + # :call-seq: + # make_switch(params, block = nil) # # Creates an OptionParser::Switch from the parameters. The parsed argument # value is passed to the given block, where it can be processed. # # See at the beginning of OptionParser for some full examples. # - # +opts+ can include the following elements: + # +params+ can include the following elements: # # [Argument style:] # One of the following: @@ -1498,11 +1506,16 @@ XXX nolong end + # :call-seq: + # define(*params, &block) + # def define(*opts, &block) top.append(*(sw = make_switch(opts, block))) sw[0] end + # :call-seq: + # on(*params, &block) # # Add option switch and handler. See #make_switch for an explanation of # parameters. @@ -1513,11 +1526,16 @@ XXX end alias def_option define + # :call-seq: + # define_head(*params, &block) + # def define_head(*opts, &block) top.prepend(*(sw = make_switch(opts, block))) sw[0] end + # :call-seq: + # on_head(*params, &block) # # Add option switch like with #on, but at head of summary. # @@ -1527,12 +1545,18 @@ XXX end alias def_head_option define_head + # :call-seq: + # define_tail(*params, &block) + # def define_tail(*opts, &block) base.append(*(sw = make_switch(opts, block))) sw[0] end # + # :call-seq: + # on_tail(*params, &block) + # # Add option switch like with #on, but at tail of summary. # def on_tail(*opts, &block) @@ -1583,6 +1607,9 @@ XXX opt.tr!('_', '-') begin sw, = complete(:long, opt, true) + if require_exact && !sw.long.include?(arg) + raise InvalidOption, arg + end rescue ParseError raise $!.set_option(arg, true) end @@ -1607,6 +1634,7 @@ XXX val = arg.delete_prefix('-') has_arg = true rescue InvalidOption + raise if require_exact # if no short options match, try completion with long # options. sw, = complete(:long, opt) @@ -1618,7 +1646,12 @@ XXX end begin opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq} + rescue ParseError + raise $!.set_option(arg, arg.length > 2) + else raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}" + end + begin argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-') val = cb.call(val) if cb setter.call(sw.switch_name, val) if setter diff --git a/lib/optparse/kwargs.rb b/lib/optparse/kwargs.rb index ed58cc142b..5a2def4747 100644 --- a/lib/optparse/kwargs.rb +++ b/lib/optparse/kwargs.rb @@ -2,6 +2,9 @@ require 'optparse' class OptionParser + # :call-seq: + # define_by_keywords(options, method, **params) + # def define_by_keywords(options, meth, **opts) meth.parameters.each do |type, name| case type diff --git a/lib/optparse/optparse.gemspec b/lib/optparse/optparse.gemspec index afb90420f0..831c787ac7 100644 --- a/lib/optparse/optparse.gemspec +++ b/lib/optparse/optparse.gemspec @@ -23,7 +23,9 @@ Gem::Specification.new do |spec| spec.metadata["source_code_uri"] = spec.homepage spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) } + `git ls-files -z`.split("\x0").reject { |f| + f.match(%r{\A(?:(?:test|spec|features)/|Gemfile|\.(?:editor|git))}) + } end spec.bindir = "exe" spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } diff --git a/lib/pp.gemspec b/lib/pp.gemspec index f3f3527086..b81df28bee 100644 --- a/lib/pp.gemspec +++ b/lib/pp.gemspec @@ -1,21 +1,24 @@ Gem::Specification.new do |spec| spec.name = "pp" - spec.version = "0.1.0" + spec.version = "0.2.1" spec.authors = ["Tanaka Akira"] spec.email = ["akr@fsij.org"] spec.summary = %q{Provides a PrettyPrinter for Ruby objects} spec.description = %q{Provides a PrettyPrinter for Ruby objects} spec.homepage = "https://github.com/ruby/pp" - spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0") spec.licenses = ["Ruby", "BSD-2-Clause"] + spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0") + spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end + spec.files = %w[ + LICENSE.txt + lib/pp.rb + pp.gemspec + ] spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] @@ -237,7 +237,7 @@ class PP < PrettyPrint else sep.call end - yield(*v, **{}) + RUBY_VERSION >= "3.0" ? yield(*v, **{}) : yield(*v) } end diff --git a/lib/prettyprint.gemspec b/lib/prettyprint.gemspec index 169267fb16..eae2227d60 100644 --- a/lib/prettyprint.gemspec +++ b/lib/prettyprint.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "prettyprint" - spec.version = "0.1.0" + spec.version = "0.1.1" spec.authors = ["Tanaka Akira"] spec.email = ["akr@fsij.org"] diff --git a/lib/racc/info.rb b/lib/racc/info.rb index 6934f902e8..f599b13cbb 100644 --- a/lib/racc/info.rb +++ b/lib/racc/info.rb @@ -11,7 +11,7 @@ #++ module Racc - VERSION = '1.5.1' + VERSION = '1.5.2' Version = VERSION Copyright = 'Copyright (c) 1999-2006 Minero Aoki' end diff --git a/lib/racc/racc.gemspec b/lib/racc/racc.gemspec index 1e375fe26b..5c345891ad 100644 --- a/lib/racc/racc.gemspec +++ b/lib/racc/racc.gemspec @@ -1,8 +1,14 @@ # -*- encoding: utf-8 -*- +begin + require_relative "lib/racc/info" +rescue LoadError # Fallback to load version file in ruby core repository + require_relative "info" +end + Gem::Specification.new do |s| s.name = "racc" - s.version = "1.5.1" + s.version = Racc::VERSION s.summary = "Racc is a LALR(1) parser generator" s.description = <<DESC Racc is a LALR(1) parser generator. diff --git a/lib/rdoc/generator/template/darkfish/css/rdoc.css b/lib/rdoc/generator/template/darkfish/css/rdoc.css index 35aff77fcb..ebe2e93af6 100644 --- a/lib/rdoc/generator/template/darkfish/css/rdoc.css +++ b/lib/rdoc/generator/template/darkfish/css/rdoc.css @@ -79,6 +79,25 @@ pre { border-radius: 0.2em; } +table { + margin: 0; + border-spacing: 0; + border-collapse: collapse; +} + +table tr th, table tr td { + padding: 0.2em 0.4em; + border: 1px solid #ccc; +} + +table tr th { + background-color: #eceaed; +} + +table tr:nth-child(even) td { + background-color: #f5f4f6; +} + /* @group Generic Classes */ .initially-hidden { @@ -166,6 +185,7 @@ nav { width: 260px; font-family: Helvetica, sans-serif; font-size: 14px; + border-right: 1px solid #ccc; } main { diff --git a/lib/rdoc/markdown.rb b/lib/rdoc/markdown.rb index 903e744105..3442f76b1b 100644 --- a/lib/rdoc/markdown.rb +++ b/lib/rdoc/markdown.rb @@ -344,9 +344,8 @@ class RDoc::Markdown end def scan(reg) - if m = reg.match(@string[@pos..-1]) - width = m.end(0) - @pos += width + if m = reg.match(@string, @pos) + @pos = m.end(0) return true end @@ -897,7 +896,7 @@ class RDoc::Markdown return _tmp end - # Block = @BlankLine* (BlockQuote | Verbatim | CodeFence | Note | Reference | HorizontalRule | Heading | OrderedList | BulletList | DefinitionList | HtmlBlock | StyleBlock | Para | Plain) + # Block = @BlankLine* (BlockQuote | Verbatim | CodeFence | Table | Note | Reference | HorizontalRule | Heading | OrderedList | BulletList | DefinitionList | HtmlBlock | StyleBlock | Para | Plain) def _Block _save = self.pos @@ -923,6 +922,9 @@ class RDoc::Markdown _tmp = apply(:_CodeFence) break if _tmp self.pos = _save2 + _tmp = apply(:_Table) + break if _tmp + self.pos = _save2 _tmp = apply(:_Note) break if _tmp self.pos = _save2 @@ -1057,7 +1059,7 @@ class RDoc::Markdown self.pos = _save3 break end - _tmp = scan(/\A(?-mix:#*)/) + _tmp = scan(/\G(?-mix:#*)/) unless _tmp self.pos = _save3 break @@ -1097,7 +1099,7 @@ class RDoc::Markdown _save = self.pos while true # sequence _text_start = self.pos - _tmp = scan(/\A(?-mix:\#{1,6})/) + _tmp = scan(/\G(?-mix:\#{1,6})/) if _tmp text = get_text(_text_start) end @@ -1162,7 +1164,7 @@ class RDoc::Markdown self.pos = _save3 break end - _tmp = scan(/\A(?-mix:#*)/) + _tmp = scan(/\G(?-mix:#*)/) unless _tmp self.pos = _save3 break @@ -1222,7 +1224,7 @@ class RDoc::Markdown _save = self.pos while true # sequence - _tmp = scan(/\A(?-mix:={1,})/) + _tmp = scan(/\G(?-mix:={1,})/) unless _tmp self.pos = _save break @@ -1243,7 +1245,7 @@ class RDoc::Markdown _save = self.pos while true # sequence - _tmp = scan(/\A(?-mix:-{1,})/) + _tmp = scan(/\G(?-mix:-{1,})/) unless _tmp self.pos = _save break @@ -2127,7 +2129,7 @@ class RDoc::Markdown self.pos = _save break end - _tmp = scan(/\A(?-mix:[+*-])/) + _tmp = scan(/\G(?-mix:[+*-])/) unless _tmp self.pos = _save break @@ -9320,7 +9322,7 @@ class RDoc::Markdown return _tmp end - # Inlines = (!@Endline Inline:i { i } | @Endline:c &Inline { c })+:chunks @Endline? { chunks } + # Inlines = (!@Endline Inline:i { i } | @Endline:c !(&{ github? } Ticks3 /[^`\n]*$/) &Inline { c })+:chunks @Endline? { chunks } def _Inlines _save = self.pos @@ -9367,12 +9369,41 @@ class RDoc::Markdown break end _save6 = self.pos - _tmp = apply(:_Inline) + + _save7 = self.pos + while true # sequence + _save8 = self.pos + _tmp = begin; github? ; end + self.pos = _save8 + unless _tmp + self.pos = _save7 + break + end + _tmp = apply(:_Ticks3) + unless _tmp + self.pos = _save7 + break + end + _tmp = scan(/\G(?-mix:[^`\n]*$)/) + unless _tmp + self.pos = _save7 + end + break + end # end sequence + + _tmp = _tmp ? nil : true self.pos = _save6 unless _tmp self.pos = _save5 break end + _save9 = self.pos + _tmp = apply(:_Inline) + self.pos = _save9 + unless _tmp + self.pos = _save5 + break + end @result = begin; c ; end _tmp = true unless _tmp @@ -9390,61 +9421,90 @@ class RDoc::Markdown _ary << @result while true - _save7 = self.pos + _save10 = self.pos while true # choice - _save8 = self.pos + _save11 = self.pos while true # sequence - _save9 = self.pos + _save12 = self.pos _tmp = _Endline() _tmp = _tmp ? nil : true - self.pos = _save9 + self.pos = _save12 unless _tmp - self.pos = _save8 + self.pos = _save11 break end _tmp = apply(:_Inline) i = @result unless _tmp - self.pos = _save8 + self.pos = _save11 break end @result = begin; i ; end _tmp = true unless _tmp - self.pos = _save8 + self.pos = _save11 end break end # end sequence break if _tmp - self.pos = _save7 + self.pos = _save10 - _save10 = self.pos + _save13 = self.pos while true # sequence _tmp = _Endline() c = @result unless _tmp - self.pos = _save10 + self.pos = _save13 break end - _save11 = self.pos + _save14 = self.pos + + _save15 = self.pos + while true # sequence + _save16 = self.pos + _tmp = begin; github? ; end + self.pos = _save16 + unless _tmp + self.pos = _save15 + break + end + _tmp = apply(:_Ticks3) + unless _tmp + self.pos = _save15 + break + end + _tmp = scan(/\G(?-mix:[^`\n]*$)/) + unless _tmp + self.pos = _save15 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save14 + unless _tmp + self.pos = _save13 + break + end + _save17 = self.pos _tmp = apply(:_Inline) - self.pos = _save11 + self.pos = _save17 unless _tmp - self.pos = _save10 + self.pos = _save13 break end @result = begin; c ; end _tmp = true unless _tmp - self.pos = _save10 + self.pos = _save13 end break end # end sequence break if _tmp - self.pos = _save7 + self.pos = _save10 break end # end choice @@ -9461,11 +9521,11 @@ class RDoc::Markdown self.pos = _save break end - _save12 = self.pos + _save18 = self.pos _tmp = _Endline() unless _tmp _tmp = true - self.pos = _save12 + self.pos = _save18 end unless _tmp self.pos = _save @@ -9664,7 +9724,7 @@ class RDoc::Markdown _save3 = self.pos while true # sequence - _tmp = scan(/\A(?-mix:_+)/) + _tmp = scan(/\G(?-mix:_+)/) unless _tmp self.pos = _save3 break @@ -9694,7 +9754,7 @@ class RDoc::Markdown _save6 = self.pos while true # sequence - _tmp = scan(/\A(?-mix:_+)/) + _tmp = scan(/\G(?-mix:_+)/) unless _tmp self.pos = _save6 break @@ -9757,7 +9817,7 @@ class RDoc::Markdown break end _text_start = self.pos - _tmp = scan(/\A(?-mix:[:\\`|*_{}\[\]()#+.!><-])/) + _tmp = scan(/\G(?-mix:[:\\`|*_{}\[\]()#+.!><-])/) if _tmp text = get_text(_text_start) end @@ -9883,7 +9943,7 @@ class RDoc::Markdown self.pos = _save5 break end - _tmp = scan(/\A(?-mix:={1,}|-{1,})/) + _tmp = scan(/\G(?-mix:={1,}|-{1,})/) unless _tmp self.pos = _save5 break @@ -10035,7 +10095,7 @@ class RDoc::Markdown _save1 = self.pos while true # sequence _text_start = self.pos - _tmp = scan(/\A(?-mix:\*{4,})/) + _tmp = scan(/\G(?-mix:\*{4,})/) if _tmp text = get_text(_text_start) end @@ -10065,7 +10125,7 @@ class RDoc::Markdown self.pos = _save3 break end - _tmp = scan(/\A(?-mix:\*+)/) + _tmp = scan(/\G(?-mix:\*+)/) unless _tmp self.pos = _save3 break @@ -10112,7 +10172,7 @@ class RDoc::Markdown _save1 = self.pos while true # sequence _text_start = self.pos - _tmp = scan(/\A(?-mix:_{4,})/) + _tmp = scan(/\G(?-mix:_{4,})/) if _tmp text = get_text(_text_start) end @@ -10142,7 +10202,7 @@ class RDoc::Markdown self.pos = _save3 break end - _tmp = scan(/\A(?-mix:_+)/) + _tmp = scan(/\G(?-mix:_+)/) unless _tmp self.pos = _save3 break @@ -11503,7 +11563,7 @@ class RDoc::Markdown _save1 = self.pos while true # sequence - _tmp = scan(/\A(?-mix:[A-Za-z]+)/) + _tmp = scan(/\G(?-mix:[A-Za-z]+)/) unless _tmp self.pos = _save1 break @@ -11628,7 +11688,7 @@ class RDoc::Markdown _save2 = self.pos while true # sequence - _tmp = scan(/\A(?i-mx:[\w+.\/!%~$-]+)/) + _tmp = scan(/\G(?i-mx:[\w+.\/!%~$-]+)/) unless _tmp self.pos = _save2 break @@ -12492,7 +12552,7 @@ class RDoc::Markdown self.pos = _save10 break end - _tmp = scan(/\A(?-mix:`+)/) + _tmp = scan(/\G(?-mix:`+)/) unless _tmp self.pos = _save10 end @@ -12629,7 +12689,7 @@ class RDoc::Markdown self.pos = _save24 break end - _tmp = scan(/\A(?-mix:`+)/) + _tmp = scan(/\G(?-mix:`+)/) unless _tmp self.pos = _save24 end @@ -12806,7 +12866,7 @@ class RDoc::Markdown self.pos = _save40 break end - _tmp = scan(/\A(?-mix:`+)/) + _tmp = scan(/\G(?-mix:`+)/) unless _tmp self.pos = _save40 end @@ -12943,7 +13003,7 @@ class RDoc::Markdown self.pos = _save54 break end - _tmp = scan(/\A(?-mix:`+)/) + _tmp = scan(/\G(?-mix:`+)/) unless _tmp self.pos = _save54 end @@ -13120,7 +13180,7 @@ class RDoc::Markdown self.pos = _save70 break end - _tmp = scan(/\A(?-mix:`+)/) + _tmp = scan(/\G(?-mix:`+)/) unless _tmp self.pos = _save70 end @@ -13257,7 +13317,7 @@ class RDoc::Markdown self.pos = _save84 break end - _tmp = scan(/\A(?-mix:`+)/) + _tmp = scan(/\G(?-mix:`+)/) unless _tmp self.pos = _save84 end @@ -13434,7 +13494,7 @@ class RDoc::Markdown self.pos = _save100 break end - _tmp = scan(/\A(?-mix:`+)/) + _tmp = scan(/\G(?-mix:`+)/) unless _tmp self.pos = _save100 end @@ -13571,7 +13631,7 @@ class RDoc::Markdown self.pos = _save114 break end - _tmp = scan(/\A(?-mix:`+)/) + _tmp = scan(/\G(?-mix:`+)/) unless _tmp self.pos = _save114 end @@ -13748,7 +13808,7 @@ class RDoc::Markdown self.pos = _save130 break end - _tmp = scan(/\A(?-mix:`+)/) + _tmp = scan(/\G(?-mix:`+)/) unless _tmp self.pos = _save130 end @@ -13885,7 +13945,7 @@ class RDoc::Markdown self.pos = _save144 break end - _tmp = scan(/\A(?-mix:`+)/) + _tmp = scan(/\G(?-mix:`+)/) unless _tmp self.pos = _save144 end @@ -14537,7 +14597,7 @@ class RDoc::Markdown _save = self.pos while true # choice - _tmp = scan(/\A(?-mix:[~*_`&\[\]()<!#\\'"])/) + _tmp = scan(/\G(?-mix:[~*_`&\[\]()<!#\\'"])/) break if _tmp self.pos = _save _tmp = _ExtendedSpecialChar() @@ -14642,13 +14702,13 @@ class RDoc::Markdown _save = self.pos while true # sequence - _tmp = scan(/\A(?i-mx:&#x)/) + _tmp = scan(/\G(?i-mx:&#x)/) unless _tmp self.pos = _save break end _text_start = self.pos - _tmp = scan(/\A(?-mix:[0-9a-fA-F]+)/) + _tmp = scan(/\G(?-mix:[0-9a-fA-F]+)/) if _tmp text = get_text(_text_start) end @@ -14684,7 +14744,7 @@ class RDoc::Markdown break end _text_start = self.pos - _tmp = scan(/\A(?-mix:[0-9]+)/) + _tmp = scan(/\G(?-mix:[0-9]+)/) if _tmp text = get_text(_text_start) end @@ -14720,7 +14780,7 @@ class RDoc::Markdown break end _text_start = self.pos - _tmp = scan(/\A(?-mix:[A-Za-z0-9]+)/) + _tmp = scan(/\G(?-mix:[A-Za-z0-9]+)/) if _tmp text = get_text(_text_start) end @@ -14752,14 +14812,14 @@ class RDoc::Markdown # NonindentSpace = / {0,3}/ def _NonindentSpace - _tmp = scan(/\A(?-mix: {0,3})/) + _tmp = scan(/\G(?-mix: {0,3})/) set_failed_rule :_NonindentSpace unless _tmp return _tmp end # Indent = /\t| / def _Indent - _tmp = scan(/\A(?-mix:\t| )/) + _tmp = scan(/\G(?-mix:\t| )/) set_failed_rule :_Indent unless _tmp return _tmp end @@ -15701,7 +15761,7 @@ class RDoc::Markdown self.pos = _save11 break end - _tmp = scan(/\A(?-mix:`+)/) + _tmp = scan(/\G(?-mix:`+)/) unless _tmp self.pos = _save11 end @@ -15782,7 +15842,7 @@ class RDoc::Markdown self.pos = _save19 break end - _tmp = scan(/\A(?-mix:`+)/) + _tmp = scan(/\G(?-mix:`+)/) unless _tmp self.pos = _save19 end @@ -15847,6 +15907,338 @@ class RDoc::Markdown return _tmp end + # Table = &{ github? } TableRow:header TableLine:line TableRow+:body { table = RDoc::Markup::Table.new(header, line, body) } + def _Table + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = begin; github? ; end + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_TableRow) + header = @result + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_TableLine) + line = @result + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + _ary = [] + _tmp = apply(:_TableRow) + if _tmp + _ary << @result + while true + _tmp = apply(:_TableRow) + _ary << @result if _tmp + break unless _tmp + end + _tmp = true + @result = _ary + else + self.pos = _save2 + end + body = @result + unless _tmp + self.pos = _save + break + end + @result = begin; table = RDoc::Markup::Table.new(header, line, body) ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Table unless _tmp + return _tmp + end + + # TableRow = TableItem+:row "|" @Newline { row } + def _TableRow + + _save = self.pos + while true # sequence + _save1 = self.pos + _ary = [] + _tmp = apply(:_TableItem) + if _tmp + _ary << @result + while true + _tmp = apply(:_TableItem) + _ary << @result if _tmp + break unless _tmp + end + _tmp = true + @result = _ary + else + self.pos = _save1 + end + row = @result + unless _tmp + self.pos = _save + break + end + _tmp = match_string("|") + unless _tmp + self.pos = _save + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save + break + end + @result = begin; row ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_TableRow unless _tmp + return _tmp + end + + # TableItem = "|" < (!"|" !@Newline .)+ > { text.strip } + def _TableItem + + _save = self.pos + while true # sequence + _tmp = match_string("|") + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + _save1 = self.pos + + _save2 = self.pos + while true # sequence + _save3 = self.pos + _tmp = match_string("|") + _tmp = _tmp ? nil : true + self.pos = _save3 + unless _tmp + self.pos = _save2 + break + end + _save4 = self.pos + _tmp = _Newline() + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save2 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + if _tmp + while true + + _save5 = self.pos + while true # sequence + _save6 = self.pos + _tmp = match_string("|") + _tmp = _tmp ? nil : true + self.pos = _save6 + unless _tmp + self.pos = _save5 + break + end + _save7 = self.pos + _tmp = _Newline() + _tmp = _tmp ? nil : true + self.pos = _save7 + unless _tmp + self.pos = _save5 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save1 + end + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + @result = begin; text.strip ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_TableItem unless _tmp + return _tmp + end + + # TableLine = TableColumn+:line "|" @Newline { line } + def _TableLine + + _save = self.pos + while true # sequence + _save1 = self.pos + _ary = [] + _tmp = apply(:_TableColumn) + if _tmp + _ary << @result + while true + _tmp = apply(:_TableColumn) + _ary << @result if _tmp + break unless _tmp + end + _tmp = true + @result = _ary + else + self.pos = _save1 + end + line = @result + unless _tmp + self.pos = _save + break + end + _tmp = match_string("|") + unless _tmp + self.pos = _save + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save + break + end + @result = begin; line ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_TableLine unless _tmp + return _tmp + end + + # TableColumn = "|" < ("-"+ ":"? | ":" "-"*) > { text.start_with?(":") ? :left : text.end_with?(":") ? :right : nil } + def _TableColumn + + _save = self.pos + while true # sequence + _tmp = match_string("|") + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + + _save1 = self.pos + while true # choice + + _save2 = self.pos + while true # sequence + _save3 = self.pos + _tmp = match_string("-") + if _tmp + while true + _tmp = match_string("-") + break unless _tmp + end + _tmp = true + else + self.pos = _save3 + end + unless _tmp + self.pos = _save2 + break + end + _save4 = self.pos + _tmp = match_string(":") + unless _tmp + _tmp = true + self.pos = _save4 + end + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break if _tmp + self.pos = _save1 + + _save5 = self.pos + while true # sequence + _tmp = match_string(":") + unless _tmp + self.pos = _save5 + break + end + while true + _tmp = match_string("-") + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + break if _tmp + self.pos = _save1 + break + end # end choice + + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + @result = begin; text.start_with?(":") ? :left : + text.end_with?(":") ? :right : nil + ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_TableColumn unless _tmp + return _tmp + end + # DefinitionList = &{ definition_lists? } DefinitionListItem+:list { RDoc::Markup::List.new :NOTE, *list.flatten } def _DefinitionList @@ -16046,7 +16438,7 @@ class RDoc::Markdown Rules = {} Rules[:_root] = rule_info("root", "Doc") Rules[:_Doc] = rule_info("Doc", "BOM? Block*:a { RDoc::Markup::Document.new(*a.compact) }") - Rules[:_Block] = rule_info("Block", "@BlankLine* (BlockQuote | Verbatim | CodeFence | Note | Reference | HorizontalRule | Heading | OrderedList | BulletList | DefinitionList | HtmlBlock | StyleBlock | Para | Plain)") + Rules[:_Block] = rule_info("Block", "@BlankLine* (BlockQuote | Verbatim | CodeFence | Table | Note | Reference | HorizontalRule | Heading | OrderedList | BulletList | DefinitionList | HtmlBlock | StyleBlock | Para | Plain)") Rules[:_Para] = rule_info("Para", "@NonindentSpace Inlines:a @BlankLine+ { paragraph a }") Rules[:_Plain] = rule_info("Plain", "Inlines:a { paragraph a }") Rules[:_AtxInline] = rule_info("AtxInline", "!@Newline !(@Sp /\#*/ @Sp @Newline) Inline") @@ -16190,7 +16582,7 @@ class RDoc::Markdown Rules[:_StyleClose] = rule_info("StyleClose", "\"<\" Spnl \"/\" (\"style\" | \"STYLE\") Spnl \">\"") Rules[:_InStyleTags] = rule_info("InStyleTags", "StyleOpen (!StyleClose .)* StyleClose") Rules[:_StyleBlock] = rule_info("StyleBlock", "< InStyleTags > @BlankLine* { if css? then RDoc::Markup::Raw.new text end }") - Rules[:_Inlines] = rule_info("Inlines", "(!@Endline Inline:i { i } | @Endline:c &Inline { c })+:chunks @Endline? { chunks }") + Rules[:_Inlines] = rule_info("Inlines", "(!@Endline Inline:i { i } | @Endline:c !(&{ github? } Ticks3 /[^`\\n]*$/) &Inline { c })+:chunks @Endline? { chunks }") Rules[:_Inline] = rule_info("Inline", "(Str | @Endline | UlOrStarLine | @Space | Strong | Emph | Strike | Image | Link | NoteReference | InlineNote | Code | RawHtml | Entity | EscapedChar | Symbol)") Rules[:_Space] = rule_info("Space", "@Spacechar+ { \" \" }") Rules[:_Str] = rule_info("Str", "@StartList:a < @NormalChar+ > { a = text } (StrChunk:c { a << c })* { a }") @@ -16279,6 +16671,11 @@ class RDoc::Markdown Rules[:_Notes] = rule_info("Notes", "(Note | SkipBlock)*") Rules[:_RawNoteBlock] = rule_info("RawNoteBlock", "@StartList:a (!@BlankLine OptionallyIndentedLine:l { a << l })+ < @BlankLine* > { a << text } { a }") Rules[:_CodeFence] = rule_info("CodeFence", "&{ github? } Ticks3 (@Sp StrChunk:format)? Spnl < ((!\"`\" Nonspacechar)+ | !Ticks3 /`+/ | Spacechar | @Newline)+ > Ticks3 @Sp @Newline* { verbatim = RDoc::Markup::Verbatim.new text verbatim.format = format.intern if format.instance_of?(String) verbatim }") + Rules[:_Table] = rule_info("Table", "&{ github? } TableRow:header TableLine:line TableRow+:body { table = RDoc::Markup::Table.new(header, line, body) }") + Rules[:_TableRow] = rule_info("TableRow", "TableItem+:row \"|\" @Newline { row }") + Rules[:_TableItem] = rule_info("TableItem", "\"|\" < (!\"|\" !@Newline .)+ > { text.strip }") + Rules[:_TableLine] = rule_info("TableLine", "TableColumn+:line \"|\" @Newline { line }") + Rules[:_TableColumn] = rule_info("TableColumn", "\"|\" < (\"-\"+ \":\"? | \":\" \"-\"*) > { text.start_with?(\":\") ? :left : text.end_with?(\":\") ? :right : nil }") Rules[:_DefinitionList] = rule_info("DefinitionList", "&{ definition_lists? } DefinitionListItem+:list { RDoc::Markup::List.new :NOTE, *list.flatten }") Rules[:_DefinitionListItem] = rule_info("DefinitionListItem", "DefinitionListLabel+:label DefinitionListDefinition+:defns { list_items = [] list_items << RDoc::Markup::ListItem.new(label, defns.shift) list_items.concat defns.map { |defn| RDoc::Markup::ListItem.new nil, defn } unless list_items.empty? list_items }") Rules[:_DefinitionListLabel] = rule_info("DefinitionListLabel", "StrChunk:label @Sp @Newline { label }") diff --git a/lib/rdoc/markdown/literals.rb b/lib/rdoc/markdown/literals.rb index 31cd237f12..943c2d268a 100644 --- a/lib/rdoc/markdown/literals.rb +++ b/lib/rdoc/markdown/literals.rb @@ -174,9 +174,8 @@ class RDoc::Markdown::Literals end def scan(reg) - if m = reg.match(@string[@pos..-1]) - width = m.end(0) - @pos += width + if m = reg.match(@string, @pos) + @pos = m.end(0) return true end @@ -366,14 +365,14 @@ class RDoc::Markdown::Literals # Alphanumeric = /\p{Word}/ def _Alphanumeric - _tmp = scan(/\A(?-mix:\p{Word})/) + _tmp = scan(/\G(?-mix:\p{Word})/) set_failed_rule :_Alphanumeric unless _tmp return _tmp end # AlphanumericAscii = /[A-Za-z0-9]/ def _AlphanumericAscii - _tmp = scan(/\A(?-mix:[A-Za-z0-9])/) + _tmp = scan(/\G(?-mix:[A-Za-z0-9])/) set_failed_rule :_AlphanumericAscii unless _tmp return _tmp end @@ -387,21 +386,21 @@ class RDoc::Markdown::Literals # Newline = /\n|\r\n?|\p{Zl}|\p{Zp}/ def _Newline - _tmp = scan(/\A(?-mix:\n|\r\n?|\p{Zl}|\p{Zp})/) + _tmp = scan(/\G(?-mix:\n|\r\n?|\p{Zl}|\p{Zp})/) set_failed_rule :_Newline unless _tmp return _tmp end # NonAlphanumeric = /\p{^Word}/ def _NonAlphanumeric - _tmp = scan(/\A(?-mix:\p{^Word})/) + _tmp = scan(/\G(?-mix:\p{^Word})/) set_failed_rule :_NonAlphanumeric unless _tmp return _tmp end # Spacechar = /\t|\p{Zs}/ def _Spacechar - _tmp = scan(/\A(?-mix:\t|\p{Zs})/) + _tmp = scan(/\G(?-mix:\t|\p{Zs})/) set_failed_rule :_Spacechar unless _tmp return _tmp end diff --git a/lib/rdoc/markup.rb b/lib/rdoc/markup.rb index fd59fca314..92aed757cf 100644 --- a/lib/rdoc/markup.rb +++ b/lib/rdoc/markup.rb @@ -843,6 +843,7 @@ https://github.com/ruby/rdoc/issues autoload :List, 'rdoc/markup/list' autoload :ListItem, 'rdoc/markup/list_item' autoload :Paragraph, 'rdoc/markup/paragraph' + autoload :Table, 'rdoc/markup/table' autoload :Raw, 'rdoc/markup/raw' autoload :Rule, 'rdoc/markup/rule' autoload :Verbatim, 'rdoc/markup/verbatim' diff --git a/lib/rdoc/markup/attr_span.rb b/lib/rdoc/markup/attr_span.rb index 63aace60d2..20ef11cd6d 100644 --- a/lib/rdoc/markup/attr_span.rb +++ b/lib/rdoc/markup/attr_span.rb @@ -7,16 +7,22 @@ class RDoc::Markup::AttrSpan ## # Creates a new AttrSpan for +length+ characters - def initialize(length) + def initialize(length, exclusive) @attrs = Array.new(length, 0) + @exclusive = exclusive end ## # Toggles +bits+ from +start+ to +length+ def set_attrs(start, length, bits) + updated = false for i in start ... (start+length) - @attrs[i] |= bits + if (@exclusive & @attrs[i]) == 0 || (@exclusive & bits) != 0 + @attrs[i] |= bits + updated = true + end end + updated end ## diff --git a/lib/rdoc/markup/attribute_manager.rb b/lib/rdoc/markup/attribute_manager.rb index f052bc8b01..50764510f3 100644 --- a/lib/rdoc/markup/attribute_manager.rb +++ b/lib/rdoc/markup/attribute_manager.rb @@ -59,6 +59,10 @@ class RDoc::Markup::AttributeManager attr_reader :regexp_handlings ## + # A bits of exclusive maps + attr_reader :exclusive_bitmap + + ## # Creates a new attribute manager that understands bold, emphasized and # teletype text. @@ -68,17 +72,18 @@ class RDoc::Markup::AttributeManager @protectable = %w[<] @regexp_handlings = [] @word_pair_map = {} + @exclusive_bitmap = 0 @attributes = RDoc::Markup::Attributes.new - add_word_pair "*", "*", :BOLD - add_word_pair "_", "_", :EM - add_word_pair "+", "+", :TT + add_word_pair "*", "*", :BOLD, true + add_word_pair "_", "_", :EM, true + add_word_pair "+", "+", :TT, true - add_html "em", :EM - add_html "i", :EM - add_html "b", :BOLD - add_html "tt", :TT - add_html "code", :TT + add_html "em", :EM, true + add_html "i", :EM, true + add_html "b", :BOLD, true + add_html "tt", :TT, true + add_html "code", :TT, true end ## @@ -122,29 +127,67 @@ class RDoc::Markup::AttributeManager res end + def exclusive?(attr) + (attr & @exclusive_bitmap) != 0 + end + + NON_PRINTING_START = "\1" # :nodoc: + NON_PRINTING_END = "\2" # :nodoc: + ## # Map attributes like <b>text</b>to the sequence # \001\002<char>\001\003<char>, where <char> is a per-attribute specific # character - def convert_attrs(str, attrs) + def convert_attrs(str, attrs, exclusive = false) + convert_attrs_matching_word_pairs(str, attrs, exclusive) + convert_attrs_word_pair_map(str, attrs, exclusive) + end + + def convert_attrs_matching_word_pairs(str, attrs, exclusive) # first do matching ones - tags = @matching_word_pairs.keys.join("") + tags = @matching_word_pairs.select { |start, bitmap| + if exclusive && exclusive?(bitmap) + true + elsif !exclusive && !exclusive?(bitmap) + true + else + false + end + }.keys + return if tags.empty? + all_tags = @matching_word_pairs.keys - re = /(^|\W)([#{tags}])([#\\]?[\w:.\/-]+?\S?)\2(\W|$)/ + re = /(^|\W|[#{all_tags.join("")}])([#{tags.join("")}])(\2*[#\\]?[\w:.\/\[\]-]+?\S?)\2(?!\2)([#{all_tags.join("")}]|\W|$)/ - 1 while str.gsub!(re) do + 1 while str.gsub!(re) { |orig| attr = @matching_word_pairs[$2] - attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr) - $1 + NULL * $2.length + $3 + NULL * $2.length + $4 - end + attr_updated = attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr) + if attr_updated + $1 + NULL * $2.length + $3 + NULL * $2.length + $4 + else + $1 + NON_PRINTING_START + $2 + NON_PRINTING_END + $3 + NON_PRINTING_START + $2 + NON_PRINTING_END + $4 + end + } + str.delete!(NON_PRINTING_START + NON_PRINTING_END) + end + def convert_attrs_word_pair_map(str, attrs, exclusive) # then non-matching unless @word_pair_map.empty? then @word_pair_map.each do |regexp, attr| - str.gsub!(regexp) { - attrs.set_attrs($`.length + $1.length, $2.length, attr) - NULL * $1.length + $2 + NULL * $3.length + if !exclusive + next if exclusive?(attr) + else + next if !exclusive?(attr) + end + 1 while str.gsub!(regexp) { |orig| + updated = attrs.set_attrs($`.length + $1.length, $2.length, attr) + if updated + NULL * $1.length + $2 + NULL * $3.length + else + orig + end } end end @@ -153,10 +196,18 @@ class RDoc::Markup::AttributeManager ## # Converts HTML tags to RDoc attributes - def convert_html(str, attrs) - tags = @html_tags.keys.join '|' + def convert_html(str, attrs, exclusive = false) + tags = @html_tags.select { |start, bitmap| + if exclusive && exclusive?(bitmap) + true + elsif !exclusive && !exclusive?(bitmap) + true + else + false + end + }.keys.join '|' - 1 while str.gsub!(/<(#{tags})>(.*?)<\/\1>/i) { + 1 while str.gsub!(/<(#{tags})>(.*?)<\/\1>/i) { |orig| attr = @html_tags[$1.downcase] html_length = $1.length + 2 seq = NULL * html_length @@ -168,8 +219,13 @@ class RDoc::Markup::AttributeManager ## # Converts regexp handling sequences to RDoc attributes - def convert_regexp_handlings str, attrs + def convert_regexp_handlings str, attrs, exclusive = false @regexp_handlings.each do |regexp, attribute| + if exclusive + next if !exclusive?(attribute) + else + next if exclusive?(attribute) + end str.scan(regexp) do capture = $~.size == 1 ? 0 : 1 @@ -205,7 +261,7 @@ class RDoc::Markup::AttributeManager # # am.add_word_pair '*', '*', :BOLD - def add_word_pair(start, stop, name) + def add_word_pair(start, stop, name, exclusive = false) raise ArgumentError, "Word flags may not start with '<'" if start[0,1] == '<' @@ -220,6 +276,8 @@ class RDoc::Markup::AttributeManager @protectable << start[0,1] @protectable.uniq! + + @exclusive_bitmap |= bitmap if exclusive end ## @@ -228,8 +286,10 @@ class RDoc::Markup::AttributeManager # # am.add_html 'em', :EM - def add_html(tag, name) - @html_tags[tag.downcase] = @attributes.bitmap_for name + def add_html(tag, name, exclusive = false) + bitmap = @attributes.bitmap_for name + @html_tags[tag.downcase] = bitmap + @exclusive_bitmap |= bitmap if exclusive end ## @@ -238,8 +298,10 @@ class RDoc::Markup::AttributeManager # # @am.add_regexp_handling(/((https?:)\S+\w)/, :HYPERLINK) - def add_regexp_handling pattern, name - @regexp_handlings << [pattern, @attributes.bitmap_for(name)] + def add_regexp_handling pattern, name, exclusive = false + bitmap = @attributes.bitmap_for(name) + @regexp_handlings << [pattern, bitmap] + @exclusive_bitmap |= bitmap if exclusive end ## @@ -250,8 +312,11 @@ class RDoc::Markup::AttributeManager mask_protected_sequences - @attrs = RDoc::Markup::AttrSpan.new @str.length + @attrs = RDoc::Markup::AttrSpan.new @str.length, @exclusive_bitmap + convert_attrs @str, @attrs, true + convert_html @str, @attrs, true + convert_regexp_handlings @str, @attrs, true convert_attrs @str, @attrs convert_html @str, @attrs convert_regexp_handlings @str, @attrs diff --git a/lib/rdoc/markup/table.rb b/lib/rdoc/markup/table.rb new file mode 100644 index 0000000000..7bcb10aff3 --- /dev/null +++ b/lib/rdoc/markup/table.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true +## +# A section of table + +class RDoc::Markup::Table + attr_accessor :header, :align, :body + + def initialize header, align, body + @header, @align, @body = header, align, body + end + + def == other + self.class == other.class and + @header == other.header and + @align == other.align and + @body == other.body + end + + def accept visitor + visitor.accept_table @header, @body, @align + end + + def pretty_print q # :nodoc: + q.group 2, '[Table: ', ']' do + q.group 2, '[Head: ', ']' do + q.seplist @header.zip(@align) do |text, align| + q.pp text + if align + q.text ":" + q.breakable + q.text align.to_s + end + end + end + q.breakable + q.group 2, '[Body: ', ']' do + q.seplist @body do |body| + q.group 2, '[', ']' do + q.seplist body do |text| + q.pp text + end + end + end + end + end + end +end diff --git a/lib/rdoc/markup/to_html.rb b/lib/rdoc/markup/to_html.rb index 3b1b0e9d40..8ae4dd4720 100644 --- a/lib/rdoc/markup/to_html.rb +++ b/lib/rdoc/markup/to_html.rb @@ -314,6 +314,29 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter @res << raw.parts.join("\n") end + ## + # Adds +table+ to the output + + def accept_table header, body, aligns + @res << "\n<table role=\"table\">\n<thead>\n<tr>\n" + header.zip(aligns) do |text, align| + @res << '<th' + @res << ' align="' << align << '"' if align + @res << '>' << CGI.escapeHTML(text) << "</th>\n" + end + @res << "</tr>\n</thead>\n<tbody>\n" + body.each do |row| + @res << "<tr>\n" + row.zip(aligns) do |text, align| + @res << '<td' + @res << ' align="' << align << '"' if align + @res << '>' << CGI.escapeHTML(text) << "</td>\n" + end + @res << "</tr>\n" + end + @res << "</tbody>\n</table>\n" + end + # :section: Utilities ## @@ -334,6 +357,10 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then "<img src=\"#{url}\" />" else + if scheme != 'link' and /\.(?:rb|rdoc|md)\z/i =~ url + url = url.sub(%r%\A([./]*)(.*)\z%) { "#$1#{$2.tr('.', '_')}.html" } + end + text = text.sub %r%^#{scheme}:/*%i, '' text = text.sub %r%^[*\^](\d+)$%, '\1' diff --git a/lib/rdoc/markup/to_joined_paragraph.rb b/lib/rdoc/markup/to_joined_paragraph.rb index 795f3f62ee..46e07c94ad 100644 --- a/lib/rdoc/markup/to_joined_paragraph.rb +++ b/lib/rdoc/markup/to_joined_paragraph.rb @@ -41,6 +41,7 @@ class RDoc::Markup::ToJoinedParagraph < RDoc::Markup::Formatter alias accept_raw ignore alias accept_rule ignore alias accept_verbatim ignore + alias accept_table ignore end diff --git a/lib/rdoc/markup/to_rdoc.rb b/lib/rdoc/markup/to_rdoc.rb index 81b16c4973..3cdf4fd08b 100644 --- a/lib/rdoc/markup/to_rdoc.rb +++ b/lib/rdoc/markup/to_rdoc.rb @@ -238,6 +238,34 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter end ## + # Adds +table+ to the output + + def accept_table header, body, aligns + widths = header.zip(body) do |h, b| + [h.size, b.size].max + end + aligns = aligns.map do |a| + case a + when nil + :center + when :left + :ljust + when :right + :rjust + end + end + @res << header.zip(widths, aligns) do |h, w, a| + h.__send__(a, w) + end.join("|").rstrip << "\n" + @res << widths.map {|w| "-" * w }.join("|") << "\n" + body.each do |row| + @res << row.zip(widths, aligns) do |t, w, a| + t.__send__(a, w) + end.join("|").rstrip << "\n" + end + end + + ## # Applies attribute-specific markup to +text+ using RDoc::AttributeManager def attributes text diff --git a/lib/rdoc/markup/to_table_of_contents.rb b/lib/rdoc/markup/to_table_of_contents.rb index f68b90bcf6..eb8e8faa16 100644 --- a/lib/rdoc/markup/to_table_of_contents.rb +++ b/lib/rdoc/markup/to_table_of_contents.rb @@ -82,6 +82,7 @@ class RDoc::Markup::ToTableOfContents < RDoc::Markup::Formatter alias accept_list_item_end ignore alias accept_list_end_bullet ignore alias accept_list_start ignore + alias accept_table ignore # :startdoc: end diff --git a/lib/rdoc/options.rb b/lib/rdoc/options.rb index 13c1abae0a..13b7ba5c6c 100644 --- a/lib/rdoc/options.rb +++ b/lib/rdoc/options.rb @@ -338,8 +338,9 @@ class RDoc::Options attr_reader :visibility - def initialize # :nodoc: + def initialize loaded_options = nil # :nodoc: init_ivars + override loaded_options if loaded_options end def init_ivars # :nodoc: @@ -417,6 +418,37 @@ class RDoc::Options init_with map end + def override map # :nodoc: + if map.has_key?('encoding') + encoding = map['encoding'] + @encoding = encoding ? Encoding.find(encoding) : encoding + end + + @charset = map['charset'] if map.has_key?('charset') + @exclude = map['exclude'] if map.has_key?('exclude') + @generator_name = map['generator_name'] if map.has_key?('generator_name') + @hyperlink_all = map['hyperlink_all'] if map.has_key?('hyperlink_all') + @line_numbers = map['line_numbers'] if map.has_key?('line_numbers') + @locale_name = map['locale_name'] if map.has_key?('locale_name') + @locale_dir = map['locale_dir'] if map.has_key?('locale_dir') + @main_page = map['main_page'] if map.has_key?('main_page') + @markup = map['markup'] if map.has_key?('markup') + @op_dir = map['op_dir'] if map.has_key?('op_dir') + @show_hash = map['show_hash'] if map.has_key?('show_hash') + @tab_width = map['tab_width'] if map.has_key?('tab_width') + @template_dir = map['template_dir'] if map.has_key?('template_dir') + @title = map['title'] if map.has_key?('title') + @visibility = map['visibility'] if map.has_key?('visibility') + @webcvs = map['webcvs'] if map.has_key?('webcvs') + + if map.has_key?('rdoc_include') + @rdoc_include = sanitize_path map['rdoc_include'] + end + if map.has_key?('static_path') + @static_path = sanitize_path map['static_path'] + end + end + def == other # :nodoc: self.class === other and @encoding == other.encoding and diff --git a/lib/rdoc/parser/changelog.rb b/lib/rdoc/parser/changelog.rb index 167892f543..9245d49376 100644 --- a/lib/rdoc/parser/changelog.rb +++ b/lib/rdoc/parser/changelog.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'time' ## # A ChangeLog file parser. @@ -106,15 +105,33 @@ class RDoc::Parser::ChangeLog < RDoc::Parser entries.group_by do |title, _| begin time = @time_cache[title] - (time || Time.parse(title)).strftime '%Y-%m-%d' + (time || parse_date(title)).strftime '%Y-%m-%d' rescue NoMethodError, ArgumentError time, = title.split ' ', 2 - Time.parse(time).strftime '%Y-%m-%d' + parse_date(time).strftime '%Y-%m-%d' end end end ## + # Parse date in ISO-8601, RFC-2822, or default of Git + + def parse_date(date) + case date + when /\A\s*(\d+)-(\d+)-(\d+)(?:[ T](\d+):(\d+):(\d+) *([-+]\d\d):?(\d\d))?\b/ + Time.new($1, $2, $3, $4, $5, $6, ("#{$7}:#{$8}" if $7)) + when /\A\s*\w{3}, +(\d+) (\w{3}) (\d+) (\d+):(\d+):(\d+) *(?:([-+]\d\d):?(\d\d))\b/ + Time.new($3, $2, $1, $4, $5, $6, ("#{$7}:#{$8}" if $7)) + when /\A\s*\w{3} (\w{3}) +(\d+) (\d+) (\d+):(\d+):(\d+) *(?:([-+]\d\d):?(\d\d))\b/ + Time.new($3, $1, $2, $4, $5, $6, ("#{$7}:#{$8}" if $7)) + when /\A\s*\w{3} (\w{3}) +(\d+) (\d+):(\d+):(\d+) (\d+)\b/ + Time.new($6, $1, $2, $3, $4, $5) + else + raise ArgumentError, "bad date: #{date}" + end + end + + ## # Parses the entries in the ChangeLog. # # Returns an Array of each ChangeLog entry in order of parsing. @@ -131,6 +148,13 @@ class RDoc::Parser::ChangeLog < RDoc::Parser def parse_entries @time_cache ||= {} + + if /\A((?:.*\n){,3})commit\s/ =~ @content + class << self; prepend Git; end + parse_info($1) + return parse_entries + end + entries = [] entry_name = nil entry_body = [] @@ -145,19 +169,10 @@ class RDoc::Parser::ChangeLog < RDoc::Parser entry_name = $& begin - time = Time.parse entry_name + time = parse_date entry_name @time_cache[entry_name] = time - # HACK Ruby 1.8 does not raise ArgumentError for Time.parse "Other" - entry_name = nil unless entry_name =~ /#{time.year}/ - rescue NoMethodError - # HACK Ruby 2.1.2 and earlier raises NoMethodError if time part is absent - entry_name.split ' ', 2 rescue ArgumentError - if /out of range/ =~ $!.message - Time.parse(entry_name.split(' ', 2)[0]) rescue entry_name = nil - else - entry_name = nil - end + entry_name = nil end entry_body = [] @@ -190,6 +205,7 @@ class RDoc::Parser::ChangeLog < RDoc::Parser def scan @time_cache = {} + entries = parse_entries grouped_entries = group_entries entries @@ -200,5 +216,120 @@ class RDoc::Parser::ChangeLog < RDoc::Parser @top_level end + module Git + def parse_info(info) + /^\s*base-url\s*=\s*(.*\S)/ =~ info + @base_url = $1 + end + + def parse_entries + entries = [] + + @content.scan(/^commit\s+(\h{20})\h*\n((?:.+\n)*)\n((?: {4}.*\n+)*)/) do + entry_name, header, entry_body = $1, $2, $3.gsub(/^ {4}/, '') + # header = header.scan(/^ *(\S+?): +(.*)/).to_h + # date = header["CommitDate"] || header["Date"] + date = header[/^ *(?:Author)?Date: +(.*)/, 1] + author = header[/^ *Author: +(.*)/, 1] + begin + time = parse_date(header[/^ *CommitDate: +(.*)/, 1] || date) + @time_cache[entry_name] = time + author.sub!(/\s*<(.*)>/, '') + email = $1 + entries << [entry_name, [author, email, date, entry_body]] + rescue ArgumentError + end + end + + entries + end + + def create_entries entries + # git log entries have no strictly itemized style like the old + # style, just assume Markdown. + entries.map do |commit, entry| + LogEntry.new(@base_url, commit, *entry) + end + end + + LogEntry = Struct.new(:base, :commit, :author, :email, :date, :contents) do + HEADING_LEVEL = 3 + + def initialize(base, commit, author, email, date, contents) + case contents + when String + contents = RDoc::Markdown.parse(contents).parts.each do |body| + case body + when RDoc::Markup::Heading + body.level += HEADING_LEVEL + 1 + end + end + case first = contents[0] + when RDoc::Markup::Paragraph + contents[0] = RDoc::Markup::Heading.new(HEADING_LEVEL + 1, first.text) + end + end + super + end + + def level + HEADING_LEVEL + end + + def aref + "label-#{commit}" + end + + def label context = nil + aref + end + + def text + case base + when nil + "#{date}" + when /%s/ + "{#{date}}[#{base % commit}]" + else + "{#{date}}[#{base}#{commit}]" + end + " {#{author}}[mailto:#{email}]" + end + + def accept visitor + visitor.accept_heading self + begin + if visitor.respond_to?(:code_object=) + code_object = visitor.code_object + visitor.code_object = self + end + contents.each do |body| + body.accept visitor + end + ensure + if visitor.respond_to?(:code_object) + visitor.code_object = code_object + end + end + end + + def pretty_print q # :nodoc: + q.group(2, '[log_entry: ', ']') do + q.text commit + q.text ',' + q.breakable + q.group(2, '[date: ', ']') { q.text date } + q.text ',' + q.breakable + q.group(2, '[author: ', ']') { q.text author } + q.text ',' + q.breakable + q.group(2, '[email: ', ']') { q.text email } + q.text ',' + q.breakable + q.pp contents + end + end + end + end end diff --git a/lib/rdoc/rd/block_parser.rb b/lib/rdoc/rd/block_parser.rb index cf30043593..462ba869a2 100644 --- a/lib/rdoc/rd/block_parser.rb +++ b/lib/rdoc/rd/block_parser.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # # DO NOT MODIFY!!!! -# This file is automatically generated by Racc 1.5.1 +# This file is automatically generated by Racc 1.5.2 # from Racc grammar file "". # diff --git a/lib/rdoc/rd/inline_parser.rb b/lib/rdoc/rd/inline_parser.rb index 007327bf0e..8f4c2c31ef 100644 --- a/lib/rdoc/rd/inline_parser.rb +++ b/lib/rdoc/rd/inline_parser.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # # DO NOT MODIFY!!!! -# This file is automatically generated by Racc 1.5.1 +# This file is automatically generated by Racc 1.5.2 # from Racc grammar file "". # diff --git a/lib/rdoc/rdoc.gemspec b/lib/rdoc/rdoc.gemspec index fd222d47e0..7725e40b22 100644 --- a/lib/rdoc/rdoc.gemspec +++ b/lib/rdoc/rdoc.gemspec @@ -50,7 +50,6 @@ RDoc includes the +rdoc+ and +ri+ tools for generating and displaying documentat "bin/setup", "exe/rdoc", "exe/ri", - "man/ri.1", "lib/rdoc.rb", "lib/rdoc/alias.rb", "lib/rdoc/anon_class.rb", @@ -167,6 +166,7 @@ RDoc includes the +rdoc+ and +ri+ tools for generating and displaying documentat "lib/rdoc/markup/raw.rb", "lib/rdoc/markup/regexp_handling.rb", "lib/rdoc/markup/rule.rb", + "lib/rdoc/markup/table.rb", "lib/rdoc/markup/to_ansi.rb", "lib/rdoc/markup/to_bs.rb", "lib/rdoc/markup/to_html.rb", @@ -222,6 +222,7 @@ RDoc includes the +rdoc+ and +ri+ tools for generating and displaying documentat "lib/rdoc/tom_doc.rb", "lib/rdoc/top_level.rb", "lib/rdoc/version.rb", + "man/ri.1", "rdoc.gemspec", ] # files from .gitignore @@ -243,4 +244,6 @@ RDoc includes the +rdoc+ and +ri+ tools for generating and displaying documentat s.required_ruby_version = Gem::Requirement.new(">= 2.4.0") s.required_rubygems_version = Gem::Requirement.new(">= 2.2") + + s.add_development_dependency("gettext") end diff --git a/lib/rdoc/rdoc.rb b/lib/rdoc/rdoc.rb index 93e764c462..6c69553588 100644 --- a/lib/rdoc/rdoc.rb +++ b/lib/rdoc/rdoc.rb @@ -162,12 +162,20 @@ class RDoc::RDoc RDoc.load_yaml begin - options = YAML.load_file '.rdoc_options' + options = YAML.safe_load_file '.rdoc_options', permitted_classes: [RDoc::Options, Symbol] rescue Psych::SyntaxError + raise RDoc::Error, "#{options_file} is not a valid rdoc options file" end + return RDoc::Options.new unless options # Allow empty file. + raise RDoc::Error, "#{options_file} is not a valid rdoc options file" unless - RDoc::Options === options + RDoc::Options === options or Hash === options + + if Hash === options + # Override the default values with the contents of YAML file. + options = RDoc::Options.new options + end options end @@ -436,7 +444,7 @@ The internal error was: files.reject do |file, *| file =~ /\.(?:class|eps|erb|scpt\.txt|svg|ttf|yml)$/i or (file =~ /tags$/i and - open(file, 'rb') { |io| + File.open(file, 'rb') { |io| io.read(100) =~ /\A(\f\n[^,]+,\d+$|!_TAG_)/ }) end diff --git a/lib/rdoc/store.rb b/lib/rdoc/store.rb index 5ba671ca1b..c793e49ed8 100644 --- a/lib/rdoc/store.rb +++ b/lib/rdoc/store.rb @@ -556,9 +556,7 @@ class RDoc::Store def load_cache #orig_enc = @encoding - File.open cache_path, 'rb' do |io| - @cache = Marshal.load io.read - end + @cache = marshal_load(cache_path) load_enc = @cache[:encoding] @@ -615,9 +613,7 @@ class RDoc::Store def load_class_data klass_name file = class_file klass_name - File.open file, 'rb' do |io| - Marshal.load io.read - end + marshal_load(file) rescue Errno::ENOENT => e error = MissingFileError.new(self, file, klass_name) error.set_backtrace e.backtrace @@ -630,14 +626,10 @@ class RDoc::Store def load_method klass_name, method_name file = method_file klass_name, method_name - File.open file, 'rb' do |io| - obj = Marshal.load io.read - obj.store = self - obj.parent = - find_class_or_module(klass_name) || load_class(klass_name) unless - obj.parent - obj - end + obj = marshal_load(file) + obj.store = self + obj.parent ||= find_class_or_module(klass_name) || load_class(klass_name) + obj rescue Errno::ENOENT => e error = MissingFileError.new(self, file, klass_name + method_name) error.set_backtrace e.backtrace @@ -650,11 +642,9 @@ class RDoc::Store def load_page page_name file = page_file page_name - File.open file, 'rb' do |io| - obj = Marshal.load io.read - obj.store = self - obj - end + obj = marshal_load(file) + obj.store = self + obj rescue Errno::ENOENT => e error = MissingFileError.new(self, file, page_name) error.set_backtrace e.backtrace @@ -976,4 +966,21 @@ class RDoc::Store @unique_modules end + private + def marshal_load(file) + File.open(file, 'rb') {|io| Marshal.load(io, MarshalFilter)} + end + + MarshalFilter = proc do |obj| + case obj + when true, false, nil, Array, Class, Encoding, Hash, Integer, String, Symbol, RDoc::Text + else + unless obj.class.name.start_with?("RDoc::") + raise TypeError, "not permitted class: #{obj.class.name}" + end + end + obj + end + private_constant :MarshalFilter + end diff --git a/lib/rdoc/version.rb b/lib/rdoc/version.rb index bfb442c5c9..8d6ae1012c 100644 --- a/lib/rdoc/version.rb +++ b/lib/rdoc/version.rb @@ -3,6 +3,6 @@ module RDoc ## # RDoc version you are using - VERSION = '6.3.0' + VERSION = '6.3.4.1' end diff --git a/lib/reline.rb b/lib/reline.rb index 81ea9f9b58..a7bd4d9280 100644 --- a/lib/reline.rb +++ b/lib/reline.rb @@ -446,6 +446,10 @@ module Reline } end + def self.ungetc(c) + Reline::IOGate.ungetc(c) + end + def self.line_editor core.line_editor end diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 12a2bde234..7d71e62d63 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -813,6 +813,7 @@ class Reline::LineEditor end move_cursor_up(back) move_cursor_down(@first_line_started_from + @started_from) + @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last) end @@ -1158,8 +1159,25 @@ class Reline::LineEditor def call_completion_proc result = retrieve_completion_block(true) - slice = result[1] - result = @completion_proc.(slice) if @completion_proc and slice + preposing, target, postposing = result + if @completion_proc and target + argnum = @completion_proc.parameters.inject(0) { |result, item| + case item.first + when :req, :opt + result + 1 + when :rest + break 3 + end + } + case argnum + when 1 + result = @completion_proc.(target) + when 2 + result = @completion_proc.(target, preposing) + when 3..Float::INFINITY + result = @completion_proc.(target, preposing, postposing) + end + end Reline.core.instance_variable_set(:@completion_quote_character, nil) result end @@ -1207,8 +1225,16 @@ class Reline::LineEditor end def retrieve_completion_block(set_completion_quote_character = false) - word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/ - quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/ + if Reline.completer_word_break_characters.empty? + word_break_regexp = nil + else + word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/ + end + if Reline.completer_quote_characters.empty? + quote_characters_regexp = nil + else + quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/ + end before = @line.byteslice(0, @byte_pointer) rest = nil break_pointer = nil @@ -1229,14 +1255,14 @@ class Reline::LineEditor elsif quote and slice.start_with?(escaped_quote) # skip i += 2 - elsif slice =~ quote_characters_regexp # find new " + elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new " rest = $' quote = $& closing_quote = /(?!\\)#{Regexp.escape(quote)}/ escaped_quote = /\\#{Regexp.escape(quote)}/ i += 1 break_pointer = i - 1 - elsif not quote and slice =~ word_break_regexp + elsif word_break_regexp and not quote and slice =~ word_break_regexp rest = $' i += 1 before = @line.byteslice(i, @byte_pointer - i) @@ -1264,6 +1290,19 @@ class Reline::LineEditor end target = before end + if @is_multiline + if @previous_line_index + lines = whole_lines(index: @previous_line_index, line: @line) + else + lines = whole_lines + end + if @line_index > 0 + preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing + end + if (lines.size - 1) > @line_index + postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n") + end + end [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)] end @@ -1291,10 +1330,32 @@ class Reline::LineEditor def delete_text(start = nil, length = nil) if start.nil? and length.nil? - @line&.clear - @byte_pointer = 0 - @cursor = 0 - @cursor_max = 0 + if @is_multiline + if @buffer_of_lines.size == 1 + @line&.clear + @byte_pointer = 0 + @cursor = 0 + @cursor_max = 0 + elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0 + @buffer_of_lines.pop + @line_index -= 1 + @line = @buffer_of_lines[@line_index] + @byte_pointer = 0 + @cursor = 0 + @cursor_max = calculate_width(@line) + elsif @line_index < (@buffer_of_lines.size - 1) + @buffer_of_lines.delete_at(@line_index) + @line = @buffer_of_lines[@line_index] + @byte_pointer = 0 + @cursor = 0 + @cursor_max = calculate_width(@line) + end + else + @line&.clear + @byte_pointer = 0 + @cursor = 0 + @cursor_max = 0 + end elsif not start.nil? and not length.nil? if @line before = @line.byteslice(0, start) diff --git a/lib/reline/version.rb b/lib/reline/version.rb index 11e8145c7f..44db465a2f 100644 --- a/lib/reline/version.rb +++ b/lib/reline/version.rb @@ -1,3 +1,3 @@ module Reline - VERSION = '0.2.4' + VERSION = '0.2.5' end diff --git a/lib/resolv.gemspec b/lib/resolv.gemspec index ccfe4c9202..c6a0609b51 100644 --- a/lib/resolv.gemspec +++ b/lib/resolv.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "resolv" - spec.version = "0.2.0" + spec.version = "0.2.1" spec.authors = ["Tanaka Akira"] spec.email = ["akr@fsij.org"] @@ -17,6 +17,6 @@ Gem::Specification.new do |spec| `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.executables = [] spec.require_paths = ["lib"] end diff --git a/lib/resolv.rb b/lib/resolv.rb index 3ca0f01cfe..b69c7045ca 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -696,17 +696,17 @@ class Resolv rescue DecodeError next # broken DNS message ignored end - if s = sender_for(from, msg) + if sender == sender_for(from, msg) break else # unexpected DNS message ignored end end - return msg, s.data + return msg, sender.data end def sender_for(addr, msg) - @senders.delete([addr,msg.id]) + @senders[[addr,msg.id]] end def close diff --git a/lib/rinda/rinda.gemspec b/lib/rinda/rinda.gemspec index 1cc5a453d8..0c13e3c2df 100644 --- a/lib/rinda/rinda.gemspec +++ b/lib/rinda/rinda.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "rinda" - spec.version = "0.1.0" + spec.version = "0.1.1" spec.authors = ["Masatoshi SEKI"] spec.email = ["seki@ruby-lang.org"] diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 41b1353e1f..6a7e56fa3c 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -8,15 +8,15 @@ require 'rbconfig' module Gem - VERSION = "3.2.9".freeze + VERSION = "3.2.33".freeze end # Must be first since it unloads the prelude from 1.9.2 -require 'rubygems/compatibility' +require_relative 'rubygems/compatibility' -require 'rubygems/defaults' -require 'rubygems/deprecate' -require 'rubygems/errors' +require_relative 'rubygems/defaults' +require_relative 'rubygems/deprecate' +require_relative 'rubygems/errors' ## # RubyGems is the Ruby standard for publishing and managing third party @@ -178,7 +178,7 @@ module Gem @configuration = nil @gemdeps = nil @loaded_specs = {} - LOADED_SPECS_MUTEX = Mutex.new + LOADED_SPECS_MUTEX = Thread::Mutex.new @path_to_default_spec_map = {} @platforms = [] @ruby = nil @@ -249,9 +249,6 @@ module Gem # you to specify specific gem versions. def self.bin_path(name, exec_name = nil, *requirements) - # TODO: fails test_self_bin_path_bin_file_gone_in_latest - # Gem::Specification.find_by_name(name, *requirements).bin_file exec_name - requirements = Gem::Requirement.default if requirements.empty? @@ -562,7 +559,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # => [#<Gem::Specification:0x1013b4528 @name="minitest", ...>] def self.install(name, version = Gem::Requirement.default, *options) - require "rubygems/dependency_installer" + require_relative "rubygems/dependency_installer" inst = Gem::DependencyInstaller.new(*options) inst.install name, version inst.installed_gems @@ -626,24 +623,14 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # Try requiring the gem version *or* stdlib version of psych. require 'psych' rescue ::LoadError - # If we can't load psych, thats fine, go on. + # If we can't load psych, that's fine, go on. else - # If 'yaml' has already been required, then we have to - # be sure to switch it over to the newly loaded psych. - if defined?(YAML::ENGINE) && YAML::ENGINE.yamler != "psych" - YAML::ENGINE.yamler = "psych" - end - - require 'rubygems/psych_additions' - require 'rubygems/psych_tree' + require_relative 'rubygems/psych_additions' + require_relative 'rubygems/psych_tree' end require 'yaml' - require 'rubygems/safe_yaml' - - # Now that we're sure some kind of yaml library is loaded, pull - # in our hack to deal with Syck's DefaultKey ugliness. - require 'rubygems/syck_hack' + require_relative 'rubygems/safe_yaml' @yaml_loaded = true end @@ -813,18 +800,18 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} ## # Safely write a file in binary mode on all platforms. def self.write_binary(path, data) - open(path, 'wb') do |io| - begin - io.flock(File::LOCK_EX) - rescue *WRITE_BINARY_ERRORS - end + File.open(path, File::RDWR | File::CREAT | File::BINARY | File::LOCK_EX) do |io| + io.write data + end + rescue *WRITE_BINARY_ERRORS + File.open(path, 'wb') do |io| io.write data end rescue Errno::ENOLCK # NFS if Thread.main != Thread.current raise else - open(path, 'wb') do |io| + File.open(path, 'wb') do |io| io.write data end end @@ -1003,7 +990,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # Lazily loads DefaultUserInteraction and returns the default UI. def self.ui - require 'rubygems/user_interaction' + require_relative 'rubygems/user_interaction' Gem::DefaultUserInteraction.ui end @@ -1063,7 +1050,9 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # Find rubygems plugin files in the standard location and load them def self.load_plugins - load_plugin_files Gem::Util.glob_files_in_dir("*#{Gem.plugin_suffix_pattern}", plugindir) + Gem.path.each do |gem_path| + load_plugin_files Gem::Util.glob_files_in_dir("*#{Gem.plugin_suffix_pattern}", plugindir(gem_path)) + end end ## @@ -1121,27 +1110,22 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} end ENV["BUNDLE_GEMFILE"] ||= File.expand_path(path) - require 'rubygems/user_interaction' - Gem::DefaultUserInteraction.use_ui(ui) do - require "bundler" - begin - Bundler.ui.silence do - @gemdeps = Bundler.setup + require_relative 'rubygems/user_interaction' + require "bundler" + begin + Gem::DefaultUserInteraction.use_ui(ui) do + begin + Bundler.ui.silence do + @gemdeps = Bundler.setup + end + ensure + Gem::DefaultUserInteraction.ui.close end - ensure - Gem::DefaultUserInteraction.ui.close end - @gemdeps.requested_specs.map(&:to_spec).sort_by(&:name) - end - - rescue => e - case e - when Gem::LoadError, Gem::UnsatisfiableDependencyError, (defined?(Bundler::GemNotFound) ? Bundler::GemNotFound : Gem::LoadError) + rescue Bundler::BundlerError => e warn e.message - warn "You may need to `gem install -g` to install missing gems" + warn "You may need to `bundle install` to install missing gems" warn "" - else - raise end end @@ -1338,19 +1322,11 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} autoload :Version, File.expand_path('rubygems/version', __dir__) end -require 'rubygems/exceptions' +require_relative 'rubygems/exceptions' # REFACTOR: This should be pulled out into some kind of hacks file. begin ## - # Defaults the operating system (or packager) wants to provide for RubyGems. - - require 'rubygems/defaults/operating_system' -rescue LoadError -end - -begin - ## # Defaults the Ruby implementation wants to provide for RubyGems require "rubygems/defaults/#{RUBY_ENGINE}" @@ -1361,8 +1337,22 @@ end # Loads the default specs. Gem::Specification.load_defaults -require 'rubygems/core_ext/kernel_gem' -require 'rubygems/core_ext/kernel_require' -require 'rubygems/core_ext/kernel_warn' +require_relative 'rubygems/core_ext/kernel_gem' +require_relative 'rubygems/core_ext/kernel_require' +require_relative 'rubygems/core_ext/kernel_warn' -Gem.use_gemdeps +begin + ## + # Defaults the operating system (or packager) wants to provide for RubyGems. + + require 'rubygems/defaults/operating_system' +rescue LoadError + # Ignored +rescue StandardError => e + msg = "#{e.message}\n" \ + "Loading the rubygems/defaults/operating_system.rb file caused an error. " \ + "This file is owned by your OS, not by rubygems upstream. " \ + "Please find out which OS package this file belongs to and follow the guidelines from your OS to report " \ + "the problem and ask for help." + raise e.class, msg +end diff --git a/lib/rubygems/command.rb b/lib/rubygems/command.rb index bf55ce3205..abdaa8e7c6 100644 --- a/lib/rubygems/command.rb +++ b/lib/rubygems/command.rb @@ -5,7 +5,7 @@ # See LICENSE.txt for permissions. #++ -require 'optparse' +require_relative 'optparse' require_relative 'requirement' require_relative 'user_interaction' @@ -19,7 +19,7 @@ require_relative 'user_interaction' class Gem::Command include Gem::UserInteraction - OptionParser.accept Symbol do |value| + Gem::OptionParser.accept Symbol do |value| value.to_sym end @@ -344,7 +344,7 @@ class Gem::Command ## # Add a command-line option and handler to the command. # - # See OptionParser#make_switch for an explanation of +opts+. + # See Gem::OptionParser#make_switch for an explanation of +opts+. # # +handler+ will be called with two values, the value of the argument and # the options hash. @@ -355,6 +355,8 @@ class Gem::Command def add_option(*opts, &handler) # :yields: value, options group_name = Symbol === opts.first ? opts.shift : :options + raise "Do not pass an empty string in opts" if opts.include?("") + @option_groups[group_name] << [opts, handler] end @@ -538,7 +540,7 @@ class Gem::Command # command. def create_option_parser - @parser = OptionParser.new + @parser = Gem::OptionParser.new add_parser_options @@ -634,6 +636,7 @@ RubyGems is a package manager for Ruby. gem install rake gem list --local gem build package.gemspec + gem push package-0.0.1.gem gem help install Further help: diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb index 97e52544ca..cb07757700 100644 --- a/lib/rubygems/command_manager.rb +++ b/lib/rubygems/command_manager.rb @@ -5,9 +5,9 @@ # See LICENSE.txt for permissions. #++ -require 'rubygems/command' -require 'rubygems/user_interaction' -require 'rubygems/text' +require_relative 'command' +require_relative 'user_interaction' +require_relative 'text' ## # The command manager registers and installs all the individual sub-commands @@ -73,7 +73,9 @@ class Gem::CommandManager ].freeze ALIAS_COMMANDS = { - 'i' => 'install', + 'i' => 'install', + 'login' => 'signin', + 'logout' => 'signout', }.freeze ## diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb index fff5f7c76f..6d1a057dfa 100644 --- a/lib/rubygems/commands/build_command.rb +++ b/lib/rubygems/commands/build_command.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/package' -require 'rubygems/version_option' +require_relative '../command' +require_relative '../package' +require_relative '../version_option' class Gem::Commands::BuildCommand < Gem::Command include Gem::VersionOption @@ -23,7 +23,7 @@ class Gem::Commands::BuildCommand < Gem::Command options[:output] = value end - add_option '-C PATH', '', 'Run as if gem build was started in <PATH> instead of the current working directory.' do |value, options| + add_option '-C PATH', 'Run as if gem build was started in <PATH> instead of the current working directory.' do |value, options| options[:build_path] = value end end diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb index 998df0621b..b59564d575 100644 --- a/lib/rubygems/commands/cert_command.rb +++ b/lib/rubygems/commands/cert_command.rb @@ -1,43 +1,15 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/security' +require_relative '../command' +require_relative '../security' class Gem::Commands::CertCommand < Gem::Command def initialize super 'cert', 'Manage RubyGems certificates and signing settings', :add => [], :remove => [], :list => [], :build => [], :sign => [] - OptionParser.accept OpenSSL::X509::Certificate do |certificate_file| - begin - certificate = OpenSSL::X509::Certificate.new File.read certificate_file - rescue Errno::ENOENT - raise OptionParser::InvalidArgument, "#{certificate_file}: does not exist" - rescue OpenSSL::X509::CertificateError - raise OptionParser::InvalidArgument, - "#{certificate_file}: invalid X509 certificate" - end - [certificate, certificate_file] - end - - OptionParser.accept OpenSSL::PKey::RSA do |key_file| - begin - passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE'] - key = OpenSSL::PKey::RSA.new File.read(key_file), passphrase - rescue Errno::ENOENT - raise OptionParser::InvalidArgument, "#{key_file}: does not exist" - rescue OpenSSL::PKey::RSAError - raise OptionParser::InvalidArgument, "#{key_file}: invalid RSA key" - end - - raise OptionParser::InvalidArgument, - "#{key_file}: private key not found" unless key.private? - - key - end - - add_option('-a', '--add CERT', OpenSSL::X509::Certificate, - 'Add a trusted certificate.') do |(cert, _), options| - options[:add] << cert + add_option('-a', '--add CERT', + 'Add a trusted certificate.') do |cert_file, options| + options[:add] << open_cert(cert_file) end add_option('-l', '--list [FILTER]', @@ -60,21 +32,26 @@ class Gem::Commands::CertCommand < Gem::Command options[:build] << email_address end - add_option('-C', '--certificate CERT', OpenSSL::X509::Certificate, - 'Signing certificate for --sign') do |(cert, cert_file), options| - options[:issuer_cert] = cert + add_option('-C', '--certificate CERT', + 'Signing certificate for --sign') do |cert_file, options| + options[:issuer_cert] = open_cert(cert_file) options[:issuer_cert_file] = cert_file end - add_option('-K', '--private-key KEY', OpenSSL::PKey::RSA, - 'Key for --sign or --build') do |key, options| - options[:key] = key + add_option('-K', '--private-key KEY', + 'Key for --sign or --build') do |key_file, options| + options[:key] = open_private_key(key_file) + end + + add_option('-A', '--key-algorithm ALGORITHM', + 'Select which key algorithm to use for --build') do |algorithm, options| + options[:key_algorithm] = algorithm end add_option('-s', '--sign CERT', 'Signs CERT with the key from -K', 'and the certificate from -C') do |cert_file, options| - raise OptionParser::InvalidArgument, "#{cert_file}: does not exist" unless + raise Gem::OptionParser::InvalidArgument, "#{cert_file}: does not exist" unless File.file? cert_file options[:sign] << cert_file @@ -97,7 +74,39 @@ class Gem::Commands::CertCommand < Gem::Command say "Added '#{certificate.subject}'" end + def check_openssl + return if Gem::HAVE_OPENSSL + + alert_error "OpenSSL library is required for the cert command" + terminate_interaction 1 + end + + def open_cert(certificate_file) + check_openssl + OpenSSL::X509::Certificate.new File.read certificate_file + rescue Errno::ENOENT + raise Gem::OptionParser::InvalidArgument, "#{certificate_file}: does not exist" + rescue OpenSSL::X509::CertificateError + raise Gem::OptionParser::InvalidArgument, + "#{certificate_file}: invalid X509 certificate" + end + + def open_private_key(key_file) + check_openssl + passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE'] + key = OpenSSL::PKey.read File.read(key_file), passphrase + raise Gem::OptionParser::InvalidArgument, + "#{key_file}: private key not found" unless key.private? + key + rescue Errno::ENOENT + raise Gem::OptionParser::InvalidArgument, "#{key_file}: does not exist" + rescue OpenSSL::PKey::PKeyError, ArgumentError + raise Gem::OptionParser::InvalidArgument, "#{key_file}: invalid RSA, DSA, or EC key" + end + def execute + check_openssl + options[:add].each do |certificate| add_certificate certificate end @@ -166,7 +175,8 @@ class Gem::Commands::CertCommand < Gem::Command raise Gem::CommandLineError, "Passphrase and passphrase confirmation don't match" unless passphrase == passphrase_confirmation - key = Gem::Security.create_key + algorithm = options[:key_algorithm] || Gem::Security::DEFAULT_KEY_ALGORITHM + key = Gem::Security.create_key(algorithm) key_path = Gem::Security.write key, "gem-private_key.pem", 0600, passphrase return key, key_path @@ -251,13 +261,14 @@ For further reading on signing gems see `ri Gem::Security`. key_file = File.join Gem.default_key_path key = File.read key_file passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE'] - options[:key] = OpenSSL::PKey::RSA.new key, passphrase + options[:key] = OpenSSL::PKey.read key, passphrase + rescue Errno::ENOENT alert_error \ "--private-key not specified and ~/.gem/gem-private_key.pem does not exist" terminate_interaction 1 - rescue OpenSSL::PKey::RSAError + rescue OpenSSL::PKey::PKeyError alert_error \ "--private-key not specified and ~/.gem/gem-private_key.pem is not valid" @@ -311,4 +322,4 @@ For further reading on signing gems see `ri Gem::Security`. # It's simple, but is all we need email =~ /\A.+@.+\z/ end -end if Gem::HAVE_OPENSSL +end diff --git a/lib/rubygems/commands/check_command.rb b/lib/rubygems/commands/check_command.rb index 8b8eda53cf..3b6b97ae3b 100644 --- a/lib/rubygems/commands/check_command.rb +++ b/lib/rubygems/commands/check_command.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/version_option' -require 'rubygems/validator' -require 'rubygems/doctor' +require_relative '../command' +require_relative '../version_option' +require_relative '../validator' +require_relative '../doctor' class Gem::Commands::CheckCommand < Gem::Command include Gem::VersionOption diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb index 662badce33..c965085880 100644 --- a/lib/rubygems/commands/cleanup_command.rb +++ b/lib/rubygems/commands/cleanup_command.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/dependency_list' -require 'rubygems/uninstaller' +require_relative '../command' +require_relative '../dependency_list' +require_relative '../uninstaller' class Gem::Commands::CleanupCommand < Gem::Command def initialize diff --git a/lib/rubygems/commands/contents_command.rb b/lib/rubygems/commands/contents_command.rb index f17aed64db..716022c458 100644 --- a/lib/rubygems/commands/contents_command.rb +++ b/lib/rubygems/commands/contents_command.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/version_option' +require_relative '../command' +require_relative '../version_option' class Gem::Commands::ContentsCommand < Gem::Command include Gem::VersionOption diff --git a/lib/rubygems/commands/dependency_command.rb b/lib/rubygems/commands/dependency_command.rb index e472d8fa8d..7d217076a5 100644 --- a/lib/rubygems/commands/dependency_command.rb +++ b/lib/rubygems/commands/dependency_command.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/local_remote_options' -require 'rubygems/version_option' +require_relative '../command' +require_relative '../local_remote_options' +require_relative '../version_option' class Gem::Commands::DependencyCommand < Gem::Command include Gem::LocalRemoteOptions diff --git a/lib/rubygems/commands/environment_command.rb b/lib/rubygems/commands/environment_command.rb index 37429fb836..b6eeb620bd 100644 --- a/lib/rubygems/commands/environment_command.rb +++ b/lib/rubygems/commands/environment_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/command' +require_relative '../command' class Gem::Commands::EnvironmentCommand < Gem::Command def initialize diff --git a/lib/rubygems/commands/fetch_command.rb b/lib/rubygems/commands/fetch_command.rb index 6a1b346dd3..373851643d 100644 --- a/lib/rubygems/commands/fetch_command.rb +++ b/lib/rubygems/commands/fetch_command.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/local_remote_options' -require 'rubygems/version_option' +require_relative '../command' +require_relative '../local_remote_options' +require_relative '../version_option' class Gem::Commands::FetchCommand < Gem::Command include Gem::LocalRemoteOptions @@ -60,7 +60,7 @@ then repackaging it. specs_and_sources = filtered unless filtered.empty? end - spec, source = specs_and_sources.max_by {|s,| s.version } + spec, source = specs_and_sources.max_by {|s,| s } if spec.nil? show_lookup_failure gem_name, version, errors, options[:domain] diff --git a/lib/rubygems/commands/generate_index_command.rb b/lib/rubygems/commands/generate_index_command.rb index 93e25ef5e4..87200dab91 100644 --- a/lib/rubygems/commands/generate_index_command.rb +++ b/lib/rubygems/commands/generate_index_command.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/indexer' +require_relative '../command' +require_relative '../indexer' ## # Generates a index files for use as a gem server. diff --git a/lib/rubygems/commands/help_command.rb b/lib/rubygems/commands/help_command.rb index 4e8d7600fb..7f3383c9f3 100644 --- a/lib/rubygems/commands/help_command.rb +++ b/lib/rubygems/commands/help_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/command' +require_relative '../command' class Gem::Commands::HelpCommand < Gem::Command # :stopdoc: diff --git a/lib/rubygems/commands/info_command.rb b/lib/rubygems/commands/info_command.rb index 9ca6ae364f..3f2dd4ae0b 100644 --- a/lib/rubygems/commands/info_command.rb +++ b/lib/rubygems/commands/info_command.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/query_utils' +require_relative '../command' +require_relative '../query_utils' class Gem::Commands::InfoCommand < Gem::Command include Gem::QueryUtils diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb index 70825b88fd..7af5060129 100644 --- a/lib/rubygems/commands/install_command.rb +++ b/lib/rubygems/commands/install_command.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/install_update_options' -require 'rubygems/dependency_installer' -require 'rubygems/local_remote_options' -require 'rubygems/validator' -require 'rubygems/version_option' +require_relative '../command' +require_relative '../install_update_options' +require_relative '../dependency_installer' +require_relative '../local_remote_options' +require_relative '../validator' +require_relative '../version_option' ## # Gem installer command line tool @@ -127,7 +127,7 @@ You can use `i` command instead of `install`. end def usage # :nodoc: - "#{program_name} GEMNAME [GEMNAME ...] [options] -- --build-flags" + "#{program_name} [options] GEMNAME [GEMNAME ...] -- --build-flags" end def check_install_dir # :nodoc: @@ -169,7 +169,7 @@ You can use `i` command instead of `install`. end def install_from_gemdeps # :nodoc: - require 'rubygems/request_set' + require_relative '../request_set' rs = Gem::RequestSet.new specs = rs.install_from_gemdeps options do |req, inst| @@ -244,11 +244,11 @@ You can use `i` command instead of `install`. def load_hooks # :nodoc: if options[:install_as_default] - require 'rubygems/install_default_message' + require_relative '../install_default_message' else - require 'rubygems/install_message' + require_relative '../install_message' end - require 'rubygems/rdoc' + require_relative '../rdoc' end def show_install_errors(errors) # :nodoc: @@ -257,7 +257,8 @@ You can use `i` command instead of `install`. errors.each do |x| return unless Gem::SourceFetchProblem === x - msg = "Unable to pull data from '#{x.source.uri}': #{x.error.message}" + require_relative "../uri" + msg = "Unable to pull data from '#{Gem::Uri.new(x.source.uri).redacted}': #{x.error.message}" alert_warning msg end diff --git a/lib/rubygems/commands/list_command.rb b/lib/rubygems/commands/list_command.rb index 5c99d3d73d..dea11111c9 100644 --- a/lib/rubygems/commands/list_command.rb +++ b/lib/rubygems/commands/list_command.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/query_utils' +require_relative '../command' +require_relative '../query_utils' ## # Searches for gems starting with the supplied argument. diff --git a/lib/rubygems/commands/lock_command.rb b/lib/rubygems/commands/lock_command.rb index f1dc1ac586..cb6229a2cb 100644 --- a/lib/rubygems/commands/lock_command.rb +++ b/lib/rubygems/commands/lock_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/command' +require_relative '../command' class Gem::Commands::LockCommand < Gem::Command def initialize diff --git a/lib/rubygems/commands/mirror_command.rb b/lib/rubygems/commands/mirror_command.rb index 86671a93b2..7daa47e2f0 100644 --- a/lib/rubygems/commands/mirror_command.rb +++ b/lib/rubygems/commands/mirror_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/command' +require_relative '../command' unless defined? Gem::Commands::MirrorCommand class Gem::Commands::MirrorCommand < Gem::Command diff --git a/lib/rubygems/commands/open_command.rb b/lib/rubygems/commands/open_command.rb index 1e40758ec5..1e616fd68f 100644 --- a/lib/rubygems/commands/open_command.rb +++ b/lib/rubygems/commands/open_command.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/version_option' +require_relative '../command' +require_relative '../version_option' class Gem::Commands::OpenCommand < Gem::Command include Gem::VersionOption @@ -36,7 +36,7 @@ class Gem::Commands::OpenCommand < Gem::Command end def usage # :nodoc: - "#{program_name} GEMNAME [-e COMMAND]" + "#{program_name} [-e COMMAND] GEMNAME" end def get_env_editor diff --git a/lib/rubygems/commands/outdated_command.rb b/lib/rubygems/commands/outdated_command.rb index 3579bfc3ba..162d338320 100644 --- a/lib/rubygems/commands/outdated_command.rb +++ b/lib/rubygems/commands/outdated_command.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/local_remote_options' -require 'rubygems/spec_fetcher' -require 'rubygems/version_option' +require_relative '../command' +require_relative '../local_remote_options' +require_relative '../spec_fetcher' +require_relative '../version_option' class Gem::Commands::OutdatedCommand < Gem::Command include Gem::LocalRemoteOptions diff --git a/lib/rubygems/commands/owner_command.rb b/lib/rubygems/commands/owner_command.rb index dd49027469..0a5665228f 100644 --- a/lib/rubygems/commands/owner_command.rb +++ b/lib/rubygems/commands/owner_command.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/local_remote_options' -require 'rubygems/gemcutter_utilities' -require 'rubygems/text' +require_relative '../command' +require_relative '../local_remote_options' +require_relative '../gemcutter_utilities' +require_relative '../text' class Gem::Commands::OwnerCommand < Gem::Command include Gem::Text diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index 143105981e..13979b0a59 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/package' -require 'rubygems/installer' -require 'rubygems/version_option' +require_relative '../command' +require_relative '../package' +require_relative '../installer' +require_relative '../version_option' class Gem::Commands::PristineCommand < Gem::Command include Gem::VersionOption @@ -50,6 +50,11 @@ class Gem::Commands::PristineCommand < Gem::Command options[:env_shebang] = value end + add_option('-i', '--install-dir DIR', + 'Gem repository to get binstubs and plugins installed') do |value, options| + options[:install_dir] = File.expand_path(value) + end + add_option('-n', '--bindir DIR', 'Directory where executables are', 'located') do |value, options| @@ -138,7 +143,7 @@ extensions will be restored. gem = spec.cache_file unless File.exist? gem or options[:only_executables] or options[:only_plugins] - require 'rubygems/remote_fetcher' + require_relative '../remote_fetcher' say "Cached gem for #{spec.full_name} not found, attempting to fetch..." @@ -163,11 +168,12 @@ extensions will be restored. end bin_dir = options[:bin_dir] if options[:bin_dir] + install_dir = options[:install_dir] if options[:install_dir] installer_options = { :wrappers => true, :force => true, - :install_dir => spec.base_dir, + :install_dir => install_dir || spec.base_dir, :env_shebang => env_shebang, :build_args => spec.build_args, :bin_dir => bin_dir, @@ -177,7 +183,7 @@ extensions will be restored. installer = Gem::Installer.for_spec(spec, installer_options) installer.generate_bin elsif options[:only_plugins] - installer = Gem::Installer.for_spec(spec) + installer = Gem::Installer.for_spec(spec, installer_options) installer.generate_plugins else installer = Gem::Installer.at(gem, installer_options) diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb index 1a9a1932f8..1864b4b095 100644 --- a/lib/rubygems/commands/push_command.rb +++ b/lib/rubygems/commands/push_command.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/local_remote_options' -require 'rubygems/gemcutter_utilities' -require 'rubygems/package' +require_relative '../command' +require_relative '../local_remote_options' +require_relative '../gemcutter_utilities' +require_relative '../package' class Gem::Commands::PushCommand < Gem::Command include Gem::LocalRemoteOptions diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb index 789afd6509..5896bec44e 100644 --- a/lib/rubygems/commands/query_command.rb +++ b/lib/rubygems/commands/query_command.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/query_utils' -require 'rubygems/deprecate' +require_relative '../command' +require_relative '../query_utils' +require_relative '../deprecate' class Gem::Commands::QueryCommand < Gem::Command extend Gem::Deprecate diff --git a/lib/rubygems/commands/rdoc_command.rb b/lib/rubygems/commands/rdoc_command.rb index e8c9e84b29..305c80ccfe 100644 --- a/lib/rubygems/commands/rdoc_command.rb +++ b/lib/rubygems/commands/rdoc_command.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/version_option' -require 'rubygems/rdoc' +require_relative '../command' +require_relative '../version_option' +require_relative '../rdoc' require 'fileutils' class Gem::Commands::RdocCommand < Gem::Command diff --git a/lib/rubygems/commands/search_command.rb b/lib/rubygems/commands/search_command.rb index aeb2119235..488d777939 100644 --- a/lib/rubygems/commands/search_command.rb +++ b/lib/rubygems/commands/search_command.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/query_utils' +require_relative '../command' +require_relative '../query_utils' class Gem::Commands::SearchCommand < Gem::Command include Gem::QueryUtils diff --git a/lib/rubygems/commands/server_command.rb b/lib/rubygems/commands/server_command.rb index 594cf77f66..79c682e8f4 100644 --- a/lib/rubygems/commands/server_command.rb +++ b/lib/rubygems/commands/server_command.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/server' -require 'rubygems/deprecate' +require_relative '../command' +require_relative '../server' +require_relative '../deprecate' class Gem::Commands::ServerCommand < Gem::Command extend Gem::Deprecate @@ -11,10 +11,10 @@ class Gem::Commands::ServerCommand < Gem::Command super 'server', 'Documentation and gem repository HTTP server', :port => 8808, :gemdir => [], :daemon => false - OptionParser.accept :Port do |port| + Gem::OptionParser.accept :Port do |port| if port =~ /\A\d+\z/ port = Integer port - raise OptionParser::InvalidArgument, "#{port}: not a port number" if + raise Gem::OptionParser::InvalidArgument, "#{port}: not a port number" if port > 65535 port @@ -22,7 +22,7 @@ class Gem::Commands::ServerCommand < Gem::Command begin Socket.getservbyname port rescue SocketError - raise OptionParser::InvalidArgument, "#{port}: no such named service" + raise Gem::OptionParser::InvalidArgument, "#{port}: no such named service" end end end diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 47e215c149..894f94b484 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/command' +require_relative '../command' ## # Installs RubyGems itself. This command is ordinarily only available from a @@ -149,13 +149,6 @@ By default, this RubyGems will install gem as: def execute @verbose = Gem.configuration.really_verbose - install_destdir = options[:destdir] - - unless install_destdir.empty? - ENV['GEM_HOME'] ||= File.join(install_destdir, - Gem.default_dir.gsub(/^[a-zA-Z]:/, '')) - end - check_ruby_version require 'fileutils' @@ -166,8 +159,8 @@ By default, this RubyGems will install gem as: end extend MakeDirs - lib_dir, bin_dir = make_destination_dirs install_destdir - man_dir = generate_default_man_dir install_destdir + lib_dir, bin_dir = make_destination_dirs + man_dir = generate_default_man_dir install_lib lib_dir @@ -189,8 +182,8 @@ By default, this RubyGems will install gem as: say "RubyGems #{Gem::VERSION} installed" - regenerate_binstubs if options[:regenerate_binstubs] - regenerate_plugins if options[:regenerate_plugins] + regenerate_binstubs(bin_dir) if options[:regenerate_binstubs] + regenerate_plugins(bin_dir) if options[:regenerate_plugins] uninstall_old_gemcutter @@ -258,35 +251,32 @@ By default, this RubyGems will install gem as: say "Installing #{tool} executable" if @verbose Dir.chdir path do - bin_files = Dir['*'] - - bin_files -= %w[update_rubygems] + bin_file = "gem" - bin_files.each do |bin_file| - dest_file = target_bin_path(bin_dir, bin_file) - bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}" + dest_file = target_bin_path(bin_dir, bin_file) + bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}" - begin - bin = File.readlines bin_file - bin[0] = shebang + begin + bin = File.readlines bin_file + bin[0] = shebang - File.open bin_tmp_file, 'w' do |fp| - fp.puts bin.join - end - - install bin_tmp_file, dest_file, :mode => prog_mode - bin_file_names << dest_file - ensure - rm bin_tmp_file + File.open bin_tmp_file, 'w' do |fp| + fp.puts bin.join end - next unless Gem.win_platform? + install bin_tmp_file, dest_file, :mode => prog_mode + bin_file_names << dest_file + ensure + rm bin_tmp_file + end + + next unless Gem.win_platform? - begin - bin_cmd_file = File.join Dir.tmpdir, "#{bin_file}.bat" + begin + bin_cmd_file = File.join Dir.tmpdir, "#{bin_file}.bat" - File.open bin_cmd_file, 'w' do |file| - file.puts <<-TEXT + File.open bin_cmd_file, 'w' do |file| + file.puts <<-TEXT @ECHO OFF IF NOT "%~f0" == "~f0" GOTO :WinNT @"#{File.basename(Gem.ruby).chomp('"')}" "#{dest_file}" %1 %2 %3 %4 %5 %6 %7 %8 %9 @@ -294,12 +284,11 @@ By default, this RubyGems will install gem as: :WinNT @"#{File.basename(Gem.ruby).chomp('"')}" "%~dpn0" %* TEXT - end - - install bin_cmd_file, "#{dest_file}.bat", :mode => prog_mode - ensure - rm bin_cmd_file end + + install bin_cmd_file, "#{dest_file}.bat", :mode => prog_mode + ensure + rm bin_cmd_file end end end @@ -348,7 +337,7 @@ By default, this RubyGems will install gem as: rm_rf dir end - require 'rubygems/rdoc' + require_relative '../rdoc' fake_spec = Gem::Specification.new 'rubygems', Gem::VERSION def fake_spec.full_gem_path @@ -371,8 +360,7 @@ By default, this RubyGems will install gem as: end def install_default_bundler_gem(bin_dir) - specs_dir = Gem.default_specifications_dir - specs_dir = File.join(options[:destdir], specs_dir) unless Gem.win_platform? + specs_dir = File.join(default_dir, "specifications", "default") mkdir_p specs_dir, :mode => 0755 bundler_spec = Dir.chdir("bundler") { Gem::Specification.load("bundler.gemspec") } @@ -387,8 +375,20 @@ By default, this RubyGems will install gem as: bundler_spec = Gem::Specification.load(default_spec_path) + # The base_dir value for a specification is inferred by walking up from the + # folder where the spec was `loaded_from`. In the case of default gems, we + # walk up two levels, because they live at `specifications/default/`, whereas + # in the case of regular gems we walk up just one level because they live at + # `specifications/`. However, in this case, the gem we are installing is + # misdetected as a regular gem, when it's a default gem in reality. This is + # because when there's a `:destdir`, the `loaded_from` path has changed and + # doesn't match `Gem.default_specifications_dir` which is the criteria to + # tag a gem as a default gem. So, in that case, write the correct + # `@base_dir` directly. + bundler_spec.instance_variable_set(:@base_dir, File.dirname(File.dirname(specs_dir))) + # Remove gemspec that was same version of vendored bundler. - normal_gemspec = File.join(Gem.default_dir, "specifications", "bundler-#{bundler_spec.version}.gemspec") + normal_gemspec = File.join(default_dir, "specifications", "bundler-#{bundler_spec.version}.gemspec") if File.file? normal_gemspec File.delete normal_gemspec end @@ -401,19 +401,26 @@ By default, this RubyGems will install gem as: end bundler_bin_dir = bundler_spec.bin_dir - bundler_bin_dir = File.join(options[:destdir], bundler_bin_dir) unless Gem.win_platform? mkdir_p bundler_bin_dir, :mode => 0755 bundler_spec.executables.each do |e| cp File.join("bundler", bundler_spec.bindir, e), File.join(bundler_bin_dir, e) end - require 'rubygems/installer' + require_relative '../installer' Dir.chdir("bundler") do built_gem = Gem::Package.build(bundler_spec) begin - installer = Gem::Installer.at(built_gem, env_shebang: options[:env_shebang], format_executable: options[:format_executable], force: options[:force], install_as_default: true, bin_dir: bin_dir, wrappers: true) - installer.install + Gem::Installer.at( + built_gem, + env_shebang: options[:env_shebang], + format_executable: options[:format_executable], + force: options[:force], + install_as_default: true, + bin_dir: bin_dir, + install_dir: default_dir, + wrappers: true + ).install ensure FileUtils.rm_f built_gem end @@ -424,11 +431,11 @@ By default, this RubyGems will install gem as: say "Bundler #{bundler_spec.version} installed" end - def make_destination_dirs(install_destdir) + def make_destination_dirs lib_dir, bin_dir = Gem.default_rubygems_dirs unless lib_dir - lib_dir, bin_dir = generate_default_dirs(install_destdir) + lib_dir, bin_dir = generate_default_dirs end mkdir_p lib_dir, :mode => 0755 @@ -437,7 +444,7 @@ By default, this RubyGems will install gem as: return lib_dir, bin_dir end - def generate_default_man_dir(install_destdir) + def generate_default_man_dir prefix = options[:prefix] if prefix.empty? @@ -447,14 +454,10 @@ By default, this RubyGems will install gem as: man_dir = File.join prefix, 'man' end - unless install_destdir.empty? - man_dir = File.join install_destdir, man_dir.gsub(/^[a-zA-Z]:/, '') - end - - man_dir + prepend_destdir_if_present(man_dir) end - def generate_default_dirs(install_destdir) + def generate_default_dirs prefix = options[:prefix] site_or_vendor = options[:site_or_vendor] @@ -478,12 +481,7 @@ By default, this RubyGems will install gem as: end end - unless install_destdir.empty? - lib_dir = File.join install_destdir, lib_dir.gsub(/^[a-zA-Z]:/, '') - bin_dir = File.join install_destdir, bin_dir.gsub(/^[a-zA-Z]:/, '') - end - - [lib_dir, bin_dir] + [prepend_destdir_if_present(lib_dir), prepend_destdir_if_present(bin_dir)] end def files_in(dir) @@ -596,7 +594,7 @@ abort "#{deprecation_message}" end def uninstall_old_gemcutter - require 'rubygems/uninstaller' + require_relative '../uninstaller' ui = Gem::Uninstaller.new('gemcutter', :all => true, :ignore => true, :version => '< 0.4') @@ -604,11 +602,12 @@ abort "#{deprecation_message}" rescue Gem::InstallError end - def regenerate_binstubs - require "rubygems/commands/pristine_command" + def regenerate_binstubs(bindir) + require_relative "pristine_command" say "Regenerating binstubs" args = %w[--all --only-executables --silent] + args << "--bindir=#{bindir}" if options[:env_shebang] args << "--env-shebang" end @@ -617,11 +616,13 @@ abort "#{deprecation_message}" command.invoke(*args) end - def regenerate_plugins - require "rubygems/commands/pristine_command" + def regenerate_plugins(bindir) + require_relative "pristine_command" say "Regenerating plugins" args = %w[--all --only-plugins --silent] + args << "--bindir=#{bindir}" + args << "--install-dir=#{default_dir}" command = Gem::Commands::PristineCommand.new command.invoke(*args) @@ -629,6 +630,25 @@ abort "#{deprecation_message}" private + def default_dir + prefix = options[:prefix] + + if prefix.empty? + dir = Gem.default_dir + else + dir = prefix + end + + prepend_destdir_if_present(dir) + end + + def prepend_destdir_if_present(path) + destdir = options[:destdir] + return path if destdir.empty? + + File.join(options[:destdir], path.gsub(/^[a-zA-Z]:/, '')) + end + def install_file_list(files, dest_dir) files.each do |file| install_file file, dest_dir diff --git a/lib/rubygems/commands/signin_command.rb b/lib/rubygems/commands/signin_command.rb index 2e19c8333c..23bb2f937f 100644 --- a/lib/rubygems/commands/signin_command.rb +++ b/lib/rubygems/commands/signin_command.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/gemcutter_utilities' +require_relative '../command' +require_relative '../gemcutter_utilities' class Gem::Commands::SigninCommand < Gem::Command include Gem::GemcutterUtilities diff --git a/lib/rubygems/commands/signout_command.rb b/lib/rubygems/commands/signout_command.rb index ebbe746cb4..c9485e0c1b 100644 --- a/lib/rubygems/commands/signout_command.rb +++ b/lib/rubygems/commands/signout_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/command' +require_relative '../command' class Gem::Commands::SignoutCommand < Gem::Command def initialize diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb index f74fb12e42..9e74f3c47d 100644 --- a/lib/rubygems/commands/sources_command.rb +++ b/lib/rubygems/commands/sources_command.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/remote_fetcher' -require 'rubygems/spec_fetcher' -require 'rubygems/local_remote_options' +require_relative '../command' +require_relative '../remote_fetcher' +require_relative '../spec_fetcher' +require_relative '../local_remote_options' class Gem::Commands::SourcesCommand < Gem::Command include Gem::LocalRemoteOptions diff --git a/lib/rubygems/commands/specification_command.rb b/lib/rubygems/commands/specification_command.rb index 3fddaaaf30..473b6e7b19 100644 --- a/lib/rubygems/commands/specification_command.rb +++ b/lib/rubygems/commands/specification_command.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/local_remote_options' -require 'rubygems/version_option' -require 'rubygems/package' +require_relative '../command' +require_relative '../local_remote_options' +require_relative '../version_option' +require_relative '../package' class Gem::Commands::SpecificationCommand < Gem::Command include Gem::LocalRemoteOptions diff --git a/lib/rubygems/commands/stale_command.rb b/lib/rubygems/commands/stale_command.rb index badc9905c1..62a97966f1 100644 --- a/lib/rubygems/commands/stale_command.rb +++ b/lib/rubygems/commands/stale_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/command' +require_relative '../command' class Gem::Commands::StaleCommand < Gem::Command def initialize diff --git a/lib/rubygems/commands/uninstall_command.rb b/lib/rubygems/commands/uninstall_command.rb index 1540b2f0fb..467c8bf7ed 100644 --- a/lib/rubygems/commands/uninstall_command.rb +++ b/lib/rubygems/commands/uninstall_command.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/version_option' -require 'rubygems/uninstaller' +require_relative '../command' +require_relative '../version_option' +require_relative '../uninstaller' require 'fileutils' ## @@ -81,7 +81,7 @@ class Gem::Commands::UninstallCommand < Gem::Command 'Uninstall gem from the vendor directory.', 'Only for use by gem repackagers.') do |value, options| unless Gem.vendor_dir - raise OptionParser::InvalidOption.new 'your platform is not supported' + raise Gem::OptionParser::InvalidOption.new 'your platform is not supported' end alert_warning 'Use your OS package manager to uninstall vendor gems' diff --git a/lib/rubygems/commands/unpack_command.rb b/lib/rubygems/commands/unpack_command.rb index 8d90d08eb4..3f1708375f 100644 --- a/lib/rubygems/commands/unpack_command.rb +++ b/lib/rubygems/commands/unpack_command.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/version_option' -require 'rubygems/security_option' -require 'rubygems/remote_fetcher' -require 'rubygems/package' +require_relative '../command' +require_relative '../version_option' +require_relative '../security_option' +require_relative '../remote_fetcher' +require_relative '../package' # forward-declare diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb index fcc52c293e..bb356fd610 100644 --- a/lib/rubygems/commands/update_command.rb +++ b/lib/rubygems/commands/update_command.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/command_manager' -require 'rubygems/dependency_installer' -require 'rubygems/install_update_options' -require 'rubygems/local_remote_options' -require 'rubygems/spec_fetcher' -require 'rubygems/version_option' -require 'rubygems/install_message' # must come before rdoc for messaging -require 'rubygems/rdoc' +require_relative '../command' +require_relative '../command_manager' +require_relative '../dependency_installer' +require_relative '../install_update_options' +require_relative '../local_remote_options' +require_relative '../spec_fetcher' +require_relative '../version_option' +require_relative '../install_message' # must come before rdoc for messaging +require_relative '../rdoc' class Gem::Commands::UpdateCommand < Gem::Command include Gem::InstallUpdateOptions @@ -25,7 +25,7 @@ class Gem::Commands::UpdateCommand < Gem::Command add_install_update_options - OptionParser.accept Gem::Version do |value| + Gem::OptionParser.accept Gem::Version do |value| Gem::Version.new value value @@ -76,7 +76,7 @@ command to remove old versions. def check_oldest_rubygems(version) # :nodoc: if oldest_supported_version > version - alert_error "rubygems #{version} is not supported. The oldest supported version is #{oldest_supported_version}" + alert_error "rubygems #{version} is not supported on #{RUBY_VERSION}. The oldest version supported by this ruby is #{oldest_supported_version}" terminate_interaction 1 end end @@ -322,8 +322,26 @@ command to remove old versions. private + # + # Oldest version we support downgrading to. This is the version that + # originally ships with the first patch version of each ruby, because we never + # test each ruby against older rubygems, so we can't really guarantee it + # works. Version list can be checked here: https://stdgems.org/rubygems + # def oldest_supported_version - # for Ruby 2.3 - @oldest_supported_version ||= Gem::Version.new("2.5.2") + @oldest_supported_version ||= + if Gem.ruby_version > Gem::Version.new("3.0.a") + Gem::Version.new("3.2.3") + elsif Gem.ruby_version > Gem::Version.new("2.7.a") + Gem::Version.new("3.1.2") + elsif Gem.ruby_version > Gem::Version.new("2.6.a") + Gem::Version.new("3.0.1") + elsif Gem.ruby_version > Gem::Version.new("2.5.a") + Gem::Version.new("2.7.3") + elsif Gem.ruby_version > Gem::Version.new("2.4.a") + Gem::Version.new("2.6.8") + else + Gem::Version.new("2.5.2") + end end end diff --git a/lib/rubygems/commands/which_command.rb b/lib/rubygems/commands/which_command.rb index d42ab18395..44e87a2b98 100644 --- a/lib/rubygems/commands/which_command.rb +++ b/lib/rubygems/commands/which_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/command' +require_relative '../command' class Gem::Commands::WhichCommand < Gem::Command def initialize diff --git a/lib/rubygems/commands/yank_command.rb b/lib/rubygems/commands/yank_command.rb index 7e8b66b300..cad78aec5f 100644 --- a/lib/rubygems/commands/yank_command.rb +++ b/lib/rubygems/commands/yank_command.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/local_remote_options' -require 'rubygems/version_option' -require 'rubygems/gemcutter_utilities' +require_relative '../command' +require_relative '../local_remote_options' +require_relative '../version_option' +require_relative '../gemcutter_utilities' class Gem::Commands::YankCommand < Gem::Command include Gem::LocalRemoteOptions @@ -24,7 +24,7 @@ data you will need to change them immediately and yank your gem. end def usage # :nodoc: - "#{program_name} GEM -v VERSION [-p PLATFORM] [--key KEY_NAME] [--host HOST]" + "#{program_name} -v VERSION [-p PLATFORM] [--key KEY_NAME] [--host HOST] GEM" end def initialize diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb index 854d09ef3d..60c1d50ba9 100644 --- a/lib/rubygems/config_file.rb +++ b/lib/rubygems/config_file.rb @@ -5,7 +5,7 @@ # See LICENSE.txt for permissions. #++ -require 'rubygems/user_interaction' +require_relative 'user_interaction' require 'rbconfig' ## @@ -45,6 +45,7 @@ class Gem::ConfigFile DEFAULT_UPDATE_SOURCES = true DEFAULT_CONCURRENT_DOWNLOADS = 8 DEFAULT_CERT_EXPIRATION_LENGTH_DAYS = 365 + DEFAULT_IPV4_FALLBACK_ENABLED = false ## # For Ruby packagers to set configuration defaults. Set in @@ -141,6 +142,12 @@ class Gem::ConfigFile attr_accessor :cert_expiration_length_days ## + # == Experimental == + # Fallback to IPv4 when IPv6 is not reachable or slow (default: false) + + attr_accessor :ipv4_fallback_enabled + + ## # Path name of directory or file of openssl client certificate, used for remote https connection with client authentication attr_reader :ssl_client_cert @@ -175,6 +182,7 @@ class Gem::ConfigFile @update_sources = DEFAULT_UPDATE_SOURCES @concurrent_downloads = DEFAULT_CONCURRENT_DOWNLOADS @cert_expiration_length_days = DEFAULT_CERT_EXPIRATION_LENGTH_DAYS + @ipv4_fallback_enabled = ENV['IPV4_FALLBACK_ENABLED'] == 'true' || DEFAULT_IPV4_FALLBACK_ENABLED operating_system_config = Marshal.load Marshal.dump(OPERATING_SYSTEM_DEFAULTS) platform_config = Marshal.load Marshal.dump(PLATFORM_DEFAULTS) @@ -203,6 +211,7 @@ class Gem::ConfigFile @disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server @sources = @hash[:sources] if @hash.key? :sources @cert_expiration_length_days = @hash[:cert_expiration_length_days] if @hash.key? :cert_expiration_length_days + @ipv4_fallback_enabled = @hash[:ipv4_fallback_enabled] if @hash.key? :ipv4_fallback_enabled @ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode @ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert @@ -311,7 +320,8 @@ if you believe they were disclosed to a third party. config = load_file(credentials_path).merge(host => api_key) dirname = File.dirname credentials_path - Dir.mkdir(dirname) unless File.exist? dirname + require 'fileutils' + FileUtils.mkdir_p(dirname) Gem.load_yaml @@ -448,9 +458,8 @@ if you believe they were disclosed to a third party. # Writes out this config file, replacing its source. def write - unless File.exist?(File.dirname(config_file_name)) - FileUtils.mkdir_p File.dirname(config_file_name) - end + require 'fileutils' + FileUtils.mkdir_p File.dirname(config_file_name) File.open config_file_name, 'w' do |io| io.write to_yaml diff --git a/lib/rubygems/core_ext/tcpsocket_init.rb b/lib/rubygems/core_ext/tcpsocket_init.rb new file mode 100644 index 0000000000..2a79b63bd6 --- /dev/null +++ b/lib/rubygems/core_ext/tcpsocket_init.rb @@ -0,0 +1,52 @@ +require 'socket' + +module CoreExtensions + module TCPSocketExt + def self.prepended(base) + base.prepend Initializer + end + + module Initializer + CONNECTION_TIMEOUT = 5 + IPV4_DELAY_SECONDS = 0.1 + + def initialize(host, serv, *rest) + mutex = Thread::Mutex.new + addrs = [] + threads = [] + cond_var = Thread::ConditionVariable.new + + Addrinfo.foreach(host, serv, nil, :STREAM) do |addr| + Thread.report_on_exception = false if defined? Thread.report_on_exception = () + + threads << Thread.new(addr) do + # give head start to ipv6 addresses + sleep IPV4_DELAY_SECONDS if addr.ipv4? + + # raises Errno::ECONNREFUSED when ip:port is unreachable + Socket.tcp(addr.ip_address, serv, connect_timeout: CONNECTION_TIMEOUT).close + mutex.synchronize do + addrs << addr.ip_address + cond_var.signal + end + end + end + + mutex.synchronize do + timeout_time = CONNECTION_TIMEOUT + Time.now.to_f + while addrs.empty? && (remaining_time = timeout_time - Time.now.to_f) > 0 + cond_var.wait(mutex, remaining_time) + end + + host = addrs.shift unless addrs.empty? + end + + threads.each {|t| t.kill.join if t.alive? } + + super(host, serv, *rest) + end + end + end +end + +TCPSocket.prepend CoreExtensions::TCPSocketExt diff --git a/lib/rubygems/defaults.rb b/lib/rubygems/defaults.rb index 8aae67cd6b..923b60f31f 100644 --- a/lib/rubygems/defaults.rb +++ b/lib/rubygems/defaults.rb @@ -73,7 +73,7 @@ module Gem # Path to specification files of default gems. def self.default_specifications_dir - File.join(Gem.default_dir, "specifications", "default") + @default_specifications_dir ||= File.join(Gem.default_dir, "specifications", "default") end ## @@ -198,7 +198,7 @@ module Gem def self.default_bindir if defined? RUBY_FRAMEWORK_VERSION # mac framework support - '/usr/bin' + '/usr/local/bin' else # generic install RbConfig::CONFIG['bindir'] end diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb index 400a5de5cf..913bba32eb 100644 --- a/lib/rubygems/dependency_installer.rb +++ b/lib/rubygems/dependency_installer.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -require 'rubygems' -require 'rubygems/dependency_list' -require 'rubygems/package' -require 'rubygems/installer' -require 'rubygems/spec_fetcher' -require 'rubygems/user_interaction' -require 'rubygems/available_set' -require 'rubygems/deprecate' +require_relative '../rubygems' +require_relative 'dependency_list' +require_relative 'package' +require_relative 'installer' +require_relative 'spec_fetcher' +require_relative 'user_interaction' +require_relative 'available_set' +require_relative 'deprecate' ## # Installs a gem along with all its dependencies from local and remote gems. diff --git a/lib/rubygems/dependency_list.rb b/lib/rubygems/dependency_list.rb index bcf436cd03..10e08fc703 100644 --- a/lib/rubygems/dependency_list.rb +++ b/lib/rubygems/dependency_list.rb @@ -5,8 +5,8 @@ # See LICENSE.txt for permissions. #++ -require 'tsort' -require 'rubygems/deprecate' +require_relative 'tsort' +require_relative 'deprecate' ## # Gem::DependencyList is used for installing and uninstalling gems in the @@ -20,7 +20,7 @@ class Gem::DependencyList attr_reader :specs include Enumerable - include TSort + include Gem::TSort ## # Allows enabling/disabling use of development dependencies diff --git a/lib/rubygems/deprecate.rb b/lib/rubygems/deprecate.rb index 67285d4161..8c822cda95 100644 --- a/lib/rubygems/deprecate.rb +++ b/lib/rubygems/deprecate.rb @@ -60,12 +60,13 @@ module Gem::Deprecate target = klass ? "#{self}." : "#{self.class}#" msg = [ "NOTE: #{target}#{name} is deprecated", repl == :none ? " with no replacement" : "; use #{repl} instead", - ". It will be removed on or after %4d-%02d-01." % [year, month], + ". It will be removed on or after %4d-%02d." % [year, month], "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}", ] warn "#{msg.join}." unless Gem::Deprecate.skip send old, *args, &block end + ruby2_keywords name if respond_to?(:ruby2_keywords, true) end end @@ -90,6 +91,7 @@ module Gem::Deprecate warn "#{msg.join}." unless Gem::Deprecate.skip send old, *args, &block end + ruby2_keywords name if respond_to?(:ruby2_keywords, true) end end diff --git a/lib/rubygems/doctor.rb b/lib/rubygems/doctor.rb index ef31aeddd0..41bcda9804 100644 --- a/lib/rubygems/doctor.rb +++ b/lib/rubygems/doctor.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems' -require 'rubygems/user_interaction' +require_relative '../rubygems' +require_relative 'user_interaction' ## # Cleans up after a partially-failed uninstall or for an invalid diff --git a/lib/rubygems/errors.rb b/lib/rubygems/errors.rb index abee20651e..86f0d1da14 100644 --- a/lib/rubygems/errors.rb +++ b/lib/rubygems/errors.rb @@ -171,8 +171,7 @@ module Gem # An English description of the error. def wordy - @source.uri.password = 'REDACTED' unless @source.uri.password.nil? - "Unable to download data from #{@source.uri} - #{@error.message}" + "Unable to download data from #{Gem::Uri.new(@source.uri).redacted} - #{@error.message}" end ## diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb index 804863f693..5f20dfaed0 100644 --- a/lib/rubygems/exceptions.rb +++ b/lib/rubygems/exceptions.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems/deprecate' +require_relative 'deprecate' ## # Base exception class for RubyGems. All exception raised by RubyGems are a @@ -259,3 +259,4 @@ end # Backwards compatible typo'd exception class for early RubyGems 2.0.x Gem::UnsatisfiableDepedencyError = Gem::UnsatisfiableDependencyError # :nodoc: +Gem.deprecate_constant :UnsatisfiableDepedencyError diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb index e4af450565..c1a1bc9c00 100644 --- a/lib/rubygems/ext/builder.rb +++ b/lib/rubygems/ext/builder.rb @@ -24,13 +24,14 @@ class Gem::Ext::Builder # try to find make program from Ruby configure arguments first RbConfig::CONFIG['configure_args'] =~ /with-make-prog\=(\w+)/ - make_program = ENV['MAKE'] || ENV['make'] || $1 - unless make_program - make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make' + make_program_name = ENV['MAKE'] || ENV['make'] || $1 + unless make_program_name + make_program_name = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make' end - make_program = Shellwords.split(make_program) + make_program = Shellwords.split(make_program_name) - destdir = 'DESTDIR=%s' % ENV['DESTDIR'] + # The installation of the bundled gems is failed when DESTDIR is empty in mswin platform. + destdir = (/\bnmake/i !~ make_program_name || ENV['DESTDIR'] && ENV['DESTDIR'] != "") ? 'DESTDIR=%s' % ENV['DESTDIR'] : '' ['clean', '', 'install'].each do |target| # Pass DESTDIR via command line to override what's in MAKEFLAGS @@ -57,6 +58,7 @@ class Gem::Ext::Builder p(command) end results << "current directory: #{dir}" + require "shellwords" results << command.shelljoin require "open3" diff --git a/lib/rubygems/ext/cmake_builder.rb b/lib/rubygems/ext/cmake_builder.rb index 269e876cfa..e47cabef84 100644 --- a/lib/rubygems/ext/cmake_builder.rb +++ b/lib/rubygems/ext/cmake_builder.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require_relative '../command' class Gem::Ext::CmakeBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args=[], lib_dir=nil, cmake_dir=Dir.pwd) unless File.exist?(File.join(cmake_dir, 'Makefile')) + require_relative '../command' cmd = ["cmake", ".", "-DCMAKE_INSTALL_PREFIX=#{dest_path}", *Gem::Command.build_args] run cmd, results, class_name, cmake_dir diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb index fede270417..3ca3463615 100644 --- a/lib/rubygems/ext/ext_conf_builder.rb +++ b/lib/rubygems/ext/ext_conf_builder.rb @@ -5,8 +5,6 @@ # See LICENSE.txt for permissions. #++ -require 'shellwords' - class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd) require 'fileutils' @@ -23,11 +21,11 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder # spaces do not work. # # Details: https://github.com/rubygems/rubygems/issues/977#issuecomment-171544940 - tmp_dest = get_relative_path(tmp_dest, extension_dir) + tmp_dest_relative = get_relative_path(tmp_dest.clone, extension_dir) Tempfile.open %w[siteconf .rb], extension_dir do |siteconf| siteconf.puts "require 'rbconfig'" - siteconf.puts "dest_path = #{tmp_dest.dump}" + siteconf.puts "dest_path = #{tmp_dest_relative.dump}" %w[sitearchdir sitelibdir].each do |dir| siteconf.puts "RbConfig::MAKEFILE_CONFIG['#{dir}'] = dest_path" siteconf.puts "RbConfig::CONFIG['#{dir}'] = dest_path" @@ -40,6 +38,7 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder begin # workaround for https://github.com/oracle/truffleruby/issues/2115 siteconf_path = RUBY_ENGINE == "truffleruby" ? siteconf.path.dup : siteconf.path + require "shellwords" cmd = Gem.ruby.shellsplit << "-I" << File.expand_path("../../..", __FILE__) << "-r" << get_relative_path(siteconf_path, extension_dir) << File.basename(extension) cmd.push(*args) @@ -63,8 +62,8 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder make dest_path, results, extension_dir - if tmp_dest - full_tmp_dest = File.join(extension_dir, tmp_dest) + if tmp_dest_relative + full_tmp_dest = File.join(extension_dir, tmp_dest_relative) # TODO remove in RubyGems 3 if Gem.install_extension_in_lib and lib_dir diff --git a/lib/rubygems/ext/rake_builder.rb b/lib/rubygems/ext/rake_builder.rb index 64a6c0eb80..fed98e741c 100644 --- a/lib/rubygems/ext/rake_builder.rb +++ b/lib/rubygems/ext/rake_builder.rb @@ -5,8 +5,6 @@ # See LICENSE.txt for permissions. #++ -require "shellwords" - class Gem::Ext::RakeBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd) if File.basename(extension) =~ /mkrf_conf/i @@ -16,6 +14,7 @@ class Gem::Ext::RakeBuilder < Gem::Ext::Builder rake = ENV['rake'] if rake + require "shellwords" rake = rake.shellsplit else begin diff --git a/lib/rubygems/gem_runner.rb b/lib/rubygems/gem_runner.rb index a36674503e..55b5a7d067 100644 --- a/lib/rubygems/gem_runner.rb +++ b/lib/rubygems/gem_runner.rb @@ -5,9 +5,9 @@ # See LICENSE.txt for permissions. #++ -require 'rubygems' -require 'rubygems/command_manager' -require 'rubygems/deprecate' +require_relative '../rubygems' +require_relative 'command_manager' +require_relative 'deprecate' ## # Load additional plugins from $LOAD_PATH diff --git a/lib/rubygems/gemcutter_utilities.rb b/lib/rubygems/gemcutter_utilities.rb index 3687e776e2..0968e1a6f9 100644 --- a/lib/rubygems/gemcutter_utilities.rb +++ b/lib/rubygems/gemcutter_utilities.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems/remote_fetcher' -require 'rubygems/text' +require_relative 'remote_fetcher' +require_relative 'text' ## # Utility methods for using the RubyGems API. @@ -31,7 +31,8 @@ module Gem::GemcutterUtilities def add_otp_option add_option('--otp CODE', - 'Digit code for multifactor authentication') do |value, options| + 'Digit code for multifactor authentication', + 'You can also use the environment variable GEM_HOST_OTP_CODE') do |value, options| options[:otp] = value end end @@ -52,6 +53,13 @@ module Gem::GemcutterUtilities end ## + # The OTP code from the command options or from the user's configuration. + + def otp + options[:otp] || ENV["GEM_HOST_OTP_CODE"] + end + + ## # The host to connect to either from the RUBYGEMS_HOST environment variable # or from the user's configuration @@ -126,7 +134,7 @@ module Gem::GemcutterUtilities response = rubygems_api_request(:put, "api/v1/api_key", sign_in_host, scope: scope) do |request| request.basic_auth email, password - request["OTP"] = options[:otp] if options[:otp] + request["OTP"] = otp if otp request.body = URI.encode_www_form({:api_key => api_key }.merge(update_scope_params)) end @@ -159,7 +167,7 @@ module Gem::GemcutterUtilities response = rubygems_api_request(:post, "api/v1/api_key", sign_in_host, scope: scope) do |request| request.basic_auth email, password - request["OTP"] = options[:otp] if options[:otp] + request["OTP"] = otp if otp request.body = URI.encode_www_form({ name: key_name }.merge(scope_params)) end @@ -224,7 +232,7 @@ module Gem::GemcutterUtilities request_method = Net::HTTP.const_get method.to_s.capitalize Gem::RemoteFetcher.fetcher.request(uri, request_method) do |req| - req["OTP"] = options[:otp] if options[:otp] + req["OTP"] = otp if otp block.call(req) end end diff --git a/lib/rubygems/indexer.rb b/lib/rubygems/indexer.rb index 31285ca962..6e8dade640 100644 --- a/lib/rubygems/indexer.rb +++ b/lib/rubygems/indexer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems' -require 'rubygems/package' +require_relative '../rubygems' +require_relative 'package' require 'tmpdir' ## @@ -136,7 +136,7 @@ class Gem::Indexer say "Generating #{name} index" Gem.time "Generated #{name} index" do - open(file, 'wb') do |io| + File.open(file, 'wb') do |io| specs = index.map do |*spec| # We have to splat here because latest_specs is an array, while the # others are hashes. diff --git a/lib/rubygems/install_default_message.rb b/lib/rubygems/install_default_message.rb index f68fd2fd04..052ef528e1 100644 --- a/lib/rubygems/install_default_message.rb +++ b/lib/rubygems/install_default_message.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems' -require 'rubygems/user_interaction' +require_relative '../rubygems' +require_relative 'user_interaction' ## # A post-install hook that displays "Successfully installed diff --git a/lib/rubygems/install_message.rb b/lib/rubygems/install_message.rb index 3c13888a84..861ead3770 100644 --- a/lib/rubygems/install_message.rb +++ b/lib/rubygems/install_message.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems' -require 'rubygems/user_interaction' +require_relative '../rubygems' +require_relative 'user_interaction' ## # A default post-install hook that displays "Successfully installed diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb index ef1ad1edcb..d890c526bc 100644 --- a/lib/rubygems/install_update_options.rb +++ b/lib/rubygems/install_update_options.rb @@ -5,8 +5,8 @@ # See LICENSE.txt for permissions. #++ -require 'rubygems' -require 'rubygems/security_option' +require_relative '../rubygems' +require_relative 'security_option' ## # Mixin methods for install and update options for Gem::Commands @@ -51,7 +51,7 @@ module Gem::InstallUpdateOptions 'Install gem into the vendor directory.', 'Only for use by gem repackagers.') do |value, options| unless Gem.vendor_dir - raise OptionParser::InvalidOption.new 'your platform is not supported' + raise Gem::OptionParser::InvalidOption.new 'your platform is not supported' end options[:vendor] = true @@ -143,7 +143,7 @@ module Gem::InstallUpdateOptions unless v message = v ? v : "(tried #{Gem::GEM_DEP_FILES.join ', '})" - raise OptionParser::InvalidArgument, + raise Gem::OptionParser::InvalidArgument, "cannot find gem dependencies file #{message}" end diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 3ec6d743a2..8e3965ef92 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -5,13 +5,12 @@ # See LICENSE.txt for permissions. #++ -require 'rubygems/command' -require 'rubygems/installer_uninstaller_utils' -require 'rubygems/exceptions' -require 'rubygems/deprecate' -require 'rubygems/package' -require 'rubygems/ext' -require 'rubygems/user_interaction' +require_relative 'installer_uninstaller_utils' +require_relative 'exceptions' +require_relative 'deprecate' +require_relative 'package' +require_relative 'ext' +require_relative 'user_interaction' ## # The installer installs the files contained in the .gem into the Gem.home. @@ -68,19 +67,28 @@ class Gem::Installer @path_warning = false - @install_lock = Mutex.new - class << self - ## - # True if we've warned about PATH not including Gem.bindir + # + # Changes in rubygems to lazily loading `rubygems/command` (in order to + # lazily load `optparse` as a side effect) affect bundler's custom installer + # which uses `Gem::Command` without requiring it (up until bundler 2.2.29). + # This hook is to compensate for that missing require. + # + # TODO: Remove when rubygems no longer supports running on bundler older + # than 2.2.29. - attr_accessor :path_warning + def inherited(klass) + if klass.name == "Bundler::RubyGemsGemInstaller" + require "rubygems/command" + end + + super(klass) + end ## - # Certain aspects of the install process are not thread-safe. This lock is - # used to allow multiple threads to install Gems at the same time. + # True if we've warned about PATH not including Gem.bindir - attr_reader :install_lock + attr_accessor :path_warning ## # Overrides the executable format. @@ -285,8 +293,6 @@ class Gem::Installer def install pre_install_checks - FileUtils.rm_f File.join gem_home, 'specifications', spec.spec_name - run_pre_install_hooks # Set loaded_from to ensure extension_dir is correct @@ -326,7 +332,7 @@ class Gem::Installer say spec.post_install_message if options[:post_install_message] && !spec.post_install_message.nil? - Gem::Installer.install_lock.synchronize { Gem::Specification.reset } + Gem::Specification.reset run_post_install_hooks @@ -440,13 +446,9 @@ class Gem::Installer # specifications directory. def write_spec - File.open spec_file, 'w' do |file| - spec.installed_by_version = Gem.rubygems_version - - file.puts spec.to_ruby_for_cache + spec.installed_by_version = Gem.rubygems_version - file.fsync rescue nil # for filesystems without fsync(2) - end + Gem.write_binary(spec_file, spec.to_ruby_for_cache) end ## @@ -454,9 +456,7 @@ class Gem::Installer # specifications/default directory. def write_default_spec - File.open(default_spec_file, "w") do |file| - file.puts spec.to_ruby - end + Gem.write_binary(default_spec_file, spec.to_ruby) end ## @@ -484,8 +484,11 @@ class Gem::Installer bin_path = File.join gem_dir, spec.bindir, filename unless File.exist? bin_path - # TODO change this to a more useful warning - warn "`#{bin_path}` does not exist, maybe `gem pristine #{spec.name}` will fix it?" + if File.symlink? bin_path + alert_warning "`#{bin_path}` is dangling symlink pointing to `#{File.readlink bin_path}`" + else + alert_warning "`#{bin_path}` does not exist, maybe `gem pristine #{spec.name}` will fix it?" + end next end @@ -508,7 +511,7 @@ class Gem::Installer end def generate_plugins # :nodoc: - latest = Gem::Installer.install_lock.synchronize { Gem::Specification.latest_spec_for(spec.name) } + latest = Gem::Specification.latest_spec_for(spec.name) return if latest && latest.version > spec.version ensure_writable_dir @plugins_dir @@ -673,7 +676,7 @@ class Gem::Installer @development = options[:development] @build_root = options[:build_root] - @build_args = options[:build_args] || Gem::Command.build_args + @build_args = options[:build_args] unless @build_root.nil? @bin_dir = File.join(@build_root, @bin_dir.gsub(/^[a-zA-Z]:/, '')) @@ -725,6 +728,10 @@ class Gem::Installer raise Gem::InstallError, "#{spec} has an invalid extensions" end + if spec.platform.to_s =~ /\R/ + raise Gem::InstallError, "#{spec.platform} is an invalid platform" + end + unless spec.specification_version.to_s =~ /\A\d+\z/ raise Gem::InstallError, "#{spec} has an invalid specification_version" end @@ -754,7 +761,7 @@ class Gem::Installer # require 'rubygems' - +#{gemdeps_load(spec.name)} version = "#{Gem::Requirement.default_prerelease}" str = ARGV.first @@ -775,6 +782,15 @@ end TEXT end + def gemdeps_load(name) + return '' if name == "bundler" + + <<-TEXT + +Gem.use_gemdeps +TEXT + end + ## # return the stub script text used to launch the true Ruby script @@ -816,7 +832,7 @@ TEXT # configure scripts and rakefiles or mkrf_conf files. def build_extensions - builder = Gem::Ext::Builder.new spec, @build_args + builder = Gem::Ext::Builder.new spec, build_args builder.build_extensions end @@ -903,7 +919,7 @@ TEXT # extensions. def write_build_info_file - return if @build_args.empty? + return if build_args.empty? build_info_dir = File.join gem_home, 'build_info' @@ -913,7 +929,7 @@ TEXT build_info_file = File.join build_info_dir, "#{spec.full_name}.info" File.open build_info_file, 'w' do |io| - @build_args.each do |arg| + build_args.each do |arg| io.puts arg end end @@ -938,4 +954,13 @@ TEXT raise Gem::FilePermissionError.new(dir) unless File.writable? dir end + + private + + def build_args + @build_args ||= begin + require_relative "command" + Gem::Command.build_args + end + end end diff --git a/lib/rubygems/local_remote_options.rb b/lib/rubygems/local_remote_options.rb index 2d0509eb03..0b8b0ee1a6 100644 --- a/lib/rubygems/local_remote_options.rb +++ b/lib/rubygems/local_remote_options.rb @@ -6,7 +6,7 @@ #++ require 'uri' -require 'rubygems' +require_relative '../rubygems' ## # Mixin methods for local and remote Gem::Command options. @@ -14,14 +14,14 @@ require 'rubygems' module Gem::LocalRemoteOptions ## - # Allows OptionParser to handle HTTP URIs. + # Allows Gem::OptionParser to handle HTTP URIs. def accept_uri_http - OptionParser.accept URI::HTTP do |value| + Gem::OptionParser.accept URI::HTTP do |value| begin uri = URI.parse value rescue URI::InvalidURIError - raise OptionParser::InvalidArgument, value + raise Gem::OptionParser::InvalidArgument, value end valid_uri_schemes = ["http", "https", "file", "s3"] diff --git a/lib/rubygems/mock_gem_ui.rb b/lib/rubygems/mock_gem_ui.rb index ec244fb7c6..914ecb9a71 100644 --- a/lib/rubygems/mock_gem_ui.rb +++ b/lib/rubygems/mock_gem_ui.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/user_interaction' +require_relative 'user_interaction' ## # This Gem::StreamUI subclass records input and output to StringIO for diff --git a/lib/rubygems/optparse.rb b/lib/rubygems/optparse.rb new file mode 100644 index 0000000000..65be9f6b74 --- /dev/null +++ b/lib/rubygems/optparse.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require_relative 'optparse/lib/optparse' diff --git a/lib/rubygems/optparse/.document b/lib/rubygems/optparse/.document new file mode 100644 index 0000000000..0c43bbd6b3 --- /dev/null +++ b/lib/rubygems/optparse/.document @@ -0,0 +1 @@ +# Vendored files do not need to be documented diff --git a/lib/rubygems/optparse/COPYING b/lib/rubygems/optparse/COPYING new file mode 100644 index 0000000000..48e5a96de7 --- /dev/null +++ b/lib/rubygems/optparse/COPYING @@ -0,0 +1,56 @@ +Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>. +You can redistribute it and/or modify it under either the terms of the +2-clause BSDL (see the file BSDL), or the conditions below: + +1. You may make and give away verbatim copies of the source form of the + software without restriction, provided that you duplicate all of the + original copyright notices and associated disclaimers. + +2. You may modify your copy of the software in any way, provided that + you do at least ONE of the following: + + a. place your modifications in the Public Domain or otherwise + make them Freely Available, such as by posting said + modifications to Usenet or an equivalent medium, or by allowing + the author to include your modifications in the software. + + b. use the modified software only within your corporation or + organization. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +3. You may distribute the software in object code or binary form, + provided that you do at least ONE of the following: + + a. distribute the binaries and library files of the software, + together with instructions (in the manual page or equivalent) + on where to get the original distribution. + + b. accompany the distribution with the machine-readable source of + the software. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +4. You may modify and include the part of the software into any other + software (possibly commercial). But some files in the distribution + are not written by the author, so that they are not under these terms. + + For the list of those files and their copying conditions, see the + file LEGAL. + +5. The scripts and library files supplied as input to or produced as + output from the software do not automatically fall under the + copyright of the software, but belong to whomever generated them, + and may be sold commercially, and may be aggregated with this + software. + +6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. diff --git a/lib/rubygems/optparse/lib/optionparser.rb b/lib/rubygems/optparse/lib/optionparser.rb new file mode 100644 index 0000000000..4b9b40d82a --- /dev/null +++ b/lib/rubygems/optparse/lib/optionparser.rb @@ -0,0 +1,2 @@ +# frozen_string_literal: false +require_relative 'optparse' diff --git a/lib/rubygems/optparse/lib/optparse.rb b/lib/rubygems/optparse/lib/optparse.rb new file mode 100644 index 0000000000..e4b1c61f79 --- /dev/null +++ b/lib/rubygems/optparse/lib/optparse.rb @@ -0,0 +1,2230 @@ +# frozen_string_literal: true +# +# optparse.rb - command-line option analysis with the Gem::OptionParser class. +# +# Author:: Nobu Nakada +# Documentation:: Nobu Nakada and Gavin Sinclair. +# +# See Gem::OptionParser for documentation. +# + + +#-- +# == Developer Documentation (not for RDoc output) +# +# === Class tree +# +# - Gem::OptionParser:: front end +# - Gem::OptionParser::Switch:: each switches +# - Gem::OptionParser::List:: options list +# - Gem::OptionParser::ParseError:: errors on parsing +# - Gem::OptionParser::AmbiguousOption +# - Gem::OptionParser::NeedlessArgument +# - Gem::OptionParser::MissingArgument +# - Gem::OptionParser::InvalidOption +# - Gem::OptionParser::InvalidArgument +# - Gem::OptionParser::AmbiguousArgument +# +# === Object relationship diagram +# +# +--------------+ +# | Gem::OptionParser |<>-----+ +# +--------------+ | +--------+ +# | ,-| Switch | +# on_head -------->+---------------+ / +--------+ +# accept/reject -->| List |<|>- +# | |<|>- +----------+ +# on ------------->+---------------+ `-| argument | +# : : | class | +# +---------------+ |==========| +# on_tail -------->| | |pattern | +# +---------------+ |----------| +# Gem::OptionParser.accept ->| DefaultList | |converter | +# reject |(shared between| +----------+ +# | all instances)| +# +---------------+ +# +#++ +# +# == Gem::OptionParser +# +# === New to \Gem::OptionParser? +# +# See the {Tutorial}[./doc/optparse/tutorial_rdoc.html]. +# +# === Introduction +# +# Gem::OptionParser is a class for command-line option analysis. It is much more +# advanced, yet also easier to use, than GetoptLong, and is a more Ruby-oriented +# solution. +# +# === Features +# +# 1. The argument specification and the code to handle it are written in the +# same place. +# 2. It can output an option summary; you don't need to maintain this string +# separately. +# 3. Optional and mandatory arguments are specified very gracefully. +# 4. Arguments can be automatically converted to a specified class. +# 5. Arguments can be restricted to a certain set. +# +# All of these features are demonstrated in the examples below. See +# #make_switch for full documentation. +# +# === Minimal example +# +# require 'rubygems/optparse/lib/optparse' +# +# options = {} +# Gem::OptionParser.new do |parser| +# parser.banner = "Usage: example.rb [options]" +# +# parser.on("-v", "--[no-]verbose", "Run verbosely") do |v| +# options[:verbose] = v +# end +# end.parse! +# +# p options +# p ARGV +# +# === Generating Help +# +# Gem::OptionParser can be used to automatically generate help for the commands you +# write: +# +# require 'rubygems/optparse/lib/optparse' +# +# Options = Struct.new(:name) +# +# class Parser +# def self.parse(options) +# args = Options.new("world") +# +# opt_parser = Gem::OptionParser.new do |parser| +# parser.banner = "Usage: example.rb [options]" +# +# parser.on("-nNAME", "--name=NAME", "Name to say hello to") do |n| +# args.name = n +# end +# +# parser.on("-h", "--help", "Prints this help") do +# puts parser +# exit +# end +# end +# +# opt_parser.parse!(options) +# return args +# end +# end +# options = Parser.parse %w[--help] +# +# #=> +# # Usage: example.rb [options] +# # -n, --name=NAME Name to say hello to +# # -h, --help Prints this help +# +# === Required Arguments +# +# For options that require an argument, option specification strings may include an +# option name in all caps. If an option is used without the required argument, +# an exception will be raised. +# +# require 'rubygems/optparse/lib/optparse' +# +# options = {} +# Gem::OptionParser.new do |parser| +# parser.on("-r", "--require LIBRARY", +# "Require the LIBRARY before executing your script") do |lib| +# puts "You required #{lib}!" +# end +# end.parse! +# +# Used: +# +# $ ruby optparse-test.rb -r +# optparse-test.rb:9:in `<main>': missing argument: -r (Gem::OptionParser::MissingArgument) +# $ ruby optparse-test.rb -r my-library +# You required my-library! +# +# === Type Coercion +# +# Gem::OptionParser supports the ability to coerce command line arguments +# into objects for us. +# +# Gem::OptionParser comes with a few ready-to-use kinds of type +# coercion. They are: +# +# - Date -- Anything accepted by +Date.parse+ +# - DateTime -- Anything accepted by +DateTime.parse+ +# - Time -- Anything accepted by +Time.httpdate+ or +Time.parse+ +# - URI -- Anything accepted by +URI.parse+ +# - Shellwords -- Anything accepted by +Shellwords.shellwords+ +# - String -- Any non-empty string +# - Integer -- Any integer. Will convert octal. (e.g. 124, -3, 040) +# - Float -- Any float. (e.g. 10, 3.14, -100E+13) +# - Numeric -- Any integer, float, or rational (1, 3.4, 1/3) +# - DecimalInteger -- Like +Integer+, but no octal format. +# - OctalInteger -- Like +Integer+, but no decimal format. +# - DecimalNumeric -- Decimal integer or float. +# - TrueClass -- Accepts '+, yes, true, -, no, false' and +# defaults as +true+ +# - FalseClass -- Same as +TrueClass+, but defaults to +false+ +# - Array -- Strings separated by ',' (e.g. 1,2,3) +# - Regexp -- Regular expressions. Also includes options. +# +# We can also add our own coercions, which we will cover below. +# +# ==== Using Built-in Conversions +# +# As an example, the built-in +Time+ conversion is used. The other built-in +# conversions behave in the same way. +# Gem::OptionParser will attempt to parse the argument +# as a +Time+. If it succeeds, that time will be passed to the +# handler block. Otherwise, an exception will be raised. +# +# require 'rubygems/optparse/lib/optparse' +# require 'rubygems/optparse/lib/optparse/time' +# Gem::OptionParser.new do |parser| +# parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time| +# p time +# end +# end.parse! +# +# Used: +# +# $ ruby optparse-test.rb -t nonsense +# ... invalid argument: -t nonsense (Gem::OptionParser::InvalidArgument) +# $ ruby optparse-test.rb -t 10-11-12 +# 2010-11-12 00:00:00 -0500 +# $ ruby optparse-test.rb -t 9:30 +# 2014-08-13 09:30:00 -0400 +# +# ==== Creating Custom Conversions +# +# The +accept+ method on Gem::OptionParser may be used to create converters. +# It specifies which conversion block to call whenever a class is specified. +# The example below uses it to fetch a +User+ object before the +on+ handler receives it. +# +# require 'rubygems/optparse/lib/optparse' +# +# User = Struct.new(:id, :name) +# +# def find_user id +# not_found = ->{ raise "No User Found for id #{id}" } +# [ User.new(1, "Sam"), +# User.new(2, "Gandalf") ].find(not_found) do |u| +# u.id == id +# end +# end +# +# op = Gem::OptionParser.new +# op.accept(User) do |user_id| +# find_user user_id.to_i +# end +# +# op.on("--user ID", User) do |user| +# puts user +# end +# +# op.parse! +# +# Used: +# +# $ ruby optparse-test.rb --user 1 +# #<struct User id=1, name="Sam"> +# $ ruby optparse-test.rb --user 2 +# #<struct User id=2, name="Gandalf"> +# $ ruby optparse-test.rb --user 3 +# optparse-test.rb:15:in `block in find_user': No User Found for id 3 (RuntimeError) +# +# === Store options to a Hash +# +# The +into+ option of +order+, +parse+ and so on methods stores command line options into a Hash. +# +# require 'rubygems/optparse/lib/optparse' +# +# options = {} +# Gem::OptionParser.new do |parser| +# parser.on('-a') +# parser.on('-b NUM', Integer) +# parser.on('-v', '--verbose') +# end.parse!(into: options) +# +# p options +# +# Used: +# +# $ ruby optparse-test.rb -a +# {:a=>true} +# $ ruby optparse-test.rb -a -v +# {:a=>true, :verbose=>true} +# $ ruby optparse-test.rb -a -b 100 +# {:a=>true, :b=>100} +# +# === Complete example +# +# The following example is a complete Ruby program. You can run it and see the +# effect of specifying various options. This is probably the best way to learn +# the features of +optparse+. +# +# require 'rubygems/optparse/lib/optparse' +# require 'rubygems/optparse/lib/optparse/time' +# require 'ostruct' +# require 'pp' +# +# class OptparseExample +# Version = '1.0.0' +# +# CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary] +# CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" } +# +# class ScriptOptions +# attr_accessor :library, :inplace, :encoding, :transfer_type, +# :verbose, :extension, :delay, :time, :record_separator, +# :list +# +# def initialize +# self.library = [] +# self.inplace = false +# self.encoding = "utf8" +# self.transfer_type = :auto +# self.verbose = false +# end +# +# def define_options(parser) +# parser.banner = "Usage: example.rb [options]" +# parser.separator "" +# parser.separator "Specific options:" +# +# # add additional options +# perform_inplace_option(parser) +# delay_execution_option(parser) +# execute_at_time_option(parser) +# specify_record_separator_option(parser) +# list_example_option(parser) +# specify_encoding_option(parser) +# optional_option_argument_with_keyword_completion_option(parser) +# boolean_verbose_option(parser) +# +# parser.separator "" +# parser.separator "Common options:" +# # No argument, shows at tail. This will print an options summary. +# # Try it and see! +# parser.on_tail("-h", "--help", "Show this message") do +# puts parser +# exit +# end +# # Another typical switch to print the version. +# parser.on_tail("--version", "Show version") do +# puts Version +# exit +# end +# end +# +# def perform_inplace_option(parser) +# # Specifies an optional option argument +# parser.on("-i", "--inplace [EXTENSION]", +# "Edit ARGV files in place", +# "(make backup if EXTENSION supplied)") do |ext| +# self.inplace = true +# self.extension = ext || '' +# self.extension.sub!(/\A\.?(?=.)/, ".") # Ensure extension begins with dot. +# end +# end +# +# def delay_execution_option(parser) +# # Cast 'delay' argument to a Float. +# parser.on("--delay N", Float, "Delay N seconds before executing") do |n| +# self.delay = n +# end +# end +# +# def execute_at_time_option(parser) +# # Cast 'time' argument to a Time object. +# parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time| +# self.time = time +# end +# end +# +# def specify_record_separator_option(parser) +# # Cast to octal integer. +# parser.on("-F", "--irs [OCTAL]", Gem::OptionParser::OctalInteger, +# "Specify record separator (default \\0)") do |rs| +# self.record_separator = rs +# end +# end +# +# def list_example_option(parser) +# # List of arguments. +# parser.on("--list x,y,z", Array, "Example 'list' of arguments") do |list| +# self.list = list +# end +# end +# +# def specify_encoding_option(parser) +# # Keyword completion. We are specifying a specific set of arguments (CODES +# # and CODE_ALIASES - notice the latter is a Hash), and the user may provide +# # the shortest unambiguous text. +# code_list = (CODE_ALIASES.keys + CODES).join(', ') +# parser.on("--code CODE", CODES, CODE_ALIASES, "Select encoding", +# "(#{code_list})") do |encoding| +# self.encoding = encoding +# end +# end +# +# def optional_option_argument_with_keyword_completion_option(parser) +# # Optional '--type' option argument with keyword completion. +# parser.on("--type [TYPE]", [:text, :binary, :auto], +# "Select transfer type (text, binary, auto)") do |t| +# self.transfer_type = t +# end +# end +# +# def boolean_verbose_option(parser) +# # Boolean switch. +# parser.on("-v", "--[no-]verbose", "Run verbosely") do |v| +# self.verbose = v +# end +# end +# end +# +# # +# # Return a structure describing the options. +# # +# def parse(args) +# # The options specified on the command line will be collected in +# # *options*. +# +# @options = ScriptOptions.new +# @args = Gem::OptionParser.new do |parser| +# @options.define_options(parser) +# parser.parse!(args) +# end +# @options +# end +# +# attr_reader :parser, :options +# end # class OptparseExample +# +# example = OptparseExample.new +# options = example.parse(ARGV) +# pp options # example.options +# pp ARGV +# +# === Shell Completion +# +# For modern shells (e.g. bash, zsh, etc.), you can use shell +# completion for command line options. +# +# === Further documentation +# +# The above examples, along with the accompanying +# {Tutorial}[./doc/optparse/tutorial_rdoc.html], +# should be enough to learn how to use this class. +# If you have any questions, file a ticket at http://bugs.ruby-lang.org. +# +class Gem::OptionParser + Gem::OptionParser::Version = "0.2.0" + + # :stopdoc: + NoArgument = [NO_ARGUMENT = :NONE, nil].freeze + RequiredArgument = [REQUIRED_ARGUMENT = :REQUIRED, true].freeze + OptionalArgument = [OPTIONAL_ARGUMENT = :OPTIONAL, false].freeze + # :startdoc: + + # + # Keyword completion module. This allows partial arguments to be specified + # and resolved against a list of acceptable values. + # + module Completion + def self.regexp(key, icase) + Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'), icase) + end + + def self.candidate(key, icase = false, pat = nil, &block) + pat ||= Completion.regexp(key, icase) + candidates = [] + block.call do |k, *v| + (if Regexp === k + kn = "" + k === key + else + kn = defined?(k.id2name) ? k.id2name : k + pat === kn + end) or next + v << k if v.empty? + candidates << [k, v, kn] + end + candidates + end + + def candidate(key, icase = false, pat = nil) + Completion.candidate(key, icase, pat, &method(:each)) + end + + public + def complete(key, icase = false, pat = nil) + candidates = candidate(key, icase, pat, &method(:each)).sort_by {|k, v, kn| kn.size} + if candidates.size == 1 + canon, sw, * = candidates[0] + elsif candidates.size > 1 + canon, sw, cn = candidates.shift + candidates.each do |k, v, kn| + next if sw == v + if String === cn and String === kn + if cn.rindex(kn, 0) + canon, sw, cn = k, v, kn + next + elsif kn.rindex(cn, 0) + next + end + end + throw :ambiguous, key + end + end + if canon + block_given? or return key, *sw + yield(key, *sw) + end + end + + def convert(opt = nil, val = nil, *) + val + end + end + + + # + # Map from option/keyword string to object with completion. + # + class OptionMap < Hash + include Completion + end + + + # + # Individual switch class. Not important to the user. + # + # Defined within Switch are several Switch-derived classes: NoArgument, + # RequiredArgument, etc. + # + class Switch + attr_reader :pattern, :conv, :short, :long, :arg, :desc, :block + + # + # Guesses argument style from +arg+. Returns corresponding + # Gem::OptionParser::Switch class (OptionalArgument, etc.). + # + def self.guess(arg) + case arg + when "" + t = self + when /\A=?\[/ + t = Switch::OptionalArgument + when /\A\s+\[/ + t = Switch::PlacedArgument + else + t = Switch::RequiredArgument + end + self >= t or incompatible_argument_styles(arg, t) + t + end + + def self.incompatible_argument_styles(arg, t) + raise(ArgumentError, "#{arg}: incompatible argument styles\n #{self}, #{t}", + ParseError.filter_backtrace(caller(2))) + end + + def self.pattern + NilClass + end + + def initialize(pattern = nil, conv = nil, + short = nil, long = nil, arg = nil, + desc = ([] if short or long), block = nil, &_block) + raise if Array === pattern + block ||= _block + @pattern, @conv, @short, @long, @arg, @desc, @block = + pattern, conv, short, long, arg, desc, block + end + + # + # Parses +arg+ and returns rest of +arg+ and matched portion to the + # argument pattern. Yields when the pattern doesn't match substring. + # + def parse_arg(arg) # :nodoc: + pattern or return nil, [arg] + unless m = pattern.match(arg) + yield(InvalidArgument, arg) + return arg, [] + end + if String === m + m = [s = m] + else + m = m.to_a + s = m[0] + return nil, m unless String === s + end + raise InvalidArgument, arg unless arg.rindex(s, 0) + return nil, m if s.length == arg.length + yield(InvalidArgument, arg) # didn't match whole arg + return arg[s.length..-1], m + end + private :parse_arg + + # + # Parses argument, converts and returns +arg+, +block+ and result of + # conversion. Yields at semi-error condition instead of raising an + # exception. + # + def conv_arg(arg, val = []) # :nodoc: + if conv + val = conv.call(*val) + else + val = proc {|v| v}.call(*val) + end + return arg, block, val + end + private :conv_arg + + # + # Produces the summary text. Each line of the summary is yielded to the + # block (without newline). + # + # +sdone+:: Already summarized short style options keyed hash. + # +ldone+:: Already summarized long style options keyed hash. + # +width+:: Width of left side (option part). In other words, the right + # side (description part) starts after +width+ columns. + # +max+:: Maximum width of left side -> the options are filled within + # +max+ columns. + # +indent+:: Prefix string indents all summarized lines. + # + def summarize(sdone = {}, ldone = {}, width = 1, max = width - 1, indent = "") + sopts, lopts = [], [], nil + @short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short + @long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long + return if sopts.empty? and lopts.empty? # completely hidden + + left = [sopts.join(', ')] + right = desc.dup + + while s = lopts.shift + l = left[-1].length + s.length + l += arg.length if left.size == 1 && arg + l < max or sopts.empty? or left << +'' + left[-1] << (left[-1].empty? ? ' ' * 4 : ', ') << s + end + + if arg + left[0] << (left[1] ? arg.sub(/\A(\[?)=/, '\1') + ',' : arg) + end + mlen = left.collect {|ss| ss.length}.max.to_i + while mlen > width and l = left.shift + mlen = left.collect {|ss| ss.length}.max.to_i if l.length == mlen + if l.length < width and (r = right[0]) and !r.empty? + l = l.to_s.ljust(width) + ' ' + r + right.shift + end + yield(indent + l) + end + + while begin l = left.shift; r = right.shift; l or r end + l = l.to_s.ljust(width) + ' ' + r if r and !r.empty? + yield(indent + l) + end + + self + end + + def add_banner(to) # :nodoc: + unless @short or @long + s = desc.join + to << " [" + s + "]..." unless s.empty? + end + to + end + + def match_nonswitch?(str) # :nodoc: + @pattern =~ str unless @short or @long + end + + # + # Main name of the switch. + # + def switch_name + (long.first || short.first).sub(/\A-+(?:\[no-\])?/, '') + end + + def compsys(sdone, ldone) # :nodoc: + sopts, lopts = [], [] + @short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short + @long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long + return if sopts.empty? and lopts.empty? # completely hidden + + (sopts+lopts).each do |opt| + # "(-x -c -r)-l[left justify]" + if /^--\[no-\](.+)$/ =~ opt + o = $1 + yield("--#{o}", desc.join("")) + yield("--no-#{o}", desc.join("")) + else + yield("#{opt}", desc.join("")) + end + end + end + + # + # Switch that takes no arguments. + # + class NoArgument < self + + # + # Raises an exception if any arguments given. + # + def parse(arg, argv) + yield(NeedlessArgument, arg) if arg + conv_arg(arg) + end + + def self.incompatible_argument_styles(*) + end + + def self.pattern + Object + end + end + + # + # Switch that takes an argument. + # + class RequiredArgument < self + + # + # Raises an exception if argument is not present. + # + def parse(arg, argv) + unless arg + raise MissingArgument if argv.empty? + arg = argv.shift + end + conv_arg(*parse_arg(arg, &method(:raise))) + end + end + + # + # Switch that can omit argument. + # + class OptionalArgument < self + + # + # Parses argument if given, or uses default value. + # + def parse(arg, argv, &error) + if arg + conv_arg(*parse_arg(arg, &error)) + else + conv_arg(arg) + end + end + end + + # + # Switch that takes an argument, which does not begin with '-'. + # + class PlacedArgument < self + + # + # Returns nil if argument is not present or begins with '-'. + # + def parse(arg, argv, &error) + if !(val = arg) and (argv.empty? or /\A-/ =~ (val = argv[0])) + return nil, block, nil + end + opt = (val = parse_arg(val, &error))[1] + val = conv_arg(*val) + if opt and !arg + argv.shift + else + val[0] = nil + end + val + end + end + end + + # + # Simple option list providing mapping from short and/or long option + # string to Gem::OptionParser::Switch and mapping from acceptable argument to + # matching pattern and converter pair. Also provides summary feature. + # + class List + # Map from acceptable argument types to pattern and converter pairs. + attr_reader :atype + + # Map from short style option switches to actual switch objects. + attr_reader :short + + # Map from long style option switches to actual switch objects. + attr_reader :long + + # List of all switches and summary string. + attr_reader :list + + # + # Just initializes all instance variables. + # + def initialize + @atype = {} + @short = OptionMap.new + @long = OptionMap.new + @list = [] + end + + # + # See Gem::OptionParser.accept. + # + def accept(t, pat = /.*/m, &block) + if pat + pat.respond_to?(:match) or + raise TypeError, "has no `match'", ParseError.filter_backtrace(caller(2)) + else + pat = t if t.respond_to?(:match) + end + unless block + block = pat.method(:convert).to_proc if pat.respond_to?(:convert) + end + @atype[t] = [pat, block] + end + + # + # See Gem::OptionParser.reject. + # + def reject(t) + @atype.delete(t) + end + + # + # Adds +sw+ according to +sopts+, +lopts+ and +nlopts+. + # + # +sw+:: Gem::OptionParser::Switch instance to be added. + # +sopts+:: Short style option list. + # +lopts+:: Long style option list. + # +nlopts+:: Negated long style options list. + # + def update(sw, sopts, lopts, nsw = nil, nlopts = nil) # :nodoc: + sopts.each {|o| @short[o] = sw} if sopts + lopts.each {|o| @long[o] = sw} if lopts + nlopts.each {|o| @long[o] = nsw} if nsw and nlopts + used = @short.invert.update(@long.invert) + @list.delete_if {|o| Switch === o and !used[o]} + end + private :update + + # + # Inserts +switch+ at the head of the list, and associates short, long + # and negated long options. Arguments are: + # + # +switch+:: Gem::OptionParser::Switch instance to be inserted. + # +short_opts+:: List of short style options. + # +long_opts+:: List of long style options. + # +nolong_opts+:: List of long style options with "no-" prefix. + # + # prepend(switch, short_opts, long_opts, nolong_opts) + # + def prepend(*args) + update(*args) + @list.unshift(args[0]) + end + + # + # Appends +switch+ at the tail of the list, and associates short, long + # and negated long options. Arguments are: + # + # +switch+:: Gem::OptionParser::Switch instance to be inserted. + # +short_opts+:: List of short style options. + # +long_opts+:: List of long style options. + # +nolong_opts+:: List of long style options with "no-" prefix. + # + # append(switch, short_opts, long_opts, nolong_opts) + # + def append(*args) + update(*args) + @list.push(args[0]) + end + + # + # Searches +key+ in +id+ list. The result is returned or yielded if a + # block is given. If it isn't found, nil is returned. + # + def search(id, key) + if list = __send__(id) + val = list.fetch(key) {return nil} + block_given? ? yield(val) : val + end + end + + # + # Searches list +id+ for +opt+ and the optional patterns for completion + # +pat+. If +icase+ is true, the search is case insensitive. The result + # is returned or yielded if a block is given. If it isn't found, nil is + # returned. + # + def complete(id, opt, icase = false, *pat, &block) + __send__(id).complete(opt, icase, *pat, &block) + end + + def get_candidates(id) + yield __send__(id).keys + end + + # + # Iterates over each option, passing the option to the +block+. + # + def each_option(&block) + list.each(&block) + end + + # + # Creates the summary table, passing each line to the +block+ (without + # newline). The arguments +args+ are passed along to the summarize + # method which is called on every option. + # + def summarize(*args, &block) + sum = [] + list.reverse_each do |opt| + if opt.respond_to?(:summarize) # perhaps Gem::OptionParser::Switch + s = [] + opt.summarize(*args) {|l| s << l} + sum.concat(s.reverse) + elsif !opt or opt.empty? + sum << "" + elsif opt.respond_to?(:each_line) + sum.concat([*opt.each_line].reverse) + else + sum.concat([*opt.each].reverse) + end + end + sum.reverse_each(&block) + end + + def add_banner(to) # :nodoc: + list.each do |opt| + if opt.respond_to?(:add_banner) + opt.add_banner(to) + end + end + to + end + + def compsys(*args, &block) # :nodoc: + list.each do |opt| + if opt.respond_to?(:compsys) + opt.compsys(*args, &block) + end + end + end + end + + # + # Hash with completion search feature. See Gem::OptionParser::Completion. + # + class CompletingHash < Hash + include Completion + + # + # Completion for hash key. + # + def match(key) + *values = fetch(key) { + raise AmbiguousArgument, catch(:ambiguous) {return complete(key)} + } + return key, *values + end + end + + # :stopdoc: + + # + # Enumeration of acceptable argument styles. Possible values are: + # + # NO_ARGUMENT:: The switch takes no arguments. (:NONE) + # REQUIRED_ARGUMENT:: The switch requires an argument. (:REQUIRED) + # OPTIONAL_ARGUMENT:: The switch requires an optional argument. (:OPTIONAL) + # + # Use like --switch=argument (long style) or -Xargument (short style). For + # short style, only portion matched to argument pattern is treated as + # argument. + # + ArgumentStyle = {} + NoArgument.each {|el| ArgumentStyle[el] = Switch::NoArgument} + RequiredArgument.each {|el| ArgumentStyle[el] = Switch::RequiredArgument} + OptionalArgument.each {|el| ArgumentStyle[el] = Switch::OptionalArgument} + ArgumentStyle.freeze + + # + # Switches common used such as '--', and also provides default + # argument classes + # + DefaultList = List.new + DefaultList.short['-'] = Switch::NoArgument.new {} + DefaultList.long[''] = Switch::NoArgument.new {throw :terminate} + + + COMPSYS_HEADER = <<'XXX' # :nodoc: + +typeset -A opt_args +local context state line + +_arguments -s -S \ +XXX + + def compsys(to, name = File.basename($0)) # :nodoc: + to << "#compdef #{name}\n" + to << COMPSYS_HEADER + visit(:compsys, {}, {}) {|o, d| + to << %Q[ "#{o}[#{d.gsub(/[\"\[\]]/, '\\\\\&')}]" \\\n] + } + to << " '*:file:_files' && return 0\n" + end + + # + # Default options for ARGV, which never appear in option summary. + # + Officious = {} + + # + # --help + # Shows option summary. + # + Officious['help'] = proc do |parser| + Switch::NoArgument.new do |arg| + puts parser.help + exit + end + end + + # + # --*-completion-bash=WORD + # Shows candidates for command line completion. + # + Officious['*-completion-bash'] = proc do |parser| + Switch::RequiredArgument.new do |arg| + puts parser.candidate(arg) + exit + end + end + + # + # --*-completion-zsh[=NAME:FILE] + # Creates zsh completion file. + # + Officious['*-completion-zsh'] = proc do |parser| + Switch::OptionalArgument.new do |arg| + parser.compsys(STDOUT, arg) + exit + end + end + + # + # --version + # Shows version string if Version is defined. + # + Officious['version'] = proc do |parser| + Switch::OptionalArgument.new do |pkg| + if pkg + begin + require 'rubygems/optparse/lib/optparse/version' + rescue LoadError + else + show_version(*pkg.split(/,/)) or + abort("#{parser.program_name}: no version found in package #{pkg}") + exit + end + end + v = parser.ver or abort("#{parser.program_name}: version unknown") + puts v + exit + end + end + + # :startdoc: + + # + # Class methods + # + + # + # Initializes a new instance and evaluates the optional block in context + # of the instance. Arguments +args+ are passed to #new, see there for + # description of parameters. + # + # This method is *deprecated*, its behavior corresponds to the older #new + # method. + # + def self.with(*args, &block) + opts = new(*args) + opts.instance_eval(&block) + opts + end + + # + # Returns an incremented value of +default+ according to +arg+. + # + def self.inc(arg, default = nil) + case arg + when Integer + arg.nonzero? + when nil + default.to_i + 1 + end + end + def inc(*args) + self.class.inc(*args) + end + + # + # Initializes the instance and yields itself if called with a block. + # + # +banner+:: Banner message. + # +width+:: Summary width. + # +indent+:: Summary indent. + # + def initialize(banner = nil, width = 32, indent = ' ' * 4) + @stack = [DefaultList, List.new, List.new] + @program_name = nil + @banner = banner + @summary_width = width + @summary_indent = indent + @default_argv = ARGV + @require_exact = false + add_officious + yield self if block_given? + end + + def add_officious # :nodoc: + list = base() + Officious.each do |opt, block| + list.long[opt] ||= block.call(self) + end + end + + # + # Terminates option parsing. Optional parameter +arg+ is a string pushed + # back to be the first non-option argument. + # + def terminate(arg = nil) + self.class.terminate(arg) + end + def self.terminate(arg = nil) + throw :terminate, arg + end + + @stack = [DefaultList] + def self.top() DefaultList end + + # + # Directs to accept specified class +t+. The argument string is passed to + # the block in which it should be converted to the desired class. + # + # +t+:: Argument class specifier, any object including Class. + # +pat+:: Pattern for argument, defaults to +t+ if it responds to match. + # + # accept(t, pat, &block) + # + def accept(*args, &blk) top.accept(*args, &blk) end + # + # See #accept. + # + def self.accept(*args, &blk) top.accept(*args, &blk) end + + # + # Directs to reject specified class argument. + # + # +t+:: Argument class specifier, any object including Class. + # + # reject(t) + # + def reject(*args, &blk) top.reject(*args, &blk) end + # + # See #reject. + # + def self.reject(*args, &blk) top.reject(*args, &blk) end + + # + # Instance methods + # + + # Heading banner preceding summary. + attr_writer :banner + + # Program name to be emitted in error message and default banner, + # defaults to $0. + attr_writer :program_name + + # Width for option list portion of summary. Must be Numeric. + attr_accessor :summary_width + + # Indentation for summary. Must be String (or have + String method). + attr_accessor :summary_indent + + # Strings to be parsed in default. + attr_accessor :default_argv + + # Whether to require that options match exactly (disallows providing + # abbreviated long option as short option). + attr_accessor :require_exact + + # + # Heading banner preceding summary. + # + def banner + unless @banner + @banner = +"Usage: #{program_name} [options]" + visit(:add_banner, @banner) + end + @banner + end + + # + # Program name to be emitted in error message and default banner, defaults + # to $0. + # + def program_name + @program_name || File.basename($0, '.*') + end + + # for experimental cascading :-) + alias set_banner banner= + alias set_program_name program_name= + alias set_summary_width summary_width= + alias set_summary_indent summary_indent= + + # Version + attr_writer :version + # Release code + attr_writer :release + + # + # Version + # + def version + (defined?(@version) && @version) || (defined?(::Version) && ::Version) + end + + # + # Release code + # + def release + (defined?(@release) && @release) || (defined?(::Release) && ::Release) || (defined?(::RELEASE) && ::RELEASE) + end + + # + # Returns version string from program_name, version and release. + # + def ver + if v = version + str = +"#{program_name} #{[v].join('.')}" + str << " (#{v})" if v = release + str + end + end + + def warn(mesg = $!) + super("#{program_name}: #{mesg}") + end + + def abort(mesg = $!) + super("#{program_name}: #{mesg}") + end + + # + # Subject of #on / #on_head, #accept / #reject + # + def top + @stack[-1] + end + + # + # Subject of #on_tail. + # + def base + @stack[1] + end + + # + # Pushes a new List. + # + def new + @stack.push(List.new) + if block_given? + yield self + else + self + end + end + + # + # Removes the last List. + # + def remove + @stack.pop + end + + # + # Puts option summary into +to+ and returns +to+. Yields each line if + # a block is given. + # + # +to+:: Output destination, which must have method <<. Defaults to []. + # +width+:: Width of left side, defaults to @summary_width. + # +max+:: Maximum length allowed for left side, defaults to +width+ - 1. + # +indent+:: Indentation, defaults to @summary_indent. + # + def summarize(to = [], width = @summary_width, max = width - 1, indent = @summary_indent, &blk) + nl = "\n" + blk ||= proc {|l| to << (l.index(nl, -1) ? l : l + nl)} + visit(:summarize, {}, {}, width, max, indent, &blk) + to + end + + # + # Returns option summary string. + # + def help; summarize("#{banner}".sub(/\n?\z/, "\n")) end + alias to_s help + + # + # Returns option summary list. + # + def to_a; summarize("#{banner}".split(/^/)) end + + # + # Checks if an argument is given twice, in which case an ArgumentError is + # raised. Called from Gem::OptionParser#switch only. + # + # +obj+:: New argument. + # +prv+:: Previously specified argument. + # +msg+:: Exception message. + # + def notwice(obj, prv, msg) # :nodoc: + unless !prv or prv == obj + raise(ArgumentError, "argument #{msg} given twice: #{obj}", + ParseError.filter_backtrace(caller(2))) + end + obj + end + private :notwice + + SPLAT_PROC = proc {|*a| a.length <= 1 ? a.first : a} # :nodoc: + + # :call-seq: + # make_switch(params, block = nil) + # + # :include: ../doc/optparse/creates_option.rdoc + # + def make_switch(opts, block = nil) + short, long, nolong, style, pattern, conv, not_pattern, not_conv, not_style = [], [], [] + ldesc, sdesc, desc, arg = [], [], [] + default_style = Switch::NoArgument + default_pattern = nil + klass = nil + q, a = nil + has_arg = false + + opts.each do |o| + # argument class + next if search(:atype, o) do |pat, c| + klass = notwice(o, klass, 'type') + if not_style and not_style != Switch::NoArgument + not_pattern, not_conv = pat, c + else + default_pattern, conv = pat, c + end + end + + # directly specified pattern(any object possible to match) + if (!(String === o || Symbol === o)) and o.respond_to?(:match) + pattern = notwice(o, pattern, 'pattern') + if pattern.respond_to?(:convert) + conv = pattern.method(:convert).to_proc + else + conv = SPLAT_PROC + end + next + end + + # anything others + case o + when Proc, Method + block = notwice(o, block, 'block') + when Array, Hash + case pattern + when CompletingHash + when nil + pattern = CompletingHash.new + conv = pattern.method(:convert).to_proc if pattern.respond_to?(:convert) + else + raise ArgumentError, "argument pattern given twice" + end + o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}} + when Module + raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4)) + when *ArgumentStyle.keys + style = notwice(ArgumentStyle[o], style, 'style') + when /^--no-([^\[\]=\s]*)(.+)?/ + q, a = $1, $2 + o = notwice(a ? Object : TrueClass, klass, 'type') + not_pattern, not_conv = search(:atype, o) unless not_style + not_style = (not_style || default_style).guess(arg = a) if a + default_style = Switch::NoArgument + default_pattern, conv = search(:atype, FalseClass) unless default_pattern + ldesc << "--no-#{q}" + (q = q.downcase).tr!('_', '-') + long << "no-#{q}" + nolong << q + when /^--\[no-\]([^\[\]=\s]*)(.+)?/ + q, a = $1, $2 + o = notwice(a ? Object : TrueClass, klass, 'type') + if a + default_style = default_style.guess(arg = a) + default_pattern, conv = search(:atype, o) unless default_pattern + end + ldesc << "--[no-]#{q}" + (o = q.downcase).tr!('_', '-') + long << o + not_pattern, not_conv = search(:atype, FalseClass) unless not_style + not_style = Switch::NoArgument + nolong << "no-#{o}" + when /^--([^\[\]=\s]*)(.+)?/ + q, a = $1, $2 + if a + o = notwice(NilClass, klass, 'type') + default_style = default_style.guess(arg = a) + default_pattern, conv = search(:atype, o) unless default_pattern + end + ldesc << "--#{q}" + (o = q.downcase).tr!('_', '-') + long << o + when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ + q, a = $1, $2 + o = notwice(Object, klass, 'type') + if a + default_style = default_style.guess(arg = a) + default_pattern, conv = search(:atype, o) unless default_pattern + else + has_arg = true + end + sdesc << "-#{q}" + short << Regexp.new(q) + when /^-(.)(.+)?/ + q, a = $1, $2 + if a + o = notwice(NilClass, klass, 'type') + default_style = default_style.guess(arg = a) + default_pattern, conv = search(:atype, o) unless default_pattern + end + sdesc << "-#{q}" + short << q + when /^=/ + style = notwice(default_style.guess(arg = o), style, 'style') + default_pattern, conv = search(:atype, Object) unless default_pattern + else + desc.push(o) + end + end + + default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern + if !(short.empty? and long.empty?) + if has_arg and default_style == Switch::NoArgument + default_style = Switch::RequiredArgument + end + s = (style || default_style).new(pattern || default_pattern, + conv, sdesc, ldesc, arg, desc, block) + elsif !block + if style or pattern + raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller) + end + s = desc + else + short << pattern + s = (style || default_style).new(pattern, + conv, nil, nil, arg, desc, block) + end + return s, short, long, + (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style), + nolong + end + + # :call-seq: + # define(*params, &block) + # + # :include: ../doc/optparse/creates_option.rdoc + # + def define(*opts, &block) + top.append(*(sw = make_switch(opts, block))) + sw[0] + end + + # :call-seq: + # on(*params, &block) + # + # :include: ../doc/optparse/creates_option.rdoc + # + def on(*opts, &block) + define(*opts, &block) + self + end + alias def_option define + + # :call-seq: + # define_head(*params, &block) + # + # :include: ../doc/optparse/creates_option.rdoc + # + def define_head(*opts, &block) + top.prepend(*(sw = make_switch(opts, block))) + sw[0] + end + + # :call-seq: + # on_head(*params, &block) + # + # :include: ../doc/optparse/creates_option.rdoc + # + # The new option is added at the head of the summary. + # + def on_head(*opts, &block) + define_head(*opts, &block) + self + end + alias def_head_option define_head + + # :call-seq: + # define_tail(*params, &block) + # + # :include: ../doc/optparse/creates_option.rdoc + # + def define_tail(*opts, &block) + base.append(*(sw = make_switch(opts, block))) + sw[0] + end + + # + # :call-seq: + # on_tail(*params, &block) + # + # :include: ../doc/optparse/creates_option.rdoc + # + # The new option is added at the tail of the summary. + # + def on_tail(*opts, &block) + define_tail(*opts, &block) + self + end + alias def_tail_option define_tail + + # + # Add separator in summary. + # + def separator(string) + top.append(string, nil, nil) + end + + # + # Parses command line arguments +argv+ in order. When a block is given, + # each non-option argument is yielded. When optional +into+ keyword + # argument is provided, the parsed option values are stored there via + # <code>[]=</code> method (so it can be Hash, or OpenStruct, or other + # similar object). + # + # Returns the rest of +argv+ left unparsed. + # + def order(*argv, into: nil, &nonopt) + argv = argv[0].dup if argv.size == 1 and Array === argv[0] + order!(argv, into: into, &nonopt) + end + + # + # Same as #order, but removes switches destructively. + # Non-option arguments remain in +argv+. + # + def order!(argv = default_argv, into: nil, &nonopt) + setter = ->(name, val) {into[name.to_sym] = val} if into + parse_in_order(argv, setter, &nonopt) + end + + def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: + opt, arg, val, rest = nil + nonopt ||= proc {|a| throw :terminate, a} + argv.unshift(arg) if arg = catch(:terminate) { + while arg = argv.shift + case arg + # long option + when /\A--([^=]*)(?:=(.*))?/m + opt, rest = $1, $2 + opt.tr!('_', '-') + begin + sw, = complete(:long, opt, true) + if require_exact && !sw.long.include?(arg) + raise InvalidOption, arg + end + rescue ParseError + raise $!.set_option(arg, true) + end + begin + opt, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)} + val = cb.call(val) if cb + setter.call(sw.switch_name, val) if setter + rescue ParseError + raise $!.set_option(arg, rest) + end + + # short option + when /\A-(.)((=).*|.+)?/m + eq, rest, opt = $3, $2, $1 + has_arg, val = eq, rest + begin + sw, = search(:short, opt) + unless sw + begin + sw, = complete(:short, opt) + # short option matched. + val = arg.delete_prefix('-') + has_arg = true + rescue InvalidOption + raise if require_exact + # if no short options match, try completion with long + # options. + sw, = complete(:long, opt) + eq ||= !rest + end + end + rescue ParseError + raise $!.set_option(arg, true) + end + begin + opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq} + rescue ParseError + raise $!.set_option(arg, arg.length > 2) + else + raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}" + end + begin + argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-') + val = cb.call(val) if cb + setter.call(sw.switch_name, val) if setter + rescue ParseError + raise $!.set_option(arg, arg.length > 2) + end + + # non-option argument + else + catch(:prune) do + visit(:each_option) do |sw0| + sw = sw0 + sw.block.call(arg) if Switch === sw and sw.match_nonswitch?(arg) + end + nonopt.call(arg) + end + end + end + + nil + } + + visit(:search, :short, nil) {|sw| sw.block.call(*argv) if !sw.pattern} + + argv + end + private :parse_in_order + + # + # Parses command line arguments +argv+ in permutation mode and returns + # list of non-option arguments. When optional +into+ keyword + # argument is provided, the parsed option values are stored there via + # <code>[]=</code> method (so it can be Hash, or OpenStruct, or other + # similar object). + # + def permute(*argv, into: nil) + argv = argv[0].dup if argv.size == 1 and Array === argv[0] + permute!(argv, into: into) + end + + # + # Same as #permute, but removes switches destructively. + # Non-option arguments remain in +argv+. + # + def permute!(argv = default_argv, into: nil) + nonopts = [] + order!(argv, into: into, &nonopts.method(:<<)) + argv[0, 0] = nonopts + argv + end + + # + # Parses command line arguments +argv+ in order when environment variable + # POSIXLY_CORRECT is set, and in permutation mode otherwise. + # When optional +into+ keyword argument is provided, the parsed option + # values are stored there via <code>[]=</code> method (so it can be Hash, + # or OpenStruct, or other similar object). + # + def parse(*argv, into: nil) + argv = argv[0].dup if argv.size == 1 and Array === argv[0] + parse!(argv, into: into) + end + + # + # Same as #parse, but removes switches destructively. + # Non-option arguments remain in +argv+. + # + def parse!(argv = default_argv, into: nil) + if ENV.include?('POSIXLY_CORRECT') + order!(argv, into: into) + else + permute!(argv, into: into) + end + end + + # + # Wrapper method for getopts.rb. + # + # params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option") + # # params["a"] = true # -a + # # params["b"] = "1" # -b1 + # # params["foo"] = "1" # --foo + # # params["bar"] = "x" # --bar x + # # params["zot"] = "z" # --zot Z + # + def getopts(*args) + argv = Array === args.first ? args.shift : default_argv + single_options, *long_options = *args + + result = {} + + single_options.scan(/(.)(:)?/) do |opt, val| + if val + result[opt] = nil + define("-#{opt} VAL") + else + result[opt] = false + define("-#{opt}") + end + end if single_options + + long_options.each do |arg| + arg, desc = arg.split(';', 2) + opt, val = arg.split(':', 2) + if val + result[opt] = val.empty? ? nil : val + define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact) + else + result[opt] = false + define("--#{opt}", *[desc].compact) + end + end + + parse_in_order(argv, result.method(:[]=)) + result + end + + # + # See #getopts. + # + def self.getopts(*args) + new.getopts(*args) + end + + # + # Traverses @stack, sending each element method +id+ with +args+ and + # +block+. + # + def visit(id, *args, &block) # :nodoc: + @stack.reverse_each do |el| + el.__send__(id, *args, &block) + end + nil + end + private :visit + + # + # Searches +key+ in @stack for +id+ hash and returns or yields the result. + # + def search(id, key) # :nodoc: + block_given = block_given? + visit(:search, id, key) do |k| + return block_given ? yield(k) : k + end + end + private :search + + # + # Completes shortened long style option switch and returns pair of + # canonical switch and switch descriptor Gem::OptionParser::Switch. + # + # +typ+:: Searching table. + # +opt+:: Searching key. + # +icase+:: Search case insensitive if true. + # +pat+:: Optional pattern for completion. + # + def complete(typ, opt, icase = false, *pat) # :nodoc: + if pat.empty? + search(typ, opt) {|sw| return [sw, opt]} # exact match or... + end + ambiguous = catch(:ambiguous) { + visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw} + } + exc = ambiguous ? AmbiguousOption : InvalidOption + raise exc.new(opt, additional: self.method(:additional_message).curry[typ]) + end + private :complete + + # + # Returns additional info. + # + def additional_message(typ, opt) + return unless typ and opt and defined?(DidYouMean::SpellChecker) + all_candidates = [] + visit(:get_candidates, typ) do |candidates| + all_candidates.concat(candidates) + end + all_candidates.select! {|cand| cand.is_a?(String) } + checker = DidYouMean::SpellChecker.new(dictionary: all_candidates) + suggestions = all_candidates & checker.correct(opt) + if DidYouMean.respond_to?(:formatter) + DidYouMean.formatter.message_for(suggestions) + else + "\nDid you mean? #{suggestions.join("\n ")}" + end + end + + def candidate(word) + list = [] + case word + when '-' + long = short = true + when /\A--/ + word, arg = word.split(/=/, 2) + argpat = Completion.regexp(arg, false) if arg and !arg.empty? + long = true + when /\A-/ + short = true + end + pat = Completion.regexp(word, long) + visit(:each_option) do |opt| + next unless Switch === opt + opts = (long ? opt.long : []) + (short ? opt.short : []) + opts = Completion.candidate(word, true, pat, &opts.method(:each)).map(&:first) if pat + if /\A=/ =~ opt.arg + opts.map! {|sw| sw + "="} + if arg and CompletingHash === opt.pattern + if opts = opt.pattern.candidate(arg, false, argpat) + opts.map!(&:last) + end + end + end + list.concat(opts) + end + list + end + + # + # Loads options from file names as +filename+. Does nothing when the file + # is not present. Returns whether successfully loaded. + # + # +filename+ defaults to basename of the program without suffix in a + # directory ~/.options, then the basename with '.options' suffix + # under XDG and Haiku standard places. + # + def load(filename = nil) + unless filename + basename = File.basename($0, '.*') + return true if load(File.expand_path(basename, '~/.options')) rescue nil + basename << ".options" + return [ + # XDG + ENV['XDG_CONFIG_HOME'], + '~/.config', + *ENV['XDG_CONFIG_DIRS']&.split(File::PATH_SEPARATOR), + + # Haiku + '~/config/settings', + ].any? {|dir| + next if !dir or dir.empty? + load(File.expand_path(basename, dir)) rescue nil + } + end + begin + parse(*IO.readlines(filename).each {|s| s.chomp!}) + true + rescue Errno::ENOENT, Errno::ENOTDIR + false + end + end + + # + # Parses environment variable +env+ or its uppercase with splitting like a + # shell. + # + # +env+ defaults to the basename of the program. + # + def environment(env = File.basename($0, '.*')) + env = ENV[env] || ENV[env.upcase] or return + require 'shellwords' + parse(*Shellwords.shellwords(env)) + end + + # + # Acceptable argument classes + # + + # + # Any string and no conversion. This is fall-back. + # + accept(Object) {|s,|s or s.nil?} + + accept(NilClass) {|s,|s} + + # + # Any non-empty string, and no conversion. + # + accept(String, /.+/m) {|s,*|s} + + # + # Ruby/C-like integer, octal for 0-7 sequence, binary for 0b, hexadecimal + # for 0x, and decimal for others; with optional sign prefix. Converts to + # Integer. + # + decimal = '\d+(?:_\d+)*' + binary = 'b[01]+(?:_[01]+)*' + hex = 'x[\da-f]+(?:_[\da-f]+)*' + octal = "0(?:[0-7]+(?:_[0-7]+)*|#{binary}|#{hex})?" + integer = "#{octal}|#{decimal}" + + accept(Integer, %r"\A[-+]?(?:#{integer})\z"io) {|s,| + begin + Integer(s) + rescue ArgumentError + raise Gem::OptionParser::InvalidArgument, s + end if s + } + + # + # Float number format, and converts to Float. + # + float = "(?:#{decimal}(?=(.)?)(?:\\.(?:#{decimal})?)?|\\.#{decimal})(?:E[-+]?#{decimal})?" + floatpat = %r"\A[-+]?#{float}\z"io + accept(Float, floatpat) {|s,| s.to_f if s} + + # + # Generic numeric format, converts to Integer for integer format, Float + # for float format, and Rational for rational format. + # + real = "[-+]?(?:#{octal}|#{float})" + accept(Numeric, /\A(#{real})(?:\/(#{real}))?\z/io) {|s, d, f, n,| + if n + Rational(d, n) + elsif f + Float(s) + else + Integer(s) + end + } + + # + # Decimal integer format, to be converted to Integer. + # + DecimalInteger = /\A[-+]?#{decimal}\z/io + accept(DecimalInteger, DecimalInteger) {|s,| + begin + Integer(s, 10) + rescue ArgumentError + raise Gem::OptionParser::InvalidArgument, s + end if s + } + + # + # Ruby/C like octal/hexadecimal/binary integer format, to be converted to + # Integer. + # + OctalInteger = /\A[-+]?(?:[0-7]+(?:_[0-7]+)*|0(?:#{binary}|#{hex}))\z/io + accept(OctalInteger, OctalInteger) {|s,| + begin + Integer(s, 8) + rescue ArgumentError + raise Gem::OptionParser::InvalidArgument, s + end if s + } + + # + # Decimal integer/float number format, to be converted to Integer for + # integer format, Float for float format. + # + DecimalNumeric = floatpat # decimal integer is allowed as float also. + accept(DecimalNumeric, floatpat) {|s, f| + begin + if f + Float(s) + else + Integer(s) + end + rescue ArgumentError + raise Gem::OptionParser::InvalidArgument, s + end if s + } + + # + # Boolean switch, which means whether it is present or not, whether it is + # absent or not with prefix no-, or it takes an argument + # yes/no/true/false/+/-. + # + yesno = CompletingHash.new + %w[- no false].each {|el| yesno[el] = false} + %w[+ yes true].each {|el| yesno[el] = true} + yesno['nil'] = false # should be nil? + accept(TrueClass, yesno) {|arg, val| val == nil or val} + # + # Similar to TrueClass, but defaults to false. + # + accept(FalseClass, yesno) {|arg, val| val != nil and val} + + # + # List of strings separated by ",". + # + accept(Array) do |s, | + if s + s = s.split(',').collect {|ss| ss unless ss.empty?} + end + s + end + + # + # Regular expression with options. + # + accept(Regexp, %r"\A/((?:\\.|[^\\])*)/([[:alpha:]]+)?\z|.*") do |all, s, o| + f = 0 + if o + f |= Regexp::IGNORECASE if /i/ =~ o + f |= Regexp::MULTILINE if /m/ =~ o + f |= Regexp::EXTENDED if /x/ =~ o + k = o.delete("imx") + k = nil if k.empty? + end + Regexp.new(s || all, f, k) + end + + # + # Exceptions + # + + # + # Base class of exceptions from Gem::OptionParser. + # + class ParseError < RuntimeError + # Reason which caused the error. + Reason = 'parse error' + + def initialize(*args, additional: nil) + @additional = additional + @arg0, = args + @args = args + @reason = nil + end + + attr_reader :args + attr_writer :reason + attr_accessor :additional + + # + # Pushes back erred argument(s) to +argv+. + # + def recover(argv) + argv[0, 0] = @args + argv + end + + def self.filter_backtrace(array) + unless $DEBUG + array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~)) + end + array + end + + def set_backtrace(array) + super(self.class.filter_backtrace(array)) + end + + def set_option(opt, eq) + if eq + @args[0] = opt + else + @args.unshift(opt) + end + self + end + + # + # Returns error reason. Override this for I18N. + # + def reason + @reason || self.class::Reason + end + + def inspect + "#<#{self.class}: #{args.join(' ')}>" + end + + # + # Default stringizing method to emit standard error message. + # + def message + "#{reason}: #{args.join(' ')}#{additional[@arg0] if additional}" + end + + alias to_s message + end + + # + # Raises when ambiguously completable string is encountered. + # + class AmbiguousOption < ParseError + const_set(:Reason, 'ambiguous option') + end + + # + # Raises when there is an argument for a switch which takes no argument. + # + class NeedlessArgument < ParseError + const_set(:Reason, 'needless argument') + end + + # + # Raises when a switch with mandatory argument has no argument. + # + class MissingArgument < ParseError + const_set(:Reason, 'missing argument') + end + + # + # Raises when switch is undefined. + # + class InvalidOption < ParseError + const_set(:Reason, 'invalid option') + end + + # + # Raises when the given argument does not match required format. + # + class InvalidArgument < ParseError + const_set(:Reason, 'invalid argument') + end + + # + # Raises when the given argument word can't be completed uniquely. + # + class AmbiguousArgument < InvalidArgument + const_set(:Reason, 'ambiguous argument') + end + + # + # Miscellaneous + # + + # + # Extends command line arguments array (ARGV) to parse itself. + # + module Arguable + + # + # Sets Gem::OptionParser object, when +opt+ is +false+ or +nil+, methods + # Gem::OptionParser::Arguable#options and Gem::OptionParser::Arguable#options= are + # undefined. Thus, there is no ways to access the Gem::OptionParser object + # via the receiver object. + # + def options=(opt) + unless @optparse = opt + class << self + undef_method(:options) + undef_method(:options=) + end + end + end + + # + # Actual Gem::OptionParser object, automatically created if nonexistent. + # + # If called with a block, yields the Gem::OptionParser object and returns the + # result of the block. If an Gem::OptionParser::ParseError exception occurs + # in the block, it is rescued, a error message printed to STDERR and + # +nil+ returned. + # + def options + @optparse ||= Gem::OptionParser.new + @optparse.default_argv = self + block_given? or return @optparse + begin + yield @optparse + rescue ParseError + @optparse.warn $! + nil + end + end + + # + # Parses +self+ destructively in order and returns +self+ containing the + # rest arguments left unparsed. + # + def order!(&blk) options.order!(self, &blk) end + + # + # Parses +self+ destructively in permutation mode and returns +self+ + # containing the rest arguments left unparsed. + # + def permute!() options.permute!(self) end + + # + # Parses +self+ destructively and returns +self+ containing the + # rest arguments left unparsed. + # + def parse!() options.parse!(self) end + + # + # Substitution of getopts is possible as follows. Also see + # Gem::OptionParser#getopts. + # + # def getopts(*args) + # ($OPT = ARGV.getopts(*args)).each do |opt, val| + # eval "$OPT_#{opt.gsub(/[^A-Za-z0-9_]/, '_')} = val" + # end + # rescue Gem::OptionParser::ParseError + # end + # + def getopts(*args) + options.getopts(self, *args) + end + + # + # Initializes instance variable. + # + def self.extend_object(obj) + super + obj.instance_eval {@optparse = nil} + end + def initialize(*args) + super + @optparse = nil + end + end + + # + # Acceptable argument classes. Now contains DecimalInteger, OctalInteger + # and DecimalNumeric. See Acceptable argument classes (in source code). + # + module Acceptables + const_set(:DecimalInteger, Gem::OptionParser::DecimalInteger) + const_set(:OctalInteger, Gem::OptionParser::OctalInteger) + const_set(:DecimalNumeric, Gem::OptionParser::DecimalNumeric) + end +end + +# ARGV is arguable by Gem::OptionParser +ARGV.extend(Gem::OptionParser::Arguable) diff --git a/lib/rubygems/optparse/lib/optparse/ac.rb b/lib/rubygems/optparse/lib/optparse/ac.rb new file mode 100644 index 0000000000..ff2f4c2dc3 --- /dev/null +++ b/lib/rubygems/optparse/lib/optparse/ac.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: false +require 'rubygems/optparse/lib/optparse' + +class Gem::OptionParser::AC < Gem::OptionParser + private + + def _check_ac_args(name, block) + unless /\A\w[-\w]*\z/ =~ name + raise ArgumentError, name + end + unless block + raise ArgumentError, "no block given", ParseError.filter_backtrace(caller) + end + end + + ARG_CONV = proc {|val| val.nil? ? true : val} + + def _ac_arg_enable(prefix, name, help_string, block) + _check_ac_args(name, block) + + sdesc = [] + ldesc = ["--#{prefix}-#{name}"] + desc = [help_string] + q = name.downcase + ac_block = proc {|val| block.call(ARG_CONV.call(val))} + enable = Switch::PlacedArgument.new(nil, ARG_CONV, sdesc, ldesc, nil, desc, ac_block) + disable = Switch::NoArgument.new(nil, proc {false}, sdesc, ldesc, nil, desc, ac_block) + top.append(enable, [], ["enable-" + q], disable, ['disable-' + q]) + enable + end + + public + + def ac_arg_enable(name, help_string, &block) + _ac_arg_enable("enable", name, help_string, block) + end + + def ac_arg_disable(name, help_string, &block) + _ac_arg_enable("disable", name, help_string, block) + end + + def ac_arg_with(name, help_string, &block) + _check_ac_args(name, block) + + sdesc = [] + ldesc = ["--with-#{name}"] + desc = [help_string] + q = name.downcase + with = Switch::PlacedArgument.new(*search(:atype, String), sdesc, ldesc, nil, desc, block) + without = Switch::NoArgument.new(nil, proc {}, sdesc, ldesc, nil, desc, block) + top.append(with, [], ["with-" + q], without, ['without-' + q]) + with + end +end diff --git a/lib/rubygems/optparse/lib/optparse/date.rb b/lib/rubygems/optparse/lib/optparse/date.rb new file mode 100644 index 0000000000..11131e92c2 --- /dev/null +++ b/lib/rubygems/optparse/lib/optparse/date.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: false +require 'rubygems/optparse/lib/optparse' +require 'date' + +Gem::OptionParser.accept(DateTime) do |s,| + begin + DateTime.parse(s) if s + rescue ArgumentError + raise Gem::OptionParser::InvalidArgument, s + end +end +Gem::OptionParser.accept(Date) do |s,| + begin + Date.parse(s) if s + rescue ArgumentError + raise Gem::OptionParser::InvalidArgument, s + end +end diff --git a/lib/rubygems/optparse/lib/optparse/kwargs.rb b/lib/rubygems/optparse/lib/optparse/kwargs.rb new file mode 100644 index 0000000000..9290344c39 --- /dev/null +++ b/lib/rubygems/optparse/lib/optparse/kwargs.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true +require 'rubygems/optparse/lib/optparse' + +class Gem::OptionParser + # :call-seq: + # define_by_keywords(options, method, **params) + # + # :include: ../../doc/optparse/creates_option.rdoc + # + def define_by_keywords(options, meth, **opts) + meth.parameters.each do |type, name| + case type + when :key, :keyreq + op, cl = *(type == :key ? %w"[ ]" : ["", ""]) + define("--#{name}=#{op}#{name.upcase}#{cl}", *opts[name]) do |o| + options[name] = o + end + end + end + options + end +end diff --git a/lib/rubygems/optparse/lib/optparse/shellwords.rb b/lib/rubygems/optparse/lib/optparse/shellwords.rb new file mode 100644 index 0000000000..60dd91990c --- /dev/null +++ b/lib/rubygems/optparse/lib/optparse/shellwords.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: false +# -*- ruby -*- + +require 'shellwords' +require 'rubygems/optparse/lib/optparse' + +Gem::OptionParser.accept(Shellwords) {|s,| Shellwords.shellwords(s)} diff --git a/lib/rubygems/optparse/lib/optparse/time.rb b/lib/rubygems/optparse/lib/optparse/time.rb new file mode 100644 index 0000000000..cb19f6e998 --- /dev/null +++ b/lib/rubygems/optparse/lib/optparse/time.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: false +require 'rubygems/optparse/lib/optparse' +require 'time' + +Gem::OptionParser.accept(Time) do |s,| + begin + (Time.httpdate(s) rescue Time.parse(s)) if s + rescue + raise Gem::OptionParser::InvalidArgument, s + end +end diff --git a/lib/rubygems/optparse/lib/optparse/uri.rb b/lib/rubygems/optparse/lib/optparse/uri.rb new file mode 100644 index 0000000000..088f309992 --- /dev/null +++ b/lib/rubygems/optparse/lib/optparse/uri.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: false +# -*- ruby -*- + +require 'rubygems/optparse/lib/optparse' +require 'uri' + +Gem::OptionParser.accept(URI) {|s,| URI.parse(s) if s} diff --git a/lib/rubygems/optparse/lib/optparse/version.rb b/lib/rubygems/optparse/lib/optparse/version.rb new file mode 100644 index 0000000000..5d79e9db44 --- /dev/null +++ b/lib/rubygems/optparse/lib/optparse/version.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: false +# Gem::OptionParser internal utility + +class << Gem::OptionParser + def show_version(*pkgs) + progname = ARGV.options.program_name + result = false + show = proc do |klass, cname, version| + str = "#{progname}" + unless klass == ::Object and cname == :VERSION + version = version.join(".") if Array === version + str << ": #{klass}" unless klass == Object + str << " version #{version}" + end + [:Release, :RELEASE].find do |rel| + if klass.const_defined?(rel) + str << " (#{klass.const_get(rel)})" + end + end + puts str + result = true + end + if pkgs.size == 1 and pkgs[0] == "all" + self.search_const(::Object, /\AV(?:ERSION|ersion)\z/) do |klass, cname, version| + unless cname[1] == ?e and klass.const_defined?(:Version) + show.call(klass, cname.intern, version) + end + end + else + pkgs.each do |pkg| + begin + pkg = pkg.split(/::|\//).inject(::Object) {|m, c| m.const_get(c)} + v = case + when pkg.const_defined?(:Version) + pkg.const_get(n = :Version) + when pkg.const_defined?(:VERSION) + pkg.const_get(n = :VERSION) + else + n = nil + "unknown" + end + show.call(pkg, n, v) + rescue NameError + end + end + end + result + end + + def each_const(path, base = ::Object) + path.split(/::|\//).inject(base) do |klass, name| + raise NameError, path unless Module === klass + klass.constants.grep(/#{name}/i) do |c| + klass.const_defined?(c) or next + klass.const_get(c) + end + end + end + + def search_const(klass, name) + klasses = [klass] + while klass = klasses.shift + klass.constants.each do |cname| + klass.const_defined?(cname) or next + const = klass.const_get(cname) + yield klass, cname, const if name === cname + klasses << const if Module === const and const != ::Object + end + end + end +end diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index 0587cd212b..94705914af 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -41,9 +41,9 @@ # #files are the files in the .gem tar file, not the Ruby files in the gem # #extract_files and #contents automatically call #verify -require "rubygems" -require 'rubygems/security' -require 'rubygems/user_interaction' +require_relative "../rubygems" +require_relative 'security' +require_relative 'user_interaction' class Gem::Package include Gem::UserInteraction @@ -71,6 +71,13 @@ class Gem::Package end end + class SymlinkError < Error + def initialize(name, destination, destination_dir) + super "installing symlink '%s' pointing to parent path %s of %s is not allowed" % + [name, destination, destination_dir] + end + end + class NonSeekableIO < Error; end class TooLongFileName < Error; end @@ -252,14 +259,7 @@ class Gem::Package stat = File.lstat file if stat.symlink? - target_path = File.readlink(file) - - unless target_path.start_with? '.' - relative_dir = File.dirname(file).sub("#{Dir.pwd}/", '') - target_path = File.join(relative_dir, target_path) - end - - tar.add_symlink file, target_path, stat.mode + tar.add_symlink file, File.readlink(file), stat.mode end next unless stat.file? @@ -407,13 +407,21 @@ EOM # extracted. def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc: - directories = [] if dir_mode + directories = [] open_tar_gz io do |tar| tar.each do |entry| next unless File.fnmatch pattern, entry.full_name, File::FNM_DOTMATCH destination = install_location entry.full_name, destination_dir + if entry.symlink? + link_target = entry.header.linkname + real_destination = link_target.start_with?("/") ? link_target : File.expand_path(link_target, File.dirname(destination)) + + raise Gem::Package::SymlinkError.new(entry.full_name, real_destination, destination_dir) unless + normalize_path(real_destination).start_with? normalize_path(destination_dir + '/') + end + FileUtils.rm_rf destination mkdir_options = {} @@ -424,9 +432,11 @@ EOM else File.dirname destination end - directories << mkdir if directories - mkdir_p_safe mkdir, mkdir_options, destination_dir, entry.full_name + unless directories.include?(mkdir) + FileUtils.mkdir_p mkdir, **mkdir_options + directories << mkdir + end File.open destination, 'wb' do |out| out.write entry.read @@ -439,8 +449,7 @@ EOM end end - if directories - directories.uniq! + if dir_mode File.chmod(dir_mode, *directories) end end @@ -473,21 +482,11 @@ EOM raise Gem::Package::PathError.new(filename, destination_dir) if filename.start_with? '/' - destination_dir = File.expand_path(File.realpath(destination_dir)) - destination = File.expand_path(File.join(destination_dir, filename)) + destination_dir = File.realpath(destination_dir) + destination = File.expand_path(filename, destination_dir) raise Gem::Package::PathError.new(destination, destination_dir) unless - destination.start_with? destination_dir + '/' - - begin - real_destination = File.expand_path(File.realpath(destination)) - rescue - # it's fine if the destination doesn't exist, because rm -rf'ing it can't cause any damage - nil - else - raise Gem::Package::PathError.new(real_destination, destination_dir) unless - real_destination.start_with? destination_dir + '/' - end + normalize_path(destination).start_with? normalize_path(destination_dir + '/') destination.tap(&Gem::UNTAINT) destination @@ -501,22 +500,6 @@ EOM end end - def mkdir_p_safe(mkdir, mkdir_options, destination_dir, file_name) - destination_dir = File.realpath(File.expand_path(destination_dir)) - parts = mkdir.split(File::SEPARATOR) - parts.reduce do |path, basename| - path = File.realpath(path) unless path == "" - path = File.expand_path(path + File::SEPARATOR + basename) - lstat = File.lstat path rescue nil - if !lstat || !lstat.directory? - unless normalize_path(path).start_with? normalize_path(destination_dir) and (FileUtils.mkdir path, **mkdir_options rescue false) - raise Gem::Package::PathError.new(file_name, destination_dir) - end - end - path - end - end - ## # Loads a Gem::Specification from the TarEntry +entry+ @@ -709,12 +692,12 @@ EOM end end -require 'rubygems/package/digest_io' -require 'rubygems/package/source' -require 'rubygems/package/file_source' -require 'rubygems/package/io_source' -require 'rubygems/package/old' -require 'rubygems/package/tar_header' -require 'rubygems/package/tar_reader' -require 'rubygems/package/tar_reader/entry' -require 'rubygems/package/tar_writer' +require_relative 'package/digest_io' +require_relative 'package/source' +require_relative 'package/file_source' +require_relative 'package/io_source' +require_relative 'package/old' +require_relative 'package/tar_header' +require_relative 'package/tar_reader' +require_relative 'package/tar_reader/entry' +require_relative 'package/tar_writer' diff --git a/lib/rubygems/package/io_source.rb b/lib/rubygems/package/io_source.rb index 7d7383110b..03d7714524 100644 --- a/lib/rubygems/package/io_source.rb +++ b/lib/rubygems/package/io_source.rb @@ -32,10 +32,14 @@ class Gem::Package::IOSource < Gem::Package::Source # :nodoc: all def with_read_io yield io + ensure + io.rewind end def with_write_io yield io + ensure + io.rewind end def path diff --git a/lib/rubygems/package/tar_reader.rb b/lib/rubygems/package/tar_reader.rb index e7c5620533..41121f3bfb 100644 --- a/lib/rubygems/package/tar_reader.rb +++ b/lib/rubygems/package/tar_reader.rb @@ -121,4 +121,4 @@ class Gem::Package::TarReader end end -require 'rubygems/package/tar_reader/entry' +require_relative 'tar_reader/entry' diff --git a/lib/rubygems/package_task.rb b/lib/rubygems/package_task.rb index d5a2885a64..bb48616b0e 100644 --- a/lib/rubygems/package_task.rb +++ b/lib/rubygems/package_task.rb @@ -20,8 +20,8 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -require 'rubygems' -require 'rubygems/package' +require_relative '../rubygems' +require_relative 'package' require 'rake/packagetask' ## diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb index fd1c0a62ac..efb046c7aa 100644 --- a/lib/rubygems/platform.rb +++ b/lib/rubygems/platform.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require "rubygems/deprecate" +require_relative "deprecate" ## # Available list of platforms for targeting Gem installations. @@ -100,6 +100,7 @@ class Gem::Platform when /^dotnet([\d.]*)/ then [ 'dotnet', $1 ] when /linux-?((?!gnu)\w+)?/ then [ 'linux', $1 ] when /mingw32/ then [ 'mingw32', nil ] + when /mingw-?(\w+)?/ then [ 'mingw', $1 ] when /(mswin\d+)(\_(\d+))?/ then os, version = $1, $3 @cpu = 'x86' if @cpu.nil? and os =~ /32$/ diff --git a/lib/rubygems/query_utils.rb b/lib/rubygems/query_utils.rb index ea0f260ab4..0acd5bf9c8 100644 --- a/lib/rubygems/query_utils.rb +++ b/lib/rubygems/query_utils.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require 'rubygems/local_remote_options' -require 'rubygems/spec_fetcher' -require 'rubygems/version_option' -require 'rubygems/text' +require_relative 'local_remote_options' +require_relative 'spec_fetcher' +require_relative 'version_option' +require_relative 'text' module Gem::QueryUtils diff --git a/lib/rubygems/rdoc.rb b/lib/rubygems/rdoc.rb index c40bb7d9f1..ac5e8f0822 100644 --- a/lib/rubygems/rdoc.rb +++ b/lib/rubygems/rdoc.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems' +require_relative '../rubygems' begin require 'rdoc/rubygems_hook' diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index 53e840978c..de6f88f39a 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -1,12 +1,11 @@ # frozen_string_literal: true -require 'rubygems' -require 'rubygems/request' -require 'rubygems/request/connection_pools' -require 'rubygems/s3_uri_signer' -require 'rubygems/uri_formatter' -require 'rubygems/uri_parsing' -require 'rubygems/user_interaction' -require 'resolv' +require_relative '../rubygems' +require_relative 'request' +require_relative 'request/connection_pools' +require_relative 's3_uri_signer' +require_relative 'uri_formatter' +require_relative 'uri' +require_relative 'user_interaction' ## # RemoteFetcher handles the details of fetching gems and gem information from @@ -14,30 +13,24 @@ require 'resolv' class Gem::RemoteFetcher include Gem::UserInteraction - include Gem::UriParsing ## # A FetchError exception wraps up the various possible IO and HTTP failures # that could happen while downloading from the internet. class FetchError < Gem::Exception - include Gem::UriParsing - ## # The URI which was being accessed when the exception happened. attr_accessor :uri, :original_uri def initialize(message, uri) - super message - - uri = parse_uri(uri) - - @original_uri = uri.dup + uri = Gem::Uri.new(uri) - uri.password = 'REDACTED' if uri.respond_to?(:password) && uri.password + super uri.redact_credentials_from(message) - @uri = uri.to_s + @original_uri = uri.to_s + @uri = uri.redacted.to_s end def to_s # :nodoc: @@ -51,6 +44,7 @@ class Gem::RemoteFetcher class UnknownHostError < FetchError end + deprecate_constant(:UnknownHostError) @fetcher = nil @@ -78,6 +72,7 @@ class Gem::RemoteFetcher # fetching the gem. def initialize(proxy=nil, dns=nil, headers={}) + require_relative 'core_ext/tcpsocket_init' if Gem.configuration.ipv4_fallback_enabled require 'net/http' require 'stringio' require 'uri' @@ -86,7 +81,7 @@ class Gem::RemoteFetcher @proxy = proxy @pools = {} - @pool_lock = Mutex.new + @pool_lock = Thread::Mutex.new @cert_files = Gem::Request.get_cert_files @headers = headers @@ -131,7 +126,7 @@ class Gem::RemoteFetcher require "fileutils" FileUtils.mkdir_p cache_dir rescue nil unless File.exist? cache_dir - source_uri = parse_uri(source_uri) + source_uri = Gem::Uri.new(source_uri) scheme = source_uri.scheme @@ -226,7 +221,7 @@ class Gem::RemoteFetcher unless location = response['Location'] raise FetchError.new("redirecting but no redirect location was given", uri) end - location = parse_uri location + location = Gem::Uri.new location if https?(uri) && !https?(location) raise FetchError.new("redirecting to non-https resource: #{location}", uri) @@ -244,7 +239,7 @@ class Gem::RemoteFetcher # Downloads +uri+ and returns it as a String. def fetch_path(uri, mtime = nil, head = false) - uri = parse_uri uri + uri = Gem::Uri.new uri unless uri.scheme raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}" @@ -261,15 +256,9 @@ class Gem::RemoteFetcher end data - rescue Timeout::Error - raise UnknownHostError.new('timed out', uri) - rescue IOError, SocketError, SystemCallError, + rescue Timeout::Error, IOError, SocketError, SystemCallError, *(OpenSSL::SSL::SSLError if Gem::HAVE_OPENSSL) => e - if e.message =~ /getaddrinfo/ - raise UnknownHostError.new('no such name', uri) - else - raise FetchError.new("#{e.class}: #{e}", uri) - end + raise FetchError.new("#{e.class}: #{e}", uri) end def fetch_s3(uri, mtime = nil, head = false) diff --git a/lib/rubygems/request.rb b/lib/rubygems/request.rb index 1ed0fbcb99..d6100c914b 100644 --- a/lib/rubygems/request.rb +++ b/lib/rubygems/request.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true require 'net/http' -require 'rubygems/user_interaction' +require_relative 'user_interaction' class Gem::Request extend Gem::UserInteraction @@ -44,7 +44,7 @@ class Gem::Request end def self.configure_connection_for_https(connection, cert_files) - raise Gem::Exception.new('OpenSSl is not available. Install OpenSSL and rebuild Ruby (preferred) or use non-HTTPS sources') unless Gem::HAVE_OPENSSL + raise Gem::Exception.new('OpenSSL is not available. Install OpenSSL and rebuild Ruby (preferred) or use non-HTTPS sources') unless Gem::HAVE_OPENSSL connection.use_ssl = true connection.verify_mode = @@ -96,8 +96,10 @@ class Gem::Request return unless cert case error_number when OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED then + require 'time' "Certificate #{cert.subject} expired at #{cert.not_after.iso8601}" when OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID then + require 'time' "Certificate #{cert.subject} not valid until #{cert.not_before.iso8601}" when OpenSSL::X509::V_ERR_CERT_REJECTED then "Certificate #{cert.subject} is rejected" @@ -191,7 +193,7 @@ class Gem::Request begin @requests[connection.object_id] += 1 - verbose "#{request.method} #{@uri}" + verbose "#{request.method} #{Gem::Uri.new(@uri).redacted}" file_name = File.basename(@uri.path) # perform download progress reporter only for gems @@ -287,6 +289,6 @@ class Gem::Request end end -require 'rubygems/request/http_pool' -require 'rubygems/request/https_pool' -require 'rubygems/request/connection_pools' +require_relative 'request/http_pool' +require_relative 'request/https_pool' +require_relative 'request/connection_pools' diff --git a/lib/rubygems/request/connection_pools.rb b/lib/rubygems/request/connection_pools.rb index 7f3988952c..a4c2929b38 100644 --- a/lib/rubygems/request/connection_pools.rb +++ b/lib/rubygems/request/connection_pools.rb @@ -11,7 +11,7 @@ class Gem::Request::ConnectionPools # :nodoc: @proxy_uri = proxy_uri @cert_files = cert_files @pools = {} - @pool_mutex = Mutex.new + @pool_mutex = Thread::Mutex.new end def pool_for(uri) diff --git a/lib/rubygems/request/http_pool.rb b/lib/rubygems/request/http_pool.rb index 9985bbafa6..f028516db8 100644 --- a/lib/rubygems/request/http_pool.rb +++ b/lib/rubygems/request/http_pool.rb @@ -12,7 +12,7 @@ class Gem::Request::HTTPPool # :nodoc: @http_args = http_args @cert_files = cert_files @proxy_uri = proxy_uri - @queue = SizedQueue.new 1 + @queue = Thread::SizedQueue.new 1 @queue << nil end diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index 5190cfc904..01b01599a8 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'tsort' +require_relative 'tsort' ## # A RequestSet groups a request to activate a set of dependencies. @@ -15,7 +15,7 @@ require 'tsort' # #=> ["nokogiri-1.6.0", "mini_portile-0.5.1", "pg-0.17.0"] class Gem::RequestSet - include TSort + include Gem::TSort ## # Array of gems to install even if already installed @@ -151,7 +151,7 @@ class Gem::RequestSet @prerelease = options[:prerelease] requests = [] - download_queue = Queue.new + download_queue = Thread::Queue.new # Create a thread-safe list of gems to download sorted_requests.each do |req| @@ -303,7 +303,7 @@ class Gem::RequestSet end end - require "rubygems/dependency_installer" + require_relative "dependency_installer" inst = Gem::DependencyInstaller.new options inst.installed_gems.replace specs @@ -461,6 +461,6 @@ class Gem::RequestSet end end -require 'rubygems/request_set/gem_dependency_api' -require 'rubygems/request_set/lockfile' -require 'rubygems/request_set/lockfile/tokenizer' +require_relative 'request_set/gem_dependency_api' +require_relative 'request_set/lockfile' +require_relative 'request_set/lockfile/tokenizer' diff --git a/lib/rubygems/request_set/lockfile.rb b/lib/rubygems/request_set/lockfile.rb index 8f8f142fff..bec29ef1b9 100644 --- a/lib/rubygems/request_set/lockfile.rb +++ b/lib/rubygems/request_set/lockfile.rb @@ -236,4 +236,4 @@ class Gem::RequestSet::Lockfile end end -require 'rubygems/request_set/lockfile/tokenizer' +require_relative 'lockfile/tokenizer' diff --git a/lib/rubygems/request_set/lockfile/tokenizer.rb b/lib/rubygems/request_set/lockfile/tokenizer.rb index 6918e8e1a5..cb8030c143 100644 --- a/lib/rubygems/request_set/lockfile/tokenizer.rb +++ b/lib/rubygems/request_set/lockfile/tokenizer.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/request_set/lockfile/parser' +require_relative 'parser' class Gem::RequestSet::Lockfile::Tokenizer Token = Struct.new :type, :value, :column, :line diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb index 6721de4055..d2e28fab5b 100644 --- a/lib/rubygems/requirement.rb +++ b/lib/rubygems/requirement.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require "rubygems/deprecate" +require_relative "deprecate" ## # A Requirement is a set of one or more version restrictions. It supports a @@ -194,24 +194,19 @@ class Gem::Requirement end def marshal_dump # :nodoc: - fix_syck_default_key_in_requirements - [@requirements] end def marshal_load(array) # :nodoc: @requirements = array[0] - fix_syck_default_key_in_requirements + raise TypeError, "wrong @requirements" unless Array === @requirements end def yaml_initialize(tag, vals) # :nodoc: vals.each do |ivar, val| instance_variable_set "@#{ivar}", val end - - Gem.load_yaml - fix_syck_default_key_in_requirements end def init_with(coder) # :nodoc: @@ -246,8 +241,7 @@ class Gem::Requirement def satisfied_by?(version) raise ArgumentError, "Need a Gem::Version: #{version.inspect}" unless Gem::Version === version - # #28965: syck has a bug with unquoted '=' YAML.loading as YAML::DefaultKey - requirements.all? {|op, rv| (OPS[op] || OPS["="]).call version, rv } + requirements.all? {|op, rv| OPS[op].call version, rv } end alias :=== :satisfied_by? @@ -289,19 +283,6 @@ class Gem::Requirement def _tilde_requirements @_tilde_requirements ||= _sorted_requirements.select {|r| r.first == "~>" } end - - private - - def fix_syck_default_key_in_requirements # :nodoc: - Gem.load_yaml - - # Fixup the Syck DefaultKey bug - @requirements.each do |r| - if r[0].kind_of? Gem::SyckDefaultKey - r[0] = "=" - end - end - end end class Gem::Version diff --git a/lib/rubygems/resolver.rb b/lib/rubygems/resolver.rb index 71c35ea3d3..51a11fed47 100644 --- a/lib/rubygems/resolver.rb +++ b/lib/rubygems/resolver.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'rubygems/dependency' -require 'rubygems/exceptions' -require 'rubygems/util/list' +require_relative 'dependency' +require_relative 'exceptions' +require_relative 'util/list' ## # Given a set of Gem::Dependency objects as +needed+ and a way to query the @@ -10,7 +10,7 @@ require 'rubygems/util/list' # all the requirements. class Gem::Resolver - require 'rubygems/resolver/molinillo' + require_relative 'resolver/molinillo' ## # If the DEBUG_RESOLVER environment variable is set then debugging mode is @@ -318,30 +318,30 @@ class Gem::Resolver private :amount_constrained end -require 'rubygems/resolver/activation_request' -require 'rubygems/resolver/conflict' -require 'rubygems/resolver/dependency_request' -require 'rubygems/resolver/requirement_list' -require 'rubygems/resolver/stats' - -require 'rubygems/resolver/set' -require 'rubygems/resolver/api_set' -require 'rubygems/resolver/composed_set' -require 'rubygems/resolver/best_set' -require 'rubygems/resolver/current_set' -require 'rubygems/resolver/git_set' -require 'rubygems/resolver/index_set' -require 'rubygems/resolver/installer_set' -require 'rubygems/resolver/lock_set' -require 'rubygems/resolver/vendor_set' -require 'rubygems/resolver/source_set' - -require 'rubygems/resolver/specification' -require 'rubygems/resolver/spec_specification' -require 'rubygems/resolver/api_specification' -require 'rubygems/resolver/git_specification' -require 'rubygems/resolver/index_specification' -require 'rubygems/resolver/installed_specification' -require 'rubygems/resolver/local_specification' -require 'rubygems/resolver/lock_specification' -require 'rubygems/resolver/vendor_specification' +require_relative 'resolver/activation_request' +require_relative 'resolver/conflict' +require_relative 'resolver/dependency_request' +require_relative 'resolver/requirement_list' +require_relative 'resolver/stats' + +require_relative 'resolver/set' +require_relative 'resolver/api_set' +require_relative 'resolver/composed_set' +require_relative 'resolver/best_set' +require_relative 'resolver/current_set' +require_relative 'resolver/git_set' +require_relative 'resolver/index_set' +require_relative 'resolver/installer_set' +require_relative 'resolver/lock_set' +require_relative 'resolver/vendor_set' +require_relative 'resolver/source_set' + +require_relative 'resolver/specification' +require_relative 'resolver/spec_specification' +require_relative 'resolver/api_specification' +require_relative 'resolver/git_specification' +require_relative 'resolver/index_specification' +require_relative 'resolver/installed_specification' +require_relative 'resolver/local_specification' +require_relative 'resolver/lock_specification' +require_relative 'resolver/vendor_specification' diff --git a/lib/rubygems/resolver/git_specification.rb b/lib/rubygems/resolver/git_specification.rb index 555dcffc22..ee47080ab4 100644 --- a/lib/rubygems/resolver/git_specification.rb +++ b/lib/rubygems/resolver/git_specification.rb @@ -21,7 +21,7 @@ class Gem::Resolver::GitSpecification < Gem::Resolver::SpecSpecification # the executables. def install(options = {}) - require 'rubygems/installer' + require_relative '../installer' installer = Gem::Installer.for_spec spec, options diff --git a/lib/rubygems/resolver/installer_set.rb b/lib/rubygems/resolver/installer_set.rb index 60181315b0..f4fee351a5 100644 --- a/lib/rubygems/resolver/installer_set.rb +++ b/lib/rubygems/resolver/installer_set.rb @@ -77,11 +77,11 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set newest = found.last unless @force - found_matching_metadata = found.select do |spec| + found_matching_metadata = found.reverse.find do |spec| metadata_satisfied?(spec) end - if found_matching_metadata.empty? + if found_matching_metadata.nil? if newest ensure_required_ruby_version_met(newest.spec) ensure_required_rubygems_version_met(newest.spec) @@ -92,7 +92,7 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set raise exc end else - newest = found_matching_metadata.last + newest = found_matching_metadata end end diff --git a/lib/rubygems/resolver/molinillo.rb b/lib/rubygems/resolver/molinillo.rb index 2357f41bee..12ca740e5a 100644 --- a/lib/rubygems/resolver/molinillo.rb +++ b/lib/rubygems/resolver/molinillo.rb @@ -1,2 +1,2 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo' +require_relative 'molinillo/lib/molinillo' diff --git a/lib/rubygems/resolver/molinillo/LICENSE b/lib/rubygems/resolver/molinillo/LICENSE new file mode 100644 index 0000000000..01feffa088 --- /dev/null +++ b/lib/rubygems/resolver/molinillo/LICENSE @@ -0,0 +1,9 @@ +This project is licensed under the MIT license. + +Copyright (c) 2014 Samuel E. Giddins segiddins@segiddins.me + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb index 16430a79f5..95f8416b96 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'tsort' +require_relative '../../../../tsort' require_relative 'dependency_graph/log' require_relative 'dependency_graph/vertex' @@ -17,7 +17,7 @@ module Gem::Resolver::Molinillo vertices.values.each { |v| yield v } end - include TSort + include Gem::TSort # @!visibility private alias tsort_each_node each diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb index 9448dc7bf3..1067bf7439 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Gem::Resolver::Molinillo - # Provides information about specifcations and dependencies to the resolver, + # Provides information about specifications and dependencies to the resolver, # allowing the {Resolver} class to remain generic while still providing power # and flexibility. # diff --git a/lib/rubygems/resolver/set.rb b/lib/rubygems/resolver/set.rb index 8046e18ea1..5d8dd51eaa 100644 --- a/lib/rubygems/resolver/set.rb +++ b/lib/rubygems/resolver/set.rb @@ -20,7 +20,6 @@ class Gem::Resolver::Set attr_accessor :prerelease def initialize # :nodoc: - require 'uri' @prerelease = false @remote = true @errors = [] diff --git a/lib/rubygems/resolver/specification.rb b/lib/rubygems/resolver/specification.rb index 8c6fc9afcf..dfcb7eb057 100644 --- a/lib/rubygems/resolver/specification.rb +++ b/lib/rubygems/resolver/specification.rb @@ -93,7 +93,7 @@ class Gem::Resolver::Specification # specification. def install(options = {}) - require 'rubygems/installer' + require_relative '../installer' gem = download options diff --git a/lib/rubygems/s3_uri_signer.rb b/lib/rubygems/s3_uri_signer.rb index f1f9229ca5..4d1deee997 100644 --- a/lib/rubygems/s3_uri_signer.rb +++ b/lib/rubygems/s3_uri_signer.rb @@ -1,6 +1,4 @@ -require 'base64' -require 'digest' -require 'rubygems/openssl' +require_relative 'openssl' ## # S3URISigner implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems @@ -88,7 +86,7 @@ class Gem::S3URISigner "AWS4-HMAC-SHA256", date_time, credential_info, - Digest::SHA256.hexdigest(canonical_request), + OpenSSL::Digest::SHA256.hexdigest(canonical_request), ].join("\n") end @@ -141,8 +139,8 @@ class Gem::S3URISigner def ec2_metadata_credentials_json require 'net/http' - require 'rubygems/request' - require 'rubygems/request/connection_pools' + require_relative 'request' + require_relative 'request/connection_pools' require 'json' iam_info = ec2_metadata_request(EC2_IAM_INFO) diff --git a/lib/rubygems/safe_yaml.rb b/lib/rubygems/safe_yaml.rb index 29312ad5a1..e905052e1c 100644 --- a/lib/rubygems/safe_yaml.rb +++ b/lib/rubygems/safe_yaml.rb @@ -17,8 +17,6 @@ module Gem Gem::Specification Gem::Version Gem::Version::Requirement - YAML::Syck::DefaultKey - Syck::DefaultKey ].freeze PERMITTED_SYMBOLS = %w[ diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb index c80639af6d..8240a1a059 100644 --- a/lib/rubygems/security.rb +++ b/lib/rubygems/security.rb @@ -5,7 +5,7 @@ # See LICENSE.txt for permissions. #++ -require 'rubygems/exceptions' +require_relative 'exceptions' require_relative 'openssl' ## @@ -152,6 +152,7 @@ require_relative 'openssl' # certificate for EMAIL_ADDR # -C, --certificate CERT Signing certificate for --sign # -K, --private-key KEY Key for --sign or --build +# -A, --key-algorithm ALGORITHM Select key algorithm for --build from RSA, DSA, or EC. Defaults to RSA. # -s, --sign CERT Signs CERT with the key from -K # and the certificate from -C # -d, --days NUMBER_OF_DAYS Days before the certificate expires @@ -317,7 +318,6 @@ require_relative 'openssl' # * Honor extension restrictions # * Might be better to store the certificate chain as a PKCS#7 or PKCS#12 # file, instead of an array embedded in the metadata. -# * Flexible signature and key algorithms, not hard-coded to RSA and SHA1. # # == Original author # @@ -337,17 +337,19 @@ module Gem::Security DIGEST_NAME = 'SHA256' # :nodoc: ## - # Algorithm for creating the key pair used to sign gems + # Length of keys created by RSA and DSA keys - KEY_ALGORITHM = - if defined?(OpenSSL::PKey::RSA) - OpenSSL::PKey::RSA - end + RSA_DSA_KEY_LENGTH = 3072 ## - # Length of keys created by KEY_ALGORITHM + # Default algorithm to use when building a key pair - KEY_LENGTH = 3072 + DEFAULT_KEY_ALGORITHM = 'RSA' + + ## + # Named curve used for Elliptic Curve + + EC_NAME = 'secp384r1' ## # Cipher used to encrypt the key pair used to sign gems. @@ -400,7 +402,7 @@ module Gem::Security serial = 1) cert = OpenSSL::X509::Certificate.new - cert.public_key = key.public_key + cert.public_key = get_public_key(key) cert.version = 2 cert.serial = serial @@ -419,6 +421,24 @@ module Gem::Security end ## + # Gets the right public key from a PKey instance + + def self.get_public_key(key) + return key.public_key unless key.is_a?(OpenSSL::PKey::EC) + + ec_key = OpenSSL::PKey::EC.new(key.group.curve_name) + ec_key.public_key = key.public_key + ec_key + end + + ## + # In Ruby 2.3 EC doesn't implement the private_key? but not the private? method + + if defined?(OpenSSL::PKey::EC) && Gem::Version.new(String.new(RUBY_VERSION)) < Gem::Version.new("2.4.0") + OpenSSL::PKey::EC.send(:alias_method, :private?, :private_key?) + end + + ## # Creates a self-signed certificate with an issuer and subject from +email+, # a subject alternative name of +email+ and the given +extensions+ for the # +key+. @@ -459,11 +479,25 @@ module Gem::Security end ## - # Creates a new key pair of the specified +length+ and +algorithm+. The - # default is a 3072 bit RSA key. - - def self.create_key(length = KEY_LENGTH, algorithm = KEY_ALGORITHM) - algorithm.new length + # Creates a new key pair of the specified +algorithm+. RSA, DSA, and EC + # are supported. + + def self.create_key(algorithm) + if defined?(OpenSSL::PKey) + case algorithm.downcase + when 'dsa' + OpenSSL::PKey::DSA.new(RSA_DSA_KEY_LENGTH) + when 'rsa' + OpenSSL::PKey::RSA.new(RSA_DSA_KEY_LENGTH) + when 'ec' + domain_key = OpenSSL::PKey::EC.new(EC_NAME) + domain_key.generate_key + domain_key + else + raise Gem::Security::Exception, + "#{algorithm} algorithm not found. RSA, DSA, and EC algorithms are supported." + end + end end ## @@ -492,7 +526,7 @@ module Gem::Security raise Gem::Security::Exception, "incorrect signing key for re-signing " + "#{expired_certificate.subject}" unless - expired_certificate.public_key.to_pem == private_key.public_key.to_pem + expired_certificate.public_key.to_pem == get_public_key(private_key).to_pem unless expired_certificate.subject.to_s == expired_certificate.issuer.to_s @@ -592,9 +626,9 @@ module Gem::Security end if Gem::HAVE_OPENSSL - require 'rubygems/security/policy' - require 'rubygems/security/policies' - require 'rubygems/security/trust_dir' + require_relative 'security/policy' + require_relative 'security/policies' + require_relative 'security/trust_dir' end -require 'rubygems/security/signer' +require_relative 'security/signer' diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb index 7629d64796..3c3cb647ee 100644 --- a/lib/rubygems/security/policy.rb +++ b/lib/rubygems/security/policy.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/user_interaction' +require_relative '../user_interaction' ## # A Gem::Security::Policy object encapsulates the settings for verifying @@ -115,9 +115,11 @@ class Gem::Security::Policy raise Gem::Security::Exception, 'missing key or signature' end + public_key = Gem::Security.get_public_key(key) + raise Gem::Security::Exception, "certificate #{signer.subject} does not match the signing key" unless - signer.public_key.to_pem == key.public_key.to_pem + signer.public_key.to_pem == public_key.to_pem true end @@ -164,9 +166,9 @@ class Gem::Security::Policy end save_cert = OpenSSL::X509::Certificate.new File.read path - save_dgst = digester.digest save_cert.public_key.to_s + save_dgst = digester.digest save_cert.public_key.to_pem - pkey_str = root.public_key.to_s + pkey_str = root.public_key.to_pem cert_dgst = digester.digest pkey_str raise Gem::Security::Exception, diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb index 6c85ab08d2..968cf88973 100644 --- a/lib/rubygems/security/signer.rb +++ b/lib/rubygems/security/signer.rb @@ -2,7 +2,7 @@ ## # Basic OpenSSL-based package signing class. -require "rubygems/user_interaction" +require_relative "../user_interaction" class Gem::Security::Signer include Gem::UserInteraction @@ -83,8 +83,8 @@ class Gem::Security::Signer @digest_name = Gem::Security::DIGEST_NAME @digest_algorithm = Gem::Security.create_digest(@digest_name) - if @key && !@key.is_a?(OpenSSL::PKey::RSA) - @key = OpenSSL::PKey::RSA.new(File.read(@key), @passphrase) + if @key && !@key.is_a?(OpenSSL::PKey::PKey) + @key = OpenSSL::PKey.read(File.read(@key), @passphrase) end if @cert_chain @@ -177,8 +177,7 @@ class Gem::Security::Signer disk_cert = File.read(disk_cert_path) rescue nil disk_key_path = File.join(Gem.default_key_path) - disk_key = - OpenSSL::PKey::RSA.new(File.read(disk_key_path), @passphrase) rescue nil + disk_key = OpenSSL::PKey.read(File.read(disk_key_path), @passphrase) rescue nil return unless disk_key diff --git a/lib/rubygems/security/trust_dir.rb b/lib/rubygems/security/trust_dir.rb index 1d93ceabd1..456947274c 100644 --- a/lib/rubygems/security/trust_dir.rb +++ b/lib/rubygems/security/trust_dir.rb @@ -104,6 +104,7 @@ class Gem::Security::TrustDir # permissions. def verify + require 'fileutils' if File.exist? @dir raise Gem::Security::Exception, "trust directory #{@dir} is not a directory" unless diff --git a/lib/rubygems/security_option.rb b/lib/rubygems/security_option.rb index 3403aaaf05..a4c570ded5 100644 --- a/lib/rubygems/security_option.rb +++ b/lib/rubygems/security_option.rb @@ -5,7 +5,7 @@ # See LICENSE.txt for permissions. #++ -require 'rubygems' +require_relative '../rubygems' # forward-declare @@ -19,16 +19,16 @@ end module Gem::SecurityOption def add_security_option - OptionParser.accept Gem::Security::Policy do |value| - require 'rubygems/security' + Gem::OptionParser.accept Gem::Security::Policy do |value| + require_relative 'security' - raise OptionParser::InvalidArgument, 'OpenSSL not installed' unless + raise Gem::OptionParser::InvalidArgument, 'OpenSSL not installed' unless defined?(Gem::Security::HighSecurity) policy = Gem::Security::Policies[value] unless policy valid = Gem::Security::Policies.keys.sort - raise OptionParser::InvalidArgument, "#{value} (#{valid.join ', '} are valid)" + raise Gem::OptionParser::InvalidArgument, "#{value} (#{valid.join ', '} are valid)" end policy end diff --git a/lib/rubygems/server.rb b/lib/rubygems/server.rb index 2c2805f31c..45be05bcda 100644 --- a/lib/rubygems/server.rb +++ b/lib/rubygems/server.rb @@ -3,8 +3,8 @@ require 'zlib' require 'erb' require 'uri' -require 'rubygems' -require 'rubygems/rdoc' +require_relative '../rubygems' +require_relative 'rdoc' ## # Gem::Server and allows users to serve gems for consumption by diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb index 4ae84cf532..b5bd6b80e6 100644 --- a/lib/rubygems/source.rb +++ b/lib/rubygems/source.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "rubygems/text" +require_relative "text" ## # A Source knows how to list and fetch gems from a RubyGems marshal index. # @@ -238,9 +238,9 @@ class Gem::Source end end -require 'rubygems/source/git' -require 'rubygems/source/installed' -require 'rubygems/source/specific_file' -require 'rubygems/source/local' -require 'rubygems/source/lock' -require 'rubygems/source/vendor' +require_relative 'source/git' +require_relative 'source/installed' +require_relative 'source/specific_file' +require_relative 'source/local' +require_relative 'source/lock' +require_relative 'source/vendor' diff --git a/lib/rubygems/source/git.rb b/lib/rubygems/source/git.rb index 9876adc24e..cda5aa8073 100644 --- a/lib/rubygems/source/git.rb +++ b/lib/rubygems/source/git.rb @@ -225,7 +225,7 @@ class Gem::Source::Git < Gem::Source # A hash for the git gem based on the git repository URI. def uri_hash # :nodoc: - require 'digest' # required here to avoid deadlocking in Gem.activate_bin_path (because digest is a gem on 2.5+) + require_relative '../openssl' normalized = if @repository =~ %r{^\w+://(\w+@)?} @@ -235,6 +235,6 @@ class Gem::Source::Git < Gem::Source @repository end - Digest::SHA1.hexdigest normalized + OpenSSL::Digest::SHA1.hexdigest normalized end end diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb index b2bcadc49c..68ff605a83 100644 --- a/lib/rubygems/spec_fetcher.rb +++ b/lib/rubygems/spec_fetcher.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require 'rubygems/remote_fetcher' -require 'rubygems/user_interaction' -require 'rubygems/errors' -require 'rubygems/text' -require 'rubygems/name_tuple' +require_relative 'remote_fetcher' +require_relative 'user_interaction' +require_relative 'errors' +require_relative 'text' +require_relative 'name_tuple' ## # SpecFetcher handles metadata updates from remote gem repositories. diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index 73062afe53..b493a47ef0 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -6,11 +6,11 @@ # See LICENSE.txt for permissions. #++ -require 'rubygems/deprecate' -require 'rubygems/basic_specification' -require 'rubygems/stub_specification' -require 'rubygems/specification_policy' -require 'rubygems/util/list' +require_relative 'deprecate' +require_relative 'basic_specification' +require_relative 'stub_specification' +require_relative 'specification_policy' +require_relative 'util/list' ## # The Specification class contains the information for a gem. Typically @@ -102,12 +102,8 @@ class Gem::Specification < Gem::BasicSpecification today = Time.now.utc TODAY = Time.utc(today.year, today.month, today.day) # :nodoc: - # rubocop:disable Style/MutableConstant - LOAD_CACHE = {} # :nodoc: - # rubocop:enable Style/MutableConstant - LOAD_CACHE_MUTEX = Mutex.new - - private_constant :LOAD_CACHE if defined? private_constant + @load_cache = {} # :nodoc: + @load_cache_mutex = Thread::Mutex.new VALID_NAME_PATTERN = /\A[a-zA-Z0-9\.\-\_]+\z/.freeze # :nodoc: @@ -182,13 +178,23 @@ class Gem::Specification < Gem::BasicSpecification @@default_value[k].nil? end - @@stubs = nil - @@stubs_by_name = {} + def self.clear_specs # :nodoc: + @@all_specs_mutex.synchronize do + @@all = nil + @@stubs = nil + @@stubs_by_name = {} + @@spec_with_requirable_file = {} + @@active_stub_with_requirable_file = {} + end + end + private_class_method :clear_specs + + @@all_specs_mutex = Thread::Mutex.new + + clear_specs # Sentinel object to represent "not found" stubs NOT_FOUND = Struct.new(:to_spec, :this).new # :nodoc: - @@spec_with_requirable_file = {} - @@active_stub_with_requirable_file = {} # Tracking removed method calls to warn users during build time. REMOVED_METHODS = [:rubyforge_project=].freeze # :nodoc: @@ -282,6 +288,15 @@ class Gem::Specification < Gem::BasicSpecification # :section: Recommended gemspec attributes ## + # The version of Ruby required by this gem + # + # Usage: + # + # spec.required_ruby_version = '>= 2.7.0' + + attr_reader :required_ruby_version + + ## # A long description of this gem # # The description should be more detailed than the summary but not @@ -324,17 +339,21 @@ class Gem::Specification < Gem::BasicSpecification # This should just be the name of your license. The full text of the license # should be inside of the gem (at the top level) when you build it. # - # The simplest way, is to specify the standard SPDX ID + # The simplest way is to specify the standard SPDX ID # https://spdx.org/licenses/ for the license. - # Ideally you should pick one that is OSI (Open Source Initiative) + # Ideally, you should pick one that is OSI (Open Source Initiative) # http://opensource.org/licenses/alphabetical approved. # - # The most commonly used OSI approved licenses are MIT and Apache-2.0. + # The most commonly used OSI-approved licenses are MIT and Apache-2.0. # GitHub also provides a license picker at http://choosealicense.com/. # + # You can also use a custom license file along with your gemspec and specify + # a LicenseRef-<idstring>, where idstring is the name of the file containing + # the license text. + # # You should specify a license for your gem so that people know how they are - # permitted to use it, and any restrictions you're placing on it. Not - # specifying a license means all rights are reserved; others have no rights + # permitted to use it and any restrictions you're placing on it. Not + # specifying a license means all rights are reserved; others have no right # to use the code for any purpose. # # You can set multiple licenses with #licenses= @@ -513,11 +532,6 @@ class Gem::Specification < Gem::BasicSpecification end ## - # The version of Ruby required by this gem - - attr_reader :required_ruby_version - - ## # The RubyGems version required by this gem attr_reader :required_rubygems_version @@ -666,6 +680,9 @@ class Gem::Specification < Gem::BasicSpecification # # # Only prereleases or final releases after 2.6.0.preview2 # spec.required_ruby_version = '> 2.6.0.preview2' + # + # # This gem will work with 2.3.0 or greater, including major version 3, but lesser than 4.0.0 + # spec.required_ruby_version = '>= 2.3', '< 4' def required_ruby_version=(req) @required_ruby_version = Gem::Requirement.create req @@ -741,23 +758,15 @@ class Gem::Specification < Gem::BasicSpecification attr_accessor :specification_version def self._all # :nodoc: - unless defined?(@@all) && @@all - @@all = stubs.map(&:to_spec) - - # After a reset, make sure already loaded specs - # are still marked as activated. - specs = {} - Gem.loaded_specs.each_value{|s| specs[s] = true } - @@all.each{|s| s.activated = true if specs[s] } - end - @@all + @@all_specs_mutex.synchronize { @@all ||= Gem.loaded_specs.values | stubs.map(&:to_spec) } end - def self._clear_load_cache # :nodoc: - LOAD_CACHE_MUTEX.synchronize do - LOAD_CACHE.clear + def self.clear_load_cache # :nodoc: + @load_cache_mutex.synchronize do + @load_cache.clear end end + private_class_method :clear_load_cache def self.each_gemspec(dirs) # :nodoc: dirs.each do |dir| @@ -1105,7 +1114,7 @@ class Gem::Specification < Gem::BasicSpecification def self.load(file) return unless file - _spec = LOAD_CACHE_MUTEX.synchronize { LOAD_CACHE[file] } + _spec = @load_cache_mutex.synchronize { @load_cache[file] } return _spec if _spec file = file.dup.tap(&Gem::UNTAINT) @@ -1120,12 +1129,12 @@ class Gem::Specification < Gem::BasicSpecification if Gem::Specification === _spec _spec.loaded_from = File.expand_path file.to_s - LOAD_CACHE_MUTEX.synchronize do - prev = LOAD_CACHE[file] + @load_cache_mutex.synchronize do + prev = @load_cache[file] if prev _spec = prev else - LOAD_CACHE[file] = _spec + @load_cache[file] = _spec end end return _spec @@ -1223,12 +1232,8 @@ class Gem::Specification < Gem::BasicSpecification def self.reset @@dirs = nil Gem.pre_reset_hooks.each {|hook| hook.call } - @@all = nil - @@stubs = nil - @@stubs_by_name = {} - @@spec_with_requirable_file = {} - @@active_stub_with_requirable_file = {} - _clear_load_cache + clear_specs + clear_load_cache unresolved = unresolved_deps unless unresolved.empty? w = "W" + "ARN" @@ -1553,9 +1558,8 @@ class Gem::Specification < Gem::BasicSpecification # the gem.build_complete file is missing. def build_extensions # :nodoc: - return if default_gem? return if extensions.empty? - return if installed_by_version < Gem::Version.new('2.2.0.preview.2') + return if default_gem? return if File.exist? gem_build_complete_path return if !File.writable?(base_dir) return if !File.exist?(File.join(base_dir, 'extensions')) @@ -1566,9 +1570,9 @@ class Gem::Specification < Gem::BasicSpecification unresolved_deps = Gem::Specification.unresolved_deps.dup Gem::Specification.unresolved_deps.clear - require 'rubygems/config_file' - require 'rubygems/ext' - require 'rubygems/user_interaction' + require_relative 'config_file' + require_relative 'ext' + require_relative 'user_interaction' ui = Gem::SilentUI.new Gem::DefaultUserInteraction.use_ui ui do @@ -1688,12 +1692,6 @@ class Gem::Specification < Gem::BasicSpecification when String then if DateTimeFormat =~ date Time.utc($1.to_i, $2.to_i, $3.to_i) - - # Workaround for where the date format output from psych isn't - # parsed as a Time object by syck and thus comes through as a - # string. - elsif /\A(\d{4})-(\d{2})-(\d{2}) \d{2}:\d{2}:\d{2}\.\d+?Z\z/ =~ date - Time.utc($1.to_i, $2.to_i, $3.to_i) else raise(Gem::InvalidSpecificationException, "invalid date format in specification: #{date.inspect}") @@ -2124,9 +2122,8 @@ class Gem::Specification < Gem::BasicSpecification # probably want to build_extensions def missing_extensions? - return false if default_gem? return false if extensions.empty? - return false if installed_by_version < Gem::Version.new('2.2.0.preview.2') + return false if default_gem? return false if File.exist? gem_build_complete_path true @@ -2420,7 +2417,6 @@ class Gem::Specification < Gem::BasicSpecification # still have their default values are omitted. def to_ruby - require_relative 'openssl' mark_version result = [] result << "# -*- encoding: utf-8 -*-" @@ -2454,16 +2450,21 @@ class Gem::Specification < Gem::BasicSpecification :has_rdoc, :default_executable, :metadata, + :signing_key, ] @@attributes.each do |attr_name| next if handled.include? attr_name current_value = self.send(attr_name) if current_value != default_value(attr_name) || self.class.required_attribute?(attr_name) - result << " s.#{attr_name} = #{ruby_code current_value}" unless defined?(OpenSSL::PKey::RSA) && current_value.is_a?(OpenSSL::PKey::RSA) + result << " s.#{attr_name} = #{ruby_code current_value}" end end + if String === signing_key + result << " s.signing_key = #{signing_key.dump}.freeze" + end + if @installed_by_version result << nil result << " s.installed_by_version = \"#{Gem::VERSION}\" if s.respond_to? :installed_by_version" @@ -2526,7 +2527,7 @@ class Gem::Specification < Gem::BasicSpecification # back, we have to check again here to make sure that our # psych code was properly loaded, and load it if not. unless Gem.const_defined?(:NoAliasYAMLTree) - require 'rubygems/psych_tree' + require_relative 'psych_tree' end builder = Gem::NoAliasYAMLTree.create diff --git a/lib/rubygems/specification_policy.rb b/lib/rubygems/specification_policy.rb index 2b8b05635e..73bd31970c 100644 --- a/lib/rubygems/specification_policy.rb +++ b/lib/rubygems/specification_policy.rb @@ -1,4 +1,4 @@ -require 'rubygems/user_interaction' +require_relative 'user_interaction' class Gem::SpecificationPolicy include Gem::UserInteraction @@ -124,25 +124,26 @@ class Gem::SpecificationPolicy end metadata.each do |key, value| + entry = "metadata['#{key}']" if !key.kind_of?(String) error "metadata keys must be a String" end if key.size > 128 - error "metadata key too large (#{key.size} > 128)" + error "metadata key is too large (#{key.size} > 128)" end if !value.kind_of?(String) - error "metadata values must be a String" + error "#{entry} value must be a String" end if value.size > 1024 - error "metadata value too large (#{value.size} > 1024)" + error "#{entry} value is too large (#{value.size} > 1024)" end if METADATA_LINK_KEYS.include? key if value !~ VALID_URI_PATTERN - error "metadata['#{key}'] has invalid link: #{value.inspect}" + error "#{entry} has invalid link: #{value.inspect}" end end end @@ -380,7 +381,7 @@ http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard li end LAZY = '"FIxxxXME" or "TOxxxDO"'.gsub(/xxx/, '') - LAZY_PATTERN = /FI XME|TO DO/x.freeze + LAZY_PATTERN = /\AFI XME|\ATO DO/x.freeze HOMEPAGE_URI_PATTERN = /\A[a-z][a-z\d+.-]*:/i.freeze def validate_lazy_metadata diff --git a/lib/rubygems/syck_hack.rb b/lib/rubygems/syck_hack.rb deleted file mode 100644 index 051483eac8..0000000000 --- a/lib/rubygems/syck_hack.rb +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true -# :stopdoc: - -# Hack to handle syck's DefaultKey bug -# -# This file is always loaded AFTER either syck or psych are already -# loaded. It then looks at what constants are available and creates -# a consistent view on all rubys. -# -# All this is so that there is always a YAML::Syck::DefaultKey -# class no matter if the full yaml library has loaded or not. -# - -module YAML # :nodoc: - # In newer 1.9.2, there is a Syck toplevel constant instead of it - # being underneath YAML. If so, reference it back under YAML as - # well. - if defined? ::Syck - # for tests that change YAML::ENGINE - # 1.8 does not support the second argument to const_defined? - remove_const :Syck rescue nil - - Syck = ::Syck - - # JRuby's "Syck" is called "Yecht" - elsif defined? YAML::Yecht - Syck = YAML::Yecht - - # Otherwise, if there is no YAML::Syck, then we've got just psych - # loaded, so lets define a stub for DefaultKey. - elsif !defined? YAML::Syck - module Syck - class DefaultKey # :nodoc: - end - end - end - - # Now that we've got something that is always here, define #to_s - # so when code tries to use this, it at least just shows up like it - # should. - module Syck - class DefaultKey - remove_method :to_s rescue nil - - def to_s - '=' - end - end - end - - SyntaxError = Error unless defined? SyntaxError -end - -# Sometime in the 1.9 dev cycle, the Syck constant was moved from under YAML -# to be a toplevel constant. So gemspecs created under these versions of Syck -# will have references to Syck::DefaultKey. -# -# So we need to be sure that we reference Syck at the toplevel too so that -# we can always load these kind of gemspecs. -# -if !defined?(Syck) - Syck = YAML::Syck -end - -# Now that we've got Syck setup in all the right places, store -# a reference to the DefaultKey class inside Gem. We do this so that -# if later on YAML, etc are redefined, we've still got a consistent -# place to find the DefaultKey class for comparison. - -module Gem - # for tests that change YAML::ENGINE - remove_const :SyckDefaultKey if const_defined? :SyckDefaultKey - - SyckDefaultKey = YAML::Syck::DefaultKey -end - -# :startdoc: diff --git a/lib/rubygems/tsort.rb b/lib/rubygems/tsort.rb new file mode 100644 index 0000000000..ebe7c3364b --- /dev/null +++ b/lib/rubygems/tsort.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require_relative 'tsort/lib/tsort' diff --git a/lib/rubygems/tsort/.document b/lib/rubygems/tsort/.document new file mode 100644 index 0000000000..0c43bbd6b3 --- /dev/null +++ b/lib/rubygems/tsort/.document @@ -0,0 +1 @@ +# Vendored files do not need to be documented diff --git a/lib/rubygems/tsort/LICENSE.txt b/lib/rubygems/tsort/LICENSE.txt new file mode 100644 index 0000000000..a009caefea --- /dev/null +++ b/lib/rubygems/tsort/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/lib/rubygems/tsort/lib/tsort.rb b/lib/rubygems/tsort/lib/tsort.rb new file mode 100644 index 0000000000..f68c5947d3 --- /dev/null +++ b/lib/rubygems/tsort/lib/tsort.rb @@ -0,0 +1,454 @@ +# frozen_string_literal: true + +#-- +# tsort.rb - provides a module for topological sorting and strongly connected components. +#++ +# + +# +# Gem::TSort implements topological sorting using Tarjan's algorithm for +# strongly connected components. +# +# Gem::TSort is designed to be able to be used with any object which can be +# interpreted as a directed graph. +# +# Gem::TSort requires two methods to interpret an object as a graph, +# tsort_each_node and tsort_each_child. +# +# * tsort_each_node is used to iterate for all nodes over a graph. +# * tsort_each_child is used to iterate for child nodes of a given node. +# +# The equality of nodes are defined by eql? and hash since +# Gem::TSort uses Hash internally. +# +# == A Simple Example +# +# The following example demonstrates how to mix the Gem::TSort module into an +# existing class (in this case, Hash). Here, we're treating each key in +# the hash as a node in the graph, and so we simply alias the required +# #tsort_each_node method to Hash's #each_key method. For each key in the +# hash, the associated value is an array of the node's child nodes. This +# choice in turn leads to our implementation of the required #tsort_each_child +# method, which fetches the array of child nodes and then iterates over that +# array using the user-supplied block. +# +# require 'rubygems/tsort/lib/tsort' +# +# class Hash +# include Gem::TSort +# alias tsort_each_node each_key +# def tsort_each_child(node, &block) +# fetch(node).each(&block) +# end +# end +# +# {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort +# #=> [3, 2, 1, 4] +# +# {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components +# #=> [[4], [2, 3], [1]] +# +# == A More Realistic Example +# +# A very simple `make' like tool can be implemented as follows: +# +# require 'rubygems/tsort/lib/tsort' +# +# class Make +# def initialize +# @dep = {} +# @dep.default = [] +# end +# +# def rule(outputs, inputs=[], &block) +# triple = [outputs, inputs, block] +# outputs.each {|f| @dep[f] = [triple]} +# @dep[triple] = inputs +# end +# +# def build(target) +# each_strongly_connected_component_from(target) {|ns| +# if ns.length != 1 +# fs = ns.delete_if {|n| Array === n} +# raise Gem::TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}") +# end +# n = ns.first +# if Array === n +# outputs, inputs, block = n +# inputs_time = inputs.map {|f| File.mtime f}.max +# begin +# outputs_time = outputs.map {|f| File.mtime f}.min +# rescue Errno::ENOENT +# outputs_time = nil +# end +# if outputs_time == nil || +# inputs_time != nil && outputs_time <= inputs_time +# sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i +# block.call +# end +# end +# } +# end +# +# def tsort_each_child(node, &block) +# @dep[node].each(&block) +# end +# include Gem::TSort +# end +# +# def command(arg) +# print arg, "\n" +# system arg +# end +# +# m = Make.new +# m.rule(%w[t1]) { command 'date > t1' } +# m.rule(%w[t2]) { command 'date > t2' } +# m.rule(%w[t3]) { command 'date > t3' } +# m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' } +# m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' } +# m.build('t5') +# +# == Bugs +# +# * 'tsort.rb' is wrong name because this library uses +# Tarjan's algorithm for strongly connected components. +# Although 'strongly_connected_components.rb' is correct but too long. +# +# == References +# +# R. E. Tarjan, "Depth First Search and Linear Graph Algorithms", +# <em>SIAM Journal on Computing</em>, Vol. 1, No. 2, pp. 146-160, June 1972. +# + +module Gem + module TSort + class Cyclic < StandardError + end + + # Returns a topologically sorted array of nodes. + # The array is sorted from children to parents, i.e. + # the first element has no child and the last node has no parent. + # + # If there is a cycle, Gem::TSort::Cyclic is raised. + # + # class G + # include Gem::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # p graph.tsort #=> [4, 2, 3, 1] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # p graph.tsort # raises Gem::TSort::Cyclic + # + def tsort + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + Gem::TSort.tsort(each_node, each_child) + end + + # Returns a topologically sorted array of nodes. + # The array is sorted from children to parents, i.e. + # the first element has no child and the last node has no parent. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # If there is a cycle, Gem::TSort::Cyclic is raised. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p Gem::TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1] + # + # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p Gem::TSort.tsort(each_node, each_child) # raises Gem::TSort::Cyclic + # + def TSort.tsort(each_node, each_child) + Gem::TSort.tsort_each(each_node, each_child).to_a + end + + # The iterator version of the #tsort method. + # <tt><em>obj</em>.tsort_each</tt> is similar to <tt><em>obj</em>.tsort.each</tt>, but + # modification of _obj_ during the iteration may lead to unexpected results. + # + # #tsort_each returns +nil+. + # If there is a cycle, Gem::TSort::Cyclic is raised. + # + # class G + # include Gem::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # graph.tsort_each {|n| p n } + # #=> 4 + # # 2 + # # 3 + # # 1 + # + def tsort_each(&block) # :yields: node + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + Gem::TSort.tsort_each(each_node, each_child, &block) + end + + # The iterator version of the Gem::TSort.tsort method. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # Gem::TSort.tsort_each(each_node, each_child) {|n| p n } + # #=> 4 + # # 2 + # # 3 + # # 1 + # + def TSort.tsort_each(each_node, each_child) # :yields: node + return to_enum(__method__, each_node, each_child) unless block_given? + + Gem::TSort.each_strongly_connected_component(each_node, each_child) {|component| + if component.size == 1 + yield component.first + else + raise Cyclic.new("topological sort failed: #{component.inspect}") + end + } + end + + # Returns strongly connected components as an array of arrays of nodes. + # The array is sorted from children to parents. + # Each elements of the array represents a strongly connected component. + # + # class G + # include Gem::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # p graph.strongly_connected_components #=> [[4], [2], [3], [1]] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # p graph.strongly_connected_components #=> [[4], [2, 3], [1]] + # + def strongly_connected_components + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + Gem::TSort.strongly_connected_components(each_node, each_child) + end + + # Returns strongly connected components as an array of arrays of nodes. + # The array is sorted from children to parents. + # Each elements of the array represents a strongly connected component. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p Gem::TSort.strongly_connected_components(each_node, each_child) + # #=> [[4], [2], [3], [1]] + # + # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p Gem::TSort.strongly_connected_components(each_node, each_child) + # #=> [[4], [2, 3], [1]] + # + def TSort.strongly_connected_components(each_node, each_child) + Gem::TSort.each_strongly_connected_component(each_node, each_child).to_a + end + + # The iterator version of the #strongly_connected_components method. + # <tt><em>obj</em>.each_strongly_connected_component</tt> is similar to + # <tt><em>obj</em>.strongly_connected_components.each</tt>, but + # modification of _obj_ during the iteration may lead to unexpected results. + # + # #each_strongly_connected_component returns +nil+. + # + # class G + # include Gem::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # graph.each_strongly_connected_component {|scc| p scc } + # #=> [4] + # # [2] + # # [3] + # # [1] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # graph.each_strongly_connected_component {|scc| p scc } + # #=> [4] + # # [2, 3] + # # [1] + # + def each_strongly_connected_component(&block) # :yields: nodes + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + Gem::TSort.each_strongly_connected_component(each_node, each_child, &block) + end + + # The iterator version of the Gem::TSort.strongly_connected_components method. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # Gem::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } + # #=> [4] + # # [2] + # # [3] + # # [1] + # + # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # Gem::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } + # #=> [4] + # # [2, 3] + # # [1] + # + def TSort.each_strongly_connected_component(each_node, each_child) # :yields: nodes + return to_enum(__method__, each_node, each_child) unless block_given? + + id_map = {} + stack = [] + each_node.call {|node| + unless id_map.include? node + Gem::TSort.each_strongly_connected_component_from(node, each_child, id_map, stack) {|c| + yield c + } + end + } + nil + end + + # Iterates over strongly connected component in the subgraph reachable from + # _node_. + # + # Return value is unspecified. + # + # #each_strongly_connected_component_from doesn't call #tsort_each_node. + # + # class G + # include Gem::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # graph.each_strongly_connected_component_from(2) {|scc| p scc } + # #=> [4] + # # [2] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # graph.each_strongly_connected_component_from(2) {|scc| p scc } + # #=> [4] + # # [2, 3] + # + def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes + Gem::TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block) + end + + # Iterates over strongly connected components in a graph. + # The graph is represented by _node_ and _each_child_. + # + # _node_ is the first node. + # _each_child_ should have +call+ method which takes a node argument + # and yields for each child node. + # + # Return value is unspecified. + # + # #Gem::TSort.each_strongly_connected_component_from is a class method and + # it doesn't need a class to represent a graph which includes Gem::TSort. + # + # graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_child = lambda {|n, &b| graph[n].each(&b) } + # Gem::TSort.each_strongly_connected_component_from(1, each_child) {|scc| + # p scc + # } + # #=> [4] + # # [2, 3] + # # [1] + # + def TSort.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes + return to_enum(__method__, node, each_child, id_map, stack) unless block_given? + + minimum_id = node_id = id_map[node] = id_map.size + stack_length = stack.length + stack << node + + each_child.call(node) {|child| + if id_map.include? child + child_id = id_map[child] + minimum_id = child_id if child_id && child_id < minimum_id + else + sub_minimum_id = + Gem::TSort.each_strongly_connected_component_from(child, each_child, id_map, stack) {|c| + yield c + } + minimum_id = sub_minimum_id if sub_minimum_id < minimum_id + end + } + + if node_id == minimum_id + component = stack.slice!(stack_length .. -1) + component.each {|n| id_map[n] = nil} + yield component + end + + minimum_id + end + + # Should be implemented by a extended class. + # + # #tsort_each_node is used to iterate for all nodes over a graph. + # + def tsort_each_node # :yields: node + raise NotImplementedError.new + end + + # Should be implemented by a extended class. + # + # #tsort_each_child is used to iterate for child nodes of _node_. + # + def tsort_each_child(node) # :yields: child + raise NotImplementedError.new + end + end +end diff --git a/lib/rubygems/uninstaller.rb b/lib/rubygems/uninstaller.rb index 51ac3494f3..1c3bf260c2 100644 --- a/lib/rubygems/uninstaller.rb +++ b/lib/rubygems/uninstaller.rb @@ -6,11 +6,11 @@ #++ require 'fileutils' -require 'rubygems' -require 'rubygems/installer_uninstaller_utils' -require 'rubygems/dependency_list' -require 'rubygems/rdoc' -require 'rubygems/user_interaction' +require_relative '../rubygems' +require_relative 'installer_uninstaller_utils' +require_relative 'dependency_list' +require_relative 'rdoc' +require_relative 'user_interaction' ## # An Uninstaller. @@ -70,6 +70,9 @@ class Gem::Uninstaller # only add user directory if install_dir is not set @user_install = false @user_install = options[:user_install] unless options[:install_dir] + + # Optimization: populated during #uninstall + @default_specs_matching_uninstall_params = [] end ## @@ -98,10 +101,8 @@ class Gem::Uninstaller default_specs, list = list.partition do |spec| spec.default_gem? end - - default_specs.each do |default_spec| - say "Gem #{default_spec.full_name} cannot be uninstalled because it is a default gem" - end + warn_cannot_uninstall_default_gems(default_specs - list) + @default_specs_matching_uninstall_params = default_specs list, other_repo_specs = list.partition do |spec| @gem_home == spec.base_dir or @@ -270,7 +271,7 @@ class Gem::Uninstaller end safe_delete { FileUtils.rm_r gemspec } - say "Successfully uninstalled #{spec.full_name}" + announce_deletion_of(spec) Gem::Specification.reset end @@ -356,7 +357,7 @@ class Gem::Uninstaller # of what it did for us to find rather than trying to recreate # it again. if @format_executable - require 'rubygems/installer' + require_relative 'installer' Gem::Installer.exec_format % File.basename(filename) else filename @@ -373,4 +374,34 @@ class Gem::Uninstaller raise e end + + private + + def announce_deletion_of(spec) + name = spec.full_name + say "Successfully uninstalled #{name}" + if default_spec_matches?(spec) + say( + "There was both a regular copy and a default copy of #{name}. The " \ + "regular copy was successfully uninstalled, but the default copy " \ + "was left around because default gems can't be removed." + ) + end + end + + # @return true if the specs of any default gems are `==` to the given `spec`. + def default_spec_matches?(spec) + !default_specs_that_match(spec).empty? + end + + # @return [Array] specs of default gems that are `==` to the given `spec`. + def default_specs_that_match(spec) + @default_specs_matching_uninstall_params.select {|default_spec| spec == default_spec } + end + + def warn_cannot_uninstall_default_gems(specs) + specs.each do |spec| + say "Gem #{spec.full_name} cannot be uninstalled because it is a default gem" + end + end end diff --git a/lib/rubygems/uri.rb b/lib/rubygems/uri.rb new file mode 100644 index 0000000000..ba30fac2f5 --- /dev/null +++ b/lib/rubygems/uri.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +## +# The Uri handles rubygems source URIs. +# + +class Gem::Uri + def initialize(source_uri) + @parsed_uri = parse(source_uri) + end + + def redacted + return self unless valid_uri? + + if token? || oauth_basic? + with_redacted_user + elsif password? + with_redacted_password + else + self + end + end + + def to_s + @parsed_uri.to_s + end + + def redact_credentials_from(text) + return text unless valid_uri? && password? + + text.sub(password, 'REDACTED') + end + + def method_missing(method_name, *args, &blk) + if @parsed_uri.respond_to?(method_name) + @parsed_uri.send(method_name, *args, &blk) + else + super + end + end + + def respond_to_missing?(method_name, include_private = false) + @parsed_uri.respond_to?(method_name, include_private) || super + end + + protected + + # Add a protected reader for the cloned instance to access the original object's parsed uri + attr_reader :parsed_uri + + private + + ## + # Parses the #uri, raising if it's invalid + + def parse!(uri) + require "uri" + + raise URI::InvalidURIError unless uri + + # Always escape URI's to deal with potential spaces and such + # It should also be considered that source_uri may already be + # a valid URI with escaped characters. e.g. "{DESede}" is encoded + # as "%7BDESede%7D". If this is escaped again the percentage + # symbols will be escaped. + begin + URI.parse(uri) + rescue URI::InvalidURIError + URI.parse(URI::DEFAULT_PARSER.escape(uri)) + end + end + + ## + # Parses the #uri, returning the original uri if it's invalid + + def parse(uri) + return uri unless uri.is_a?(String) + + parse!(uri) + rescue URI::InvalidURIError + uri + end + + def with_redacted_user + clone.tap {|uri| uri.user = 'REDACTED' } + end + + def with_redacted_password + clone.tap {|uri| uri.password = 'REDACTED' } + end + + def valid_uri? + !@parsed_uri.is_a?(String) + end + + def password? + !!password + end + + def oauth_basic? + password == 'x-oauth-basic' + end + + def token? + !user.nil? && password.nil? + end + + def initialize_copy(original) + @parsed_uri = original.parsed_uri.clone + end +end diff --git a/lib/rubygems/uri_parser.rb b/lib/rubygems/uri_parser.rb deleted file mode 100644 index f350edec8c..0000000000 --- a/lib/rubygems/uri_parser.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -## -# The UriParser handles parsing URIs. -# - -class Gem::UriParser - ## - # Parses the #uri, raising if it's invalid - - def parse!(uri) - raise URI::InvalidURIError unless uri - - # Always escape URI's to deal with potential spaces and such - # It should also be considered that source_uri may already be - # a valid URI with escaped characters. e.g. "{DESede}" is encoded - # as "%7BDESede%7D". If this is escaped again the percentage - # symbols will be escaped. - begin - URI.parse(uri) - rescue URI::InvalidURIError - URI.parse(URI::DEFAULT_PARSER.escape(uri)) - end - end - - ## - # Parses the #uri, returning the original uri if it's invalid - - def parse(uri) - parse!(uri) - rescue URI::InvalidURIError - uri - end -end diff --git a/lib/rubygems/uri_parsing.rb b/lib/rubygems/uri_parsing.rb deleted file mode 100644 index 941d7e023a..0000000000 --- a/lib/rubygems/uri_parsing.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require "rubygems/uri_parser" - -module Gem::UriParsing - - def parse_uri(source_uri) - return source_uri unless source_uri.is_a?(String) - - uri_parser.parse(source_uri) - end - - private :parse_uri - - def uri_parser - require "uri" - - Gem::UriParser.new - end - - private :uri_parser - -end diff --git a/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb index 27a9957117..0ab44fbf6c 100644 --- a/lib/rubygems/user_interaction.rb +++ b/lib/rubygems/user_interaction.rb @@ -5,8 +5,8 @@ # See LICENSE.txt for permissions. #++ -require 'rubygems/deprecate' -require 'rubygems/text' +require_relative 'deprecate' +require_relative 'text' ## # Module that defines the default UserInteraction. Any class including this @@ -543,7 +543,7 @@ class Gem::StreamUI # A progress reporter that behaves nicely with threaded downloading. class ThreadedDownloadReporter - MUTEX = Mutex.new + MUTEX = Thread::Mutex.new ## # The current file name being displayed diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb index 2a55305172..4363c5adce 100644 --- a/lib/rubygems/util.rb +++ b/lib/rubygems/util.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/deprecate' +require_relative 'deprecate' ## # This module contains various utility methods as module methods. diff --git a/lib/rubygems/util/licenses.rb b/lib/rubygems/util/licenses.rb index 29bf310ea0..3f4178c6e0 100644 --- a/lib/rubygems/util/licenses.rb +++ b/lib/rubygems/util/licenses.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true -require 'rubygems/text' +require_relative '../text' class Gem::Licenses extend Gem::Text NONSTANDARD = 'Nonstandard'.freeze + LICENSE_REF = 'LicenseRef-.+'.freeze # Software Package Data Exchange (SPDX) standard open-source software # license identifiers @@ -18,6 +19,8 @@ class Gem::Licenses AFL-2.1 AFL-3.0 AGPL-1.0 + AGPL-1.0-only + AGPL-1.0-or-later AGPL-3.0 AGPL-3.0-only AGPL-3.0-or-later @@ -25,6 +28,7 @@ class Gem::Licenses AML AMPAS ANTLR-PD + ANTLR-PD-fallback APAFML APL-1.0 APSL-1.0 @@ -48,29 +52,41 @@ class Gem::Licenses BSD-2-Clause-FreeBSD BSD-2-Clause-NetBSD BSD-2-Clause-Patent + BSD-2-Clause-Views BSD-3-Clause BSD-3-Clause-Attribution BSD-3-Clause-Clear BSD-3-Clause-LBNL + BSD-3-Clause-Modification + BSD-3-Clause-No-Military-License BSD-3-Clause-No-Nuclear-License BSD-3-Clause-No-Nuclear-License-2014 BSD-3-Clause-No-Nuclear-Warranty + BSD-3-Clause-Open-MPI BSD-4-Clause + BSD-4-Clause-Shortened BSD-4-Clause-UC BSD-Protection BSD-Source-Code BSL-1.0 + BUSL-1.1 Bahyph Barr Beerware BitTorrent-1.0 BitTorrent-1.1 + BlueOak-1.0.0 Borceux + C-UDA-1.0 + CAL-1.0 + CAL-1.0-Combined-Work-Exception CATOSL-1.1 CC-BY-1.0 CC-BY-2.0 CC-BY-2.5 CC-BY-3.0 + CC-BY-3.0-AT + CC-BY-3.0-US CC-BY-4.0 CC-BY-NC-1.0 CC-BY-NC-2.0 @@ -81,6 +97,7 @@ class Gem::Licenses CC-BY-NC-ND-2.0 CC-BY-NC-ND-2.5 CC-BY-NC-ND-3.0 + CC-BY-NC-ND-3.0-IGO CC-BY-NC-ND-4.0 CC-BY-NC-SA-1.0 CC-BY-NC-SA-2.0 @@ -94,12 +111,17 @@ class Gem::Licenses CC-BY-ND-4.0 CC-BY-SA-1.0 CC-BY-SA-2.0 + CC-BY-SA-2.0-UK + CC-BY-SA-2.1-JP CC-BY-SA-2.5 CC-BY-SA-3.0 + CC-BY-SA-3.0-AT CC-BY-SA-4.0 + CC-PDDC CC0-1.0 CDDL-1.0 CDDL-1.1 + CDL-1.0 CDLA-Permissive-1.0 CDLA-Sharing-1.0 CECILL-1.0 @@ -108,6 +130,11 @@ class Gem::Licenses CECILL-2.1 CECILL-B CECILL-C + CERN-OHL-1.1 + CERN-OHL-1.2 + CERN-OHL-P-2.0 + CERN-OHL-S-2.0 + CERN-OHL-W-2.0 CNRI-Jython CNRI-Python CNRI-Python-GPL-Compatible @@ -123,12 +150,14 @@ class Gem::Licenses Cube D-FSL-1.0 DOC + DRL-1.0 DSDP Dotseqn ECL-1.0 ECL-2.0 EFL-1.0 EFL-2.0 + EPICS EPL-1.0 EPL-2.0 EUDatagrid @@ -144,17 +173,32 @@ class Gem::Licenses FTL Fair Frameworx-1.0 + FreeBSD-DOC FreeImage + GD GFDL-1.1 + GFDL-1.1-invariants-only + GFDL-1.1-invariants-or-later + GFDL-1.1-no-invariants-only + GFDL-1.1-no-invariants-or-later GFDL-1.1-only GFDL-1.1-or-later GFDL-1.2 + GFDL-1.2-invariants-only + GFDL-1.2-invariants-or-later + GFDL-1.2-no-invariants-only + GFDL-1.2-no-invariants-or-later GFDL-1.2-only GFDL-1.2-or-later GFDL-1.3 + GFDL-1.3-invariants-only + GFDL-1.3-invariants-or-later + GFDL-1.3-no-invariants-only + GFDL-1.3-no-invariants-or-later GFDL-1.3-only GFDL-1.3-or-later GL2PS + GLWTPL GPL-1.0 GPL-1.0+ GPL-1.0-only @@ -178,7 +222,10 @@ class Gem::Licenses Glide Glulxe HPND + HPND-sell-variant + HTMLTIDY HaskellReport + Hippocratic-2.1 IBM-pibs ICU IJG @@ -191,6 +238,7 @@ class Gem::Licenses Intel Intel-ACPI Interbase-1.0 + JPNIC JSON JasPer-2.0 LAL-1.2 @@ -221,11 +269,15 @@ class Gem::Licenses LiLiQ-R-1.1 LiLiQ-Rplus-1.1 Libpng + Linux-OpenIB MIT + MIT-0 MIT-CMU + MIT-Modern-Variant MIT-advertising MIT-enna MIT-feh + MIT-open-group MITNFA MPL-1.0 MPL-1.1 @@ -237,12 +289,18 @@ class Gem::Licenses MakeIndex MirOS Motosoto + MulanPSL-1.0 + MulanPSL-2.0 Multics Mup + NAIST-2003 NASA-1.3 NBPL-1.0 + NCGL-UK-2.0 NCSA NGPL + NIST-PD + NIST-PD-fallback NLOD-1.0 NLPL NOSL @@ -251,6 +309,7 @@ class Gem::Licenses NPOSL-3.0 NRL NTP + NTP-0 Naumen Net-SNMP NetCDF @@ -258,11 +317,23 @@ class Gem::Licenses Nokia Noweb Nunit + O-UDA-1.0 OCCT-PL OCLC-2.0 + ODC-By-1.0 ODbL-1.0 OFL-1.0 + OFL-1.0-RFN + OFL-1.0-no-RFN OFL-1.1 + OFL-1.1-RFN + OFL-1.1-no-RFN + OGC-1.0 + OGDL-Taiwan-1.0 + OGL-Canada-2.0 + OGL-UK-1.0 + OGL-UK-2.0 + OGL-UK-3.0 OGTSL OLDAP-1.1 OLDAP-1.2 @@ -292,7 +363,12 @@ class Gem::Licenses PDDL-1.0 PHP-3.0 PHP-3.01 + PSF-2.0 + Parity-6.0.0 + Parity-7.0.0 Plexus + PolyForm-Noncommercial-1.0.0 + PolyForm-Small-Business-1.0.0 PostgreSQL Python-2.0 QPL-1.0 @@ -310,15 +386,21 @@ class Gem::Licenses SGI-B-1.0 SGI-B-1.1 SGI-B-2.0 + SHL-0.5 + SHL-0.51 SISSL SISSL-1.2 SMLNJ SMPPL SNIA SPL-1.0 + SSH-OpenSSH + SSH-short + SSPL-1.0 SWL Saxpath Sendmail + Sendmail-8.23 SimPL-2.0 Sleepycat Spencer-86 @@ -326,11 +408,15 @@ class Gem::Licenses Spencer-99 StandardML-NJ SugarCRM-1.1.3 + TAPR-OHL-1.0 TCL TCP-wrappers TMate TORQUE-1.1 TOSL + TU-Berlin-1.0 + TU-Berlin-2.0 + UCL-1.0 UPL-1.0 Unicode-DFS-2015 Unicode-DFS-2016 @@ -360,16 +446,22 @@ class Gem::Licenses Zimbra-1.3 Zimbra-1.4 Zlib + blessing bzip2-1.0.5 bzip2-1.0.6 + copyleft-next-0.3.0 + copyleft-next-0.3.1 curl diffmark dvipdfm eCos-2.0 eGenix + etalab-2.0 gSOAP-1.3b gnuplot iMatix + libpng-2.0 + libselinux-1.0 libtiff mpich2 psfrag @@ -395,12 +487,26 @@ class Gem::Licenses Font-exception-2.0 GCC-exception-2.0 GCC-exception-3.1 + GPL-3.0-linking-exception + GPL-3.0-linking-source-exception + GPL-CC-1.0 + LGPL-3.0-linking-exception + LLVM-exception LZMA-exception Libtool-exception Linux-syscall-note Nokia-Qt-exception-1.1 OCCT-exception-1.0 + OCaml-LGPL-linking-exception + OpenJDK-assembly-exception-1.0 + PS-or-PDF-font-exception-20170817 + Qt-GPL-exception-1.0 + Qt-LGPL-exception-1.1 Qwt-exception-1.0 + SHL-2.0 + SHL-2.1 + Swift-exception + Universal-FOSS-exception-1.0 WxWindows-exception-3.1 eCos-exception-2.0 freertos-exception-2.0 @@ -413,11 +519,12 @@ class Gem::Licenses REGEXP = %r{ \A - ( + (?: #{Regexp.union(LICENSE_IDENTIFIERS)} \+? - (\s WITH \s #{Regexp.union(EXCEPTION_IDENTIFIERS)})? + (?:\s WITH \s #{Regexp.union(EXCEPTION_IDENTIFIERS)})? | #{NONSTANDARD} + | #{LICENSE_REF} ) \Z }ox.freeze diff --git a/lib/rubygems/validator.rb b/lib/rubygems/validator.rb index 30cdd93b5c..728595e778 100644 --- a/lib/rubygems/validator.rb +++ b/lib/rubygems/validator.rb @@ -5,8 +5,8 @@ # See LICENSE.txt for permissions. #++ -require 'rubygems/package' -require 'rubygems/installer' +require_relative 'package' +require_relative 'installer' ## # Validator performs various gem file and gem database validation diff --git a/lib/rubygems/version_option.rb b/lib/rubygems/version_option.rb index be71ef409b..1db382fa7f 100644 --- a/lib/rubygems/version_option.rb +++ b/lib/rubygems/version_option.rb @@ -5,7 +5,7 @@ # See LICENSE.txt for permissions. #++ -require 'rubygems' +require_relative '../rubygems' ## # Mixin methods for --version and --platform Gem::Command options. @@ -16,7 +16,7 @@ module Gem::VersionOption # Add the --platform option to the option parser. def add_platform_option(task = command, *wrap) - OptionParser.accept Gem::Platform do |value| + Gem::OptionParser.accept Gem::Platform do |value| if value == Gem::Platform::RUBY value else @@ -51,7 +51,7 @@ module Gem::VersionOption # Add the --version option to the option parser. def add_version_option(task = command, *wrap) - OptionParser.accept Gem::Requirement do |value| + Gem::OptionParser.accept Gem::Requirement do |value| Gem::Requirement.new(*value.split(/\s*,\s*/)) end diff --git a/lib/time.gemspec b/lib/time.gemspec index 040b9f34cf..be6de35c99 100644 --- a/lib/time.gemspec +++ b/lib/time.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "time" - spec.version = "0.1.0" + spec.version = "0.1.1" spec.authors = ["Tanaka Akira"] spec.email = ["akr@fsij.org"] @@ -19,6 +19,4 @@ Gem::Specification.new do |spec| spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - - spec.add_dependency "date" end diff --git a/lib/time.rb b/lib/time.rb index 625c2c87bb..8af482cb68 100644 --- a/lib/time.rb +++ b/lib/time.rb @@ -506,8 +506,8 @@ class Time (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+ (\d{2,})\s+ (\d{2})\s* - :\s*(\d{2})\s* - (?::\s*(\d{2}))?\s+ + :\s*(\d{2}) + (?:\s*:\s*(\d\d))?\s+ ([+-]\d{4}| UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[A-IK-Z])/ix =~ date # Since RFC 2822 permit comments, the regexp has no right anchor. diff --git a/lib/tmpdir.gemspec b/lib/tmpdir.gemspec index de30665507..7b76403002 100644 --- a/lib/tmpdir.gemspec +++ b/lib/tmpdir.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "tmpdir" - spec.version = "0.1.1" + spec.version = "0.1.2" spec.authors = ["Yukihiro Matsumoto"] spec.email = ["matz@ruby-lang.org"] diff --git a/lib/tmpdir.rb b/lib/tmpdir.rb index 0b1f00aecf..bf7db5282a 100644 --- a/lib/tmpdir.rb +++ b/lib/tmpdir.rb @@ -115,7 +115,7 @@ class Dir Dir.tmpdir end - UNUSABLE_CHARS = [File::SEPARATOR, File::ALT_SEPARATOR, File::PATH_SEPARATOR, ":"].uniq.join("").freeze + UNUSABLE_CHARS = "^,-.0-9A-Z_a-z~" class << (RANDOM = Random.new) MAX = 36**6 # < 0x100000000 diff --git a/lib/uri/rfc2396_parser.rb b/lib/uri/rfc2396_parser.rb index c719aa0726..253c54b786 100644 --- a/lib/uri/rfc2396_parser.rb +++ b/lib/uri/rfc2396_parser.rb @@ -491,8 +491,8 @@ module URI ret = {} # for URI::split - ret[:ABS_URI] = Regexp.new('\A\s*' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED) - ret[:REL_URI] = Regexp.new('\A\s*' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED) + ret[:ABS_URI] = Regexp.new('\A\s*+' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED) + ret[:REL_URI] = Regexp.new('\A\s*+' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED) # for URI::extract ret[:URI_REF] = Regexp.new(pattern[:URI_REF]) diff --git a/lib/uri/rfc3986_parser.rb b/lib/uri/rfc3986_parser.rb index 49a594c17d..5a9e44e08c 100644 --- a/lib/uri/rfc3986_parser.rb +++ b/lib/uri/rfc3986_parser.rb @@ -3,8 +3,8 @@ module URI class RFC3986_Parser # :nodoc: # URI defined in RFC3986 # this regexp is modified not to host is not empty string - RFC3986_URI = /\A(?<URI>(?<scheme>[A-Za-z][+\-.0-9A-Za-z]*):(?<hier-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])+))?(?::(?<port>\d*))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g<segment>)*)?)|(?<path-rootless>\g<segment-nz>(?:\/\g<segment>)*)|(?<path-empty>))(?:\?(?<query>[^#]*))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/ - RFC3986_relative_ref = /\A(?<relative-ref>(?<relative-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?<host>(?<IP-literal>\[(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:){,1}\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+)\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])+))?(?::(?<port>\d*))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g<segment>)*)?)|(?<path-noscheme>(?<segment-nz-nc>(?:%\h\h|[!$&-.0-9;=@-Z_a-z~])+)(?:\/\g<segment>)*)|(?<path-empty>))(?:\?(?<query>[^#]*))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/ + RFC3986_URI = /\A(?<URI>(?<scheme>[A-Za-z][+\-.0-9A-Za-z]*+):(?<hier-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*+)@)?(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h++\.[!$&-.0-;=A-Z_a-z~]++))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])++))?(?::(?<port>\d*+))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*+))*+)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])++)(?:\/\g<segment>)*+)?)|(?<path-rootless>\g<segment-nz>(?:\/\g<segment>)*+)|(?<path-empty>))(?:\?(?<query>[^#]*+))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*+))?)\z/ + RFC3986_relative_ref = /\A(?<relative-ref>(?<relative-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*+)@)?(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:){,1}\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h++\.[!$&-.0-;=A-Z_a-z~]++))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])++))?(?::(?<port>\d*+))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*+))*+)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])++)(?:\/\g<segment>)*+)?)|(?<path-noscheme>(?<segment-nz-nc>(?:%\h\h|[!$&-.0-9;=@-Z_a-z~])++)(?:\/\g<segment>)*+)|(?<path-empty>))(?:\?(?<query>[^#]*+))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*+))?)\z/ attr_reader :regexp def initialize @@ -95,7 +95,7 @@ module URI QUERY: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*\z/, FRAGMENT: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*\z/, OPAQUE: /\A(?:[^\/].*)?\z/, - PORT: /\A[\x09\x0a\x0c\x0d ]*\d*[\x09\x0a\x0c\x0d ]*\z/, + PORT: /\A[\x09\x0a\x0c\x0d ]*+\d*[\x09\x0a\x0c\x0d ]*\z/, } end diff --git a/lib/uri/version.rb b/lib/uri/version.rb index 41da51d844..fb195ee686 100644 --- a/lib/uri/version.rb +++ b/lib/uri/version.rb @@ -1,6 +1,6 @@ module URI # :stopdoc: - VERSION_CODE = '001001'.freeze + VERSION_CODE = '001003'.freeze VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze # :startdoc: end @@ -35,6 +35,11 @@ static const char *const loadable_ext[] = { 0 }; +static const char *const ruby_ext[] = { + ".rb", + 0 +}; + enum expand_type { EXPAND_ALL, EXPAND_RELATIVE, @@ -185,8 +190,17 @@ feature_key(const char *str, size_t len) return st_hash(str, len, 0xfea7009e); } +static bool +is_rbext_path(VALUE feature_path) +{ + long len = RSTRING_LEN(feature_path); + long rbext_len = rb_strlen_lit(".rb"); + if (len <= rbext_len) return false; + return IS_RBEXT(RSTRING_PTR(feature_path) + len - rbext_len); +} + static void -features_index_add_single(const char* str, size_t len, VALUE offset) +features_index_add_single(const char* str, size_t len, VALUE offset, bool rb) { struct st_table *features_index; VALUE this_feature_index = Qnil; @@ -202,17 +216,43 @@ features_index_add_single(const char* str, size_t len, VALUE offset) st_insert(features_index, short_feature_key, (st_data_t)offset); } else if (RB_TYPE_P(this_feature_index, T_FIXNUM)) { + VALUE loaded_features = get_loaded_features(); + VALUE this_feature_path = RARRAY_AREF(loaded_features, FIX2LONG(this_feature_index)); VALUE feature_indexes[2]; - feature_indexes[0] = this_feature_index; - feature_indexes[1] = offset; + int top = (rb && !is_rbext_path(this_feature_path)) ? 1 : 0; + feature_indexes[top^0] = this_feature_index; + feature_indexes[top^1] = offset; this_feature_index = (VALUE)xcalloc(1, sizeof(struct RArray)); RBASIC(this_feature_index)->flags = T_ARRAY; /* fake VALUE, do not mark/sweep */ rb_ary_cat(this_feature_index, feature_indexes, numberof(feature_indexes)); st_insert(features_index, short_feature_key, (st_data_t)this_feature_index); } else { + long pos = -1; + Check_Type(this_feature_index, T_ARRAY); + if (rb) { + VALUE loaded_features = get_loaded_features(); + for (long i = 0; i < RARRAY_LEN(this_feature_index); ++i) { + VALUE idx = RARRAY_AREF(this_feature_index, i); + VALUE this_feature_path = RARRAY_AREF(loaded_features, FIX2LONG(idx)); + Check_Type(this_feature_path, T_STRING); + if (!is_rbext_path(this_feature_path)) { + /* as this_feature_index is a fake VALUE, `push` (which + * doesn't wb_unprotect like as rb_ary_splice) first, + * then rotate partially. */ + pos = i; + break; + } + } + } rb_ary_push(this_feature_index, offset); + if (pos >= 0) { + VALUE *ptr = (VALUE *)RARRAY_CONST_PTR_TRANSIENT(this_feature_index); + long len = RARRAY_LEN(this_feature_index); + MEMMOVE(ptr + pos, ptr + pos + 1, VALUE, len - pos - 1); + ptr[pos] = offset; + } } } @@ -228,6 +268,7 @@ static void features_index_add(VALUE feature, VALUE offset) { const char *feature_str, *feature_end, *ext, *p; + bool rb = false; feature_str = StringValuePtr(feature); feature_end = feature_str + RSTRING_LEN(feature); @@ -237,6 +278,8 @@ features_index_add(VALUE feature, VALUE offset) break; if (*ext != '.') ext = NULL; + else + rb = IS_RBEXT(ext); /* Now `ext` points to the only string matching %r{^\.[^./]*$} that is at the end of `feature`, or is NULL if there is no such string. */ @@ -248,14 +291,14 @@ features_index_add(VALUE feature, VALUE offset) if (p < feature_str) break; /* Now *p == '/'. We reach this point for every '/' in `feature`. */ - features_index_add_single(p + 1, feature_end - p - 1, offset); + features_index_add_single(p + 1, feature_end - p - 1, offset, false); if (ext) { - features_index_add_single(p + 1, ext - p - 1, offset); + features_index_add_single(p + 1, ext - p - 1, offset, rb); } } - features_index_add_single(feature_str, feature_end - feature_str, offset); + features_index_add_single(feature_str, feature_end - feature_str, offset, false); if (ext) { - features_index_add_single(feature_str, ext - feature_str, offset); + features_index_add_single(feature_str, ext - feature_str, offset, rb); } } @@ -563,6 +606,7 @@ rb_provide_feature(VALUE feature) } rb_str_freeze(feature); + get_loaded_features_index(); rb_ary_push(features, rb_fstring(feature)); features_index_add(feature, INT2FIX(RARRAY_LEN(features)-1)); reset_loaded_features_snapshot(); @@ -926,7 +970,7 @@ search_required(VALUE fname, volatile VALUE *path, feature_func rb_feature_p) return 'r'; } tmp = fname; - type = rb_find_file_ext(&tmp, loadable_ext); + type = rb_find_file_ext(&tmp, ft == 's' ? ruby_ext : loadable_ext); switch (type) { case 0: if (ft) @@ -1087,7 +1131,7 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception) if (ftptr) load_unlock(RSTRING_PTR(path), !state); if (state) { - if (state == TAG_FATAL) { + if (state == TAG_FATAL || state == TAG_THROW) { EC_JUMP_TAG(ec, state); } else if (exception) { @@ -1137,6 +1137,7 @@ struct load_arg { long offset; st_table *symbols; st_table *data; + st_table *partial_objects; VALUE proc; st_table *compat_tbl; }; @@ -1163,6 +1164,7 @@ mark_load_arg(void *ptr) return; rb_mark_tbl(p->symbols); rb_mark_tbl(p->data); + rb_mark_tbl(p->partial_objects); rb_mark_hash(p->compat_tbl); } @@ -1424,9 +1426,10 @@ ruby2_keywords_flag_check(VALUE sym) { const char *p; long l; + if (rb_enc_get_index(sym) != ENCINDEX_US_ASCII) return 0; RSTRING_GETMEM(sym, p, l); if (l <= 0) return 0; - if (name_equal(name_s_ruby2_keywords_flag, rb_strlen_lit(name_s_ruby2_keywords_flag), p, 1)) { + if (name_equal(name_s_ruby2_keywords_flag, rb_strlen_lit(name_s_ruby2_keywords_flag), p, l)) { return 1; } return 0; @@ -1461,7 +1464,13 @@ r_symreal(struct load_arg *arg, int ivar) idx = sym2encidx(sym, r_object(arg)); } } - if (idx > 0) rb_enc_associate_index(s, idx); + if (idx > 0) { + rb_enc_associate_index(s, idx); + if (rb_enc_str_coderange(s) == ENC_CODERANGE_BROKEN) { + rb_raise(rb_eArgError, "invalid byte sequence in %s: %+"PRIsVALUE, + rb_enc_name(rb_enc_from_index(idx)), s); + } + } return s; } @@ -1509,6 +1518,7 @@ r_entry0(VALUE v, st_index_t num, struct load_arg *arg) st_lookup(arg->compat_tbl, v, &real_obj); } st_insert(arg->data, num, real_obj); + st_insert(arg->partial_objects, (st_data_t)real_obj, Qtrue); return v; } @@ -1539,10 +1549,15 @@ r_post_proc(VALUE v, struct load_arg *arg) } static VALUE -r_leave(VALUE v, struct load_arg *arg) +r_leave(VALUE v, struct load_arg *arg, bool partial) { v = r_fixup_compat(v, arg); - v = r_post_proc(v, arg); + if (!partial) { + st_data_t data; + st_data_t key = (st_data_t)v; + st_delete(arg->partial_objects, &key, &data); + v = r_post_proc(v, arg); + } return v; } @@ -1669,7 +1684,7 @@ append_extmod(VALUE obj, VALUE extmod) } while (0) static VALUE -r_object0(struct load_arg *arg, int *ivp, VALUE extmod) +r_object0(struct load_arg *arg, bool partial, int *ivp, VALUE extmod) { VALUE v = Qnil; int type = r_byte(arg); @@ -1683,15 +1698,18 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) rb_raise(rb_eArgError, "dump format error (unlinked)"); } v = (VALUE)link; - v = r_post_proc(v, arg); + if (!st_lookup(arg->partial_objects, (st_data_t)v, &link)) { + v = r_post_proc(v, arg); + } break; case TYPE_IVAR: { int ivar = TRUE; - v = r_object0(arg, &ivar, extmod); + v = r_object0(arg, true, &ivar, extmod); if (ivar) r_ivar(v, NULL, arg); + v = r_leave(v, arg, partial); } break; @@ -1704,7 +1722,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) if (RB_TYPE_P(m, T_CLASS)) { /* prepended */ VALUE c; - v = r_object0(arg, 0, Qnil); + v = r_object0(arg, true, 0, Qnil); c = CLASS_OF(v); if (c != m || FL_TEST(c, FL_SINGLETON)) { rb_raise(rb_eArgError, @@ -1721,7 +1739,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) must_be_module(m, path); rb_ary_push(extmod, m); - v = r_object0(arg, 0, extmod); + v = r_object0(arg, true, 0, extmod); while (RARRAY_LEN(extmod) > 0) { m = rb_ary_pop(extmod); rb_extend_object(v, m); @@ -1737,7 +1755,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) if (FL_TEST(c, FL_SINGLETON)) { rb_raise(rb_eTypeError, "singleton can't be loaded"); } - v = r_object0(arg, 0, extmod); + v = r_object0(arg, partial, 0, extmod); if (rb_special_const_p(v) || RB_TYPE_P(v, T_OBJECT) || RB_TYPE_P(v, T_CLASS)) { goto format_error; } @@ -1755,17 +1773,17 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) case TYPE_NIL: v = Qnil; - v = r_leave(v, arg); + v = r_leave(v, arg, false); break; case TYPE_TRUE: v = Qtrue; - v = r_leave(v, arg); + v = r_leave(v, arg, false); break; case TYPE_FALSE: v = Qfalse; - v = r_leave(v, arg); + v = r_leave(v, arg, false); break; case TYPE_FIXNUM: @@ -1773,7 +1791,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) long i = r_long(arg); v = LONG2FIX(i); } - v = r_leave(v, arg); + v = r_leave(v, arg, false); break; case TYPE_FLOAT: @@ -1798,7 +1816,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) } v = DBL2NUM(d); v = r_entry(v, arg); - v = r_leave(v, arg); + v = r_leave(v, arg, false); } break; @@ -1815,13 +1833,13 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) INTEGER_PACK_LITTLE_ENDIAN | (sign == '-' ? INTEGER_PACK_NEGATIVE : 0)); rb_str_resize(data, 0L); v = r_entry(v, arg); - v = r_leave(v, arg); + v = r_leave(v, arg, false); } break; case TYPE_STRING: v = r_entry(r_string(arg), arg); - v = r_leave(v, arg); + v = r_leave(v, arg, partial); break; case TYPE_REGEXP: @@ -1856,7 +1874,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) rb_str_set_len(str, dst - ptr); } v = r_entry0(rb_reg_new_str(str, options), idx, arg); - v = r_leave(v, arg); + v = r_leave(v, arg, partial); } break; @@ -1871,7 +1889,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) rb_ary_push(v, r_object(arg)); arg->readable--; } - v = r_leave(v, arg); + v = r_leave(v, arg, partial); arg->readable++; } break; @@ -1894,7 +1912,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) if (type == TYPE_HASH_DEF) { RHASH_SET_IFNONE(v, r_object(arg)); } - v = r_leave(v, arg); + v = r_leave(v, arg, partial); } break; @@ -1946,7 +1964,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) } } rb_struct_initialize(v, values); - v = r_leave(v, arg); + v = r_leave(v, arg, partial); arg->readable += 2; } break; @@ -1973,7 +1991,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) marshal_compat_t *compat = (marshal_compat_t*)d; v = compat->loader(klass, v); } - v = r_post_proc(v, arg); + if (!partial) v = r_post_proc(v, arg); } break; @@ -2015,7 +2033,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) } v = r_entry0(v, idx, arg); r_ivar(v, NULL, arg); - v = r_leave(v, arg); + v = r_leave(v, arg, partial); } break; @@ -2036,9 +2054,9 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) "class %"PRIsVALUE" needs to have instance method `_load_data'", name); } - r = r_object0(arg, 0, extmod); + r = r_object0(arg, partial, 0, extmod); load_funcall(arg, v, s_load_data, 1, &r); - v = r_leave(v, arg); + v = r_leave(v, arg, partial); } break; @@ -2049,7 +2067,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) v = rb_path_to_class(str); prohibit_ivar("class/module", str); v = r_entry(v, arg); - v = r_leave(v, arg); + v = r_leave(v, arg, partial); } break; @@ -2060,7 +2078,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) v = path2class(str); prohibit_ivar("class", str); v = r_entry(v, arg); - v = r_leave(v, arg); + v = r_leave(v, arg, partial); } break; @@ -2071,7 +2089,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) v = path2module(str); prohibit_ivar("module", str); v = r_entry(v, arg); - v = r_leave(v, arg); + v = r_leave(v, arg, partial); } break; @@ -2084,7 +2102,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) v = r_symreal(arg, 0); } v = rb_str_intern(v); - v = r_leave(v, arg); + v = r_leave(v, arg, partial); break; case TYPE_SYMLINK: @@ -2106,7 +2124,7 @@ r_object0(struct load_arg *arg, int *ivp, VALUE extmod) static VALUE r_object(struct load_arg *arg) { - return r_object0(arg, 0, Qnil); + return r_object0(arg, false, 0, Qnil); } static void @@ -2124,6 +2142,8 @@ clear_load_arg(struct load_arg *arg) arg->symbols = 0; st_free_table(arg->data); arg->data = 0; + st_free_table(arg->partial_objects); + arg->partial_objects = 0; if (arg->compat_tbl) { st_free_table(arg->compat_tbl); arg->compat_tbl = 0; @@ -2178,6 +2198,7 @@ rb_marshal_load_with_proc(VALUE port, VALUE proc) arg->offset = 0; arg->symbols = st_init_numtable(); arg->data = rb_init_identtable(); + arg->partial_objects = rb_init_identtable(); arg->compat_tbl = 0; arg->proc = 0; arg->readable = 0; @@ -173,6 +173,7 @@ struct rb_method_definition_struct { BITFIELD(rb_method_type_t, type, VM_METHOD_TYPE_MINIMUM_BITS); int alias_count : 28; int complemented_count : 28; + unsigned int no_redef_warning: 1; union { rb_method_iseq_t iseq; diff --git a/missing/dtoa.c b/missing/dtoa.c index cbee13ee81..b7a8302875 100644 --- a/missing/dtoa.c +++ b/missing/dtoa.c @@ -183,12 +183,16 @@ #undef Long #undef ULong -#if SIZEOF_INT == 4 +#include <limits.h> + +#if (INT_MAX >> 30) && !(INT_MAX >> 31) #define Long int #define ULong unsigned int -#elif SIZEOF_LONG == 4 +#elif (LONG_MAX >> 30) && !(LONG_MAX >> 31) #define Long long int #define ULong unsigned long int +#else +#error No 32bit integer #endif #if HAVE_LONG_LONG @@ -202,6 +206,11 @@ #define Bug(x) {fprintf(stderr, "%s\n", (x)); exit(EXIT_FAILURE);} #endif +#ifndef ISDIGIT +#include <ctype.h> +#define ISDIGIT(c) isdigit(c) +#endif +#include <errno.h> #include <stdlib.h> #include <string.h> @@ -219,6 +228,9 @@ extern void FREE(void*); #else #define FREE xfree #endif +#ifndef NO_SANITIZE +#define NO_SANITIZE(x, y) y +#endif #ifndef Omit_Private_Memory #ifndef PRIVATE_MEM @@ -280,7 +292,7 @@ extern "C" { #endif #ifndef hexdigit -static const char hexdigits[] = "0123456789abcdef0123456789ABCDEF"; +static const char hexdigit[] = "0123456789abcdef0123456789ABCDEF"; #endif #if defined(IEEE_LITTLE_ENDIAN) + defined(IEEE_BIG_ENDIAN) + defined(VAX) + defined(IBM) != 1 @@ -489,6 +501,19 @@ extern double rnd_prod(double, double), rnd_quot(double, double); #define FREE_DTOA_LOCK(n) /*unused right now*/ #endif +#ifndef ATOMIC_PTR_CAS +#define ATOMIC_PTR_CAS(var, old, new) ((var) = (new), (old)) +#endif +#ifndef LIKELY +#define LIKELY(x) (x) +#endif +#ifndef UNLIKELY +#define UNLIKELY(x) (x) +#endif +#ifndef ASSUME +#define ASSUME(x) (void)(x) +#endif + #define Kmax 15 struct Bigint { @@ -501,6 +526,8 @@ typedef struct Bigint Bigint; static Bigint *freelist[Kmax+1]; +#define BLOCKING_BIGINT ((Bigint *)(-1)) + static Bigint * Balloc(int k) { @@ -510,22 +537,41 @@ Balloc(int k) size_t len; #endif + rv = 0; ACQUIRE_DTOA_LOCK(0); - if (k <= Kmax && (rv = freelist[k]) != 0) { - freelist[k] = rv->next; + if (k <= Kmax) { + rv = freelist[k]; + while (rv) { + Bigint *rvn = rv; + rv = ATOMIC_PTR_CAS(freelist[k], rv, BLOCKING_BIGINT); + if (LIKELY(rv != BLOCKING_BIGINT && rvn == rv)) { + rvn = ATOMIC_PTR_CAS(freelist[k], BLOCKING_BIGINT, rv->next); + assert(rvn == BLOCKING_BIGINT); + ASSUME(rv); + break; + } + } } - else { + if (!rv) { x = 1 << k; #ifdef Omit_Private_Memory rv = (Bigint *)MALLOC(sizeof(Bigint) + (x-1)*sizeof(ULong)); #else len = (sizeof(Bigint) + (x-1)*sizeof(ULong) + sizeof(double) - 1) /sizeof(double); - if (k <= Kmax && pmem_next - private_mem + len <= PRIVATE_mem) { - rv = (Bigint*)pmem_next; - pmem_next += len; + if (k <= Kmax) { + double *pnext = pmem_next; + while (pnext - private_mem + len <= PRIVATE_mem) { + double *p = pnext; + pnext = ATOMIC_PTR_CAS(pmem_next, pnext, pnext + len); + if (LIKELY(p == pnext)) { + rv = (Bigint*)pnext; + ASSUME(rv); + break; + } + } } - else + if (!rv) rv = (Bigint*)MALLOC(len*sizeof(double)); #endif rv->k = k; @@ -539,14 +585,19 @@ Balloc(int k) static void Bfree(Bigint *v) { + Bigint *vn; if (v) { if (v->k > Kmax) { FREE(v); return; } ACQUIRE_DTOA_LOCK(0); - v->next = freelist[v->k]; - freelist[v->k] = v; + do { + do { + vn = ATOMIC_PTR_CAS(freelist[v->k], 0, 0); + } while (UNLIKELY(vn == BLOCKING_BIGINT)); + v->next = vn; + } while (UNLIKELY(ATOMIC_PTR_CAS(freelist[v->k], vn, v) != vn)); FREE_DTOA_LOCK(0); } } @@ -829,8 +880,9 @@ static Bigint * pow5mult(Bigint *b, int k) { Bigint *b1, *p5, *p51; + Bigint *p5tmp; int i; - static int p05[3] = { 5, 25, 125 }; + static const int p05[3] = { 5, 25, 125 }; if ((i = k & 3) != 0) b = multadd(b, p05[i-1], 0); @@ -839,17 +891,17 @@ pow5mult(Bigint *b, int k) return b; if (!(p5 = p5s)) { /* first time */ -#ifdef MULTIPLE_THREADS ACQUIRE_DTOA_LOCK(1); if (!(p5 = p5s)) { - p5 = p5s = i2b(625); + p5 = i2b(625); p5->next = 0; + p5tmp = ATOMIC_PTR_CAS(p5s, NULL, p5); + if (UNLIKELY(p5tmp)) { + Bfree(p5); + p5 = p5tmp; + } } FREE_DTOA_LOCK(1); -#else - p5 = p5s = i2b(625); - p5->next = 0; -#endif } for (;;) { if (k & 1) { @@ -860,17 +912,17 @@ pow5mult(Bigint *b, int k) if (!(k >>= 1)) break; if (!(p51 = p5->next)) { -#ifdef MULTIPLE_THREADS ACQUIRE_DTOA_LOCK(1); if (!(p51 = p5->next)) { - p51 = p5->next = mult(p5,p5); + p51 = mult(p5,p5); p51->next = 0; + p5tmp = ATOMIC_PTR_CAS(p5->next, NULL, p51); + if (UNLIKELY(p5tmp)) { + Bfree(p51); + p51 = p5tmp; + } } FREE_DTOA_LOCK(1); -#else - p51 = p5->next = mult(p5,p5); - p51->next = 0; -#endif } p5 = p51; } @@ -1500,6 +1552,7 @@ break2: if (!*++s || !(s1 = strchr(hexdigit, *s))) goto ret0; if (*s == '0') { while (*++s == '0'); + if (!*s) goto ret; s1 = strchr(hexdigit, *s); } if (s1 != NULL) { @@ -1522,7 +1575,7 @@ break2: for (; *s && (s1 = strchr(hexdigit, *s)); ++s) { adj += aadj * ((s1 - hexdigit) & 15); if ((aadj /= 16) == 0.0) { - while (strchr(hexdigit, *++s)); + while (*++s && strchr(hexdigit, *s)); break; } } @@ -2520,10 +2573,10 @@ static char *dtoa_result; static char * rv_alloc(int i) { - return dtoa_result = xmalloc(i); + return dtoa_result = MALLOC(i); } #else -#define rv_alloc(i) xmalloc(i) +#define rv_alloc(i) MALLOC(i) #endif static char * @@ -2550,7 +2603,7 @@ nrv_alloc(const char *s, char **rve, size_t n) static void freedtoa(char *s) { - xfree(s); + FREE(s); } #endif @@ -230,13 +230,13 @@ finish_conts(void) } } -// Create unit for `iseq`. +// Create unit for `iseq`. This function may be called from an MJIT worker. static void create_unit(const rb_iseq_t *iseq) { struct rb_mjit_unit *unit; - unit = ZALLOC(struct rb_mjit_unit); + unit = calloc(1, sizeof(struct rb_mjit_unit)); if (unit == NULL) return; @@ -245,8 +245,9 @@ create_unit(const rb_iseq_t *iseq) iseq->body->jit_unit = unit; } +// This is called from an MJIT worker when worker_p is true. static void -mjit_add_iseq_to_process(const rb_iseq_t *iseq, const struct rb_mjit_compile_info *compile_info) +mjit_add_iseq_to_process(const rb_iseq_t *iseq, const struct rb_mjit_compile_info *compile_info, bool worker_p) { if (!mjit_enabled || pch_status == PCH_FAILED) return; @@ -260,14 +261,18 @@ mjit_add_iseq_to_process(const rb_iseq_t *iseq, const struct rb_mjit_compile_inf if (compile_info != NULL) iseq->body->jit_unit->compile_info = *compile_info; - CRITICAL_SECTION_START(3, "in add_iseq_to_process"); + if (!worker_p) { + CRITICAL_SECTION_START(3, "in add_iseq_to_process"); + } add_to_list(iseq->body->jit_unit, &unit_queue); if (active_units.length >= mjit_opts.max_cache_size) { unload_requests++; } - verbose(3, "Sending wakeup signal to workers in mjit_add_iseq_to_process"); - rb_native_cond_broadcast(&mjit_worker_wakeup); - CRITICAL_SECTION_FINISH(3, "in add_iseq_to_process"); + if (!worker_p) { + verbose(3, "Sending wakeup signal to workers in mjit_add_iseq_to_process"); + rb_native_cond_broadcast(&mjit_worker_wakeup); + CRITICAL_SECTION_FINISH(3, "in add_iseq_to_process"); + } } // Add ISEQ to be JITed in parallel with the current thread. @@ -275,7 +280,7 @@ mjit_add_iseq_to_process(const rb_iseq_t *iseq, const struct rb_mjit_compile_inf void rb_mjit_add_iseq_to_process(const rb_iseq_t *iseq) { - mjit_add_iseq_to_process(iseq, NULL); + mjit_add_iseq_to_process(iseq, NULL, false); } // For this timeout seconds, --jit-wait will wait for JIT compilation finish. @@ -334,17 +339,22 @@ mjit_recompile(const rb_iseq_t *iseq) RSTRING_PTR(rb_iseq_path(iseq)), FIX2INT(iseq->body->location.first_lineno)); assert(iseq->body->jit_unit != NULL); - // Lazily move active_units to stale_units to avoid race conditions around active_units with compaction - CRITICAL_SECTION_START(3, "in rb_mjit_recompile_iseq"); - iseq->body->jit_unit->stale_p = true; - pending_stale_p = true; - CRITICAL_SECTION_FINISH(3, "in rb_mjit_recompile_iseq"); - - iseq->body->jit_func = (mjit_func_t)NOT_ADDED_JIT_ISEQ_FUNC; - mjit_add_iseq_to_process(iseq, &iseq->body->jit_unit->compile_info); if (UNLIKELY(mjit_opts.wait)) { + remove_from_list(iseq->body->jit_unit, &active_units); + add_to_list(iseq->body->jit_unit, &stale_units); + mjit_add_iseq_to_process(iseq, &iseq->body->jit_unit->compile_info, false); mjit_wait(iseq->body); } + else { + // Lazily move active_units to stale_units to avoid race conditions around active_units with compaction. + // Also, it's lazily moved to unit_queue as well because otherwise it won't be added to stale_units properly. + // It's good to avoid a race condition between mjit_add_iseq_to_process and mjit_compile around jit_unit as well. + CRITICAL_SECTION_START(3, "in rb_mjit_recompile_iseq"); + iseq->body->jit_unit->stale_p = true; + iseq->body->jit_func = (mjit_func_t)NOT_ADDED_JIT_ISEQ_FUNC; + pending_stale_p = true; + CRITICAL_SECTION_FINISH(3, "in rb_mjit_recompile_iseq"); + } } // Recompile iseq, disabling send optimization @@ -931,24 +941,32 @@ mjit_mark(void) return; RUBY_MARK_ENTER("mjit"); - if (compiling_iseq != NULL) - rb_gc_mark((VALUE)compiling_iseq); - // We need to release a lock when calling rb_gc_mark to avoid doubly acquiring // a lock by by mjit_gc_start_hook inside rb_gc_mark. // // Because an MJIT worker may modify active_units anytime, we need to convert // the linked list to an array to safely loop its ISeqs without keeping a lock. CRITICAL_SECTION_START(4, "mjit_mark"); - int length = active_units.length; - rb_iseq_t **iseqs = ALLOCA_N(rb_iseq_t *, length); + int length = 0; + if (compiling_iseqs != NULL) { + while (compiling_iseqs[length]) length++; + } + length += active_units.length; + const rb_iseq_t **iseqs = ALLOCA_N(const rb_iseq_t *, length); struct rb_mjit_unit *unit = NULL; int i = 0; + if (compiling_iseqs != NULL) { + while (compiling_iseqs[i]) { + iseqs[i] = compiling_iseqs[i]; + i++; + } + } list_for_each(&active_units.head, unit, unode) { iseqs[i] = unit->iseq; i++; } + assert(i == length); CRITICAL_SECTION_FINISH(4, "mjit_mark"); for (i = 0; i < length; i++) { diff --git a/mjit_compile.c b/mjit_compile.c index dc188864ca..c857153e35 100644 --- a/mjit_compile.c +++ b/mjit_compile.c @@ -441,6 +441,22 @@ inlinable_iseq_p(const struct rb_iseq_constant_body *body) return true; } +// Return an iseq pointer if cc has inlinable iseq. +const rb_iseq_t * +rb_mjit_inlinable_iseq(const struct rb_callinfo *ci, const struct rb_callcache *cc) +{ + const rb_iseq_t *iseq; + if (has_valid_method_type(cc) && + !(vm_ci_flag(ci) & VM_CALL_TAILCALL) && // inlining only non-tailcall path + vm_cc_cme(cc)->def->type == VM_METHOD_TYPE_ISEQ && + fastpath_applied_iseq_p(ci, cc, iseq = def_iseq_ptr(vm_cc_cme(cc)->def)) && + // CC_SET_FASTPATH in vm_callee_setup_arg + inlinable_iseq_p(iseq->body)) { + return iseq; + } + return NULL; +} + static void init_ivar_compile_status(const struct rb_iseq_constant_body *body, struct compile_status *status) { @@ -521,13 +537,9 @@ precompile_inlinable_iseqs(FILE *f, const rb_iseq_t *iseq, struct compile_status const struct rb_callinfo *ci = cd->ci; const struct rb_callcache *cc = captured_cc_entries(status)[call_data_index(cd, body)]; // use copy to avoid race condition + extern bool rb_mjit_compiling_iseq_p(const rb_iseq_t *iseq); const rb_iseq_t *child_iseq; - if (has_valid_method_type(cc) && - !(vm_ci_flag(ci) & VM_CALL_TAILCALL) && // inlining only non-tailcall path - vm_cc_cme(cc)->def->type == VM_METHOD_TYPE_ISEQ && - fastpath_applied_iseq_p(ci, cc, child_iseq = def_iseq_ptr(vm_cc_cme(cc)->def)) && - // CC_SET_FASTPATH in vm_callee_setup_arg - inlinable_iseq_p(child_iseq->body)) { + if ((child_iseq = rb_mjit_inlinable_iseq(ci, cc)) != NULL && rb_mjit_compiling_iseq_p(child_iseq)) { status->inlined_iseqs[pos] = child_iseq->body; if (mjit_opts.verbose >= 1) // print beforehand because ISeq may be GCed during copy job. diff --git a/mjit_worker.c b/mjit_worker.c index ba90cca7b6..f7f0b6bced 100644 --- a/mjit_worker.c +++ b/mjit_worker.c @@ -93,6 +93,10 @@ #include "ruby/debug.h" #include "ruby/thread.h" #include "ruby/version.h" +#include "builtin.h" +#include "insns.inc" +#include "insns_info.inc" +#include "internal/compile.h" #ifdef _WIN32 #include <winsock2.h> @@ -729,6 +733,51 @@ sprint_funcname(char *funcname, const struct rb_mjit_unit *unit) } } +static const rb_iseq_t **compiling_iseqs = NULL; + +static bool +set_compiling_iseqs(const rb_iseq_t *iseq) +{ + compiling_iseqs = calloc(iseq->body->iseq_size + 2, sizeof(rb_iseq_t *)); // 2: 1 (unit->iseq) + 1 (NULL end) + if (compiling_iseqs == NULL) + return false; + + compiling_iseqs[0] = iseq; + int i = 1; + + unsigned int pos = 0; + while (pos < iseq->body->iseq_size) { +#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE + int insn = rb_vm_insn_addr2insn((void *)iseq->body->iseq_encoded[pos]); +#else + int insn = (int)iseq->body->iseq_encoded[pos]; +#endif + if (insn == BIN(opt_send_without_block)) { + CALL_DATA cd = (CALL_DATA)iseq->body->iseq_encoded[pos + 1]; + extern const rb_iseq_t *rb_mjit_inlinable_iseq(const struct rb_callinfo *ci, const struct rb_callcache *cc); + const rb_iseq_t *iseq = rb_mjit_inlinable_iseq(cd->ci, cd->cc); + if (iseq != NULL) { + compiling_iseqs[i] = iseq; + i++; + } + } + pos += insn_len(insn); + } + return true; +} + +bool +rb_mjit_compiling_iseq_p(const rb_iseq_t *iseq) +{ + assert(compiling_iseqs != NULL); + int i = 0; + while (compiling_iseqs[i]) { + if (compiling_iseqs[i] == iseq) return true; + i++; + } + return false; +} + static const int c_file_access_mode = #ifdef O_BINARY O_BINARY| @@ -820,6 +869,7 @@ make_pch(void) const char *rest_args[] = { # ifdef __clang__ "-emit-pch", + "-c", # endif // -nodefaultlibs is a linker flag, but it may affect cc1 behavior on Gentoo, which should NOT be changed on pch: // https://gitweb.gentoo.org/proj/gcc-patches.git/tree/7.3.0/gentoo/13_all_default-ssp-fix.patch @@ -887,7 +937,13 @@ compile_c_to_so(const char *c_file, const char *so_file) # endif o_file, NULL }; - args = form_args(6, CC_LDSHARED_ARGS, CC_CODEFLAG_ARGS, so_args, CC_LIBS, CC_DLDFLAGS_ARGS, CC_LINKER_ARGS); +# if defined(__MACH__) + extern VALUE rb_libruby_selfpath; + const char *loader_args[] = {"-bundle_loader", StringValuePtr(rb_libruby_selfpath), NULL}; +# else + const char *loader_args[] = {NULL}; +# endif + args = form_args(7, CC_LDSHARED_ARGS, CC_CODEFLAG_ARGS, so_args, loader_args, CC_LIBS, CC_DLDFLAGS_ARGS, CC_LINKER_ARGS); if (args == NULL) return false; exit_code = exec_process(cc_path, args); free(args); @@ -951,6 +1007,11 @@ compile_compact_jit_code(char* c_file) // compacted functions (not done yet). bool success = true; list_for_each(&active_units.head, child_unit, unode) { + CRITICAL_SECTION_START(3, "before set_compiling_iseqs"); + success &= set_compiling_iseqs(child_unit->iseq); + CRITICAL_SECTION_FINISH(3, "after set_compiling_iseqs"); + if (!success) continue; + char funcname[MAXPATHLEN]; sprint_funcname(funcname, child_unit); @@ -964,6 +1025,11 @@ compile_compact_jit_code(char* c_file) if (!iseq_label) iseq_label = sep = ""; fprintf(f, "\n/* %s%s%s:%ld */\n", iseq_label, sep, iseq_path, iseq_lineno); success &= mjit_compile(f, child_unit->iseq, funcname, child_unit->id); + + CRITICAL_SECTION_START(3, "before compiling_iseqs free"); + free(compiling_iseqs); + compiling_iseqs = NULL; + CRITICAL_SECTION_FINISH(3, "after compiling_iseqs free"); } // release blocking mjit_gc_start_hook @@ -1103,8 +1169,6 @@ compile_prelude(FILE *f) #endif } -static rb_iseq_t *compiling_iseq = NULL; - // Compile ISeq in UNIT and return function pointer of JIT-ed code. // It may return NOT_COMPILED_JIT_ISEQ_FUNC if something went wrong. static mjit_func_t @@ -1139,7 +1203,7 @@ convert_unit_to_func(struct rb_mjit_unit *unit) // We need to check again here because we could've waited on GC above in_jit = (unit->iseq != NULL); if (in_jit) - compiling_iseq = unit->iseq; + in_jit &= set_compiling_iseqs(unit->iseq); CRITICAL_SECTION_FINISH(3, "before mjit_compile to wait GC finish"); if (!in_jit) { fclose(f); @@ -1164,7 +1228,8 @@ convert_unit_to_func(struct rb_mjit_unit *unit) // release blocking mjit_gc_start_hook CRITICAL_SECTION_START(3, "after mjit_compile to wakeup client for GC"); - compiling_iseq = NULL; + free(compiling_iseqs); + compiling_iseqs = NULL; in_jit = false; verbose(3, "Sending wakeup signal to client in a mjit-worker for GC"); rb_native_cond_signal(&mjit_client_wakeup); @@ -1344,6 +1409,8 @@ unload_units(void) } } +static void mjit_add_iseq_to_process(const rb_iseq_t *iseq, const struct rb_mjit_compile_info *compile_info, bool worker_p); + // The function implementing a worker. It is executed in a separate // thread by rb_thread_create_mjit_thread. It compiles precompiled header // and then compiles requested ISeqs. @@ -1380,7 +1447,7 @@ mjit_worker(void) // Wait until a unit becomes available CRITICAL_SECTION_START(3, "in worker dequeue"); - while ((pending_stale_p || list_empty(&unit_queue.head) || active_units.length >= mjit_opts.max_cache_size) && !stop_worker_p) { + while ((list_empty(&unit_queue.head) || active_units.length >= mjit_opts.max_cache_size) && !stop_worker_p) { rb_native_cond_wait(&mjit_worker_wakeup, &mjit_engine_mutex); verbose(3, "Getting wakeup from client"); @@ -1393,6 +1460,8 @@ mjit_worker(void) unit->stale_p = false; remove_from_list(unit, &active_units); add_to_list(unit, &stale_units); + // Lazily put it to unit_queue as well to avoid race conditions on jit_unit with mjit_compile. + mjit_add_iseq_to_process(unit->iseq, &unit->iseq->body->jit_unit->compile_info, true); } } } @@ -4529,6 +4529,7 @@ rb_fix_lshift(VALUE x, VALUE y) long val, width; val = NUM2LONG(x); + if (!val) return (rb_to_int(y), INT2FIX(0)); if (!FIXNUM_P(y)) return rb_big_lshift(rb_int2big(val), y); width = FIX2LONG(y); @@ -4575,6 +4576,7 @@ rb_fix_rshift(VALUE x, VALUE y) long i, val; val = FIX2LONG(x); + if (!val) return (rb_to_int(y), INT2FIX(0)); if (!FIXNUM_P(y)) return rb_big_rshift(rb_int2big(val), y); i = FIX2LONG(y); @@ -89,7 +89,6 @@ struct lex_context { #define YYCALLOC(nelem, size) rb_parser_calloc(p, (nelem), (size)) #define YYFREE(ptr) rb_parser_free(p, (ptr)) #define YYFPRINTF rb_parser_printf -#define YYPRINT(out, tok, val) parser_token_value_print(p, (tok), &(val)) #define YY_LOCATION_PRINT(File, loc) \ rb_parser_printf(p, "%d.%d-%d.%d", \ (loc).beg_pos.lineno, (loc).beg_pos.column,\ @@ -654,7 +653,6 @@ RUBY_SYMBOL_EXPORT_END static void error_duplicate_pattern_variable(struct parser_params *p, ID id, const YYLTYPE *loc); static void error_duplicate_pattern_key(struct parser_params *p, ID id, const YYLTYPE *loc); -static void parser_token_value_print(struct parser_params *p, enum yytokentype type, const YYSTYPE *valp); #ifndef RIPPER static ID formal_argument(struct parser_params*, ID); #else @@ -1084,6 +1082,35 @@ static int looking_at_eol_p(struct parser_params *p); %expect 0 %define api.pure %define parse.error verbose +%printer { +#ifndef RIPPER + rb_parser_printf(p, "%"PRIsVALUE, rb_id2str($$)); +#else + rb_parser_printf(p, "%"PRIsVALUE, RNODE($$)->nd_rval); +#endif +} tIDENTIFIER tFID tGVAR tIVAR tCONSTANT tCVAR tLABEL tOP_ASGN +%printer { +#ifndef RIPPER + rb_parser_printf(p, "%+"PRIsVALUE, $$->nd_lit); +#else + rb_parser_printf(p, "%+"PRIsVALUE, get_value($$)); +#endif +} tINTEGER tFLOAT tRATIONAL tIMAGINARY tSTRING_CONTENT tCHAR +%printer { +#ifndef RIPPER + rb_parser_printf(p, "$%ld", $$->nd_nth); +#else + rb_parser_printf(p, "%"PRIsVALUE, $$); +#endif +} tNTH_REF +%printer { +#ifndef RIPPER + rb_parser_printf(p, "$%c", (int)$$->nd_nth); +#else + rb_parser_printf(p, "%"PRIsVALUE, $$); +#endif +} tBACK_REF + %lex-param {struct parser_params *p} %parse-param {struct parser_params *p} %initial-action @@ -6763,7 +6790,11 @@ read_escape(struct parser_params *p, int flags, rb_encoding **encp) goto eof; } if ((c = nextc(p)) == '\\') { - if (peek(p, 'u')) goto eof; + switch (peekc(p)) { + case 'u': case 'U': + nextc(p); + goto eof; + } return read_escape(p, flags|ESCAPE_META, encp) | 0x80; } else if (c == -1 || !ISASCII(c)) goto eof; @@ -6788,7 +6819,11 @@ read_escape(struct parser_params *p, int flags, rb_encoding **encp) case 'c': if (flags & ESCAPE_CONTROL) goto eof; if ((c = nextc(p))== '\\') { - if (peek(p, 'u')) goto eof; + switch (peekc(p)) { + case 'u': case 'U': + nextc(p); + goto eof; + } c = read_escape(p, flags|ESCAPE_CONTROL, encp); } else if (c == '?') @@ -3199,7 +3199,7 @@ method_super_method(VALUE method) TypedData_Get_Struct(method, struct METHOD, &method_data_type, data); iclass = data->iclass; if (!iclass) return Qnil; - if (data->me->def->type == VM_METHOD_TYPE_ALIAS) { + if (data->me->def->type == VM_METHOD_TYPE_ALIAS && data->me->defined_class) { super_class = RCLASS_SUPER(rb_find_defined_class_by_owner(data->me->defined_class, data->me->def->body.alias.original_me->owner)); mid = data->me->def->body.alias.original_me->def->original_id; @@ -34,7 +34,7 @@ ASSERT_ractor_unlocking(rb_ractor_t *r) { #if RACTOR_CHECK_MODE > 0 // GET_EC is NULL in an MJIT worker - if (GET_EC() != NULL && r->sync.locked_by == rb_ractor_self(GET_RACTOR())) { + if (rb_current_execution_context(false) != NULL && r->sync.locked_by == rb_ractor_self(GET_RACTOR())) { rb_bug("recursive ractor locking"); } #endif @@ -45,7 +45,7 @@ ASSERT_ractor_locking(rb_ractor_t *r) { #if RACTOR_CHECK_MODE > 0 // GET_EC is NULL in an MJIT worker - if (GET_EC() != NULL && r->sync.locked_by != rb_ractor_self(GET_RACTOR())) { + if (rb_current_execution_context(false) != NULL && r->sync.locked_by != rb_ractor_self(GET_RACTOR())) { rp(r->sync.locked_by); rb_bug("ractor lock is not acquired."); } @@ -61,7 +61,7 @@ ractor_lock(rb_ractor_t *r, const char *file, int line) rb_native_mutex_lock(&r->sync.lock); #if RACTOR_CHECK_MODE > 0 - if (GET_EC() != NULL) { // GET_EC is NULL in an MJIT worker + if (rb_current_execution_context(false) != NULL) { // GET_EC is NULL in an MJIT worker r->sync.locked_by = rb_ractor_self(GET_RACTOR()); } #endif @@ -917,7 +917,7 @@ static VALUE ractor_move(VALUE obj); // in this file static VALUE ractor_copy(VALUE obj); // in this file static void -ractor_basket_setup(rb_execution_context_t *ec, struct rb_ractor_basket *basket, VALUE obj, VALUE move, bool exc, bool is_will) +ractor_basket_setup(rb_execution_context_t *ec, struct rb_ractor_basket *basket, VALUE obj, VALUE move, bool exc, bool is_will, bool is_yield) { basket->sender = rb_ec_ractor_ptr(ec)->pub.self; basket->exception = exc; @@ -936,7 +936,13 @@ ractor_basket_setup(rb_execution_context_t *ec, struct rb_ractor_basket *basket, } else { basket->type = basket_type_move; - basket->v = ractor_move(obj); + + if (is_yield) { + basket->v = obj; // call ractor_move() when yielding timing. + } + else { + basket->v = ractor_move(obj); + } } } @@ -944,7 +950,7 @@ static VALUE ractor_send(rb_execution_context_t *ec, rb_ractor_t *r, VALUE obj, VALUE move) { struct rb_ractor_basket basket; - ractor_basket_setup(ec, &basket, obj, move, false, false); + ractor_basket_setup(ec, &basket, obj, move, false, false, false); ractor_send_basket(ec, r, &basket); return r->pub.self; } @@ -959,17 +965,23 @@ ractor_try_take(rb_execution_context_t *ec, rb_ractor_t *r) RACTOR_LOCK(r); { - if (ractor_wakeup(r, wait_yielding, wakeup_by_take)) { + if (ractor_sleeping_by(r, wait_yielding)) { + MAYBE_UNUSED(bool) wakeup_result; VM_ASSERT(r->sync.wait.yielded_basket.type != basket_type_none); - basket = r->sync.wait.yielded_basket; - ractor_basket_clear(&r->sync.wait.yielded_basket); + + if (r->sync.wait.yielded_basket.type == basket_type_move) { + wakeup_result = ractor_wakeup(r, wait_yielding, wakeup_by_retry); + } + else { + wakeup_result = ractor_wakeup(r, wait_yielding, wakeup_by_take); + basket = r->sync.wait.yielded_basket; + ractor_basket_clear(&r->sync.wait.yielded_basket); + } + VM_ASSERT(wakeup_result); } else if (r->sync.outgoing_port_closed) { closed = true; } - else { - // not reached. - } } RACTOR_UNLOCK(r); @@ -986,6 +998,12 @@ ractor_try_take(rb_execution_context_t *ec, rb_ractor_t *r) } } +static VALUE +ractor_yield_move_body(VALUE v) +{ + return ractor_move(v); +} + static bool ractor_try_yield(rb_execution_context_t *ec, rb_ractor_t *cr, struct rb_ractor_basket *basket) { @@ -1010,8 +1028,34 @@ ractor_try_yield(rb_execution_context_t *ec, rb_ractor_t *cr, struct rb_ractor_b RACTOR_LOCK(r); { - if (ractor_wakeup(r, wait_taking, wakeup_by_yield)) { + if (ractor_sleeping_by(r, wait_taking)) { VM_ASSERT(r->sync.wait.taken_basket.type == basket_type_none); + + if (basket->type == basket_type_move) { + enum ractor_wait_status prev_wait_status = r->sync.wait.status; + r->sync.wait.status = wait_moving; + + RACTOR_UNLOCK(r); + { + int state; + VALUE moved_value = rb_protect(ractor_yield_move_body, basket->v, &state); + if (state) { + r->sync.wait.status = prev_wait_status; + rb_jump_tag(state); + } + else { + basket->v = moved_value; + } + } + RACTOR_LOCK(r); + + if (!ractor_wakeup(r, wait_moving, wakeup_by_yield)) { + // terminating? + } + } + else { + ractor_wakeup(r, wait_taking, wakeup_by_yield); + } r->sync.wait.taken_basket = *basket; } else { @@ -1036,7 +1080,7 @@ ractor_try_yield(rb_execution_context_t *ec, rb_ractor_t *cr, struct rb_ractor_b // select(r1, r2, r3, receive: true, yield: obj) static VALUE -ractor_select(rb_execution_context_t *ec, const VALUE *rs, int alen, VALUE yielded_value, bool move, VALUE *ret_r) +ractor_select(rb_execution_context_t *ec, const VALUE *rs, const int rs_len, VALUE yielded_value, bool move, VALUE *ret_r) { rb_ractor_t *cr = rb_ec_ractor_ptr(ec); VALUE crv = cr->pub.self; @@ -1045,7 +1089,7 @@ ractor_select(rb_execution_context_t *ec, const VALUE *rs, int alen, VALUE yield bool interrupted = false; enum ractor_wait_status wait_status = 0; bool yield_p = (yielded_value != Qundef) ? true : false; - const int rs_len = alen; + const int alen = rs_len + (yield_p ? 1 : 0); struct ractor_select_action { enum ractor_select_action_type { @@ -1054,7 +1098,7 @@ ractor_select(rb_execution_context_t *ec, const VALUE *rs, int alen, VALUE yield ractor_select_action_yield, } type; VALUE v; - } *actions = ALLOCA_N(struct ractor_select_action, alen + (yield_p ? 1 : 0)); + } *actions = ALLOCA_N(struct ractor_select_action, alen); VM_ASSERT(cr->sync.wait.status == wait_none); VM_ASSERT(cr->sync.wait.wakeup_status == wakeup_none); @@ -1062,7 +1106,7 @@ ractor_select(rb_execution_context_t *ec, const VALUE *rs, int alen, VALUE yield VM_ASSERT(cr->sync.wait.yielded_basket.type == basket_type_none); // setup actions - for (i=0; i<alen; i++) { + for (i=0; i<rs_len; i++) { VALUE v = rs[i]; if (v == crv) { @@ -1087,9 +1131,7 @@ ractor_select(rb_execution_context_t *ec, const VALUE *rs, int alen, VALUE yield actions[rs_len].type = ractor_select_action_yield; actions[rs_len].v = Qundef; wait_status |= wait_yielding; - alen++; - - ractor_basket_setup(ec, &cr->sync.wait.yielded_basket, yielded_value, move, false, false); + ractor_basket_setup(ec, &cr->sync.wait.yielded_basket, yielded_value, move, false, false, true); } // TODO: shuffle actions @@ -1443,6 +1485,9 @@ vm_remove_ractor(rb_vm_t *vm, rb_ractor_t *cr) } vm->ractor.cnt--; + /* Clear the cached freelist to prevent a memory leak. */ + rb_gc_ractor_newobj_cache_clear(&cr->newobj_cache); + ractor_status_set(cr, ractor_terminated); } RB_VM_UNLOCK(); @@ -1542,11 +1587,6 @@ rb_ractor_main_setup(rb_vm_t *vm, rb_ractor_t *r, rb_thread_t *th) rb_ractor_living_threads_insert(r, th); } -// io.c -VALUE rb_io_prep_stdin(void); -VALUE rb_io_prep_stdout(void); -VALUE rb_io_prep_stderr(void); - static VALUE ractor_create(rb_execution_context_t *ec, VALUE self, VALUE loc, VALUE name, VALUE args, VALUE block) { @@ -1558,10 +1598,6 @@ ractor_create(rb_execution_context_t *ec, VALUE self, VALUE loc, VALUE name, VAL r->pub.id = ractor_next_id(); RUBY_DEBUG_LOG("r:%u", r->pub.id); - r->r_stdin = rb_io_prep_stdin(); - r->r_stdout = rb_io_prep_stdout(); - r->r_stderr = rb_io_prep_stderr(); - rb_ractor_t *cr = rb_ec_ractor_ptr(ec); r->verbose = cr->verbose; r->debug = cr->debug; @@ -1582,7 +1618,7 @@ ractor_yield_atexit(rb_execution_context_t *ec, rb_ractor_t *cr, VALUE v, bool e ASSERT_ractor_unlocking(cr); struct rb_ractor_basket basket; - ractor_basket_setup(ec, &basket, v, Qfalse, exc, true); + ractor_basket_setup(ec, &basket, v, Qfalse, exc, true, true /* this flag is ignored because move is Qfalse */); retry: if (ractor_try_yield(ec, cr, &basket)) { @@ -2047,6 +2083,8 @@ void Init_Ractor(void) { rb_cRactor = rb_define_class("Ractor", rb_cObject); + rb_undef_alloc_func(rb_cRactor); + rb_eRactorError = rb_define_class_under(rb_cRactor, "Error", rb_eRuntimeError); rb_eRactorIsolationError = rb_define_class_under(rb_cRactor, "IsolationError", rb_eRactorError); rb_eRactorRemoteError = rb_define_class_under(rb_cRactor, "RemoteError", rb_eRactorError); @@ -2329,7 +2367,11 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data) .stop = false, .data = data, }; - rb_objspace_reachable_objects_from(obj, obj_traverse_reachable_i, &d); + RB_VM_LOCK_ENTER_NO_BARRIER(); + { + rb_objspace_reachable_objects_from(obj, obj_traverse_reachable_i, &d); + } + RB_VM_LOCK_LEAVE_NO_BARRIER(); if (d.stop) return 1; } break; @@ -2639,7 +2681,11 @@ static int obj_refer_only_shareables_p(VALUE obj) { int cnt = 0; - rb_objspace_reachable_objects_from(obj, obj_refer_only_shareables_p_i, &cnt); + RB_VM_LOCK_ENTER_NO_BARRIER(); + { + rb_objspace_reachable_objects_from(obj, obj_refer_only_shareables_p_i, &cnt); + } + RB_VM_LOCK_LEAVE_NO_BARRIER(); return cnt == 0; } diff --git a/ractor_core.h b/ractor_core.h index 2516277f4f..800e7ce409 100644 --- a/ractor_core.h +++ b/ractor_core.h @@ -61,6 +61,7 @@ struct rb_ractor_sync { wait_receiving = 0x01, wait_taking = 0x02, wait_yielding = 0x04, + wait_moving = 0x08, } status; enum ractor_wakeup_status { @@ -138,10 +139,7 @@ struct rb_ractor_struct { VALUE verbose; VALUE debug; - struct { - struct RVALUE *freelist; - struct heap_page *using_page; - } newobj_cache; + rb_ractor_newobj_cache_t newobj_cache; // gc.c rb_objspace_reachable_objects_from struct gc_mark_func_data_struct { @@ -261,7 +261,8 @@ const rb_data_type_t rb_random_data_type = { static void random_mt_free(void *ptr) { - if (ptr != default_rand()) + rb_random_mt_t *rnd = rb_ractor_local_storage_ptr(default_rand_key); + if (ptr != rnd) xfree(ptr); } @@ -368,15 +369,12 @@ rand_init(const rb_random_interface_t *rng, rb_random_t *rnd, VALUE seed) int sign; len = rb_absint_numwords(seed, 32, NULL); + if (len == 0) len = 1; buf = ALLOCV_N(uint32_t, buf0, len); sign = rb_integer_pack(seed, buf, len, sizeof(uint32_t), 0, INTEGER_PACK_LSWORD_FIRST|INTEGER_PACK_NATIVE_BYTE_ORDER); if (sign < 0) sign = -sign; - if (len == 0) { - buf[0] = 0; - len = 1; - } if (len > 1) { if (sign != 2 && buf[len-1] == 1) /* remove leading-zero-guard */ len--; @@ -813,7 +811,7 @@ rand_mt_init(rb_random_t *rnd, const uint32_t *buf, size_t len) { struct MT *mt = &((rb_random_mt_t *)rnd)->mt; if (len <= 1) { - init_genrand(mt, buf[0]); + init_genrand(mt, len ? buf[0] : 0); } else { init_by_array(mt, buf, (int)len); diff --git a/rational.c b/rational.c index 872bcd7e14..d621773838 100644 --- a/rational.c +++ b/rational.c @@ -1744,8 +1744,8 @@ nurat_rationalize(int argc, VALUE *argv, VALUE self) } /* :nodoc: */ -static VALUE -nurat_hash(VALUE self) +st_index_t +rb_rational_hash(VALUE self) { st_index_t v, h[2]; VALUE n; @@ -1756,9 +1756,16 @@ nurat_hash(VALUE self) n = rb_hash(dat->den); h[1] = NUM2LONG(n); v = rb_memhash(h, sizeof(h)); - return ST2FIX(v); + return v; +} + +static VALUE +nurat_hash(VALUE self) +{ + return ST2FIX(rb_rational_hash(self)); } + static VALUE f_format(VALUE self, VALUE (*func)(VALUE)) { @@ -1151,6 +1151,14 @@ match_size(VALUE match) } static int name_to_backref_number(struct re_registers *, VALUE, const char*, const char*); +NORETURN(static void name_to_backref_error(VALUE name)); + +static void +name_to_backref_error(VALUE name) +{ + rb_raise(rb_eIndexError, "undefined group name reference: % "PRIsVALUE, + name); +} static int match_backref_number(VALUE match, VALUE backref) @@ -1170,10 +1178,10 @@ match_backref_number(VALUE match, VALUE backref) } name = StringValueCStr(backref); - num = name_to_backref_number(regs, regexp, name, name + strlen(name)); + num = name_to_backref_number(regs, regexp, name, name + RSTRING_LEN(backref)); if (num < 1) { - rb_raise(rb_eIndexError, "undefined group name reference: %s", name); + name_to_backref_error(backref); } return num; @@ -1538,8 +1546,8 @@ rb_reg_adjust_startpos(VALUE re, VALUE str, long pos, int reverse) } /* returns byte offset */ -long -rb_reg_search0(VALUE re, VALUE str, long pos, int reverse, int set_backref_str) +static long +rb_reg_search_set_match(VALUE re, VALUE str, long pos, int reverse, int set_backref_str, VALUE *set_match) { long result; VALUE match; @@ -1561,18 +1569,7 @@ rb_reg_search0(VALUE re, VALUE str, long pos, int reverse, int set_backref_str) tmpreg = reg != RREGEXP_PTR(re); if (!tmpreg) RREGEXP(re)->usecnt++; - match = rb_backref_get(); - if (!NIL_P(match)) { - if (FL_TEST(match, MATCH_BUSY)) { - match = Qnil; - } - else { - regs = RMATCH_REGS(match); - } - } - if (NIL_P(match)) { - MEMZERO(regs, struct re_registers, 1); - } + MEMZERO(regs, struct re_registers, 1); if (!reverse) { range += len; } @@ -1605,13 +1602,10 @@ rb_reg_search0(VALUE re, VALUE str, long pos, int reverse, int set_backref_str) } } - if (NIL_P(match)) { - int err; - match = match_alloc(rb_cMatch); - err = rb_reg_region_copy(RMATCH_REGS(match), regs); - onig_region_free(regs, 0); - if (err) rb_memerror(); - } + match = match_alloc(rb_cMatch); + int copy_err = rb_reg_region_copy(RMATCH_REGS(match), regs); + onig_region_free(regs, 0); + if (copy_err) rb_memerror(); if (set_backref_str) { RMATCH(match)->str = rb_str_new4(str); @@ -1619,11 +1613,18 @@ rb_reg_search0(VALUE re, VALUE str, long pos, int reverse, int set_backref_str) RMATCH(match)->regexp = re; rb_backref_set(match); + if (set_match) *set_match = match; return result; } long +rb_reg_search0(VALUE re, VALUE str, long pos, int reverse, int set_backref_str) +{ + return rb_reg_search_set_match(re, str, pos, reverse, set_backref_str, NULL); +} + +long rb_reg_search(VALUE re, VALUE str, long pos, int reverse) { return rb_reg_search0(re, str, pos, reverse, 1); @@ -1928,14 +1929,6 @@ name_to_backref_number(struct re_registers *regs, VALUE regexp, const char* name (const unsigned char *)name, (const unsigned char *)name_end, regs); } -NORETURN(static void name_to_backref_error(VALUE name)); -static void -name_to_backref_error(VALUE name) -{ - rb_raise(rb_eIndexError, "undefined group name reference: % "PRIsVALUE, - name); -} - #define NAME_TO_NUMBER(regs, re, name, name_ptr, name_end) \ (NIL_P(re) ? 0 : \ !rb_enc_compatible(RREGEXP_SRC(re), (name)) ? 0 : \ @@ -3127,7 +3120,7 @@ reg_operand(VALUE s, int check) } static long -reg_match_pos(VALUE re, VALUE *strp, long pos) +reg_match_pos(VALUE re, VALUE *strp, long pos, VALUE* set_match) { VALUE str = *strp; @@ -3146,7 +3139,7 @@ reg_match_pos(VALUE re, VALUE *strp, long pos) } pos = rb_str_offset(str, pos); } - return rb_reg_search(re, str, pos, 0); + return rb_reg_search_set_match(re, str, pos, 0, 1, set_match); } /* @@ -3200,7 +3193,7 @@ reg_match_pos(VALUE re, VALUE *strp, long pos) VALUE rb_reg_match(VALUE re, VALUE str) { - long pos = reg_match_pos(re, &str, 0); + long pos = reg_match_pos(re, &str, 0, NULL); if (pos < 0) return Qnil; pos = rb_str_sublen(str, pos); return LONG2FIX(pos); @@ -3311,7 +3304,7 @@ rb_reg_match2(VALUE re) static VALUE rb_reg_match_m(int argc, VALUE *argv, VALUE re) { - VALUE result, str, initpos; + VALUE result = Qnil, str, initpos; long pos; if (rb_scan_args(argc, argv, "11", &str, &initpos) == 2) { @@ -3321,12 +3314,11 @@ rb_reg_match_m(int argc, VALUE *argv, VALUE re) pos = 0; } - pos = reg_match_pos(re, &str, pos); + pos = reg_match_pos(re, &str, pos, &result); if (pos < 0) { rb_backref_set(Qnil); return Qnil; } - result = rb_backref_get(); rb_match_busy(result); if (!NIL_P(result) && rb_block_given_p()) { return rb_yield(result); @@ -142,8 +142,13 @@ bitset_on_num(BitSetRef bs) static void onig_reg_resize(regex_t *reg) { - resize: - if (reg->alloc > reg->used) { + do { + if (!reg->used) { + xfree(reg->p); + reg->alloc = 0; + reg->p = 0; + } + else if (reg->alloc > reg->used) { unsigned char *new_ptr = xrealloc(reg->p, reg->used); // Skip the right size optimization if memory allocation fails if (new_ptr) { @@ -151,10 +156,7 @@ onig_reg_resize(regex_t *reg) reg->p = new_ptr; } } - if (reg->chain) { - reg = reg->chain; - goto resize; - } + } while ((reg = reg->chain) != 0); } extern int @@ -1933,7 +1935,7 @@ noname_disable_map(Node** plink, GroupNumRemap* map, int* counter) } static int -renumber_node_backref(Node* node, GroupNumRemap* map) +renumber_node_backref(Node* node, GroupNumRemap* map, const int num_mem) { int i, pos, n, old_num; int *backs; @@ -1949,6 +1951,7 @@ renumber_node_backref(Node* node, GroupNumRemap* map) backs = bn->back_dynamic; for (i = 0, pos = 0; i < old_num; i++) { + if (backs[i] > num_mem) return ONIGERR_INVALID_BACKREF; n = map[backs[i]].new_val; if (n > 0) { backs[pos] = n; @@ -1961,7 +1964,7 @@ renumber_node_backref(Node* node, GroupNumRemap* map) } static int -renumber_by_map(Node* node, GroupNumRemap* map) +renumber_by_map(Node* node, GroupNumRemap* map, const int num_mem) { int r = 0; @@ -1969,28 +1972,30 @@ renumber_by_map(Node* node, GroupNumRemap* map) case NT_LIST: case NT_ALT: do { - r = renumber_by_map(NCAR(node), map); + r = renumber_by_map(NCAR(node), map, num_mem); } while (r == 0 && IS_NOT_NULL(node = NCDR(node))); break; case NT_QTFR: - r = renumber_by_map(NQTFR(node)->target, map); + r = renumber_by_map(NQTFR(node)->target, map, num_mem); break; case NT_ENCLOSE: { EncloseNode* en = NENCLOSE(node); - if (en->type == ENCLOSE_CONDITION) + if (en->type == ENCLOSE_CONDITION) { + if (en->regnum > num_mem) return ONIGERR_INVALID_BACKREF; en->regnum = map[en->regnum].new_val; - r = renumber_by_map(en->target, map); + } + r = renumber_by_map(en->target, map, num_mem); } break; case NT_BREF: - r = renumber_node_backref(node, map); + r = renumber_node_backref(node, map, num_mem); break; case NT_ANCHOR: if (NANCHOR(node)->target) - r = renumber_by_map(NANCHOR(node)->target, map); + r = renumber_by_map(NANCHOR(node)->target, map, num_mem); break; default: @@ -2052,7 +2057,7 @@ disable_noname_group_capture(Node** root, regex_t* reg, ScanEnv* env) r = noname_disable_map(root, map, &counter); if (r != 0) return r; - r = renumber_by_map(*root, map); + r = renumber_by_map(*root, map, env->num_mem); if (r != 0) return r; for (i = 1, pos = 1; i <= env->num_mem; i++) { @@ -2528,8 +2528,8 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, CASE(OP_MEMORY_END_PUSH_REC) MOP_IN(OP_MEMORY_END_PUSH_REC); GET_MEMNUM_INC(mem, p); STACK_GET_MEM_START(mem, stkp); /* should be before push mem-end. */ - STACK_PUSH_MEM_END(mem, s); mem_start_stk[mem] = GET_STACK_INDEX(stkp); + STACK_PUSH_MEM_END(mem, s); MOP_OUT; JUMP; @@ -3902,12 +3902,17 @@ forward_search_range(regex_t* reg, const UChar* str, const UChar* end, UChar* s, UChar* range, UChar** low, UChar** high, UChar** low_prev) { UChar *p, *pprev = (UChar* )NULL; + size_t input_len = end - str; #ifdef ONIG_DEBUG_SEARCH fprintf(stderr, "forward_search_range: str: %"PRIuPTR" (%p), end: %"PRIuPTR" (%p), s: %"PRIuPTR" (%p), range: %"PRIuPTR" (%p)\n", (uintptr_t )str, str, (uintptr_t )end, end, (uintptr_t )s, s, (uintptr_t )range, range); #endif + if (reg->dmin > input_len) { + return 0; + } + p = s; if (reg->dmin > 0) { if (ONIGENC_IS_SINGLEBYTE(reg->enc)) { @@ -4044,6 +4049,11 @@ backward_search_range(regex_t* reg, const UChar* str, const UChar* end, UChar** low, UChar** high) { UChar *p; + size_t input_len = end - str; + + if (reg->dmin > input_len) { + return 0; + } range += reg->dmin; p = s; @@ -26,7 +26,7 @@ # include <sys/pstat.h> #endif -#if defined(LOAD_RELATIVE) && defined(HAVE_DLADDR) +#if (defined(LOAD_RELATIVE) || defined(__MACH__)) && defined(HAVE_DLADDR) # include <dlfcn.h> #endif @@ -531,7 +531,7 @@ str_conv_enc(VALUE str, rb_encoding *from, rb_encoding *to) void ruby_init_loadpath(void); -#if defined(LOAD_RELATIVE) +#if defined(LOAD_RELATIVE) || defined(__MACH__) static VALUE runtime_libruby_path(void) { @@ -607,6 +607,10 @@ runtime_libruby_path(void) #define INITIAL_LOAD_PATH_MARK rb_intern_const("@gem_prelude_index") VALUE ruby_archlibdir_path, ruby_prefix_path; +#if defined(__MACH__) +// A path to libruby.dylib itself or where it's statically linked to. +VALUE rb_libruby_selfpath; +#endif void ruby_init_loadpath(void) @@ -614,6 +618,14 @@ ruby_init_loadpath(void) VALUE load_path, archlibdir = 0; ID id_initial_load_path_mark; const char *paths = ruby_initial_load_paths; +#if defined(LOAD_RELATIVE) || defined(__MACH__) + VALUE libruby_path = runtime_libruby_path(); +# if defined(__MACH__) + rb_libruby_selfpath = libruby_path; + rb_gc_register_address(&rb_libruby_selfpath); +# endif +#endif + #if defined LOAD_RELATIVE #if !defined ENABLE_MULTIARCH # define RUBY_ARCH_PATH "" @@ -627,7 +639,7 @@ ruby_init_loadpath(void) size_t baselen; const char *p; - sopath = runtime_libruby_path(); + sopath = libruby_path; libpath = RSTRING_PTR(sopath); p = strrchr(libpath, '/'); @@ -1657,7 +1669,7 @@ tty_enabled(void) DWORD m; if (!GetConsoleMode(h, &m)) return 0; # ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING -# define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x200 +# define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x4 # endif if (!(m & ENABLE_VIRTUAL_TERMINAL_PROCESSING)) return 0; return 1; @@ -1667,6 +1679,17 @@ tty_enabled(void) #endif static VALUE +copy_str(VALUE str, rb_encoding *enc, bool intern) +{ + if (!intern) { + if (rb_enc_str_coderange_scan(str, enc) == ENC_CODERANGE_BROKEN) + return 0; + return rb_enc_associate(rb_str_dup(str), enc); + } + return rb_enc_interned_str(RSTRING_PTR(str), RSTRING_LEN(str), enc); +} + +static VALUE process_options(int argc, char **argv, ruby_cmdline_options_t *opt) { rb_ast_t *ast = 0; @@ -1682,6 +1705,8 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) char fbuf[MAXPATHLEN]; int i = (int)proc_options(argc, argv, opt, 0); unsigned int dump = opt->dump & dump_exit_bits; + rb_vm_t *vm = GET_VM(); + const long loaded_before_enc = RARRAY_LEN(vm->loaded_features); if (opt->dump & (DUMP_BIT(usage)|DUMP_BIT(help))) { int tty = isatty(1); @@ -1883,7 +1908,6 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) rb_obj_freeze(opt->script_name); if (IF_UTF8_PATH(uenc != lenc, 1)) { long i; - rb_vm_t *vm = GET_VM(); VALUE load_path = vm->load_path; const ID id_initial_load_path_mark = INITIAL_LOAD_PATH_MARK; int modifiable = FALSE; @@ -1897,7 +1921,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) if (newpath == path) continue; path = newpath; #else - path = rb_enc_associate(rb_str_dup(path), lenc); + if (!(path = copy_str(path, lenc, !mark))) continue; #endif if (mark) rb_ivar_set(path, id_initial_load_path_mark, path); if (!modifiable) { @@ -1910,6 +1934,19 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) rb_ary_replace(vm->load_path_snapshot, load_path); } } + { + VALUE loaded_features = vm->loaded_features; + bool modified = false; + for (long i = loaded_before_enc; i < RARRAY_LEN(loaded_features); ++i) { + VALUE path = RARRAY_AREF(loaded_features, i); + if (!(path = copy_str(path, IF_UTF8_PATH(uenc, lenc), true))) continue; + modified = true; + RARRAY_ASET(loaded_features, i, path); + } + if (modified) { + rb_ary_replace(vm->loaded_features_snapshot, loaded_features); + } + } if (opt->features.mask & COMPILATION_FEATURES) { VALUE option = rb_hash_new(); diff --git a/scheduler.c b/scheduler.c index 88db433f1e..66cbfc6f10 100644 --- a/scheduler.c +++ b/scheduler.c @@ -49,12 +49,36 @@ rb_scheduler_get(void) return thread->scheduler; } +static void +verify_interface(VALUE scheduler) +{ + if (!rb_respond_to(scheduler, id_block)) { + rb_raise(rb_eArgError, "Scheduler must implement #block!"); + } + + if (!rb_respond_to(scheduler, id_unblock)) { + rb_raise(rb_eArgError, "Scheduler must implement #unblock!"); + } + + if (!rb_respond_to(scheduler, id_kernel_sleep)) { + rb_raise(rb_eArgError, "Scheduler must implement #kernel_sleep!"); + } + + if (!rb_respond_to(scheduler, id_io_wait)) { + rb_raise(rb_eArgError, "Scheduler must implement #io_wait!"); + } +} + VALUE rb_scheduler_set(VALUE scheduler) { rb_thread_t *thread = GET_THREAD(); VM_ASSERT(thread); + if (scheduler != Qnil) { + verify_interface(scheduler); + } + // We invoke Scheduler#close when setting it to something else, to ensure the previous scheduler runs to completion before changing the scheduler. That way, we do not need to consider interactions, e.g., of a Fiber from the previous scheduler with the new scheduler. if (thread->scheduler != Qnil) { rb_scheduler_close(thread->scheduler); @@ -557,10 +557,13 @@ static int rb_sigaltstack_size_value = 0; void * rb_allocate_sigaltstack(void) { + void *altstack; if (!rb_sigaltstack_size_value) { rb_sigaltstack_size_value = rb_sigaltstack_size(); } - return xmalloc(rb_sigaltstack_size_value); + altstack = malloc(rb_sigaltstack_size_value); + if (!altstack) rb_memerror(); + return altstack; } /* alternate stack for SIGSEGV */ diff --git a/spec/bundler/bundler/bundler_spec.rb b/spec/bundler/bundler/bundler_spec.rb index 56ef4ce75a..b8191fe20f 100644 --- a/spec/bundler/bundler/bundler_spec.rb +++ b/spec/bundler/bundler/bundler_spec.rb @@ -176,11 +176,9 @@ RSpec.describe Bundler do describe "configuration" do context "disable_shared_gems" do it "should unset GEM_PATH with empty string" do - env = {} expect(Bundler).to receive(:use_system_gems?).and_return(false) - Bundler.send(:configure_gem_path, env) - expect(env.keys).to include("GEM_PATH") - expect(env["GEM_PATH"]).to eq "" + Bundler.send(:configure_gem_path) + expect(ENV["GEM_PATH"]).to eq "" end end end @@ -249,11 +247,8 @@ EOF allow(Bundler.rubygems).to receive(:user_home).and_return(path) allow(File).to receive(:directory?).with(path).and_return false allow(Bundler).to receive(:tmp).and_return(Pathname.new("/tmp/trulyrandom")) - message = <<EOF -`/home/oggy` is not a directory. -Bundler will use `/tmp/trulyrandom' as your home directory temporarily. -EOF - expect(Bundler.ui).to receive(:warn).with(message) + expect(Bundler.ui).to receive(:warn).with("`/home/oggy` is not a directory.\n") + expect(Bundler.ui).to receive(:warn).with("Bundler will use `/tmp/trulyrandom' as your home directory temporarily.\n") expect(Bundler.user_home).to eq(Pathname("/tmp/trulyrandom")) end end @@ -268,11 +263,8 @@ EOF allow(File).to receive(:writable?).with(path).and_return false allow(File).to receive(:directory?).with(dotbundle).and_return false allow(Bundler).to receive(:tmp).and_return(Pathname.new("/tmp/trulyrandom")) - message = <<EOF -`/home/oggy` is not writable. -Bundler will use `/tmp/trulyrandom' as your home directory temporarily. -EOF - expect(Bundler.ui).to receive(:warn).with(message) + expect(Bundler.ui).to receive(:warn).with("`/home/oggy` is not writable.\n") + expect(Bundler.ui).to receive(:warn).with("Bundler will use `/tmp/trulyrandom' as your home directory temporarily.\n") expect(Bundler.user_home).to eq(Pathname("/tmp/trulyrandom")) end @@ -293,11 +285,8 @@ EOF it "should issue warning and return a temporary user home" do allow(Bundler.rubygems).to receive(:user_home).and_return(nil) allow(Bundler).to receive(:tmp).and_return(Pathname.new("/tmp/trulyrandom")) - message = <<EOF -Your home directory is not set. -Bundler will use `/tmp/trulyrandom' as your home directory temporarily. -EOF - expect(Bundler.ui).to receive(:warn).with(message) + expect(Bundler.ui).to receive(:warn).with("Your home directory is not set.\n") + expect(Bundler.ui).to receive(:warn).with("Bundler will use `/tmp/trulyrandom' as your home directory temporarily.\n") expect(Bundler.user_home).to eq(Pathname("/tmp/trulyrandom")) end end diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index e49066f7dc..c5de12c211 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -2,16 +2,6 @@ require "bundler/cli" -using Module.new { - # Some `man` (e.g., on macOS) always highlights the output even to - # non-tty. - refine Spec::Helpers do - def out - super.gsub(/.[\b]/, "") - end - end -} if RUBY_VERSION >= "2.4" - RSpec.describe "bundle executable" do it "returns non-zero exit status when passed unrecognized options" do bundle "--invalid_argument", :raise_on_error => false @@ -42,49 +32,57 @@ RSpec.describe "bundle executable" do it "aliases e to exec" do bundle "e --help" - expect(out).to include("bundle-exec") + expect(out_with_macos_man_workaround).to include("bundle-exec") end it "aliases ex to exec" do bundle "ex --help" - expect(out).to include("bundle-exec") + expect(out_with_macos_man_workaround).to include("bundle-exec") end it "aliases exe to exec" do bundle "exe --help" - expect(out).to include("bundle-exec") + expect(out_with_macos_man_workaround).to include("bundle-exec") end it "aliases c to check" do bundle "c --help" - expect(out).to include("bundle-check") + expect(out_with_macos_man_workaround).to include("bundle-check") end it "aliases i to install" do bundle "i --help" - expect(out).to include("bundle-install") + expect(out_with_macos_man_workaround).to include("bundle-install") end it "aliases ls to list" do bundle "ls --help" - expect(out).to include("bundle-list") + expect(out_with_macos_man_workaround).to include("bundle-list") end it "aliases package to cache" do bundle "package --help" - expect(out).to include("bundle-cache") + expect(out_with_macos_man_workaround).to include("bundle-cache") end it "aliases pack to cache" do bundle "pack --help" - expect(out).to include("bundle-cache") + expect(out_with_macos_man_workaround).to include("bundle-cache") + end + + private + + # Some `man` (e.g., on macOS) always highlights the output even to + # non-tty. + def out_with_macos_man_workaround + out.gsub(/.[\b]/, "") end end @@ -113,36 +111,20 @@ RSpec.describe "bundle executable" do end end - context "when ENV['RUBYGEMS_GEMDEPS'] is set" do - it "displays a warning" do - gemfile bundled_app_gemfile, <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' - G - - bundle :install, :env => { "RUBYGEMS_GEMDEPS" => "foo" } - expect(err).to include("RUBYGEMS_GEMDEPS") - expect(err).to include("conflict with Bundler") - - bundle :install, :env => { "RUBYGEMS_GEMDEPS" => "" } - expect(err).not_to include("RUBYGEMS_GEMDEPS") - end - end - context "with --verbose" do it "prints the running command" do - gemfile "" + gemfile "source \"#{file_uri_for(gem_repo1)}\"" bundle "info bundler", :verbose => true expect(out).to start_with("Running `bundle info bundler --verbose` with bundler #{Bundler::VERSION}") end it "doesn't print defaults" do - install_gemfile "", :verbose => true + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"", :verbose => true expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}") end it "doesn't print defaults" do - install_gemfile "", :verbose => true + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"", :verbose => true expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}") end end diff --git a/spec/bundler/bundler/compact_index_client/updater_spec.rb b/spec/bundler/bundler/compact_index_client/updater_spec.rb index d8bb15df7e..fe417e3920 100644 --- a/spec/bundler/bundler/compact_index_client/updater_spec.rb +++ b/spec/bundler/bundler/compact_index_client/updater_spec.rb @@ -36,14 +36,24 @@ RSpec.describe Bundler::CompactIndexClient::Updater do end end - context "when bundler doesn't have permissions on Dir.tmpdir" do - it "Errno::EACCES is raised" do - local_path # create local path before stubbing mktmpdir - allow(Bundler::Dir).to receive(:mktmpdir) { raise Errno::EACCES } + context "when receiving non UTF-8 data and default internal encoding set to ASCII" do + let(:response) { double(:response, :body => "\x8B".b) } + + it "works just fine" do + old_verbose = $VERBOSE + previous_internal_encoding = Encoding.default_internal + + begin + $VERBOSE = false + Encoding.default_internal = "ASCII" + expect(response).to receive(:[]).with("ETag") { nil } + expect(fetcher).to receive(:call) { response } - expect do updater.update(local_path, remote_path) - end.to raise_error(Bundler::PermissionError) + ensure + Encoding.default_internal = previous_internal_encoding + $VERBOSE = old_verbose + end end end end diff --git a/spec/bundler/bundler/definition_spec.rb b/spec/bundler/bundler/definition_spec.rb index dcdde75315..cd70d77cba 100644 --- a/spec/bundler/bundler/definition_spec.rb +++ b/spec/bundler/bundler/definition_spec.rb @@ -49,7 +49,7 @@ RSpec.describe Bundler::Definition do bundle :install, :env => { "DEBUG" => "1" } expect(out).to match(/re-resolving dependencies/) - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: @@ -86,7 +86,7 @@ RSpec.describe Bundler::Definition do bundle :check, :env => { "DEBUG" => "1" } expect(out).to match(/using resolution from the lockfile/) - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: @@ -119,15 +119,14 @@ RSpec.describe Bundler::Definition do bundle :check, :env => { "DEBUG" => "1" } expect(out).to match(/using resolution from the lockfile/) - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo1)}/ specs: only_java (1.1-java) PLATFORMS - java - #{lockfile_platforms} + #{lockfile_platforms_for(["java"] + local_platforms)} DEPENDENCIES only_java @@ -146,7 +145,7 @@ RSpec.describe Bundler::Definition do bundle :check, :env => { "DEBUG" => "1" } expect(out).to match(/using resolution from the lockfile/) - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -194,7 +193,7 @@ RSpec.describe Bundler::Definition do context "eager unlock" do let(:source_list) do Bundler::SourceList.new.tap do |source_list| - source_list.global_rubygems_source = file_uri_for(gem_repo4) + source_list.add_global_rubygems_remote(file_uri_for(gem_repo4)) end end @@ -258,7 +257,7 @@ RSpec.describe Bundler::Definition do bundled_app_lock, updated_deps_in_gemfile, source_list, - :gems => ["shared_owner_a"], :lock_shared_dependencies => true + :gems => ["shared_owner_a"], :conservative => true ) locked = definition.send(:converge_locked_specs).map(&:name) expect(locked).to eq %w[isolated_dep isolated_owner shared_dep shared_owner_b] @@ -268,33 +267,6 @@ RSpec.describe Bundler::Definition do end end - describe "find_resolved_spec" do - it "with no platform set in SpecSet" do - ss = Bundler::SpecSet.new([build_stub_spec("a", "1.0"), build_stub_spec("b", "1.0")]) - dfn = Bundler::Definition.new(nil, [], mock_source_list, true) - dfn.instance_variable_set("@specs", ss) - found = dfn.find_resolved_spec(build_spec("a", "0.9", "ruby").first) - expect(found.name).to eq "a" - expect(found.version.to_s).to eq "1.0" - end - end - - describe "find_indexed_specs" do - it "with no platform set in indexed specs" do - index = Bundler::Index.new - %w[1.0.0 1.0.1 1.1.0].each {|v| index << build_stub_spec("foo", v) } - - dfn = Bundler::Definition.new(nil, [], mock_source_list, true) - dfn.instance_variable_set("@index", index) - found = dfn.find_indexed_specs(build_spec("foo", "0.9", "ruby").first) - expect(found.length).to eq 3 - end - end - - def build_stub_spec(name, version) - Bundler::StubSpecification.new(name, version, nil, nil) - end - def mock_source_list Class.new do def all_sources diff --git a/spec/bundler/bundler/dep_proxy_spec.rb b/spec/bundler/bundler/dep_proxy_spec.rb index 84243d2ee2..8d02a33725 100644 --- a/spec/bundler/bundler/dep_proxy_spec.rb +++ b/spec/bundler/bundler/dep_proxy_spec.rb @@ -22,7 +22,7 @@ RSpec.describe Bundler::DepProxy do end describe "frozen" do - if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.5.0") + if Gem.ruby_version >= Gem::Version.new("2.5.0") error = Object.const_get("FrozenError") else error = RuntimeError diff --git a/spec/bundler/bundler/digest_spec.rb b/spec/bundler/bundler/digest_spec.rb new file mode 100644 index 0000000000..d6bb043fd0 --- /dev/null +++ b/spec/bundler/bundler/digest_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "digest" +require "bundler/digest" + +RSpec.describe Bundler::Digest do + context "SHA1" do + subject { Bundler::Digest } + let(:stdlib) { ::Digest::SHA1 } + + it "is compatible with stdlib" do + ["foo", "skfjsdlkfjsdf", "3924m", "ldskfj"].each do |payload| + expect(subject.sha1(payload)).to be == stdlib.hexdigest(payload) + end + end + end +end diff --git a/spec/bundler/bundler/dsl_spec.rb b/spec/bundler/bundler/dsl_spec.rb index ff87b57886..a44a12924c 100644 --- a/spec/bundler/bundler/dsl_spec.rb +++ b/spec/bundler/bundler/dsl_spec.rb @@ -25,7 +25,46 @@ RSpec.describe Bundler::Dsl do expect { subject.git_source(:example) }.to raise_error(Bundler::InvalidOption) end - context "default hosts", :bundler => "2" do + it "converts :github PR to URI using https" do + subject.gem("sparks", :github => "https://github.com/indirect/sparks/pull/5") + github_uri = "https://github.com/indirect/sparks.git" + expect(subject.dependencies.first.source.uri).to eq(github_uri) + expect(subject.dependencies.first.source.branch).to eq("refs/pull/5/head") + end + + it "rejects :github PR URI with a branch, ref or tag" do + expect do + subject.gem("sparks", :github => "https://github.com/indirect/sparks/pull/5", :branch => "foo") + end.to raise_error( + Bundler::GemfileError, + %(The :branch option can't be used with `github: "https://github.com/indirect/sparks/pull/5"`), + ) + + expect do + subject.gem("sparks", :github => "https://github.com/indirect/sparks/pull/5", :ref => "foo") + end.to raise_error( + Bundler::GemfileError, + %(The :ref option can't be used with `github: "https://github.com/indirect/sparks/pull/5"`), + ) + + expect do + subject.gem("sparks", :github => "https://github.com/indirect/sparks/pull/5", :tag => "foo") + end.to raise_error( + Bundler::GemfileError, + %(The :tag option can't be used with `github: "https://github.com/indirect/sparks/pull/5"`), + ) + end + + it "rejects :github with :git" do + expect do + subject.gem("sparks", :github => "indirect/sparks", :git => "https://github.com/indirect/sparks.git") + end.to raise_error( + Bundler::GemfileError, + %(The :git option can't be used with `github: "indirect/sparks"`), + ) + end + + context "default hosts", :bundler => "< 3" do it "converts :github to URI using https" do subject.gem("sparks", :github => "indirect/sparks") github_uri = "https://github.com/indirect/sparks.git" @@ -195,19 +234,6 @@ RSpec.describe Bundler::Dsl do # gem 'spree_api' # gem 'spree_backend' # end - describe "#github", :bundler => "< 3" do - it "from github" do - spree_gems = %w[spree_core spree_api spree_backend] - subject.github "spree" do - spree_gems.each {|spree_gem| subject.send :gem, spree_gem } - end - - subject.dependencies.each do |d| - expect(d.source.uri).to eq("https://github.com/spree/spree.git") - end - end - end - describe "#github" do it "from github" do spree_gems = %w[spree_core spree_api spree_backend] @@ -254,4 +280,21 @@ RSpec.describe Bundler::Dsl do end end end + + describe "#check_primary_source_safety" do + context "when a global source is not defined implicitly" do + it "will raise a major deprecation warning" do + not_a_global_source = double("not-a-global-source", :no_remotes? => true) + allow(Bundler::Source::Rubygems).to receive(:new).and_return(not_a_global_source) + + warning = "This Gemfile does not include an explicit global source. " \ + "Not using an explicit global source may result in a different lockfile being generated depending on " \ + "the gems you have installed locally before bundler is run. " \ + "Instead, define a global source in your Gemfile like this: source \"https://rubygems.org\"." + expect(Bundler::SharedHelpers).to receive(:major_deprecation).with(2, warning) + + subject.check_primary_source_safety + end + end + end end diff --git a/spec/bundler/bundler/env_spec.rb b/spec/bundler/bundler/env_spec.rb index ac65c34b0d..8cd59311b2 100644 --- a/spec/bundler/bundler/env_spec.rb +++ b/spec/bundler/bundler/env_spec.rb @@ -73,7 +73,7 @@ RSpec.describe Bundler::Env do context "when there is a Gemfile and a lockfile and print_gemfile is true" do before do - gemfile "gem 'rack', '1.0.0'" + gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem 'rack', '1.0.0'" lockfile <<-L GEM @@ -112,6 +112,34 @@ RSpec.describe Bundler::Env do end end + context "when there's bundler config with credentials" do + before do + bundle "config set https://localgemserver.test/ user:pass" + end + + let(:output) { described_class.report(:print_gemfile => true) } + + it "prints the config with redacted values" do + expect(output).to include("https://localgemserver.test") + expect(output).to include("user:[REDACTED]") + expect(output).to_not include("user:pass") + end + end + + context "when there's bundler config with OAuth token credentials" do + before do + bundle "config set https://localgemserver.test/ api_token:x-oauth-basic" + end + + let(:output) { described_class.report(:print_gemfile => true) } + + it "prints the config with redacted values" do + expect(output).to include("https://localgemserver.test") + expect(output).to include("[REDACTED]:x-oauth-basic") + expect(output).to_not include("api_token:x-oauth-basic") + end + end + context "when Gemfile contains a gemspec and print_gemspecs is true" do let(:gemspec) do strip_whitespace(<<-GEMSPEC) @@ -123,7 +151,7 @@ RSpec.describe Bundler::Env do end before do - gemfile("gemspec") + gemfile("source \"#{file_uri_for(gem_repo1)}\"; gemspec") File.open(bundled_app.join("foo.gemspec"), "wb") do |f| f.write(gemspec) diff --git a/spec/bundler/bundler/fetcher/downloader_spec.rb b/spec/bundler/bundler/fetcher/downloader_spec.rb index ba8451d9fa..94a0993a54 100644 --- a/spec/bundler/bundler/fetcher/downloader_spec.rb +++ b/spec/bundler/bundler/fetcher/downloader_spec.rb @@ -83,6 +83,11 @@ RSpec.describe Bundler::Fetcher::Downloader do /Authentication is required for www.uri-to-fetch.com/) end + it "should raise a Bundler::Fetcher::AuthenticationRequiredError with advices" do + expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError, + /`bundle config set --global www\.uri-to-fetch\.com username:password`.*`BUNDLE_WWW__URI___TO___FETCH__COM`/m) + end + context "when the there are credentials provided in the request" do let(:uri) { Bundler::URI("http://user:password@www.uri-to-fetch.com") } @@ -188,7 +193,7 @@ RSpec.describe Bundler::Fetcher::Downloader do let(:message) { "undefined method 'undefined_method_call'" } it "should raise the original NoMethodError" do - expect { subject.request(uri, options) }.to raise_error(NoMethodError, "undefined method 'undefined_method_call'") + expect { subject.request(uri, options) }.to raise_error(NoMethodError, /undefined method 'undefined_method_call'/) end end end @@ -226,16 +231,7 @@ RSpec.describe Bundler::Fetcher::Downloader do end end - context "when error message is about getaddrinfo issues" do - let(:message) { "getaddrinfo: nodename nor servname provided for http://www.uri-to-fetch.com" } - - it "should raise a Bundler::Fetcher::NetworkDownError" do - expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError, - /Could not reach host www.uri-to-fetch.com/) - end - end - - context "when error message is about neither host down or getaddrinfo" do + context "when error message is not about host down" do let(:message) { "other error about network" } it "should raise a Bundler::HTTPError" do diff --git a/spec/bundler/bundler/fetcher/index_spec.rb b/spec/bundler/bundler/fetcher/index_spec.rb index b8ce46321e..f0db07583c 100644 --- a/spec/bundler/bundler/fetcher/index_spec.rb +++ b/spec/bundler/bundler/fetcher/index_spec.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "rubygems/remote_fetcher" + RSpec.describe Bundler::Fetcher::Index do let(:downloader) { nil } let(:remote) { nil } diff --git a/spec/bundler/bundler/gem_helper_spec.rb b/spec/bundler/bundler/gem_helper_spec.rb index d718615ad2..2c43719aa1 100644 --- a/spec/bundler/bundler/gem_helper_spec.rb +++ b/spec/bundler/bundler/gem_helper_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Bundler::GemHelper do let(:app_gemspec_path) { app_path.join("#{app_name}.gemspec") } before(:each) do - global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false", "BUNDLE_GEM__RUBOCOP" => "false", + global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false", "BUNDLE_GEM__LINTER" => "false", "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__CHANGELOG" => "false" bundle "gem #{app_name}" prepare_gemspec(app_gemspec_path) @@ -61,10 +61,16 @@ RSpec.describe Bundler::GemHelper do mock_confirm_message message end + def mock_checksum_message(name, version) + message = "#{name} #{version} checksum written to checksums/#{name}-#{version}.gem.sha512." + mock_confirm_message message + end + subject! { Bundler::GemHelper.new(app_path) } let(:app_version) { "0.1.0" } let(:app_gem_dir) { app_path.join("pkg") } let(:app_gem_path) { app_gem_dir.join("#{app_name}-#{app_version}.gem") } + let(:app_sha_path) { app_path.join("checksums", "#{app_name}-#{app_version}.gem.sha512") } let(:app_gemspec_content) { File.read(app_gemspec_path) } before(:each) do @@ -162,6 +168,37 @@ RSpec.describe Bundler::GemHelper do end end + describe "#build_checksum" do + context "when build was successful" do + it "creates .sha512 file" do + mock_build_message app_name, app_version + mock_checksum_message app_name, app_version + subject.build_checksum + expect(app_sha_path).to exist + end + end + context "when building in the current working directory" do + it "creates a .sha512 file" do + mock_build_message app_name, app_version + mock_checksum_message app_name, app_version + Dir.chdir app_path do + Bundler::GemHelper.new.build_checksum + end + expect(app_sha_path).to exist + end + end + context "when building in a location relative to the current working directory" do + it "creates a .sha512 file" do + mock_build_message app_name, app_version + mock_checksum_message app_name, app_version + Dir.chdir File.dirname(app_path) do + Bundler::GemHelper.new(File.basename(app_path)).build_checksum + end + expect(app_sha_path).to exist + end + end + end + describe "#install_gem" do context "when installation was successful" do it "gem is installed" do @@ -182,7 +219,7 @@ RSpec.describe Bundler::GemHelper do FileUtils.touch app_gem_path app_gem_path end - expect { subject.install_gem }.to raise_error(/Couldn't install gem/) + expect { subject.install_gem }.to raise_error(/Running `#{gem_bin} install #{app_gem_path}` failed/) end end end diff --git a/spec/bundler/bundler/installer/parallel_installer_spec.rb b/spec/bundler/bundler/installer/parallel_installer_spec.rb index ace5c1a23a..e680633862 100644 --- a/spec/bundler/bundler/installer/parallel_installer_spec.rb +++ b/spec/bundler/bundler/installer/parallel_installer_spec.rb @@ -44,4 +44,37 @@ The missing gems are: end end end + + context "when the spec set is not a valid resolution" do + let(:all_specs) do + [ + build_spec("cucumber", "4.1.0") {|s| s.runtime "diff-lcs", "< 1.4" }, + build_spec("diff-lcs", "1.4.4"), + ].flatten + end + + it "prints a warning" do + expect(Bundler.ui).to receive(:warn).with(<<-W.strip) +Your lockfile doesn't include a valid resolution. +You can fix this by regenerating your lockfile or trying to manually editing the bad locked gems to a version that satisfies all dependencies. +The unmet dependencies are: +* diff-lcs (< 1.4), depended upon cucumber-4.1.0, unsatisfied by diff-lcs-1.4.4 + W + subject.check_for_unmet_dependencies + end + end + + context "when the spec set is a valid resolution" do + let(:all_specs) do + [ + build_spec("cucumber", "4.1.0") {|s| s.runtime "diff-lcs", "< 1.4" }, + build_spec("diff-lcs", "1.3"), + ].flatten + end + + it "doesn't print a warning" do + expect(Bundler.ui).not_to receive(:warn) + subject.check_for_unmet_dependencies + end + end end diff --git a/spec/bundler/bundler/installer/spec_installation_spec.rb b/spec/bundler/bundler/installer/spec_installation_spec.rb index a9cf09a372..e63ef26cb3 100644 --- a/spec/bundler/bundler/installer/spec_installation_spec.rb +++ b/spec/bundler/bundler/installer/spec_installation_spec.rb @@ -8,6 +8,10 @@ RSpec.describe Bundler::ParallelInstaller::SpecInstallation do def a_spec.name "I like tests" end + + def a_spec.full_name + "I really like tests" + end a_spec end diff --git a/spec/bundler/bundler/plugin/dsl_spec.rb b/spec/bundler/bundler/plugin/dsl_spec.rb index be23db3bba..00e39dca69 100644 --- a/spec/bundler/bundler/plugin/dsl_spec.rb +++ b/spec/bundler/bundler/plugin/dsl_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Bundler::Plugin::DSL do expect(dsl.inferred_plugins).to eq(["bundler-source-news"]) end - it "registers a source type plugin only once for multiple declataions" do + it "registers a source type plugin only once for multiple declarations" do expect(dsl).to receive(:plugin).with("bundler-source-news").and_call_original.once dsl.source("some_random_url", :type => "news") {} diff --git a/spec/bundler/bundler/plugin/index_spec.rb b/spec/bundler/bundler/plugin/index_spec.rb index 925dc558ac..d34b0de342 100644 --- a/spec/bundler/bundler/plugin/index_spec.rb +++ b/spec/bundler/bundler/plugin/index_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Bundler::Plugin::Index do before do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - gemfile "" + gemfile "source \"#{file_uri_for(gem_repo1)}\"" path = lib_path(plugin_name) index.register_plugin("new-plugin", path.to_s, [path.join("lib").to_s], commands, sources, hooks) end @@ -22,7 +22,7 @@ RSpec.describe Bundler::Plugin::Index do expect(index.plugin_path(plugin_name)).to eq(lib_path(plugin_name)) end - it "load_paths is available for retrival" do + it "load_paths is available for retrieval" do expect(index.load_paths(plugin_name)).to eq([lib_path(plugin_name).join("lib").to_s]) end @@ -98,7 +98,13 @@ RSpec.describe Bundler::Plugin::Index do expect(index.hook_plugins("after-bar")).to eq([plugin_name]) end - context "that are not registered", :focused do + it "is gone after unregistration" do + expect(index.index_file.read).to include("after-bar:\n - \"new-plugin\"\n") + index.unregister_plugin(plugin_name) + expect(index.index_file.read).to_not include("after-bar:\n - \n") + end + + context "that are not registered" do let(:file) { double("index-file") } before do diff --git a/spec/bundler/bundler/plugin_spec.rb b/spec/bundler/bundler/plugin_spec.rb index 8c95723bcc..25cae014dd 100644 --- a/spec/bundler/bundler/plugin_spec.rb +++ b/spec/bundler/bundler/plugin_spec.rb @@ -67,6 +67,8 @@ RSpec.describe Bundler::Plugin do end it "passes the name and options to installer" do + allow(index).to receive(:installed?). + with("new-plugin") allow(installer).to receive(:install).with(["new-plugin"], opts) do { "new-plugin" => spec } end.once @@ -75,6 +77,8 @@ RSpec.describe Bundler::Plugin do end it "validates the installed plugin" do + allow(index).to receive(:installed?). + with("new-plugin") allow(subject). to receive(:validate_plugin!).with(lib_path("new-plugin")).once @@ -82,6 +86,8 @@ RSpec.describe Bundler::Plugin do end it "registers the plugin with index" do + allow(index).to receive(:installed?). + with("new-plugin") allow(index).to receive(:register_plugin). with("new-plugin", lib_path("new-plugin").to_s, [lib_path("new-plugin").join("lib").to_s], []).once subject.install ["new-plugin"], opts @@ -98,6 +104,7 @@ RSpec.describe Bundler::Plugin do end.once allow(subject).to receive(:validate_plugin!).twice + allow(index).to receive(:installed?).twice allow(index).to receive(:register_plugin).twice subject.install ["new-plugin", "another-plugin"], opts end @@ -112,6 +119,7 @@ RSpec.describe Bundler::Plugin do before do allow(Plugin::DSL).to receive(:new) { builder } allow(builder).to receive(:eval_gemfile).with(gemfile) + allow(builder).to receive(:check_primary_source_safety) allow(builder).to receive(:to_definition) { definition } allow(builder).to receive(:inferred_plugins) { [] } end @@ -278,7 +286,7 @@ RSpec.describe Bundler::Plugin do Bundler::Plugin::Events.send(:define, :EVENT_2, "event-2") allow(index).to receive(:hook_plugins).with(Bundler::Plugin::Events::EVENT_1). - and_return(["foo-plugin"]) + and_return(["foo-plugin", "", nil]) allow(index).to receive(:hook_plugins).with(Bundler::Plugin::Events::EVENT_2). and_return(["foo-plugin"]) allow(index).to receive(:plugin_path).with("foo-plugin").and_return(path) diff --git a/spec/bundler/bundler/rubygems_integration_spec.rb b/spec/bundler/bundler/rubygems_integration_spec.rb index 11fa2f4e0d..7557c806fe 100644 --- a/spec/bundler/bundler/rubygems_integration_spec.rb +++ b/spec/bundler/bundler/rubygems_integration_spec.rb @@ -43,12 +43,10 @@ RSpec.describe Bundler::RubygemsIntegration do describe "#download_gem" do let(:bundler_retry) { double(Bundler::Retry) } - let(:retry) { double("Bundler::Retry") } - let(:uri) { Bundler::URI.parse("https://foo.bar") } - let(:path) { Gem.path.first } + let(:uri) { Bundler::URI.parse("https://foo.bar") } + let(:cache_dir) { "#{Gem.path.first}/cache" } let(:spec) do - spec = Bundler::RemoteSpecification.new("Foo", Gem::Version.new("2.5.2"), - Gem::Platform::RUBY, nil) + spec = Gem::Specification.new("Foo", Gem::Version.new("2.5.2")) spec.remote = Bundler::Source::Rubygems::Remote.new(uri.to_s) spec end @@ -56,13 +54,13 @@ RSpec.describe Bundler::RubygemsIntegration do it "successfully downloads gem with retries" do expect(Bundler.rubygems).to receive(:gem_remote_fetcher).and_return(fetcher) - expect(fetcher).to receive(:headers=).with("X-Gemfile-Source" => "https://foo.bar") + expect(fetcher).to receive(:headers=).with({ "X-Gemfile-Source" => "https://foo.bar" }) expect(Bundler::Retry).to receive(:new).with("download gem from #{uri}/"). and_return(bundler_retry) expect(bundler_retry).to receive(:attempts).and_yield - expect(fetcher).to receive(:download).with(spec, uri, path) + expect(fetcher).to receive(:cache_update_path) - Bundler.rubygems.download_gem(spec, uri, path) + Bundler.rubygems.download_gem(spec, uri, cache_dir) end end @@ -78,7 +76,7 @@ RSpec.describe Bundler::RubygemsIntegration do it "sets the 'X-Gemfile-Source' header containing the original source" do expect(Bundler.rubygems).to receive(:gem_remote_fetcher).twice.and_return(fetcher) - expect(fetcher).to receive(:headers=).with("X-Gemfile-Source" => "http://zombo.com").twice + expect(fetcher).to receive(:headers=).with({ "X-Gemfile-Source" => "http://zombo.com" }).twice expect(fetcher).to receive(:fetch_path).with(uri + "specs.4.8.gz").and_return(specs_response) expect(fetcher).to receive(:fetch_path).with(uri + "prerelease_specs.4.8.gz").and_return(prerelease_specs_response) result = Bundler.rubygems.fetch_all_remote_specs(remote_with_mirror) diff --git a/spec/bundler/bundler/settings_spec.rb b/spec/bundler/bundler/settings_spec.rb index 116a038445..24e3de7ba8 100644 --- a/spec/bundler/bundler/settings_spec.rb +++ b/spec/bundler/bundler/settings_spec.rb @@ -64,13 +64,10 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow describe "#global_config_file" do context "when $HOME is not accessible" do - context "when $TMPDIR is not writable" do - it "does not raise" do - expect(Bundler.rubygems).to receive(:user_home).twice.and_return(nil) - expect(Bundler).to receive(:tmp).twice.and_raise(Errno::EROFS, "Read-only file system @ dir_s_mkdir - /tmp/bundler") + it "does not raise" do + expect(Bundler.rubygems).to receive(:user_home).twice.and_return(nil) - expect(subject.send(:global_config_file)).to be_nil - end + expect(subject.send(:global_config_file)).to be_nil end end end @@ -312,16 +309,26 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow describe "BUNDLE_ keys format" do let(:settings) { described_class.new(bundled_app(".bundle")) } - it "converts older keys without double dashes" do + it "converts older keys without double underscore" do config("BUNDLE_MY__PERSONAL.RACK" => "~/Work/git/rack") expect(settings["my.personal.rack"]).to eq("~/Work/git/rack") end - it "converts older keys without trailing slashes and double dashes" do + it "converts older keys without trailing slashes and double underscore" do config("BUNDLE_MIRROR__HTTPS://RUBYGEMS.ORG" => "http://rubygems-mirror.org") expect(settings["mirror.https://rubygems.org/"]).to eq("http://rubygems-mirror.org") end + it "converts older keys with dashes" do + config("BUNDLE_MY-PERSONAL-SERVER__ORG" => "my-personal-server.org") + expect(Bundler.ui).to receive(:warn).with( + "Your #{bundled_app(".bundle/config")} config includes `BUNDLE_MY-PERSONAL-SERVER__ORG`, which contains the dash character (`-`).\n" \ + "This is deprecated, because configuration through `ENV` should be possible, but `ENV` keys cannot include dashes.\n" \ + "Please edit #{bundled_app(".bundle/config")} and replace any dashes in configuration keys with a triple underscore (`___`)." + ) + expect(settings["my-personal-server.org"]).to eq("my-personal-server.org") + end + it "reads newer keys format properly" do config("BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/" => "http://rubygems-mirror.org") expect(settings["mirror.https://rubygems.org/"]).to eq("http://rubygems-mirror.org") diff --git a/spec/bundler/bundler/source/git/git_proxy_spec.rb b/spec/bundler/bundler/source/git/git_proxy_spec.rb index 97f06973cb..cffd72cc3f 100644 --- a/spec/bundler/bundler/source/git/git_proxy_spec.rb +++ b/spec/bundler/bundler/source/git/git_proxy_spec.rb @@ -11,21 +11,21 @@ RSpec.describe Bundler::Source::Git::GitProxy do context "with configured credentials" do it "adds username and password to URI" do Bundler.settings.temporary(uri => "u:p") do - expect(subject).to receive(:git_retry).with("clone", "https://u:p@github.com/rubygems/rubygems.git", any_args) + expect(subject).to receive(:git_retry).with("clone", "--bare", "--no-hardlinks", "--quiet", "--", "https://u:p@github.com/rubygems/rubygems.git", path.to_s) subject.checkout end end it "adds username and password to URI for host" do Bundler.settings.temporary("github.com" => "u:p") do - expect(subject).to receive(:git_retry).with("clone", "https://u:p@github.com/rubygems/rubygems.git", any_args) + expect(subject).to receive(:git_retry).with("clone", "--bare", "--no-hardlinks", "--quiet", "--", "https://u:p@github.com/rubygems/rubygems.git", path.to_s) subject.checkout end end it "does not add username and password to mismatched URI" do Bundler.settings.temporary("https://u:p@github.com/rubygems/rubygems-mismatch.git" => "u:p") do - expect(subject).to receive(:git_retry).with("clone", uri, any_args) + expect(subject).to receive(:git_retry).with("clone", "--bare", "--no-hardlinks", "--quiet", "--", uri, path.to_s) subject.checkout end end @@ -34,7 +34,7 @@ RSpec.describe Bundler::Source::Git::GitProxy do Bundler.settings.temporary("github.com" => "u:p") do original = "https://orig:info@github.com/rubygems/rubygems.git" subject = described_class.new(Pathname("path"), original, "HEAD") - expect(subject).to receive(:git_retry).with("clone", original, any_args) + expect(subject).to receive(:git_retry).with("clone", "--bare", "--no-hardlinks", "--quiet", "--", original, path.to_s) subject.checkout end end @@ -148,4 +148,24 @@ RSpec.describe Bundler::Source::Git::GitProxy do end end end + + it "doesn't allow arbitrary code execution through Gemfile uris with a leading dash" do + gemfile <<~G + gem "poc", git: "-u./pay:load.sh" + G + + file = bundled_app("pay:load.sh") + + create_file file, <<~RUBY + #!/bin/sh + + touch #{bundled_app("canary")} + RUBY + + FileUtils.chmod("+x", file) + + bundle :lock, :raise_on_error => false + + expect(Pathname.new(bundled_app("canary"))).not_to exist + end end diff --git a/spec/bundler/bundler/source/git_spec.rb b/spec/bundler/bundler/source/git_spec.rb index 6668b6e69a..ed6dc3cd29 100644 --- a/spec/bundler/bundler/source/git_spec.rb +++ b/spec/bundler/bundler/source/git_spec.rb @@ -24,5 +24,50 @@ RSpec.describe Bundler::Source::Git do expect(subject.to_s).to eq "https://x-oauth-basic@github.com/foo/bar.git" end end + + context "when the source has a glob specifier" do + let(:glob) { "bar/baz/*.gemspec" } + let(:options) do + { "uri" => uri, "glob" => glob } + end + + it "includes it" do + expect(subject.to_s).to eq "https://github.com/foo/bar.git (glob: bar/baz/*.gemspec)" + end + end + + context "when the source has a reference" do + let(:git_proxy_stub) do + instance_double(Bundler::Source::Git::GitProxy, :revision => "123abc", :branch => "v1.0.0") + end + let(:options) do + { "uri" => uri, "ref" => "v1.0.0" } + end + + before do + allow(Bundler::Source::Git::GitProxy).to receive(:new).and_return(git_proxy_stub) + end + + it "includes it" do + expect(subject.to_s).to eq "https://github.com/foo/bar.git (at v1.0.0@123abc)" + end + end + + context "when the source has both reference and glob specifiers" do + let(:git_proxy_stub) do + instance_double(Bundler::Source::Git::GitProxy, :revision => "123abc", :branch => "v1.0.0") + end + let(:options) do + { "uri" => uri, "ref" => "v1.0.0", "glob" => "gems/foo/*.gemspec" } + end + + before do + allow(Bundler::Source::Git::GitProxy).to receive(:new).and_return(git_proxy_stub) + end + + it "includes both" do + expect(subject.to_s).to eq "https://github.com/foo/bar.git (at v1.0.0@123abc, glob: gems/foo/*.gemspec)" + end + end end end diff --git a/spec/bundler/bundler/source/rubygems_spec.rb b/spec/bundler/bundler/source/rubygems_spec.rb index 7c457a7265..884fa81046 100644 --- a/spec/bundler/bundler/source/rubygems_spec.rb +++ b/spec/bundler/bundler/source/rubygems_spec.rb @@ -30,4 +30,18 @@ RSpec.describe Bundler::Source::Rubygems do end end end + + describe "#no_remotes?" do + context "when no remote provided" do + it "returns a truthy value" do + expect(described_class.new("remotes" => []).no_remotes?).to be_truthy + end + end + + context "when a remote provided" do + it "returns a falsey value" do + expect(described_class.new("remotes" => ["https://rubygems.org"]).no_remotes?).to be_falsey + end + end + end end diff --git a/spec/bundler/bundler/source_list_spec.rb b/spec/bundler/bundler/source_list_spec.rb index 3a0691b959..f860e9ff58 100644 --- a/spec/bundler/bundler/source_list_spec.rb +++ b/spec/bundler/bundler/source_list_spec.rb @@ -115,15 +115,15 @@ RSpec.describe Bundler::SourceList do end end - describe "#add_rubygems_remote", :bundler => "< 3" do - let!(:returned_source) { source_list.add_rubygems_remote("https://rubygems.org/") } + describe "#add_global_rubygems_remote" do + let!(:returned_source) { source_list.add_global_rubygems_remote("https://rubygems.org/") } it "returns the aggregate rubygems source" do expect(returned_source).to be_instance_of(Bundler::Source::Rubygems) end it "adds the provided remote to the beginning of the aggregate source" do - source_list.add_rubygems_remote("https://othersource.org") + source_list.add_global_rubygems_remote("https://othersource.org") expect(returned_source.remotes).to eq [ Bundler::URI("https://othersource.org/"), Bundler::URI("https://rubygems.org/"), @@ -212,22 +212,22 @@ RSpec.describe Bundler::SourceList do describe "#path_sources" do it "returns an empty array when no path sources have been added" do - source_list.add_rubygems_remote("https://rubygems.org") + source_list.add_global_rubygems_remote("https://rubygems.org") source_list.add_git_source("uri" => "git://host/path.git") expect(source_list.path_sources).to be_empty end it "returns path sources in the reverse order that they were added" do source_list.add_git_source("uri" => "git://third-git.org/path.git") - source_list.add_rubygems_remote("https://fifth-rubygems.org") + source_list.add_global_rubygems_remote("https://fifth-rubygems.org") source_list.add_path_source("path" => "/third/path/to/gem") - source_list.add_rubygems_remote("https://fourth-rubygems.org") + source_list.add_global_rubygems_remote("https://fourth-rubygems.org") source_list.add_path_source("path" => "/second/path/to/gem") - source_list.add_rubygems_remote("https://third-rubygems.org") + source_list.add_global_rubygems_remote("https://third-rubygems.org") source_list.add_git_source("uri" => "git://second-git.org/path.git") - source_list.add_rubygems_remote("https://second-rubygems.org") + source_list.add_global_rubygems_remote("https://second-rubygems.org") source_list.add_path_source("path" => "/first/path/to/gem") - source_list.add_rubygems_remote("https://first-rubygems.org") + source_list.add_global_rubygems_remote("https://first-rubygems.org") source_list.add_git_source("uri" => "git://first-git.org/path.git") expect(source_list.path_sources).to eq [ @@ -240,7 +240,7 @@ RSpec.describe Bundler::SourceList do describe "#git_sources" do it "returns an empty array when no git sources have been added" do - source_list.add_rubygems_remote("https://rubygems.org") + source_list.add_global_rubygems_remote("https://rubygems.org") source_list.add_path_source("path" => "/path/to/gem") expect(source_list.git_sources).to be_empty @@ -248,15 +248,15 @@ RSpec.describe Bundler::SourceList do it "returns git sources in the reverse order that they were added" do source_list.add_git_source("uri" => "git://third-git.org/path.git") - source_list.add_rubygems_remote("https://fifth-rubygems.org") + source_list.add_global_rubygems_remote("https://fifth-rubygems.org") source_list.add_path_source("path" => "/third/path/to/gem") - source_list.add_rubygems_remote("https://fourth-rubygems.org") + source_list.add_global_rubygems_remote("https://fourth-rubygems.org") source_list.add_path_source("path" => "/second/path/to/gem") - source_list.add_rubygems_remote("https://third-rubygems.org") + source_list.add_global_rubygems_remote("https://third-rubygems.org") source_list.add_git_source("uri" => "git://second-git.org/path.git") - source_list.add_rubygems_remote("https://second-rubygems.org") + source_list.add_global_rubygems_remote("https://second-rubygems.org") source_list.add_path_source("path" => "/first/path/to/gem") - source_list.add_rubygems_remote("https://first-rubygems.org") + source_list.add_global_rubygems_remote("https://first-rubygems.org") source_list.add_git_source("uri" => "git://first-git.org/path.git") expect(source_list.git_sources).to eq [ @@ -269,7 +269,7 @@ RSpec.describe Bundler::SourceList do describe "#plugin_sources" do it "returns an empty array when no plugin sources have been added" do - source_list.add_rubygems_remote("https://rubygems.org") + source_list.add_global_rubygems_remote("https://rubygems.org") source_list.add_path_source("path" => "/path/to/gem") expect(source_list.plugin_sources).to be_empty @@ -279,13 +279,13 @@ RSpec.describe Bundler::SourceList do source_list.add_plugin_source("new_source", "uri" => "https://third-git.org/path.git") source_list.add_git_source("https://new-git.org") source_list.add_path_source("path" => "/third/path/to/gem") - source_list.add_rubygems_remote("https://fourth-rubygems.org") + source_list.add_global_rubygems_remote("https://fourth-rubygems.org") source_list.add_path_source("path" => "/second/path/to/gem") - source_list.add_rubygems_remote("https://third-rubygems.org") + source_list.add_global_rubygems_remote("https://third-rubygems.org") source_list.add_plugin_source("new_source", "uri" => "git://second-git.org/path.git") - source_list.add_rubygems_remote("https://second-rubygems.org") + source_list.add_global_rubygems_remote("https://second-rubygems.org") source_list.add_path_source("path" => "/first/path/to/gem") - source_list.add_rubygems_remote("https://first-rubygems.org") + source_list.add_global_rubygems_remote("https://first-rubygems.org") source_list.add_plugin_source("new_source", "uri" => "git://first-git.org/path.git") expect(source_list.plugin_sources).to eq [ @@ -339,7 +339,7 @@ RSpec.describe Bundler::SourceList do describe "#get" do context "when it includes an equal source" do let(:rubygems_source) { Bundler::Source::Rubygems.new("remotes" => ["https://rubygems.org"]) } - before { @equal_source = source_list.add_rubygems_remote("https://rubygems.org") } + before { @equal_source = source_list.add_global_rubygems_remote("https://rubygems.org") } it "returns the equal source" do expect(source_list.get(rubygems_source)).to be @equal_source @@ -372,26 +372,7 @@ RSpec.describe Bundler::SourceList do source_list.add_git_source("uri" => "git://first-git.org/path.git") end - it "combines the rubygems sources into a single instance, removing duplicate remotes from the end", :bundler => "< 3" do - expect(source_list.lock_sources).to eq [ - Bundler::Source::Git.new("uri" => "git://first-git.org/path.git"), - Bundler::Source::Git.new("uri" => "git://second-git.org/path.git"), - Bundler::Source::Git.new("uri" => "git://third-git.org/path.git"), - ASourcePlugin.new("uri" => "https://second-plugin.org/random"), - ASourcePlugin.new("uri" => "https://third-bar.org/foo"), - Bundler::Source::Path.new("path" => "/first/path/to/gem"), - Bundler::Source::Path.new("path" => "/second/path/to/gem"), - Bundler::Source::Path.new("path" => "/third/path/to/gem"), - Bundler::Source::Rubygems.new("remotes" => [ - "https://duplicate-rubygems.org", - "https://first-rubygems.org", - "https://second-rubygems.org", - "https://third-rubygems.org", - ]), - ] - end - - it "returns all sources, without combining rubygems sources", :bundler => "3" do + it "returns all sources, without combining rubygems sources" do expect(source_list.lock_sources).to eq [ Bundler::Source::Git.new("uri" => "git://first-git.org/path.git"), Bundler::Source::Git.new("uri" => "git://second-git.org/path.git"), @@ -460,4 +441,19 @@ RSpec.describe Bundler::SourceList do source_list.remote! end end + + describe "implicit_global_source?" do + context "when a global rubygem source provided" do + it "returns a falsy value" do + source_list.add_global_rubygems_remote("https://rubygems.org") + + expect(source_list.implicit_global_source?).to be_falsey + end + end + context "when no global rubygem source provided" do + it "returns a truthy value" do + expect(source_list.implicit_global_source?).to be_truthy + end + end + end end diff --git a/spec/bundler/bundler/stub_specification_spec.rb b/spec/bundler/bundler/stub_specification_spec.rb index 0a54f9d7b5..fb612813c2 100644 --- a/spec/bundler/bundler/stub_specification_spec.rb +++ b/spec/bundler/bundler/stub_specification_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Bundler::StubSpecification do s.name = "gemname" s.version = "1.0.0" s.loaded_from = __FILE__ + s.extensions = "ext/gemname" end described_class.from_stub(gemspec) @@ -17,4 +18,30 @@ RSpec.describe Bundler::StubSpecification do expect(stub).to be(with_bundler_stub_spec) end end + + describe "#manually_installed?" do + it "returns true if installed_by_version is nil or 0" do + stub = described_class.from_stub(with_bundler_stub_spec) + expect(stub.manually_installed?).to be true + end + + it "returns false if installed_by_version is greater than 0" do + stub = described_class.from_stub(with_bundler_stub_spec) + stub.installed_by_version = Gem::Version.new(1) + expect(stub.manually_installed?).to be false + end + end + + describe "#missing_extensions?" do + it "returns false if manually_installed?" do + stub = described_class.from_stub(with_bundler_stub_spec) + expect(stub.missing_extensions?).to be false + end + + it "returns true if not manually_installed?" do + stub = described_class.from_stub(with_bundler_stub_spec) + stub.installed_by_version = Gem::Version.new(1) + expect(stub.missing_extensions?).to be true + end + end end diff --git a/spec/bundler/bundler/worker_spec.rb b/spec/bundler/bundler/worker_spec.rb index 2e5642709d..e4ebbd2932 100644 --- a/spec/bundler/bundler/worker_spec.rb +++ b/spec/bundler/bundler/worker_spec.rb @@ -19,4 +19,51 @@ RSpec.describe Bundler::Worker do end end end + + describe "handling interrupts" do + let(:status) do + pid = Process.fork do + $stderr.reopen File.new("/dev/null", "w") + Signal.trap "INT", previous_interrupt_handler + subject.enq "a" + subject.stop unless interrupt_before_stopping + Process.kill "INT", Process.pid + end + + Process.wait2(pid).last + end + + before do + skip "requires Process.fork" unless Process.respond_to?(:fork) + end + + context "when interrupted before stopping" do + let(:interrupt_before_stopping) { true } + let(:previous_interrupt_handler) { ->(*) { exit 0 } } + + it "aborts" do + expect(status.exitstatus).to eq(1) + end + end + + context "when interrupted after stopping" do + let(:interrupt_before_stopping) { false } + + context "when the previous interrupt handler was the default" do + let(:previous_interrupt_handler) { "DEFAULT" } + + it "uses the default interrupt handler" do + expect(status).to be_signaled + end + end + + context "when the previous interrupt handler was customized" do + let(:previous_interrupt_handler) { ->(*) { exit 42 } } + + it "restores the custom interrupt handler after stopping" do + expect(status.exitstatus).to eq(42) + end + end + end + end end diff --git a/spec/bundler/cache/gems_spec.rb b/spec/bundler/cache/gems_spec.rb index 161ec64218..a8382a5d8c 100644 --- a/spec/bundler/cache/gems_spec.rb +++ b/spec/bundler/cache/gems_spec.rb @@ -4,6 +4,7 @@ RSpec.describe "bundle cache" do shared_examples_for "when there are only gemsources" do before :each do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'rack' G @@ -39,6 +40,7 @@ RSpec.describe "bundle cache" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack" G @@ -49,6 +51,7 @@ RSpec.describe "bundle cache" do system_gems "rack-1.0.0", :path => default_bundle_path gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack" G @@ -64,6 +67,7 @@ RSpec.describe "bundle cache" do cache_gems "rack-1.0.0" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack" G @@ -85,35 +89,33 @@ RSpec.describe "bundle cache" do it_behaves_like "when there are only gemsources" end - describe "when there is a built-in gem" do + describe "when there is a built-in gem", :ruby_repo do + let(:default_json_version) { ruby "gem 'json'; require 'json'; puts JSON::VERSION" } + before :each do build_repo2 do - build_gem "builtin_gem", "1.0.2" + build_gem "json", default_json_version end - build_gem "builtin_gem", "1.0.2", :to_system => true do |s| - s.summary = "This builtin_gem is bundled with Ruby" - end - - FileUtils.rm("#{system_gem_path}/cache/builtin_gem-1.0.2.gem") + build_gem "json", default_json_version, :to_system => true, :default => true end it "uses builtin gems when installing to system gems" do bundle "config set path.system true" - install_gemfile %(gem 'builtin_gem', '1.0.2') - expect(the_bundle).to include_gems("builtin_gem 1.0.2") + install_gemfile %(source "#{file_uri_for(gem_repo1)}"; gem 'json', '#{default_json_version}'), :verbose => true + expect(out).to include("Using json #{default_json_version}") end it "caches remote and builtin gems" do install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" - gem 'builtin_gem', '1.0.2' + gem 'json', '#{default_json_version}' gem 'rack', '1.0.0' G bundle :cache expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist - expect(bundled_app("vendor/cache/builtin_gem-1.0.2.gem")).to exist + expect(bundled_app("vendor/cache/json-#{default_json_version}.gem")).to exist end it "doesn't make remote request after caching the gem" do @@ -134,12 +136,13 @@ RSpec.describe "bundle cache" do bundle "config set path.system true" install_gemfile <<-G - gem 'builtin_gem', '1.0.2' + source "#{file_uri_for(gem_repo1)}" + gem 'json', '#{default_json_version}' G bundle :cache, :raise_on_error => false expect(exitstatus).to_not eq(0) - expect(err).to include("builtin_gem-1.0.2 is built in to Ruby, and can't be cached") + expect(err).to include("json-#{default_json_version} is built in to Ruby, and can't be cached") end end @@ -302,6 +305,7 @@ RSpec.describe "bundle cache" do :path => bundled_app("vendor/cache") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo-bundler" G diff --git a/spec/bundler/cache/git_spec.rb b/spec/bundler/cache/git_spec.rb index 25f12a9e87..10e44656b8 100644 --- a/spec/bundler/cache/git_spec.rb +++ b/spec/bundler/cache/git_spec.rb @@ -18,6 +18,7 @@ RSpec.describe "bundle cache with git" do ref = git.ref_for("master", 11) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => '#{lib_path("foo-1.0")}' G @@ -36,6 +37,7 @@ RSpec.describe "bundle cache with git" do ref = git.ref_for("master", 11) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => '#{lib_path("foo-1.0")}' G @@ -55,6 +57,7 @@ RSpec.describe "bundle cache with git" do build_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => '#{lib_path("foo-1.0")}' G @@ -72,6 +75,7 @@ RSpec.describe "bundle cache with git" do old_ref = git.ref_for("master", 11) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => '#{lib_path("foo-1.0")}' G @@ -102,6 +106,7 @@ RSpec.describe "bundle cache with git" do old_ref = git.ref_for("master", 11) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => '#{lib_path("foo-1.0")}' G @@ -130,6 +135,7 @@ RSpec.describe "bundle cache with git" do ref = git.ref_for("master", 11) gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => '#{lib_path("foo-invalid")}', :branch => :master G @@ -150,6 +156,9 @@ RSpec.describe "bundle cache with git" do end it "copies repository to vendor cache, including submodules" do + # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/ + system(*%W[git config --global protocol.file.allow always]) + build_git "submodule", "1.0" git = build_git "has_submodule", "1.0" do |s| @@ -160,6 +169,7 @@ RSpec.describe "bundle cache with git" do sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("has_submodule-1.0")}", :submodules => true do gem "has_submodule" end @@ -183,6 +193,7 @@ RSpec.describe "bundle cache with git" do update_git("foo") {|s| s.write "foo.gemspec", spec_lines.join("\n") } install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => '#{lib_path("foo-1.0")}' G bundle "config set cache_all true" @@ -197,6 +208,7 @@ RSpec.describe "bundle cache with git" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => '#{lib_path("foo-1.0")}' G bundle "config set cache_all true" diff --git a/spec/bundler/cache/path_spec.rb b/spec/bundler/cache/path_spec.rb index c81dda7405..2ad136a008 100644 --- a/spec/bundler/cache/path_spec.rb +++ b/spec/bundler/cache/path_spec.rb @@ -5,6 +5,7 @@ RSpec.describe "bundle cache with path" do build_lib "foo", :path => bundled_app("lib/foo") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => '#{bundled_app("lib/foo")}' G @@ -18,6 +19,7 @@ RSpec.describe "bundle cache with path" do build_lib "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => '#{lib_path("foo-1.0")}' G @@ -36,6 +38,7 @@ RSpec.describe "bundle cache with path" do build_lib libname, :path => libpath install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "#{libname}", :path => '#{libpath}' G @@ -51,6 +54,7 @@ RSpec.describe "bundle cache with path" do build_lib "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => '#{lib_path("foo-1.0")}' G @@ -73,6 +77,7 @@ RSpec.describe "bundle cache with path" do build_lib "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => '#{lib_path("foo-1.0")}' G @@ -84,6 +89,7 @@ RSpec.describe "bundle cache with path" do build_lib "bar" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "bar", :path => '#{lib_path("bar-1.0")}' G @@ -95,6 +101,7 @@ RSpec.describe "bundle cache with path" do build_lib "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => '#{lib_path("foo-1.0")}' G @@ -107,6 +114,7 @@ RSpec.describe "bundle cache with path" do build_lib "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => '#{lib_path("foo-1.0")}' G @@ -119,6 +127,7 @@ RSpec.describe "bundle cache with path" do build_lib "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => '#{lib_path("foo-1.0")}' G @@ -127,6 +136,7 @@ RSpec.describe "bundle cache with path" do build_lib "bar" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => '#{lib_path("foo-1.0")}' gem "bar", :path => '#{lib_path("bar-1.0")}' G @@ -139,6 +149,7 @@ RSpec.describe "bundle cache with path" do build_lib "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => '#{lib_path("foo-1.0")}' G @@ -147,6 +158,7 @@ RSpec.describe "bundle cache with path" do build_lib "baz" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => '#{lib_path("foo-1.0")}' gem "baz", :path => '#{lib_path("baz-1.0")}' G diff --git a/spec/bundler/commands/binstubs_spec.rb b/spec/bundler/commands/binstubs_spec.rb index 3b177b32ea..1cd0e16d95 100644 --- a/spec/bundler/commands/binstubs_spec.rb +++ b/spec/bundler/commands/binstubs_spec.rb @@ -116,7 +116,7 @@ RSpec.describe "bundle binstubs <gem>" do s.executables = "print_loaded_gems" s.bindir = "exe" s.write "exe/print_loaded_gems", <<-R - specs = Gem.loaded_specs.values.reject {|s| Bundler.rubygems.spec_default_gem?(s) } + specs = Gem.loaded_specs.values.reject {|s| s.default_gem? } puts specs.map(&:full_name).sort.inspect R end @@ -140,8 +140,15 @@ RSpec.describe "bundle binstubs <gem>" do it "runs the correct version of bundler" do sys_exec "bin/bundle install", :env => { "BUNDLER_VERSION" => "999.999.999" }, :raise_on_error => false expect(exitstatus).to eq(42) - expect(err).to include("Activating bundler (~> 999.999) failed:"). - and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 999.999'`") + expect(err).to include("Activating bundler (999.999.999) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`") + end + + it "runs the correct version of bundler even if a higher version is installed" do + system_gems "bundler-999.999.998", "bundler-999.999.999" + + sys_exec "bin/bundle install", :env => { "BUNDLER_VERSION" => "999.999.998", "DEBUG" => "1" }, :raise_on_error => false + expect(out).to include %(Using bundler 999.999.998\n) end end @@ -215,8 +222,8 @@ RSpec.describe "bundle binstubs <gem>" do it "calls through to the explicit bundler version" do sys_exec "bin/bundle update --bundler=999.999.999", :raise_on_error => false expect(exitstatus).to eq(42) - expect(err).to include("Activating bundler (~> 999.999) failed:"). - and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 999.999'`") + expect(err).to include("Activating bundler (999.999.999) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`") end end @@ -254,6 +261,7 @@ RSpec.describe "bundle binstubs <gem>" do s.executables = %w[foo] end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo")}" G @@ -269,6 +277,7 @@ RSpec.describe "bundle binstubs <gem>" do s.executables = %w[foo] end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo")}" G @@ -278,8 +287,6 @@ RSpec.describe "bundle binstubs <gem>" do end it "sets correct permissions for binstubs" do - skip "https://github.com/rubygems/rubygems/issues/3352" if Gem.win_platform? - with_umask(0o002) do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -288,7 +295,7 @@ RSpec.describe "bundle binstubs <gem>" do bundle "binstubs rack" binary = bundled_app("bin/rackup") - expect(File.stat(binary).mode.to_s(8)).to eq("100775") + expect(File.stat(binary).mode.to_s(8)).to eq(Gem.win_platform? ? "100644" : "100775") end end diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index df5f01bc45..9bb85c6587 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -234,7 +234,7 @@ RSpec.describe "bundle cache" do end end - bundle "config --local without wo" + bundle "config set --local without wo" install_gemfile <<-G source "file:#{gem_repo1}" gem "rack" @@ -250,11 +250,26 @@ RSpec.describe "bundle cache" do expect(the_bundle).to include_gem "rack 1.0" expect(the_bundle).not_to include_gems "weakling", "uninstallable" - bundle "config --local without wo" + bundle "config set --local without wo" bundle :install expect(the_bundle).to include_gem "rack 1.0" expect(the_bundle).not_to include_gems "weakling", "uninstallable" end + + it "does not fail to cache gems in excluded groups when there's a lockfile but gems not previously installed" do + bundle "config set --local without wo" + gemfile <<-G + source "https://my.gem.repo.1" + gem "rack" + group :wo do + gem "weakling" + end + G + + bundle :lock, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } + bundle :cache, "all-platforms" => true, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } + expect(bundled_app("vendor/cache/weakling-0.0.3.gem")).to exist + end end context "with frozen configured" do @@ -267,7 +282,7 @@ RSpec.describe "bundle cache" do end subject do - bundle "config --local frozen true" + bundle "config set --local frozen true" bundle :cache, :raise_on_error => false end @@ -287,6 +302,30 @@ RSpec.describe "bundle cache" do expect(out).to include("frozen").or include("deployment") end end + + context "with gems with extensions" do + before do + build_repo2 do + build_gem "racc", "2.0" do |s| + s.add_dependency "rake" + s.extensions << "Rakefile" + s.write "Rakefile", "task(:default) { puts 'INSTALLING rack' }" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo2)}" + + gem "racc" + G + end + + it "installs them properly from cache to a different path" do + bundle "cache" + bundle "config set --local path vendor/bundle" + bundle "install --local" + end + end end RSpec.describe "bundle install with gem sources" do @@ -306,7 +345,7 @@ RSpec.describe "bundle install with gem sources" do expect(the_bundle).to include_gems "rack 1.0.0" end - it "does not hit the remote at all" do + it "does not hit the remote at all in frozen mode" do build_repo2 install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" @@ -317,12 +356,30 @@ RSpec.describe "bundle install with gem sources" do simulate_new_machine FileUtils.rm_rf gem_repo2 - bundle "config --local deployment true" - bundle "config --local path vendor/bundle" + bundle "config set --local deployment true" + bundle "config set --local path vendor/bundle" bundle :install expect(the_bundle).to include_gems "rack 1.0.0" end + it "does not hit the remote at all when cache_all_platforms configured" do + build_repo2 + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "rack" + G + + bundle :cache + simulate_new_machine + FileUtils.rm_rf gem_repo2 + + bundle "config set --local cache_all_platforms true" + bundle "config set --local path vendor/bundle" + bundle "install --local" + expect(out).not_to include("Fetching gem metadata") + expect(the_bundle).to include_gems "rack 1.0.0" + end + it "does not reinstall already-installed gems" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" diff --git a/spec/bundler/commands/check_spec.rb b/spec/bundler/commands/check_spec.rb index fdf3bc7d51..1fa35136eb 100644 --- a/spec/bundler/commands/check_spec.rb +++ b/spec/bundler/commands/check_spec.rb @@ -124,7 +124,7 @@ RSpec.describe "bundle check" do gem "rack", :group => :foo G - bundle "config --local without foo" + bundle "config set --local without foo" bundle :install gemfile <<-G @@ -137,6 +137,22 @@ RSpec.describe "bundle check" do expect(exitstatus).to eq(1) end + it "ensures that gems are actually installed and not just cached in applications' cache" do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack" + G + + bundle "config set --local path vendor/bundle" + bundle :cache + + gem_command "uninstall rack", :env => { "GEM_HOME" => vendored_gems.to_s } + + bundle "check", :raise_on_error => false + expect(err).to include("* rack (1.0.0)") + expect(exitstatus).to eq(1) + end + it "ignores missing gems restricted to other platforms" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -217,7 +233,7 @@ RSpec.describe "bundle check" do gem "foo" G - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle "install" FileUtils.rm(bundled_app_lock) @@ -288,9 +304,136 @@ RSpec.describe "bundle check" do end end + describe "when locked with multiple dependents with different requirements" do + before :each do + build_repo4 do + build_gem "depends_on_rack" do |s| + s.add_dependency "rack", ">= 1.0" + end + build_gem "also_depends_on_rack" do |s| + s.add_dependency "rack", "~> 1.0" + end + build_gem "rack" + end + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "depends_on_rack" + gem "also_depends_on_rack" + G + + bundle "lock" + end + + it "shows what is missing with the current Gemfile without duplications" do + bundle :check, :raise_on_error => false + expect(err).to match(/The following gems are missing/) + expect(err).to include("* rack (1.0").once + end + end + + describe "when locked under multiple platforms" do + before :each do + build_repo4 do + build_gem "rack" + end + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "rack" + G + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rack (1.0) + + PLATFORMS + ruby + #{specific_local_platform} + + DEPENDENCIES + rack + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "shows what is missing with the current Gemfile without duplications" do + bundle :check, :raise_on_error => false + expect(err).to match(/The following gems are missing/) + expect(err).to include("* rack (1.0").once + end + end + + describe "when using only scoped rubygems sources" do + before do + gemfile <<~G + source "#{file_uri_for(gem_repo2)}" + source "#{file_uri_for(gem_repo1)}" do + gem "rack" + end + G + end + + it "returns success when the Gemfile is satisfied" do + system_gems "rack-1.0.0", :path => default_bundle_path + bundle :check + expect(out).to include("The Gemfile's dependencies are satisfied") + end + end + + describe "when using only scoped rubygems sources with indirect dependencies" do + before do + build_repo4 do + build_gem "depends_on_rack" do |s| + s.add_dependency "rack" + end + + build_gem "rack" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo1)}" + source "#{file_uri_for(gem_repo4)}" do + gem "depends_on_rack" + end + G + end + + it "returns success when the Gemfile is satisfied and generates a correct lockfile" do + system_gems "depends_on_rack-1.0", "rack-1.0", :gem_repo => gem_repo4, :path => default_bundle_path + bundle :check + expect(out).to include("The Gemfile's dependencies are satisfied") + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + depends_on_rack (1.0) + rack + rack (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + depends_on_rack! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + describe "BUNDLED WITH" do def lock_with(bundler_version = nil) - lock = <<-L + lock = <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -304,7 +447,7 @@ RSpec.describe "bundle check" do L if bundler_version - lock += "\n BUNDLED WITH\n #{bundler_version}\n" + lock += "\nBUNDLED WITH\n #{bundler_version}\n" end lock @@ -323,7 +466,7 @@ RSpec.describe "bundle check" do it "does not change the lock" do lockfile lock_with(nil) bundle :check - lockfile_should_be lock_with(nil) + expect(lockfile).to eq lock_with(nil) end end @@ -332,7 +475,7 @@ RSpec.describe "bundle check" do lockfile lock_with(Bundler::VERSION.succ) bundle :check expect(err).to include("the running version of Bundler (#{Bundler::VERSION}) is older than the version that created the lockfile (#{Bundler::VERSION.succ})") - lockfile_should_be lock_with(Bundler::VERSION.succ) + expect(lockfile).to eq lock_with(Bundler::VERSION.succ) end end @@ -341,7 +484,7 @@ RSpec.describe "bundle check" do system_gems "bundler-1.18.0" lockfile lock_with("1.18.0") bundle :check - lockfile_should_be lock_with("1.18.0") + expect(lockfile).to eq lock_with("1.18.0") end end end diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb index 7c43aaabc4..9cce998416 100644 --- a/spec/bundler/commands/clean_spec.rb +++ b/spec/bundler/commands/clean_spec.rb @@ -236,7 +236,7 @@ RSpec.describe "bundle clean" do bundle "config set path vendor/bundle" bundle "install" - update_git "foo", :path => lib_path("foo-bar") + update_git "foo-bar", :path => lib_path("foo-bar") revision2 = revision_for(lib_path("foo-bar")) bundle "update", :all => true @@ -261,6 +261,7 @@ RSpec.describe "bundle clean" do revision = revision_for(lib_path("rails")) gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "activesupport", :git => "#{lib_path("rails")}", :ref => '#{revision}' G @@ -625,21 +626,19 @@ RSpec.describe "bundle clean" do end it "when using --force, it doesn't remove default gem binaries" do - skip "does not work on ruby 3.0 because it changes the path to look for default gems, tsort is a default gem there, and we can't install it either like we do with fiddle because it doesn't yet exist" unless RUBY_VERSION < "3.0.0" + skip "does not work on old rubies because the realworld gems that need to be installed don't support them" if RUBY_VERSION < "2.7.0" skip "does not work on rubygems versions where `--install_dir` doesn't respect --default" unless Gem::Installer.for_spec(loaded_gemspec, :install_dir => "/foo").default_spec_file == "/foo/specifications/default/bundler-#{Bundler::VERSION}.gemspec" # Since rubygems 3.2.0.rc.2 default_irb_version = ruby "gem 'irb', '< 999999'; require 'irb'; puts IRB::VERSION", :raise_on_error => false skip "irb isn't a default gem" if default_irb_version.empty? - build_repo2 do - # simulate executable for default gem - build_gem "irb", default_irb_version, :to_system => true, :default => true do |s| - s.executables = "irb" - end + # simulate executable for default gem + build_gem "irb", default_irb_version, :to_system => true, :default => true do |s| + s.executables = "irb" end - realworld_system_gems "fiddle --version 1.0.0" + realworld_system_gems "fiddle --version 1.0.6", "tsort --version 0.1.0", "pathname --version 0.1.0", "set --version 1.0.1" install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" @@ -869,6 +868,7 @@ RSpec.describe "bundle clean" do expect(very_simple_binary_extensions_dir).to exist gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}", :ref => "#{revision}" G @@ -878,6 +878,7 @@ RSpec.describe "bundle clean" do expect(very_simple_binary_extensions_dir).to exist gemfile <<-G + source "#{file_uri_for(gem_repo1)}" G bundle "install" diff --git a/spec/bundler/commands/config_spec.rb b/spec/bundler/commands/config_spec.rb index 70e3feff00..2d0a7dc989 100644 --- a/spec/bundler/commands/config_spec.rb +++ b/spec/bundler/commands/config_spec.rb @@ -45,7 +45,7 @@ RSpec.describe ".bundle/config" do it "can be moved with an environment variable" do ENV["BUNDLE_APP_CONFIG"] = tmp("foo/bar").to_s - bundle "config --local path vendor/bundle" + bundle "config set --local path vendor/bundle" bundle "install" expect(bundled_app(".bundle")).not_to exist @@ -57,7 +57,7 @@ RSpec.describe ".bundle/config" do FileUtils.mkdir_p bundled_app("omg") ENV["BUNDLE_APP_CONFIG"] = "../foo" - bundle "config --local path vendor/bundle" + bundle "config set --local path vendor/bundle" bundle "install", :dir => bundled_app("omg") expect(bundled_app(".bundle")).not_to exist @@ -76,6 +76,30 @@ RSpec.describe ".bundle/config" do end end + describe "config location" do + let(:bundle_user_config) { File.join(Dir.home, ".config/bundler") } + + before do + Dir.mkdir File.dirname(bundle_user_config) + end + + it "can be configured through BUNDLE_USER_CONFIG" do + bundle "config set path vendor", :env => { "BUNDLE_USER_CONFIG" => bundle_user_config } + bundle "config get path", :env => { "BUNDLE_USER_CONFIG" => bundle_user_config } + expect(out).to include("Set for the current user (#{bundle_user_config}): \"vendor\"") + end + + context "when not explicitly configured, but BUNDLE_USER_HOME set" do + let(:bundle_user_home) { bundled_app(".bundle").to_s } + + it "uses the right location" do + bundle "config set path vendor", :env => { "BUNDLE_USER_HOME" => bundle_user_home } + bundle "config get path", :env => { "BUNDLE_USER_HOME" => bundle_user_home } + expect(out).to include("Set for the current user (#{bundle_user_home}/config): \"vendor\"") + end + end + end + describe "global" do before(:each) do install_gemfile <<-G @@ -321,7 +345,7 @@ E end describe "quoting" do - before(:each) { gemfile "# no gems" } + before(:each) { gemfile "source \"#{file_uri_for(gem_repo1)}\"" } let(:long_string) do "--with-xml2-include=/usr/pkg/include/libxml2 --with-xml2-lib=/usr/pkg/lib " \ "--with-xslt-dir=/usr/pkg" @@ -408,6 +432,22 @@ E expect(out).to eq "spec_run=true" end + it "list with credentials" do + bundle "config list", :env => { "BUNDLE_GEMS__MYSERVER__COM" => "user:password" } + expect(out).to eq "Settings are listed in order of priority. The top value will be used.\ngems.myserver.com\nSet via BUNDLE_GEMS__MYSERVER__COM: \"user:[REDACTED]\"\n\nspec_run\nSet via BUNDLE_SPEC_RUN: \"true\"" + + bundle "config list", :parseable => true, :env => { "BUNDLE_GEMS__MYSERVER__COM" => "user:password" } + expect(out).to eq "gems.myserver.com=user:password\nspec_run=true" + end + + it "list with API token credentials" do + bundle "config list", :env => { "BUNDLE_GEMS__MYSERVER__COM" => "api_token:x-oauth-basic" } + expect(out).to eq "Settings are listed in order of priority. The top value will be used.\ngems.myserver.com\nSet via BUNDLE_GEMS__MYSERVER__COM: \"[REDACTED]:x-oauth-basic\"\n\nspec_run\nSet via BUNDLE_SPEC_RUN: \"true\"" + + bundle "config list", :parseable => true, :env => { "BUNDLE_GEMS__MYSERVER__COM" => "api_token:x-oauth-basic" } + expect(out).to eq "gems.myserver.com=api_token:x-oauth-basic\nspec_run=true" + end + it "get" do ENV["BUNDLE_BAR"] = "bar_val" diff --git a/spec/bundler/commands/doctor_spec.rb b/spec/bundler/commands/doctor_spec.rb index d8e9674a24..860b638f06 100644 --- a/spec/bundler/commands/doctor_spec.rb +++ b/spec/bundler/commands/doctor_spec.rb @@ -32,6 +32,8 @@ RSpec.describe "bundle doctor" do unwritable_file = double("file") allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) allow(Find).to receive(:find).with(Bundler.bundle_path.to_s) { [unwritable_file] } + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with(unwritable_file).and_return(true) allow(File).to receive(:stat).with(unwritable_file) { stat } allow(stat).to receive(:uid) { Process.uid } allow(File).to receive(:writable?).with(unwritable_file) { true } @@ -47,7 +49,6 @@ RSpec.describe "bundle doctor" do doctor = Bundler::CLI::Doctor.new({}) expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/rack/rack.bundle"] expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/lib/libSystem.dylib"] - allow(File).to receive(:exist?).and_call_original allow(File).to receive(:exist?).with("/usr/lib/libSystem.dylib").and_return(true) expect { doctor.run }.not_to(raise_error, @stdout.string) expect(@stdout.string).to be_empty @@ -57,7 +58,6 @@ RSpec.describe "bundle doctor" do doctor = Bundler::CLI::Doctor.new({}) expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/rack/rack.bundle"] expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib"] - allow(File).to receive(:exist?).and_call_original allow(File).to receive(:exist?).with("/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib").and_return(false) expect { doctor.run }.to raise_error(Bundler::ProductionError, strip_whitespace(<<-E).strip), @stdout.string The following gems are missing OS dependencies: @@ -67,12 +67,32 @@ RSpec.describe "bundle doctor" do end end + context "when home contains broken symlinks" do + before(:each) do + @broken_symlink = double("file") + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + allow(Find).to receive(:find).with(Bundler.bundle_path.to_s) { [@broken_symlink] } + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with(@broken_symlink) { false } + end + + it "exits with an error if home contains files that are not readable/writable" do + expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error + expect(@stdout.string).to include( + "Broken links exist in the Bundler home. Please report them to the offending gem's upstream repo. These files are:\n - #{@broken_symlink}" + ) + expect(@stdout.string).not_to include("No issues") + end + end + context "when home contains files that are not readable/writable" do before(:each) do @stat = double("stat") @unwritable_file = double("file") allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) allow(Find).to receive(:find).with(Bundler.bundle_path.to_s) { [@unwritable_file] } + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with(@unwritable_file) { true } allow(File).to receive(:stat).with(@unwritable_file) { @stat } end @@ -113,4 +133,14 @@ RSpec.describe "bundle doctor" do end end end + + context "when home contains filesname with special characters" do + it "escape filename before command execute" do + doctor = Bundler::CLI::Doctor.new({}) + expect(doctor).to receive(:`).with("/usr/bin/otool -L \\$\\(date\\)\\ \\\"\\'\\\\.bundle").and_return("dummy string") + doctor.dylibs_darwin('$(date) "\'\.bundle') + expect(doctor).to receive(:`).with("/usr/bin/ldd \\$\\(date\\)\\ \\\"\\'\\\\.bundle").and_return("dummy string") + doctor.dylibs_ldd('$(date) "\'\.bundle') + end + end end diff --git a/spec/bundler/commands/exec_spec.rb b/spec/bundler/commands/exec_spec.rb index 5d43586116..e67e5b96ef 100644 --- a/spec/bundler/commands/exec_spec.rb +++ b/spec/bundler/commands/exec_spec.rb @@ -8,6 +8,7 @@ RSpec.describe "bundle exec" do it "works with --gemfile flag" do create_file "CustomGemfile", <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack", "1.0.0" G @@ -17,6 +18,7 @@ RSpec.describe "bundle exec" do it "activates the correct gem" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack", "0.9.1" G @@ -24,8 +26,20 @@ RSpec.describe "bundle exec" do expect(out).to eq("0.9.1") end + it "works and prints no warnings when HOME is not writable" do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack", "0.9.1" + G + + bundle "exec rackup", :env => { "HOME" => "/" } + expect(out).to eq("0.9.1") + expect(err).to be_empty + end + it "works when the bins are in ~/.bundle" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack" G @@ -35,6 +49,7 @@ RSpec.describe "bundle exec" do it "works when running from a random directory" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack" G @@ -44,37 +59,39 @@ RSpec.describe "bundle exec" do end it "works when exec'ing something else" do - install_gemfile 'gem "rack"' + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\"" bundle "exec echo exec" expect(out).to eq("exec") end it "works when exec'ing to ruby" do - install_gemfile 'gem "rack"' + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\"" bundle "exec ruby -e 'puts %{hi}'" expect(out).to eq("hi") end it "works when exec'ing to rubygems" do - install_gemfile 'gem "rack"' + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\"" bundle "exec #{gem_cmd} --version" expect(out).to eq(Gem::VERSION) end it "works when exec'ing to rubygems through sh -c" do - install_gemfile 'gem "rack"' + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\"" bundle "exec sh -c '#{gem_cmd} --version'" expect(out).to eq(Gem::VERSION) end it "works when exec'ing back to bundler with a lockfile that doesn't include the current platform" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack", "0.9.1" G # simulate lockfile generated with old version not including specific platform lockfile <<-L GEM + remote: #{file_uri_for(gem_repo1)}/ specs: rack (0.9.1) @@ -100,20 +117,20 @@ RSpec.describe "bundle exec" do Process.setproctitle("1-2-3-4-5-6-7") puts `ps -ocommand= -p#{$$}` RUBY - create_file "Gemfile" + create_file "Gemfile", "source \"#{file_uri_for(gem_repo1)}\"" create_file "a.rb", script_that_changes_its_own_title_and_checks_if_picked_up_by_ps_unix_utility bundle "exec ruby a.rb" expect(out).to eq("1-2-3-4-5-6-7") end it "accepts --verbose" do - install_gemfile 'gem "rack"' + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\"" bundle "exec --verbose echo foobar" expect(out).to eq("foobar") end it "passes --verbose to command if it is given after the command" do - install_gemfile 'gem "rack"' + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\"" bundle "exec echo --verbose" expect(out).to eq("--verbose") end @@ -137,7 +154,7 @@ RSpec.describe "bundle exec" do end G - install_gemfile "" + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" sys_exec "#{Gem.ruby} #{command.path}" expect(out).to be_empty @@ -145,7 +162,7 @@ RSpec.describe "bundle exec" do end it "accepts --keep-file-descriptors" do - install_gemfile "" + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" bundle "exec --keep-file-descriptors echo foobar" expect(err).to be_empty @@ -154,7 +171,7 @@ RSpec.describe "bundle exec" do it "can run a command named --verbose" do skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - install_gemfile 'gem "rack"' + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\"" File.open(bundled_app("--verbose"), "w") do |f| f.puts "#!/bin/sh" f.puts "echo foobar" @@ -200,7 +217,7 @@ RSpec.describe "bundle exec" do before do skip "irb isn't a default gem" if default_irb_version.empty? - install_gemfile "" + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" end it "uses version provided by ruby" do @@ -295,7 +312,7 @@ RSpec.describe "bundle exec" do end it "handles gems installed with --without" do - bundle "config --local without middleware" + bundle "config set --local without middleware" install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" # rack 0.9.1 and 1.0 exist @@ -315,6 +332,7 @@ RSpec.describe "bundle exec" do skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack" G @@ -333,6 +351,7 @@ RSpec.describe "bundle exec" do skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack" G @@ -349,6 +368,7 @@ RSpec.describe "bundle exec" do it "errors nicely when the argument doesn't exist" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack" G @@ -360,6 +380,7 @@ RSpec.describe "bundle exec" do it "errors nicely when the argument is not executable" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack" G @@ -371,6 +392,7 @@ RSpec.describe "bundle exec" do it "errors nicely when no arguments are passed" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack" G @@ -402,6 +424,7 @@ RSpec.describe "bundle exec" do skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack" G @@ -486,6 +509,7 @@ RSpec.describe "bundle exec" do describe "run from a random directory" do before(:each) do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack" G end @@ -509,6 +533,7 @@ RSpec.describe "bundle exec" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "fizz", :path => "#{File.expand_path(home("fizz"))}" G end @@ -533,6 +558,7 @@ RSpec.describe "bundle exec" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "fizz_git", :git => "#{lib_path("fizz_git-1.0")}" G end @@ -556,6 +582,7 @@ RSpec.describe "bundle exec" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "fizz_no_gemspec", "1.0", :git => "#{lib_path("fizz_no_gemspec-1.0")}" G end @@ -585,6 +612,36 @@ RSpec.describe "bundle exec" do expect(out).to include("Installing foo 1.0") end + it "loads the correct optparse when `auto_install` is set, and optparse is a dependency" do + if Gem.ruby_version >= Gem::Version.new("3.0.0") && Gem.rubygems_version < Gem::Version.new("3.3.0.a") + skip "optparse is a default gem, and rubygems loads it during install" + end + + build_repo4 do + build_gem "fastlane", "2.192.0" do |s| + s.executables = "fastlane" + s.add_dependency "optparse", "~> 999.999.999" + end + + build_gem "optparse", "999.999.998" + build_gem "optparse", "999.999.999" + end + + system_gems "optparse-999.999.998", :gem_repo => gem_repo4 + + bundle "config set auto_install 1" + bundle "config set --local path vendor/bundle" + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "fastlane" + G + + bundle "exec fastlane" + expect(out).to include("Installing optparse 999.999.999") + expect(out).to include("2.192.0") + end + describe "with gems bundled via :path with invalid gemspecs" do it "outputs the gemspec validation errors" do build_lib "foo" @@ -602,6 +659,7 @@ RSpec.describe "bundle exec" do end gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo-1.0")}" G @@ -617,6 +675,8 @@ RSpec.describe "bundle exec" do skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + module Monkey def bin_path(a,b,c) raise Gem::GemNotFoundException.new('Fail') @@ -650,6 +710,7 @@ RSpec.describe "bundle exec" do bundled_app(path).chmod(0o755) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack" G end @@ -668,29 +729,40 @@ RSpec.describe "bundle exec" do subject { bundle "exec #{path} arg1 arg2", :raise_on_error => false } - shared_examples_for "it runs" do - it "like a normally executed executable" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + it "runs" do + skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - subject - expect(exitstatus).to eq(exit_code) - expect(err).to eq(expected_err) - expect(out).to eq(expected) - end + subject + expect(exitstatus).to eq(exit_code) + expect(err).to eq(expected_err) + expect(out).to eq(expected) end - it_behaves_like "it runs" - context "the executable exits explicitly" do let(:executable) { super() << "\nexit #{exit_code}\nputs 'POST_EXIT'\n" } context "with exit 0" do - it_behaves_like "it runs" + it "runs" do + skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + + subject + expect(exitstatus).to eq(exit_code) + expect(err).to eq(expected_err) + expect(out).to eq(expected) + end end context "with exit 99" do let(:exit_code) { 99 } - it_behaves_like "it runs" + + it "runs" do + skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + + subject + expect(exitstatus).to eq(exit_code) + expect(err).to eq(expected_err) + expect(out).to eq(expected) + end end end @@ -707,7 +779,15 @@ RSpec.describe "bundle exec" do # this is specified by C99 128 + 15 end - it_behaves_like "it runs" + + it "runs" do + skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + + subject + expect(exitstatus).to eq(exit_code) + expect(err).to eq(expected_err) + expect(out).to eq(expected) + end end context "the executable is empty" do @@ -716,7 +796,15 @@ RSpec.describe "bundle exec" do let(:exit_code) { 0 } let(:expected_err) { "#{path} is empty" } let(:expected) { "" } - it_behaves_like "it runs" + + it "runs" do + skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + + subject + expect(exitstatus).to eq(exit_code) + expect(err).to eq(expected_err) + expect(out).to eq(expected) + end end context "the executable raises" do @@ -743,17 +831,33 @@ RSpec.describe "bundle exec" do let(:expected_err) { "bundler: failed to load command: #{path} (#{path})\n#{system_gem_path("bin/bundle")}: Err (Err)" } let(:expected) { super() } - it_behaves_like "it runs" + it "runs" do + skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + + subject + expect(exitstatus).to eq(exit_code) + expect(err).to eq(expected_err) + expect(out).to eq(expected) + end end context "when the file uses the current ruby shebang" do let(:shebang) { "#!#{Gem.ruby}" } - it_behaves_like "it runs" + + it "runs" do + skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + + subject + expect(exitstatus).to eq(exit_code) + expect(err).to eq(expected_err) + expect(out).to eq(expected) + end end context "when Bundler.setup fails", :bundler => "< 3" do before do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'rack', '2' G ENV["BUNDLER_FORCE_TTY"] = "true" @@ -762,16 +866,25 @@ RSpec.describe "bundle exec" do let(:exit_code) { Bundler::GemNotFound.new.status_code } let(:expected) { "" } let(:expected_err) { <<-EOS.strip } -\e[31mCould not find gem 'rack (= 2)' in any of the gem sources listed in your Gemfile.\e[0m -\e[33mRun `bundle install` to install missing gems.\e[0m +Could not find gem 'rack (= 2)' in locally installed gems. +The source contains the following versions of 'rack': 0.9.1, 1.0.0 +Run `bundle install` to install missing gems. EOS - it_behaves_like "it runs" + it "runs" do + skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + + subject + expect(exitstatus).to eq(exit_code) + expect(err).to eq(expected_err) + expect(out).to eq(expected) + end end context "when Bundler.setup fails", :bundler => "3" do before do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'rack', '2' G ENV["BUNDLER_FORCE_TTY"] = "true" @@ -780,19 +893,33 @@ RSpec.describe "bundle exec" do let(:exit_code) { Bundler::GemNotFound.new.status_code } let(:expected) { "" } let(:expected_err) { <<-EOS.strip } -\e[31mCould not find gem 'rack (= 2)' in locally installed gems. -The source contains the following versions of 'rack': 1.0.0\e[0m -\e[33mRun `bundle install` to install missing gems.\e[0m +Could not find gem 'rack (= 2)' in locally installed gems. +The source contains the following versions of 'rack': 1.0.0 +Run `bundle install` to install missing gems. EOS - it_behaves_like "it runs" + it "runs" do + skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + + subject + expect(exitstatus).to eq(exit_code) + expect(err).to eq(expected_err) + expect(out).to eq(expected) + end end context "when the executable exits non-zero via at_exit" do let(:executable) { super() + "\n\nat_exit { $! ? raise($!) : exit(1) }" } let(:exit_code) { 1 } - it_behaves_like "it runs" + it "runs" do + skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + + subject + expect(exitstatus).to eq(exit_code) + expect(err).to eq(expected_err) + expect(out).to eq(expected) + end end context "when disable_exec_load is set" do @@ -803,7 +930,14 @@ The source contains the following versions of 'rack': 1.0.0\e[0m bundle "config set disable_exec_load true" end - it_behaves_like "it runs" + it "runs" do + skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + + subject + expect(exitstatus).to eq(exit_code) + expect(err).to eq(expected_err) + expect(out).to eq(expected) + end end context "regarding $0 and __FILE__" do @@ -819,12 +953,26 @@ $0: #{path.to_s.inspect} __FILE__: #{path.to_s.inspect} EOS - it_behaves_like "it runs" + it "runs" do + skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + + subject + expect(exitstatus).to eq(exit_code) + expect(err).to eq(expected_err) + expect(out).to eq(expected) + end context "when the path is relative" do let(:path) { super().relative_path_from(bundled_app) } - it_behaves_like "it runs" + it "runs" do + skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + + subject + expect(exitstatus).to eq(exit_code) + expect(err).to eq(expected_err) + expect(out).to eq(expected) + end end context "when the path is relative with a leading ./" do @@ -925,7 +1073,7 @@ __FILE__: #{path.to_s.inspect} before do skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - install_gemfile "" + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" end it "does not undo the monkeypatches" do @@ -984,7 +1132,7 @@ __FILE__: #{path.to_s.inspect} RUBY # A Gemfile needs to be in the root to trick bundler's root resolution - create_file(bundled_app("Gemfile")) + create_file(bundled_app("Gemfile"), "source \"#{file_uri_for(gem_repo1)}\"") bundle "install" end @@ -1015,7 +1163,7 @@ __FILE__: #{path.to_s.inspect} skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? skip "openssl isn't a default gem" if expected.empty? - install_gemfile "" # must happen before installing the broken system gem + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" # must happen before installing the broken system gem build_repo4 do build_gem "openssl", openssl_version do |s| @@ -1056,6 +1204,7 @@ __FILE__: #{path.to_s.inspect} build_git "simple_git_binary", &:add_c_extension bundle "config set --local path .bundle" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "simple_git_binary", :git => '#{lib_path("simple_git_binary-1.0")}' G end diff --git a/spec/bundler/commands/info_spec.rb b/spec/bundler/commands/info_spec.rb index 7702959306..7f618b5f6c 100644 --- a/spec/bundler/commands/info_spec.rb +++ b/spec/bundler/commands/info_spec.rb @@ -50,6 +50,19 @@ RSpec.describe "bundle info" do expect(out).to eq(root.to_s) end + it "prints gem version if exists in bundle" do + bundle "info rails --version" + expect(out).to eq("2.3.2") + end + + it "doesn't claim that bundler has been deleted, even if using a custom path without bundler there" do + bundle "config set --local path vendor/bundle" + bundle "install" + bundle "info bundler" + expect(out).to include("\tPath: #{root}") + expect(err).not_to match(/The gem bundler has been deleted/i) + end + it "complains if gem not in bundle" do bundle "info missing", :raise_on_error => false expect(err).to eq("Could not find gem 'missing'.") @@ -60,7 +73,15 @@ RSpec.describe "bundle info" do bundle "info rails --path" - expect(err).to match(/has been deleted/i) + expect(err).to match(/The gem rails has been deleted/i) + expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s) + + bundle "info rail --path" + expect(err).to match(/The gem rails has been deleted/i) + expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s) + + bundle "info rails" + expect(err).to match(/The gem rails has been deleted/i) expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s) end @@ -111,6 +132,7 @@ RSpec.describe "bundle info" do it "prints out git info" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G expect(the_bundle).to include_gems "foo 1.0" @@ -126,6 +148,7 @@ RSpec.describe "bundle info" do @revision = revision_for(lib_path("foo-1.0"))[0...6] install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "omg" G expect(the_bundle).to include_gems "foo 1.0.omg" @@ -137,6 +160,7 @@ RSpec.describe "bundle info" do it "doesn't print the branch when tied to a ref" do sha = revision_for(lib_path("foo-1.0")) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{sha}" G @@ -147,6 +171,7 @@ RSpec.describe "bundle info" do it "handles when a version is a '-' prerelease" do @git = build_git("foo", "1.0.0-beta.1", :path => lib_path("foo")) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", "1.0.0-beta.1", :git => "#{lib_path("foo")}" G expect(the_bundle).to include_gems "foo 1.0.0.pre.beta.1" @@ -182,4 +207,18 @@ RSpec.describe "bundle info" do expect(err).to include("Could not find gem '#{invalid_regexp}'.") end end + + context "with without configured" do + it "does not find the gem, but gives a helpful error" do + bundle "config without test" + + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rails", group: :test + G + + bundle "info rails", :raise_on_error => false + expect(err).to include("Could not find gem 'rails', because it's in the group 'test', configured to be ignored.") + end + end end diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index f12d32d6a8..e00caa5315 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -94,6 +94,21 @@ RSpec.describe "bundle install with gem sources" do expect(the_bundle).to include_gems("rack 1.0.0") end + it "auto-heals missing gems" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem 'rack' + G + + FileUtils.rm_rf(default_bundle_path("gems/rack-1.0.0")) + + bundle "install --verbose" + + expect(out).to include("Installing rack 1.0.0") + expect(default_bundle_path("gems/rack-1.0.0")).to exist + expect(the_bundle).to include_gems("rack 1.0.0") + end + it "fetches gems when multiple versions are specified" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -291,7 +306,7 @@ RSpec.describe "bundle install with gem sources" do end it "works" do - bundle "config --local path vendor" + bundle "config set --local path vendor" bundle "install" expect(the_bundle).to include_gems "rack 1.0" end @@ -334,64 +349,87 @@ RSpec.describe "bundle install with gem sources" do gem "rack" G - expect(err).to include("Your Gemfile has no gem server sources") + expect(err).to include("This Gemfile does not include an explicit global source. " \ + "Not using an explicit global source may result in a different lockfile being generated depending on " \ + "the gems you have installed locally before bundler is run. " \ + "Instead, define a global source in your Gemfile like this: source \"https://rubygems.org\".") end it "creates a Gemfile.lock on a blank Gemfile" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" G expect(File.exist?(bundled_app_lock)).to eq(true) end - context "throws a warning if a gem is added twice in Gemfile" do - it "without version requirements" do - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo2)}" - gem "rack" - gem "rack" - G + it "throws a warning if a gem is added twice in Gemfile without version requirements" do + install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo2)}" + gem "rack" + gem "rack" + G - expect(err).to include("Your Gemfile lists the gem rack (>= 0) more than once.") - expect(err).to include("Remove any duplicate entries and specify the gem only once.") - expect(err).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.") - end + expect(err).to include("Your Gemfile lists the gem rack (>= 0) more than once.") + expect(err).to include("Remove any duplicate entries and specify the gem only once.") + expect(err).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.") + end - it "with same versions" do - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo2)}" - gem "rack", "1.0" - gem "rack", "1.0" - G + it "throws a warning if a gem is added twice in Gemfile with same versions" do + install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo2)}" + gem "rack", "1.0" + gem "rack", "1.0" + G - expect(err).to include("Your Gemfile lists the gem rack (= 1.0) more than once.") - expect(err).to include("Remove any duplicate entries and specify the gem only once.") - expect(err).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.") - end + expect(err).to include("Your Gemfile lists the gem rack (= 1.0) more than once.") + expect(err).to include("Remove any duplicate entries and specify the gem only once.") + expect(err).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.") end - context "throws an error if a gem is added twice in Gemfile" do - it "when version of one dependency is not specified" do - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo2)}" - gem "rack" - gem "rack", "1.0" - G + it "does not throw a warning if a gem is added once in Gemfile and also inside a gemspec as a development dependency" do + build_lib "my-gem", :path => bundled_app do |s| + s.add_development_dependency "my-private-gem" + end - expect(err).to include("You cannot specify the same gem twice with different version requirements") - expect(err).to include("You specified: rack (>= 0) and rack (= 1.0).") + build_repo2 do + build_gem "my-private-gem" end - it "when different versions of both dependencies are specified" do - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo2)}" - gem "rack", "1.0" - gem "rack", "1.1" - G + gemfile <<~G + source "#{file_uri_for(gem_repo2)}" - expect(err).to include("You cannot specify the same gem twice with different version requirements") - expect(err).to include("You specified: rack (= 1.0) and rack (= 1.1).") - end + gemspec + + gem "my-private-gem", :group => :development + G + + bundle :install + + expect(err).to be_empty + expect(the_bundle).to include_gems("my-private-gem 1.0") + end + + it "throws an error if a gem is added twice in Gemfile when version of one dependency is not specified" do + install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo2)}" + gem "rack" + gem "rack", "1.0" + G + + expect(err).to include("You cannot specify the same gem twice with different version requirements") + expect(err).to include("You specified: rack (>= 0) and rack (= 1.0).") + end + + it "throws an error if a gem is added twice in Gemfile when different versions of both dependencies are specified" do + install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo2)}" + gem "rack", "1.0" + gem "rack", "1.1" + G + + expect(err).to include("You cannot specify the same gem twice with different version requirements") + expect(err).to include("You specified: rack (= 1.0) and rack (= 1.1).") end it "gracefully handles error when rubygems server is unavailable" do @@ -429,7 +467,7 @@ RSpec.describe "bundle install with gem sources" do expect(last_command.stdboth).not_to match(/Error Report/i) expect(err).to include("An error occurred while installing ajp-rails (0.0.0), and Bundler cannot continue."). - and include("Make sure that `gem install ajp-rails -v '0.0.0' --source '#{file_uri_for(gem_repo2)}/'` succeeds before bundling.") + and include("Bundler::APIResponseInvalidDependenciesError") end it "doesn't blow up when the local .bundle/config is empty" do @@ -463,6 +501,7 @@ RSpec.describe "bundle install with gem sources" do install_gemfile <<-G, :raise_on_error => false ::RUBY_VERSION = '2.0.1' ruby '~> 2.2' + source "#{file_uri_for(gem_repo1)}" G expect(err).to include("Your Ruby version is 2.0.1, but your Gemfile specified ~> 2.2") end @@ -474,12 +513,14 @@ RSpec.describe "bundle install with gem sources" do ::RUBY_VERSION = '2.1.3' ::RUBY_PATCHLEVEL = 100 ruby '~> 2.1.0' + source "#{file_uri_for(gem_repo1)}" G end it "writes current Ruby version to Gemfile.lock" do - lockfile_should_be <<-L + expect(lockfile).to eq <<~L GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -500,10 +541,12 @@ RSpec.describe "bundle install with gem sources" do ::RUBY_VERSION = '2.2.3' ::RUBY_PATCHLEVEL = 100 ruby '~> 2.2.0' + source "#{file_uri_for(gem_repo1)}" G - lockfile_should_be <<-L + expect(lockfile).to eq <<~L GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -521,6 +564,7 @@ RSpec.describe "bundle install with gem sources" do it "does not crash when unlocking" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" ruby '>= 2.1.0' G @@ -539,6 +583,7 @@ RSpec.describe "bundle install with gem sources" do build_lib "foo" gemfile = <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "#{lib_path("foo-1.0")}" G File.open("#{root_dir}/Gemfile", "w") do |file| @@ -555,6 +600,7 @@ RSpec.describe "bundle install with gem sources" do build_lib "foo", :path => root_dir gemfile = <<-G + source "#{file_uri_for(gem_repo1)}" gemspec G File.open("#{root_dir}/Gemfile", "w") do |file| @@ -566,16 +612,48 @@ RSpec.describe "bundle install with gem sources" do end describe "when requesting a quiet install via --quiet" do - it "should be quiet" do + it "should be quiet if there are no warnings" do bundle "config set force_ruby_platform true" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'rack' G - bundle :install, :quiet => true, :raise_on_error => false - expect(err).to include("Could not find gem 'rack'") - expect(err).to_not include("Your Gemfile has no gem server sources") + bundle :install, :quiet => true + expect(out).to be_empty + expect(err).to be_empty + end + + it "should still display warnings and errors" do + bundle "config set force_ruby_platform true" + + create_file("install_with_warning.rb", <<~RUBY) + require "#{lib_dir}/bundler" + require "#{lib_dir}/bundler/cli" + require "#{lib_dir}/bundler/cli/install" + + module RunWithWarning + def run + super + rescue + Bundler.ui.warn "BOOOOO" + raise + end + end + + Bundler::CLI::Install.prepend(RunWithWarning) + RUBY + + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem 'non-existing-gem' + G + + bundle :install, :quiet => true, :raise_on_error => false, :env => { "RUBYOPT" => "-r#{bundled_app("install_with_warning.rb")}" } + expect(out).to be_empty + expect(err).to include("Could not find gem 'non-existing-gem'") + expect(err).to include("BOOOOO") end end @@ -600,6 +678,77 @@ RSpec.describe "bundle install with gem sources" do end end + describe "when bundle gems path does not have write access", :permissions do + let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") } + + before do + FileUtils.mkdir_p(gems_path) + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem 'rack' + G + end + + it "should display a proper message to explain the problem" do + FileUtils.chmod("-x", gems_path) + bundle "config set --local path vendor" + + begin + bundle :install, :raise_on_error => false + ensure + FileUtils.chmod("+x", gems_path) + end + + expect(err).not_to include("ERROR REPORT TEMPLATE") + + expect(err).to include( + "There was an error while trying to create `#{gems_path.join("rack-1.0.0")}`. " \ + "It is likely that you need to grant executable permissions for all parent directories and write permissions for `#{gems_path}`." + ) + end + end + + describe "when the path of a specific gem is not writable", :permissions do + let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") } + let(:foo_path) { gems_path.join("foo-1.0.0") } + + before do + build_repo4 do + build_gem "foo", "1.0.0" do |s| + s.write "CHANGELOG.md", "foo" + end + end + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'foo' + G + end + + it "should display a proper message to explain the problem" do + bundle "config set --local path vendor" + bundle :install + expect(out).to include("Bundle complete!") + expect(err).to be_empty + + FileUtils.chmod("-x", foo_path) + + begin + bundle "install --redownload", :raise_on_error => false + ensure + FileUtils.chmod("+x", foo_path) + end + + expect(err).not_to include("ERROR REPORT TEMPLATE") + + expect(err).to include( + "There was an error while trying to delete `#{foo_path}`. " \ + "It is likely that you need to grant executable permissions for all parent directories " \ + "and write permissions for `#{gems_path}`, and the same thing for all subdirectories inside #{foo_path}." + ) + end + end + describe "when bundle cache path does not have write access", :permissions do let(:cache_path) { bundled_app("vendor/#{Bundler.ruby_scope}/cache") } @@ -614,7 +763,7 @@ RSpec.describe "bundle install with gem sources" do it "should display a proper message to explain the problem" do FileUtils.chmod(0o500, cache_path) - bundle "config --local path vendor" + bundle "config set --local path vendor" bundle :install, :raise_on_error => false expect(err).to include(cache_path.to_s) expect(err).to include("grant write permissions") @@ -627,7 +776,7 @@ RSpec.describe "bundle install with gem sources" do source "#{file_uri_for(gem_repo1)}" gem "rack" G - bundle "config --local path bundle" + bundle "config set --local path bundle" bundle "install", :standalone => true end @@ -696,4 +845,117 @@ RSpec.describe "bundle install with gem sources" do ) end end + + context "with missing platform specific gems in lockfile" do + before do + build_repo4 do + build_gem "racc", "1.5.2" + + build_gem "nokogiri", "1.12.4" do |s| + s.platform = "x86_64-darwin" + s.add_runtime_dependency "racc", "~> 1.4" + end + + build_gem "nokogiri", "1.12.4" do |s| + s.platform = "x86_64-linux" + s.add_runtime_dependency "racc", "~> 1.4" + end + + build_gem "crass", "1.0.6" + + build_gem "loofah", "2.12.0" do |s| + s.add_runtime_dependency "crass", "~> 1.0.2" + s.add_runtime_dependency "nokogiri", ">= 1.5.9" + end + end + + gemfile <<-G + source "https://gem.repo4" + + ruby "#{RUBY_VERSION}" + + gem "loofah", "~> 2.12.0" + G + + lockfile <<-L + GEM + remote: https://gem.repo4/ + specs: + crass (1.0.6) + loofah (2.12.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + nokogiri (1.12.4-x86_64-darwin) + racc (~> 1.4) + racc (1.5.2) + + PLATFORMS + x86_64-darwin-20 + x86_64-linux + + DEPENDENCIES + loofah (~> 2.12.0) + + RUBY VERSION + #{Bundler::RubyVersion.system} + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile" do + bundle "config set --local path vendor/bundle" + + simulate_platform "x86_64-linux" do + bundle "install", :artifice => "compact_index" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + crass (1.0.6) + loofah (2.12.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + nokogiri (1.12.4-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.12.4-x86_64-linux) + racc (~> 1.4) + racc (1.5.2) + + PLATFORMS + x86_64-darwin-20 + x86_64-linux + + DEPENDENCIES + loofah (~> 2.12.0) + + RUBY VERSION + #{Bundler::RubyVersion.system} + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "with --local flag" do + before do + system_gems "rack-1.0.0", :path => default_bundle_path + end + + it "respects installed gems without fetching any remote sources" do + install_gemfile <<-G, :local => true + source "#{file_uri_for(gem_repo1)}" + + source "https://not-existing-source" do + gem "rack" + end + G + + expect(last_command).to be_success + end + end end diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index 44bde4a9b8..22709f4528 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -86,7 +86,7 @@ RSpec.describe "bundle lock" do it "does not fetch remote specs when using the --local option" do bundle "lock --update --local", :raise_on_error => false - expect(err).to match(/sources listed in your Gemfile|installed locally/) + expect(err).to match(/locally installed gems/) end it "works with --gemfile flag" do @@ -314,7 +314,7 @@ RSpec.describe "bundle lock" do simulate_platform(mingw) { bundle :lock } - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo4)}/ specs: @@ -339,7 +339,7 @@ RSpec.describe "bundle lock" do simulate_platform(rb) { bundle :lock } - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo4)}/ specs: @@ -426,7 +426,7 @@ RSpec.describe "bundle lock" do simulate_platform(Gem::Platform.new("x86_64-darwin")) { bundle "lock" } - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo4)}/ specs: @@ -491,6 +491,51 @@ RSpec.describe "bundle lock" do end end + it "does not conflict on ruby requirements when adding new platforms" do + next_minor = Gem.ruby_version.segments[0..1].map.with_index {|s, i| i == 1 ? s + 1 : s }.join(".") + + build_repo4 do + build_gem "raygun-apm", "1.0.78" do |s| + s.platform = "x86_64-linux" + s.required_ruby_version = "< #{next_minor}.dev" + end + + build_gem "raygun-apm", "1.0.78" do |s| + s.platform = "universal-darwin" + s.required_ruby_version = "< #{next_minor}.dev" + end + + build_gem "raygun-apm", "1.0.78" do |s| + s.platform = "x64-mingw32" + s.required_ruby_version = "< #{next_minor}.dev" + end + end + + gemfile <<-G + source "https://localgemserver.test" + + gem "raygun-apm" + G + + lockfile <<-L + GEM + remote: https://localgemserver.test/ + specs: + raygun-apm (1.0.78-universal-darwin) + + PLATFORMS + x86_64-darwin-19 + + DEPENDENCIES + raygun-apm + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock --add-platform x86_64-linux", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + end + context "when an update is available" do let(:repo) { gem_repo2 } diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb index c5294a6b4d..db5228ebc2 100644 --- a/spec/bundler/commands/newgem_spec.rb +++ b/spec/bundler/commands/newgem_spec.rb @@ -12,15 +12,16 @@ RSpec.describe "bundle gem" do def bundle_exec_rubocop prepare_gemspec(bundled_app(gem_name, "#{gem_name}.gemspec")) - rubocop_version = RUBY_VERSION > "2.4" ? "1.7.0" : "0.81.0" - gems = ["minitest", "rake", "rake-compiler", "rspec", "rubocop -v #{rubocop_version}", "test-unit"] - gems.unshift "parallel -v 1.19.2" if RUBY_VERSION < "2.5" - gems += ["rubocop-ast -v 1.4.0"] if rubocop_version == "1.7.0" - path = Bundler.feature_flag.default_install_uses_path? ? local_gem_path(:base => bundled_app(gem_name)) : system_gem_path - realworld_system_gems gems, :path => path + bundle "config set path #{rubocop_gems}", :dir => bundled_app(gem_name) bundle "exec rubocop --debug --config .rubocop.yml", :dir => bundled_app(gem_name) end + def bundle_exec_standardrb + prepare_gemspec(bundled_app(gem_name, "#{gem_name}.gemspec")) + bundle "config set path #{standard_gems}", :dir => bundled_app(gem_name) + bundle "exec standardrb --debug", :dir => bundled_app(gem_name) + end + let(:generated_gemspec) { Bundler.load_gemspec_uncached(bundled_app(gem_name).join("#{gem_name}.gemspec")) } let(:gem_name) { "mygem" } @@ -28,77 +29,38 @@ RSpec.describe "bundle gem" do let(:require_path) { "mygem" } before do - git_config_content = <<-EOF - [user] - name = "Bundler User" - email = user@example.com - [github] - user = bundleuser - EOF - @git_config_location = ENV["GIT_CONFIG"] - path = "#{tmp}/test_git_config.txt" - File.open(path, "w") {|f| f.write(git_config_content) } - ENV["GIT_CONFIG"] = path - end - - after do - FileUtils.rm(ENV["GIT_CONFIG"]) if File.exist?(ENV["GIT_CONFIG"]) - ENV["GIT_CONFIG"] = @git_config_location - end - - shared_examples_for "git config is present" do - context "git config user.{name,email} present" do - it "sets gemspec author to git user.name if available" do - expect(generated_gemspec.authors.first).to eq("Bundler User") - end - - it "sets gemspec email to git user.email if available" do - expect(generated_gemspec.email.first).to eq("user@example.com") - end - end - end - - shared_examples_for "git config is absent" do - it "sets gemspec author to default message if git user.name is not set or empty" do - expect(generated_gemspec.authors.first).to eq("TODO: Write your name") - end - - it "sets gemspec email to default message if git user.email is not set or empty" do - expect(generated_gemspec.email.first).to eq("TODO: Write your email address") - end + sys_exec("git config --global user.name 'Bundler User'") + sys_exec("git config --global user.email user@example.com") + sys_exec("git config --global github.user bundleuser") end describe "git repo initialization" do - shared_examples_for "a gem with an initial git repo" do - before do - bundle "gem #{gem_name} #{flags}" - end - - it "generates a gem skeleton with a .git folder", :readline do - gem_skeleton_assertions - expect(bundled_app("#{gem_name}/.git")).to exist - end + it "generates a gem skeleton with a .git folder", :readline do + bundle "gem #{gem_name}" + gem_skeleton_assertions + expect(bundled_app("#{gem_name}/.git")).to exist end - context "when using the default" do - it_behaves_like "a gem with an initial git repo" do - let(:flags) { "" } - end + it "generates a gem skeleton with a .git folder when passing --git", :readline do + bundle "gem #{gem_name} --git" + gem_skeleton_assertions + expect(bundled_app("#{gem_name}/.git")).to exist end - context "when explicitly passing --git" do - it_behaves_like "a gem with an initial git repo" do - let(:flags) { "--git" } - end + it "generates a gem skeleton without a .git folder when passing --no-git", :readline do + bundle "gem #{gem_name} --no-git" + gem_skeleton_assertions + expect(bundled_app("#{gem_name}/.git")).not_to exist end - context "when passing --no-git", :readline do + context "on a path with spaces" do before do - bundle "gem #{gem_name} --no-git" + Dir.mkdir(bundled_app("path with spaces")) end - it "generates a gem skeleton without a .git folder" do - gem_skeleton_assertions - expect(bundled_app("#{gem_name}/.git")).not_to exist + + it "properly initializes git repo", :readline do + bundle "gem #{gem_name}", :dir => bundled_app("path with spaces") + expect(bundled_app("path with spaces/#{gem_name}/.git")).to exist end end end @@ -125,19 +87,24 @@ RSpec.describe "bundle gem" do end shared_examples_for "--coc flag" do - before do - bundle "gem #{gem_name} --coc" - end it "generates a gem skeleton with MIT license" do + bundle "gem #{gem_name} --coc" gem_skeleton_assertions expect(bundled_app("#{gem_name}/CODE_OF_CONDUCT.md")).to exist end - describe "README additions" do - it "generates the README with a section for the Code of Conduct" do - expect(bundled_app("#{gem_name}/README.md").read).to include("## Code of Conduct") - expect(bundled_app("#{gem_name}/README.md").read).to include("https://github.com/bundleuser/#{gem_name}/blob/master/CODE_OF_CONDUCT.md") - end + it "generates the README with a section for the Code of Conduct" do + bundle "gem #{gem_name} --coc" + expect(bundled_app("#{gem_name}/README.md").read).to include("## Code of Conduct") + expect(bundled_app("#{gem_name}/README.md").read).to match(%r{https://github\.com/bundleuser/#{gem_name}/blob/.*/CODE_OF_CONDUCT.md}) + end + + it "generates the README with a section for the Code of Conduct, respecting the configured git default branch", :git => ">= 2.28.0" do + sys_exec("git config --global init.defaultBranch main") + bundle "gem #{gem_name} --coc" + + expect(bundled_app("#{gem_name}/README.md").read).to include("## Code of Conduct") + expect(bundled_app("#{gem_name}/README.md").read).to include("https://github.com/bundleuser/#{gem_name}/blob/main/CODE_OF_CONDUCT.md") end end @@ -150,11 +117,9 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/CODE_OF_CONDUCT.md")).to_not exist end - describe "README additions" do - it "generates the README without a section for the Code of Conduct" do - expect(bundled_app("#{gem_name}/README.md").read).not_to include("## Code of Conduct") - expect(bundled_app("#{gem_name}/README.md").read).not_to include("https://github.com/bundleuser/#{gem_name}/blob/master/CODE_OF_CONDUCT.md") - end + it "generates the README without a section for the Code of Conduct" do + expect(bundled_app("#{gem_name}/README.md").read).not_to include("## Code of Conduct") + expect(bundled_app("#{gem_name}/README.md").read).not_to match(%r{https://github\.com/bundleuser/#{gem_name}/blob/.*/CODE_OF_CONDUCT.md}) end end @@ -179,8 +144,68 @@ RSpec.describe "bundle gem" do end shared_examples_for "--rubocop flag" do + context "is deprecated", :bundler => "< 3" do + before do + bundle "gem #{gem_name} --rubocop" + end + + it "generates a gem skeleton with rubocop" do + gem_skeleton_assertions + expect(bundled_app("test-gem/Rakefile")).to read_as( + include("# frozen_string_literal: true"). + and(include('require "rubocop/rake_task"'). + and(include("RuboCop::RakeTask.new"). + and(match(/default:.+:rubocop/)))) + ) + end + + it "includes rubocop in generated Gemfile" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + builder = Bundler::Dsl.new + builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) + builder.dependencies + rubocop_dep = builder.dependencies.find {|d| d.name == "rubocop" } + expect(rubocop_dep).not_to be_nil + end + + it "generates a default .rubocop.yml" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist + end + end + end + + shared_examples_for "--no-rubocop flag" do + context "is deprecated", :bundler => "< 3" do + define_negated_matcher :exclude, :include + + before do + bundle "gem #{gem_name} --no-rubocop" + end + + it "generates a gem skeleton without rubocop" do + gem_skeleton_assertions + expect(bundled_app("test-gem/Rakefile")).to read_as(exclude("rubocop")) + expect(bundled_app("test-gem/#{gem_name}.gemspec")).to read_as(exclude("rubocop")) + end + + it "does not include rubocop in generated Gemfile" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + builder = Bundler::Dsl.new + builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) + builder.dependencies + rubocop_dep = builder.dependencies.find {|d| d.name == "rubocop" } + expect(rubocop_dep).to be_nil + end + + it "doesn't generate a default .rubocop.yml" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist + end + end + end + + shared_examples_for "--linter=rubocop flag" do before do - bundle "gem #{gem_name} --rubocop" + bundle "gem #{gem_name} --linter=rubocop" end it "generates a gem skeleton with rubocop" do @@ -207,11 +232,38 @@ RSpec.describe "bundle gem" do end end - shared_examples_for "--no-rubocop flag" do + shared_examples_for "--linter=standard flag" do + before do + bundle "gem #{gem_name} --linter=standard" + end + + it "generates a gem skeleton with standard" do + gem_skeleton_assertions + expect(bundled_app("test-gem/Rakefile")).to read_as( + include('require "standard/rake"'). + and(match(/default:.+:standard/)) + ) + end + + it "includes standard in generated Gemfile" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + builder = Bundler::Dsl.new + builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) + builder.dependencies + standard_dep = builder.dependencies.find {|d| d.name == "standard" } + expect(standard_dep).not_to be_nil + end + + it "generates a default .standard.yml" do + expect(bundled_app("#{gem_name}/.standard.yml")).to exist + end + end + + shared_examples_for "--linter=none flag" do define_negated_matcher :exclude, :include before do - bundle "gem #{gem_name} --no-rubocop" + bundle "gem #{gem_name} --linter=none" end it "generates a gem skeleton without rubocop" do @@ -229,43 +281,63 @@ RSpec.describe "bundle gem" do expect(rubocop_dep).to be_nil end + it "does not include standard in generated Gemfile" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + builder = Bundler::Dsl.new + builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) + builder.dependencies + standard_dep = builder.dependencies.find {|d| d.name == "standard" } + expect(standard_dep).to be_nil + end + it "doesn't generate a default .rubocop.yml" do expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist end + + it "doesn't generate a default .standard.yml" do + expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist + end end - it "has no rubocop offenses when using --rubocop flag", :readline do + it "has no rubocop offenses when using --linter=rubocop flag", :readline do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - bundle "gem #{gem_name} --rubocop" + bundle "gem #{gem_name} --linter=rubocop" bundle_exec_rubocop - expect(err).to be_empty + expect(last_command).to be_success end - it "has no rubocop offenses when using --ext and --rubocop flag", :readline do + it "has no rubocop offenses when using --ext and --linter=rubocop flag", :readline do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - bundle "gem #{gem_name} --ext --rubocop" + bundle "gem #{gem_name} --ext --linter=rubocop" bundle_exec_rubocop - expect(err).to be_empty + expect(last_command).to be_success end - it "has no rubocop offenses when using --ext, --test=minitest, and --rubocop flag", :readline do + it "has no rubocop offenses when using --ext, --test=minitest, and --linter=rubocop flag", :readline do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - bundle "gem #{gem_name} --ext --test=minitest --rubocop" + bundle "gem #{gem_name} --ext --test=minitest --linter=rubocop" bundle_exec_rubocop - expect(err).to be_empty + expect(last_command).to be_success end - it "has no rubocop offenses when using --ext, --test=rspec, and --rubocop flag", :readline do + it "has no rubocop offenses when using --ext, --test=rspec, and --linter=rubocop flag", :readline do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - bundle "gem #{gem_name} --ext --test=rspec --rubocop" + bundle "gem #{gem_name} --ext --test=rspec --linter=rubocop" bundle_exec_rubocop - expect(err).to be_empty + expect(last_command).to be_success end - it "has no rubocop offenses when using --ext, --ext=test-unit, and --rubocop flag", :readline do + it "has no rubocop offenses when using --ext, --ext=test-unit, and --linter=rubocop flag", :readline do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - bundle "gem #{gem_name} --ext --test=test-unit --rubocop" + bundle "gem #{gem_name} --ext --test=test-unit --linter=rubocop" bundle_exec_rubocop + expect(last_command).to be_success + end + + it "has no standard offenses when using --linter=standard flag", :readline do + skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? + bundle "gem #{gem_name} --linter=standard" + bundle_exec_standardrb expect(err).to be_empty end @@ -302,7 +374,7 @@ RSpec.describe "bundle gem" do context "git config github.user is absent" do before do - sys_exec("git config --unset github.user") + sys_exec("git config --global --unset github.user") bundle "gem #{gem_name}" end @@ -383,6 +455,55 @@ RSpec.describe "bundle gem" do end end + shared_examples_for "--github-username option" do |github_username| + before do + bundle "gem #{gem_name} --github-username=#{github_username}" + end + + it "generates a gem skeleton" do + gem_skeleton_assertions + end + + it "contribute URL set to given github username" do + expect(bundled_app("#{gem_name}/README.md").read).not_to include("[USERNAME]") + expect(bundled_app("#{gem_name}/README.md").read).to include("github.com/#{github_username}") + end + end + + shared_examples_for "github_username configuration" do + context "with github_username setting set to some value" do + before do + global_config "BUNDLE_GEM__GITHUB_USERNAME" => "different_username" + bundle "gem #{gem_name}" + end + + it "generates a gem skeleton" do + gem_skeleton_assertions + end + + it "contribute URL set to bundle config setting" do + expect(bundled_app("#{gem_name}/README.md").read).not_to include("[USERNAME]") + expect(bundled_app("#{gem_name}/README.md").read).to include("github.com/different_username") + end + end + + context "with github_username setting set to false" do + before do + global_config "BUNDLE_GEM__GITHUB_USERNAME" => "false" + bundle "gem #{gem_name}" + end + + it "generates a gem skeleton" do + gem_skeleton_assertions + end + + it "contribute URL set to [USERNAME]" do + expect(bundled_app("#{gem_name}/README.md").read).to include("[USERNAME]") + expect(bundled_app("#{gem_name}/README.md").read).not_to include("github.com/bundleuser") + end + end + end + shared_examples_for "generating a gem" do it "generates a gem skeleton" do bundle "gem #{gem_name}" @@ -392,6 +513,7 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/Rakefile")).to exist expect(bundled_app("#{gem_name}/lib/#{require_path}.rb")).to exist expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb")).to exist + expect(bundled_app("#{gem_name}/sig/#{require_path}.rbs")).to exist expect(bundled_app("#{gem_name}/.gitignore")).to exist expect(bundled_app("#{gem_name}/bin/setup")).to exist @@ -408,35 +530,53 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb").read).to match(/VERSION = "0.1.0"/) end + it "declare String type for VERSION constant" do + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/sig/#{require_path}.rbs").read).to match(/VERSION: String/) + end + context "git config user.{name,email} is set" do before do bundle "gem #{gem_name}" end - it_should_behave_like "git config is present" + it "sets gemspec author to git user.name if available" do + expect(generated_gemspec.authors.first).to eq("Bundler User") + end + + it "sets gemspec email to git user.email if available" do + expect(generated_gemspec.email.first).to eq("user@example.com") + end end context "git config user.{name,email} is not set" do before do - sys_exec("git config --unset user.name", :dir => bundled_app) - sys_exec("git config --unset user.email", :dir => bundled_app) + sys_exec("git config --global --unset user.name") + sys_exec("git config --global --unset user.email") bundle "gem #{gem_name}" end - it_should_behave_like "git config is absent" + it "sets gemspec author to default message if git user.name is not set or empty" do + expect(generated_gemspec.authors.first).to eq("TODO: Write your name") + end + + it "sets gemspec email to default message if git user.email is not set or empty" do + expect(generated_gemspec.email.first).to eq("TODO: Write your email address") + end end it "sets gemspec metadata['allowed_push_host']" do bundle "gem #{gem_name}" expect(generated_gemspec.metadata["allowed_push_host"]). - to match(/mygemserver\.com/) + to match(/example\.com/) end it "sets a minimum ruby version" do bundle "gem #{gem_name}" - expect(generated_gemspec.required_ruby_version).to eq(Gem::Requirement.new(Gem.ruby_version < Gem::Version.new("2.4.a") ? ">= 2.3.0" : ">= 2.4.0")) + expect(generated_gemspec.required_ruby_version.to_s).to start_with(">=") end it "requires the version file" do @@ -451,6 +591,14 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/class Error < StandardError; end$/) end + it "does not include the gemspec file in files" do + bundle "gem #{gem_name}" + + bundler_gemspec = Bundler::GemHelper.new(gemspec_dir).gemspec + + expect(bundler_gemspec.files).not_to include("#{gem_name}.gemspec") + end + it "runs rake without problems" do bundle "gem #{gem_name}" @@ -861,6 +1009,127 @@ RSpec.describe "bundle gem" do end end + context "--linter with no argument" do + it "does not generate any linter config" do + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist + expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist + end + end + + context "--linter set to rubocop" do + it "generates a RuboCop config" do + bundle "gem #{gem_name} --linter=rubocop" + + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist + expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist + end + end + + context "--linter set to standard" do + it "generates a Standard config" do + bundle "gem #{gem_name} --linter=standard" + + expect(bundled_app("#{gem_name}/.standard.yml")).to exist + expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist + end + end + + context "gem.linter setting set to none" do + it "doesn't generate any linter config" do + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist + expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist + end + end + + context "gem.linter setting set to rubocop" do + it "generates a RuboCop config file" do + bundle "config set gem.linter rubocop" + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist + end + end + + context "gem.linter setting set to standard" do + it "generates a Standard config file" do + bundle "config set gem.linter standard" + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/.standard.yml")).to exist + end + end + + context "gem.rubocop setting set to true", :bundler => "< 3" do + before do + bundle "config set gem.rubocop true" + bundle "gem #{gem_name}" + end + + it "generates rubocop config" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist + end + + it "unsets gem.rubocop" do + bundle "config gem.rubocop" + expect(out).to include("You have not configured a value for `gem.rubocop`") + end + + it "sets gem.linter=rubocop instead" do + bundle "config gem.linter" + expect(out).to match(/Set for the current user .*: "rubocop"/) + end + end + + context "gem.linter set to rubocop and --linter with no arguments", :hint_text do + before do + bundle "config set gem.linter rubocop" + bundle "gem #{gem_name} --linter" + end + + it "generates a RuboCop config file" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist + end + + it "hints that --linter is already configured" do + expect(out).to match("rubocop is already configured, ignoring --linter flag.") + end + end + + context "gem.linter setting set to false and --linter with no arguments", :hint_text do + before do + bundle "config set gem.linter false" + bundle "gem #{gem_name} --linter" + end + + it "asks to setup a linter" do + expect(out).to match("Do you want to add a code linter and formatter to your gem?") + end + + it "hints that the choice will only be applied to the current gem" do + expect(out).to match("Your choice will only be applied to this gem.") + end + end + + context "gem.linter setting not set and --linter with no arguments", :hint_text do + before do + bundle "gem #{gem_name} --linter" + end + + it "asks to setup a linter" do + expect(out).to match("Do you want to add a code linter and formatter to your gem?") + end + + it "hints that the choice will be applied to future bundle gem calls" do + hint = "Future `bundle gem` calls will use your choice. " \ + "This setting can be changed anytime with `bundle config gem.linter`." + expect(out).to match(hint) + end + end + context "--edit option" do it "opens the generated gemspec in the user's text editor" do output = bundle "gem #{gem_name} --edit=echo" @@ -911,6 +1180,9 @@ RSpec.describe "bundle gem" do before do global_config "BUNDLE_GEM__RUBOCOP" => "true" end + it_behaves_like "--linter=rubocop flag" + it_behaves_like "--linter=standard flag" + it_behaves_like "--linter=none flag" it_behaves_like "--rubocop flag" it_behaves_like "--no-rubocop flag" end @@ -919,10 +1191,40 @@ RSpec.describe "bundle gem" do before do global_config "BUNDLE_GEM__RUBOCOP" => "false" end + it_behaves_like "--linter=rubocop flag" + it_behaves_like "--linter=standard flag" + it_behaves_like "--linter=none flag" it_behaves_like "--rubocop flag" it_behaves_like "--no-rubocop flag" end + context "with linter option in bundle config settings set to rubocop" do + before do + global_config "BUNDLE_GEM__LINTER" => "rubocop" + end + it_behaves_like "--linter=rubocop flag" + it_behaves_like "--linter=standard flag" + it_behaves_like "--linter=none flag" + end + + context "with linter option in bundle config settings set to standard" do + before do + global_config "BUNDLE_GEM__LINTER" => "standard" + end + it_behaves_like "--linter=rubocop flag" + it_behaves_like "--linter=standard flag" + it_behaves_like "--linter=none flag" + end + + context "with linter option in bundle config settings set to false" do + before do + global_config "BUNDLE_GEM__LINTER" => "false" + end + it_behaves_like "--linter=rubocop flag" + it_behaves_like "--linter=standard flag" + it_behaves_like "--linter=none flag" + end + context "with changelog option in bundle config settings set to true" do before do global_config "BUNDLE_GEM__CHANGELOG" => "true" @@ -940,6 +1242,57 @@ RSpec.describe "bundle gem" do end end + context "testing --github-username option against git and bundle config settings", :readline do + context "without git config set" do + before do + sys_exec("git config --global --unset github.user") + end + context "with github-username option in bundle config settings set to some value" do + before do + global_config "BUNDLE_GEM__GITHUB_USERNAME" => "different_username" + end + it_behaves_like "--github-username option", "gh_user" + end + + context "with github-username option in bundle config settings set to false" do + before do + global_config "BUNDLE_GEM__GITHUB_USERNAME" => "false" + end + it_behaves_like "--github-username option", "gh_user" + end + end + + context "with git config set" do + context "with github-username option in bundle config settings set to some value" do + before do + global_config "BUNDLE_GEM__GITHUB_USERNAME" => "different_username" + end + it_behaves_like "--github-username option", "gh_user" + end + + context "with github-username option in bundle config settings set to false" do + before do + global_config "BUNDLE_GEM__GITHUB_USERNAME" => "false" + end + it_behaves_like "--github-username option", "gh_user" + end + end + end + + context "testing github_username bundle config against git config settings", :readline do + context "without git config set" do + before do + sys_exec("git config --global --unset github.user") + end + + it_behaves_like "github_username configuration" + end + + context "with git config set" do + it_behaves_like "github_username configuration" + end + end + context "gem naming with underscore", :readline do let(:gem_name) { "test_gem" } @@ -1093,7 +1446,7 @@ Usage: "bundle gem NAME [OPTIONS]" end it "asks about CI service" do - global_config "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__COC" => "false", "BUNDLE_GEM__RUBOCOP" => "false" + global_config "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__COC" => "false", "BUNDLE_GEM__LINTER" => "false" bundle "gem foobar" do |input, _, _| input.puts "github" @@ -1103,7 +1456,7 @@ Usage: "bundle gem NAME [OPTIONS]" end it "asks about MIT license" do - global_config "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false", "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__RUBOCOP" => "false" + global_config "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false", "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__LINTER" => "false" bundle "config list" @@ -1115,7 +1468,7 @@ Usage: "bundle gem NAME [OPTIONS]" end it "asks about CoC" do - global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__RUBOCOP" => "false" + global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__LINTER" => "false" bundle "gem foobar" do |input, _, _| input.puts "yes" @@ -1125,7 +1478,7 @@ Usage: "bundle gem NAME [OPTIONS]" end it "asks about CHANGELOG" do - global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__RUBOCOP" => "false", + global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__LINTER" => "false", "BUNDLE_GEM__COC" => "false" bundle "gem foobar" do |input, _, _| @@ -1140,7 +1493,7 @@ Usage: "bundle gem NAME [OPTIONS]" it "should fail gracefully" do FileUtils.touch(bundled_app("conflict-foobar")) bundle "gem conflict-foobar", :raise_on_error => false - expect(err).to include("Errno::ENOTDIR") + expect(err).to eq("Couldn't create a new gem named `conflict-foobar` because there's an existing file named `conflict-foobar`.") expect(exitstatus).to eql(32) end end diff --git a/spec/bundler/commands/open_spec.rb b/spec/bundler/commands/open_spec.rb index d18e620783..53dc35c2c7 100644 --- a/spec/bundler/commands/open_spec.rb +++ b/spec/bundler/commands/open_spec.rb @@ -105,6 +105,7 @@ RSpec.describe "bundle open" do skip "No default gems available on this test run" if default_gems.empty? install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "json" G end diff --git a/spec/bundler/commands/outdated_spec.rb b/spec/bundler/commands/outdated_spec.rb index 0ee8bd425a..731d67af1b 100644 --- a/spec/bundler/commands/outdated_spec.rb +++ b/spec/bundler/commands/outdated_spec.rb @@ -1,24 +1,24 @@ # frozen_string_literal: true RSpec.describe "bundle outdated" do - before :each do - build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") - end - - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "zebra", :git => "#{lib_path("zebra")}" - gem "foo", :git => "#{lib_path("foo")}" - gem "activesupport", "2.3.5" - gem "weakling", "~> 0.0.1" - gem "duradura", '7.0' - gem "terranova", '8' - G - end - describe "with no arguments" do + before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "zebra", :git => "#{lib_path("zebra")}" + gem "foo", :git => "#{lib_path("foo")}" + gem "activesupport", "2.3.5" + gem "weakling", "~> 0.0.1" + gem "duradura", '7.0' + gem "terranova", '8' + G + end + it "returns a sorted list of outdated gems" do update_repo2 do build_gem "activesupport", "3.0" @@ -102,6 +102,23 @@ RSpec.describe "bundle outdated" do end describe "with --verbose option" do + before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "zebra", :git => "#{lib_path("zebra")}" + gem "foo", :git => "#{lib_path("foo")}" + gem "activesupport", "2.3.5" + gem "weakling", "~> 0.0.1" + gem "duradura", '7.0' + gem "terranova", '8' + G + end + it "shows the location of the latest version's gemspec if installed" do bundle "config set clean false" @@ -134,8 +151,79 @@ RSpec.describe "bundle outdated" do end end + describe "with multiple, duplicated sources, with lockfile in old format", :bundler => "< 3" do + before do + build_repo2 do + build_gem "dotenv", "2.7.6" + + build_gem "oj", "3.11.3" + build_gem "oj", "3.11.5" + + build_gem "vcr", "6.0.0" + end + + build_repo gem_repo3 do + build_gem "pkg-gem-flowbyte-with-dep", "1.0.0" do |s| + s.add_dependency "oj" + end + end + + gemfile <<~G + source "https://gem.repo2" + + gem "dotenv" + + source "https://gem.repo3" do + gem 'pkg-gem-flowbyte-with-dep' + end + + gem "vcr",source: "https://gem.repo2" + G + + lockfile <<~L + GEM + remote: https://gem.repo2/ + remote: https://gem.repo3/ + specs: + dotenv (2.7.6) + oj (3.11.3) + pkg-gem-flowbyte-with-dep (1.0.0) + oj + vcr (6.0.0) + + PLATFORMS + #{specific_local_platform} + + DEPENDENCIES + dotenv + pkg-gem-flowbyte-with-dep! + vcr! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "works" do + bundle :install, :artifice => "compact_index" + bundle :outdated, :artifice => "compact_index", :raise_on_error => false + + expected_output = <<~TABLE + Gem Current Latest Requested Groups + oj 3.11.3 3.11.5 + TABLE + + expect(out).to include(expected_output.strip) + end + end + describe "with --group option" do before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" @@ -201,7 +289,10 @@ RSpec.describe "bundle outdated" do describe "with --groups option and outdated transitive dependencies" do before do - update_repo2 do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + build_gem "bar", %w[2.0.0] build_gem "bar_dependant", "7.0" do |s| @@ -234,6 +325,11 @@ RSpec.describe "bundle outdated" do describe "with --groups option" do before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" @@ -272,6 +368,24 @@ RSpec.describe "bundle outdated" do end describe "with --local option" do + before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + + gem "weakling", "~> 0.0.1" + gem "terranova", '8' + group :development, :test do + gem 'activesupport', '2.3.5' + gem "duradura", '7.0' + end + G + end + it "uses local cache to return a list of outdated gems" do update_repo2 do build_gem "activesupport", "2.3.4" @@ -305,10 +419,23 @@ RSpec.describe "bundle outdated" do shared_examples_for "a minimal output is desired" do context "and gems are outdated" do before do - update_repo2 do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + build_gem "activesupport", "3.0" build_gem "weakling", "0.2" end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "zebra", :git => "#{lib_path("zebra")}" + gem "foo", :git => "#{lib_path("foo")}" + gem "activesupport", "2.3.5" + gem "weakling", "~> 0.0.1" + gem "duradura", '7.0' + gem "terranova", '8' + G end it "outputs a sorted list of outdated gems with a more minimal format" do @@ -341,6 +468,21 @@ RSpec.describe "bundle outdated" do describe "with specified gems" do it "returns list of outdated gems" do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "zebra", :git => "#{lib_path("zebra")}" + gem "foo", :git => "#{lib_path("foo")}" + gem "activesupport", "2.3.5" + gem "weakling", "~> 0.0.1" + gem "duradura", '7.0' + gem "terranova", '8' + G + update_repo2 do build_gem "activesupport", "3.0" update_git "foo", :path => lib_path("foo") @@ -358,6 +500,23 @@ RSpec.describe "bundle outdated" do end describe "pre-release gems" do + before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "zebra", :git => "#{lib_path("zebra")}" + gem "foo", :git => "#{lib_path("foo")}" + gem "activesupport", "2.3.5" + gem "weakling", "~> 0.0.1" + gem "duradura", '7.0' + gem "terranova", '8' + G + end + context "without the --pre option" do it "ignores pre-release versions" do update_repo2 do @@ -413,6 +572,23 @@ RSpec.describe "bundle outdated" do filter_strict_option = Bundler.feature_flag.bundler_2_mode? ? :"filter-strict" : :strict describe "with --#{filter_strict_option} option" do + before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "zebra", :git => "#{lib_path("zebra")}" + gem "foo", :git => "#{lib_path("foo")}" + gem "activesupport", "2.3.5" + gem "weakling", "~> 0.0.1" + gem "duradura", '7.0' + gem "terranova", '8' + G + end + it "only reports gems that have a newer version that matches the specified dependency version requirements" do update_repo2 do build_gem "activesupport", "3.0" @@ -521,6 +697,23 @@ RSpec.describe "bundle outdated" do end describe "with invalid gem name" do + before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "zebra", :git => "#{lib_path("zebra")}" + gem "foo", :git => "#{lib_path("foo")}" + gem "activesupport", "2.3.5" + gem "weakling", "~> 0.0.1" + gem "duradura", '7.0' + gem "terranova", '8' + G + end + it "returns could not find gem name" do bundle "outdated invalid_gem_name", :raise_on_error => false expect(err).to include("Could not find gem 'invalid_gem_name'.") @@ -546,12 +739,16 @@ RSpec.describe "bundle outdated" do context "after bundle install --deployment", :bundler => "< 3" do before do - install_gemfile <<-G, :deployment => true, :raise_on_error => false + build_repo2 + + gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "rack" gem "foo" G + bundle :lock + bundle :install, :deployment => true end it "outputs a helpful message about being in deployment mode" do @@ -568,6 +765,11 @@ RSpec.describe "bundle outdated" do context "after bundle config set --local deployment true" do before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" @@ -591,6 +793,8 @@ RSpec.describe "bundle outdated" do context "update available for a gem on a different platform" do before do + build_repo2 + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "laduradura", '= 5.15.2' @@ -604,6 +808,10 @@ RSpec.describe "bundle outdated" do end context "update available for a gem on the same platform while multiple platforms used for gem" do + before do + build_repo2 + end + it "reports that updates are available if the Ruby platform is used" do install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" @@ -643,6 +851,21 @@ RSpec.describe "bundle outdated" do shared_examples_for "major version updates are detected" do before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "zebra", :git => "#{lib_path("zebra")}" + gem "foo", :git => "#{lib_path("foo")}" + gem "activesupport", "2.3.5" + gem "weakling", "~> 0.0.1" + gem "duradura", '7.0' + gem "terranova", '8' + G + update_repo2 do build_gem "activesupport", "3.3.5" build_gem "weakling", "0.8.0" @@ -654,6 +877,21 @@ RSpec.describe "bundle outdated" do context "when on a new machine" do before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "zebra", :git => "#{lib_path("zebra")}" + gem "foo", :git => "#{lib_path("foo")}" + gem "activesupport", "2.3.5" + gem "weakling", "~> 0.0.1" + gem "duradura", '7.0' + gem "terranova", '8' + G + simulate_new_machine update_git "foo", :path => lib_path("foo") @@ -669,6 +907,21 @@ RSpec.describe "bundle outdated" do shared_examples_for "minor version updates are detected" do before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "zebra", :git => "#{lib_path("zebra")}" + gem "foo", :git => "#{lib_path("foo")}" + gem "activesupport", "2.3.5" + gem "weakling", "~> 0.0.1" + gem "duradura", '7.0' + gem "terranova", '8' + G + update_repo2 do build_gem "activesupport", "2.7.5" build_gem "weakling", "2.0.1" @@ -680,6 +933,21 @@ RSpec.describe "bundle outdated" do shared_examples_for "patch version updates are detected" do before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "zebra", :git => "#{lib_path("zebra")}" + gem "foo", :git => "#{lib_path("foo")}" + gem "activesupport", "2.3.5" + gem "weakling", "~> 0.0.1" + gem "duradura", '7.0' + gem "terranova", '8' + G + update_repo2 do build_gem "activesupport", "2.3.7" build_gem "weakling", "0.3.1" @@ -698,6 +966,21 @@ RSpec.describe "bundle outdated" do shared_examples_for "major version is ignored" do before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "zebra", :git => "#{lib_path("zebra")}" + gem "foo", :git => "#{lib_path("foo")}" + gem "activesupport", "2.3.5" + gem "weakling", "~> 0.0.1" + gem "duradura", '7.0' + gem "terranova", '8' + G + update_repo2 do build_gem "activesupport", "3.3.5" build_gem "weakling", "1.0.1" @@ -709,6 +992,21 @@ RSpec.describe "bundle outdated" do shared_examples_for "minor version is ignored" do before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "zebra", :git => "#{lib_path("zebra")}" + gem "foo", :git => "#{lib_path("foo")}" + gem "activesupport", "2.3.5" + gem "weakling", "~> 0.0.1" + gem "duradura", '7.0' + gem "terranova", '8' + G + update_repo2 do build_gem "activesupport", "2.4.5" build_gem "weakling", "0.3.1" @@ -720,6 +1018,21 @@ RSpec.describe "bundle outdated" do shared_examples_for "patch version is ignored" do before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "zebra", :git => "#{lib_path("zebra")}" + gem "foo", :git => "#{lib_path("foo")}" + gem "activesupport", "2.3.5" + gem "weakling", "~> 0.0.1" + gem "duradura", '7.0' + gem "terranova", '8' + G + update_repo2 do build_gem "activesupport", "2.3.6" build_gem "weakling", "0.0.4" @@ -929,4 +1242,103 @@ RSpec.describe "bundle outdated" do expect(out).to end_with(expected_output) end end + + describe "with a multiplatform lockfile" do + before do + build_repo4 do + build_gem "nokogiri", "1.11.1" + build_gem "nokogiri", "1.11.1" do |s| + s.platform = Bundler.local_platform + end + + build_gem "nokogiri", "1.11.2" + build_gem "nokogiri", "1.11.2" do |s| + s.platform = Bundler.local_platform + end + end + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.11.1) + nokogiri (1.11.1-#{Bundler.local_platform}) + + PLATFORMS + ruby + #{Bundler.local_platform} + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "nokogiri" + G + end + + it "reports a single entry per gem" do + bundle "outdated", :raise_on_error => false + + expected_output = <<~TABLE.strip + Gem Current Latest Requested Groups + nokogiri 1.11.1 1.11.2 >= 0 default + TABLE + + expect(out).to end_with(expected_output) + end + end + + context "when a gem is no longer a dependency after a full update" do + before do + build_repo4 do + build_gem "mini_portile2", "2.5.2" do |s| + s.add_dependency "net-ftp", "~> 0.1" + end + + build_gem "mini_portile2", "2.5.3" + + build_gem "net-ftp", "0.1.2" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "mini_portile2" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + mini_portile2 (2.5.2) + net-ftp (~> 0.1) + net-ftp (0.1.2) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + mini_portile2 + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "works" do + bundle "outdated", :raise_on_error => false + + expected_output = <<~TABLE.strip + Gem Current Latest Requested Groups + mini_portile2 2.5.2 2.5.3 >= 0 default + TABLE + + expect(out).to end_with(expected_output) + end + end end diff --git a/spec/bundler/commands/post_bundle_message_spec.rb b/spec/bundler/commands/post_bundle_message_spec.rb index 2c965f0ddd..3050b87754 100644 --- a/spec/bundler/commands/post_bundle_message_spec.rb +++ b/spec/bundler/commands/post_bundle_message_spec.rb @@ -29,24 +29,24 @@ RSpec.describe "post bundle message" do expect(out).to include(bundle_complete_message) expect(out).to include(installed_gems_stats) - bundle "config --local without emo" + bundle "config set --local without emo" bundle :install expect(out).to include(bundle_show_message) - expect(out).to include("Gems in the group emo were not installed") + expect(out).to include("Gems in the group 'emo' were not installed") expect(out).to include(bundle_complete_message) expect(out).to include(installed_gems_stats) - bundle "config --local without emo test" + bundle "config set --local without emo test" bundle :install expect(out).to include(bundle_show_message) - expect(out).to include("Gems in the groups emo and test were not installed") + expect(out).to include("Gems in the groups 'emo' and 'test' were not installed") expect(out).to include(bundle_complete_message) expect(out).to include("4 Gemfile dependencies, 3 gems now installed.") - bundle "config --local without emo obama test" + bundle "config set --local without emo obama test" bundle :install expect(out).to include(bundle_show_message) - expect(out).to include("Gems in the groups emo, obama and test were not installed") + expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed") expect(out).to include(bundle_complete_message) expect(out).to include("4 Gemfile dependencies, 2 gems now installed.") end @@ -55,31 +55,31 @@ RSpec.describe "post bundle message" do let(:bundle_path) { "./vendor" } it "shows proper messages according to the configured groups" do - bundle "config --local path vendor" + bundle "config set --local path vendor" bundle :install expect(out).to include(bundle_show_path_message) expect(out).to_not include("Gems in the group") expect(out).to include(bundle_complete_message) - bundle "config --local path vendor" - bundle "config --local without emo" + bundle "config set --local path vendor" + bundle "config set --local without emo" bundle :install expect(out).to include(bundle_show_path_message) - expect(out).to include("Gems in the group emo were not installed") + expect(out).to include("Gems in the group 'emo' were not installed") expect(out).to include(bundle_complete_message) - bundle "config --local path vendor" - bundle "config --local without emo test" + bundle "config set --local path vendor" + bundle "config set --local without emo test" bundle :install expect(out).to include(bundle_show_path_message) - expect(out).to include("Gems in the groups emo and test were not installed") + expect(out).to include("Gems in the groups 'emo' and 'test' were not installed") expect(out).to include(bundle_complete_message) - bundle "config --local path vendor" - bundle "config --local without emo obama test" + bundle "config set --local path vendor" + bundle "config set --local without emo obama test" bundle :install expect(out).to include(bundle_show_path_message) - expect(out).to include("Gems in the groups emo, obama and test were not installed") + expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed") expect(out).to include(bundle_complete_message) end end @@ -88,7 +88,7 @@ RSpec.describe "post bundle message" do let(:bundle_path) { bundled_app("cache") } it "shows proper messages according to the configured groups" do - bundle "config --local path #{bundle_path}" + bundle "config set --local path #{bundle_path}" bundle :install expect(out).to include("Bundled gems are installed into `./cache`") expect(out).to_not include("Gems in the group") @@ -100,7 +100,7 @@ RSpec.describe "post bundle message" do let(:bundle_path) { tmp("not_bundled_app") } it "shows proper messages according to the configured groups" do - bundle "config --local path #{bundle_path}" + bundle "config set --local path #{bundle_path}" bundle :install expect(out).to include("Bundled gems are installed into `#{tmp("not_bundled_app")}`") expect(out).to_not include("Gems in the group") @@ -113,16 +113,7 @@ RSpec.describe "post bundle message" do bundle "config set force_ruby_platform true" end - it "should report a helpful error message", :bundler => "< 3" do - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "not-a-gem", :group => :development - G - expect(err).to include("Could not find gem 'not-a-gem' in any of the gem sources listed in your Gemfile.") - end - - it "should report a helpful error message", :bundler => "3" do + it "should report a helpful error message" do install_gemfile <<-G, :raise_on_error => false source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -130,7 +121,6 @@ RSpec.describe "post bundle message" do G expect(err).to include <<-EOS.strip Could not find gem 'not-a-gem' in rubygems repository #{file_uri_for(gem_repo1)}/ or installed locally. -The source does not contain any versions of 'not-a-gem' EOS end @@ -165,7 +155,7 @@ The source does not contain any versions of 'not-a-gem' bundle "install --without emo" bundle :install expect(out).to include(bundle_show_message) - expect(out).to include("Gems in the group emo were not installed") + expect(out).to include("Gems in the group 'emo' were not installed") expect(out).to include(bundle_complete_message) expect(out).to include(installed_gems_stats) end @@ -174,7 +164,7 @@ The source does not contain any versions of 'not-a-gem' bundle "install --without emo test" bundle :install expect(out).to include(bundle_show_message) - expect(out).to include("Gems in the groups emo and test were not installed") + expect(out).to include("Gems in the groups 'emo' and 'test' were not installed") expect(out).to include(bundle_complete_message) end @@ -182,7 +172,7 @@ The source does not contain any versions of 'not-a-gem' bundle "install --without emo obama test" bundle :install expect(out).to include(bundle_show_message) - expect(out).to include("Gems in the groups emo, obama and test were not installed") + expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed") expect(out).to include(bundle_complete_message) end end @@ -193,22 +183,22 @@ The source does not contain any versions of 'not-a-gem' expect(out).not_to include("Gems in the groups") expect(out).to include(bundle_updated_message) - bundle "config --local without emo" + bundle "config set --local without emo" bundle :install bundle :update, :all => true - expect(out).to include("Gems in the group emo were not updated") + expect(out).to include("Gems in the group 'emo' were not updated") expect(out).to include(bundle_updated_message) - bundle "config --local without emo test" + bundle "config set --local without emo test" bundle :install bundle :update, :all => true - expect(out).to include("Gems in the groups emo and test were not updated") + expect(out).to include("Gems in the groups 'emo' and 'test' were not updated") expect(out).to include(bundle_updated_message) - bundle "config --local without emo obama test" + bundle "config set --local without emo obama test" bundle :install bundle :update, :all => true - expect(out).to include("Gems in the groups emo, obama and test were not updated") + expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not updated") expect(out).to include(bundle_updated_message) end end diff --git a/spec/bundler/commands/pristine_spec.rb b/spec/bundler/commands/pristine_spec.rb index 6978f302c1..2f730bd4e2 100644 --- a/spec/bundler/commands/pristine_spec.rb +++ b/spec/bundler/commands/pristine_spec.rb @@ -203,6 +203,16 @@ RSpec.describe "bundle pristine", :ruby_repo do end end + context "when BUNDLE_GEMFILE doesn't exist" do + before do + bundle "pristine", :env => { "BUNDLE_GEMFILE" => "does/not/exist" }, :raise_on_error => false + end + + it "shows a meaningful error" do + expect(err).to eq("#{bundled_app("does/not/exist")} not found") + end + end + def find_spec(name) without_env_side_effects do Bundler.definition.specs[name].first diff --git a/spec/bundler/commands/remove_spec.rb b/spec/bundler/commands/remove_spec.rb index 46c42fea10..95d6e75e9f 100644 --- a/spec/bundler/commands/remove_spec.rb +++ b/spec/bundler/commands/remove_spec.rb @@ -13,7 +13,7 @@ RSpec.describe "bundle remove" do end end - context "when --install flag is specified" do + context "when --install flag is specified", :bundler => "< 3" do it "removes gems from .bundle" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -40,19 +40,22 @@ RSpec.describe "bundle remove" do bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(the_bundle).to_not include_gems "rack" + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end context "when gem is specified in multiple lines" do it "shows success for removed gem" do + build_git "rack" + gemfile <<-G source '#{file_uri_for(gem_repo1)}' gem 'git' gem 'rack', - git: 'https://github.com/rack/rack', + git: "#{lib_path("rack-1.0")}", branch: 'master' gem 'nokogiri' G @@ -60,7 +63,7 @@ RSpec.describe "bundle remove" do bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source '#{file_uri_for(gem_repo1)}' gem 'git' @@ -97,7 +100,7 @@ RSpec.describe "bundle remove" do expect(out).to include("rack was removed.") expect(out).to include("rails was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -116,7 +119,7 @@ RSpec.describe "bundle remove" do bundle "remove rails rack minitest", :raise_on_error => false expect(err).to include("`rack` is not specified in #{bundled_app_gemfile} so it could not be removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" gem "rails" @@ -138,7 +141,7 @@ RSpec.describe "bundle remove" do bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -158,7 +161,7 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -178,7 +181,7 @@ RSpec.describe "bundle remove" do bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" group :test do @@ -204,7 +207,7 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -223,13 +226,13 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end end - context "when the gem is present in mutiple groups" do + context "when the gem is present in multiple groups" do it "removes all empty blocks" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -246,7 +249,7 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -269,7 +272,7 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -292,7 +295,7 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" group :test do @@ -319,7 +322,7 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" group :test do @@ -333,7 +336,7 @@ RSpec.describe "bundle remove" do end describe "arbitrary gemfile" do - context "when mutiple gems are present in same line" do + context "when multiple gems are present in same line" do it "shows warning for gems not removed" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -343,7 +346,7 @@ RSpec.describe "bundle remove" do bundle "remove rails", :raise_on_error => false expect(err).to include("Gems could not be removed. rack (>= 0) would also have been removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" gem "rack"; gem "rails" G @@ -365,7 +368,7 @@ RSpec.describe "bundle remove" do expect(out).to include("rails was removed.") expect(out).to include("minitest was removed.") expect(out).to include("rack, rspec could not be removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" gem"rack" gem"rspec" @@ -397,7 +400,7 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -481,7 +484,7 @@ RSpec.describe "bundle remove" do expect(out).to include("rack was removed.") expect(err).to include("`rack` is not specified in #{bundled_app("Gemfile-other")} so it could not be removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" eval_gemfile "Gemfile-other" @@ -506,7 +509,7 @@ RSpec.describe "bundle remove" do expect(out).to include("rack was removed.") expect(err).to include("Gems could not be removed. rails (>= 0) would also have been removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" eval_gemfile "Gemfile-other" @@ -531,7 +534,7 @@ RSpec.describe "bundle remove" do expect(err).to include("Gems could not be removed. rails (>= 0) would also have been removed.") expect(bundled_app("Gemfile-other").read).to include("gem \"rack\"") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" eval_gemfile "Gemfile-other" @@ -574,7 +577,7 @@ RSpec.describe "bundle remove" do bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -593,7 +596,7 @@ RSpec.describe "bundle remove" do bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -630,7 +633,7 @@ RSpec.describe "bundle remove" do bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" # gem "rack" might be used in the future @@ -649,7 +652,7 @@ RSpec.describe "bundle remove" do bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -668,7 +671,7 @@ RSpec.describe "bundle remove" do expect(out).to_not include("puma was removed.") expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" gem "puma" # implements interface provided by gem "rack" G @@ -688,7 +691,7 @@ RSpec.describe "bundle remove" do expect(out).to include("puma was removed.") expect(out).to_not include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" gem "rack" diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb index 521c175711..403a48a508 100644 --- a/spec/bundler/commands/update_spec.rb +++ b/spec/bundler/commands/update_spec.rb @@ -1,18 +1,18 @@ # frozen_string_literal: true RSpec.describe "bundle update" do - before :each do - build_repo2 + describe "with no arguments" do + before do + build_repo2 - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "activesupport" - gem "rack-obama" - gem "platform_specific" - G - end + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "activesupport" + gem "rack-obama" + gem "platform_specific" + G + end - describe "with no arguments", :bundler => "< 3" do it "updates the entire bundle" do update_repo2 do build_gem "rack", "1.2" do |s| @@ -39,7 +39,18 @@ RSpec.describe "bundle update" do end end - describe "with --all", :bundler => "3" do + describe "with --all" do + before do + build_repo2 + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "activesupport" + gem "rack-obama" + gem "platform_specific" + G + end + it "updates the entire bundle" do update_repo2 do build_gem "rack", "1.2" do |s| @@ -55,6 +66,8 @@ RSpec.describe "bundle update" do end it "doesn't delete the Gemfile.lock file if something goes wrong" do + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" + gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "activesupport" @@ -83,25 +96,36 @@ RSpec.describe "bundle update" do before { bundle "config set update_requires_all_flag true" } it "errors when passed nothing" do - install_gemfile "" + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" bundle :update, :raise_on_error => false expect(err).to eq("To update everything, pass the `--all` flag.") end it "errors when passed --all and another option" do - install_gemfile "" + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" bundle "update --all foo", :raise_on_error => false expect(err).to eq("Cannot specify --all along with specific options.") end it "updates everything when passed --all" do - install_gemfile "" + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" bundle "update --all" expect(out).to include("Bundle updated!") end end describe "--quiet argument" do + before do + build_repo2 + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "activesupport" + gem "rack-obama" + gem "platform_specific" + G + end + it "hides UI messages" do bundle "update --quiet" expect(out).not_to include("Bundle updated!") @@ -109,6 +133,17 @@ RSpec.describe "bundle update" do end describe "with a top level dependency" do + before do + build_repo2 + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "activesupport" + gem "rack-obama" + gem "platform_specific" + G + end + it "unlocks all child dependencies that are unrelated to other locked dependencies" do update_repo2 do build_gem "rack", "1.2" do |s| @@ -124,6 +159,17 @@ RSpec.describe "bundle update" do end describe "with an unknown dependency" do + before do + build_repo2 + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "activesupport" + gem "rack-obama" + gem "platform_specific" + G + end + it "should inform the user" do bundle "update halting-problem-solver", :raise_on_error => false expect(err).to include "Could not find gem 'halting-problem-solver'" @@ -135,6 +181,17 @@ RSpec.describe "bundle update" do end describe "with a child dependency" do + before do + build_repo2 + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "activesupport" + gem "rack-obama" + gem "platform_specific" + G + end + it "should update the child dependency" do update_repo2 do build_gem "rack", "1.2" do |s| @@ -148,76 +205,230 @@ RSpec.describe "bundle update" do end describe "when a possible resolve requires an older version of a locked gem" do - context "and only_update_to_newer_versions is set" do - before do - bundle "config set only_update_to_newer_versions true" + it "does not go to an older version" do + build_repo4 do + build_gem "tilt", "2.0.8" + build_gem "slim", "3.0.9" do |s| + s.add_dependency "tilt", [">= 1.3.3", "< 2.1"] + end + build_gem "slim_lint", "0.16.1" do |s| + s.add_dependency "slim", [">= 3.0", "< 5.0"] + end + build_gem "slim-rails", "0.2.1" do |s| + s.add_dependency "slim", ">= 0.9.2" + end + build_gem "slim-rails", "3.1.3" do |s| + s.add_dependency "slim", "~> 3.0" + end end - it "does not go to an older version" do - build_repo4 do - build_gem "tilt", "2.0.8" - build_gem "slim", "3.0.9" do |s| - s.add_dependency "tilt", [">= 1.3.3", "< 2.1"] - end - build_gem "slim_lint", "0.16.1" do |s| - s.add_dependency "slim", [">= 3.0", "< 5.0"] - end - build_gem "slim-rails", "0.2.1" do |s| - s.add_dependency "slim", ">= 0.9.2" - end - build_gem "slim-rails", "3.1.3" do |s| - s.add_dependency "slim", "~> 3.0" - end + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "slim-rails" + gem "slim_lint" + G + + expect(the_bundle).to include_gems("slim 3.0.9", "slim-rails 3.1.3", "slim_lint 0.16.1") + + update_repo4 do + build_gem "slim", "4.0.0" do |s| + s.add_dependency "tilt", [">= 2.0.6", "< 2.1"] end + end - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "slim-rails" - gem "slim_lint" - G + bundle "update", :all => true - expect(the_bundle).to include_gems("slim 3.0.9", "slim-rails 3.1.3", "slim_lint 0.16.1") + expect(the_bundle).to include_gems("slim 3.0.9", "slim-rails 3.1.3", "slim_lint 0.16.1") + end - update_repo4 do - build_gem "slim", "4.0.0" do |s| - s.add_dependency "tilt", [">= 2.0.6", "< 2.1"] - end + it "does not go to an older version, even if the version upgrade that could cause another gem to downgrade is activated first" do + build_repo4 do + # countries is processed before country_select by the resolver due to having less spec groups (groups of versions with the same dependencies) (2 vs 3) + + build_gem "countries", "2.1.4" + build_gem "countries", "3.1.0" + + build_gem "countries", "4.0.0" do |s| + s.add_dependency "sixarm_ruby_unaccent", "~> 1.1" + end + + build_gem "country_select", "1.2.0" + + build_gem "country_select", "2.1.4" do |s| + s.add_dependency "countries", "~> 2.0" + end + build_gem "country_select", "3.1.1" do |s| + s.add_dependency "countries", "~> 2.0" end - bundle "update", :all => true + build_gem "country_select", "5.1.0" do |s| + s.add_dependency "countries", "~> 3.0" + end - expect(the_bundle).to include_gems("slim 3.0.9", "slim-rails 3.1.3", "slim_lint 0.16.1") + build_gem "sixarm_ruby_unaccent", "1.1.0" end - it "should still downgrade if forced by the Gemfile" do - build_repo4 do - build_gem "a" - build_gem "b", "1.0" - build_gem "b", "2.0" + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "country_select" + gem "countries" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + countries (3.1.0) + country_select (5.1.0) + countries (~> 3.0) + + PLATFORMS + #{specific_local_platform} + + DEPENDENCIES + countries + country_select + + BUNDLED WITH + #{Bundler::VERSION} + L + + previous_lockfile = lockfile + + bundle "lock --update" + + expect(lockfile).to eq(previous_lockfile) + end + + it "does not downgrade indirect dependencies unnecessarily" do + build_repo4 do + build_gem "a" do |s| + s.add_dependency "b" + s.add_dependency "c" end + build_gem "b" + build_gem "c" + build_gem "c", "2.0" + end - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "a" - gem "b" - G + install_gemfile <<-G, :verbose => true + source "#{file_uri_for(gem_repo4)}" + gem "a" + G - expect(the_bundle).to include_gems("a 1.0", "b 2.0") + expect(the_bundle).to include_gems("a 1.0", "b 1.0", "c 2.0") - gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "a" - gem "b", "1.0" - G + update_repo4 do + build_gem "b", "2.0" do |s| + s.add_dependency "c", "< 2" + end + end - bundle "update b" + bundle "update", :all => true, :verbose => true + expect(the_bundle).to include_gems("a 1.0", "b 1.0", "c 2.0") + end - expect(the_bundle).to include_gems("a 1.0", "b 1.0") + it "should still downgrade if forced by the Gemfile" do + build_repo4 do + build_gem "a" + build_gem "b", "1.0" + build_gem "b", "2.0" end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "a" + gem "b" + G + + expect(the_bundle).to include_gems("a 1.0", "b 2.0") + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "a" + gem "b", "1.0" + G + + bundle "update b" + + expect(the_bundle).to include_gems("a 1.0", "b 1.0") + end + + it "should still downgrade if forced by the Gemfile, when transitive dependencies also need downgrade" do + build_repo4 do + build_gem "activesupport", "6.1.4.1" do |s| + s.add_dependency "tzinfo", "~> 2.0" + end + + build_gem "activesupport", "6.0.4.1" do |s| + s.add_dependency "tzinfo", "~> 1.1" + end + + build_gem "tzinfo", "2.0.4" + build_gem "tzinfo", "1.2.9" + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "activesupport", "~> 6.1.0" + G + + expect(the_bundle).to include_gems("activesupport 6.1.4.1", "tzinfo 2.0.4") + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "activesupport", "~> 6.0.0" + G + + original_lockfile = lockfile + + expected_lockfile = <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + activesupport (6.0.4.1) + tzinfo (~> 1.1) + tzinfo (1.2.9) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + activesupport (~> 6.0.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update activesupport" + expect(the_bundle).to include_gems("activesupport 6.0.4.1", "tzinfo 1.2.9") + expect(lockfile).to eq(expected_lockfile) + + lockfile original_lockfile + bundle "update" + expect(the_bundle).to include_gems("activesupport 6.0.4.1", "tzinfo 1.2.9") + expect(lockfile).to eq(expected_lockfile) + + lockfile original_lockfile + bundle "lock --update" + expect(the_bundle).to include_gems("activesupport 6.0.4.1", "tzinfo 1.2.9") + expect(lockfile).to eq(expected_lockfile) end end describe "with --local option" do + before do + build_repo2 + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "activesupport" + gem "rack-obama" + gem "platform_specific" + G + end + it "doesn't hit repo2" do FileUtils.rm_rf(gem_repo2) @@ -227,6 +438,10 @@ RSpec.describe "bundle update" do end describe "with --group option" do + before do + build_repo2 + end + it "should update only specified group gems" do install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" @@ -263,7 +478,7 @@ RSpec.describe "bundle update" do end context "when there is a source with the same name as a gem in a group" do - before :each do + before do build_git "foo", :path => lib_path("activesupport") install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" @@ -305,6 +520,17 @@ RSpec.describe "bundle update" do end describe "in a frozen bundle" do + before do + build_repo2 + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "activesupport" + gem "rack-obama" + gem "platform_specific" + G + end + it "should fail loudly", :bundler => "< 3" do bundle "install --deployment" bundle "update", :all => true, :raise_on_error => false @@ -330,7 +556,11 @@ RSpec.describe "bundle update" do end describe "with --source option" do - it "should not update gems not included in the source that happen to have the same name", :bundler => "< 3" do + before do + build_repo2 + end + + it "should not update gems not included in the source that happen to have the same name" do install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "activesupport" @@ -338,10 +568,10 @@ RSpec.describe "bundle update" do update_repo2 { build_gem "activesupport", "3.0" } bundle "update --source activesupport" - expect(the_bundle).to include_gem "activesupport 3.0" + expect(the_bundle).not_to include_gem "activesupport 3.0" end - it "should not update gems not included in the source that happen to have the same name", :bundler => "3" do + it "should not update gems not included in the source that happen to have the same name" do install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "activesupport" @@ -349,22 +579,7 @@ RSpec.describe "bundle update" do update_repo2 { build_gem "activesupport", "3.0" } bundle "update --source activesupport" - expect(the_bundle).not_to include_gem "activesupport 3.0" - end - - context "with unlock_source_unlocks_spec set to false" do - before { bundle "config set unlock_source_unlocks_spec false" } - - it "should not update gems not included in the source that happen to have the same name" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "activesupport" - G - update_repo2 { build_gem "activesupport", "3.0" } - - bundle "update --source activesupport" - expect(the_bundle).not_to include_gems "activesupport 3.0" - end + expect(the_bundle).not_to include_gems "activesupport 3.0" end end @@ -384,20 +599,7 @@ RSpec.describe "bundle update" do G end - it "should not update the child dependencies of a gem that has the same name as the source", :bundler => "< 3" do - update_repo2 do - build_gem "fred", "2.0" - build_gem "harry", "2.0" do |s| - s.add_dependency "fred" - end - end - - bundle "update --source harry" - expect(the_bundle).to include_gems "harry 2.0" - expect(the_bundle).to include_gems "fred 1.0" - end - - it "should not update the child dependencies of a gem that has the same name as the source", :bundler => "3" do + it "should not update the child dependencies of a gem that has the same name as the source" do update_repo2 do build_gem "fred", "2.0" build_gem "harry", "2.0" do |s| @@ -429,7 +631,7 @@ RSpec.describe "bundle update" do G end - it "should not update the child dependencies of a gem that has the same name as the source", :bundler => "< 3" do + it "should not update the child dependencies of a gem that has the same name as the source" do update_repo2 do build_gem "george", "2.0" build_gem "harry", "2.0" do |s| @@ -438,27 +640,133 @@ RSpec.describe "bundle update" do end bundle "update --source harry" - expect(the_bundle).to include_gems "harry 2.0" - expect(the_bundle).to include_gems "fred 1.0" - expect(the_bundle).to include_gems "george 1.0" + expect(the_bundle).to include_gems "harry 1.0", "fred 1.0", "george 1.0" end + end - it "should not update the child dependencies of a gem that has the same name as the source", :bundler => "3" do - update_repo2 do - build_gem "george", "2.0" - build_gem "harry", "2.0" do |s| - s.add_dependency "george" + it "shows the previous version of the gem when updated from rubygems source", :bundler => "< 3" do + build_repo2 + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "activesupport" + G + + bundle "update", :all => true + expect(out).to include("Using activesupport 2.3.5") + + update_repo2 do + build_gem "activesupport", "3.0" + end + + bundle "update", :all => true + expect(out).to include("Installing activesupport 3.0 (was 2.3.5)") + end + + context "with suppress_install_using_messages set" do + before { bundle "config set suppress_install_using_messages true" } + + it "only prints `Using` for versions that have changed" do + build_repo4 do + build_gem "bar" + build_gem "foo" + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "bar" + gem "foo" + G + + bundle "update", :all => true + expect(out).to match(/Resolving dependencies\.\.\.\.*\nBundle updated!/) + + update_repo4 do + build_gem "foo", "2.0" + end + + bundle "update", :all => true + out.sub!("Removing foo (1.0)\n", "") + expect(out).to match(/Resolving dependencies\.\.\.\.*\nFetching foo 2\.0 \(was 1\.0\)\nInstalling foo 2\.0 \(was 1\.0\)\nBundle updated/) + end + end + + it "shows error message when Gemfile.lock is not preset and gem is specified" do + gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "activesupport" + G + + bundle "update nonexisting", :raise_on_error => false + expect(err).to include("This Bundle hasn't been installed yet. Run `bundle install` to update and install the bundled gems.") + expect(exitstatus).to eq(22) + end + + context "with multiple, duplicated sources, with lockfile in old format", :bundler => "< 3" do + before do + build_repo2 do + build_gem "dotenv", "2.7.6" + + build_gem "oj", "3.11.3" + build_gem "oj", "3.11.5" + + build_gem "vcr", "6.0.0" + end + + build_repo gem_repo3 do + build_gem "pkg-gem-flowbyte-with-dep", "1.0.0" do |s| + s.add_dependency "oj" end end - bundle "update --source harry" - expect(the_bundle).to include_gems "harry 1.0", "fred 1.0", "george 1.0" + gemfile <<~G + source "https://gem.repo2" + + gem "dotenv" + + source "https://gem.repo3" do + gem 'pkg-gem-flowbyte-with-dep' + end + + gem "vcr",source: "https://gem.repo2" + G + + lockfile <<~L + GEM + remote: https://gem.repo2/ + remote: https://gem.repo3/ + specs: + dotenv (2.7.6) + oj (3.11.3) + pkg-gem-flowbyte-with-dep (1.0.0) + oj + vcr (6.0.0) + + PLATFORMS + #{specific_local_platform} + + DEPENDENCIES + dotenv + pkg-gem-flowbyte-with-dep! + vcr! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "works" do + bundle :install, :artifice => "compact_index" + bundle "update oj", :artifice => "compact_index" + + expect(out).to include("Bundle updated!") + expect(the_bundle).to include_gems "oj 3.11.5" end end end RSpec.describe "bundle update in more complicated situations" do - before :each do + before do build_repo2 end @@ -506,6 +814,7 @@ RSpec.describe "bundle update in more complicated situations" do build_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => '#{lib_path("foo-1.0")}' G @@ -522,6 +831,7 @@ RSpec.describe "bundle update in more complicated situations" do build_git "rack" install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" gem "rack", :git => '#{lib_path("rack-1.0")}' G @@ -646,7 +956,7 @@ RSpec.describe "bundle update without a Gemfile.lock" do end RSpec.describe "bundle update when a gem depends on a newer version of bundler" do - before(:each) do + before do build_repo2 do build_gem "rails", "3.0.1" do |s| s.add_dependency "bundler", Bundler::VERSION.succ @@ -669,72 +979,13 @@ RSpec.describe "bundle update when a gem depends on a newer version of bundler" end end -RSpec.describe "bundle update" do - it "shows the previous version of the gem when updated from rubygems source", :bundler => "< 3" do - build_repo2 - - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "activesupport" - G - - bundle "update", :all => true - expect(out).to include("Using activesupport 2.3.5") - - update_repo2 do - build_gem "activesupport", "3.0" - end - - bundle "update", :all => true - expect(out).to include("Installing activesupport 3.0 (was 2.3.5)") - end - - context "with suppress_install_using_messages set" do - before { bundle "config set suppress_install_using_messages true" } - - it "only prints `Using` for versions that have changed" do - build_repo4 do - build_gem "bar" - build_gem "foo" - end - - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "bar" - gem "foo" - G - - bundle "update", :all => true - expect(out).to match(/Resolving dependencies\.\.\.\.*\nBundle updated!/) - - update_repo4 do - build_gem "foo", "2.0" - end - - bundle "update", :all => true - out.sub!("Removing foo (1.0)\n", "") - expect(out).to match(/Resolving dependencies\.\.\.\.*\nFetching foo 2\.0 \(was 1\.0\)\nInstalling foo 2\.0 \(was 1\.0\)\nBundle updated/) - end - end - - it "shows error message when Gemfile.lock is not preset and gem is specified" do - gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "activesupport" - G - - bundle "update nonexisting", :raise_on_error => false - expect(err).to include("This Bundle hasn't been installed yet. Run `bundle install` to update and install the bundled gems.") - expect(exitstatus).to eq(22) - end -end - RSpec.describe "bundle update --ruby" do before do install_gemfile <<-G ::RUBY_VERSION = '2.1.3' ::RUBY_PATCHLEVEL = 100 ruby '~> 2.1.0' + source "#{file_uri_for(gem_repo1)}" G end @@ -743,13 +994,15 @@ RSpec.describe "bundle update --ruby" do gemfile <<-G ::RUBY_VERSION = '2.1.4' ::RUBY_PATCHLEVEL = 222 + source "#{file_uri_for(gem_repo1)}" G end it "removes the Ruby from the Gemfile.lock" do bundle "update --ruby" - lockfile_should_be <<-L + expect(lockfile).to eq <<~L GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -769,13 +1022,15 @@ RSpec.describe "bundle update --ruby" do ::RUBY_VERSION = '2.1.4' ::RUBY_PATCHLEVEL = 222 ruby '~> 2.1.0' + source "#{file_uri_for(gem_repo1)}" G end it "updates the Gemfile.lock with the latest version" do bundle "update --ruby" - lockfile_should_be <<-L + expect(lockfile).to eq <<~L GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -798,6 +1053,7 @@ RSpec.describe "bundle update --ruby" do ::RUBY_VERSION = '2.2.2' ::RUBY_PATCHLEVEL = 505 ruby '~> 2.1.0' + source "#{file_uri_for(gem_repo1)}" G end it "shows a helpful error message" do @@ -813,13 +1069,15 @@ RSpec.describe "bundle update --ruby" do ::RUBY_VERSION = '1.8.3' ::RUBY_PATCHLEVEL = 55 ruby '~> 1.8.0' + source "#{file_uri_for(gem_repo1)}" G end it "updates the Gemfile.lock with the latest version" do bundle "update --ruby" - lockfile_should_be <<-L + expect(lockfile).to eq <<~L GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -965,9 +1223,9 @@ RSpec.describe "bundle update conservative" do gem 'shared_owner_b' G - lockfile <<-L + lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)} + remote: #{file_uri_for(gem_repo4)}/ specs: isolated_dep (2.0.1) isolated_owner (1.0.1) @@ -979,12 +1237,12 @@ RSpec.describe "bundle update conservative" do shared_dep (~> 5.0) PLATFORMS - ruby + #{specific_local_platform} DEPENDENCIES + isolated_owner shared_owner_a shared_owner_b - isolated_owner BUNDLED WITH #{Bundler::VERSION} @@ -1006,7 +1264,42 @@ RSpec.describe "bundle update conservative" do it "should not eagerly unlock with --conservative" do bundle "update --conservative shared_owner_a isolated_owner" - expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1" + expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.1", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1" + end + + it "should only update direct dependencies when fully updating with --conservative" do + bundle "update --conservative" + + expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.1", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.2" + end + + it "should only change direct dependencies when updating the lockfile with --conservative" do + bundle "lock --update --conservative" + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + isolated_dep (2.0.1) + isolated_owner (1.0.2) + isolated_dep (~> 2.0) + shared_dep (5.0.1) + shared_owner_a (3.0.2) + shared_dep (~> 5.0) + shared_owner_b (4.0.2) + shared_dep (~> 5.0) + + PLATFORMS + #{specific_local_platform} + + DEPENDENCIES + isolated_owner + shared_owner_a + shared_owner_b + + BUNDLED WITH + #{Bundler::VERSION} + L end it "should match bundle install conservative update behavior when not eagerly unlocking" do @@ -1026,7 +1319,7 @@ RSpec.describe "bundle update conservative" do context "error handling" do before do - gemfile "" + gemfile "source \"#{file_uri_for(gem_repo1)}\"" end it "raises if too many flags are provided" do diff --git a/spec/bundler/install/allow_offline_install_spec.rb b/spec/bundler/install/allow_offline_install_spec.rb index d0aa4e4d9e..524363fde5 100644 --- a/spec/bundler/install/allow_offline_install_spec.rb +++ b/spec/bundler/install/allow_offline_install_spec.rb @@ -75,6 +75,7 @@ RSpec.describe "bundle install with :allow_offline_install" do git = build_git "a", "1.0.0", :path => lib_path("a") update_git("a", :path => git.path, :branch => "new_branch") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "a", :git => #{git.path.to_s.dump} G @@ -84,6 +85,7 @@ RSpec.describe "bundle install with :allow_offline_install" do break_git_remote_ops! do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "a", :git => #{git.path.to_s.dump}, :branch => "new_branch" G end diff --git a/spec/bundler/install/bundler_spec.rb b/spec/bundler/install/bundler_spec.rb index 9445be66f1..963ce82db8 100644 --- a/spec/bundler/install/bundler_spec.rb +++ b/spec/bundler/install/bundler_spec.rb @@ -21,12 +21,12 @@ RSpec.describe "bundle install" do expect(the_bundle).to include_gems "bundler #{Bundler::VERSION}" end - it "are not added if not already present" do + it "are forced to the current bundler version even if not already present" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" G - expect(the_bundle).not_to include_gems "bundler #{Bundler::VERSION}" + expect(the_bundle).to include_gems "bundler #{Bundler::VERSION}" end it "causes a conflict if explicitly requesting a different version of bundler" do diff --git a/spec/bundler/install/deploy_spec.rb b/spec/bundler/install/deploy_spec.rb index 73a0c18f4a..54fc6371cb 100644 --- a/spec/bundler/install/deploy_spec.rb +++ b/spec/bundler/install/deploy_spec.rb @@ -44,8 +44,8 @@ RSpec.describe "install in deployment or frozen mode" do it "still works if you are not in the app directory and specify --gemfile" do bundle "install" simulate_new_machine - bundle "config --local deployment true" - bundle "config --local path vendor/bundle" + bundle "config set --local deployment true" + bundle "config set --local path vendor/bundle" bundle "install --gemfile #{tmp}/bundled_app/Gemfile", :dir => tmp expect(the_bundle).to include_gems "rack 1.0" end @@ -53,13 +53,14 @@ RSpec.describe "install in deployment or frozen mode" do it "works if you exclude a group with a git gem" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" group :test do gem "foo", :git => "#{lib_path("foo-1.0")}" end G bundle :install - bundle "config --local deployment true" - bundle "config --local without test" + bundle "config set --local deployment true" + bundle "config set --local without test" bundle :install end @@ -67,7 +68,7 @@ RSpec.describe "install in deployment or frozen mode" do skip "doesn't find bundle" if Gem.win_platform? bundle :install - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install bundle "exec bundle check", :env => { "PATH" => path } end @@ -76,12 +77,26 @@ RSpec.describe "install in deployment or frozen mode" do build_lib "foo", :path => lib_path("nested/foo") build_lib "bar", :path => lib_path("nested/bar") gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", "1.0", :path => "#{lib_path("nested")}" gem "bar", :path => "#{lib_path("nested")}" G bundle :install - bundle "config --local deployment true" + bundle "config set --local deployment true" + bundle :install + end + + it "works when path gems are specified twice" do + build_lib "foo", :path => lib_path("nested/foo") + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "foo", :path => "#{lib_path("nested/foo")}" + gem "foo", :path => "#{lib_path("nested/foo")}" + G + + bundle :install + bundle "config set --local deployment true" bundle :install end @@ -92,18 +107,19 @@ RSpec.describe "install in deployment or frozen mode" do gem "rack-obama", ">= 1.0" G - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install, :artifice => "endpoint_strict_basic_authentication" end it "works with sources given by a block" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" source "#{file_uri_for(gem_repo1)}" do gem "rack" end G - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install expect(the_bundle).to include_gems "rack 1.0" @@ -111,24 +127,24 @@ RSpec.describe "install in deployment or frozen mode" do context "when replacing a host with the same host with credentials" do before do - bundle "config --local path vendor/bundle" + bundle "config set --local path vendor/bundle" bundle "install" gemfile <<-G - source "http://user_name:password@localgemserver.test/" - gem "rack" + source "http://user_name:password@localgemserver.test/" + gem "rack" G lockfile <<-G - GEM - remote: http://localgemserver.test/ - specs: - rack (1.0.0) + GEM + remote: http://localgemserver.test/ + specs: + rack (1.0.0) - PLATFORMS - #{local} + PLATFORMS + #{local} - DEPENDENCIES - rack + DEPENDENCIES + rack G bundle "config set --local deployment true" @@ -215,7 +231,7 @@ RSpec.describe "install in deployment or frozen mode" do gem "rack-obama" G - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install, :raise_on_error => false expect(err).to include("deployment mode") expect(err).to include("You have added to the Gemfile") @@ -234,9 +250,9 @@ RSpec.describe "install in deployment or frozen mode" do expect(the_bundle).to include_gems "path_gem 1.0" FileUtils.rm_r lib_path("path_gem-1.0") - bundle "config --local path .bundle" - bundle "config --local without development" - bundle "config --local deployment true" + bundle "config set --local path .bundle" + bundle "config set --local without development" + bundle "config set --local deployment true" bundle :install, :env => { "DEBUG" => "1" } run "puts :WIN" expect(out).to eq("WIN") @@ -252,8 +268,8 @@ RSpec.describe "install in deployment or frozen mode" do expect(the_bundle).to include_gems "path_gem 1.0" FileUtils.rm_r lib_path("path_gem-1.0") - bundle "config --local path .bundle" - bundle "config --local deployment true" + bundle "config set --local path .bundle" + bundle "config set --local deployment true" bundle :install, :raise_on_error => false expect(err).to include("The path `#{lib_path("path_gem-1.0")}` does not exist.") end @@ -324,7 +340,7 @@ RSpec.describe "install in deployment or frozen mode" do gem "activesupport" G - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install, :raise_on_error => false expect(err).to include("deployment mode") expect(err).to include("You have added to the Gemfile:\n* activesupport\n\n") @@ -338,14 +354,14 @@ RSpec.describe "install in deployment or frozen mode" do gem "rack", :git => "git://hubz.com" G - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install, :raise_on_error => false expect(err).to include("deployment mode") - expect(err).to include("You have added to the Gemfile:\n* source: git://hubz.com") - expect(err).not_to include("You have changed in the Gemfile") + expect(err).not_to include("You have added to the Gemfile") + expect(err).to include("You have changed in the Gemfile:\n* rack from `no specified source` to `git://hubz.com`") end - it "explodes if you unpin a source" do + it "explodes if you change a source" do build_git "rack" install_gemfile <<-G @@ -358,15 +374,15 @@ RSpec.describe "install in deployment or frozen mode" do gem "rack" G - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install, :raise_on_error => false expect(err).to include("deployment mode") - expect(err).to include("You have deleted from the Gemfile:\n* source: #{lib_path("rack-1.0")}") + expect(err).not_to include("You have deleted from the Gemfile") expect(err).not_to include("You have added to the Gemfile") - expect(err).not_to include("You have changed in the Gemfile") + expect(err).to include("You have changed in the Gemfile:\n* rack from `#{lib_path("rack-1.0")}` to `no specified source`") end - it "explodes if you unpin a source, leaving it pinned somewhere else" do + it "explodes if you change a source" do build_lib "foo", :path => lib_path("rack/foo") build_git "rack", :path => lib_path("rack") @@ -382,10 +398,10 @@ RSpec.describe "install in deployment or frozen mode" do gem "foo", :git => "#{lib_path("rack")}" G - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install, :raise_on_error => false expect(err).to include("deployment mode") - expect(err).to include("You have changed in the Gemfile:\n* rack from `no specified source` to `#{lib_path("rack")}`") + expect(err).to include("You have changed in the Gemfile:\n* rack from `#{lib_path("rack")}` to `no specified source`") expect(err).not_to include("You have added to the Gemfile") expect(err).not_to include("You have deleted from the Gemfile") end @@ -401,7 +417,7 @@ RSpec.describe "install in deployment or frozen mode" do gem "rack-obama" G - expect(the_bundle).not_to include_gems "rack 1.0.0" + run "require 'rack'", :raise_on_error => false expect(err).to include strip_whitespace(<<-E).strip The dependencies in your gemfile changed @@ -419,6 +435,7 @@ You have deleted from the Gemfile: it "works fine after bundle package and bundle install --local" do build_lib "foo", :path => lib_path("foo") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo")}" G diff --git a/spec/bundler/install/failure_spec.rb b/spec/bundler/install/failure_spec.rb index d265fafa63..4a9c33754f 100644 --- a/spec/bundler/install/failure_spec.rb +++ b/spec/bundler/install/failure_spec.rb @@ -2,7 +2,7 @@ RSpec.describe "bundle install" do context "installing a gem fails" do - it "prints out why that gem was being installed" do + it "prints out why that gem was being installed and the underlying error" do build_repo2 do build_gem "activesupport", "2.3.2" do |s| s.extensions << "Rakefile" @@ -18,102 +18,9 @@ RSpec.describe "bundle install" do source "#{file_uri_for(gem_repo2)}" gem "rails" G + expect(err).to start_with("Gem::Ext::BuildError: ERROR: Failed to build gem native extension.") expect(err).to end_with(<<-M.strip) An error occurred while installing activesupport (2.3.2), and Bundler cannot continue. -Make sure that `gem install activesupport -v '2.3.2' --source '#{file_uri_for(gem_repo2)}/'` succeeds before bundling. - -In Gemfile: - rails was resolved to 2.3.2, which depends on - actionmailer was resolved to 2.3.2, which depends on - activesupport - M - end - - context "when installing a git gem" do - it "does not tell the user to run 'gem install'" do - build_git "activesupport", "2.3.2", :path => lib_path("activesupport") do |s| - s.extensions << "Rakefile" - s.write "Rakefile", <<-RUBY - task :default do - abort "make installing activesupport-2.3.2 fail" - end - RUBY - end - - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" - gem "rails" - gem "activesupport", :git => "#{lib_path("activesupport")}" - G - - expect(err).to end_with(<<-M.strip) -An error occurred while installing activesupport (2.3.2), and Bundler cannot continue. - -In Gemfile: - rails was resolved to 2.3.2, which depends on - actionmailer was resolved to 2.3.2, which depends on - activesupport - M - end - end - - context "when installing a gem using a git block" do - it "does not tell the user to run 'gem install'" do - build_git "activesupport", "2.3.2", :path => lib_path("activesupport") do |s| - s.extensions << "Rakefile" - s.write "Rakefile", <<-RUBY - task :default do - abort "make installing activesupport-2.3.2 fail" - end - RUBY - end - - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" - gem "rails" - - git "#{lib_path("activesupport")}" do - gem "activesupport" - end - G - - expect(err).to end_with(<<-M.strip) -An error occurred while installing activesupport (2.3.2), and Bundler cannot continue. - - -In Gemfile: - rails was resolved to 2.3.2, which depends on - actionmailer was resolved to 2.3.2, which depends on - activesupport - M - end - end - - it "prints out the hint for the remote source when available" do - build_repo2 do - build_gem "activesupport", "2.3.2" do |s| - s.extensions << "Rakefile" - s.write "Rakefile", <<-RUBY - task :default do - abort "make installing activesupport-2.3.2 fail" - end - RUBY - end - end - - build_repo4 do - build_gem "a" - end - - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo4)}" - source "#{file_uri_for(gem_repo2)}" do - gem "rails" - end - G - expect(err).to end_with(<<-M.strip) -An error occurred while installing activesupport (2.3.2), and Bundler cannot continue. -Make sure that `gem install activesupport -v '2.3.2' --source '#{file_uri_for(gem_repo2)}/'` succeeds before bundling. In Gemfile: rails was resolved to 2.3.2, which depends on diff --git a/spec/bundler/install/gemfile/eval_gemfile_spec.rb b/spec/bundler/install/gemfile/eval_gemfile_spec.rb index c42ae7ef79..02283291b4 100644 --- a/spec/bundler/install/gemfile/eval_gemfile_spec.rb +++ b/spec/bundler/install/gemfile/eval_gemfile_spec.rb @@ -11,12 +11,14 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do context "eval-ed Gemfile points to an internal gemspec" do before do create_file "Gemfile-other", <<-G + source "#{file_uri_for(gem_repo1)}" gemspec :path => 'gems/gunks' G end it "installs the gemspec specified gem" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" eval_gemfile 'Gemfile-other' G expect(out).to include("Resolving dependencies") @@ -26,14 +28,50 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do end end + context "eval-ed Gemfile points to an internal gemspec and uses a scoped source that duplicates the main Gemfile global source" do + before do + build_repo2 do + build_gem "rails", "6.1.3.2" + + build_gem "zip-zip", "0.3" + end + + create_file bundled_app("gems/Gemfile"), <<-G + source "#{file_uri_for(gem_repo2)}" + + gemspec :path => "\#{__dir__}/gunks" + + source "#{file_uri_for(gem_repo2)}" do + gem "zip-zip" + end + G + end + + it "installs and finds gems correctly" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + + gem "rails" + + eval_gemfile File.join(__dir__, "gems/Gemfile") + G + expect(out).to include("Resolving dependencies") + expect(out).to include("Bundle complete") + + expect(the_bundle).to include_gem "rails 6.1.3.2" + end + end + context "eval-ed Gemfile has relative-path gems" do before do build_lib("a", :path => bundled_app("gems/a")) create_file bundled_app("nested/Gemfile-nested"), <<-G + source "#{file_uri_for(gem_repo1)}" gem "a", :path => "../gems/a" G gemfile <<-G + source "#{file_uri_for(gem_repo1)}" eval_gemfile "nested/Gemfile-nested" G end @@ -47,7 +85,7 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do # parsed lockfile and the evaluated gemfile. it "bundles with deployment mode configured" do bundle :install - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install end end @@ -57,6 +95,7 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do it "installs the gemspec specified gem" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" eval_gemfile 'other/Gemfile-other' gemspec :path => 'gems/gunks' G diff --git a/spec/bundler/install/gemfile/gemspec_spec.rb b/spec/bundler/install/gemfile/gemspec_spec.rb index 7a95a8abde..6d9cd2daff 100644 --- a/spec/bundler/install/gemfile/gemspec_spec.rb +++ b/spec/bundler/install/gemfile/gemspec_spec.rb @@ -210,6 +210,7 @@ RSpec.describe "bundle install from an existing gemspec" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gemspec G @@ -259,16 +260,17 @@ RSpec.describe "bundle install from an existing gemspec" do expect(out).to eq("WIN") end - it "works with only_update_to_newer_versions" do + it "handles downgrades" do build_lib "omg", "2.0", :path => lib_path("omg") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gemspec :path => "#{lib_path("omg")}" G build_lib "omg", "1.0", :path => lib_path("omg") - bundle :install, :env => { "BUNDLE_BUNDLE_ONLY_UPDATE_TO_NEWER_VERSIONS" => "true" } + bundle :install expect(the_bundle).to include_gems "omg 1.0" end @@ -291,7 +293,7 @@ RSpec.describe "bundle install from an existing gemspec" do s.add_dependency "activesupport", ">= 1.0.1" end - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install, :raise_on_error => false expect(err).to include("changed") @@ -422,14 +424,13 @@ RSpec.describe "bundle install from an existing gemspec" do end end - %w[ruby jruby].each do |platform| - simulate_platform(platform) do - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gemspec - G - end - end + gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gemspec + G + + simulate_platform("ruby") { bundle "install" } + simulate_platform("jruby") { bundle "install" } end context "on ruby" do @@ -558,7 +559,7 @@ RSpec.describe "bundle install from an existing gemspec" do it "installs the ruby platform gemspec and skips dev deps with `without development` configured" do simulate_platform "ruby" - bundle "config --local without development" + bundle "config set --local without development" install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gemspec :path => '#{tmp.join("foo")}', :name => 'foo' @@ -568,4 +569,57 @@ RSpec.describe "bundle install from an existing gemspec" do expect(the_bundle).not_to include_gem "rack" end end + + context "with multiple platforms and resolving for more specific platforms" do + before do + build_lib("chef", :path => tmp.join("chef")) do |s| + s.version = "17.1.17" + s.write "chef-universal-mingw32.gemspec", build_spec("chef", "17.1.17", "universal-mingw32") {|sw| sw.runtime "win32-api", "~> 1.5.3" }.first.to_ruby + end + end + + it "does not remove the platform specific specs from the lockfile when updating" do + build_repo4 do + build_gem "win32-api", "1.5.3" do |s| + s.platform = "universal-mingw32" + end + end + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gemspec :path => "../chef" + G + + initial_lockfile = <<~L + PATH + remote: ../chef + specs: + chef (17.1.17) + chef (17.1.17-universal-mingw32) + win32-api (~> 1.5.3) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + win32-api (1.5.3-universal-mingw32) + + PLATFORMS + ruby + x64-mingw32 + x86-mingw32 + + DEPENDENCIES + chef! + + BUNDLED WITH + #{Bundler::VERSION} + L + + lockfile initial_lockfile + + bundle "update" + + expect(lockfile).to eq initial_lockfile + end + end end diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb index c0181a8788..fd3afe366a 100644 --- a/spec/bundler/install/gemfile/git_spec.rb +++ b/spec/bundler/install/gemfile/git_spec.rb @@ -62,6 +62,7 @@ RSpec.describe "bundle install with git sources" do update_git "foo" install_gemfile bundled_app2("Gemfile"), <<-G, :dir => bundled_app2 + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}" do gem 'foo' end @@ -84,6 +85,7 @@ RSpec.describe "bundle install with git sources" do build_git "foo" install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo1)}" gem "foo", "1.1", :git => "#{lib_path("foo-1.0")}" G @@ -98,6 +100,7 @@ RSpec.describe "bundle install with git sources" do end install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo1)}" platforms :jruby do gem "only_java", "1.2", :git => "#{lib_path("only_java-1.0-java")}" end @@ -119,6 +122,7 @@ RSpec.describe "bundle install with git sources" do end install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo1)}" platforms :jruby do gem "only_java", "1.2", :git => "#{lib_path("only_java-1.1-java")}" end @@ -128,7 +132,7 @@ RSpec.describe "bundle install with git sources" do end it "still works after moving the application directory" do - bundle "config --local path vendor/bundle" + bundle "config set --local path vendor/bundle" bundle "install" FileUtils.mv bundled_app, tmp("bundled_app.bck") @@ -137,7 +141,7 @@ RSpec.describe "bundle install with git sources" do end it "can still install after moving the application directory" do - bundle "config --local path vendor/bundle" + bundle "config set --local path vendor/bundle" bundle "install" FileUtils.mv bundled_app, tmp("bundled_app.bck") @@ -187,6 +191,7 @@ RSpec.describe "bundle install with git sources" do it "works" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}", :ref => "#{@revision}" do gem "foo" end @@ -202,6 +207,7 @@ RSpec.describe "bundle install with git sources" do it "works when the revision is a symbol" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}", :ref => #{@revision.to_sym.inspect} do gem "foo" end @@ -226,10 +232,11 @@ RSpec.describe "bundle install with git sources" do # want to ensure we don't fallback to HEAD update_git "foo", :path => lib_path("foo-1.0"), :branch => "rando" do |s| - s.write("lib/foo.rb", "raise 'FAIL'") + s.write("lib/foo.rb", "raise 'FAIL_FROM_RANDO'") end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do gem "foo" end @@ -246,6 +253,7 @@ RSpec.describe "bundle install with git sources" do it "works when the revision is a non-head ref and it was previously downloaded" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}" do gem "foo" end @@ -260,10 +268,11 @@ RSpec.describe "bundle install with git sources" do # want to ensure we don't fallback to HEAD update_git "foo", :path => lib_path("foo-1.0"), :branch => "rando" do |s| - s.write("lib/foo.rb", "raise 'FAIL'") + s.write("lib/foo.rb", "raise 'FAIL_FROM_RANDO'") end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do gem "foo" end @@ -284,6 +293,7 @@ RSpec.describe "bundle install with git sources" do bundle "config set global_gem_cache true" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}" do gem "foo" end @@ -306,6 +316,7 @@ RSpec.describe "bundle install with git sources" do update_git("foo", :path => repo, :branch => branch) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{repo}", :branch => #{branch.dump} do gem "foo" end @@ -322,6 +333,7 @@ RSpec.describe "bundle install with git sources" do update_git("foo", :path => repo, :branch => branch) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{repo}", :branch => #{branch.dump} do gem "foo" end @@ -339,6 +351,7 @@ RSpec.describe "bundle install with git sources" do update_git("foo", :path => repo, :branch => branch) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{repo}", :branch => #{branch.dump} do gem "foo" end @@ -357,6 +370,7 @@ RSpec.describe "bundle install with git sources" do update_git("foo", :path => repo, :tag => tag) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{repo}", :tag => #{tag.dump} do gem "foo" end @@ -373,6 +387,7 @@ RSpec.describe "bundle install with git sources" do update_git("foo", :path => repo, :tag => tag) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{repo}", :tag => #{tag.dump} do gem "foo" end @@ -390,6 +405,7 @@ RSpec.describe "bundle install with git sources" do update_git("foo", :path => repo, :tag => tag) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{repo}", :tag => #{tag.dump} do gem "foo" end @@ -647,8 +663,6 @@ RSpec.describe "bundle install with git sources" do end it "installs dependencies from git even if a newer gem is available elsewhere" do - skip "override is not winning" if Gem.win_platform? - system_gems "rack-1.0.0" build_lib "rack", "1.0", :path => lib_path("nested/bar") do |s| @@ -707,6 +721,7 @@ RSpec.describe "bundle install with git sources" do build_lib "hi2u", :path => lib_path("hi2u") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "#{lib_path("hi2u")}" do gem "omg" gem "hi2u" @@ -723,6 +738,7 @@ RSpec.describe "bundle install with git sources" do update_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{@revision}" G @@ -787,6 +803,7 @@ RSpec.describe "bundle install with git sources" do build_git "foo", "1.0" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", "1.0", :git => "#{lib_path("foo-1.0")}" G @@ -808,6 +825,7 @@ RSpec.describe "bundle install with git sources" do it "catches git errors and spits out useful output" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", "1.0", :git => "omgomg" G @@ -822,6 +840,7 @@ RSpec.describe "bundle install with git sources" do build_git "foo", :path => lib_path("foo space-1.0") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo space-1.0")}" G @@ -832,6 +851,7 @@ RSpec.describe "bundle install with git sources" do build_git "forced", "1.0" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("forced-1.0")}" do gem 'forced' end @@ -852,6 +872,9 @@ RSpec.describe "bundle install with git sources" do end it "ignores submodules if :submodule is not passed" do + # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/ + system(*%W[git config --global protocol.file.allow always]) + build_git "submodule", "1.0" build_git "has_submodule", "1.0" do |s| s.add_dependency "submodule" @@ -860,6 +883,7 @@ RSpec.describe "bundle install with git sources" do sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0") install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("has_submodule-1.0")}" do gem "has_submodule" end @@ -870,6 +894,9 @@ RSpec.describe "bundle install with git sources" do end it "handles repos with submodules" do + # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/ + system(*%W[git config --global protocol.file.allow always]) + build_git "submodule", "1.0" build_git "has_submodule", "1.0" do |s| s.add_dependency "submodule" @@ -878,6 +905,7 @@ RSpec.describe "bundle install with git sources" do sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("has_submodule-1.0")}", :submodules => true do gem "has_submodule" end @@ -887,6 +915,9 @@ RSpec.describe "bundle install with git sources" do end it "does not warn when deiniting submodules" do + # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/ + system(*%W[git config --global protocol.file.allow always]) + build_git "submodule", "1.0" build_git "has_submodule", "1.0" @@ -894,6 +925,7 @@ RSpec.describe "bundle install with git sources" do sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("has_submodule-1.0")}" do gem "has_submodule" end @@ -908,6 +940,7 @@ RSpec.describe "bundle install with git sources" do git = build_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}" do gem "foo" end @@ -917,6 +950,7 @@ RSpec.describe "bundle install with git sources" do update_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}", :ref => "#{git.ref_for("HEAD^")}" do gem "foo" end @@ -934,6 +968,7 @@ RSpec.describe "bundle install with git sources" do build_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -947,6 +982,7 @@ RSpec.describe "bundle install with git sources" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -961,6 +997,7 @@ RSpec.describe "bundle install with git sources" do FileUtils.touch(default_bundle_path("bundler")) install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -978,6 +1015,7 @@ RSpec.describe "bundle install with git sources" do build_git "bar", :path => lib_path("nested") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("nested")}" gem "bar", :git => "#{lib_path("nested")}" G @@ -1035,6 +1073,7 @@ RSpec.describe "bundle install with git sources" do build_git "valim" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "valim", :git => "#{file_uri_for(lib_path("valim-1.0"))}" G @@ -1060,11 +1099,13 @@ RSpec.describe "bundle install with git sources" do revision = revision_for(lib_path("foo-1.0")) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :ref => "#{revision}" G expect(out).to_not match(/Revision.*does not exist/) install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :ref => "deadbeef" G expect(err).to include("Revision deadbeef does not exist in the repository") @@ -1082,7 +1123,7 @@ RSpec.describe "bundle install with git sources" do simulate_new_machine - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install end end @@ -1091,6 +1132,7 @@ RSpec.describe "bundle install with git sources" do it "runs pre-install hooks" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1110,6 +1152,7 @@ RSpec.describe "bundle install with git sources" do it "runs post-install hooks" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1129,6 +1172,7 @@ RSpec.describe "bundle install with git sources" do it "complains if the install hook fails" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1401,10 +1445,48 @@ In Gemfile: end describe "without git installed" do - it "prints a better error message" do + it "prints a better error message when installing" do + build_git "foo" + + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + + gem "rake", git: "https://github.com/ruby/rake" + G + + lockfile <<-L + GIT + remote: https://github.com/ruby/rake + revision: 5c60da8644a9e4f655e819252e3b6ca77f42b7af + specs: + rake (13.0.6) + + GEM + remote: https://rubygems.org/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rake! + + BUNDLED WITH + #{Bundler::VERSION} + L + + with_path_as("") do + bundle "install", :raise_on_error => false + end + expect(err). + to include("You need to install git to be able to use gems from git repositories. For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git") + end + + it "prints a better error message when updating" do build_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}" do gem 'foo' end @@ -1421,6 +1503,7 @@ In Gemfile: build_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}" do gem 'foo' end @@ -1450,6 +1533,7 @@ In Gemfile: build_git "foo", "1.0", :path => lib_path("foo") gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo")}", :branch => "master" G @@ -1465,6 +1549,7 @@ In Gemfile: it "does not display the password" do install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo1)}" git "https://#{credentials}@github.com/company/private-repo" do gem "foo" end @@ -1480,6 +1565,7 @@ In Gemfile: it "displays the oauth scheme but not the oauth token" do install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo1)}" git "https://#{credentials}:x-oauth-basic@github.com/company/private-repo" do gem "foo" end diff --git a/spec/bundler/install/gemfile/groups_spec.rb b/spec/bundler/install/gemfile/groups_spec.rb index 567a9b1172..c92b5dcc57 100644 --- a/spec/bundler/install/gemfile/groups_spec.rb +++ b/spec/bundler/install/gemfile/groups_spec.rb @@ -86,7 +86,7 @@ RSpec.describe "bundle install with groups" do end it "installs gems in the default group" do - bundle "config --local without emo" + bundle "config set --local without emo" bundle :install expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default] end @@ -117,7 +117,7 @@ RSpec.describe "bundle install with groups" do end it "does not install gems from the excluded group" do - bundle "config --local without emo" + bundle "config set --local without emo" bundle :install expect(the_bundle).not_to include_gems "activesupport 2.3.5", :groups => [:default] end @@ -130,13 +130,13 @@ RSpec.describe "bundle install with groups" do end it "does not say it installed gems from the excluded group" do - bundle "config --local without emo" + bundle "config set --local without emo" bundle :install expect(out).not_to include("activesupport") end it "allows Bundler.setup for specific groups" do - bundle "config --local without emo" + bundle "config set --local without emo" bundle :install run("require 'rack'; puts RACK", :default) expect(out).to eq("1.0.0") @@ -151,7 +151,7 @@ RSpec.describe "bundle install with groups" do end G - bundle "config --local without emo" + bundle "config set --local without emo" bundle :install expect(the_bundle).to include_gems "activesupport 2.3.2", :groups => [:default] end @@ -188,7 +188,7 @@ RSpec.describe "bundle install with groups" do end it "installs gems from the optional group when requested" do - bundle "config --local with debugging" + bundle "config set --local with debugging" bundle :install expect(the_bundle).to include_gems "thin 1.0" end @@ -214,13 +214,13 @@ RSpec.describe "bundle install with groups" do end it "removes groups from without when passed at --with", :bundler => "< 3" do - bundle "config --local without emo" + bundle "config set --local without emo" bundle "install --with emo" expect(the_bundle).to include_gems "activesupport 2.3.5" end it "removes groups from with when passed at --without", :bundler => "< 3" do - bundle "config --local with debugging" + bundle "config set --local with debugging" bundle "install --without debugging", :raise_on_error => false expect(the_bundle).not_to include_gem "thin 1.0" end @@ -251,13 +251,13 @@ RSpec.describe "bundle install with groups" do end it "has no effect when listing a not optional group in with" do - bundle "config --local with emo" + bundle "config set --local with emo" bundle :install expect(the_bundle).to include_gems "activesupport 2.3.5" end it "has no effect when listing an optional group in without" do - bundle "config --local without debugging" + bundle "config set --local without debugging" bundle :install expect(the_bundle).not_to include_gems "thin 1.0" end @@ -275,13 +275,13 @@ RSpec.describe "bundle install with groups" do end it "installs gems in the default group" do - bundle "config --local without emo lolercoaster" + bundle "config set --local without emo lolercoaster" bundle :install expect(the_bundle).to include_gems "rack 1.0.0" end it "installs the gem if any of its groups are installed" do - bundle "config --local without emo" + bundle "config set --local without emo" bundle :install expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5" end @@ -303,19 +303,19 @@ RSpec.describe "bundle install with groups" do end it "installs the gem unless all groups are excluded" do - bundle "config --local without emo" + bundle "config set --local without emo" bundle :install expect(the_bundle).to include_gems "activesupport 2.3.5" - bundle "config --local without lolercoaster" + bundle "config set --local without lolercoaster" bundle :install expect(the_bundle).to include_gems "activesupport 2.3.5" - bundle "config --local without emo lolercoaster" + bundle "config set --local without emo lolercoaster" bundle :install expect(the_bundle).not_to include_gems "activesupport 2.3.5" - bundle "config --local without 'emo lolercoaster'" + bundle "config set --local without 'emo lolercoaster'" bundle :install expect(the_bundle).not_to include_gems "activesupport 2.3.5" end @@ -336,13 +336,13 @@ RSpec.describe "bundle install with groups" do end it "installs gems in the default group" do - bundle "config --local without emo lolercoaster" + bundle "config set --local without emo lolercoaster" bundle :install expect(the_bundle).to include_gems "rack 1.0.0" end it "installs the gem if any of its groups are installed" do - bundle "config --local without emo" + bundle "config set --local without emo" bundle :install expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5" end @@ -358,7 +358,7 @@ RSpec.describe "bundle install with groups" do G ruby <<-R - require "#{lib_dir}/bundler" + require "#{entrypoint}" Bundler.setup :default Bundler.require :default puts RACK @@ -380,7 +380,7 @@ RSpec.describe "bundle install with groups" do system_gems "rack-0.9.1" - bundle "config --local without rack" + bundle "config set --local without rack" install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "rack" @@ -404,7 +404,7 @@ RSpec.describe "bundle install with groups" do it "does not hit the remote a second time" do FileUtils.rm_rf gem_repo2 - bundle "config --local without rack" + bundle "config set --local without rack" bundle :install, :verbose => true expect(last_command.stdboth).not_to match(/fetching/i) end diff --git a/spec/bundler/install/gemfile/install_if_spec.rb b/spec/bundler/install/gemfile/install_if_spec.rb index 786e0e9258..3d2d15a698 100644 --- a/spec/bundler/install/gemfile/install_if_spec.rb +++ b/spec/bundler/install/gemfile/install_if_spec.rb @@ -18,7 +18,7 @@ RSpec.describe "bundle install with install_if conditionals" do expect(the_bundle).not_to include_gems("thin") expect(the_bundle).not_to include_gems("foo") - lockfile_should_be <<-L + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: diff --git a/spec/bundler/install/gemfile/path_spec.rb b/spec/bundler/install/gemfile/path_spec.rb index 6cd981e3d3..bea7c11dec 100644 --- a/spec/bundler/install/gemfile/path_spec.rb +++ b/spec/bundler/install/gemfile/path_spec.rb @@ -16,6 +16,7 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "#{lib_path("foo-1.0")}" do gem 'foo' end @@ -28,6 +29,7 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "#{lib_path("foo-1.0")}" G @@ -40,6 +42,7 @@ RSpec.describe "bundle install with explicit source paths" do relative_path = lib_path("foo-1.0").relative_path_from(bundled_app) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "#{relative_path}" G @@ -52,6 +55,7 @@ RSpec.describe "bundle install with explicit source paths" do relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("~").expand_path) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "~/#{relative_path}" G @@ -66,6 +70,7 @@ RSpec.describe "bundle install with explicit source paths" do relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("/home/#{username}").expand_path) install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "~#{username}/#{relative_path}" G expect(err).to match("There was an error while trying to use the path `~#{username}/#{relative_path}`.") @@ -76,6 +81,7 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo", :path => bundled_app("foo-1.0") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "./foo-1.0" G @@ -87,6 +93,7 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "aaa", :path => lib_path("demo/aaa") gemfile = <<-G + source "#{file_uri_for(gem_repo1)}" gemspec gem "aaa", :path => "./aaa" G @@ -105,6 +112,7 @@ RSpec.describe "bundle install with explicit source paths" do aaa (1.0) GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -119,25 +127,24 @@ RSpec.describe "bundle install with explicit source paths" do L bundle :install, :dir => lib_path("demo") - expect(lib_path("demo/Gemfile.lock")).to have_lockfile(lockfile) + expect(lib_path("demo/Gemfile.lock")).to read_as(lockfile) bundle :update, :all => true, :dir => lib_path("demo") - expect(lib_path("demo/Gemfile.lock")).to have_lockfile(lockfile) + expect(lib_path("demo/Gemfile.lock")).to read_as(lockfile) end it "expands paths when comparing locked paths to Gemfile paths" do build_lib "foo", :path => bundled_app("foo-1.0") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => File.expand_path("../foo-1.0", __FILE__) G - bundle "config --local frozen true" + bundle "config set --local frozen true" bundle :install end it "installs dependencies from the path even if a newer gem is available elsewhere" do - skip "override is not winning" if Gem.win_platform? - system_gems "rack-1.0.0" build_lib "rack", "1.0", :path => lib_path("nested/bar") do |s| @@ -169,22 +176,90 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo", "1.0.0", :path => lib_path("omg/foo") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "omg", :path => "#{lib_path("omg")}" G expect(the_bundle).to include_gems "foo 1.0" end - it "works with only_update_to_newer_versions" do + it "works when using prereleases of 0.0.0" do + build_lib "foo", "0.0.0.dev", :path => lib_path("foo") + + gemfile <<~G + source "#{file_uri_for(gem_repo1)}" + gem "foo", :path => "#{lib_path("foo")}" + G + + lockfile <<~L + PATH + remote: #{lib_path("foo")} + specs: + foo (0.0.0.dev) + + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install + + expect(the_bundle).to include_gems "foo 0.0.0.dev" + end + + it "works when using uppercase prereleases of 0.0.0" do + build_lib "foo", "0.0.0.SNAPSHOT", :path => lib_path("foo") + + gemfile <<~G + source "#{file_uri_for(gem_repo1)}" + gem "foo", :path => "#{lib_path("foo")}" + G + + lockfile <<~L + PATH + remote: #{lib_path("foo")} + specs: + foo (0.0.0.SNAPSHOT) + + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install + + expect(the_bundle).to include_gems "foo 0.0.0.SNAPSHOT" + end + + it "handles downgrades" do build_lib "omg", "2.0", :path => lib_path("omg") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "omg", :path => "#{lib_path("omg")}" G build_lib "omg", "1.0", :path => lib_path("omg") - bundle :install, :env => { "BUNDLE_BUNDLE_ONLY_UPDATE_TO_NEWER_VERSIONS" => "true" } + bundle :install expect(the_bundle).to include_gems "omg 1.0" end @@ -202,6 +277,7 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "premailer", :path => "#{lib_path("premailer")}" G @@ -223,6 +299,7 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo-1.0")}" G @@ -306,6 +383,7 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo1)}" gemspec :path => "#{lib_path("foo")}" G @@ -319,6 +397,7 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gemspec :path => "#{lib_path("foo")}", :name => "foo" G @@ -330,11 +409,13 @@ RSpec.describe "bundle install with explicit source paths" do s.executables = "foobar" end - install_gemfile <<-G + install_gemfile <<-G, :verbose => true + source "#{file_uri_for(gem_repo1)}" path "#{lib_path("foo-1.0")}" do gem 'foo' end G + expect(out).to include("Using foo 1.0 from source at `#{lib_path("foo-1.0")}` and installing its executables") expect(the_bundle).to include_gems "foo 1.0" bundle "exec foobar" @@ -347,6 +428,7 @@ RSpec.describe "bundle install with explicit source paths" do lib_path("foo-1.0").join("bin/performance").mkpath install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', '1.0', :path => "#{lib_path("foo-1.0")}" G expect(err).to be_empty @@ -356,6 +438,7 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "#{lib_path("foo-1.0")}" G @@ -368,6 +451,7 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "hi2u" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "#{lib_path}" do gem "omg" gem "hi2u" @@ -386,6 +470,7 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo")}" gem "omg", :path => "#{lib_path("omg")}" G @@ -397,6 +482,7 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo", :gemspec => false gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", "1.0", :path => "#{lib_path("foo-1.0")}" G @@ -412,12 +498,13 @@ RSpec.describe "bundle install with explicit source paths" do specs: GEM - remote: http://rubygems.org + remote: http://rubygems.org/ L FileUtils.mkdir_p(bundled_app("vendor/bar")) install_gemfile <<-G + source "http://rubygems.org" gem "bar", "1.0.0", path: "vendor/bar", require: "bar/nyard" G end @@ -462,6 +549,7 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo-1.0")}" G @@ -477,6 +565,7 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "bar", "1.0", :path => lib_path("foo/bar") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo")}" G end @@ -529,7 +618,7 @@ RSpec.describe "bundle install with explicit source paths" do expect(the_bundle).to include_gems "rack 0.9.1" - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: @@ -557,7 +646,7 @@ RSpec.describe "bundle install with explicit source paths" do bundle "install" - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: @@ -591,7 +680,7 @@ RSpec.describe "bundle install with explicit source paths" do expect(the_bundle).to include_gems "rack 0.9.1" - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: @@ -620,7 +709,7 @@ RSpec.describe "bundle install with explicit source paths" do bundle "install" - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: @@ -701,8 +790,6 @@ RSpec.describe "bundle install with explicit source paths" do describe "when there are both a gemspec and remote gems" do it "doesn't query rubygems for local gemspec name" do - skip "platform issues" if Gem.win_platform? - build_lib "private_lib", "2.2", :path => lib_path("private_lib") gemfile = <<-G source "http://localgemserver.test" @@ -723,6 +810,7 @@ RSpec.describe "bundle install with explicit source paths" do it "runs pre-install hooks" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -742,6 +830,7 @@ RSpec.describe "bundle install with explicit source paths" do it "runs post-install hooks" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -761,6 +850,7 @@ RSpec.describe "bundle install with explicit source paths" do it "complains if the install hook fails" do build_git "foo" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -791,6 +881,7 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo-1.0")}" gem "bar", :path => "#{lib_path("bar-1.0")}" G diff --git a/spec/bundler/install/gemfile/platform_spec.rb b/spec/bundler/install/gemfile/platform_spec.rb index 8ab59abeeb..35a3872c03 100644 --- a/spec/bundler/install/gemfile/platform_spec.rb +++ b/spec/bundler/install/gemfile/platform_spec.rb @@ -88,14 +88,15 @@ RSpec.describe "bundle install across platforms" do simulate_new_machine simulate_platform "ruby" - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - - gem "nokogiri" - G + bundle "install" expect(the_bundle).to include_gems "nokogiri 1.4.2" expect(the_bundle).not_to include_gems "weakling" + + simulate_platform "java" + bundle "install" + + expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3" end it "does not keep unneeded platforms for gems that are used" do @@ -127,7 +128,7 @@ RSpec.describe "bundle install across platforms" do gem "pry" G - lockfile_should_be <<-L + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo4)}/ specs: @@ -155,7 +156,7 @@ RSpec.describe "bundle install across platforms" do bundle "lock --add-platform ruby" - good_lockfile = strip_whitespace(<<-L) + good_lockfile = <<~L GEM remote: #{file_uri_for(gem_repo4)}/ specs: @@ -185,9 +186,9 @@ RSpec.describe "bundle install across platforms" do #{Bundler::VERSION} L - lockfile_should_be good_lockfile + expect(lockfile).to eq good_lockfile - bad_lockfile = strip_whitespace <<-L + bad_lockfile = <<~L GEM remote: #{file_uri_for(gem_repo4)}/ specs: @@ -221,40 +222,26 @@ RSpec.describe "bundle install across platforms" do aggregate_failures do lockfile bad_lockfile bundle :install - lockfile_should_be good_lockfile + expect(lockfile).to eq good_lockfile lockfile bad_lockfile bundle :update, :all => true - lockfile_should_be good_lockfile + expect(lockfile).to eq good_lockfile lockfile bad_lockfile bundle "update ffi" - lockfile_should_be good_lockfile + expect(lockfile).to eq good_lockfile lockfile bad_lockfile bundle "update empyrean" - lockfile_should_be good_lockfile + expect(lockfile).to eq good_lockfile lockfile bad_lockfile bundle :lock - lockfile_should_be good_lockfile + expect(lockfile).to eq good_lockfile end end - it "works the other way with gems that have different dependencies" do - simulate_platform "ruby" - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - - gem "nokogiri" - G - - simulate_platform "java" - bundle "install" - - expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3" - end - it "works with gems with platform-specific dependency having different requirements order" do simulate_platform x64_mac @@ -291,7 +278,7 @@ RSpec.describe "bundle install across platforms" do gem "rack", "1.0.0" G - bundle "config --local path vendor/bundle" + bundle "config set --local path vendor/bundle" bundle :install FileUtils.mv(vendored_gems, bundled_app("vendor/bundle", Gem.ruby_engine, "1.8")) @@ -323,7 +310,7 @@ RSpec.describe "bundle install across platforms" do expect(the_bundle).to include_gem "platform_specific 1.0 RUBY" - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -371,6 +358,49 @@ RSpec.describe "bundle install with platform conditionals" do expect(the_bundle).not_to include_gems "nokogiri 1.4.2" end + it "installs gems tagged w/ another platform but also dependent on the current one transitively" do + build_repo4 do + build_gem "activesupport", "6.1.4.1" do |s| + s.add_dependency "tzinfo", "~> 2.0" + end + + build_gem "tzinfo", "2.0.4" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "activesupport" + + platforms :#{not_local_tag} do + gem "tzinfo", "~> 1.2" + end + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + activesupport (6.1.4.1) + tzinfo (~> 2.0) + tzinfo (2.0.4) + + PLATFORMS + #{specific_local_platform} + + DEPENDENCIES + activesupport + tzinfo (~> 1.2) + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install --verbose" + + expect(the_bundle).to include_gems "tzinfo 2.0.4" + end + it "installs gems tagged w/ the current platforms inline" do skip "platform issues" if Gem.win_platform? @@ -413,6 +443,7 @@ RSpec.describe "bundle install with platform conditionals" do build_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" platform :#{not_local_tag} do gem "foo", :git => "#{lib_path("foo-1.0")}" end @@ -459,7 +490,7 @@ RSpec.describe "bundle install with platform conditionals" do expect(err).to be_empty - lockfile_should_be <<-L + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb index 655f91dd69..5456e95f33 100644 --- a/spec/bundler/install/gemfile/sources_spec.rb +++ b/spec/bundler/install/gemfile/sources_spec.rb @@ -20,23 +20,23 @@ RSpec.describe "bundle install with gems on multiple sources" do before do gemfile <<-G - source "#{file_uri_for(gem_repo3)}" - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo3" + source "https://gem.repo1" gem "rack-obama" gem "rack" G end - it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", :bundler => "2" do - bundle :install + it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", :bundler => "< 3" do + bundle :install, :artifice => "compact_index" expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") - expect(err).to include("Installed from: #{file_uri_for(gem_repo1)}") + expect(err).to include("Installed from: https://gem.repo1") expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1") end it "fails", :bundler => "3" do - bundle :instal, :raise_on_error => false + bundle :instal, :artifice => "compact_index", :raise_on_error => false expect(err).to include("Each source after the first must include a block") expect(exitstatus).to eq(4) end @@ -47,22 +47,22 @@ RSpec.describe "bundle install with gems on multiple sources" do before do gemfile <<-G - source "#{file_uri_for(gem_repo3)}" - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo3" + source "https://gem.repo1" gem "rack-obama" gem "rack", "1.0.0" # force it to install the working version in repo1 G end - it "warns about ambiguous gems, but installs anyway", :bundler => "2" do - bundle :install + it "warns about ambiguous gems, but installs anyway", :bundler => "< 3" do + bundle :install, :artifice => "compact_index" expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") - expect(err).to include("Installed from: #{file_uri_for(gem_repo1)}") + expect(err).to include("Installed from: https://gem.repo1") expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1") end it "fails", :bundler => "3" do - bundle :install, :raise_on_error => false + bundle :install, :artifice => "compact_index", :raise_on_error => false expect(err).to include("Each source after the first must include a block") expect(exitstatus).to eq(4) end @@ -85,30 +85,30 @@ RSpec.describe "bundle install with gems on multiple sources" do end gemfile <<-G - source "#{file_uri_for(gem_repo3)}" - source "#{file_uri_for(gem_repo1)}" do + source "https://gem.repo3" + source "https://gem.repo1" do gem "thin" # comes first to test name sorting gem "rack" end - gem "rack-obama" # shoud come from repo3! + gem "rack-obama" # should come from repo3! G end it "installs the gems without any warning" do - bundle :install - expect(out).not_to include("Warning") + bundle :install, :artifice => "compact_index" + expect(err).not_to include("Warning") expect(the_bundle).to include_gems("rack-obama 1.0.0") expect(the_bundle).to include_gems("rack 1.0.0", :source => "remote1") end it "can cache and deploy" do - bundle :cache + bundle :cache, :artifice => "compact_index" expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist expect(bundled_app("vendor/cache/rack-obama-1.0.gem")).to exist - bundle "config --local deployment true" - bundle :install + bundle "config set --local deployment true" + bundle :install, :artifice => "compact_index" expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0") end @@ -128,258 +128,813 @@ RSpec.describe "bundle install with gems on multiple sources" do end end - gemfile <<-G - source "#{file_uri_for(gem_repo3)}" + install_gemfile <<-G, :artifice => "compact_index" + source "https://gem.repo3" gem "rack-obama" # should come from repo3! - gem "rack", :source => "#{file_uri_for(gem_repo1)}" + gem "rack", :source => "https://gem.repo1" G end it "installs the gems without any warning" do - bundle :install - expect(out).not_to include("Warning") + expect(err).not_to include("Warning") expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0") end end - context "when a pinned gem has an indirect dependency" do + context "when a pinned gem has an indirect dependency in the pinned source" do before do build_repo gem_repo3 do build_gem "depends_on_rack", "1.0.1" do |s| s.add_dependency "rack" end end + + # we need a working rack gem in repo3 + update_repo gem_repo3 do + build_gem "rack", "1.0.0" + end + + gemfile <<-G + source "https://gem.repo2" + source "https://gem.repo3" do + gem "depends_on_rack" + end + G end - context "when the indirect dependency is in the pinned source" do + context "and not in any other sources" do before do - # we need a working rack gem in repo3 - update_repo gem_repo3 do - build_gem "rack", "1.0.0" + build_repo(gem_repo2) {} + end + + it "installs from the same source without any warning" do + bundle :install, :artifice => "compact_index" + expect(err).not_to include("Warning") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + end + end + + context "and in another source" do + before do + # need this to be broken to check for correct source ordering + build_repo gem_repo2 do + build_gem "rack", "1.0.0" do |s| + s.write "lib/rack.rb", "RACK = 'FAIL'" + end end + end + + it "installs from the same source without any warning" do + bundle :install, :artifice => "compact_index" + + expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + # In https://github.com/bundler/bundler/issues/3585 this failed + # when there is already a lock file, and the gems are missing, so try again + system_gems [] + bundle :install, :artifice => "compact_index" + + expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + end + end + end + + context "when a pinned gem has an indirect dependency in a different source" do + before do + # In these tests, we need a working rack gem in repo2 and not repo3 + + build_repo gem_repo3 do + build_gem "depends_on_rack", "1.0.1" do |s| + s.add_dependency "rack" + end + end + + build_repo gem_repo2 do + build_gem "rack", "1.0.0" + end + end + + context "and not in any other sources" do + before do + install_gemfile <<-G, :artifice => "compact_index" + source "https://gem.repo2" + source "https://gem.repo3" do + gem "depends_on_rack" + end + G + end + + it "installs from the other source without any warning" do + expect(err).not_to include("Warning") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + end + end + + context "and in yet another source" do + before do gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - source "#{file_uri_for(gem_repo3)}" do + source "https://gem.repo1" + source "https://gem.repo2" + source "https://gem.repo3" do gem "depends_on_rack" end G end - context "and not in any other sources" do - before do - build_repo(gem_repo2) {} - end + it "installs from the other source and warns about ambiguous gems", :bundler => "< 3" do + bundle :install, :artifice => "compact_index" + expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") + expect(err).to include("Installed from: https://gem.repo2") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo1/ + remote: https://gem.repo2/ + specs: + rack (1.0.0) + + GEM + remote: https://gem.repo3/ + specs: + depends_on_rack (1.0.1) + rack + + PLATFORMS + #{specific_local_platform} + + DEPENDENCIES + depends_on_rack! + + BUNDLED WITH + #{Bundler::VERSION} + L + + previous_lockfile = lockfile + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + expect(lockfile).to eq(previous_lockfile) + end - it "installs from the same source without any warning" do - bundle :install - expect(out).not_to include("Warning") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") - end + it "fails", :bundler => "3" do + bundle :install, :artifice => "compact_index", :raise_on_error => false + expect(err).to include("Each source after the first must include a block") + expect(exitstatus).to eq(4) end + end - context "and in another source" do - before do - # need this to be broken to check for correct source ordering - build_repo gem_repo2 do - build_gem "rack", "1.0.0" do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" - end + context "and only the dependency is pinned" do + before do + # need this to be broken to check for correct source ordering + build_repo gem_repo2 do + build_gem "rack", "1.0.0" do |s| + s.write "lib/rack.rb", "RACK = 'FAIL'" end end - context "when disable_multisource is set" do - before do - bundle "config set disable_multisource true" - end + gemfile <<-G + source "https://gem.repo3" # contains depends_on_rack + source "https://gem.repo2" # contains broken rack - it "installs from the same source without any warning" do - bundle :install + gem "depends_on_rack" # installed from gem_repo3 + gem "rack", :source => "https://gem.repo1" + G + end - expect(out).not_to include("Warning: the gem 'rack' was found in multiple sources.") - expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + it "installs the dependency from the pinned source without warning", :bundler => "< 3" do + bundle :install, :artifice => "compact_index" - # when there is already a lock file, and the gems are missing, so try again - system_gems [] - bundle :install + expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") - expect(out).not_to include("Warning: the gem 'rack' was found in multiple sources.") - expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") - end - end + # In https://github.com/rubygems/bundler/issues/3585 this failed + # when there is already a lock file, and the gems are missing, so try again + system_gems [] + bundle :install, :artifice => "compact_index" + + expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + end + + it "fails", :bundler => "3" do + bundle :install, :artifice => "compact_index", :raise_on_error => false + expect(err).to include("Each source after the first must include a block") + expect(exitstatus).to eq(4) end end + end - context "when the indirect dependency is in a different source" do - before do - # In these tests, we need a working rack gem in repo2 and not repo3 - build_repo gem_repo2 do - build_gem "rack", "1.0.0" - end + context "when a top-level gem can only be found in an scoped source" do + before do + build_repo2 + + build_repo gem_repo3 do + build_gem "private_gem_1", "1.0.0" + build_gem "private_gem_2", "1.0.0" end - context "and not in any other sources" do - before do - gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - source "#{file_uri_for(gem_repo3)}" do - gem "depends_on_rack" - end - G + gemfile <<-G + source "https://gem.repo2" + + gem "private_gem_1" + + source "https://gem.repo3" do + gem "private_gem_2" end + G + end + + it "fails" do + bundle :install, :artifice => "compact_index", :raise_on_error => false + expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository https://gem.repo2/ or installed locally.") + end + end + + context "when an indirect dependency can't be found in the aggregate rubygems source", :bundler => "< 3" do + before do + build_repo2 - it "installs from the other source without any warning" do - bundle :install - expect(out).not_to include("Warning") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + build_repo gem_repo3 do + build_gem "depends_on_missing", "1.0.1" do |s| + s.add_dependency "missing" end end - context "and in yet another source" do - before do - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - source "#{file_uri_for(gem_repo2)}" - source "#{file_uri_for(gem_repo3)}" do - gem "depends_on_rack" - end - G + gemfile <<-G + source "https://gem.repo2" + + source "https://gem.repo3" + + gem "depends_on_missing" + G + end + + it "fails" do + bundle :install, :artifice => "compact_index", :raise_on_error => false + expect(err).to include("Could not find gem 'missing', which is required by gem 'depends_on_missing', in any of the sources.") + end + end + + context "when a top-level gem has an indirect dependency" do + before do + build_repo gem_repo2 do + build_gem "depends_on_rack", "1.0.1" do |s| + s.add_dependency "rack" end + end - it "installs from the other source and warns about ambiguous gems", :bundler => "2" do - bundle :install - expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") - expect(err).to include("Installed from: #{file_uri_for(gem_repo2)}") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + build_repo gem_repo3 do + build_gem "unrelated_gem", "1.0.0" + end + + gemfile <<-G + source "https://gem.repo2" + + gem "depends_on_rack" + + source "https://gem.repo3" do + gem "unrelated_gem" end + G + end - it "fails", :bundler => "3" do - bundle :install, :raise_on_error => false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) + context "and the dependency is only in the top-level source" do + before do + update_repo gem_repo2 do + build_gem "rack", "1.0.0" end end - context "and only the dependency is pinned" do - before do - # need this to be broken to check for correct source ordering - build_repo gem_repo2 do - build_gem "rack", "1.0.0" do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" - end + it "installs the dependency from the top-level source without warning" do + bundle :install, :artifice => "compact_index" + expect(err).not_to include("Warning") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote2") + expect(the_bundle).to include_gems("unrelated_gem 1.0.0", :source => "remote3") + end + end + + context "and the dependency is only in a pinned source" do + before do + update_repo gem_repo3 do + build_gem "rack", "1.0.0" do |s| + s.write "lib/rack.rb", "RACK = 'FAIL'" end + end + end + + it "does not find the dependency" do + bundle :install, :artifice => "compact_index", :raise_on_error => false + expect(err).to include( + "Could not find gem 'rack', which is required by gem 'depends_on_rack', in rubygems repository https://gem.repo2/ or installed locally." + ) + end + end - gemfile <<-G - source "#{file_uri_for(gem_repo3)}" # contains depends_on_rack - source "#{file_uri_for(gem_repo2)}" # contains broken rack + context "and the dependency is in both the top-level and a pinned source" do + before do + update_repo gem_repo2 do + build_gem "rack", "1.0.0" + end - gem "depends_on_rack" # installed from gem_repo3 - gem "rack", :source => "#{file_uri_for(gem_repo1)}" - G + update_repo gem_repo3 do + build_gem "rack", "1.0.0" do |s| + s.write "lib/rack.rb", "RACK = 'FAIL'" + end end + end - it "installs the dependency from the pinned source without warning", :bundler => "2" do - bundle :install + it "installs the dependency from the top-level source without warning" do + bundle :install, :artifice => "compact_index" + expect(err).not_to include("Warning") + expect(run("require 'rack'; puts RACK")).to eq("1.0.0") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote2") + expect(the_bundle).to include_gems("unrelated_gem 1.0.0", :source => "remote3") + end + end + end + + context "when a scoped gem has a deeply nested indirect dependency" do + before do + build_repo gem_repo3 do + build_gem "depends_on_depends_on_rack", "1.0.1" do |s| + s.add_dependency "depends_on_rack" + end - expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + build_gem "depends_on_rack", "1.0.1" do |s| + s.add_dependency "rack" + end + end - # In https://github.com/rubygems/bundler/issues/3585 this failed - # when there is already a lock file, and the gems are missing, so try again - system_gems [] - bundle :install + gemfile <<-G + source "https://gem.repo2" - expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + source "https://gem.repo3" do + gem "depends_on_depends_on_rack" end + G + end - it "fails", :bundler => "3" do - bundle :install, :raise_on_error => false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) + context "and the dependency is only in the top-level source" do + before do + update_repo gem_repo2 do + build_gem "rack", "1.0.0" end end + + it "installs the dependency from the top-level source" do + bundle :install, :artifice => "compact_index" + expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0") + expect(the_bundle).to include_gems("rack 1.0.0", :source => "remote2") + expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", :source => "remote3") + end end - end - context "when a top-level gem has an indirect dependency" do - context "when disable_multisource is set" do + context "and the dependency is only in a pinned source" do before do - bundle "config set disable_multisource true" + build_repo2 + + update_repo gem_repo3 do + build_gem "rack", "1.0.0" + end end + it "installs the dependency from the pinned source" do + bundle :install, :artifice => "compact_index" + expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + end + end + + context "and the dependency is in both the top-level and a pinned source" do before do - build_repo gem_repo2 do - build_gem "depends_on_rack", "1.0.1" do |s| - s.add_dependency "rack" + update_repo gem_repo2 do + build_gem "rack", "1.0.0" do |s| + s.write "lib/rack.rb", "RACK = 'FAIL'" end end - build_repo gem_repo3 do - build_gem "unrelated_gem", "1.0.0" + update_repo gem_repo3 do + build_gem "rack", "1.0.0" end + end - gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + it "installs the dependency from the pinned source without warning" do + bundle :install, :artifice => "compact_index" + expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + end + end + end - gem "depends_on_rack" + context "when the lockfile has aggregated rubygems sources and newer versions of dependencies are available" do + before do + build_repo gem_repo2 do + build_gem "activesupport", "6.0.3.4" do |s| + s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" + s.add_dependency "i18n", ">= 0.7", "< 2" + s.add_dependency "minitest", "~> 5.1" + s.add_dependency "tzinfo", "~> 1.1" + s.add_dependency "zeitwerk", "~> 2.2", ">= 2.2.2" + end - source "#{file_uri_for(gem_repo3)}" do - gem "unrelated_gem" - end - G - end + build_gem "activesupport", "6.1.2.1" do |s| + s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" + s.add_dependency "i18n", ">= 1.6", "< 2" + s.add_dependency "minitest", ">= 5.1" + s.add_dependency "tzinfo", "~> 2.0" + s.add_dependency "zeitwerk", "~> 2.3" + end - context "and the dependency is only in the top-level source" do - before do - update_repo gem_repo2 do - build_gem "rack", "1.0.0" - end + build_gem "concurrent-ruby", "1.1.8" + build_gem "concurrent-ruby", "1.1.9" + build_gem "connection_pool", "2.2.3" + + build_gem "i18n", "1.8.9" do |s| + s.add_dependency "concurrent-ruby", "~> 1.0" + end + + build_gem "minitest", "5.14.3" + build_gem "rack", "2.2.3" + build_gem "redis", "4.2.5" + + build_gem "sidekiq", "6.1.3" do |s| + s.add_dependency "connection_pool", ">= 2.2.2" + s.add_dependency "rack", "~> 2.0" + s.add_dependency "redis", ">= 4.2.0" + end + + build_gem "thread_safe", "0.3.6" + + build_gem "tzinfo", "1.2.9" do |s| + s.add_dependency "thread_safe", "~> 0.1" end - it "installs all gems without warning" do - bundle :install - expect(err).not_to include("Warning") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0") + build_gem "tzinfo", "2.0.4" do |s| + s.add_dependency "concurrent-ruby", "~> 1.0" end + + build_gem "zeitwerk", "2.4.2" end - context "and the dependency is only in a pinned source" do - before do - update_repo gem_repo3 do - build_gem "rack", "1.0.0" do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" - end - end + build_repo gem_repo3 do + build_gem "sidekiq-pro", "5.2.1" do |s| + s.add_dependency "connection_pool", ">= 2.2.3" + s.add_dependency "sidekiq", ">= 6.1.0" + end + end + + gemfile <<-G + # frozen_string_literal: true + + source "https://gem.repo2" + + gem "activesupport" + + source "https://gem.repo3" do + gem "sidekiq-pro" end + G + + lockfile <<~L + GEM + remote: https://gem.repo2/ + remote: https://gem.repo3/ + specs: + activesupport (6.0.3.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.2, >= 2.2.2) + concurrent-ruby (1.1.8) + connection_pool (2.2.3) + i18n (1.8.9) + concurrent-ruby (~> 1.0) + minitest (5.14.3) + rack (2.2.3) + redis (4.2.5) + sidekiq (6.1.3) + connection_pool (>= 2.2.2) + rack (~> 2.0) + redis (>= 4.2.0) + sidekiq-pro (5.2.1) + connection_pool (>= 2.2.3) + sidekiq (>= 6.1.0) + thread_safe (0.3.6) + tzinfo (1.2.9) + thread_safe (~> 0.1) + zeitwerk (2.4.2) + + PLATFORMS + #{specific_local_platform} + + DEPENDENCIES + activesupport + sidekiq-pro! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "does not install newer versions but updates the lockfile format when running bundle install in non frozen mode, and doesn't warn" do + bundle :install, :artifice => "compact_index" + expect(err).to be_empty + + expect(the_bundle).to include_gems("activesupport 6.0.3.4") + expect(the_bundle).not_to include_gems("activesupport 6.1.2.1") + expect(the_bundle).to include_gems("tzinfo 1.2.9") + expect(the_bundle).not_to include_gems("tzinfo 2.0.4") + expect(the_bundle).to include_gems("concurrent-ruby 1.1.8") + expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.9") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo2/ + specs: + activesupport (6.0.3.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.2, >= 2.2.2) + concurrent-ruby (1.1.8) + connection_pool (2.2.3) + i18n (1.8.9) + concurrent-ruby (~> 1.0) + minitest (5.14.3) + rack (2.2.3) + redis (4.2.5) + sidekiq (6.1.3) + connection_pool (>= 2.2.2) + rack (~> 2.0) + redis (>= 4.2.0) + thread_safe (0.3.6) + tzinfo (1.2.9) + thread_safe (~> 0.1) + zeitwerk (2.4.2) + + GEM + remote: https://gem.repo3/ + specs: + sidekiq-pro (5.2.1) + connection_pool (>= 2.2.3) + sidekiq (>= 6.1.0) + + PLATFORMS + #{specific_local_platform} + + DEPENDENCIES + activesupport + sidekiq-pro! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "does not install newer versions or generate lockfile changes when running bundle install in frozen mode, and warns", :bundler => "< 3" do + initial_lockfile = lockfile + + bundle "config set --local frozen true" + bundle :install, :artifice => "compact_index" + + expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") + + expect(the_bundle).to include_gems("activesupport 6.0.3.4") + expect(the_bundle).not_to include_gems("activesupport 6.1.2.1") + expect(the_bundle).to include_gems("tzinfo 1.2.9") + expect(the_bundle).not_to include_gems("tzinfo 2.0.4") + expect(the_bundle).to include_gems("concurrent-ruby 1.1.8") + expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.9") + + expect(lockfile).to eq(initial_lockfile) + end + + it "fails when running bundle install in frozen mode", :bundler => "3" do + initial_lockfile = lockfile + + bundle "config set --local frozen true" + bundle :install, :artifice => "compact_index", :raise_on_error => false + + expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") + + expect(lockfile).to eq(initial_lockfile) + end + + it "splits sections and upgrades gems when running bundle update, and doesn't warn" do + bundle "update --all", :artifice => "compact_index" + expect(err).to be_empty + + expect(the_bundle).not_to include_gems("activesupport 6.0.3.4") + expect(the_bundle).to include_gems("activesupport 6.1.2.1") + expect(the_bundle).not_to include_gems("tzinfo 1.2.9") + expect(the_bundle).to include_gems("tzinfo 2.0.4") + expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8") + expect(the_bundle).to include_gems("concurrent-ruby 1.1.9") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo2/ + specs: + activesupport (6.1.2.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + concurrent-ruby (1.1.9) + connection_pool (2.2.3) + i18n (1.8.9) + concurrent-ruby (~> 1.0) + minitest (5.14.3) + rack (2.2.3) + redis (4.2.5) + sidekiq (6.1.3) + connection_pool (>= 2.2.2) + rack (~> 2.0) + redis (>= 4.2.0) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) + zeitwerk (2.4.2) + + GEM + remote: https://gem.repo3/ + specs: + sidekiq-pro (5.2.1) + connection_pool (>= 2.2.3) + sidekiq (>= 6.1.0) + + PLATFORMS + #{specific_local_platform} + + DEPENDENCIES + activesupport + sidekiq-pro! + + BUNDLED WITH + #{Bundler::VERSION} + L + end - it "does not find the dependency" do - bundle :install, :raise_on_error => false - expect(err).to include("Could not find gem 'rack', which is required by gem 'depends_on_rack', in any of the relevant sources") + it "upgrades the lockfile format and upgrades the requested gem when running bundle update with an argument" do + bundle "update concurrent-ruby", :artifice => "compact_index" + expect(err).to be_empty + + expect(the_bundle).to include_gems("activesupport 6.0.3.4") + expect(the_bundle).not_to include_gems("activesupport 6.1.2.1") + expect(the_bundle).to include_gems("tzinfo 1.2.9") + expect(the_bundle).not_to include_gems("tzinfo 2.0.4") + expect(the_bundle).to include_gems("concurrent-ruby 1.1.9") + expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo2/ + specs: + activesupport (6.0.3.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.2, >= 2.2.2) + concurrent-ruby (1.1.9) + connection_pool (2.2.3) + i18n (1.8.9) + concurrent-ruby (~> 1.0) + minitest (5.14.3) + rack (2.2.3) + redis (4.2.5) + sidekiq (6.1.3) + connection_pool (>= 2.2.2) + rack (~> 2.0) + redis (>= 4.2.0) + thread_safe (0.3.6) + tzinfo (1.2.9) + thread_safe (~> 0.1) + zeitwerk (2.4.2) + + GEM + remote: https://gem.repo3/ + specs: + sidekiq-pro (5.2.1) + connection_pool (>= 2.2.3) + sidekiq (>= 6.1.0) + + PLATFORMS + #{specific_local_platform} + + DEPENDENCIES + activesupport + sidekiq-pro! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "when a top-level gem has an indirect dependency present in the default source, but with a different version from the one resolved" do + before do + build_lib "activesupport", "7.0.0.alpha", :path => lib_path("rails/activesupport") + build_lib "rails", "7.0.0.alpha", :path => lib_path("rails") do |s| + s.add_dependency "activesupport", "= 7.0.0.alpha" + end + + build_repo gem_repo2 do + build_gem "activesupport", "6.1.2" + + build_gem "webpacker", "5.2.1" do |s| + s.add_dependency "activesupport", ">= 5.2" end end - context "and the dependency is in both the top-level and a pinned source" do - before do - update_repo gem_repo2 do - build_gem "rack", "1.0.0" - end + gemfile <<-G + source "https://gem.repo2" - update_repo gem_repo3 do - build_gem "rack", "1.0.0" do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" - end - end + gemspec :path => "#{lib_path("rails")}" + + gem "webpacker", "~> 5.0" + G + end + + it "installs all gems without warning" do + bundle :install, :artifice => "compact_index" + expect(err).not_to include("Warning") + expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", "rails 7.0.0.alpha") + expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", :source => "path@#{lib_path("rails/activesupport")}") + expect(the_bundle).to include_gems("rails 7.0.0.alpha", :source => "path@#{lib_path("rails")}") + end + end + + context "when a pinned gem has an indirect dependency with more than one level of indirection in the default source " do + before do + build_repo gem_repo3 do + build_gem "handsoap", "0.2.5.5" do |s| + s.add_dependency "nokogiri", ">= 1.2.3" end + end - it "installs the dependency from the top-level source without warning" do - bundle :install - expect(err).not_to include("Warning") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0") + update_repo gem_repo2 do + build_gem "nokogiri", "1.11.1" do |s| + s.add_dependency "racca", "~> 1.4" end + + build_gem "racca", "1.5.2" end + + gemfile <<-G + source "https://gem.repo2" + + source "https://gem.repo3" do + gem "handsoap" + end + + gem "nokogiri" + G + end + + it "installs from the default source without any warnings or errors and generates a proper lockfile" do + expected_lockfile = <<~L + GEM + remote: https://gem.repo2/ + specs: + nokogiri (1.11.1) + racca (~> 1.4) + racca (1.5.2) + + GEM + remote: https://gem.repo3/ + specs: + handsoap (0.2.5.5) + nokogiri (>= 1.2.3) + + PLATFORMS + #{specific_local_platform} + + DEPENDENCIES + handsoap! + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install --verbose", :artifice => "compact_index" + expect(err).not_to include("Warning") + expect(the_bundle).to include_gems("handsoap 0.2.5.5", "nokogiri 1.11.1", "racca 1.5.2") + expect(the_bundle).to include_gems("handsoap 0.2.5.5", :source => "remote3") + expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", :source => "remote2") + expect(lockfile).to eq(expected_lockfile) + + # Even if the gems are already installed + FileUtils.rm bundled_app_lock + bundle "install --verbose", :artifice => "compact_index" + expect(err).not_to include("Warning") + expect(the_bundle).to include_gems("handsoap 0.2.5.5", "nokogiri 1.11.1", "racca 1.5.2") + expect(the_bundle).to include_gems("handsoap 0.2.5.5", :source => "remote3") + expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", :source => "remote2") + expect(lockfile).to eq(expected_lockfile) end end @@ -389,14 +944,13 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "not_in_repo1", "1.0.0" end - gemfile <<-G - source "#{file_uri_for(gem_repo3)}" - gem "not_in_repo1", :source => "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false + source "https://gem.repo3" + gem "not_in_repo1", :source => "https://gem.repo1" G end it "does not install the gem" do - bundle :install, :raise_on_error => false expect(err).to include("Could not find gem 'not_in_repo1'") end end @@ -407,21 +961,24 @@ RSpec.describe "bundle install with gems on multiple sources" do lockfile <<-L GEM - remote: #{file_uri_for(gem_repo1)} - remote: #{file_uri_for(gem_repo3)} + remote: https://gem.repo1 + specs: + + GEM + remote: https://gem.repo3 specs: rack (0.9.1) PLATFORMS - ruby + #{specific_local_platform} DEPENDENCIES rack! L gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - source "#{file_uri_for(gem_repo3)}" do + source "https://gem.repo1" + source "https://gem.repo3" do gem 'rack' end G @@ -433,18 +990,97 @@ RSpec.describe "bundle install with gems on multiple sources" do end end + context "with a lockfile with aggregated rubygems sources" do + let(:aggregate_gem_section_lockfile) do + <<~L + GEM + remote: https://gem.repo1/ + remote: https://gem.repo3/ + specs: + rack (0.9.1) + + PLATFORMS + #{specific_local_platform} + + DEPENDENCIES + rack! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + let(:split_gem_section_lockfile) do + <<~L + GEM + remote: https://gem.repo1/ + specs: + + GEM + remote: https://gem.repo3/ + specs: + rack (0.9.1) + + PLATFORMS + #{specific_local_platform} + + DEPENDENCIES + rack! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + before do + build_repo gem_repo3 do + build_gem "rack", "0.9.1" + end + + gemfile <<-G + source "https://gem.repo1" + source "https://gem.repo3" do + gem 'rack' + end + G + + lockfile aggregate_gem_section_lockfile + end + + it "installs the existing lockfile but prints a warning", :bundler => "< 3" do + bundle "config set --local deployment true" + + bundle "install", :artifice => "compact_index" + + expect(lockfile).to eq(aggregate_gem_section_lockfile) + expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") + expect(the_bundle).to include_gems("rack 0.9.1", :source => "remote3") + end + + it "refuses to install the existing lockfile and prints an error", :bundler => "3" do + bundle "config set --local deployment true" + + bundle "install", :artifice => "compact_index", :raise_on_error =>false + + expect(lockfile).to eq(aggregate_gem_section_lockfile) + expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") + expect(out).to be_empty + end + end + context "with a path gem in the same Gemfile" do before do build_lib "foo" gemfile <<-G - gem "rack", :source => "#{file_uri_for(gem_repo1)}" + source "#{file_uri_for(gem_repo1)}" + gem "rack", :source => "https://gem.repo1" gem "foo", :path => "#{lib_path("foo-1.0")}" G end it "does not unlock the non-path gem after install" do - bundle :install + bundle :install, :artifice => "compact_index" bundle %(exec ruby -e 'puts "OK"') @@ -457,14 +1093,13 @@ RSpec.describe "bundle install with gems on multiple sources" do before do system_gems "rack-0.9.1" - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" # shoud come from repo1! + install_gemfile <<-G, :artifice => "compact_index" + source "https://gem.repo1" + gem "rack" # should come from repo1! G end it "installs the gems without any warning" do - bundle :install expect(err).not_to include("Warning") expect(the_bundle).to include_gems("rack 1.0.0") end @@ -480,14 +1115,14 @@ RSpec.describe "bundle install with gems on multiple sources" do # Installing this gemfile... gemfile <<-G - source '#{file_uri_for(gem_repo1)}' + source 'https://gem.repo1' gem 'rack' - gem 'foo', '~> 0.1', :source => '#{file_uri_for(gem_repo4)}' - gem 'bar', '~> 0.1', :source => '#{file_uri_for(gem_repo4)}' + gem 'foo', '~> 0.1', :source => 'https://gem.repo4' + gem 'bar', '~> 0.1', :source => 'https://gem.repo4' G - bundle "config --local path ../gems/system" - bundle :install + bundle "config set --local path ../gems/system" + bundle :install, :artifice => "compact_index" # And then we add some new versions... update_repo4 do @@ -498,11 +1133,11 @@ RSpec.describe "bundle install with gems on multiple sources" do it "allows them to be unlocked separately" do # And install this gemfile, updating only foo. - install_gemfile <<-G - source '#{file_uri_for(gem_repo1)}' + install_gemfile <<-G, :artifice => "compact_index" + source 'https://gem.repo1' gem 'rack' - gem 'foo', '~> 0.2', :source => '#{file_uri_for(gem_repo4)}' - gem 'bar', '~> 0.1', :source => '#{file_uri_for(gem_repo4)}' + gem 'foo', '~> 0.2', :source => 'https://gem.repo4' + gem 'bar', '~> 0.1', :source => 'https://gem.repo4' G # It should update foo to 0.2, but not the (locked) bar 0.1 @@ -513,17 +1148,20 @@ RSpec.describe "bundle install with gems on multiple sources" do context "re-resolving" do context "when there is a mix of sources in the gemfile" do before do - build_repo3 + build_repo gem_repo3 do + build_gem "rack" + end + build_lib "path1" build_lib "path2" build_git "git1" build_git "git2" - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, :artifice => "compact_index" + source "https://gem.repo1" gem "rails" - source "#{file_uri_for(gem_repo3)}" do + source "https://gem.repo3" do gem "rack" end @@ -535,7 +1173,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "does not re-resolve" do - bundle :install, :verbose => true + bundle :install, :artifice => "compact_index", :verbose => true expect(out).to include("using resolution from the lockfile") expect(out).not_to include("re-resolving dependencies") end @@ -544,27 +1182,24 @@ RSpec.describe "bundle install with gems on multiple sources" do context "when a gem is installed to system gems" do before do - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, :artifice => "compact_index" + source "https://gem.repo1" gem "rack" G end context "and the gemfile changes" do it "is still able to find that gem from remote sources" do - source_uri = file_uri_for(gem_repo1) - second_uri = file_uri_for(gem_repo4) - build_repo4 do build_gem "rack", "2.0.1.1.forked" build_gem "thor", "0.19.1.1.forked" end # When this gemfile is installed... - install_gemfile <<-G - source "#{source_uri}" + install_gemfile <<-G, :artifice => "compact_index" + source "https://gem.repo1" - source "#{second_uri}" do + source "https://gem.repo4" do gem "rack", "2.0.1.1.forked" gem "thor" end @@ -573,9 +1208,9 @@ RSpec.describe "bundle install with gems on multiple sources" do # Then we change the Gemfile by adding a version to thor gemfile <<-G - source "#{source_uri}" + source "https://gem.repo1" - source "#{second_uri}" do + source "https://gem.repo4" do gem "rack", "2.0.1.1.forked" gem "thor", "0.19.1.1.forked" end @@ -583,15 +1218,15 @@ RSpec.describe "bundle install with gems on multiple sources" do G # But we should still be able to find rack 2.0.1.1.forked and install it - bundle :install + bundle :install, :artifice => "compact_index" end end end describe "source changed to one containing a higher version of a dependency" do before do - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, :artifice => "compact_index" + source "https://gem.repo1" gem "rack" G @@ -608,23 +1243,109 @@ RSpec.describe "bundle install with gems on multiple sources" do s.add_dependency "bar", "=1.0.0" end - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + install_gemfile <<-G, :artifice => "compact_index" + source "https://gem.repo2" gem "rack" gemspec :path => "#{tmp.join("gemspec_test")}" G end - it "keeps the old version", :bundler => "2" do + it "conservatively installs the existing locked version" do expect(the_bundle).to include_gems("rack 1.0.0") end + end + + it "doesn't update version when a gem uses a source block but a higher version from another source is already installed locally" do + build_repo2 do + build_gem "example", "0.1.0" + end + + build_repo4 do + build_gem "example", "1.0.2" + end + + install_gemfile <<-G, :artifice => "compact_index" + source "https://gem.repo4" + + gem "example", :source => "https://gem.repo2" + G + + bundle "info example" + expect(out).to include("example (0.1.0)") + + system_gems "example-1.0.2", :path => default_bundle_path, :gem_repo => gem_repo4 + + bundle "update example --verbose", :artifice => "compact_index" + expect(out).not_to include("Using example 1.0.2") + expect(out).to include("Using example 0.1.0") + end + + it "fails inmmediately with a helpful error when a rubygems source does not exist and bundler/setup is required" do + gemfile <<-G + source "https://gem.repo1" + + source "https://gem.repo4" do + gem "example" + end + G + + simulate_bundler_version_when_missing_prerelease_default_gem_activation do + ruby <<~R, :raise_on_error => false + require 'bundler/setup' + R + end + + expect(last_command).to be_failure + expect(err).to include("Could not find gem 'example' in locally installed gems.") + end + + it "fails inmmediately with a helpful error when a non retriable network error happens while resolving sources" do + gemfile <<-G + source "https://gem.repo1" + + source "https://gem.repo4" do + gem "example" + end + G + + bundle "install", :artifice => nil, :raise_on_error => false + + expect(last_command).to be_failure + expect(err).to include("Could not reach host gem.repo4. Check your network connection and try again.") + end + + context "when an indirect dependency is available from multiple ambiguous sources", :bundler => "< 3" do + it "succeeds but warns, suggesting a source block" do + build_repo4 do + build_gem "depends_on_rack" do |s| + s.add_dependency "rack" + end + build_gem "rack" + end - it "installs the higher version in the new repo", :bundler => "3" do - expect(the_bundle).to include_gems("rack 1.2") + install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false + source "#{file_uri_for(gem_repo1)}" + + source "https://gem.repo4" do + gem "depends_on_rack" + end + + source "https://gem.repo1" do + gem "thin" + end + G + expect(err).to eq strip_whitespace(<<-EOS).strip + Warning: The gem 'rack' was found in multiple relevant sources. + * rubygems repository https://gem.repo1/ + * rubygems repository https://gem.repo4/ + You should add this gem to the source block for the source you wish it to be installed from. + EOS + expect(last_command).to be_success + expect(the_bundle).to be_locked end end - context "when a gem is available from multiple ambiguous sources", :bundler => "3" do + context "when an indirect dependency is available from multiple ambiguous sources", :bundler => "3" do it "raises, suggesting a source block" do build_repo4 do build_gem "depends_on_rack" do |s| @@ -633,21 +1354,93 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "rack" end - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo4)}" - source "#{file_uri_for(gem_repo1)}" do + install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false + source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo4" do + gem "depends_on_rack" + end + source "https://gem.repo1" do gem "thin" end - gem "depends_on_rack" G expect(last_command).to be_failure expect(err).to eq strip_whitespace(<<-EOS).strip The gem 'rack' was found in multiple relevant sources. - * rubygems repository #{file_uri_for(gem_repo1)}/ or installed locally - * rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally + * rubygems repository https://gem.repo1/ + * rubygems repository https://gem.repo4/ You must add this gem to the source block for the source you wish it to be installed from. EOS expect(the_bundle).not_to be_locked end end + + context "when upgrading a lockfile suffering from dependency confusion" do + before do + build_repo4 do + build_gem "mime-types", "3.0.0" + end + + build_repo2 do + build_gem "capybara", "2.5.0" do |s| + s.add_dependency "mime-types", ">= 1.16" + end + + build_gem "mime-types", "3.3.1" + end + + gemfile <<~G + source "https://gem.repo2" + + gem "capybara", "~> 2.5.0" + + source "https://gem.repo4" do + gem "mime-types", "~> 3.0" + end + G + + lockfile <<-L + GEM + remote: https://gem.repo2/ + remote: https://gem.repo4/ + specs: + capybara (2.5.0) + mime-types (>= 1.16) + mime-types (3.3.1) + + PLATFORMS + #{specific_local_platform} + + DEPENDENCIES + capybara (~> 2.5.0) + mime-types (~> 3.0)! + L + end + + it "upgrades the lockfile correctly" do + bundle "lock --update", :artifice => "compact_index" + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo2/ + specs: + capybara (2.5.0) + mime-types (>= 1.16) + + GEM + remote: https://gem.repo4/ + specs: + mime-types (3.0.0) + + PLATFORMS + #{specific_local_platform} + + DEPENDENCIES + capybara (~> 2.5.0) + mime-types (~> 3.0)! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end end diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb index 9e30fc4fd4..ce2823ce9a 100644 --- a/spec/bundler/install/gemfile/specific_platform_spec.rb +++ b/spec/bundler/install/gemfile/specific_platform_spec.rb @@ -20,7 +20,7 @@ RSpec.describe "bundle install with specific platforms" do ]) end - it "understands that a non-plaform specific gem in a old lockfile doesn't necessarily mean installing the non-specific variant" do + it "understands that a non-platform specific gem in a old lockfile doesn't necessarily mean installing the non-specific variant" do setup_multiplatform_gem system_gems "bundler-2.1.4" @@ -54,7 +54,7 @@ RSpec.describe "bundle install with specific platforms" do expect(the_bundle).to include_gem("google-protobuf 3.0.0.alpha.5.0.5.1 universal-darwin") end - it "understands that a non-plaform specific gem in a new lockfile locked only to RUBY doesn't necessarily mean installing the non-specific variant" do + it "understands that a non-platform specific gem in a new lockfile locked only to RUBY doesn't necessarily mean installing the non-specific variant" do setup_multiplatform_gem system_gems "bundler-2.1.4" @@ -87,7 +87,7 @@ RSpec.describe "bundle install with specific platforms" do expect(the_bundle).to include_gem("google-protobuf 3.0.0.alpha.5.0.5.1 universal-darwin") # make sure we're still only locked to ruby - lockfile_should_be <<-L + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -104,6 +104,50 @@ RSpec.describe "bundle install with specific platforms" do L end + it "doesn't discard previously installed platform specific gem and fall back to ruby on subsequent bundles" do + build_repo2 do + build_gem("libv8", "8.4.255.0") + build_gem("libv8", "8.4.255.0") {|s| s.platform = "universal-darwin" } + + build_gem("mini_racer", "1.0.0") do |s| + s.add_runtime_dependency "libv8" + end + end + + system_gems "bundler-2.1.4" + + # Consistent location to install and look for gems + bundle "config set --local path vendor/bundle", :env => { "BUNDLER_VERSION" => "2.1.4" } + + gemfile <<-G + source "https://localgemserver.test" + gem "libv8" + G + + # simulate lockfile created with old bundler, which only locks for ruby platform + lockfile <<-L + GEM + remote: https://localgemserver.test/ + specs: + libv8 (8.4.255.0) + + PLATFORMS + ruby + + DEPENDENCIES + libv8 + + BUNDLED WITH + 2.1.4 + L + + bundle "install --verbose", :artifice => "compact_index", :env => { "BUNDLER_VERSION" => "2.1.4", "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + expect(out).to include("Installing libv8 8.4.255.0 (universal-darwin)") + + bundle "add mini_racer --verbose", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + expect(out).to include("Using libv8 8.4.255.0 (universal-darwin)") + end + it "caches the universal-darwin gem when --all-platforms is passed and properly picks it up on further bundler invocations" do setup_multiplatform_gem gemfile(google_protobuf) @@ -129,6 +173,7 @@ RSpec.describe "bundle install with specific platforms" do git = build_git "pg_array_parser", "1.0" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "pg_array_parser", :git => "#{lib_path("pg_array_parser-1.0")}" G @@ -205,6 +250,38 @@ RSpec.describe "bundle install with specific platforms" do end end + it "installs sorbet-static, which does not provide a pure ruby variant, just fine on truffleruby", :truffleruby do + build_repo2 do + build_gem("sorbet-static", "0.5.6403") {|s| s.platform = "x86_64-linux" } + build_gem("sorbet-static", "0.5.6403") {|s| s.platform = "universal-darwin-20" } + end + + gemfile <<~G + source "#{file_uri_for(gem_repo2)}" + + gem "sorbet-static", "0.5.6403" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + sorbet-static (0.5.6403-universal-darwin-20) + sorbet-static (0.5.6403-x86_64-linux) + + PLATFORMS + ruby + + DEPENDENCIES + sorbet-static (= 0.5.6403) + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install --verbose" + end + private def setup_multiplatform_gem diff --git a/spec/bundler/install/gemfile_spec.rb b/spec/bundler/install/gemfile_spec.rb index ffbb2e3a61..0f8f1ecfa8 100644 --- a/spec/bundler/install/gemfile_spec.rb +++ b/spec/bundler/install/gemfile_spec.rb @@ -4,6 +4,8 @@ RSpec.describe "bundle install" do context "with duplicated gems" do it "will display a warning" do install_gemfile <<-G, :raise_on_error => false + source "#{file_uri_for(gem_repo1)}" + gem 'rails', '~> 4.0.0' gem 'rails', '~> 4.0.0' G @@ -54,6 +56,8 @@ RSpec.describe "bundle install" do context "with deprecated features" do it "reports that lib is an invalid option" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack", :lib => "rack" G @@ -86,6 +90,8 @@ RSpec.describe "bundle install" do context "with a Gemfile containing non-US-ASCII characters" do it "reads the Gemfile with the UTF-8 encoding by default" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + str = "Il était une fois ..." puts "The source encoding is: " + str.encoding.name G @@ -99,6 +105,8 @@ RSpec.describe "bundle install" do # NOTE: This works thanks to #eval interpreting the magic encoding comment install_gemfile <<-G # encoding: iso-8859-1 + source "#{file_uri_for(gem_repo1)}" + str = "Il #{"\xE9".dup.force_encoding("binary")}tait une fois ..." puts "The source encoding is: " + str.encoding.name G diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb index b5fca9c6ad..0cee69f702 100644 --- a/spec/bundler/install/gems/compact_index_spec.rb +++ b/spec/bundler/install/gems/compact_index_spec.rb @@ -80,8 +80,8 @@ RSpec.describe "compact index api" do G bundle :install, :artifice => "compact_index" - bundle "config --local deployment true" - bundle "config --local path vendor/bundle" + bundle "config set --local deployment true" + bundle "config set --local path vendor/bundle" bundle :install, :artifice => "compact_index" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" @@ -118,7 +118,7 @@ RSpec.describe "compact index api" do bundle :install, :artifice => "compact_index" - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install, :artifice => "compact_index" expect(the_bundle).to include_gems("rails 2.3.2") @@ -132,7 +132,7 @@ RSpec.describe "compact index api" do G bundle "install", :artifice => "compact_index" - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install, :artifice => "compact_index" expect(the_bundle).to include_gems("foo 1.0") @@ -366,31 +366,6 @@ The checksum of /versions does not match the checksum provided by the server! So expect(the_bundle).to include_gems "activesupport 1.2.3" end - it "considers all possible versions of dependencies from all api gem sources when using blocks", :bundler => "< 3" do - # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that - # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0 - # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other - # repo and installs it. - build_repo4 do - build_gem "activesupport", "1.2.0" - build_gem "somegem", "1.0.0" do |s| - s.add_dependency "activesupport", "1.2.3" # This version exists only in repo1 - end - end - - gemfile <<-G - source "#{source_uri}" - source "#{source_uri}/extra" do - gem 'somegem', '1.0.0' - end - G - - bundle :install, :artifice => "compact_index_extra_api" - - expect(the_bundle).to include_gems "somegem 1.0.0" - expect(the_bundle).to include_gems "activesupport 1.2.3" - end - it "prints API output properly with back deps" do build_repo2 do build_gem "back_deps" do |s| @@ -424,7 +399,7 @@ The checksum of /versions does not match the checksum provided by the server! So api_request_limit = low_api_request_limit_for(gem_repo2) - install_gemfile <<-G, :artifice => "compact_index_extra_missing", :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) + install_gemfile <<-G, :artifice => "compact_index_extra_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) source "#{source_uri}" source "#{source_uri}/extra" do gem "back_deps" @@ -446,7 +421,7 @@ The checksum of /versions does not match the checksum provided by the server! So api_request_limit = low_api_request_limit_for(gem_repo4) - install_gemfile <<-G, :artifice => "compact_index_extra_api_missing", :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) + install_gemfile <<-G, :artifice => "compact_index_extra_api_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) source "#{source_uri}" source "#{source_uri}/extra" do gem "back_deps" @@ -467,7 +442,7 @@ The checksum of /versions does not match the checksum provided by the server! So expect(the_bundle).to include_gems "foo 1.0" end - it "fetches again when more dependencies are found in subsequent sources using --deployment", :bundler => "< 3" do + it "fetches again when more dependencies are found in subsequent sources using deployment mode", :bundler => "< 3" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -482,8 +457,8 @@ The checksum of /versions does not match the checksum provided by the server! So G bundle :install, :artifice => "compact_index_extra" - - bundle "install --deployment", :artifice => "compact_index_extra" + bundle "config --set local deployment true" + bundle :install, :artifice => "compact_index_extra" expect(the_bundle).to include_gems "back_deps 1.0" end @@ -503,7 +478,7 @@ The checksum of /versions does not match the checksum provided by the server! So G bundle :install, :artifice => "compact_index_extra" - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install, :artifice => "compact_index_extra" expect(the_bundle).to include_gems "back_deps 1.0" end @@ -614,6 +589,17 @@ The checksum of /versions does not match the checksum provided by the server! So expect(the_bundle).to include_gems "rack 1.0.0" end + it "passes basic authentication details and strips out creds also in verbose mode" do + gemfile <<-G + source "#{basic_auth_source_uri}" + gem "rack" + G + + bundle :install, :verbose => true, :artifice => "compact_index_basic_authentication" + expect(out).not_to include("#{user}:#{password}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + it "strips http basic auth creds when warning about ambiguous sources", :bundler => "< 3" do gemfile <<-G source "#{basic_auth_source_uri}" @@ -815,6 +801,28 @@ The checksum of /versions does not match the checksum provided by the server! So expect(the_bundle).to include_gems "rack 1.0.0" end + it "performs full update if server endpoints serve partial content responses but don't have incremental content and provide no Etag" do + build_repo4 do + build_gem "rack", "0.9.1" + end + + install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + source "#{source_uri}" + gem 'rack', '0.9.1' + G + + update_repo4 do + build_gem "rack", "1.0.0" + end + + install_gemfile <<-G, :artifice => "compact_index_partial_update_no_etag_not_incremental", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + source "#{source_uri}" + gem 'rack', '1.0.0' + G + + expect(the_bundle).to include_gems "rack 1.0.0" + end + it "performs full update of compact index info cache if range is not satisfiable" do gemfile <<-G source "#{source_uri}" @@ -922,6 +930,7 @@ Either installing with `--full-index` or running `bundle update rails` should fi it "does not duplicate specs in the lockfile when updating and a dependency is not installed" do install_gemfile <<-G, :artifice => "compact_index" + source "#{file_uri_for(gem_repo1)}" source "#{source_uri}" do gem "rails" gem "activemerchant" diff --git a/spec/bundler/install/gems/dependency_api_spec.rb b/spec/bundler/install/gems/dependency_api_spec.rb index 5e0be89995..9738a75474 100644 --- a/spec/bundler/install/gems/dependency_api_spec.rb +++ b/spec/bundler/install/gems/dependency_api_spec.rb @@ -60,8 +60,8 @@ RSpec.describe "gemcutter's dependency API" do G bundle :install, :artifice => "endpoint" - bundle "config --local deployment true" - bundle "config --local path vendor/bundle" + bundle "config set --local deployment true" + bundle "config set --local path vendor/bundle" bundle :install, :artifice => "endpoint" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" @@ -98,7 +98,7 @@ RSpec.describe "gemcutter's dependency API" do bundle :install, :artifice => "endpoint" - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install, :artifice => "endpoint" expect(the_bundle).to include_gems("rails 2.3.2") @@ -112,7 +112,7 @@ RSpec.describe "gemcutter's dependency API" do G bundle "install", :artifice => "endpoint" - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle :install, :artifice => "endpoint" expect(the_bundle).to include_gems("foo 1.0") @@ -338,31 +338,6 @@ RSpec.describe "gemcutter's dependency API" do expect(the_bundle).to include_gems "activesupport 1.2.3" end - it "considers all possible versions of dependencies from all api gem sources using blocks" do - # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that - # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0 - # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other - # repo and installs it. - build_repo4 do - build_gem "activesupport", "1.2.0" - build_gem "somegem", "1.0.0" do |s| - s.add_dependency "activesupport", "1.2.3" # This version exists only in repo1 - end - end - - gemfile <<-G - source "#{source_uri}" - source "#{source_uri}/extra" do - gem 'somegem', '1.0.0' - end - G - - bundle :install, :artifice => "endpoint_extra_api" - - expect(the_bundle).to include_gems "somegem 1.0.0" - expect(the_bundle).to include_gems "activesupport 1.2.3" - end - it "prints API output properly with back deps" do build_repo2 do build_gem "back_deps" do |s| @@ -396,7 +371,7 @@ RSpec.describe "gemcutter's dependency API" do api_request_limit = low_api_request_limit_for(gem_repo2) - install_gemfile <<-G, :artifice => "endpoint_extra_missing", :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) + install_gemfile <<-G, :artifice => "endpoint_extra_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) source "#{source_uri}" source "#{source_uri}/extra" gem "back_deps" @@ -417,7 +392,7 @@ RSpec.describe "gemcutter's dependency API" do api_request_limit = low_api_request_limit_for(gem_repo2) - install_gemfile <<-G, :artifice => "endpoint_extra_missing", :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) + install_gemfile <<-G, :artifice => "endpoint_extra_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) source "#{source_uri}" source "#{source_uri}/extra" do gem "back_deps" @@ -438,7 +413,7 @@ RSpec.describe "gemcutter's dependency API" do expect(the_bundle).to include_gems "foo 1.0" end - it "fetches again when more dependencies are found in subsequent sources using --deployment", :bundler => "< 3" do + it "fetches again when more dependencies are found in subsequent sources using deployment mode", :bundler => "< 3" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -453,8 +428,8 @@ RSpec.describe "gemcutter's dependency API" do G bundle :install, :artifice => "endpoint_extra" - - bundle "install --deployment", :artifice => "endpoint_extra" + bundle "config set --local deployment true" + bundle :install, :artifice => "endpoint_extra" expect(the_bundle).to include_gems "back_deps 1.0" end @@ -474,8 +449,7 @@ RSpec.describe "gemcutter's dependency API" do G bundle :install, :artifice => "endpoint_extra" - - bundle "config --local deployment true" + bundle "config set --local deployment true" bundle "install", :artifice => "endpoint_extra" expect(the_bundle).to include_gems "back_deps 1.0" end @@ -586,6 +560,17 @@ RSpec.describe "gemcutter's dependency API" do expect(the_bundle).to include_gems "rack 1.0.0" end + it "passes basic authentication details and strips out creds also in verbose mode" do + gemfile <<-G + source "#{basic_auth_source_uri}" + gem "rack" + G + + bundle :install, :verbose => true, :artifice => "endpoint_basic_authentication" + expect(out).not_to include("#{user}:#{password}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + it "strips http basic authentication creds for modern index" do gemfile <<-G source "#{basic_auth_source_uri}" @@ -630,6 +615,22 @@ RSpec.describe "gemcutter's dependency API" do expect(the_bundle).to include_gems "rack 1.0.0" end + describe "with host including dashes" do + before do + gemfile <<-G + source "http://local-gemserver.test" + gem "rack" + G + end + + it "reads authentication details from a valid ENV variable" do + bundle :install, :artifice => "endpoint_strict_basic_authentication", :env => { "BUNDLE_LOCAL___GEMSERVER__TEST" => "#{user}:#{password}" } + + expect(out).to include("Fetching gem metadata from http://local-gemserver.test") + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + describe "with authentication details in bundle config" do before do gemfile <<-G diff --git a/spec/bundler/install/gems/flex_spec.rb b/spec/bundler/install/gems/flex_spec.rb index 7ab0ded26d..f9b374cf01 100644 --- a/spec/bundler/install/gems/flex_spec.rb +++ b/spec/bundler/install/gems/flex_spec.rb @@ -229,14 +229,27 @@ RSpec.describe "bundle flex_install" do G end - it "does something" do - expect do - bundle "install", :raise_on_error => false - end.not_to change { File.read(bundled_app_lock) } - - expect(err).to include("rack = 0.9.1") - expect(err).to include("locked at 1.0.0") - expect(err).to include("bundle update rack") + it "should work when you install" do + bundle "install" + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + rack (0.9.1) + rack-obama (1.0) + rack + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack (= 0.9.1) + rack-obama + + BUNDLED WITH + #{Bundler::VERSION} + L end it "should work when you update" do @@ -245,37 +258,7 @@ RSpec.describe "bundle flex_install" do end describe "when adding a new source" do - it "updates the lockfile", :bundler => "< 3" do - build_repo2 - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - G - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - source "#{file_uri_for(gem_repo2)}" - gem "rack" - G - - lockfile_should_be <<-L - GEM - remote: #{file_uri_for(gem_repo1)}/ - remote: #{file_uri_for(gem_repo2)}/ - specs: - rack (1.0.0) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - rack - - BUNDLED WITH - #{Bundler::VERSION} - L - end - - it "updates the lockfile", :bundler => "3" do + it "updates the lockfile" do build_repo2 install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -289,24 +272,24 @@ RSpec.describe "bundle flex_install" do gem "rack" G - lockfile_should_be <<-L - GEM - remote: #{file_uri_for(gem_repo1)}/ - specs: - rack (1.0.0) + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + rack (1.0.0) - GEM - remote: #{file_uri_for(gem_repo2)}/ - specs: + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: - PLATFORMS - #{lockfile_platforms} + PLATFORMS + #{lockfile_platforms} - DEPENDENCIES - rack + DEPENDENCIES + rack - BUNDLED WITH - #{Bundler::VERSION} + BUNDLED WITH + #{Bundler::VERSION} L end end diff --git a/spec/bundler/install/gems/native_extensions_spec.rb b/spec/bundler/install/gems/native_extensions_spec.rb index 8a4de3cf92..d5cafcfc2c 100644 --- a/spec/bundler/install/gems/native_extensions_spec.rb +++ b/spec/bundler/install/gems/native_extensions_spec.rb @@ -78,6 +78,7 @@ RSpec.describe "installing a gem with native extensions", :ruby_repo do bundle "config set build.c_extension --with-c_extension=hello" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "c_extension", :git => #{lib_path("c_extension-1.0").to_s.dump} G @@ -126,11 +127,13 @@ RSpec.describe "installing a gem with native extensions", :ruby_repo do # 1st time, require only one gem -- only one of the extensions gets built. install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "c_extension_one", :git => #{lib_path("gems").to_s.dump} G # 2nd time, require both gems -- we need both extensions to be built now. install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "c_extension_one", :git => #{lib_path("gems").to_s.dump} gem "c_extension_two", :git => #{lib_path("gems").to_s.dump} G @@ -171,6 +174,7 @@ RSpec.describe "installing a gem with native extensions", :ruby_repo do bundle "config set build.c_extension --with-c_extension=hello --with-c_extension_bundle-dir=hola" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "c_extension", :git => #{lib_path("c_extension-1.0").to_s.dump} G diff --git a/spec/bundler/install/gems/post_install_spec.rb b/spec/bundler/install/gems/post_install_spec.rb index 3f6d7ce42c..7426f54877 100644 --- a/spec/bundler/install/gems/post_install_spec.rb +++ b/spec/bundler/install/gems/post_install_spec.rb @@ -33,7 +33,7 @@ RSpec.describe "bundle install" do end end - context "when a dependecy includes a post install message" do + context "when a dependency includes a post install message" do it "should display the post install message" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" diff --git a/spec/bundler/install/gems/resolving_spec.rb b/spec/bundler/install/gems/resolving_spec.rb index 2d0ac5a805..94fac0052c 100644 --- a/spec/bundler/install/gems/resolving_spec.rb +++ b/spec/bundler/install/gems/resolving_spec.rb @@ -3,6 +3,32 @@ RSpec.describe "bundle install with install-time dependencies" do before do build_repo2 do + build_gem "with_implicit_rake_dep" do |s| + s.extensions << "Rakefile" + s.write "Rakefile", <<-RUBY + task :default do + path = File.expand_path("../lib", __FILE__) + FileUtils.mkdir_p(path) + File.open("\#{path}/implicit_rake_dep.rb", "w") do |f| + f.puts "IMPLICIT_RAKE_DEP = 'YES'" + end + end + RUBY + end + + build_gem "another_implicit_rake_dep" do |s| + s.extensions << "Rakefile" + s.write "Rakefile", <<-RUBY + task :default do + path = File.expand_path("../lib", __FILE__) + FileUtils.mkdir_p(path) + File.open("\#{path}/another_implicit_rake_dep.rb", "w") do |f| + f.puts "ANOTHER_IMPLICIT_RAKE_DEP = 'YES'" + end + end + RUBY + end + # Test complicated gem dependencies for install build_gem "net_a" do |s| s.add_dependency "net_b" @@ -55,6 +81,25 @@ RSpec.describe "bundle install with install-time dependencies" do expect(out).to eq("YES\nYES") end + it "installs gems with implicit rake dependencies without rake previously installed" do + with_path_as("") do + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "with_implicit_rake_dep" + gem "another_implicit_rake_dep" + gem "rake" + G + end + + run <<-R + require 'implicit_rake_dep' + require 'another_implicit_rake_dep' + puts IMPLICIT_RAKE_DEP + puts ANOTHER_IMPLICIT_RAKE_DEP + R + expect(out).to eq("YES\nYES") + end + it "installs gems with a dependency with no type" do skip "incorrect data check error" if Gem.win_platform? @@ -160,10 +205,6 @@ RSpec.describe "bundle install with install-time dependencies" do describe "when a required ruby version" do context "allows only an older version" do - before do - skip "gem not found" if Gem.win_platform? - end - it "installs the older version" do build_repo2 do build_gem "rack", "1.2" do |s| diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index 0a0f992704..db16a1b0e1 100644 --- a/spec/bundler/install/gems/standalone_spec.rb +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -94,7 +94,7 @@ RSpec.shared_examples "bundle install --standalone" do source "#{file_uri_for(gem_repo1)}" gem "rails" G - bundle "config --local path #{bundled_app("bundle")}" + bundle "config set --local path #{bundled_app("bundle")}" bundle :install, :standalone => true, :dir => cwd end @@ -108,9 +108,78 @@ RSpec.shared_examples "bundle install --standalone" do include_examples "common functionality" end + describe "with default gems and a lockfile", :ruby_repo do + before do + skip "does not work on rubygems versions where `--install_dir` doesn't respect --default" unless Gem::Installer.for_spec(loaded_gemspec, :install_dir => "/foo").default_spec_file == "/foo/specifications/default/bundler-#{Bundler::VERSION}.gemspec" # Since rubygems 3.2.0.rc.2 + skip "does not work on old rubies because the realworld gems that need to be installed don't support them" if RUBY_VERSION < "2.7.0" + + realworld_system_gems "fiddle --version 1.0.6", "tsort --version 0.1.0" + + necessary_system_gems = ["optparse --version 0.1.1", "psych --version 3.3.2", "yaml --version 0.1.1", "logger --version 1.4.3", "etc --version 1.2.0", "stringio --version 3.0.0"] + necessary_system_gems += ["shellwords --version 0.1.0", "base64 --version 0.1.0", "resolv --version 0.2.1"] if Gem.rubygems_version < Gem::Version.new("3.3.3.a") + realworld_system_gems(*necessary_system_gems, :path => scoped_gem_path(bundled_app("bundle"))) + + build_gem "foo", "1.0.0", :to_system => true, :default => true do |s| + s.add_dependency "bar" + end + + build_gem "bar", "1.0.0", :to_system => true, :default => true + + build_repo4 do + build_gem "foo", "1.0.0" do |s| + s.add_dependency "bar" + end + + build_gem "bar", "1.0.0" + end + + gemfile <<-G + source "https://gem.repo4" + gem "foo" + G + + bundle "lock", :dir => cwd, :artifice => "compact_index" + end + + it "works" do + bundle "config set --local path #{bundled_app("bundle")}" + bundle :install, :standalone => true, :dir => cwd, :artifice => "compact_index", :env => { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s } + end + end + + describe "with Gemfiles using path sources and resulting bundle moved to a folder hierarchy with different nesting" do + before do + build_lib "minitest", "1.0.0", :path => lib_path("minitest") + + Dir.mkdir bundled_app("app") + + gemfile bundled_app("app/Gemfile"), <<-G + source "#{file_uri_for(gem_repo1)}" + gem "minitest", :path => "#{lib_path("minitest")}" + G + + bundle "install", :standalone => true, :dir => bundled_app("app") + + Dir.mkdir tmp("one_more_level") + FileUtils.mv bundled_app, tmp("one_more_level") + end + + it "also works" do + ruby <<-RUBY, :dir => tmp("one_more_level/bundled_app/app") + require "./bundle/bundler/setup" + + require "minitest" + puts MINITEST + RUBY + + expect(out).to eq("1.0.0") + expect(err).to be_empty + end + end + describe "with gems with native extension", :ruby_repo do before do - bundle "config --local path #{bundled_app("bundle")}" + bundle "config set --local path #{bundled_app("bundle")}" install_gemfile <<-G, :standalone => true, :dir => cwd source "#{file_uri_for(gem_repo1)}" gem "very_simple_binary" @@ -120,7 +189,7 @@ RSpec.shared_examples "bundle install --standalone" do it "generates a bundle/bundler/setup.rb with the proper paths" do expected_path = bundled_app("bundle/bundler/setup.rb") extension_line = File.read(expected_path).each_line.find {|line| line.include? "/extensions/" }.strip - expect(extension_line).to start_with '$:.unshift File.expand_path("#{path}/../#{ruby_engine}/#{ruby_version}/extensions/' + expect(extension_line).to start_with '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{RbConfig::CONFIG["ruby_version"]}/extensions/' expect(extension_line).to end_with '/very_simple_binary-1.0")' end end @@ -144,8 +213,9 @@ RSpec.shared_examples "bundle install --standalone" do end G end - bundle "config --local path #{bundled_app("bundle")}" + bundle "config set --local path #{bundled_app("bundle")}" install_gemfile <<-G, :standalone => true, :dir => cwd, :raise_on_error => false + source "#{file_uri_for(gem_repo1)}" gem "bar", :git => "#{lib_path("bar-1.0")}" G end @@ -165,7 +235,7 @@ RSpec.shared_examples "bundle install --standalone" do gem "rails" gem "devise", :git => "#{lib_path("devise-1.0")}" G - bundle "config --local path #{bundled_app("bundle")}" + bundle "config set --local path #{bundled_app("bundle")}" bundle :install, :standalone => true, :dir => cwd end @@ -193,7 +263,7 @@ RSpec.shared_examples "bundle install --standalone" do gem "rack-test" end G - bundle "config --local path #{bundled_app("bundle")}" + bundle "config set --local path #{bundled_app("bundle")}" bundle :install, :standalone => true, :dir => cwd end @@ -207,7 +277,7 @@ RSpec.shared_examples "bundle install --standalone" do include_examples "common functionality" it "allows creating a standalone file with limited groups" do - bundle "config --local path #{bundled_app("bundle")}" + bundle "config set --local path #{bundled_app("bundle")}" bundle :install, :standalone => "default", :dir => cwd load_error_ruby <<-RUBY, "spec" @@ -224,8 +294,8 @@ RSpec.shared_examples "bundle install --standalone" do end it "allows `without` configuration to limit the groups used in a standalone" do - bundle "config --local path #{bundled_app("bundle")}" - bundle "config --local without test" + bundle "config set --local path #{bundled_app("bundle")}" + bundle "config set --local without test" bundle :install, :standalone => true, :dir => cwd load_error_ruby <<-RUBY, "spec" @@ -242,7 +312,7 @@ RSpec.shared_examples "bundle install --standalone" do end it "allows `path` configuration to change the location of the standalone bundle" do - bundle "config --local path path/to/bundle" + bundle "config set --local path path/to/bundle" bundle "install", :standalone => true, :dir => cwd ruby <<-RUBY @@ -257,9 +327,9 @@ RSpec.shared_examples "bundle install --standalone" do end it "allows `without` to limit the groups used in a standalone" do - bundle "config --local without test" + bundle "config set --local without test" bundle :install, :dir => cwd - bundle "config --local path #{bundled_app("bundle")}" + bundle "config set --local path #{bundled_app("bundle")}" bundle :install, :standalone => true, :dir => cwd load_error_ruby <<-RUBY, "spec" @@ -285,7 +355,7 @@ RSpec.shared_examples "bundle install --standalone" do source "#{source_uri}" gem "rails" G - bundle "config --local path #{bundled_app("bundle")}" + bundle "config set --local path #{bundled_app("bundle")}" bundle :install, :standalone => true, :artifice => "endpoint", :dir => cwd end @@ -306,7 +376,7 @@ RSpec.shared_examples "bundle install --standalone" do source "#{file_uri_for(gem_repo1)}" gem "rails" G - bundle "config --local path #{bundled_app("bundle")}" + bundle "config set --local path #{bundled_app("bundle")}" bundle :install, :standalone => true, :binstubs => true, :dir => cwd end diff --git a/spec/bundler/install/gems/sudo_spec.rb b/spec/bundler/install/gems/sudo_spec.rb index ff73b4a1fa..3e5d38ea4c 100644 --- a/spec/bundler/install/gems/sudo_spec.rb +++ b/spec/bundler/install/gems/sudo_spec.rb @@ -49,8 +49,23 @@ RSpec.describe "when using sudo", :sudo => true do end it "installs rake and a gem dependent on rake in the same session" do + build_repo2 do + build_gem "another_implicit_rake_dep" do |s| + s.extensions << "Rakefile" + s.write "Rakefile", <<-RUBY + task :default do + path = File.expand_path("../lib", __FILE__) + FileUtils.mkdir_p(path) + File.open("\#{path}/another_implicit_rake_dep.rb", "w") do |f| + f.puts "ANOTHER_IMPLICIT_RAKE_DEP = 'YES'" + end + end + RUBY + end + end + gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "#{file_uri_for(gem_repo2)}" gem "rake" gem "another_implicit_rake_dep" G diff --git a/spec/bundler/install/gemspecs_spec.rb b/spec/bundler/install/gemspecs_spec.rb index 0db1f1985b..3684d8749d 100644 --- a/spec/bundler/install/gemspecs_spec.rb +++ b/spec/bundler/install/gemspecs_spec.rb @@ -21,6 +21,7 @@ RSpec.describe "bundle install" do build_lib "yaml_spec", :gemspec => :yaml install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'yaml_spec', :path => "#{lib_path("yaml_spec-1.0")}" G expect(err).to be_empty @@ -28,13 +29,13 @@ RSpec.describe "bundle install" do end it "should use gemspecs in the system cache when available" do - skip "weird incompatible marshal file format error" if Gem.win_platform? - gemfile <<-G source "http://localtestserver.gem" gem 'rack' G + system_gems "rack-1.0.0", :path => default_bundle_path + FileUtils.mkdir_p "#{default_bundle_path}/specifications" File.open("#{default_bundle_path}/specifications/rack-1.0.0.gemspec", "w+") do |f| spec = Gem::Specification.new do |s| @@ -45,7 +46,7 @@ RSpec.describe "bundle install" do f.write spec.to_ruby end bundle :install, :artifice => "endpoint_marshal_fail" # force gemspec load - expect(the_bundle).to include_gems "activesupport 2.3.2" + expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.2" end it "does not hang when gemspec has incompatible encoding" do @@ -59,6 +60,7 @@ RSpec.describe "bundle install" do G install_gemfile <<-G, :env => { "LANG" => "C" } + source "#{file_uri_for(gem_repo1)}" gemspec G @@ -84,6 +86,7 @@ RSpec.describe "bundle install" do G install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gemspec G @@ -98,6 +101,7 @@ RSpec.describe "bundle install" do install_gemfile <<-G ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby' + source "#{file_uri_for(gem_repo1)}" gemspec G expect(the_bundle).to include_gems "foo 1.0" @@ -111,6 +115,7 @@ RSpec.describe "bundle install" do install_gemfile <<-G, :raise_on_error => false ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{RUBY_PATCHLEVEL}' + source "#{file_uri_for(gem_repo1)}" gemspec G expect(the_bundle).to include_gems "foo 1.0" @@ -125,6 +130,7 @@ RSpec.describe "bundle install" do install_gemfile <<-G, :raise_on_error => false ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{patchlevel}' + source "#{file_uri_for(gem_repo1)}" gemspec G @@ -142,6 +148,7 @@ RSpec.describe "bundle install" do install_gemfile <<-G, :raise_on_error => false ruby '#{version}', :engine_version => '#{version}', :engine => 'ruby' + source "#{file_uri_for(gem_repo1)}" gemspec G diff --git a/spec/bundler/install/git_spec.rb b/spec/bundler/install/git_spec.rb index 61eaee4a84..d43aacee7e 100644 --- a/spec/bundler/install/git_spec.rb +++ b/spec/bundler/install/git_spec.rb @@ -6,6 +6,7 @@ RSpec.describe "bundle install" do build_git "foo", "1.0", :path => lib_path("foo") install_gemfile <<-G, :verbose => true + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{file_uri_for(lib_path("foo"))}" G @@ -13,10 +14,11 @@ RSpec.describe "bundle install" do expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}" end - it "displays the correct default branch" do + it "displays the correct default branch", :git => ">= 2.28.0" do build_git "foo", "1.0", :path => lib_path("foo"), :default_branch => "main" install_gemfile <<-G, :verbose => true + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{file_uri_for(lib_path("foo"))}" G @@ -34,6 +36,7 @@ RSpec.describe "bundle install" do update_git "foo", "3.0", :path => lib_path("foo"), :gemspec => true install_gemfile <<-G, :verbose => true + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{file_uri_for(lib_path("foo"))}", :ref => "master~2" G @@ -51,6 +54,7 @@ RSpec.describe "bundle install" do revision = build_git("foo").ref_for("HEAD") gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :group => :development G @@ -68,8 +72,8 @@ RSpec.describe "bundle install" do foo! L - bundle "config --local path vendor/bundle" - bundle "config --local without development" + bundle "config set --local path vendor/bundle" + bundle "config set --local without development" bundle :install expect(out).to include("Bundle complete!") diff --git a/spec/bundler/install/global_cache_spec.rb b/spec/bundler/install/global_cache_spec.rb index f3609715fb..afa0ff76c1 100644 --- a/spec/bundler/install/global_cache_spec.rb +++ b/spec/bundler/install/global_cache_spec.rb @@ -37,6 +37,18 @@ RSpec.describe "global gem caching" do expect(the_bundle).to include_gems "rack 1.0.0" end + it "shows a proper error message if a cached gem is corrupted" do + source_global_cache.mkpath + FileUtils.touch(source_global_cache("rack-1.0.0.gem")) + + install_gemfile <<-G, :artifice => "compact_index_no_gem", :raise_on_error => false + source "#{source}" + gem "rack" + G + + expect(err).to include("Gem::Package::FormatError: package metadata is missing in #{source_global_cache("rack-1.0.0.gem")}") + end + describe "when the same gem from different sources is installed" do it "should use the appropriate one from the global cache" do install_gemfile <<-G, :artifice => "compact_index" @@ -113,6 +125,8 @@ RSpec.describe "global gem caching" do expect(source2_global_cache("rack-0.9.1.gem")).to exist bundle :install, :artifice => "compact_index_no_gem", :raise_on_error => false expect(err).to include("Internal Server Error 500") + expect(err).not_to include("ERROR REPORT TEMPLATE") + # rack 1.0.0 is not installed and rack 0.9.1 is not expect(the_bundle).not_to include_gems "rack 1.0.0" expect(the_bundle).not_to include_gems "rack 0.9.1" @@ -126,6 +140,8 @@ RSpec.describe "global gem caching" do expect(source2_global_cache("rack-0.9.1.gem")).to exist bundle :install, :artifice => "compact_index_no_gem", :raise_on_error => false expect(err).to include("Internal Server Error 500") + expect(err).not_to include("ERROR REPORT TEMPLATE") + # rack 0.9.1 is not installed and rack 1.0.0 is not expect(the_bundle).not_to include_gems "rack 0.9.1" expect(the_bundle).not_to include_gems "rack 1.0.0" @@ -177,8 +193,11 @@ RSpec.describe "global gem caching" do bundle :install, :artifice => "compact_index_no_gem", :dir => bundled_app2 # activesupport is installed and both are in the global cache - expect(the_bundle).not_to include_gems "rack 1.0.0", :dir => bundled_app2 - expect(the_bundle).to include_gems "activesupport 2.3.5", :dir => bundled_app2 + simulate_bundler_version_when_missing_prerelease_default_gem_activation do + expect(the_bundle).not_to include_gems "rack 1.0.0", :dir => bundled_app2 + expect(the_bundle).to include_gems "activesupport 2.3.5", :dir => bundled_app2 + end + expect(source_global_cache("rack-1.0.0.gem")).to exist expect(source_global_cache("activesupport-2.3.5.gem")).to exist end diff --git a/spec/bundler/install/path_spec.rb b/spec/bundler/install/path_spec.rb index a05467db12..b0392c4ed2 100644 --- a/spec/bundler/install/path_spec.rb +++ b/spec/bundler/install/path_spec.rb @@ -14,14 +14,14 @@ RSpec.describe "bundle install" do end it "does not use available system gems with `vendor/bundle" do - bundle "config --local path vendor/bundle" + bundle "config set --local path vendor/bundle" bundle :install expect(the_bundle).to include_gems "rack 1.0.0" end it "uses system gems with `path.system` configured with more priority than `path`" do - bundle "config --local path.system true" - bundle "config --global path vendor/bundle" + bundle "config set --local path.system true" + bundle "config set --global path vendor/bundle" bundle :install run "require 'rack'", :raise_on_error => false expect(out).to include("FAIL") @@ -31,7 +31,7 @@ RSpec.describe "bundle install" do dir = bundled_app("bun++dle") dir.mkpath - bundle "config --local path #{dir.join("vendor/bundle")}" + bundle "config set --local path #{dir.join("vendor/bundle")}" bundle :install, :dir => dir expect(out).to include("installed into `./vendor/bundle`") @@ -39,7 +39,7 @@ RSpec.describe "bundle install" do end it "prints a message to let the user know where gems where installed" do - bundle "config --local path vendor/bundle" + bundle "config set --local path vendor/bundle" bundle :install expect(out).to include("gems are installed into `./vendor/bundle`") end @@ -109,7 +109,7 @@ RSpec.describe "bundle install" do context "when set via #{type}" do it "installs gems to a path if one is specified" do set_bundle_path(type, bundled_app("vendor2").to_s) - bundle "config --local path vendor/bundle" + bundle "config set --local path vendor/bundle" bundle :install expect(vendored_gems("gems/rack-1.0.0")).to be_directory @@ -159,7 +159,7 @@ RSpec.describe "bundle install" do end it "sets BUNDLE_PATH as the first argument to bundle install" do - bundle "config --local path ./vendor/bundle" + bundle "config set --local path ./vendor/bundle" bundle :install expect(vendored_gems("gems/rack-1.0.0")).to be_directory @@ -169,7 +169,7 @@ RSpec.describe "bundle install" do it "disables system gems when passing a path to install" do # This is so that vendored gems can be distributed to others build_gem "rack", "1.1.0", :to_system => true - bundle "config --local path ./vendor/bundle" + bundle "config set --local path ./vendor/bundle" bundle :install expect(vendored_gems("gems/rack-1.0.0")).to be_directory @@ -186,7 +186,7 @@ RSpec.describe "bundle install" do gem "very_simple_binary" G - bundle "config --local path ./vendor/bundle" + bundle "config set --local path ./vendor/bundle" bundle :install expect(vendored_gems("gems/very_simple_binary-1.0")).to be_directory @@ -198,7 +198,7 @@ RSpec.describe "bundle install" do run "require 'very_simple_binary_c'", :raise_on_error => false expect(err).to include("Bundler::GemNotFound") - bundle "config --local path ./vendor/bundle" + bundle "config set --local path ./vendor/bundle" bundle :install expect(vendored_gems("gems/very_simple_binary-1.0")).to be_directory @@ -218,7 +218,7 @@ RSpec.describe "bundle install" do gem "rack" G - bundle "config --local path bundle" + bundle "config set --local path bundle" bundle :install, :raise_on_error => false expect(err).to include("file already exists") end diff --git a/spec/bundler/install/prereleases_spec.rb b/spec/bundler/install/prereleases_spec.rb index c3f968ad70..629eb89dac 100644 --- a/spec/bundler/install/prereleases_spec.rb +++ b/spec/bundler/install/prereleases_spec.rb @@ -38,7 +38,11 @@ RSpec.describe "bundle install" do describe "when prerelease gems are not available" do it "still works" do - build_repo3 + build_repo gem_repo3 do + build_gem "rack" + end + FileUtils.rm_rf Dir[gem_repo3("prerelease*")] + install_gemfile <<-G source "#{file_uri_for(gem_repo3)}" gem "rack" diff --git a/spec/bundler/install/redownload_spec.rb b/spec/bundler/install/redownload_spec.rb index 0eae615c59..a936b2b536 100644 --- a/spec/bundler/install/redownload_spec.rb +++ b/spec/bundler/install/redownload_spec.rb @@ -33,6 +33,7 @@ RSpec.describe "bundle install" do before do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G end diff --git a/spec/bundler/install/yanked_spec.rb b/spec/bundler/install/yanked_spec.rb index 6d3065a836..c5f3d788ba 100644 --- a/spec/bundler/install/yanked_spec.rb +++ b/spec/bundler/install/yanked_spec.rb @@ -70,4 +70,35 @@ RSpec.context "when using gem before installing" do expect(err).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.") expect(err).to_not include("You'll need to update your bundle to a different version of rack (0.9.1) that hasn't been removed in order to install.") end + + it "does not suggest the author has yanked the gem when using more than one gem, but shows all gems that couldn't be found in the source" do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack", "0.9.1" + gem "rack_middleware", "1.0" + G + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo1)} + specs: + rack (0.9.1) + rack_middleware (1.0) + + PLATFORMS + ruby + + DEPENDENCIES + rack (= 0.9.1) + rack_middleware (1.0) + L + + bundle :list, :raise_on_error => false + + expect(err).to include("Could not find rack-0.9.1, rack_middleware-1.0 in any of the sources") + expect(err).to include("Install missing gems with `bundle install`.") + expect(err).to_not include("Your bundle is locked to rack (0.9.1), but that version could not be found in any of the sources listed in your Gemfile.") + expect(err).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.") + expect(err).to_not include("You'll need to update your bundle to a different version of rack (0.9.1) that hasn't been removed in order to install.") + end end diff --git a/spec/bundler/lock/git_spec.rb b/spec/bundler/lock/git_spec.rb index 14b80483ee..56db5d8305 100644 --- a/spec/bundler/lock/git_spec.rb +++ b/spec/bundler/lock/git_spec.rb @@ -5,6 +5,7 @@ RSpec.describe "bundle lock with git gems" do build_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', :git => "#{lib_path("foo-1.0")}" G end diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb index d3591cd62a..8befb0d400 100644 --- a/spec/bundler/lock/lockfile_spec.rb +++ b/spec/bundler/lock/lockfile_spec.rb @@ -22,7 +22,7 @@ RSpec.describe "the lockfile format" do gem "rack" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -70,7 +70,7 @@ RSpec.describe "the lockfile format" do gem "rack" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -112,7 +112,7 @@ RSpec.describe "the lockfile format" do gem "rack" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -149,7 +149,7 @@ RSpec.describe "the lockfile format" do gem "rack", "> 0" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -166,9 +166,10 @@ RSpec.describe "the lockfile format" do G end - it "warns if the current is older than lockfile's bundler version" do - current_version = Bundler::VERSION - newer_minor = bump_minor(current_version) + it "warns if the current version is older than lockfile's bundler version, and locked version is a final release" do + current_version = "999.998.999" + system_gems "bundler-#{current_version}" + newer_minor = "999.999.0" lockfile <<-L GEM @@ -186,20 +187,115 @@ RSpec.describe "the lockfile format" do #{newer_minor} L - install_gemfile <<-G + install_gemfile <<-G, :env => { "BUNDLER_VERSION" => current_version } source "#{file_uri_for(gem_repo2)}" gem "rack" G - pre_flag = prerelease?(newer_minor) ? " --pre" : "" warning_message = "the running version of Bundler (#{current_version}) is older " \ "than the version that created the lockfile (#{newer_minor}). " \ "We suggest you to upgrade to the version that created the " \ - "lockfile by running `gem install bundler:#{newer_minor}#{pre_flag}`." + "lockfile by running `gem install bundler:#{newer_minor}`." expect(err).to include warning_message - lockfile_should_be <<-G + expect(lockfile).to eq <<~G + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + rack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + + BUNDLED WITH + #{newer_minor} + G + end + + it "warns if the current version is older than lockfile's bundler version, and locked version is a prerelease" do + current_version = "999.998.999" + system_gems "bundler-#{current_version}" + newer_minor = "999.999.0.pre1" + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + rack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + + BUNDLED WITH + #{newer_minor} + L + + install_gemfile <<-G, :env => { "BUNDLER_VERSION" => current_version } + source "#{file_uri_for(gem_repo2)}" + + gem "rack" + G + + warning_message = "the running version of Bundler (#{current_version}) is older " \ + "than the version that created the lockfile (#{newer_minor}). " \ + "We suggest you to upgrade to the version that created the " \ + "lockfile by running `gem install bundler:#{newer_minor} --pre`." + expect(err).to include warning_message + + expect(lockfile).to eq <<~G + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + rack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + + BUNDLED WITH + #{newer_minor} + G + end + + it "doesn't warn if the current version is older than lockfile's bundler version, and locked version is a dev version" do + current_version = "999.998.999" + system_gems "bundler-#{current_version}" + newer_minor = "999.999.0.dev" + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + rack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + + BUNDLED WITH + #{newer_minor} + L + + install_gemfile <<-G, :env => { "BUNDLER_VERSION" => current_version } + source "#{file_uri_for(gem_repo2)}" + + gem "rack" + G + + expect(err).to be_empty + + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -249,7 +345,7 @@ RSpec.describe "the lockfile format" do "#{current_version.split(".").first}, after which you will be unable to return to Bundler #{older_major.split(".").first}." ) - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -273,7 +369,7 @@ RSpec.describe "the lockfile format" do gem "rack-obama" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -299,7 +395,7 @@ RSpec.describe "the lockfile format" do gem "rack-obama", ">= 1.0" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -318,43 +414,12 @@ RSpec.describe "the lockfile format" do G end - it "generates a lockfile without credentials for a configured source", :bundler => "< 3" do + it "generates a lockfile without credentials for a configured source" do bundle "config set http://localgemserver.test/ user:pass" install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true) - source "http://localgemserver.test/" do - - end - - source "http://user:pass@othergemserver.test/" do - gem "rack-obama", ">= 1.0" - end - G - - lockfile_should_be <<-G - GEM - remote: http://localgemserver.test/ - remote: http://user:pass@othergemserver.test/ - specs: - rack (1.0.0) - rack-obama (1.0) - rack - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - rack-obama (>= 1.0)! - - BUNDLED WITH - #{Bundler::VERSION} - G - end + source "#{file_uri_for(gem_repo1)}" - it "generates a lockfile without credentials for a configured source", :bundler => "3" do - bundle "config set http://localgemserver.test/ user:pass" - - install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true) source "http://localgemserver.test/" do end @@ -364,8 +429,9 @@ RSpec.describe "the lockfile format" do end G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM + remote: #{file_uri_for(gem_repo1)}/ specs: GEM @@ -396,7 +462,7 @@ RSpec.describe "the lockfile format" do gem "net-sftp" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -421,10 +487,11 @@ RSpec.describe "the lockfile format" do git = build_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GIT remote: #{lib_path("foo-1.0")} revision: #{git.ref_for("master")} @@ -432,6 +499,7 @@ RSpec.describe "the lockfile format" do foo (1.0) GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -488,12 +556,13 @@ RSpec.describe "the lockfile format" do git = build_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}" do gem "foo" end G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GIT remote: #{lib_path("foo-1.0")} revision: #{git.ref_for("master")} @@ -501,6 +570,7 @@ RSpec.describe "the lockfile format" do foo (1.0) GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -519,10 +589,11 @@ RSpec.describe "the lockfile format" do update_git "foo", :branch => "omg" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "omg" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GIT remote: #{lib_path("foo-1.0")} revision: #{git.ref_for("omg")} @@ -531,6 +602,7 @@ RSpec.describe "the lockfile format" do foo (1.0) GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -549,10 +621,11 @@ RSpec.describe "the lockfile format" do update_git "foo", :tag => "omg" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}", :tag => "omg" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GIT remote: #{lib_path("foo-1.0")} revision: #{git.ref_for("omg")} @@ -561,6 +634,7 @@ RSpec.describe "the lockfile format" do foo (1.0) GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -578,16 +652,18 @@ RSpec.describe "the lockfile format" do build_lib "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo-1.0")}" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo-1.0")} specs: foo (1.0) GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -605,6 +681,7 @@ RSpec.describe "the lockfile format" do build_lib "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo-1.0")}" G @@ -612,13 +689,14 @@ RSpec.describe "the lockfile format" do bundle :cache bundle :install, :local => true - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo-1.0")} specs: foo (1.0) GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -644,7 +722,7 @@ RSpec.describe "the lockfile format" do gem "bar", :git => "#{lib_path("bar-1.0")}" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GIT remote: #{lib_path("bar-1.0")} revision: #{bar.ref_for("master")} @@ -674,6 +752,30 @@ RSpec.describe "the lockfile format" do G end + it "removes redundant sources" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}/" + + gem "rack", :source => "#{file_uri_for(gem_repo2)}/" + G + + expect(lockfile).to eq <<~G + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + rack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack! + + BUNDLED WITH + #{Bundler::VERSION} + G + end + it "lists gems alphabetically" do install_gemfile <<-G source "#{file_uri_for(gem_repo2)}/" @@ -683,7 +785,7 @@ RSpec.describe "the lockfile format" do gem "rack-obama" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -716,7 +818,7 @@ RSpec.describe "the lockfile format" do gem "rails" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -754,7 +856,7 @@ RSpec.describe "the lockfile format" do gem 'double_deps' G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -781,7 +883,7 @@ RSpec.describe "the lockfile format" do gem "rack-obama", ">= 1.0", :require => "rack/obama" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -807,7 +909,7 @@ RSpec.describe "the lockfile format" do gem "rack-obama", ">= 1.0", :group => :test G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -830,18 +932,20 @@ RSpec.describe "the lockfile format" do build_lib "foo", :path => bundled_app("foo") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "foo" do gem "foo" end G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: foo specs: foo (1.0) GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -859,18 +963,20 @@ RSpec.describe "the lockfile format" do build_lib "foo", :path => bundled_app(File.join("..", "foo")) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "../foo" do gem "foo" end G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: ../foo specs: foo (1.0) GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -888,18 +994,20 @@ RSpec.describe "the lockfile format" do build_lib "foo", :path => bundled_app("foo") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path File.expand_path("../foo", __FILE__) do gem "foo" end G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: foo specs: foo (1.0) GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -917,16 +1025,18 @@ RSpec.describe "the lockfile format" do build_lib("foo", :path => tmp.join("foo")) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gemspec :path => "../foo" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: ../foo specs: foo (1.0) GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -963,15 +1073,14 @@ RSpec.describe "the lockfile format" do gem "rack" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: rack (1.0.0) PLATFORMS - java - #{lockfile_platforms} + #{lockfile_platforms_for(["java"] + local_platforms)} DEPENDENCIES rack @@ -995,7 +1104,7 @@ RSpec.describe "the lockfile format" do gem "platform_specific" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -1024,7 +1133,7 @@ RSpec.describe "the lockfile format" do gem "activesupport" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -1050,7 +1159,7 @@ RSpec.describe "the lockfile format" do gem "rack" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -1074,7 +1183,7 @@ RSpec.describe "the lockfile format" do gem "rack", "1.0" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -1098,7 +1207,7 @@ RSpec.describe "the lockfile format" do gem "rack", "1.0", :group => :two G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -1143,7 +1252,7 @@ RSpec.describe "the lockfile format" do gem "rack", "> 0.9", "< 1.0" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -1167,7 +1276,7 @@ RSpec.describe "the lockfile format" do gem "rack", "> 0.9", "< 1.0" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -1187,83 +1296,6 @@ RSpec.describe "the lockfile format" do G end - # Some versions of the Bundler 1.1 RC series introduced corrupted - # lockfiles. There were two major problems: - # - # * multiple copies of the same GIT section appeared in the lockfile - # * when this happened, those sections got multiple copies of gems - # in those sections. - it "fixes corrupted lockfiles" do - build_git "omg", :path => lib_path("omg") - revision = revision_for(lib_path("omg")) - - gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" - gem "omg", :git => "#{lib_path("omg")}", :branch => 'master' - G - - bundle "config --local path vendor" - bundle :install - expect(the_bundle).to include_gems "omg 1.0" - - # Create a Gemfile.lock that has duplicate GIT sections - lockfile <<-L - GIT - remote: #{lib_path("omg")} - revision: #{revision} - branch: master - specs: - omg (1.0) - - GIT - remote: #{lib_path("omg")} - revision: #{revision} - branch: master - specs: - omg (1.0) - - GEM - remote: #{file_uri_for(gem_repo2)}/ - specs: - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - omg! - - BUNDLED WITH - #{Bundler::VERSION} - L - - FileUtils.rm_rf(bundled_app("vendor")) - bundle "install" - expect(the_bundle).to include_gems "omg 1.0" - - # Confirm that duplicate specs do not appear - lockfile_should_be(<<-L) - GIT - remote: #{lib_path("omg")} - revision: #{revision} - branch: master - specs: - omg (1.0) - - GEM - remote: #{file_uri_for(gem_repo2)}/ - specs: - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - omg! - - BUNDLED WITH - #{Bundler::VERSION} - L - end - it "raises a helpful error message when the lockfile is missing deps" do lockfile <<-L GEM @@ -1335,7 +1367,10 @@ RSpec.describe "the lockfile format" do expect { bundle "update", :all => true }.to change { File.mtime(bundled_app_lock) } expect(File.read(bundled_app_lock)).to match("\r\n") - expect(the_bundle).to include_gems "rack 1.2" + + simulate_bundler_version_when_missing_prerelease_default_gem_activation do + expect(the_bundle).to include_gems "rack 1.2" + end end end @@ -1356,7 +1391,7 @@ RSpec.describe "the lockfile format" do expect do ruby <<-RUBY - require '#{lib_dir}/bundler' + require '#{entrypoint}' Bundler.setup RUBY end.not_to change { File.mtime(bundled_app_lock) } diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index d061ca7064..b228027c03 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -17,7 +17,7 @@ RSpec.describe "major deprecations" do bundle "exec ruby -e #{source.dump}" end - it "is deprecated in favor of .unbundled_env", :bundler => "2" do + it "is deprecated in favor of .unbundled_env", :bundler => "< 3" do expect(deprecations).to include \ "`Bundler.clean_env` has been deprecated in favor of `Bundler.unbundled_env`. " \ "If you instead want the environment before bundler was originally loaded, use `Bundler.original_env` " \ @@ -33,7 +33,7 @@ RSpec.describe "major deprecations" do bundle "exec ruby -e #{source.dump}" end - it "is deprecated in favor of .unbundled_env", :bundler => "2" do + it "is deprecated in favor of .unbundled_env", :bundler => "< 3" do expect(deprecations).to include( "`Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. " \ "If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env` " \ @@ -50,7 +50,7 @@ RSpec.describe "major deprecations" do bundle "exec ruby -e #{source.dump}" end - it "is deprecated in favor of .unbundled_system", :bundler => "2" do + it "is deprecated in favor of .unbundled_system", :bundler => "< 3" do expect(deprecations).to include( "`Bundler.clean_system` has been deprecated in favor of `Bundler.unbundled_system`. " \ "If you instead want to run the command in the environment before bundler was originally loaded, use `Bundler.original_system` " \ @@ -67,7 +67,7 @@ RSpec.describe "major deprecations" do bundle "exec ruby -e #{source.dump}" end - it "is deprecated in favor of .unbundled_exec", :bundler => "2" do + it "is deprecated in favor of .unbundled_exec", :bundler => "< 3" do expect(deprecations).to include( "`Bundler.clean_exec` has been deprecated in favor of `Bundler.unbundled_exec`. " \ "If you instead want to exec to a command in the environment before bundler was originally loaded, use `Bundler.original_exec` " \ @@ -84,7 +84,7 @@ RSpec.describe "major deprecations" do bundle "exec ruby -e #{source.dump}" end - it "is deprecated in favor of .load", :bundler => "2" do + it "is deprecated in favor of .load", :bundler => "< 3" do expect(deprecations).to include "Bundler.environment has been removed in favor of Bundler.load (called at -e:1)" end @@ -92,6 +92,18 @@ RSpec.describe "major deprecations" do end end + describe "bundle exec --no-keep-file-descriptors" do + before do + bundle "exec --no-keep-file-descriptors -e 1", :raise_on_error => false + end + + it "is deprecated", :bundler => "< 3" do + expect(deprecations).to include "The `--no-keep-file-descriptors` has been deprecated. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to" + end + + pending "is removed and shows a helpful error message about it", :bundler => "3" + end + describe "bundle update --quiet" do it "does not print any deprecations" do bundle :update, :quiet => true, :raise_on_error => false @@ -109,7 +121,7 @@ RSpec.describe "major deprecations" do bundle "check --path vendor/bundle", :raise_on_error => false end - it "should print a deprecation warning", :bundler => "2" do + it "should print a deprecation warning", :bundler => "< 3" do expect(deprecations).to include( "The `--path` flag is deprecated because it relies on being " \ "remembered across bundler invocations, which bundler will no " \ @@ -118,7 +130,7 @@ RSpec.describe "major deprecations" do ) end - pending "should fail with a helpful error", :bundler => "3" + pending "fails with a helpful error", :bundler => "3" end context "bundle check --path=" do @@ -131,7 +143,7 @@ RSpec.describe "major deprecations" do bundle "check --path=vendor/bundle", :raise_on_error => false end - it "should print a deprecation warning", :bundler => "2" do + it "should print a deprecation warning", :bundler => "< 3" do expect(deprecations).to include( "The `--path` flag is deprecated because it relies on being " \ "remembered across bundler invocations, which bundler will no " \ @@ -140,7 +152,7 @@ RSpec.describe "major deprecations" do ) end - pending "should fail with a helpful error", :bundler => "3" + pending "fails with a helpful error", :bundler => "3" end context "bundle cache --all" do @@ -153,7 +165,7 @@ RSpec.describe "major deprecations" do bundle "cache --all", :raise_on_error => false end - it "should print a deprecation warning", :bundler => "2" do + it "should print a deprecation warning", :bundler => "< 3" do expect(deprecations).to include( "The `--all` flag is deprecated because it relies on being " \ "remembered across bundler invocations, which bundler will no " \ @@ -162,7 +174,29 @@ RSpec.describe "major deprecations" do ) end - pending "should fail with a helpful error", :bundler => "3" + pending "fails with a helpful error", :bundler => "3" + end + + context "bundle cache --path" do + before do + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack" + G + + bundle "cache --path foo", :raise_on_error => false + end + + it "should print a deprecation warning", :bundler => "< 3" do + expect(deprecations).to include( + "The `--path` flag is deprecated because its semantics are unclear. " \ + "Use `bundle config cache_path` to configure the path of your cache of gems, " \ + "and `bundle config path` to configure the path where your gems are installed, " \ + "and stop using this flag" + ) + end + + pending "fails with a helpful error", :bundler => "3" end describe "bundle config" do @@ -292,7 +326,7 @@ RSpec.describe "major deprecations" do G end - it "should output a deprecation warning", :bundler => "2" do + it "should output a deprecation warning", :bundler => "< 3" do expect(deprecations).to include("The --binstubs option will be removed in favor of `bundle binstubs --all`") end @@ -311,7 +345,7 @@ RSpec.describe "major deprecations" do end it "should print a proper warning, and use gems.rb" do - create_file "gems.rb" + create_file "gems.rb", "source \"#{file_uri_for(gem_repo1)}\"" install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -356,7 +390,7 @@ RSpec.describe "major deprecations" do bundle "install #{flag_name} #{value}" end - it "should print a deprecation warning", :bundler => "2" do + it "should print a deprecation warning", :bundler => "< 3" do expect(deprecations).to include( "The `#{flag_name}` flag is deprecated because it relies on " \ "being remembered across bundler invocations, which bundler " \ @@ -365,7 +399,7 @@ RSpec.describe "major deprecations" do ) end - pending "should fail with a helpful error", :bundler => "3" + pending "fails with a helpful error", :bundler => "3" end end end @@ -378,30 +412,98 @@ RSpec.describe "major deprecations" do G end - it "shows a deprecation", :bundler => "2" do + it "shows a deprecation", :bundler => "< 3" do + expect(deprecations).to include( + "Your Gemfile contains multiple primary sources. " \ + "Using `source` more than once without a block is a security risk, and " \ + "may result in installing unexpected gems. To resolve this warning, use " \ + "a block to indicate which gems should come from the secondary source." + ) + end + + it "doesn't show lockfile deprecations if there's a lockfile", :bundler => "< 3" do + bundle "install" + + expect(deprecations).to include( + "Your Gemfile contains multiple primary sources. " \ + "Using `source` more than once without a block is a security risk, and " \ + "may result in installing unexpected gems. To resolve this warning, use " \ + "a block to indicate which gems should come from the secondary source." + ) + expect(deprecations).not_to include( + "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. " \ + "Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." + ) + bundle "config set --local frozen true" + bundle "install" + expect(deprecations).to include( "Your Gemfile contains multiple primary sources. " \ "Using `source` more than once without a block is a security risk, and " \ "may result in installing unexpected gems. To resolve this warning, use " \ - "a block to indicate which gems should come from the secondary source. " \ - "To upgrade this warning to an error, run `bundle config set --local " \ - "disable_multisource true`." + "a block to indicate which gems should come from the secondary source." ) + expect(deprecations).not_to include( + "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. " \ + "Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." + ) + end + + pending "fails with a helpful error", :bundler => "3" + end + + context "bundle install in frozen mode with a lockfile with a single rubygems section with multiple remotes" do + before do + build_repo gem_repo3 do + build_gem "rack", "0.9.1" + end + + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + source "#{file_uri_for(gem_repo3)}" do + gem 'rack' + end + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo1)}/ + remote: #{file_uri_for(gem_repo3)}/ + specs: + rack (0.9.1) + + PLATFORMS + ruby + + DEPENDENCIES + rack! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "config set --local frozen true" + end + + it "shows a deprecation", :bundler => "< 3" do + bundle "install" + + expect(deprecations).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure.") end - pending "should fail with a helpful error", :bundler => "3" + pending "fails with a helpful error", :bundler => "3" end context "when Bundler.setup is run in a ruby script" do before do - create_file "gems.rb" + create_file "gems.rb", "source \"#{file_uri_for(gem_repo1)}\"" install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack", :group => :test G ruby <<-RUBY - require '#{lib_dir}/bundler' + require '#{entrypoint}' Bundler.setup Bundler.setup @@ -422,14 +524,14 @@ RSpec.describe "major deprecations" do RUBY end - it "should print a capistrano deprecation warning", :bundler => "2" do + it "should print a capistrano deprecation warning", :bundler => "< 3" do expect(deprecations).to include("Bundler no longer integrates " \ "with Capistrano, but Capistrano provides " \ "its own integration with Bundler via the " \ "capistrano-bundler gem. Use it instead.") end - pending "should fail with a helpful error", :bundler => "3" + pending "fails with a helpful error", :bundler => "3" end describe Bundler::Dsl do @@ -439,7 +541,7 @@ RSpec.describe "major deprecations" do end context "with github gems" do - it "does not warn about removal", :bundler => "2" do + it "does not warn about removal", :bundler => "< 3" do expect(Bundler.ui).not_to receive(:warn) subject.gem("sparks", :github => "indirect/sparks") github_uri = "https://github.com/indirect/sparks.git" @@ -461,7 +563,7 @@ The :github git source is deprecated, and will be removed in the future. Change end context "with bitbucket gems" do - it "does not warn about removal", :bundler => "2" do + it "does not warn about removal", :bundler => "< 3" do expect(Bundler.ui).not_to receive(:warn) subject.gem("not-really-a-gem", :bitbucket => "mcorp/flatlab-rails") end @@ -483,7 +585,7 @@ The :bitbucket git source is deprecated, and will be removed in the future. Add end context "with gist gems" do - it "does not warn about removal", :bundler => "2" do + it "does not warn about removal", :bundler => "< 3" do expect(Bundler.ui).not_to receive(:warn) subject.gem("not-really-a-gem", :gist => "1234") end @@ -509,73 +611,32 @@ The :gist git source is deprecated, and will be removed in the future. Add this G end - context "without flags" do - before do - bundle :show - end - - it "prints a deprecation warning recommending `bundle list`", :bundler => "2" do - expect(deprecations).to include("use `bundle list` instead of `bundle show`") - end - - pending "fails with a helpful message", :bundler => "3" - end - context "with --outdated flag" do before do bundle "show --outdated" end - it "prints a deprecation warning informing about its removal", :bundler => "2" do + it "prints a deprecation warning informing about its removal", :bundler => "< 3" do expect(deprecations).to include("the `--outdated` flag to `bundle show` was undocumented and will be removed without replacement") end pending "fails with a helpful message", :bundler => "3" end + end - context "with --verbose flag" do - before do - bundle "show --verbose" - end - - it "prints a deprecation warning informing about its removal", :bundler => "2" do - expect(deprecations).to include("the `--verbose` flag to `bundle show` was undocumented and will be removed without replacement") - end - - pending "fails with a helpful message", :bundler => "3" - end - - context "with a gem argument" do - before do - bundle "show rack" - end - - it "prints a deprecation warning recommending `bundle info`", :bundler => "2" do - expect(deprecations).to include("use `bundle info rack` instead of `bundle show rack`") - end - - pending "fails with a helpful message", :bundler => "3" - end - - context "with the --paths option" do - before do - bundle "show --paths" - end - - it "prints a deprecation warning recommending `bundle list`", :bundler => "2" do - expect(deprecations).to include("use `bundle list` instead of `bundle show --paths`") - end - - pending "fails with a helpful message", :bundler => "3" + context "bundle remove" do + before do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack" + G end - context "with a gem argument and the --paths option" do - before do - bundle "show rack --paths" - end + context "with --install" do + it "shows a deprecation warning", :bundler => "< 3" do + bundle "remove rack --install" - it "prints deprecation warning recommending `bundle info`", :bundler => "2" do - expect(deprecations).to include("use `bundle info rack --path` instead of `bundle show rack --paths`") + expect(err).to include "[DEPRECATED] The `--install` flag has been deprecated. `bundle install` is triggered by default." end pending "fails with a helpful message", :bundler => "3" @@ -587,7 +648,7 @@ The :gist git source is deprecated, and will be removed in the future. Add this bundle "console", :raise_on_error => false end - it "prints a deprecation warning", :bundler => "2" do + it "prints a deprecation warning", :bundler => "< 3" do expect(deprecations).to include \ "bundle console will be replaced by `bin/console` generated by `bundle gem <name>`" end @@ -599,14 +660,60 @@ The :gist git source is deprecated, and will be removed in the future. Add this before do graphviz_version = RUBY_VERSION >= "2.4" ? "1.2.5" : "1.2.4" realworld_system_gems "ruby-graphviz --version #{graphviz_version}" - create_file "gems.rb" + create_file "gems.rb", "source \"#{file_uri_for(gem_repo1)}\"" bundle "viz" end - it "prints a deprecation warning", :bundler => "2" do - expect(deprecations).to include "The `viz` command has been moved to the `bundle-viz` gem, see https://github.com/bundler/bundler-viz" + it "prints a deprecation warning", :bundler => "< 3" do + expect(deprecations).to include "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph" end pending "fails with a helpful message", :bundler => "3" end + + describe "deprecating rubocop", :readline do + context "bundle gem --rubocop" do + before do + bundle "gem my_new_gem --rubocop", :raise_on_error => false + end + + it "prints a deprecation warning", :bundler => "< 3" do + expect(deprecations).to include \ + "--rubocop is deprecated, use --linter=rubocop" + end + end + + context "bundle gem --no-rubocop" do + before do + bundle "gem my_new_gem --no-rubocop", :raise_on_error => false + end + + it "prints a deprecation warning", :bundler => "< 3" do + expect(deprecations).to include \ + "--no-rubocop is deprecated, use --linter" + end + end + + context "bundle gem with gem.rubocop set to true" do + before do + bundle "gem my_new_gem", :env => { "BUNDLE_GEM__RUBOCOP" => "true" }, :raise_on_error => false + end + + it "prints a deprecation warning", :bundler => "< 3" do + expect(deprecations).to include \ + "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead" + end + end + + context "bundle gem with gem.rubocop set to false" do + before do + bundle "gem my_new_gem", :env => { "BUNDLE_GEM__RUBOCOP" => "false" }, :raise_on_error => false + end + + it "prints a deprecation warning", :bundler => "< 3" do + expect(deprecations).to include \ + "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead" + end + end + end end diff --git a/spec/bundler/other/platform_spec.rb b/spec/bundler/other/platform_spec.rb index eb3539b412..5693d6bce6 100644 --- a/spec/bundler/other/platform_spec.rb +++ b/spec/bundler/other/platform_spec.rb @@ -213,11 +213,13 @@ G it "handles when there is a locked requirement" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" ruby "< 1.8.7" G lockfile <<-L GEM + remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS @@ -238,6 +240,7 @@ G it "handles when there is a requirement in the gemfile" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" ruby ">= 1.8.7" G @@ -247,6 +250,7 @@ G it "handles when there are multiple requirements in the gemfile" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" ruby ">= 1.8.7", "< 2.0.0" G @@ -685,6 +689,7 @@ G it "copies the .gem file to vendor/cache when ruby version matches" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'rack' #{ruby_version_correct} @@ -708,6 +713,7 @@ G it "fails if the ruby version doesn't match" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'rack' #{ruby_version_incorrect} @@ -719,6 +725,7 @@ G it "fails if the engine doesn't match" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'rack' #{engine_incorrect} @@ -730,6 +737,7 @@ G it "fails if the engine version doesn't match", :jruby do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'rack' #{engine_version_incorrect} @@ -762,6 +770,7 @@ G it "copies the .gem file to vendor/cache when ruby version matches" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'rack' #{ruby_version_correct} @@ -785,6 +794,7 @@ G it "fails if the ruby version doesn't match" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'rack' #{ruby_version_incorrect} @@ -796,6 +806,7 @@ G it "fails if the engine doesn't match" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'rack' #{engine_incorrect} @@ -807,6 +818,7 @@ G it "fails if the engine version doesn't match", :jruby do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'rack' #{engine_version_incorrect} @@ -837,6 +849,7 @@ G it "activates the correct gem when ruby version matches" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack", "0.9.1" #{ruby_version_correct} @@ -849,6 +862,7 @@ G it "activates the correct gem when ruby version matches any engine", :jruby do system_gems "rack-1.0.0", "rack-0.9.1", :path => default_bundle_path gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack", "0.9.1" #{ruby_version_correct_engineless} @@ -860,6 +874,7 @@ G it "fails when the ruby version doesn't match" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack", "0.9.1" #{ruby_version_incorrect} @@ -871,6 +886,7 @@ G it "fails when the engine doesn't match" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack", "0.9.1" #{engine_incorrect} diff --git a/spec/bundler/plugins/command_spec.rb b/spec/bundler/plugins/command_spec.rb index 4567a39081..3a7adf4b48 100644 --- a/spec/bundler/plugins/command_spec.rb +++ b/spec/bundler/plugins/command_spec.rb @@ -69,12 +69,10 @@ RSpec.describe "command plugins" do end end - bundle "plugin install copycat --source #{file_uri_for(gem_repo2)}" + bundle "plugin install copycat --source #{file_uri_for(gem_repo2)}", :raise_on_error => false expect(out).not_to include("Installed plugin copycat") - expect(err).to include("Failed to install the following plugins: `copycat`") - - expect(err).to include("Command(s) `mahcommand` declared by copycat are already registered.") + expect(err).to include("Failed to install plugin `copycat`, due to Bundler::Plugin::Index::CommandConflict (Command(s) `mahcommand` declared by copycat are already registered.)") end end diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb index 370973ad1a..009516260a 100644 --- a/spec/bundler/plugins/install_spec.rb +++ b/spec/bundler/plugins/install_spec.rb @@ -22,6 +22,19 @@ RSpec.describe "bundler plugin install" do plugin_should_be_installed("foo") end + it "installs from sources configured as Gem.sources without any flags" do + bundle "plugin install foo", :env => { "BUNDLER_SPEC_GEM_SOURCES" => file_uri_for(gem_repo2).to_s } + + expect(out).to include("Installed plugin foo") + plugin_should_be_installed("foo") + end + + it "shows help when --help flag is given" do + bundle "plugin install --help" + + expect(out).to include("bundle plugin install PLUGINS # Install the plugin from the source") + end + context "plugin is already installed" do before do bundle "plugin install foo --source #{file_uri_for(gem_repo2)}" @@ -56,6 +69,21 @@ RSpec.describe "bundler plugin install" do plugin_should_be_installed("foo", "kung-foo") end + it "installs the latest version if not installed" do + update_repo2 do + build_plugin "foo", "1.1" + end + + bundle "plugin install foo --version 1.0 --source #{file_uri_for(gem_repo2)} --verbose" + expect(out).to include("Installing foo 1.0") + + bundle "plugin install foo --source #{file_uri_for(gem_repo2)} --verbose" + expect(out).to include("Installing foo 1.1") + + bundle "plugin install foo --source #{file_uri_for(gem_repo2)} --verbose" + expect(out).to include("Using foo 1.1") + end + it "works with different load paths" do build_repo2 do build_plugin "testing" do |s| @@ -96,9 +124,9 @@ RSpec.describe "bundler plugin install" do build_gem "charlie" end - bundle "plugin install charlie --source #{file_uri_for(gem_repo2)}" + bundle "plugin install charlie --source #{file_uri_for(gem_repo2)}", :raise_on_error => false - expect(err).to include("Failed to install the following plugins: `charlie`. The underlying error was: plugins.rb was not found") + expect(err).to include("Failed to install plugin `charlie`, due to Bundler::Plugin::MalformattedPlugin (plugins.rb was not found in the plugin.)") expect(global_plugin_gem("charlie-1.0")).not_to be_directory @@ -115,7 +143,7 @@ RSpec.describe "bundler plugin install" do end end - bundle "plugin install chaplin --source #{file_uri_for(gem_repo2)}" + bundle "plugin install chaplin --source #{file_uri_for(gem_repo2)}", :raise_on_error => false expect(global_plugin_gem("chaplin-1.0")).not_to be_directory @@ -201,6 +229,7 @@ RSpec.describe "bundler plugin install" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" plugin 'ga-plugin', :git => "#{lib_path("ga-plugin-1.0")}" G @@ -214,6 +243,7 @@ RSpec.describe "bundler plugin install" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" plugin 'ga-plugin', :path => "#{lib_path("ga-plugin-1.0")}" G @@ -228,7 +258,7 @@ RSpec.describe "bundler plugin install" do gem 'rack', "1.0.0" G - bundle "config --local deployment true" + bundle "config set --local deployment true" install_gemfile <<-G source '#{file_uri_for(gem_repo2)}' plugin 'foo' diff --git a/spec/bundler/plugins/source/example_spec.rb b/spec/bundler/plugins/source/example_spec.rb index 03a377ac93..7d098997ec 100644 --- a/spec/bundler/plugins/source/example_spec.rb +++ b/spec/bundler/plugins/source/example_spec.rb @@ -70,7 +70,7 @@ RSpec.describe "real source plugins" do it "writes to lock file" do bundle "install" - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PLUGIN SOURCE remote: #{lib_path("a-path-gem-1.0")} type: mpath @@ -132,7 +132,7 @@ RSpec.describe "real source plugins" do end it "copies repository to vendor cache and uses it even when installed with `path` configured" do - bundle "config --local path vendor/bundle" + bundle "config set --local path vendor/bundle" bundle :install bundle "config set cache_all true" bundle :cache @@ -144,7 +144,7 @@ RSpec.describe "real source plugins" do end it "bundler package copies repository to vendor cache" do - bundle "config --local path vendor/bundle" + bundle "config set --local path vendor/bundle" bundle :install bundle "config set cache_all true" bundle :cache @@ -342,7 +342,7 @@ RSpec.describe "real source plugins" do revision = revision_for(lib_path("ma-gitp-gem-1.0")) bundle "install" - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PLUGIN SOURCE remote: #{file_uri_for(lib_path("ma-gitp-gem-1.0"))} type: gitp diff --git a/spec/bundler/quality_spec.rb b/spec/bundler/quality_spec.rb index 2b1e28fa30..62f3722a39 100644 --- a/spec/bundler/quality_spec.rb +++ b/spec/bundler/quality_spec.rb @@ -3,25 +3,6 @@ require "set" RSpec.describe "The library itself" do - def check_for_debugging_mechanisms(filename) - debugging_mechanisms_regex = / - (binding\.pry)| - (debugger)| - (sleep\s*\(?\d+)| - (fit\s*\(?("|\w)) - /x - - failing_lines = [] - each_line(filename) do |line, number| - if line =~ debugging_mechanisms_regex && !line.end_with?("# ignore quality_spec\n") - failing_lines << number + 1 - end - end - - return if failing_lines.empty? - "#{filename} has debugging mechanisms (like binding.pry, sleep, debugger, rspec focusing, etc.) on lines #{failing_lines.join(", ")}" - end - def check_for_git_merge_conflicts(filename) merge_conflicts_regex = / <<<<<<<| @@ -125,16 +106,6 @@ RSpec.describe "The library itself" do expect(error_messages.compact).to be_well_formed end - it "does not include any leftover debugging or development mechanisms" do - exempt = %r{quality_spec.rb|support/helpers|vcr_cassettes|\.md|\.ronn|index\.txt|\.5|\.1} - error_messages = [] - tracked_files.each do |filename| - next if filename =~ exempt - error_messages << check_for_debugging_mechanisms(filename) - end - expect(error_messages.compact).to be_well_formed - end - it "does not include any unresolved merge conflicts" do error_messages = [] exempt = %r{lock/lockfile_spec|quality_spec|vcr_cassettes|\.ronn|lockfile_parser\.rb} @@ -169,11 +140,17 @@ RSpec.describe "The library itself" do it "documents all used settings" do exemptions = %w[ - deployment_means_frozen forget_cli_options + gem.changelog + gem.ci gem.coc + gem.linter gem.mit + gem.rubocop + gem.test + git.allow_insecure inline + trust-policy use_gem_version_promoter_for_major_updates ] @@ -183,6 +160,7 @@ RSpec.describe "The library itself" do Bundler::Settings::BOOL_KEYS.each {|k| all_settings[k] << "in Bundler::Settings::BOOL_KEYS" } Bundler::Settings::NUMBER_KEYS.each {|k| all_settings[k] << "in Bundler::Settings::NUMBER_KEYS" } Bundler::Settings::ARRAY_KEYS.each {|k| all_settings[k] << "in Bundler::Settings::ARRAY_KEYS" } + Bundler::Settings::STRING_KEYS.each {|k| all_settings[k] << "in Bundler::Settings::STRING_KEYS" } key_pattern = /([a-z\._-]+)/i lib_tracked_files.each do |filename| @@ -216,7 +194,7 @@ RSpec.describe "The library itself" do end it "ships the correct set of files" do - git_list = git_ls_files(ruby_core? ? "lib/bundler lib/bundler.rb man/bundle* man/gemfile* libexec/bundle*" : "lib man exe CHANGELOG.md LICENSE.md README.md bundler.gemspec") + git_list = git_ls_files(ruby_core? ? "lib/bundler lib/bundler.rb libexec/bundle*" : "lib exe CHANGELOG.md LICENSE.md README.md bundler.gemspec") gem_list = loaded_gemspec.files diff --git a/spec/bundler/realworld/double_check_spec.rb b/spec/bundler/realworld/double_check_spec.rb index 07697f080e..d7f28d10bb 100644 --- a/spec/bundler/realworld/double_check_spec.rb +++ b/spec/bundler/realworld/double_check_spec.rb @@ -25,9 +25,9 @@ RSpec.describe "double checking sources", :realworld => true do RUBY cmd = <<-RUBY - require "#{lib_dir}/bundler" + require "#{entrypoint}" require "#{spec_dir}/support/artifice/vcr" - require "#{lib_dir}/bundler/inline" + require "#{entrypoint}/inline" gemfile(true) do source "https://rubygems.org" gem "rails", path: "." diff --git a/spec/bundler/realworld/edgecases_spec.rb b/spec/bundler/realworld/edgecases_spec.rb index 1925f76c06..df5eeda9fe 100644 --- a/spec/bundler/realworld/edgecases_spec.rb +++ b/spec/bundler/realworld/edgecases_spec.rb @@ -4,9 +4,9 @@ RSpec.describe "real world edgecases", :realworld => true do def rubygems_version(name, requirement) ruby <<-RUBY require "#{spec_dir}/support/artifice/vcr" - require "#{lib_dir}/bundler" - require "#{lib_dir}/bundler/source/rubygems/remote" - require "#{lib_dir}/bundler/fetcher" + require "#{entrypoint}" + require "#{entrypoint}/source/rubygems/remote" + require "#{entrypoint}/fetcher" rubygem = Bundler.ui.silence do source = Bundler::Source::Rubygems::Remote.new(Bundler::URI("https://rubygems.org")) fetcher = Bundler::Fetcher.new(source) @@ -196,167 +196,26 @@ RSpec.describe "real world edgecases", :realworld => true do expect(lockfile).to include(rubygems_version("paperclip", "~> 5.1.0")) end - # https://github.com/rubygems/bundler/issues/1500 - it "does not fail install because of gem plugins" do - realworld_system_gems("open_gem --version 1.4.2", "rake --version 0.9.2") - gemfile <<-G - source "https://rubygems.org" - - gem 'rack', '1.0.1' - G - - bundle "config set --local path vendor/bundle" - bundle :install - expect(err).not_to include("Could not find rake") - expect(err).to be_empty - end - - it "checks out git repos when the lockfile is corrupted" do - gemfile <<-G - source "https://rubygems.org" - git_source(:github) {|repo| "https://github.com/\#{repo}.git" } - - gem 'activerecord', :github => 'carlhuda/rails-bundler-test', :branch => 'master' - gem 'activesupport', :github => 'carlhuda/rails-bundler-test', :branch => 'master' - gem 'actionpack', :github => 'carlhuda/rails-bundler-test', :branch => 'master' - G - - lockfile <<-L - GIT - remote: https://github.com/carlhuda/rails-bundler-test.git - revision: 369e28a87419565f1940815219ea9200474589d4 - branch: master - specs: - actionpack (3.2.2) - activemodel (= 3.2.2) - activesupport (= 3.2.2) - builder (~> 3.0.0) - erubis (~> 2.7.0) - journey (~> 1.0.1) - rack (~> 1.4.0) - rack-cache (~> 1.2) - rack-test (~> 0.6.1) - sprockets (~> 2.1.2) - activemodel (3.2.2) - activesupport (= 3.2.2) - builder (~> 3.0.0) - activerecord (3.2.2) - activemodel (= 3.2.2) - activesupport (= 3.2.2) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activesupport (3.2.2) - i18n (~> 0.6) - multi_json (~> 1.0) - - GIT - remote: https://github.com/carlhuda/rails-bundler-test.git - revision: 369e28a87419565f1940815219ea9200474589d4 - branch: master - specs: - actionpack (3.2.2) - activemodel (= 3.2.2) - activesupport (= 3.2.2) - builder (~> 3.0.0) - erubis (~> 2.7.0) - journey (~> 1.0.1) - rack (~> 1.4.0) - rack-cache (~> 1.2) - rack-test (~> 0.6.1) - sprockets (~> 2.1.2) - activemodel (3.2.2) - activesupport (= 3.2.2) - builder (~> 3.0.0) - activerecord (3.2.2) - activemodel (= 3.2.2) - activesupport (= 3.2.2) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activesupport (3.2.2) - i18n (~> 0.6) - multi_json (~> 1.0) - - GIT - remote: https://github.com/carlhuda/rails-bundler-test.git - revision: 369e28a87419565f1940815219ea9200474589d4 - branch: master - specs: - actionpack (3.2.2) - activemodel (= 3.2.2) - activesupport (= 3.2.2) - builder (~> 3.0.0) - erubis (~> 2.7.0) - journey (~> 1.0.1) - rack (~> 1.4.0) - rack-cache (~> 1.2) - rack-test (~> 0.6.1) - sprockets (~> 2.1.2) - activemodel (3.2.2) - activesupport (= 3.2.2) - builder (~> 3.0.0) - activerecord (3.2.2) - activemodel (= 3.2.2) - activesupport (= 3.2.2) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activesupport (3.2.2) - i18n (~> 0.6) - multi_json (~> 1.0) - - GEM - remote: https://rubygems.org/ - specs: - arel (3.0.2) - builder (3.0.0) - erubis (2.7.0) - hike (1.2.1) - i18n (0.6.0) - journey (1.0.3) - multi_json (1.1.0) - rack (1.4.1) - rack-cache (1.2) - rack (>= 0.4) - rack-test (0.6.1) - rack (>= 1.0) - sprockets (2.1.2) - hike (~> 1.2) - rack (~> 1.0) - tilt (~> 1.1, != 1.3.0) - tilt (1.3.3) - tzinfo (0.3.32) - - PLATFORMS - ruby - - DEPENDENCIES - actionpack! - activerecord! - activesupport! - L - - bundle :lock - expect(err).to be_empty - end - it "outputs a helpful error message when gems have invalid gemspecs" do - install_gemfile <<-G, :standalone => true, :raise_on_error => false + install_gemfile <<-G, :standalone => true, :raise_on_error => false, :env => { "BUNDLE_FORCE_RUBY_PLATFORM" => "1" } source 'https://rubygems.org' gem "resque-scheduler", "2.2.0" gem "redis-namespace", "1.6.0" # for a consistent resolution including ruby 2.3.0 + gem "ruby2_keywords", "0.0.5" G expect(err).to include("You have one or more invalid gemspecs that need to be fixed.") expect(err).to include("resque-scheduler 2.2.0 has an invalid gemspec") end it "doesn't hang on big gemfile" do - skip "Only for ruby 2.7.2" if RUBY_VERSION != "2.7.2" + skip "Only for ruby 2.7.3" if RUBY_VERSION != "2.7.3" || RUBY_PLATFORM =~ /darwin/ gemfile <<~G # frozen_string_literal: true source "https://rubygems.org" - ruby "2.7.2" + ruby "2.7.3" gem "rails" gem "pg", ">= 0.18", "< 2.0" @@ -461,7 +320,7 @@ RSpec.describe "real world edgecases", :realworld => true do end it "doesn't hang on tricky gemfile" do - skip "Only for ruby 2.7.2" if RUBY_VERSION != "2.7.2" + skip "Only for ruby 2.7.3" if RUBY_VERSION != "2.7.3" || RUBY_PLATFORM =~ /darwin/ gemfile <<~G source 'https://rubygems.org' @@ -487,7 +346,7 @@ RSpec.describe "real world edgecases", :realworld => true do end it "doesn't hang on nix gemfile" do - skip "Only for ruby 3.0.0" if RUBY_VERSION != "3.0.0" + skip "Only for ruby 3.0.1" if RUBY_VERSION != "3.0.1" || RUBY_PLATFORM =~ /darwin/ gemfile <<~G source "https://rubygems.org" do diff --git a/spec/bundler/realworld/ffi_spec.rb b/spec/bundler/realworld/ffi_spec.rb new file mode 100644 index 0000000000..083ea38901 --- /dev/null +++ b/spec/bundler/realworld/ffi_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +RSpec.describe "loading dinamically linked library on a bundle exec context", :realworld => true do + it "passes ENV right after argv in memory" do + create_file "foo.rb", <<~RUBY + require 'ffi' + + module FOO + extend FFI::Library + ffi_lib './libfoo.so' + + attach_function :Hello, [], :void + end + + FOO.Hello() + RUBY + + create_file "libfoo.c", <<~'C' + #include <stdio.h> + + static int foo_init(int argc, char** argv, char** envp) { + if (argv[argc+1] == NULL) { + printf("FAIL\n"); + } else { + printf("OK\n"); + } + + return 0; + } + + #if defined(__APPLE__) && defined(__MACH__) + __attribute__((section("__DATA,__mod_init_func"), used, aligned(sizeof(void*)))) + #else + __attribute__((section(".init_array"))) + #endif + static void *ctr = &foo_init; + + extern char** environ; + + void Hello() { + return; + } + C + + sys_exec "gcc -g -o libfoo.so -shared -fpic libfoo.c" + + install_gemfile <<-G + source "https://rubygems.org" + + gem 'ffi' + G + + bundle "exec ruby foo.rb" + + expect(out).to eq("OK") + end +end diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock index 6945be3ed2..05bcb877db 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock @@ -6,7 +6,7 @@ PATH GEM remote: https://rubygems.org/ specs: - jruby-jars (9.2.14.0) + jruby-jars (9.2.16.0) jruby-rack (1.1.21) rake (13.0.1) rubyzip (1.3.0) @@ -19,6 +19,7 @@ GEM PLATFORMS java ruby + universal-java-11 DEPENDENCIES demo! @@ -26,4 +27,4 @@ DEPENDENCIES warbler (~> 2.0) BUNDLED WITH - 2.2.0.rc.2 + 2.3.0.dev diff --git a/spec/bundler/realworld/mirror_probe_spec.rb b/spec/bundler/realworld/mirror_probe_spec.rb index a2b5c89150..84d6a9c782 100644 --- a/spec/bundler/realworld/mirror_probe_spec.rb +++ b/spec/bundler/realworld/mirror_probe_spec.rb @@ -74,10 +74,10 @@ RSpec.describe "fetching dependencies with a not available mirror", :realworld = bundle :install, :artifice => nil, :raise_on_error => false expect(out).to include("Fetching source index from #{mirror}") - expect(err).to include("Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") - expect(err).to include("Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") - expect(err).to include("Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") - expect(err).to include("Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") + expect(err).to include("Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2)") + expect(err).to include("Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2)") + expect(err).to include("Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2)") + expect(err).to include("Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2)") end it "prints each error and warning on a new line" do @@ -89,12 +89,7 @@ RSpec.describe "fetching dependencies with a not available mirror", :realworld = bundle :install, :artifice => nil, :raise_on_error => false expect(out).to include "Fetching source index from #{mirror}/" - expect(err).to include <<-EOS.strip -Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)> -Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)> -Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)> -Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)> - EOS + expect(err.split("\n").count).to eq(4) end end @@ -112,10 +107,10 @@ Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUS bundle :install, :artifice => nil, :raise_on_error => false expect(out).to include("Fetching source index from #{mirror}") - expect(err).to include("Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") - expect(err).to include("Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") - expect(err).to include("Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") - expect(err).to include("Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") + expect(err).to include("Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2)") + expect(err).to include("Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2)") + expect(err).to include("Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2)") + expect(err).to include("Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2)") end end diff --git a/spec/bundler/realworld/slow_perf_spec.rb b/spec/bundler/realworld/slow_perf_spec.rb index 518da2800b..aced5a1641 100644 --- a/spec/bundler/realworld/slow_perf_spec.rb +++ b/spec/bundler/realworld/slow_perf_spec.rb @@ -4,17 +4,19 @@ require "spec_helper" RSpec.describe "bundle install with complex dependencies", :realworld => true do it "resolves quickly" do - start_time = Time.now - - install_gemfile <<-G + gemfile <<-G source 'https://rubygems.org' gem "actionmailer" gem "mongoid", ">= 0.10.2" G + start_time = Time.now + + bundle "lock" + duration = Time.now - start_time - expect(duration.to_f).to be < 120 # seconds + expect(duration.to_f).to be < 12 # seconds end end diff --git a/spec/bundler/resolver/platform_spec.rb b/spec/bundler/resolver/platform_spec.rb index 7169ba4b95..bc4081f8b5 100644 --- a/spec/bundler/resolver/platform_spec.rb +++ b/spec/bundler/resolver/platform_spec.rb @@ -28,6 +28,23 @@ RSpec.describe "Resolving platform craziness" do end end + it "resolves multiplatform gems with redundant platforms correctly" do + @index = build_index do + gem "zookeeper", "1.4.11" + gem "zookeeper", "1.4.11", "java" do + dep "slyphon-log4j", "= 1.2.15" + dep "slyphon-zookeeper_jar", "= 3.3.5" + end + gem "slyphon-log4j", "1.2.15" + gem "slyphon-zookeeper_jar", "3.3.5", "java" + end + + dep "zookeeper" + platforms "java", "ruby", "universal-java-11" + + should_resolve_as %w[zookeeper-1.4.11 zookeeper-1.4.11-java slyphon-log4j-1.2.15 slyphon-zookeeper_jar-3.3.5-java] + end + it "takes the latest ruby gem, even if an older platform specific version is available" do @index = build_index do gem "foo", "1.0.0" diff --git a/spec/bundler/runtime/executable_spec.rb b/spec/bundler/runtime/executable_spec.rb index e39338e425..a11f547648 100644 --- a/spec/bundler/runtime/executable_spec.rb +++ b/spec/bundler/runtime/executable_spec.rb @@ -63,6 +63,7 @@ RSpec.describe "Running bin/* commands" do end gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack", :path => "#{lib_path("rack")}" G diff --git a/spec/bundler/runtime/inline_spec.rb b/spec/bundler/runtime/inline_spec.rb index b46da20096..3b5ede41ee 100644 --- a/spec/bundler/runtime/inline_spec.rb +++ b/spec/bundler/runtime/inline_spec.rb @@ -2,7 +2,7 @@ RSpec.describe "bundler/inline#gemfile" do def script(code, options = {}) - requires = ["#{lib_dir}/bundler/inline"] + requires = ["#{entrypoint}/inline"] requires.unshift "#{spec_dir}/support/artifice/" + options.delete(:artifice) if options.key?(:artifice) requires = requires.map {|r| "require '#{r}'" }.join("\n") ruby("#{requires}\n\n" + code, options) @@ -46,10 +46,9 @@ RSpec.describe "bundler/inline#gemfile" do end it "requires the gems" do - skip "gems not found" if Gem.win_platform? - script <<-RUBY gemfile do + source "#{file_uri_for(gem_repo1)}" path "#{lib_path}" do gem "two" end @@ -60,6 +59,7 @@ RSpec.describe "bundler/inline#gemfile" do script <<-RUBY, :raise_on_error => false gemfile do + source "#{file_uri_for(gem_repo1)}" path "#{lib_path}" do gem "eleven" end @@ -94,10 +94,8 @@ RSpec.describe "bundler/inline#gemfile" do end it "lets me use my own ui object" do - skip "prints just one CONFIRMED" if Gem.win_platform? - script <<-RUBY, :artifice => "endpoint" - require '#{lib_dir}/bundler' + require '#{entrypoint}' class MyBundlerUI < Bundler::UI::Silent def confirm(msg, newline = nil) puts "CONFIRMED!" @@ -114,7 +112,7 @@ RSpec.describe "bundler/inline#gemfile" do it "has an option for quiet installation" do script <<-RUBY, :artifice => "endpoint" - require '#{lib_dir}/bundler/inline' + require '#{entrypoint}/inline' gemfile(true, :quiet => true) do source "https://notaserver.com" @@ -140,9 +138,10 @@ RSpec.describe "bundler/inline#gemfile" do it "does not mutate the option argument" do script <<-RUBY - require '#{lib_dir}/bundler' + require '#{entrypoint}' options = { :ui => Bundler::UI::Shell.new } gemfile(false, options) do + source "#{file_uri_for(gem_repo1)}" path "#{lib_path}" do gem "two" end @@ -172,6 +171,7 @@ RSpec.describe "bundler/inline#gemfile" do baz_ref = build_git("baz", "2.0.0").ref_for("HEAD") script <<-RUBY gemfile do + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => #{lib_path("foo-1.0.0").to_s.dump} gem "baz", :git => #{lib_path("baz-2.0.0").to_s.dump}, :ref => #{baz_ref.dump} end @@ -188,12 +188,14 @@ RSpec.describe "bundler/inline#gemfile" do script <<-RUBY gemfile do path "#{lib_path}" do + source "#{file_uri_for(gem_repo1)}" gem "two" end end gemfile do path "#{lib_path}" do + source "#{file_uri_for(gem_repo1)}" gem "four" end end @@ -222,7 +224,7 @@ RSpec.describe "bundler/inline#gemfile" do rake BUNDLED WITH - 1.13.6 + #{Bundler::VERSION} G script <<-RUBY @@ -250,6 +252,19 @@ RSpec.describe "bundler/inline#gemfile" do expect(last_command.stderr).to be_empty end + it "installs inline gems when deployment is set" do + script <<-RUBY, :env => { "BUNDLE_DEPLOYMENT" => "true" } + gemfile do + source "#{file_uri_for(gem_repo1)}" + gem "rack" + end + + puts RACK + RUBY + + expect(last_command.stderr).to be_empty + end + it "installs inline gems when BUNDLE_GEMFILE is set to an empty string" do ENV["BUNDLE_GEMFILE"] = "" @@ -358,6 +373,7 @@ RSpec.describe "bundler/inline#gemfile" do script <<-RUBY, :dir => tmp("path_without_gemfile") gemfile do + source "#{file_uri_for(gem_repo2)}" path "#{lib_path}" do gem "foo", require: false end @@ -374,7 +390,7 @@ RSpec.describe "bundler/inline#gemfile" do dependency_installer_loads_fileutils = ruby "require 'rubygems/dependency_installer'; puts $LOADED_FEATURES.grep(/fileutils/)", :raise_on_error => false skip "does not work if rubygems/dependency_installer loads fileutils, which happens until rubygems 3.2.0" unless dependency_installer_loads_fileutils.empty? - skip "does not work on ruby 3.0 because it changes the path to look for default gems, tsort is a default gem there, and we can't install it either like we do with fiddle because it doesn't yet exist" unless RUBY_VERSION < "3.0.0" + skip "pathname does not install cleanly on this ruby" if RUBY_VERSION < "2.7.0" Dir.mkdir tmp("path_without_gemfile") @@ -383,6 +399,8 @@ RSpec.describe "bundler/inline#gemfile" do realworld_system_gems "fileutils --version 1.4.1" + realworld_system_gems "pathname --version 0.2.0" + realworld_system_gems "fiddle" # not sure why, but this is needed on Windows to boot rubygems successfully realworld_system_gems "timeout uri" # this spec uses net/http which requires these default gems diff --git a/spec/bundler/runtime/load_spec.rb b/spec/bundler/runtime/load_spec.rb index 0274ba18b8..96a22a46cc 100644 --- a/spec/bundler/runtime/load_spec.rb +++ b/spec/bundler/runtime/load_spec.rb @@ -82,7 +82,7 @@ RSpec.describe "Bundler.load" do G ruby <<-RUBY - require "#{lib_dir}/bundler" + require "#{entrypoint}" Bundler.setup :default Bundler.require :default puts RACK diff --git a/spec/bundler/runtime/platform_spec.rb b/spec/bundler/runtime/platform_spec.rb index f28b5ea16b..433396d106 100644 --- a/spec/bundler/runtime/platform_spec.rb +++ b/spec/bundler/runtime/platform_spec.rb @@ -22,7 +22,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do ruby <<-R begin - require '#{lib_dir}/bundler' + require '#{entrypoint}' Bundler.ui.silence { Bundler.setup } rescue Bundler::GemNotFound => e puts "WIN" @@ -57,6 +57,126 @@ RSpec.describe "Bundler.setup with multi platform stuff" do expect(the_bundle).to include_gems "nokogiri 1.4.2" end + it "will keep both platforms when both ruby and a specific ruby platform are locked and the bundle is unlocked" do + build_repo4 do + build_gem "nokogiri", "1.11.1" do |s| + s.add_dependency "mini_portile2", "~> 2.5.0" + s.add_dependency "racc", "~> 1.5.2" + end + + build_gem "nokogiri", "1.11.1" do |s| + s.platform = Bundler.local_platform + s.add_dependency "racc", "~> 1.4" + end + + build_gem "mini_portile2", "2.5.0" + build_gem "racc", "1.5.2" + end + + good_lockfile = <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + mini_portile2 (2.5.0) + nokogiri (1.11.1) + mini_portile2 (~> 2.5.0) + racc (~> 1.5.2) + nokogiri (1.11.1-#{Bundler.local_platform}) + racc (~> 1.4) + racc (1.5.2) + + PLATFORMS + #{lockfile_platforms_for(["ruby"] + local_platforms)} + + DEPENDENCIES + nokogiri (~> 1.11) + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "nokogiri", "~> 1.11" + G + + lockfile good_lockfile + + bundle "update nokogiri" + + expect(lockfile).to eq(good_lockfile) + end + + it "will not try to install platform specific gems when they don't match the current ruby if locked only to ruby" do + build_repo4 do + build_gem "nokogiri", "1.11.1" + + build_gem "nokogiri", "1.11.1" do |s| + s.platform = Bundler.local_platform + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<-G + source "https://gems.repo4" + gem "nokogiri" + G + + lockfile <<~L + GEM + remote: https://gems.repo4/ + specs: + nokogiri (1.11.1) + + PLATFORMS + ruby + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + expect(out).to include("Fetching nokogiri 1.11.1") + expect(the_bundle).to include_gems "nokogiri 1.11.1" + expect(the_bundle).not_to include_gems "nokogiri 1.11.1 #{Bundler.local_platform}" + end + + it "will use the java platform if both generic java and generic ruby platforms are locked", :jruby do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "nokogiri" + G + + lockfile <<-G + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + nokogiri (1.4.2) + nokogiri (1.4.2-java) + weakling (>= 0.0.3) + weakling (0.0.3) + + PLATFORMS + java + ruby + + DEPENDENCIES + nokogiri + + BUNDLED WITH + 2.1.4 + G + + bundle "install" + + expect(out).to include("Fetching nokogiri 1.4.2 (java)") + expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA" + end + it "will add the resolve for the current platform" do lockfile <<-G GEM diff --git a/spec/bundler/runtime/require_spec.rb b/spec/bundler/runtime/require_spec.rb index ad30529e86..d91b5f8666 100644 --- a/spec/bundler/runtime/require_spec.rb +++ b/spec/bundler/runtime/require_spec.rb @@ -46,6 +46,7 @@ RSpec.describe "Bundler.require" do end gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "#{lib_path}" do gem "one", :group => :bar, :require => %w[baz qux] gem "two" @@ -112,6 +113,7 @@ RSpec.describe "Bundler.require" do it "raises an exception if a require is specified but the file does not exist" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "#{lib_path}" do gem "two", :require => 'fail' end @@ -130,6 +132,7 @@ RSpec.describe "Bundler.require" do end gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "#{lib_path}" do gem "faulty" end @@ -146,6 +149,7 @@ RSpec.describe "Bundler.require" do end gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "#{lib_path}" do gem "loadfuuu" end @@ -172,6 +176,7 @@ RSpec.describe "Bundler.require" do it "requires gem names that are namespaced" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path '#{lib_path}' do gem 'jquery-rails' end @@ -186,13 +191,15 @@ RSpec.describe "Bundler.require" do s.write "lib/brcrypt.rb", "BCrypt = '1.0.0'" end gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + path "#{lib_path}" do gem "bcrypt-ruby" end G cmd = <<-RUBY - require '#{lib_dir}/bundler' + require '#{entrypoint}' Bundler.require RUBY ruby(cmd) @@ -202,6 +209,7 @@ RSpec.describe "Bundler.require" do it "does not mangle explicitly given requires" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "#{lib_path}" do gem 'jquery-rails', :require => 'jquery-rails' end @@ -219,6 +227,7 @@ RSpec.describe "Bundler.require" do end gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "#{lib_path}" do gem "load-fuuu" end @@ -242,6 +251,7 @@ RSpec.describe "Bundler.require" do end gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "#{lib_path}" do gem "load-fuuu" end @@ -300,6 +310,7 @@ RSpec.describe "Bundler.require" do it "works when the gems are in the Gemfile in the correct order" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "#{lib_path}" do gem "two" gem "one" @@ -318,6 +329,7 @@ RSpec.describe "Bundler.require" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "multi_gem", :require => "one", :group => :one gem "multi_gem", :require => "two", :group => :two G @@ -341,6 +353,7 @@ RSpec.describe "Bundler.require" do it "fails when the gems are in the Gemfile in the wrong order" do gemfile <<-G + source "#{file_uri_for(gem_repo1)}" path "#{lib_path}" do gem "one" gem "two" @@ -358,6 +371,7 @@ RSpec.describe "Bundler.require" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "busted_require" G @@ -396,6 +410,7 @@ RSpec.describe "Bundler.require" do build_git "foo" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb index 7af1cd9801..35873dcaa9 100644 --- a/spec/bundler/runtime/setup_spec.rb +++ b/spec/bundler/runtime/setup_spec.rb @@ -108,8 +108,8 @@ RSpec.describe "Bundler.setup" do context "load order" do def clean_load_path(lp) without_bundler_load_path = ruby("puts $LOAD_PATH").split("\n") - lp -= without_bundler_load_path - lp.map! {|p| p.sub(/^#{Regexp.union system_gem_path.to_s, default_bundle_path.to_s, lib_dir.to_s}/i, "") } + lp -= [*without_bundler_load_path, lib_dir.to_s] + lp.map! {|p| p.sub(system_gem_path.to_s, "") } end it "puts loaded gems after -I and RUBYLIB", :ruby_repo do @@ -143,12 +143,8 @@ RSpec.describe "Bundler.setup" do gem "rails" G - # We require an absolute path because relying on the $LOAD_PATH behaves - # inconsistently depending on whether we're in a ruby-core setup (and - # bundler's lib is in RUBYLIB) or not. - ruby <<-RUBY - require '#{lib_dir}/bundler' + require 'bundler' Bundler.setup puts $LOAD_PATH RUBY @@ -157,7 +153,6 @@ RSpec.describe "Bundler.setup" do expect(load_path).to start_with( "/gems/rails-2.3.2/lib", - "/gems/bundler-#{Bundler::VERSION}/lib", "/gems/activeresource-2.3.2/lib", "/gems/activerecord-2.3.2/lib", "/gems/actionpack-2.3.2/lib", @@ -168,6 +163,8 @@ RSpec.describe "Bundler.setup" do end it "falls back to order the load path alphabetically for backwards compatibility" do + bundle "config set path.system true" + install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "weakling" @@ -175,12 +172,8 @@ RSpec.describe "Bundler.setup" do gem "terranova" G - # We require an absolute path because relying on the $LOAD_PATH behaves - # inconsistently depending on whether we're in a ruby-core setup (and - # bundler's lib is in RUBYLIB) or not. - ruby <<-RUBY - require '#{lib_dir}/bundler/setup' + require 'bundler/setup' puts $LOAD_PATH RUBY @@ -200,8 +193,6 @@ RSpec.describe "Bundler.setup" do gem "rack" G - entrypoint = mis_activates_prerelease_default_bundler? ? "#{lib_dir}/bundler" : "bundler" - ruby <<-R require '#{entrypoint}' @@ -439,6 +430,7 @@ RSpec.describe "Bundler.setup" do build_git "rack", "1.0.0" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rack", :git => "#{lib_path("rack-1.0.0")}" G end @@ -474,8 +466,6 @@ RSpec.describe "Bundler.setup" do break_git! - entrypoint = mis_activates_prerelease_default_bundler? ? "#{lib_dir}/bundler" : "bundler" - ruby <<-R require "#{entrypoint}" @@ -493,14 +483,14 @@ RSpec.describe "Bundler.setup" do end it "works even when the cache directory has been deleted" do - bundle "config --local path vendor/bundle" + bundle "config set --local path vendor/bundle" bundle :install FileUtils.rm_rf vendored_gems("cache") expect(the_bundle).to include_gems "rack 1.0.0" end it "does not randomly change the path when specifying --path and the bundle directory becomes read only" do - bundle "config --local path vendor/bundle" + bundle "config set --local path vendor/bundle" bundle :install with_read_only("#{bundled_app}/**/*") do @@ -604,7 +594,7 @@ RSpec.describe "Bundler.setup" do describe "when excluding groups" do it "doesn't change the resolve if --without is used" do - bundle "config --local without rails" + bundle "config set --local without rails" install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "activesupport" @@ -620,7 +610,7 @@ RSpec.describe "Bundler.setup" do end it "remembers --without and does not bail on bare Bundler.setup" do - bundle "config --local without rails" + bundle "config set --local without rails" install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "activesupport" @@ -636,7 +626,7 @@ RSpec.describe "Bundler.setup" do end it "remembers --without and does not bail on bare Bundler.setup, even in the case of path gems no longer available" do - bundle "config --local without development" + bundle "config set --local without development" path = bundled_app(File.join("vendor", "foo")) build_lib "foo", :path => path @@ -655,8 +645,27 @@ RSpec.describe "Bundler.setup" do expect(err).to be_empty end + it "doesn't re-resolve when a pre-release bundler is used and a dependency includes a dependency on bundler" do + system_gems "bundler-9.99.9.beta1" + + build_repo4 do + build_gem "depends_on_bundler", "1.0" do |s| + s.add_dependency "bundler", ">= 1.5.0" + end + end + + install_gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "depends_on_bundler" + G + + ruby "require '#{system_gem_path("gems/bundler-9.99.9.beta1/lib/bundler.rb")}'; Bundler.setup", :env => { "DEBUG" => "1" } + expect(out).to include("Found no changes, using resolution from the lockfile") + expect(err).to be_empty + end + it "remembers --without and does not include groups passed to Bundler.setup" do - bundle "config --local without rails" + bundle "config set --local without rails" install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "activesupport" @@ -736,41 +745,68 @@ end expect(err).to be_empty end - describe "$MANPATH" do - before do + context "when the user has `MANPATH` set", :man do + before { ENV["MANPATH"] = "/foo#{File::PATH_SEPARATOR}" } + + it "adds the gem's man dir to the MANPATH" do build_repo4 do build_gem "with_man" do |s| s.write("man/man1/page.1", "MANPAGE") end end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "with_man" + G + + run "puts ENV['MANPATH']" + expect(out).to eq("#{default_bundle_path("gems/with_man-1.0/man")}#{File::PATH_SEPARATOR}/foo") end + end - context "when the user has one set" do - before { ENV["MANPATH"] = "/foo#{File::PATH_SEPARATOR}" } + context "when the user does not have `MANPATH` set", :man do + before { ENV.delete("MANPATH") } - it "adds the gem's man dir to the MANPATH" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "with_man" - G + it "adds the gem's man dir to the MANPATH, leaving : in the end so that system man pages still work" do + build_repo4 do + build_gem "with_man" do |s| + s.write("man/man1/page.1", "MANPAGE") + end - run "puts ENV['MANPATH']" - expect(out).to eq("#{default_bundle_path("gems/with_man-1.0/man")}#{File::PATH_SEPARATOR}/foo") + build_gem "with_man_overriding_system_man" do |s| + s.write("man/man1/ls.1", "LS MANPAGE") + end end - end - context "when the user does not have one set" do - before { ENV.delete("MANPATH") } + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "with_man" + G - it "adds the gem's man dir to the MANPATH" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "with_man" - G + run <<~RUBY + puts ENV['MANPATH'] + require "open3" + puts Open3.capture2e("man", "ls")[1].success? + RUBY - run "puts ENV['MANPATH']" - expect(out).to eq(default_bundle_path("gems/with_man-1.0/man").to_s) - end + expect(out).to eq("#{default_bundle_path("gems/with_man-1.0/man")}#{File::PATH_SEPARATOR}\ntrue") + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "with_man_overriding_system_man" + G + + run <<~RUBY + puts ENV['MANPATH'] + require "open3" + puts Open3.capture2e("man", "ls")[0] + RUBY + + lines = out.split("\n") + + expect(lines).to include("#{default_bundle_path("gems/with_man_overriding_system_man-1.0/man")}#{File::PATH_SEPARATOR}") + expect(lines).to include("LS MANPAGE") end end @@ -849,7 +885,7 @@ end end it "should not remove itself from the LOAD_PATH and require a different copy of 'bundler/setup'" do - install_gemfile "" + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" ruby <<-R, :env => { "GEM_PATH" => symlinked_gem_home } TracePoint.trace(:class) do |tp| @@ -898,6 +934,7 @@ end FileUtils.rm(File.join(path, "foo.gemspec")) install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', '1.2.3', :path => 'vendor/foo' G @@ -918,6 +955,7 @@ end FileUtils.rm(File.join(absolute_path, "foo.gemspec")) gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', '1.2.3', :path => '#{relative_path}' G @@ -936,6 +974,7 @@ end build_git "no_gemspec", :gemspec => false install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "no_gemspec", "1.0", :git => "#{lib_path("no_gemspec-1.0")}" G end @@ -1038,6 +1077,7 @@ end end gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "bar", :git => "#{lib_path("bar-1.0")}" G end @@ -1058,7 +1098,6 @@ end expect(err.lines.map(&:chomp)).to include( a_string_starting_with("[!] There was an error while loading `bar.gemspec`:"), - a_string_starting_with("Does it try to require a relative path? That's been removed in Ruby 1.9."), " # from #{default_bundle_path "bundler", "gems", "bar-1.0-#{ref[0, 12]}", "bar.gemspec"}:1", " > require 'foobarbaz'" ) @@ -1085,6 +1124,7 @@ end describe "when Bundler is bundled" do it "doesn't blow up" do install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "bundler", :path => "#{root}" G @@ -1095,7 +1135,7 @@ end describe "when BUNDLED WITH" do def lock_with(bundler_version = nil) - lock = <<-L + lock = <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -1109,7 +1149,7 @@ end L if bundler_version - lock += "\n BUNDLED WITH\n #{bundler_version}\n" + lock += "\nBUNDLED WITH\n #{bundler_version}\n" end lock @@ -1126,10 +1166,9 @@ end context "is not present" do it "does not change the lock" do - entrypoint = mis_activates_prerelease_default_bundler? ? "#{lib_dir}/bundler/setup" : "bundler/setup" lockfile lock_with(nil) - ruby "require '#{entrypoint}'" - lockfile_should_be lock_with(nil) + ruby "require '#{entrypoint}/setup'" + expect(lockfile).to eq lock_with(nil) end end @@ -1139,17 +1178,16 @@ end ruby "require 'bundler/setup'" expect(out).to be_empty expect(err).to be_empty - lockfile_should_be lock_with(Bundler::VERSION.succ) + expect(lockfile).to eq lock_with(Bundler::VERSION.succ) end end context "is older" do it "does not change the lock" do - entrypoint = mis_activates_prerelease_default_bundler? ? "#{lib_dir}/bundler/setup" : "bundler/setup" system_gems "bundler-1.10.1" lockfile lock_with("1.10.1") - ruby "require '#{entrypoint}'" - lockfile_should_be lock_with("1.10.1") + ruby "require '#{entrypoint}/setup'" + expect(lockfile).to eq lock_with("1.10.1") end end end @@ -1158,7 +1196,7 @@ end let(:ruby_version) { nil } def lock_with(ruby_version = nil) - lock = <<-L + lock = <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -1172,10 +1210,10 @@ end L if ruby_version - lock += "\n RUBY VERSION\n ruby #{ruby_version}\n" + lock += "\nRUBY VERSION\n ruby #{ruby_version}\n" end - lock += <<-L + lock += <<~L BUNDLED WITH #{Bundler::VERSION} @@ -1217,11 +1255,45 @@ end end describe "with gemified standard libraries" do + it "does not load Digest", :ruby_repo do + skip "Only for Ruby 3.0+" unless RUBY_VERSION >= "3.0" + + build_git "bar", :gemspec => false do |s| + s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0') + s.write "bar.gemspec", <<-G + require_relative 'lib/bar/version' + + Gem::Specification.new do |s| + s.name = 'bar' + s.version = BAR_VERSION + s.summary = 'Bar' + s.files = Dir["lib/**/*.rb"] + s.author = 'no one' + + s.add_runtime_dependency 'digest' + end + G + end + + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "bar", :git => "#{lib_path("bar-1.0")}" + G + + bundle :install + + ruby <<-RUBY + require '#{entrypoint}/setup' + puts defined?(::Digest) ? "Digest defined" : "Digest undefined" + require 'digest' + RUBY + expect(out).to eq("Digest undefined") + end + it "does not load Psych" do - gemfile "" - entrypoint = mis_activates_prerelease_default_bundler? ? "#{lib_dir}/bundler/setup" : "bundler/setup" + gemfile "source \"#{file_uri_for(gem_repo1)}\"" ruby <<-RUBY - require '#{entrypoint}' + require '#{entrypoint}/setup' puts defined?(Psych::VERSION) ? Psych::VERSION : "undefined" require 'psych' puts Psych::VERSION @@ -1232,7 +1304,7 @@ end end it "does not load openssl" do - install_gemfile "" + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" ruby <<-RUBY require "bundler/setup" puts defined?(OpenSSL) || "undefined" @@ -1244,16 +1316,18 @@ end describe "default gem activation" do let(:exemptions) do - exempts = if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("2.7") + exempts = if Gem.rubygems_version >= Gem::Version.new("2.7") %w[did_you_mean] else %w[io-console openssl] end << "bundler" - exempts << "fiddle" if Gem.win_platform? && Gem::Version.new(Gem::VERSION) >= Gem::Version.new("2.7") - exempts << "uri" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7") - exempts << "pathname" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.0") - exempts << "set" unless Gem::Version.new(Gem::VERSION) >= Gem::Version.new("3.2.6") - exempts << "tsort" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.0") + exempts << "fiddle" if Gem.win_platform? && Gem.rubygems_version >= Gem::Version.new("2.7") + exempts << "uri" if Gem.ruby_version >= Gem::Version.new("2.7") + exempts << "pathname" if Gem.ruby_version >= Gem::Version.new("3.0") + exempts << "set" unless Gem.rubygems_version >= Gem::Version.new("3.2.6") + exempts << "tsort" unless Gem.rubygems_version >= Gem::Version.new("3.2.31") + exempts << "error_highlight" # added in Ruby 3.1 as a default gem + exempts << "ruby2_keywords" # added in Ruby 3.1 as a default gem exempts end @@ -1291,13 +1365,13 @@ end RUBY it "activates no gems with -rbundler/setup" do - install_gemfile "" + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" ruby code, :env => { "RUBYOPT" => activation_warning_hack_rubyopt + " -rbundler/setup" } expect(out).to eq("{}") end it "activates no gems with bundle exec" do - install_gemfile "" + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" create_file("script.rb", code) bundle "exec ruby ./script.rb", :env => { "RUBYOPT" => activation_warning_hack_rubyopt } expect(out).to eq("{}") @@ -1306,7 +1380,7 @@ end it "activates no gems with bundle exec that is loaded" do skip "not executable" if Gem.win_platform? - install_gemfile "" + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" create_file("script.rb", "#!/usr/bin/env ruby\n\n#{code}") FileUtils.chmod(0o777, bundled_app("script.rb")) bundle "exec ./script.rb", :artifice => nil, :env => { "RUBYOPT" => activation_warning_hack_rubyopt } @@ -1421,10 +1495,44 @@ end expect(last_command.stdboth).to eq("true") end - end - # Tested rubygems does not include https://github.com/rubygems/rubygems/pull/2728 and will not always end up activating the current bundler - def mis_activates_prerelease_default_bundler? - Gem.rubygems_version < Gem::Version.new("3.1.a") + it "memoizes initial set of specs when requiring bundler/setup, so that even if further code mutates dependencies, Bundler.definition.specs is not affected" do + install_gemfile <<~G + source "#{file_uri_for(gem_repo1)}" + gem "yard" + gem "rack", :group => :test + G + + ruby <<-RUBY, :raise_on_error => false + require "bundler/setup" + Bundler.require(:test).select! {|d| (d.groups & [:test]).any? } + puts Bundler.definition.specs.map(&:name).join(", ") + RUBY + + expect(out).to include("rack, yard") + end + + it "does not cause double loads when higher versions of default gems are activated before bundler" do + build_repo2 do + build_gem "json", "999.999.999" do |s| + s.write "lib/json.rb", <<~RUBY + module JSON + VERSION = "999.999.999" + end + RUBY + end + end + + system_gems "json-999.999.999", :gem_repo => gem_repo2 + + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" + ruby <<-RUBY + require "json" + require "bundler/setup" + require "json" + RUBY + + expect(err).to be_empty + end end end diff --git a/spec/bundler/runtime/with_unbundled_env_spec.rb b/spec/bundler/runtime/with_unbundled_env_spec.rb index 03de830ea0..731a9921a2 100644 --- a/spec/bundler/runtime/with_unbundled_env_spec.rb +++ b/spec/bundler/runtime/with_unbundled_env_spec.rb @@ -8,7 +8,7 @@ RSpec.describe "Bundler.with_env helpers" do def build_bundler_context(options = {}) bundle "config set path vendor/bundle" - gemfile "" + gemfile "source \"#{file_uri_for(gem_repo1)}\"" bundle "install", options end diff --git a/spec/bundler/spec_helper.rb b/spec/bundler/spec_helper.rb index 68d8537715..4d558b9907 100644 --- a/spec/bundler/spec_helper.rb +++ b/spec/bundler/spec_helper.rb @@ -13,6 +13,7 @@ require "bundler" require "rspec/core" require "rspec/expectations" require "rspec/mocks" +require "rspec/support/differ" require_relative "support/builders" require_relative "support/build_metadata" @@ -65,20 +66,15 @@ RSpec.configure do |config| mocks.allow_message_expectations_on_nil = false end - config.around :each do |example| - if ENV["RUBY"] - orig_ruby = Gem.ruby - Gem.ruby = ENV["RUBY"] - end - example.run - Gem.ruby = orig_ruby if ENV["RUBY"] - end - config.before :suite do + Gem.ruby = ENV["RUBY"] if ENV["RUBY"] + require_relative "support/rubygems_ext" Spec::Rubygems.test_setup ENV["BUNDLE_SPEC_RUN"] = "true" ENV["BUNDLE_USER_CONFIG"] = ENV["BUNDLE_USER_CACHE"] = ENV["BUNDLE_USER_PLUGIN"] = nil + ENV["RUBYGEMS_GEMDEPS"] = nil + ENV["XDG_CONFIG_HOME"] = nil ENV["GEMRC"] = nil # Don't wrap output in tests @@ -89,6 +85,8 @@ RSpec.configure do |config| end config.before :all do + check_test_gems! + build_repo1 reset_paths! diff --git a/spec/bundler/support/api_request_limit_hax.rb b/spec/bundler/support/api_request_limit_hax.rb new file mode 100644 index 0000000000..37ff0203b3 --- /dev/null +++ b/spec/bundler/support/api_request_limit_hax.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +if ENV["BUNDLER_SPEC_API_REQUEST_LIMIT"] + require_relative "path" + require "bundler/source" + require "bundler/source/rubygems" + + module Bundler + class Source + class Rubygems < Source + remove_const :API_REQUEST_LIMIT + API_REQUEST_LIMIT = ENV["BUNDLER_SPEC_API_REQUEST_LIMIT"].to_i + end + end + end +end diff --git a/spec/bundler/support/artifice/compact_index.rb b/spec/bundler/support/artifice/compact_index.rb index 5cf3a79f29..1b314e89ef 100644 --- a/spec/bundler/support/artifice/compact_index.rb +++ b/spec/bundler/support/artifice/compact_index.rb @@ -62,7 +62,7 @@ class CompactIndexAPI < Endpoint body.byteslice(range) end - def gems(gem_repo = GEM_REPO) + def gems(gem_repo = default_gem_repo) @gems ||= {} @gems[gem_repo] ||= begin specs = Bundler::Deprecate.skip_during do @@ -80,7 +80,7 @@ class CompactIndexAPI < Endpoint CompactIndex::Dependency.new(d.name, reqs) end checksum = begin - Digest(:SHA256).file("#{GEM_REPO}/gems/#{spec.original_name}.gem").base64digest + Digest(:SHA256).file("#{gem_repo}/gems/#{spec.original_name}.gem").base64digest rescue StandardError nil end diff --git a/spec/bundler/support/artifice/compact_index_partial_update_no_etag_not_incremental.rb b/spec/bundler/support/artifice/compact_index_partial_update_no_etag_not_incremental.rb new file mode 100644 index 0000000000..acf76dfbf0 --- /dev/null +++ b/spec/bundler/support/artifice/compact_index_partial_update_no_etag_not_incremental.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require_relative "compact_index" + +Artifice.deactivate + +class CompactIndexPartialUpdateNoEtagNotIncremental < CompactIndexAPI + def partial_update_no_etag + response_body = yield + headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60" + content_type "text/plain" + requested_range_for(response_body) + end + + get "/versions" do + partial_update_no_etag do + file = tmp("versions.list") + FileUtils.rm_f(file) + file = CompactIndex::VersionsFile.new(file.to_s) + file.create(gems) + lines = file.contents([], :calculate_info_checksums => true).split("\n") + name, versions, checksum = lines.last.split(" ") + + # shuffle versions so new versions are not appended to the end + [*lines[0..-2], [name, versions.split(",").reverse.join(","), checksum].join(" ")].join("\n") + end + end + + get "/info/:name" do + partial_update_no_etag do + gem = gems.find {|g| g.name == params[:name] } + lines = CompactIndex.info(gem ? gem.versions : []).split("\n") + + # shuffle versions so new versions are not appended to the end + [lines.first, lines.last, *lines[1..-2]].join("\n") + end + end +end + +Artifice.activate_with(CompactIndexPartialUpdateNoEtagNotIncremental) diff --git a/spec/bundler/support/artifice/compact_index_rate_limited.rb b/spec/bundler/support/artifice/compact_index_rate_limited.rb index ba17476045..570105e2a0 100644 --- a/spec/bundler/support/artifice/compact_index_rate_limited.rb +++ b/spec/bundler/support/artifice/compact_index_rate_limited.rb @@ -7,7 +7,7 @@ Artifice.deactivate class CompactIndexRateLimited < CompactIndexAPI class RequestCounter def self.queue - @queue ||= Queue.new + @queue ||= Thread::Queue.new end def self.size diff --git a/spec/bundler/support/artifice/endpoint.rb b/spec/bundler/support/artifice/endpoint.rb index 1dc7101389..4a820e5a3f 100644 --- a/spec/bundler/support/artifice/endpoint.rb +++ b/spec/bundler/support/artifice/endpoint.rb @@ -8,7 +8,7 @@ require "artifice" require "sinatra/base" ALL_REQUESTS = [] # rubocop:disable Style/MutableConstant -ALL_REQUESTS_MUTEX = Mutex.new +ALL_REQUESTS_MUTEX = Thread::Mutex.new at_exit do if expected = ENV["BUNDLER_SPEC_ALL_REQUESTS"] @@ -26,7 +26,6 @@ class Endpoint < Sinatra::Base @all_requests ||= [] end - GEM_REPO = Pathname.new(ENV["BUNDLER_SPEC_GEM_REPO"] || Spec::Path.gem_repo1) set :raise_errors, true set :show_exceptions, false @@ -41,7 +40,26 @@ class Endpoint < Sinatra::Base helpers do include Spec::Path - def dependencies_for(gem_names, gem_repo = GEM_REPO) + def default_gem_repo + if ENV["BUNDLER_SPEC_GEM_REPO"] + Pathname.new(ENV["BUNDLER_SPEC_GEM_REPO"]) + else + case request.host + when "gem.repo1" + Spec::Path.gem_repo1 + when "gem.repo2" + Spec::Path.gem_repo2 + when "gem.repo3" + Spec::Path.gem_repo3 + when "gem.repo4" + Spec::Path.gem_repo4 + else + Spec::Path.gem_repo1 + end + end + end + + def dependencies_for(gem_names, gem_repo = default_gem_repo) return [] if gem_names.nil? || gem_names.empty? all_specs = %w[specs.4.8 prerelease_specs.4.8].map do |filename| @@ -74,11 +92,11 @@ class Endpoint < Sinatra::Base end get "/fetch/actual/gem/:id" do - File.binread("#{GEM_REPO}/quick/Marshal.4.8/#{params[:id]}") + File.binread("#{default_gem_repo}/quick/Marshal.4.8/#{params[:id]}") end get "/gems/:id" do - File.binread("#{GEM_REPO}/gems/#{params[:id]}") + File.binread("#{default_gem_repo}/gems/#{params[:id]}") end get "/api/v1/dependencies" do @@ -86,11 +104,11 @@ class Endpoint < Sinatra::Base end get "/specs.4.8.gz" do - File.binread("#{GEM_REPO}/specs.4.8.gz") + File.binread("#{default_gem_repo}/specs.4.8.gz") end get "/prerelease_specs.4.8.gz" do - File.binread("#{GEM_REPO}/prerelease_specs.4.8.gz") + File.binread("#{default_gem_repo}/prerelease_specs.4.8.gz") end end diff --git a/spec/bundler/support/artifice/vcr.rb b/spec/bundler/support/artifice/vcr.rb index 88c33d93dc..0d51201bef 100644 --- a/spec/bundler/support/artifice/vcr.rb +++ b/spec/bundler/support/artifice/vcr.rb @@ -133,6 +133,19 @@ class BundlerVCRHTTP < Net::HTTP end end + def start_with_vcr + if ENV["BUNDLER_SPEC_PRE_RECORDED"] + raise IOError, "HTTP session already opened" if @started + @socket = nil + @started = true + else + start_without_vcr + end + end + + alias_method :start_without_vcr, :start + alias_method :start, :start_with_vcr + def request_with_vcr(request, *args, &block) handler = request.instance_eval do remove_instance_variable(:@__vcr_request_handler) if defined?(@__vcr_request_handler) diff --git a/spec/bundler/support/artifice/windows.rb b/spec/bundler/support/artifice/windows.rb index f5b4baae30..ddbbd62b96 100644 --- a/spec/bundler/support/artifice/windows.rb +++ b/spec/bundler/support/artifice/windows.rb @@ -14,7 +14,7 @@ class Windows < Sinatra::Base set :show_exceptions, false helpers do - def gem_repo + def default_gem_repo Pathname.new(ENV["BUNDLER_SPEC_GEM_REPO"] || Spec::Path.gem_repo1) end end @@ -26,7 +26,7 @@ class Windows < Sinatra::Base files.each do |file| get "/#{file}" do - File.binread gem_repo.join(file) + File.binread default_gem_repo.join(file) end end diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index c76c3f505e..90e9cbb242 100644 --- a/spec/bundler/support/builders.rb +++ b/spec/bundler/support/builders.rb @@ -30,7 +30,16 @@ module Spec end def build_repo1 + rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first + build_repo gem_repo1 do + FileUtils.cp rake_path, "#{gem_repo1}/gems/" + + build_gem "coffee-script-source" + build_gem "git" + build_gem "puma" + build_gem "minitest" + build_gem "rack", %w[0.9.1 1.0.0] do |s| s.executables = "rackup" s.post_install_message = "Rack's post install message" @@ -150,32 +159,6 @@ module Spec build_gem "duradura", "7.0" - build_gem "with_implicit_rake_dep" do |s| - s.extensions << "Rakefile" - s.write "Rakefile", <<-RUBY - task :default do - path = File.expand_path("../lib", __FILE__) - FileUtils.mkdir_p(path) - File.open("\#{path}/implicit_rake_dep.rb", "w") do |f| - f.puts "IMPLICIT_RAKE_DEP = 'YES'" - end - end - RUBY - end - - build_gem "another_implicit_rake_dep" do |s| - s.extensions << "Rakefile" - s.write "Rakefile", <<-RUBY - task :default do - path = File.expand_path("../lib", __FILE__) - FileUtils.mkdir_p(path) - File.open("\#{path}/another_implicit_rake_dep.rb", "w") do |f| - f.puts "ANOTHER_IMPLICIT_RAKE_DEP = 'YES'" - end - end - RUBY - end - build_gem "very_simple_binary", &:add_c_extension build_gem "simple_binary", &:add_c_extension @@ -214,13 +197,6 @@ module Spec update_repo2(&blk) if block_given? end - def build_repo3 - build_repo gem_repo3 do - build_gem "rack" - end - FileUtils.rm_rf Dir[gem_repo3("prerelease*")] - end - # A repo that has no pre-installed gems included. (The caller completely # determines the contents with the block.) def build_repo4(&blk) @@ -255,6 +231,13 @@ module Spec def build_repo(path, &blk) return if File.directory?(path) + + FileUtils.mkdir_p("#{path}/gems") + + update_repo(path, &blk) + end + + def check_test_gems! rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first if rake_path.nil? @@ -263,14 +246,9 @@ module Spec rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first end - if rake_path - FileUtils.mkdir_p("#{path}/gems") - FileUtils.cp rake_path, "#{path}/gems/" - else + if rake_path.nil? abort "Your test gems are missing! Run `rm -rf #{tmp}` and try again." end - - update_repo(path, &blk) end def update_repo(path) @@ -575,17 +553,8 @@ module Spec update_gemspec = options[:gemspec] || false source = options[:source] || "git@#{libpath}" - @context.git "checkout master", libpath - if branch = options[:branch] - raise "You can't specify `master` as the branch" if branch == "master" - escaped_branch = Shellwords.shellescape(branch) - - if @context.git("branch -l #{escaped_branch}", libpath).empty? - @context.git("branch #{escaped_branch}", libpath) - end - - @context.git("checkout #{escaped_branch}", libpath) + @context.git("checkout -b #{Shellwords.shellescape(branch)}", libpath) elsif tag = options[:tag] @context.git("tag #{Shellwords.shellescape(tag)}", libpath) elsif options[:remote] @@ -599,8 +568,7 @@ module Spec _default_files[path] += "\n#{Builders.constantize(name)}_PREV_REF = '#{current_ref}'" end super(options.merge(:path => libpath, :gemspec => update_gemspec, :source => source)) - @context.git("add *", libpath) - @context.git("commit -m BUMP", libpath, :raise_on_error => false) + @context.git("commit -am BUMP", libpath) end end diff --git a/spec/bundler/support/bundle.rb b/spec/bundler/support/bundle.rb new file mode 100644 index 0000000000..bb21526d35 --- /dev/null +++ b/spec/bundler/support/bundle.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "rubygems" +require_relative "path" +bundler_gemspec = Spec::Path.loaded_gemspec +bundler_gemspec.instance_variable_set(:@full_gem_path, Spec::Path.source_root) +bundler_gemspec.activate if bundler_gemspec.respond_to?(:activate) +load File.expand_path("bundle", Spec::Path.bindir) diff --git a/spec/bundler/support/filters.rb b/spec/bundler/support/filters.rb index 0c1f27e470..3b91897a2e 100644 --- a/spec/bundler/support/filters.rb +++ b/spec/bundler/support/filters.rb @@ -34,6 +34,7 @@ RSpec.configure do |config| config.filter_run_excluding :readline => Gem.win_platform? config.filter_run_excluding :jruby => RUBY_ENGINE != "jruby" config.filter_run_excluding :truffleruby => RUBY_ENGINE != "truffleruby" + config.filter_run_excluding :man => Gem.win_platform? config.filter_run_when_matching :focus unless ENV["CI"] end diff --git a/spec/bundler/support/hax.rb b/spec/bundler/support/hax.rb index fc8e0ad55d..0ad5239128 100644 --- a/spec/bundler/support/hax.rb +++ b/spec/bundler/support/hax.rb @@ -9,7 +9,10 @@ module Gem Gem.ruby = ENV["RUBY"] end - @default_dir = ENV["BUNDLER_GEM_DEFAULT_DIR"] if ENV["BUNDLER_GEM_DEFAULT_DIR"] + if ENV["BUNDLER_GEM_DEFAULT_DIR"] + @default_dir = ENV["BUNDLER_GEM_DEFAULT_DIR"] + @default_specifications_dir = nil + end if ENV["BUNDLER_SPEC_PLATFORM"] class Platform @@ -28,35 +31,14 @@ module Gem end end + if ENV["BUNDLER_SPEC_GEM_SOURCES"] + @sources = [ENV["BUNDLER_SPEC_GEM_SOURCES"]] + end + # We only need this hack for rubygems versions without the BundlerVersionFinder - if Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0") + if Gem.rubygems_version < Gem::Version.new("2.7.0") @path_to_default_spec_map.delete_if do |_path, spec| spec.name == "bundler" end end end - -if ENV["BUNDLER_SPEC_WINDOWS"] == "true" - require_relative "path" - require "bundler/constants" - - module Bundler - remove_const :WINDOWS if defined?(WINDOWS) - WINDOWS = true - end -end - -if ENV["BUNDLER_SPEC_API_REQUEST_LIMIT"] - require_relative "path" - require "bundler/source" - require "bundler/source/rubygems" - - module Bundler - class Source - class Rubygems < Source - remove_const :API_REQUEST_LIMIT - API_REQUEST_LIMIT = ENV["BUNDLER_SPEC_API_REQUEST_LIMIT"].to_i - end - end - end -end diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb index a7cc1ce810..e44c67835f 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -10,7 +10,7 @@ module Spec def reset! Dir.glob("#{tmp}/{gems/*,*}", File::FNM_DOTMATCH).each do |dir| - next if %w[base base_system remote1 gems rubygems . ..].include?(File.basename(dir)) + next if %w[base base_system remote1 rubocop standard gems rubygems . ..].include?(File.basename(dir)) FileUtils.rm_rf(dir) end FileUtils.mkdir_p(home) @@ -60,7 +60,7 @@ module Spec def run(cmd, *args) opts = args.last.is_a?(Hash) ? args.pop : {} groups = args.map(&:inspect).join(", ") - setup = "require '#{lib_dir}/bundler' ; Bundler.ui.silence { Bundler.setup(#{groups}) }" + setup = "require '#{entrypoint}' ; Bundler.ui.silence { Bundler.setup(#{groups}) }" ruby([setup, cmd].join(" ; "), opts) end @@ -130,7 +130,7 @@ module Spec def ruby(ruby, options = {}) ruby_cmd = build_ruby_cmd - escaped_ruby = RUBY_PLATFORM == "java" ? ruby.shellescape.dump : ruby.shellescape + escaped_ruby = ruby.shellescape sys_exec(%(#{ruby_cmd} -w -e #{escaped_ruby}), options) end @@ -218,7 +218,7 @@ module Spec end def all_commands_output - return [] if command_executions.empty? + return "" if command_executions.empty? "\n\nCommands:\n#{command_executions.map(&:to_s_verbose).join("\n\n")}" end @@ -455,20 +455,15 @@ module Spec end def simulate_windows(platform = mswin) - old = ENV["BUNDLER_SPEC_WINDOWS"] - ENV["BUNDLER_SPEC_WINDOWS"] = "true" simulate_platform platform do simulate_bundler_version_when_missing_prerelease_default_gem_activation do yield end end - ensure - ENV["BUNDLER_SPEC_WINDOWS"] = old end - # workaround for missing https://github.com/rubygems/rubygems/commit/929e92d752baad3a08f3ac92eaec162cb96aedd1 def simulate_bundler_version_when_missing_prerelease_default_gem_activation - return yield unless Gem.rubygems_version < Gem::Version.new("3.1.0.pre.1") + return yield unless rubygems_version_failing_to_activate_bundler_prereleases old = ENV["BUNDLER_VERSION"] ENV["BUNDLER_VERSION"] = Bundler::VERSION @@ -477,15 +472,20 @@ module Spec ENV["BUNDLER_VERSION"] = old end - # workaround for missing https://github.com/rubygems/rubygems/commit/929e92d752baad3a08f3ac92eaec162cb96aedd1 def env_for_missing_prerelease_default_gem_activation - if Gem.rubygems_version < Gem::Version.new("3.1.0.pre.1") + if rubygems_version_failing_to_activate_bundler_prereleases { "BUNDLER_VERSION" => Bundler::VERSION } else {} end end + # versions providing a bundler version finder but not including + # https://github.com/rubygems/rubygems/commit/929e92d752baad3a08f3ac92eaec162cb96aedd1 + def rubygems_version_failing_to_activate_bundler_prereleases + Gem.rubygems_version < Gem::Version.new("3.1.0.pre.1") && Gem.rubygems_version >= Gem::Version.new("2.7.0") + end + def revision_for(path) sys_exec("git rev-parse HEAD", :dir => path).strip end diff --git a/spec/bundler/support/indexes.rb b/spec/bundler/support/indexes.rb index 45b68541f5..638f394e76 100644 --- a/spec/bundler/support/indexes.rb +++ b/spec/bundler/support/indexes.rb @@ -17,20 +17,19 @@ module Spec def resolve(args = []) @platforms ||= ["ruby"] deps = [] - default_source = instance_double("Bundler::Source::Rubygems", :specs => @index) + default_source = instance_double("Bundler::Source::Rubygems", :specs => @index, :to_s => "locally install gems") source_requirements = { :default => default_source } @deps.each do |d| + source_requirements[d.name] = d.source = default_source @platforms.each do |p| - source_requirements[d.name] = d.source = default_source deps << Bundler::DepProxy.get_proxy(d, p) end end - source_requirements ||= {} args[0] ||= [] # base args[1] ||= Bundler::GemVersionPromoter.new # gem_version_promoter args[2] ||= [] # additional_base_requirements args[3] ||= @platforms # platforms - Bundler::Resolver.resolve(deps, @index, source_requirements, *args) + Bundler::Resolver.resolve(deps, source_requirements, *args) end def should_resolve_as(specs) diff --git a/spec/bundler/support/matchers.rb b/spec/bundler/support/matchers.rb index a6944495ff..3c2a7f9f58 100644 --- a/spec/bundler/support/matchers.rb +++ b/spec/bundler/support/matchers.rb @@ -114,30 +114,49 @@ module Spec match do opts = names.last.is_a?(Hash) ? names.pop : {} source = opts.delete(:source) - groups = Array(opts[:groups]) + groups = Array(opts.delete(:groups)).map(&:inspect).join(", ") opts[:raise_on_error] = false - groups << opts - @errors = names.map do |name| - name, version, platform = name.split(/\s+/) - require_path = name == "bundler" ? "#{lib_dir}/bundler" : name.tr("-", "/") + @errors = names.map do |full_name| + name, version, platform = full_name.split(/\s+/) + require_path = name.tr("-", "/") version_const = name == "bundler" ? "Bundler::VERSION" : Spec::Builders.constantize(name) - code = [] - code << "require '#{require_path}.rb'" - code << "puts #{version_const}" - run code.join("; "), *groups - actual_version, actual_platform = out.strip.split(/\s+/, 2) - unless Gem::Version.new(actual_version) == Gem::Version.new(version) + source_const = "#{Spec::Builders.constantize(name)}_SOURCE" + ruby <<~R, opts + require 'bundler' + Bundler.setup(#{groups}) + + require '#{require_path}' + actual_version, actual_platform = #{version_const}.split(/\s+/, 2) + unless Gem::Version.new(actual_version) == Gem::Version.new('#{version}') + puts actual_version + exit 64 + end + unless actual_platform.to_s == '#{platform}' + puts actual_platform + exit 65 + end + require '#{require_path}/source' + exit 0 if #{source.nil?} + actual_source = #{source_const} + unless actual_source == '#{source}' + puts actual_source + exit 66 + end + R + next if exitstatus == 0 + if exitstatus == 64 + actual_version = out.split("\n").last next "#{name} was expected to be at version #{version} but was #{actual_version}" end - unless actual_platform == platform + if exitstatus == 65 + actual_platform = out.split("\n").last next "#{name} was expected to be of platform #{platform} but was #{actual_platform}" end - next unless source - source_const = "#{Spec::Builders.constantize(name)}_SOURCE" - run "require '#{require_path}/source'; puts #{source_const}", *groups - unless out.strip == source - next "Expected #{name} (#{version}) to be installed from `#{source}`, was actually from `#{out}`" + if exitstatus == 66 + actual_source = out.split("\n").last + next "Expected #{name} (#{version}) to be installed from `#{source}`, was actually from `#{actual_source}`" end + next "Command to check for inclusion of gem #{full_name} failed" end.compact @errors.empty? @@ -145,23 +164,34 @@ module Spec match_when_negated do opts = names.last.is_a?(Hash) ? names.pop : {} - groups = Array(opts[:groups]) || [] + groups = Array(opts.delete(:groups)).map(&:inspect).join(", ") opts[:raise_on_error] = false @errors = names.map do |name| name, version = name.split(/\s+/, 2) - run <<-R, *(groups + [opts]) + ruby <<-R, opts + begin + require 'bundler' + Bundler.setup(#{groups}) + rescue Bundler::GemNotFound, Bundler::GitError + exit 0 + end + begin require '#{name}' - puts #{Spec::Builders.constantize(name)} + name_constant = '#{Spec::Builders.constantize(name)}' + if #{version.nil?} || name_constant == '#{version}' + exit 64 + else + exit 0 + end rescue LoadError, NameError - puts "WIN" + exit 0 end R - next if out == "WIN" + next if exitstatus == 0 + next "command to check version of #{name} installed failed" unless exitstatus == 64 next "expected #{name} to not be installed, but it was" if version.nil? - if Gem::Version.new(out) == Gem::Version.new(version) - next "expected #{name} (#{version}) not to be installed, but it was" - end + next "expected #{name} (#{version}) not to be installed, but it was" end.compact @errors.empty? @@ -178,10 +208,6 @@ module Spec RSpec::Matchers.define_negated_matcher :not_include_gems, :include_gems RSpec::Matchers.alias_matcher :include_gem, :include_gems - def have_lockfile(expected) - read_as(strip_whitespace(expected)) - end - def plugin_should_be_installed(*names) names.each do |name| expect(Bundler::Plugin).to be_installed(name) @@ -195,13 +221,5 @@ module Spec expect(Bundler::Plugin).not_to be_installed(name) end end - - def lockfile_should_be(expected) - expect(bundled_app_lock).to have_lockfile(expected) - end - - def gemfile_should_be(expected) - expect(bundled_app_gemfile).to read_as(strip_whitespace(expected)) - end end end diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index 56d3c71f3c..a73b3e699e 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -6,7 +6,7 @@ require "rbconfig" module Spec module Path def source_root - @source_root ||= Pathname.new(ruby_core? ? "../../../.." : "../../..").expand_path(__FILE__) + @source_root ||= Pathname.new(ruby_core? ? "../../.." : "../..").expand_path(__dir__) end def root @@ -30,7 +30,15 @@ module Spec end def test_gemfile - @test_gemfile ||= source_root.join(ruby_core? ? "tool/bundler/test_gems.rb" : "test_gems.rb") + @test_gemfile ||= source_root.join("tool/bundler/test_gems.rb") + end + + def rubocop_gemfile + @rubocop_gemfile ||= source_root.join(rubocop_gemfile_basename) + end + + def standard_gemfile + @standard_gemfile ||= source_root.join(standard_gemfile_basename) end def dev_gemfile @@ -63,6 +71,10 @@ module Spec @spec_dir ||= source_root.join(ruby_core? ? "spec/bundler" : "spec") end + def api_request_limit_hack_file + spec_dir.join("support/api_request_limit_hax.rb") + end + def man_dir @man_dir ||= lib_dir.join("bundler/man") end @@ -119,7 +131,7 @@ module Spec end def vendored_gems(path = nil) - bundled_app(*["vendor/bundle", Gem.ruby_engine, RbConfig::CONFIG["ruby_version"], path].compact) + scoped_gem_path(bundled_app("vendor/bundle")).join(*[path].compact) end def cached_gem(path) @@ -138,6 +150,14 @@ module Spec tmp.join("gems/base") end + def rubocop_gems + tmp.join("gems/rubocop") + end + + def standard_gems + tmp.join("gems/standard") + end + def file_uri_for(path) protocol = "file://" root = Gem.win_platform? ? "/" : "" @@ -178,7 +198,11 @@ module Spec end def local_gem_path(*path, base: bundled_app) - base.join(*[".bundle", Gem.ruby_engine, RbConfig::CONFIG["ruby_version"], *path].compact) + scoped_gem_path(base.join(".bundle")).join(*path) + end + + def scoped_gem_path(base) + base.join(Gem.ruby_engine, RbConfig::CONFIG["ruby_version"]) end def lib_path(*args) @@ -193,6 +217,13 @@ module Spec root.join("lib") end + # Sometimes rubygems version under test does not include + # https://github.com/rubygems/rubygems/pull/2728 and will not always end up + # activating the current bundler. In that case, require bundler absolutely. + def entrypoint + Gem.rubygems_version < Gem::Version.new("3.1.a") ? "#{lib_dir}/bundler" : "bundler" + end + def global_plugin_gem(*args) home ".bundle", "plugin", "gems", *args end @@ -251,6 +282,28 @@ module Spec !git_root.join(".git").directory? end + def rubocop_gemfile_basename + filename = if RUBY_VERSION.start_with?("2.3") + "rubocop23_gems" + elsif RUBY_VERSION.start_with?("2.4") + "rubocop24_gems" + else + "rubocop_gems" + end + source_root.join("tool/bundler/#{filename}.rb") + end + + def standard_gemfile_basename + filename = if RUBY_VERSION.start_with?("2.3") + "standard23_gems" + elsif RUBY_VERSION.start_with?("2.4") + "standard24_gems" + else + "standard_gems" + end + source_root.join("tool/bundler/#{filename}.rb") + end + extend self end end diff --git a/spec/bundler/support/platforms.rb b/spec/bundler/support/platforms.rb index 0cb7f7cd29..07973fd727 100644 --- a/spec/bundler/support/platforms.rb +++ b/spec/bundler/support/platforms.rb @@ -90,7 +90,11 @@ module Spec end def lockfile_platforms - local_platforms.map(&:to_s).sort.join("\n ") + lockfile_platforms_for(local_platforms) + end + + def lockfile_platforms_for(platforms) + platforms.map(&:to_s).sort.join("\n ") end def local_platforms diff --git a/spec/bundler/support/rubygems_ext.rb b/spec/bundler/support/rubygems_ext.rb index d743a76391..f957440ab0 100644 --- a/spec/bundler/support/rubygems_ext.rb +++ b/spec/bundler/support/rubygems_ext.rb @@ -9,7 +9,7 @@ module Spec extend self def dev_setup - install_gems(dev_gemfile, dev_lockfile) + install_gems(dev_gemfile) end def gem_load(gem_name, bin_container) @@ -39,18 +39,19 @@ module Spec end def install_parallel_test_deps + Gem.clear_paths + require "parallel" + require "fileutils" - prev_env_test_number = ENV["TEST_ENV_NUMBER"] + install_test_deps - begin - Parallel.processor_count.times do |n| - ENV["TEST_ENV_NUMBER"] = (n + 1).to_s + (2..Parallel.processor_count).each do |n| + source = Path.source_root.join("tmp", "1") + destination = Path.source_root.join("tmp", n.to_s) - install_test_deps - end - ensure - ENV["TEST_ENV_NUMBER"] = prev_env_test_number + FileUtils.rm_rf destination + FileUtils.cp_r source, destination end end @@ -66,30 +67,18 @@ module Spec def install_test_deps setup_test_paths - workaround_loaded_specs_issue - - install_gems(test_gemfile, test_lockfile) + install_gems(test_gemfile) + install_gems(rubocop_gemfile, Path.rubocop_gems.to_s) + install_gems(standard_gemfile, Path.standard_gems.to_s) end private - # Some rubygems versions include loaded specs when loading gemspec stubs - # from the file system. In this situation, that makes bundler incorrectly - # assume that `rake` is already installed at `tmp/` because it's installed - # globally, and makes it skip installing it to the proper location for our - # tests. To workaround, we remove `rake` from the loaded specs when running - # under those versions, so that `bundler` does the right thing. - def workaround_loaded_specs_issue - current_rubygems_version = Gem::Version.new(Gem::VERSION) - - Gem.loaded_specs.delete("rake") if current_rubygems_version >= Gem::Version.new("3.0.0.beta2") && current_rubygems_version < Gem::Version.new("3.2.0") - end - def gem_load_and_activate(gem_name, bin_container) gem_activate(gem_name) load Gem.bin_path(gem_name, bin_container) rescue Gem::LoadError => e - abort "We couln't activate #{gem_name} (#{e.requirement}). Run `gem install #{gem_name}:'#{e.requirement}'`" + abort "We couldn't activate #{gem_name} (#{e.requirement}). Run `gem install #{gem_name}:'#{e.requirement}'`" end def gem_activate(gem_name) @@ -98,14 +87,27 @@ module Spec gem gem_name, gem_requirement end - def install_gems(gemfile, lockfile) + def install_gems(gemfile, path = nil) old_gemfile = ENV["BUNDLE_GEMFILE"] ENV["BUNDLE_GEMFILE"] = gemfile.to_s - require "bundler" - definition = Bundler::Definition.build(gemfile, lockfile, nil) - definition.validate_runtime! - Bundler::Installer.install(Path.source_root, definition, :path => ENV["GEM_HOME"]) + + if path + old_path = ENV["BUNDLE_PATH"] + ENV["BUNDLE_PATH"] = path + else + old_path__system = ENV["BUNDLE_PATH__SYSTEM"] + ENV["BUNDLE_PATH__SYSTEM"] = "true" + end + + output = `#{Gem.ruby} #{File.expand_path("support/bundle.rb", Path.spec_dir)} install` + raise "Error when installing gems in #{gemfile}: #{output}" unless $?.success? ensure + if path + ENV["BUNDLE_PATH"] = old_path + else + ENV["BUNDLE_PATH__SYSTEM"] = old_path__system + end + ENV["BUNDLE_GEMFILE"] = old_gemfile end @@ -113,8 +115,12 @@ module Spec Path.test_gemfile end - def test_lockfile - lockfile_for(test_gemfile) + def rubocop_gemfile + Path.rubocop_gemfile + end + + def standard_gemfile + Path.standard_gemfile end def dev_gemfile diff --git a/spec/bundler/support/rubygems_version_manager.rb b/spec/bundler/support/rubygems_version_manager.rb index c2e5a5f484..d1b1f8dd03 100644 --- a/spec/bundler/support/rubygems_version_manager.rb +++ b/spec/bundler/support/rubygems_version_manager.rb @@ -24,12 +24,6 @@ class RubygemsVersionManager def assert_system_features_not_loaded! at_exit do - errors = if $?.nil? - "" - else - all_commands_output - end - rubylibdir = RbConfig::CONFIG["rubylibdir"] rubygems_path = rubylibdir + "/rubygems" @@ -43,11 +37,11 @@ class RubygemsVersionManager (loaded_feature.start_with?(bundler_path) && !bundler_exemptions.any? {|bundler_exemption| loaded_feature.start_with?(bundler_exemption) }) end - if bad_loaded_features.any? - errors += "the following features were incorrectly loaded:\n#{bad_loaded_features.join("\n")}" + errors = if bad_loaded_features.any? + all_commands_output + "the following features were incorrectly loaded:\n#{bad_loaded_features.join("\n")}" end - raise errors unless errors.empty? + raise errors if errors end end diff --git a/spec/bundler/update/git_spec.rb b/spec/bundler/update/git_spec.rb index bf078fa576..f02fb37d0f 100644 --- a/spec/bundler/update/git_spec.rb +++ b/spec/bundler/update/git_spec.rb @@ -7,12 +7,13 @@ RSpec.describe "bundle update" do update_git "foo", :branch => "omg" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}", :branch => "omg" do gem 'foo' end G - update_git "foo", :branch => "omg" do |s| + update_git "foo" do |s| s.write "lib/foo.rb", "FOO = '1.1'" end @@ -28,6 +29,7 @@ RSpec.describe "bundle update" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rails", :git => "#{file_uri_for(lib_path("rails"))}" G @@ -40,12 +42,13 @@ RSpec.describe "bundle update" do update_git "foo", :branch => "omg", :path => lib_path("foo") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo")}", :branch => "omg" do gem 'foo' end G - update_git "foo", :branch => "omg", :path => lib_path("foo") do |s| + update_git "foo", :path => lib_path("foo") do |s| s.write "lib/foo.rb", "FOO = '1.1'" end @@ -61,6 +64,7 @@ RSpec.describe "bundle update" do end install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{file_uri_for(lib_path("foo"))}" gem "bar" G @@ -79,12 +83,14 @@ RSpec.describe "bundle update" do build_git "foo", :path => lib_path("foo_two") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", "1.0", :git => "#{file_uri_for(lib_path("foo_one"))}" G FileUtils.rm_rf lib_path("foo_one") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", "1.0", :git => "#{file_uri_for(lib_path("foo_two"))}" G @@ -100,6 +106,7 @@ RSpec.describe "bundle update" do update_git "foo", :push => "master" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', :git => "#{@remote.path}" G @@ -108,6 +115,7 @@ RSpec.describe "bundle update" do update_git "foo", :push => "fubar" gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem 'foo', :git => "#{@remote.path}", :tag => "fubar" G @@ -116,6 +124,9 @@ RSpec.describe "bundle update" do describe "with submodules" do before :each do + # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/ + system(*%W[git config --global protocol.file.allow always]) + build_repo4 do build_gem "submodule" do |s| s.write "lib/submodule.rb", "puts 'GEM'" @@ -183,6 +194,7 @@ RSpec.describe "bundle update" do build_git "foo", "1.0" install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}" G @@ -214,6 +226,7 @@ RSpec.describe "bundle update" do build_git "rails", "2.3.2", :path => lib_path("rails") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "rails", :git => "#{file_uri_for(lib_path("rails"))}" G @@ -283,43 +296,7 @@ RSpec.describe "bundle update" do G end - it "the --source flag updates version of gems that were originally pulled in by the source", :bundler => "< 3" do - spec_lines = lib_path("bar/foo.gemspec").read.split("\n") - spec_lines[5] = "s.version = '2.0'" - - update_git "foo", "2.0", :path => @git.path do |s| - s.write "foo.gemspec", spec_lines.join("\n") - end - - ref = @git.ref_for "master" - - bundle "update --source bar" - - lockfile_should_be <<-G - GIT - remote: #{@git.path} - revision: #{ref} - specs: - foo (2.0) - - GEM - remote: #{file_uri_for(gem_repo2)}/ - specs: - rack (1.0.0) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - foo! - rack - - BUNDLED WITH - #{Bundler::VERSION} - G - end - - it "the --source flag updates version of gems that were originally pulled in by the source", :bundler => "3" do + it "the --source flag updates version of gems that were originally pulled in by the source" do spec_lines = lib_path("bar/foo.gemspec").read.split("\n") spec_lines[5] = "s.version = '2.0'" @@ -331,7 +308,7 @@ RSpec.describe "bundle update" do bundle "update --source bar" - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GIT remote: #{@git.path} revision: #{ref} diff --git a/spec/bundler/update/path_spec.rb b/spec/bundler/update/path_spec.rb index 38c125e04b..756770313b 100644 --- a/spec/bundler/update/path_spec.rb +++ b/spec/bundler/update/path_spec.rb @@ -6,6 +6,7 @@ RSpec.describe "path sources" do build_lib "activesupport", "2.3.5", :path => lib_path("rails/activesupport") install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" gem "activesupport", :path => "#{lib_path("rails/activesupport")}" G diff --git a/spec/ruby/core/hash/transform_keys_spec.rb b/spec/ruby/core/hash/transform_keys_spec.rb index 2c5d4124e0..a7ea80d053 100644 --- a/spec/ruby/core/hash/transform_keys_spec.rb +++ b/spec/ruby/core/hash/transform_keys_spec.rb @@ -84,7 +84,7 @@ describe "Hash#transform_keys!" do end end - ruby_version_is "2.5.1" do + ruby_version_is "2.5.1"..."3.0.2" do it "returns the processed keys if we broke from the block" do @hash.transform_keys! do |v| break if v == :c @@ -94,6 +94,16 @@ describe "Hash#transform_keys!" do end end + ruby_version_is "3.0.2" do + it "returns the processed keys and non evaluated keys if we broke from the block" do + @hash.transform_keys! do |v| + break if v == :c + v.succ + end + @hash.should == { b: 1, c: 2, d: 4 } + end + end + it "keeps later pair if new keys conflict" do @hash.transform_keys! { |_| :a }.should == { a: 4 } end diff --git a/spec/ruby/core/marshal/shared/load.rb b/spec/ruby/core/marshal/shared/load.rb index 9d42b9df4a..97023c1640 100644 --- a/spec/ruby/core/marshal/shared/load.rb +++ b/spec/ruby/core/marshal/shared/load.rb @@ -20,42 +20,62 @@ describe :marshal_load, shared: true do end describe "when called with a proc" do - it "returns the value of the proc" do - Marshal.send(@method, Marshal.dump([1,2]), proc { [3,4] }).should == [3,4] - end + ruby_bug "#18141", ""..."3.1" do + it "call the proc with fully initialized strings" do + utf8_string = "foo".encode(Encoding::UTF_8) + Marshal.send(@method, Marshal.dump(utf8_string), proc { |arg| + if arg.is_a?(String) + arg.should == utf8_string + arg.encoding.should == Encoding::UTF_8 + end + arg + }) + end - it "calls the proc for recursively visited data" do - a = [1] - a << a - ret = [] - Marshal.send(@method, Marshal.dump(a), proc { |arg| ret << arg; arg }) - ret.first.should == 1 - ret[1].should == [1,a] - ret[2].should == a - ret.size.should == 3 + it "no longer mutate the object after it was passed to the proc" do + string = Marshal.load(Marshal.dump("foo"), :freeze.to_proc) + string.should.frozen? + end end - it "loads an Array with proc" do - arr = [] - s = 'hi' - s.instance_variable_set(:@foo, 5) - st = Struct.new("Brittle", :a).new - st.instance_variable_set(:@clue, 'none') - st.a = 0.0 - h = Hash.new('def') - h['nine'] = 9 - a = [:a, :b, :c] - a.instance_variable_set(:@two, 2) - obj = [s, 10, s, s, st, a] - obj.instance_variable_set(:@zoo, 'ant') - proc = Proc.new { |o| arr << o; o} - - Marshal.send(@method, "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F", proc) + it "returns the value of the proc" do + Marshal.send(@method, Marshal.dump([1,2]), proc { [3,4] }).should == [3,4] + end - arr.should == ["hi", false, 5, 10, "hi", "hi", 0.0, st, "none", false, - :b, :c, a, 2, ["hi", 10, "hi", "hi", st, [:a, :b, :c]], "ant", false] + ruby_bug "#18141", ""..."3.1" do + it "calls the proc for recursively visited data" do + a = [1] + a << a + ret = [] + Marshal.send(@method, Marshal.dump(a), proc { |arg| ret << arg.inspect; arg }) + ret[0].should == 1.inspect + ret[1].should == a.inspect + ret.size.should == 2 + end - Struct.send(:remove_const, :Brittle) + it "loads an Array with proc" do + arr = [] + s = 'hi' + s.instance_variable_set(:@foo, 5) + st = Struct.new("Brittle", :a).new + st.instance_variable_set(:@clue, 'none') + st.a = 0.0 + h = Hash.new('def') + h['nine'] = 9 + a = [:a, :b, :c] + a.instance_variable_set(:@two, 2) + obj = [s, 10, s, s, st, a] + obj.instance_variable_set(:@zoo, 'ant') + proc = Proc.new { |o| arr << o.dup; o} + + Marshal.send(@method, "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F", proc) + + arr.should == [ + false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st, + :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]], + ] + Struct.send(:remove_const, :Brittle) + end end end @@ -119,28 +139,39 @@ describe :marshal_load, shared: true do end end - it "loads an array containing objects having _dump method, and with proc" do - arr = [] - myproc = Proc.new { |o| arr << o; o } - o1 = UserDefined.new; - o2 = UserDefinedWithIvar.new - obj = [o1, o2, o1, o2] + ruby_bug "#18141", ""..."3.1" do + it "loads an array containing objects having _dump method, and with proc" do + arr = [] + myproc = Proc.new { |o| arr << o.dup; o } + o1 = UserDefined.new; + o2 = UserDefinedWithIvar.new + obj = [o1, o2, o1, o2] + + Marshal.send(@method, "\x04\b[\tu:\x10UserDefined\x18\x04\b[\aI\"\nstuff\x06:\x06EF@\x06u:\x18UserDefinedWithIvar>\x04\b[\bI\"\nstuff\a:\x06EF:\t@foo:\x18UserDefinedWithIvarI\"\tmore\x06;\x00F@\a@\x06@\a", myproc) - Marshal.send(@method, "\x04\b[\tu:\x10UserDefined\x18\x04\b[\aI\"\nstuff\x06:\x06EF@\x06u:\x18UserDefinedWithIvar>\x04\b[\bI\"\nstuff\a:\x06EF:\t@foo:\x18UserDefinedWithIvarI\"\tmore\x06;\x00F@\a@\x06@\a", myproc) + arr[0].should == o1 + arr[1].should == o2 + arr[2].should == obj + arr.size.should == 3 + end - arr.should == [o1, o2, o1, o2, obj] - end + it "loads an array containing objects having marshal_dump method, and with proc" do + arr = [] + proc = Proc.new { |o| arr << o.dup; o } + o1 = UserMarshal.new + o2 = UserMarshalWithIvar.new - it "loads an array containing objects having marshal_dump method, and with proc" do - arr = [] - proc = Proc.new { |o| arr << o; o } - o1 = UserMarshal.new - o2 = UserMarshalWithIvar.new - obj = [o1, o2, o1, o2] + Marshal.send(@method, "\004\b[\tU:\020UserMarshal\"\nstuffU:\030UserMarshalWithIvar[\006\"\fmy data@\006@\b", proc) - Marshal.send(@method, "\004\b[\tU:\020UserMarshal\"\nstuffU:\030UserMarshalWithIvar[\006\"\fmy data@\006@\b", proc) + arr[0].should == 'stuff' + arr[1].should == o1 + arr[2].should == 'my data' + arr[3].should == ['my data'] + arr[4].should == o2 + arr[5].should == [o1, o2, o1, o2] - arr.should == ['stuff', o1, 'my data', ['my data'], o2, o1, o2, obj] + arr.size.should == 6 + end end it "assigns classes to nested subclasses of Array correctly" do diff --git a/spec/ruby/core/string/lstrip_spec.rb b/spec/ruby/core/string/lstrip_spec.rb index 12c7b13200..6b40189500 100644 --- a/spec/ruby/core/string/lstrip_spec.rb +++ b/spec/ruby/core/string/lstrip_spec.rb @@ -7,11 +7,13 @@ describe "String#lstrip" do " hello world ".lstrip.should == "hello world " "\n\r\t\n\v\r hello world ".lstrip.should == "hello world " "hello".lstrip.should == "hello" - "\000 \000hello\000 \000".lstrip.should == "\000 \000hello\000 \000" end - it "does not strip leading \\0" do - "\x00hello".lstrip.should == "\x00hello" + ruby_version_is '3.1' do + it "strips leading \\0" do + "\x00hello".lstrip.should == "hello" + "\000 \000hello\000 \000".lstrip.should == "hello\000 \000" + end end ruby_version_is ''...'2.7' do @@ -28,10 +30,14 @@ describe "String#lstrip!" do a = " hello " a.lstrip!.should equal(a) a.should == "hello " + end - a = "\000 \000hello\000 \000" - a.lstrip! - a.should == "\000 \000hello\000 \000" + ruby_version_is '3.1' do + it "strips leading \\0" do + a = "\000 \000hello\000 \000" + a.lstrip! + a.should == "hello\000 \000" + end end it "returns nil if no modifications were made" do diff --git a/spec/ruby/core/string/strip_spec.rb b/spec/ruby/core/string/strip_spec.rb index 252d4a9cc3..463a9fedf3 100644 --- a/spec/ruby/core/string/strip_spec.rb +++ b/spec/ruby/core/string/strip_spec.rb @@ -6,11 +6,12 @@ describe "String#strip" do " hello ".strip.should == "hello" " hello world ".strip.should == "hello world" "\tgoodbye\r\v\n".strip.should == "goodbye" - "\x00 goodbye \x00".strip.should == "\x00 goodbye" end - it "returns a copy of self with trailing NULL bytes and whitespace" do - " \x00 goodbye \x00 ".strip.should == "\x00 goodbye" + ruby_version_is '3.1' do + it "returns a copy of self without leading and trailing NULL bytes and whitespace" do + " \x00 goodbye \x00 ".strip.should == "goodbye" + end end ruby_version_is ''...'2.7' do @@ -31,11 +32,6 @@ describe "String#strip!" do a = "\tgoodbye\r\v\n" a.strip! a.should == "goodbye" - - a = "\000 goodbye \000" - a.strip! - a.should == "\000 goodbye" - end it "returns nil if no modifications where made" do @@ -44,10 +40,12 @@ describe "String#strip!" do a.should == "hello" end - it "modifies self removing trailing NULL bytes and whitespace" do - a = " \x00 goodbye \x00 " - a.strip! - a.should == "\x00 goodbye" + ruby_version_is '3.1' do + it "removes leading and trailing NULL bytes and whitespace" do + a = "\000 goodbye \000" + a.strip! + a.should == "goodbye" + end end it "raises a FrozenError on a frozen instance that is modified" do diff --git a/spec/ruby/core/time/shared/local.rb b/spec/ruby/core/time/shared/local.rb index 43f331c4c1..997b7186f1 100644 --- a/spec/ruby/core/time/shared/local.rb +++ b/spec/ruby/core/time/shared/local.rb @@ -6,6 +6,7 @@ describe :time_local, shared: true do end end +=begin platform_is_not :windows do describe "timezone changes" do it "correctly adjusts the timezone change to 'CEST' on 'Europe/Amsterdam'" do @@ -16,6 +17,7 @@ describe :time_local, shared: true do end end end +=end end describe :time_local_10_arg, shared: true do diff --git a/spec/ruby/library/cgi/cookie/name_spec.rb b/spec/ruby/library/cgi/cookie/name_spec.rb index 14226824c8..326a43ade3 100644 --- a/spec/ruby/library/cgi/cookie/name_spec.rb +++ b/spec/ruby/library/cgi/cookie/name_spec.rb @@ -6,18 +6,18 @@ describe "CGI::Cookie#name" do cookie = CGI::Cookie.new("test-cookie") cookie.name.should == "test-cookie" - cookie = CGI::Cookie.new("name" => "another cookie") - cookie.name.should == "another cookie" + cookie = CGI::Cookie.new("name" => "another-cookie") + cookie.name.should == "another-cookie" end end describe "CGI::Cookie#name=" do it "sets self's expiration date" do cookie = CGI::Cookie.new("test-cookie") - cookie.name = "another name" - cookie.name.should == "another name" + cookie.name = "another-name" + cookie.name.should == "another-name" - cookie.name = "and one more" - cookie.name.should == "and one more" + cookie.name = "and-one-more" + cookie.name.should == "and-one-more" end end diff --git a/spec/ruby/library/cgi/cookie/parse_spec.rb b/spec/ruby/library/cgi/cookie/parse_spec.rb index 90d2c3d148..d484c7bad9 100644 --- a/spec/ruby/library/cgi/cookie/parse_spec.rb +++ b/spec/ruby/library/cgi/cookie/parse_spec.rb @@ -6,16 +6,16 @@ describe "CGI::Cookie.parse" do expected = { "test-cookie" => ["one", "two", "three"] } CGI::Cookie.parse("test-cookie=one&two&three").should == expected - expected = { "second cookie" => ["three", "four"], "first cookie" => ["one", "two"] } - CGI::Cookie.parse("first cookie=one&two;second cookie=three&four").should == expected + expected = { "second-cookie" => ["three", "four"], "first-cookie" => ["one", "two"] } + CGI::Cookie.parse("first-cookie=one&two;second-cookie=three&four").should == expected end it "does not use , for cookie separators" do expected = { - "first cookie" => ["one", "two"], - "second cookie" => ["three", "four,third_cookie=five", "six"] + "first-cookie" => ["one", "two"], + "second-cookie" => ["three", "four,third_cookie=five", "six"] } - CGI::Cookie.parse("first cookie=one&two;second cookie=three&four,third_cookie=five&six").should == expected + CGI::Cookie.parse("first-cookie=one&two;second-cookie=three&four,third_cookie=five&six").should == expected end it "unescapes the Cookie values" do @@ -1356,7 +1356,6 @@ st_shift(st_table *tab, st_data_t *key, st_data_t *value) return 1; } } - tab->entries_start = tab->entries_bound = 0; if (value != 0) *value = 0; return 0; } @@ -414,6 +414,11 @@ setup_fake_str(struct RString *fake_str, const char *name, long len, int encidx) fake_str->basic.flags = T_STRING|RSTRING_NOEMBED|STR_NOFREE|STR_FAKESTR; /* SHARED to be allocated by the callback */ + if (!name) { + RUBY_ASSERT_ALWAYS(len == 0); + name = ""; + } + ENCODING_SET_INLINED((VALUE)fake_str, encidx); RBASIC_SET_CLASS_RAW((VALUE)fake_str, rb_cString); @@ -698,6 +703,24 @@ rb_enc_cr_str_exact_copy(VALUE dest, VALUE src) ENC_CODERANGE_SET(dest, ENC_CODERANGE(src)); } +static int +enc_coderange_scan(VALUE str, rb_encoding *enc, int encidx) +{ + if (rb_enc_mbminlen(enc) > 1 && rb_enc_dummy_p(enc) && + rb_enc_mbminlen(enc = get_actual_encoding(encidx, str)) == 1) { + return ENC_CODERANGE_BROKEN; + } + else { + return coderange_scan(RSTRING_PTR(str), RSTRING_LEN(str), enc); + } +} + +int +rb_enc_str_coderange_scan(VALUE str, rb_encoding *enc) +{ + return enc_coderange_scan(str, enc, rb_enc_to_index(enc)); +} + int rb_enc_str_coderange(VALUE str) { @@ -706,14 +729,7 @@ rb_enc_str_coderange(VALUE str) if (cr == ENC_CODERANGE_UNKNOWN) { int encidx = ENCODING_GET(str); rb_encoding *enc = rb_enc_from_index(encidx); - if (rb_enc_mbminlen(enc) > 1 && rb_enc_dummy_p(enc) && - rb_enc_mbminlen(enc = get_actual_encoding(encidx, str)) == 1) { - cr = ENC_CODERANGE_BROKEN; - } - else { - cr = coderange_scan(RSTRING_PTR(str), RSTRING_LEN(str), - enc); - } + cr = enc_coderange_scan(str, enc, encidx); ENC_CODERANGE_SET(str, cr); } return cr; @@ -955,6 +971,15 @@ static VALUE str_cat_conv_enc_opts(VALUE newstr, long ofs, const char *ptr, long rb_encoding *from, rb_encoding *to, int ecflags, VALUE ecopts); +static inline bool +is_enc_ascii_string(VALUE str, rb_encoding *enc) +{ + int encidx = rb_enc_to_index(enc); + if (rb_enc_get_index(str) == encidx) + return is_ascii_string(str); + return enc_coderange_scan(str, enc, encidx) == ENC_CODERANGE_7BIT; +} + VALUE rb_str_conv_enc_opts(VALUE str, rb_encoding *from, rb_encoding *to, int ecflags, VALUE ecopts) { @@ -965,7 +990,7 @@ rb_str_conv_enc_opts(VALUE str, rb_encoding *from, rb_encoding *to, int ecflags, if (!to) return str; if (!from) from = rb_enc_get(str); if (from == to) return str; - if ((rb_enc_asciicompat(to) && is_ascii_string(str)) || + if ((rb_enc_asciicompat(to) && is_enc_ascii_string(str, from)) || to == rb_ascii8bit_encoding()) { if (STR_ENC_GET(str) != to) { str = rb_str_dup(str); @@ -1716,7 +1741,7 @@ rb_str_init(int argc, VALUE *argv, VALUE str) const size_t osize = RSTRING(str)->as.heap.len + TERM_LEN(str); char *new_ptr = ALLOC_N(char, (size_t)capa + termlen); memcpy(new_ptr, old_ptr, osize < size ? osize : size); - FL_UNSET_RAW(str, STR_SHARED); + FL_UNSET_RAW(str, STR_SHARED|STR_NOFREE); RSTRING(str)->as.heap.ptr = new_ptr; } else if (STR_HEAP_SIZE(str) != (size_t)capa + termlen) { @@ -3135,13 +3160,13 @@ rb_str_concat_literals(size_t num, const VALUE *strary) /* * call-seq: - * string.concat(*objects) -> new_string + * string.concat(*objects) -> string * - * Returns a new \String containing the concatenation - * of +self+ and all objects in +objects+: + * Concatenates each object in +objects+ to +self+ and returns +self+: * * s = 'foo' * s.concat('bar', 'baz') # => "foobarbaz" + * s # => "foobarbaz" * * For each given object +object+ that is an \Integer, * the value is considered a codepoint and converted to a character before concatenation: @@ -3173,12 +3198,13 @@ rb_str_concat_multi(int argc, VALUE *argv, VALUE str) /* * call-seq: - * string << object -> str + * string << object -> string + * + * Concatenates +object+ to +self+ and returns +self+: * - * Returns a new \String containing the concatenation - * of +self+ and +object+: * s = 'foo' * s << 'bar' # => "foobar" + * s # => "foobar" * * If +object+ is an \Integer, * the value is considered a codepoint and converted to a character before concatenation: @@ -3253,12 +3279,12 @@ rb_str_concat(VALUE str1, VALUE str2) /* * call-seq: - * string.prepend(*other_strings) -> str + * string.prepend(*other_strings) -> string * - * Returns a new \String containing the concatenation - * of all given +other_strings+ and +self+: + * Prepends each string in +other_strings+ to +self+ and returns +self+: * s = 'foo' * s.prepend('bar', 'baz') # => "barbazfoo" + * s # => "barbazfoo" * * Related: String#concat. */ @@ -3434,9 +3460,9 @@ rb_str_eql(VALUE str1, VALUE str2) * string <=> other_string -> -1, 0, 1, or nil * * Compares +self+ and +other_string+, returning: - * - -1 if +other_string+ is smaller. + * - -1 if +other_string+ is larger. * - 0 if the two are equal. - * - 1 if +other_string+ is larger. + * - 1 if +other_string+ is smaller. * - +nil+ if the two are incomparable. * * Examples: @@ -3468,9 +3494,9 @@ static VALUE str_casecmp_p(VALUE str1, VALUE str2); * str.casecmp(other_str) -> -1, 0, 1, or nil * * Compares +self+ and +other_string+, ignoring case, and returning: - * - -1 if +other_string+ is smaller. + * - -1 if +other_string+ is larger. * - 0 if the two are equal. - * - 1 if +other_string+ is larger. + * - 1 if +other_string+ is smaller. * - +nil+ if the two are incomparable. * * Examples: @@ -3825,7 +3851,6 @@ rb_str_rindex(VALUE str, VALUE sub, long pos) return str_rindex(str, sub, s, pos, enc); } - /* * call-seq: * string.rindex(substring, offset = self.length) -> integer or nil @@ -3845,6 +3870,23 @@ rb_str_rindex(VALUE str, VALUE sub, long pos) * 'foo'.rindex(/oo/) # => 1 * 'foo'.rindex(/ooo/) # => nil * + * The _last_ match means starting at the possible last position, not + * the last of longest matches. + * + * 'foo'.rindex(/o+/) # => 2 + * $~ #=> #<MatchData "o"> + * + * To get the last longest match, needs to combine with negative + * lookbehind. + * + * 'foo'.rindex(/(?<!o)o+/) # => 1 + * $~ #=> #<MatchData "oo"> + * + * Or String#index with negative lookforward. + * + * 'foo'.index(/o+(?!.*o)/) # => 1 + * $~ #=> #<MatchData "oo"> + * * \Integer argument +offset+, if given and non-negative, specifies the maximum starting position in the * string to _end_ the search: * 'foo'.rindex('o', 0) # => nil @@ -9265,14 +9307,14 @@ lstrip_offset(VALUE str, const char *s, const char *e, rb_encoding *enc) /* remove spaces at head */ if (single_byte_optimizable(str)) { - while (s < e && ascii_isspace(*s)) s++; + while (s < e && (*s == '\0' || ascii_isspace(*s))) s++; } else { while (s < e) { int n; unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); - if (!rb_isspace(cc)) break; + if (cc && !rb_isspace(cc)) break; s += n; } } @@ -10080,6 +10122,20 @@ rb_str_partition(VALUE str, VALUE sep) * "hello".rpartition("l") #=> ["hel", "l", "o"] * "hello".rpartition("x") #=> ["", "", "hello"] * "hello".rpartition(/.l/) #=> ["he", "ll", "o"] + * + * The match from the end means starting at the possible last position, not + * the last of longest matches. + * + * "hello".rpartition(/l+/) #=> ["hel", "l", "o"] + * + * To partition at the last longest match, needs to combine with + * negative lookbehind. + * + * "hello".rpartition(/(?<!l)l+/) #=> ["he", "ll", "o"] + * + * Or String#partition with negative lookforward. + * + * "hello".partition(/l+(?!.*l)/) #=> ["he", "ll", "o"] */ static VALUE @@ -11505,6 +11561,10 @@ rb_interned_str_cstr(const char *ptr) VALUE rb_enc_interned_str(const char *ptr, long len, rb_encoding *enc) { + if (UNLIKELY(rb_enc_autoload_p(enc))) { + rb_enc_autoload(enc); + } + struct RString fake_str; return register_fstring(rb_setup_fake_str(&fake_str, ptr, len, enc), TRUE); } diff --git a/template/Makefile.in b/template/Makefile.in index f5a31499b3..bcd4532359 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -684,4 +684,4 @@ mjit_build_dir.$(SOEXT): $(MJIT_MIN_HEADER) $(srcdir)/ruby-runner.c ruby-runner. # yes-test-basic: leaked-globals leaked-globals: $(COMMONOBJS) prog $(tooldir)/leaked-globals PHONY - $(Q) $(XRUBY) $(tooldir)/leaked-globals NM=$(NM) SYMBOL_PREFIX=$(SYMBOL_PREFIX) $(srcdir)/configure.ac $(COMMONOBJS) + $(Q) $(XRUBY) $(tooldir)/leaked-globals NM="$(NM) -Pgp" SYMBOL_PREFIX=$(SYMBOL_PREFIX) PLATFORM=$(hdrdir)/ruby/$(PLATFORM_DIR).h $(srcdir)/configure.ac $(COMMONOBJS) diff --git a/test/-ext-/array/test_to_ary_concat.rb b/test/-ext-/array/test_to_ary_concat.rb new file mode 100644 index 0000000000..feb1bc1109 --- /dev/null +++ b/test/-ext-/array/test_to_ary_concat.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: false +require 'test/unit' +require '-test-/array/to_ary_concat' + +class TestConcatStress < Test::Unit::TestCase + def setup + @stress_level = GC.stress + GC.stress = true + end + + def teardown + GC.stress = @stress_level + end + + def test_concat + arr = [nil] + bar = Bug::Bar.new + arr.concat(bar) + end +end diff --git a/test/-ext-/postponed_job/test_postponed_job.rb b/test/-ext-/postponed_job/test_postponed_job.rb index 7dc28776d0..fee0172d11 100644 --- a/test/-ext-/postponed_job/test_postponed_job.rb +++ b/test/-ext-/postponed_job/test_postponed_job.rb @@ -25,4 +25,11 @@ class TestPostponed_job < Test::Unit::TestCase Bug.postponed_job_register_one(ary = []) assert_equal [1], ary end + + if Bug.respond_to?(:postponed_job_register_in_c_thread) + def test_register_in_c_thread + assert Bug.postponed_job_register_in_c_thread(ary = []) + assert_equal [1], ary + end + end end diff --git a/test/-ext-/string/test_enc_str_buf_cat.rb b/test/-ext-/string/test_enc_str_buf_cat.rb index 72f903903c..b9a63ec2de 100644 --- a/test/-ext-/string/test_enc_str_buf_cat.rb +++ b/test/-ext-/string/test_enc_str_buf_cat.rb @@ -13,4 +13,13 @@ class Test_StringEncStrBufCat < Test::Unit::TestCase assert_equal(:unknown, Bug::String.new(cr_unknown_str).coderange, "an assertion for following tests") assert_equal(:valid, Bug::String.new(a8_str).enc_str_buf_cat(cr_unknown_str).coderange, Bug6509) end + + def test_str_conv_enc + str = Bug::String.new("aaa".encode("US-ASCII")) + assert_same(str, str.str_conv_enc_opts("UTF-8", "US-ASCII", 0, nil)) + + str = Bug::String.new("aaa".encode("UTF-16LE").force_encoding("UTF-8")) + assert_predicate(str, :ascii_only?) # cache coderange + assert_equal("aaa", str.str_conv_enc_opts("UTF-16LE", "UTF-8", 0, nil)) + end end diff --git a/test/-ext-/string/test_fstring.rb b/test/-ext-/string/test_fstring.rb index 76afa30e14..9b4956ecef 100644 --- a/test/-ext-/string/test_fstring.rb +++ b/test/-ext-/string/test_fstring.rb @@ -12,6 +12,22 @@ class Test_String_Fstring < Test::Unit::TestCase yield fstr end + def test_rb_enc_interned_str_autoloaded_encoding + assert_separately([], <<~RUBY) + require '-test-/string' + assert_include(Encoding::Windows_31J.inspect, 'autoload') + Bug::String.rb_enc_interned_str(Encoding::Windows_31J) + RUBY + end + + def test_rb_enc_str_new_autoloaded_encoding + assert_separately([], <<~RUBY) + require '-test-/string' + assert_include(Encoding::Windows_31J.inspect, 'autoload') + Bug::String.rb_enc_str_new(Encoding::Windows_31J) + RUBY + end + def test_instance_variable str = __method__.to_s * 3 str.instance_variable_set(:@test, 42) diff --git a/test/cgi/test_cgi_cookie.rb b/test/cgi/test_cgi_cookie.rb index 115a57e4a1..e3ec4bea52 100644 --- a/test/cgi/test_cgi_cookie.rb +++ b/test/cgi/test_cgi_cookie.rb @@ -60,6 +60,24 @@ class CGICookieTest < Test::Unit::TestCase end + def test_cgi_cookie_new_with_domain + h = {'name'=>'name1', 'value'=>'value1'} + cookie = CGI::Cookie.new('domain'=>'a.example.com', **h) + assert_equal('a.example.com', cookie.domain) + + cookie = CGI::Cookie.new('domain'=>'1.example.com', **h) + assert_equal('1.example.com', cookie.domain, 'enhanced by RFC 1123') + + assert_raise(ArgumentError) { + CGI::Cookie.new('domain'=>'-a.example.com', **h) + } + + assert_raise(ArgumentError) { + CGI::Cookie.new('domain'=>'a-.example.com', **h) + } + end + + def test_cgi_cookie_scriptname cookie = CGI::Cookie.new('name1', 'value1') assert_equal('', cookie.path) @@ -101,6 +119,11 @@ class CGICookieTest < Test::Unit::TestCase end end + def test_cgi_cookie_parse_not_decode_name + cookie_str = "%66oo=baz;foo=bar" + cookies = CGI::Cookie.parse(cookie_str) + assert_equal({"%66oo" => ["baz"], "foo" => ["bar"]}, cookies) + end def test_cgi_cookie_arrayinterface cookie = CGI::Cookie.new('name1', 'a', 'b', 'c') @@ -113,6 +136,70 @@ class CGICookieTest < Test::Unit::TestCase end + def test_cgi_cookie_domain_injection_into_name + name = "a=b; domain=example.com;" + path = "/" + domain = "example.jp" + assert_raise(ArgumentError) do + CGI::Cookie.new('name' => name, + 'value' => "value", + 'domain' => domain, + 'path' => path) + end + end + + + def test_cgi_cookie_newline_injection_into_name + name = "a=b;\r\nLocation: http://example.com#" + path = "/" + domain = "example.jp" + assert_raise(ArgumentError) do + CGI::Cookie.new('name' => name, + 'value' => "value", + 'domain' => domain, + 'path' => path) + end + end + + + def test_cgi_cookie_multibyte_injection_into_name + name = "a=b;\u3042" + path = "/" + domain = "example.jp" + assert_raise(ArgumentError) do + CGI::Cookie.new('name' => name, + 'value' => "value", + 'domain' => domain, + 'path' => path) + end + end + + + def test_cgi_cookie_injection_into_path + name = "name" + path = "/; samesite=none" + domain = "example.jp" + assert_raise(ArgumentError) do + CGI::Cookie.new('name' => name, + 'value' => "value", + 'domain' => domain, + 'path' => path) + end + end + + + def test_cgi_cookie_injection_into_domain + name = "name" + path = "/" + domain = "example.jp; samesite=none" + assert_raise(ArgumentError) do + CGI::Cookie.new('name' => name, + 'value' => "value", + 'domain' => domain, + 'path' => path) + end + end + instance_methods.each do |method| private method if method =~ /^test_(.*)/ && $1 != ENV['TEST'] diff --git a/test/cgi/test_cgi_header.rb b/test/cgi/test_cgi_header.rb index bab2d0348a..ec2f4deb72 100644 --- a/test/cgi/test_cgi_header.rb +++ b/test/cgi/test_cgi_header.rb @@ -176,6 +176,14 @@ class CGIHeaderTest < Test::Unit::TestCase end + def test_cgi_http_header_crlf_injection + cgi = CGI.new + assert_raise(RuntimeError) { cgi.http_header("text/xhtml\r\nBOO") } + assert_raise(RuntimeError) { cgi.http_header("type" => "text/xhtml\r\nBOO") } + assert_raise(RuntimeError) { cgi.http_header("status" => "200 OK\r\nBOO") } + assert_raise(RuntimeError) { cgi.http_header("location" => "text/xhtml\r\nBOO") } + end + instance_methods.each do |method| private method if method =~ /^test_(.*)/ && $1 != ENV['TEST'] diff --git a/test/date/test_date_parse.rb b/test/date/test_date_parse.rb index 9f92635387..34a672b069 100644 --- a/test/date/test_date_parse.rb +++ b/test/date/test_date_parse.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'test/unit' require 'date' +require 'timeout' class TestDateParse < Test::Unit::TestCase @@ -847,6 +848,13 @@ class TestDateParse < Test::Unit::TestCase h = Date._iso8601('') assert_equal({}, h) + + h = Date._iso8601(nil) + assert_equal({}, h) + + h = Date._iso8601('01-02-03T04:05:06Z'.to_sym) + assert_equal([2001, 2, 3, 4, 5, 6, 0], + h.values_at(:year, :mon, :mday, :hour, :min, :sec, :offset)) end def test__rfc3339 @@ -862,6 +870,13 @@ class TestDateParse < Test::Unit::TestCase h = Date._rfc3339('') assert_equal({}, h) + + h = Date._rfc3339(nil) + assert_equal({}, h) + + h = Date._rfc3339('2001-02-03T04:05:06Z'.to_sym) + assert_equal([2001, 2, 3, 4, 5, 6, 0], + h.values_at(:year, :mon, :mday, :hour, :min, :sec, :offset)) end def test__xmlschema @@ -944,6 +959,13 @@ class TestDateParse < Test::Unit::TestCase h = Date._xmlschema('') assert_equal({}, h) + + h = Date._xmlschema(nil) + assert_equal({}, h) + + h = Date._xmlschema('2001-02-03'.to_sym) + assert_equal([2001, 2, 3, nil, nil, nil, nil], + h.values_at(:year, :mon, :mday, :hour, :min, :sec, :offset)) end def test__rfc2822 @@ -976,6 +998,13 @@ class TestDateParse < Test::Unit::TestCase h = Date._rfc2822('') assert_equal({}, h) + + h = Date._rfc2822(nil) + assert_equal({}, h) + + h = Date._rfc2822('Sat, 3 Feb 2001 04:05:06 UT'.to_sym) + assert_equal([2001, 2, 3, 4, 5, 6, 0], + h.values_at(:year, :mon, :mday, :hour, :min, :sec, :offset)) end def test__httpdate @@ -996,6 +1025,13 @@ class TestDateParse < Test::Unit::TestCase h = Date._httpdate('') assert_equal({}, h) + + h = Date._httpdate(nil) + assert_equal({}, h) + + h = Date._httpdate('Sat, 03 Feb 2001 04:05:06 GMT'.to_sym) + assert_equal([2001, 2, 3, 4, 5, 6, 0], + h.values_at(:year, :mon, :mday, :hour, :min, :sec, :offset)) end def test__jisx0301 @@ -1072,6 +1108,13 @@ class TestDateParse < Test::Unit::TestCase h = Date._jisx0301('') assert_equal({}, h) + + h = Date._jisx0301(nil) + assert_equal({}, h) + + h = Date._jisx0301('H13.02.03T04:05:06.07+0100'.to_sym) + assert_equal([2001, 2, 3, 4, 5, 6, 3600], + h.values_at(:year, :mon, :mday, :hour, :min, :sec, :offset)) end def test_iso8601 @@ -1228,4 +1271,32 @@ class TestDateParse < Test::Unit::TestCase assert_equal(s0, s) end + def test_length_limit + assert_raise(ArgumentError) { Date._parse("1" * 1000) } + assert_raise(ArgumentError) { Date._iso8601("1" * 1000) } + assert_raise(ArgumentError) { Date._rfc3339("1" * 1000) } + assert_raise(ArgumentError) { Date._xmlschema("1" * 1000) } + assert_raise(ArgumentError) { Date._rfc2822("1" * 1000) } + assert_raise(ArgumentError) { Date._rfc822("1" * 1000) } + assert_raise(ArgumentError) { Date._jisx0301("1" * 1000) } + + assert_raise(ArgumentError) { Date.parse("1" * 1000) } + assert_raise(ArgumentError) { Date.iso8601("1" * 1000) } + assert_raise(ArgumentError) { Date.rfc3339("1" * 1000) } + assert_raise(ArgumentError) { Date.xmlschema("1" * 1000) } + assert_raise(ArgumentError) { Date.rfc2822("1" * 1000) } + assert_raise(ArgumentError) { Date.rfc822("1" * 1000) } + assert_raise(ArgumentError) { Date.jisx0301("1" * 1000) } + + assert_raise(ArgumentError) { DateTime.parse("1" * 1000) } + assert_raise(ArgumentError) { DateTime.iso8601("1" * 1000) } + assert_raise(ArgumentError) { DateTime.rfc3339("1" * 1000) } + assert_raise(ArgumentError) { DateTime.xmlschema("1" * 1000) } + assert_raise(ArgumentError) { DateTime.rfc2822("1" * 1000) } + assert_raise(ArgumentError) { DateTime.rfc822("1" * 1000) } + assert_raise(ArgumentError) { DateTime.jisx0301("1" * 1000) } + + assert_raise(ArgumentError) { Date._parse("Jan " + "9" * 1000000) } + assert_raise(Timeout::Error) { Timeout.timeout(1) { Date._parse("Jan " + "9" * 1000000, limit: nil) } } + end end diff --git a/test/drb/test_drb.rb b/test/drb/test_drb.rb index 47b2966ae2..6d7b10e347 100644 --- a/test/drb/test_drb.rb +++ b/test/drb/test_drb.rb @@ -323,7 +323,7 @@ class TestDRbAnyToS < Test::Unit::TestCase end def test_any_to_s - server = DRb::DRbServer.new('druby://:0') + server = DRb::DRbServer.new('druby://localhost:0') server.singleton_class.send(:public, :any_to_s) assert_equal("foo:String", server.any_to_s("foo")) assert_match(/\A#<DRbTests::TestDRbAnyToS::BO:0x[0-9a-f]+>\z/, server.any_to_s(BO.new)) @@ -335,7 +335,7 @@ end class TestDRbTCP < Test::Unit::TestCase def test_immediate_close - server = DRb::DRbServer.new('druby://:0') + server = DRb::DRbServer.new('druby://localhost:0') host, port, = DRb::DRbTCPSocket.send(:parse_uri, server.uri) socket = TCPSocket.open host, port socket.shutdown diff --git a/test/drb/test_drbssl.rb b/test/drb/test_drbssl.rb index 1763b38448..0254c7ab50 100644 --- a/test/drb/test_drbssl.rb +++ b/test/drb/test_drbssl.rb @@ -34,7 +34,7 @@ class DRbSSLService < DRbService [ ["C","JP"], ["O","Foo.DRuby.Org"], ["CN", "Sample"] ] end - @server = DRb::DRbServer.new('drbssl://:0', manager, config) + @server = DRb::DRbServer.new('drbssl://localhost:0', manager, config) end end diff --git a/test/etc/test_etc.rb b/test/etc/test_etc.rb index dc224d0d32..2eddcf49d7 100644 --- a/test/etc/test_etc.rb +++ b/test/etc/test_etc.rb @@ -171,6 +171,7 @@ class TestEtc < Test::Unit::TestCase def test_ractor return unless Etc.passwd # => skip test if no platform support + Etc.endpwent assert_ractor(<<~RUBY, require: 'etc') ractor = Ractor.new do diff --git a/test/fiber/scheduler.rb b/test/fiber/scheduler.rb index b3c3eaff59..df82c2ff93 100644 --- a/test/fiber/scheduler.rb +++ b/test/fiber/scheduler.rb @@ -100,8 +100,10 @@ class Scheduler self.run ensure - @urgent.each(&:close) - @urgent = nil + if @urgent + @urgent.each(&:close) + @urgent = nil + end @closed = true @@ -188,3 +190,21 @@ class Scheduler return fiber end end + +class BrokenUnblockScheduler < Scheduler + def unblock(blocker, fiber) + super + + raise "Broken unblock!" + end +end + +class SleepingUnblockScheduler < Scheduler + # This method is invoked when the thread is exiting. + def unblock(blocker, fiber) + super + + # This changes the current thread state to `THREAD_RUNNING` which causes `thread_join_sleep` to hang. + sleep(0.1) + end +end diff --git a/test/fiber/test_io.rb b/test/fiber/test_io.rb index f01cc3af1b..fafda7c310 100644 --- a/test/fiber/test_io.rb +++ b/test/fiber/test_io.rb @@ -62,4 +62,39 @@ class TestFiberIO < Test::Unit::TestCase end end.each(&:join) end + + def test_epipe_on_read + skip "UNIXSocket is not defined!" unless defined?(UNIXSocket) + + i, o = UNIXSocket.pair + + unless i.nonblock? && o.nonblock? + i.close + o.close + skip "I/O is not non-blocking!" + end + + error = nil + + thread = Thread.new do + scheduler = Scheduler.new + Fiber.set_scheduler scheduler + + Fiber.schedule do + begin + i.close + o.write(MESSAGE) + rescue => error + # Saved into error. + end + end + end + + thread.join + + i.close + o.close + + assert_kind_of Errno::EPIPE, error + end end diff --git a/test/fiber/test_process.rb b/test/fiber/test_process.rb index c6583cac9b..a5990be204 100644 --- a/test/fiber/test_process.rb +++ b/test/fiber/test_process.rb @@ -33,4 +33,19 @@ class TestFiberProcess < Test::Unit::TestCase end end.join end + + def test_fork + omit 'fork not supported' unless Process.respond_to?(:fork) + Thread.new do + scheduler = Scheduler.new + Fiber.set_scheduler scheduler + + Fiber.schedule do + pid = Process.fork {} + Process.wait(pid) + + assert_predicate $?, :success? + end + end.join + end end diff --git a/test/fiber/test_scheduler.rb b/test/fiber/test_scheduler.rb index 72bde9fcc3..d1fb89d693 100644 --- a/test/fiber/test_scheduler.rb +++ b/test/fiber/test_scheduler.rb @@ -66,9 +66,23 @@ class TestFiberScheduler < Test::Unit::TestCase RUBY end - def test_optional_close + def test_minimal_interface + scheduler = Object.new + + def scheduler.block + end + + def scheduler.unblock + end + + def scheduler.io_wait + end + + def scheduler.kernel_sleep + end + thread = Thread.new do - Fiber.set_scheduler Object.new + Fiber.set_scheduler scheduler end thread.join diff --git a/test/fiber/test_sleep.rb b/test/fiber/test_sleep.rb index e882766345..a7e88c0367 100644 --- a/test/fiber/test_sleep.rb +++ b/test/fiber/test_sleep.rb @@ -43,4 +43,29 @@ class TestFiberSleep < Test::Unit::TestCase assert_operator seconds, :>=, 2, "actual: %p" % seconds end + + def test_broken_sleep + thread = Thread.new do + Thread.current.report_on_exception = false + + scheduler = Scheduler.new + + def scheduler.kernel_sleep(duration = nil) + raise "Broken sleep!" + end + + Fiber.set_scheduler scheduler + + Fiber.schedule do + sleep 0 + end + + ensure + scheduler.close + end + + assert_raise(RuntimeError) do + thread.join + end + end end diff --git a/test/fiber/test_thread.rb b/test/fiber/test_thread.rb new file mode 100644 index 0000000000..5c25c43de2 --- /dev/null +++ b/test/fiber/test_thread.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true +require "test/unit" +require_relative 'scheduler' + +class TestFiberThread < Test::Unit::TestCase + def test_thread_join + thread = Thread.new do + scheduler = Scheduler.new + Fiber.set_scheduler scheduler + + result = nil + Fiber.schedule do + result = Thread.new{:done}.value + end + + scheduler.run + result + end + + assert_equal :done, thread.value + end + + def test_thread_join_implicit + sleeping = false + finished = false + + thread = Thread.new do + scheduler = Scheduler.new + Fiber.set_scheduler scheduler + + Fiber.schedule do + sleeping = true + sleep(0.1) + finished = true + end + + :done + end + + Thread.pass until sleeping + + thread.join + + assert_equal :done, thread.value + assert finished, "Scheduler thread's task should be finished!" + end + + def test_thread_join_blocking + thread = Thread.new do + scheduler = Scheduler.new + Fiber.set_scheduler scheduler + + result = nil + Fiber.schedule do + Fiber.new(blocking: true) do + # This can deadlock if the blocking state is not taken into account: + Thread.new do + sleep(0) + result = :done + end.join + end.resume + end + + scheduler.run + result + end + + assert_equal :done, thread.value + end + + def test_broken_unblock + thread = Thread.new do + Thread.current.report_on_exception = false + + scheduler = BrokenUnblockScheduler.new + + Fiber.set_scheduler scheduler + + Fiber.schedule do + Thread.new{ + Thread.current.report_on_exception = false + }.join + end + + scheduler.run + ensure + scheduler.close + end + + assert_raise(RuntimeError) do + thread.join + end + end + + def test_thread_join_hang + thread = Thread.new do + scheduler = SleepingUnblockScheduler.new + + Fiber.set_scheduler scheduler + + Fiber.schedule do + Thread.new{sleep(0.01)}.value + end + end + + thread.join + end +end diff --git a/test/fiddle/helper.rb b/test/fiddle/helper.rb index f38f9036a3..a6e2019924 100644 --- a/test/fiddle/helper.rb +++ b/test/fiddle/helper.rb @@ -47,8 +47,8 @@ when /linux/ libm_so = libc_so else # glibc - libc_so = File.join(libdir, "libc.so.6") - libm_so = File.join(libdir, "libm.so.6") + libc_so = "libc.so.6" + libm_so = "libm.so.6" end when /mingw/, /mswin/ require "rbconfig" diff --git a/test/fiddle/test_closure.rb b/test/fiddle/test_closure.rb index 2de0660725..9e748bf5ee 100644 --- a/test/fiddle/test_closure.rb +++ b/test/fiddle/test_closure.rb @@ -20,6 +20,18 @@ module Fiddle end end + def test_type_symbol + closure = Closure.new(:int, [:void]) + assert_equal([ + TYPE_INT, + [TYPE_VOID], + ], + [ + closure.instance_variable_get(:@ctype), + closure.instance_variable_get(:@args), + ]) + end + def test_call closure = Class.new(Closure) { def call @@ -42,6 +54,19 @@ module Fiddle assert_equal 10, func.call(10) end + def test_const_string + closure_class = Class.new(Closure) do + def call(string) + @return_string = "Hello! #{string}" + @return_string + end + end + closure = closure_class.new(:const_string, [:const_string]) + + func = Function.new(closure, [:const_string], :const_string) + assert_equal("Hello! World!", func.call("World!")) + end + def test_block_caller cb = Closure::BlockCaller.new(TYPE_INT, [TYPE_INT]) do |one| one diff --git a/test/fiddle/test_cparser.rb b/test/fiddle/test_cparser.rb index ef8cec5daa..24e1800e59 100644 --- a/test/fiddle/test_cparser.rb +++ b/test/fiddle/test_cparser.rb @@ -12,53 +12,77 @@ module Fiddle def test_char_ctype assert_equal(TYPE_CHAR, parse_ctype('char')) + assert_equal(TYPE_CHAR, parse_ctype('const char')) assert_equal(TYPE_CHAR, parse_ctype('signed char')) + assert_equal(TYPE_CHAR, parse_ctype('const signed char')) assert_equal(-TYPE_CHAR, parse_ctype('unsigned char')) + assert_equal(-TYPE_CHAR, parse_ctype('const unsigned char')) end def test_short_ctype assert_equal(TYPE_SHORT, parse_ctype('short')) + assert_equal(TYPE_SHORT, parse_ctype('const short')) assert_equal(TYPE_SHORT, parse_ctype('short int')) + assert_equal(TYPE_SHORT, parse_ctype('const short int')) assert_equal(TYPE_SHORT, parse_ctype('signed short')) + assert_equal(TYPE_SHORT, parse_ctype('const signed short')) assert_equal(TYPE_SHORT, parse_ctype('signed short int')) + assert_equal(TYPE_SHORT, parse_ctype('const signed short int')) assert_equal(-TYPE_SHORT, parse_ctype('unsigned short')) + assert_equal(-TYPE_SHORT, parse_ctype('const unsigned short')) assert_equal(-TYPE_SHORT, parse_ctype('unsigned short int')) + assert_equal(-TYPE_SHORT, parse_ctype('const unsigned short int')) end def test_int_ctype assert_equal(TYPE_INT, parse_ctype('int')) + assert_equal(TYPE_INT, parse_ctype('const int')) assert_equal(TYPE_INT, parse_ctype('signed int')) + assert_equal(TYPE_INT, parse_ctype('const signed int')) assert_equal(-TYPE_INT, parse_ctype('uint')) + assert_equal(-TYPE_INT, parse_ctype('const uint')) assert_equal(-TYPE_INT, parse_ctype('unsigned int')) + assert_equal(-TYPE_INT, parse_ctype('const unsigned int')) end def test_long_ctype assert_equal(TYPE_LONG, parse_ctype('long')) + assert_equal(TYPE_LONG, parse_ctype('const long')) assert_equal(TYPE_LONG, parse_ctype('long int')) + assert_equal(TYPE_LONG, parse_ctype('const long int')) assert_equal(TYPE_LONG, parse_ctype('signed long')) + assert_equal(TYPE_LONG, parse_ctype('const signed long')) assert_equal(TYPE_LONG, parse_ctype('signed long int')) + assert_equal(TYPE_LONG, parse_ctype('const signed long int')) assert_equal(-TYPE_LONG, parse_ctype('unsigned long')) + assert_equal(-TYPE_LONG, parse_ctype('const unsigned long')) assert_equal(-TYPE_LONG, parse_ctype('unsigned long int')) + assert_equal(-TYPE_LONG, parse_ctype('const unsigned long int')) end def test_size_t_ctype assert_equal(TYPE_SIZE_T, parse_ctype("size_t")) + assert_equal(TYPE_SIZE_T, parse_ctype("const size_t")) end def test_ssize_t_ctype assert_equal(TYPE_SSIZE_T, parse_ctype("ssize_t")) + assert_equal(TYPE_SSIZE_T, parse_ctype("const ssize_t")) end def test_ptrdiff_t_ctype assert_equal(TYPE_PTRDIFF_T, parse_ctype("ptrdiff_t")) + assert_equal(TYPE_PTRDIFF_T, parse_ctype("const ptrdiff_t")) end def test_intptr_t_ctype assert_equal(TYPE_INTPTR_T, parse_ctype("intptr_t")) + assert_equal(TYPE_INTPTR_T, parse_ctype("const intptr_t")) end def test_uintptr_t_ctype assert_equal(TYPE_UINTPTR_T, parse_ctype("uintptr_t")) + assert_equal(TYPE_UINTPTR_T, parse_ctype("const uintptr_t")) end def test_undefined_ctype @@ -66,7 +90,10 @@ module Fiddle end def test_undefined_ctype_with_type_alias - assert_equal(-TYPE_LONG, parse_ctype('DWORD', {"DWORD" => "unsigned long"})) + assert_equal(-TYPE_LONG, + parse_ctype('DWORD', {"DWORD" => "unsigned long"})) + assert_equal(-TYPE_LONG, + parse_ctype('const DWORD', {"DWORD" => "unsigned long"})) end def expand_struct_types(types) @@ -83,11 +110,21 @@ module Fiddle end def test_struct_basic - assert_equal [[TYPE_INT, TYPE_CHAR], ['i', 'c']], parse_struct_signature(['int i', 'char c']) + assert_equal([[TYPE_INT, TYPE_CHAR], ['i', 'c']], + parse_struct_signature(['int i', 'char c'])) + assert_equal([[TYPE_INT, TYPE_CHAR], ['i', 'c']], + parse_struct_signature(['const int i', 'const char c'])) end def test_struct_array - assert_equal [[[TYPE_CHAR,80],[TYPE_INT,5]], ['buffer','x']], parse_struct_signature(['char buffer[80]', 'int[5] x']) + assert_equal([[[TYPE_CHAR, 80], [TYPE_INT, 5]], + ['buffer', 'x']], + parse_struct_signature(['char buffer[80]', + 'int[5] x'])) + assert_equal([[[TYPE_CHAR, 80], [TYPE_INT, 5]], + ['buffer', 'x']], + parse_struct_signature(['const char buffer[80]', + 'const int[5] x'])) end def test_struct_nested_struct @@ -178,15 +215,22 @@ module Fiddle end def test_struct_array_str - assert_equal [[[TYPE_CHAR,80],[TYPE_INT,5]], ['buffer','x']], parse_struct_signature('char buffer[80], int[5] x') + assert_equal([[[TYPE_CHAR, 80], [TYPE_INT, 5]], + ['buffer', 'x']], + parse_struct_signature('char buffer[80], int[5] x')) + assert_equal([[[TYPE_CHAR, 80], [TYPE_INT, 5]], + ['buffer', 'x']], + parse_struct_signature('const char buffer[80], const int[5] x')) end def test_struct_function_pointer - assert_equal [[TYPE_VOIDP], ['cb']], parse_struct_signature(['void (*cb)(const char*)']) + assert_equal([[TYPE_VOIDP], ['cb']], + parse_struct_signature(['void (*cb)(const char*)'])) end def test_struct_function_pointer_str - assert_equal [[TYPE_VOIDP,TYPE_VOIDP], ['cb', 'data']], parse_struct_signature('void (*cb)(const char*), const char* data') + assert_equal([[TYPE_VOIDP, TYPE_VOIDP], ['cb', 'data']], + parse_struct_signature('void (*cb)(const char*), const char* data')) end def test_struct_string diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index 3962de3790..bec10c5da0 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -235,6 +235,15 @@ defined?(PTY) and defined?(IO.console) and TestIO_Console.class_eval do assert_equal("\r\n", r.gets) assert_equal("\"asdf\"", r.gets.chomp) end + + run_pty("p IO.console.getpass('> ')") do |r, w| + assert_equal("> ", r.readpartial(10)) + sleep 0.1 + w.print "asdf\C-D\C-D" + sleep 0.1 + assert_equal("\r\n", r.gets) + assert_equal("\"asdf\"", r.gets.chomp) + end end def test_iflush diff --git a/test/io/wait/test_io_wait_uncommon.rb b/test/io/wait/test_io_wait_uncommon.rb index 28b4a0f8c4..b6f1c29bcd 100644 --- a/test/io/wait/test_io_wait_uncommon.rb +++ b/test/io/wait/test_io_wait_uncommon.rb @@ -6,15 +6,10 @@ require 'io/wait' # We may optimize IO#wait_*able for non-Linux kernels in the future class TestIOWaitUncommon < Test::Unit::TestCase def test_tty_wait - begin - tty = File.open('/dev/tty', 'w+') - rescue Errno::ENOENT, Errno::ENXIO => e - skip "/dev/tty: #{e.message} (#{e.class})" + check_dev('/dev/tty', mode: 'w+') do |tty| + assert_include [ nil, tty ], tty.wait_readable(0) + assert_equal tty, tty.wait_writable(1), 'portability test' end - assert_include [ nil, tty ], tty.wait_readable(0) - assert_equal tty, tty.wait_writable(1), 'portability test' - ensure - tty&.close end def test_fifo_wait @@ -44,36 +39,40 @@ class TestIOWaitUncommon < Test::Unit::TestCase # used to find portability problems because some ppoll implementations # are incomplete and do not work for certain "file" types - def check_dev(dev, m = :wait_readable) + def check_dev(dev, m = :wait_readable, mode: m == :wait_readable ? 'r' : 'w', &block) begin - fp = File.open("/dev/#{dev}", m == :wait_readable ? 'r' : 'w') + fp = File.open(dev, mode) + rescue Errno::ENOENT + return # Ignore silently rescue SystemCallError => e skip "#{dev} could not be opened #{e.message} (#{e.class})" end - assert_same fp, fp.__send__(m) + if block + yield fp + else + assert_same fp, fp.__send__(m) + end ensure fp&.close end def test_wait_readable_urandom - check_dev 'urandom' + check_dev('/dev/urandom') end def test_wait_readable_random - File.open('/dev/random') do |fp| + check_dev('/dev/random') do |fp| assert_nothing_raised do fp.wait_readable(0) end end - rescue SystemCallError => e - skip "/dev/random could not be opened #{e.message} (#{e.class})" end def test_wait_readable_zero - check_dev 'zero' + check_dev('/dev/zero') end def test_wait_writable_null - check_dev 'null', :wait_writable + check_dev(IO::NULL, :wait_writable) end end diff --git a/test/io/wait/test_ractor.rb b/test/io/wait/test_ractor.rb new file mode 100644 index 0000000000..3d286af77f --- /dev/null +++ b/test/io/wait/test_ractor.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true +require 'test/unit' +require 'rbconfig' +require 'io/wait' + +class TestIOWaitInRactor < Test::Unit::TestCase + def setup + omit unless defined? Ractor + end + + def test_ractor + ext = "/io/wait.#{RbConfig::CONFIG['DLEXT']}" + path = $".find {|path| path.end_with?(ext)} + assert_in_out_err(%W[-r#{path}], <<-"end;", ["true"], []) + $VERBOSE = nil + r = Ractor.new do + $stdout.equal?($stdout.wait_writable) + end + puts r.take + end; + end +end diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 41f84f1922..044d852a32 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -5,6 +5,32 @@ require "irb/extend-command" module TestIRB class ExtendCommand < Test::Unit::TestCase + class TestInputMethod < ::IRB::InputMethod + attr_reader :list, :line_no + + def initialize(list = []) + super("test") + @line_no = 0 + @list = list + end + + def gets + @list[@line_no]&.tap {@line_no += 1} + end + + def eof? + @line_no >= @list.size + end + + def encoding + Encoding.default_external + end + + def reset + @line_no = 0 + end + end + def setup @pwd = Dir.pwd @tmpdir = File.join(Dir.tmpdir, "test_reline_config_#{$$}") @@ -17,12 +43,14 @@ module TestIRB Dir.chdir(@tmpdir) @home_backup = ENV["HOME"] ENV["HOME"] = @tmpdir + @xdg_config_home_backup = ENV.delete("XDG_CONFIG_HOME") @default_encoding = [Encoding.default_external, Encoding.default_internal] @stdio_encodings = [STDIN, STDOUT, STDERR].map {|io| [io.external_encoding, io.internal_encoding] } IRB.instance_variable_get(:@CONF).clear end def teardown + ENV["XDG_CONFIG_HOME"] = @xdg_config_home_backup ENV["HOME"] = @home_backup Dir.chdir(@pwd) FileUtils.rm_rf(@tmpdir) @@ -42,12 +70,12 @@ module TestIRB IRB.conf[:USE_SINGLELINE] = false IRB.conf[:VERBOSE] = false workspace = IRB::WorkSpace.new(self) - irb = IRB::Irb.new(workspace) + irb = IRB::Irb.new(workspace, TestInputMethod.new([])) IRB.conf[:MAIN_CONTEXT] = irb.context expected = %r{ Ruby\sversion: .+\n IRB\sversion:\sirb .+\n - InputMethod:\sReidlineInputMethod\swith\sReline .+ and .+\n + InputMethod:\sAbstract\sInputMethod\n \.irbrc\spath: .+\n RUBY_PLATFORM: .+ }x @@ -62,12 +90,12 @@ module TestIRB IRB.conf[:USE_SINGLELINE] = true IRB.conf[:VERBOSE] = false workspace = IRB::WorkSpace.new(self) - irb = IRB::Irb.new(workspace) + irb = IRB::Irb.new(workspace, TestInputMethod.new([])) IRB.conf[:MAIN_CONTEXT] = irb.context expected = %r{ Ruby\sversion: .+\n IRB\sversion:\sirb .+\n - InputMethod:\sReadlineInputMethod\swith .+ and .+\n + InputMethod:\sAbstract\sInputMethod\n \.irbrc\spath: .+\n RUBY_PLATFORM: .+ }x @@ -85,12 +113,12 @@ module TestIRB IRB.conf[:USE_SINGLELINE] = false IRB.conf[:VERBOSE] = false workspace = IRB::WorkSpace.new(self) - irb = IRB::Irb.new(workspace) + irb = IRB::Irb.new(workspace, TestInputMethod.new([])) IRB.conf[:MAIN_CONTEXT] = irb.context expected = %r{ Ruby\sversion: .+\n IRB\sversion:\sirb .+\n - InputMethod:\sReidlineInputMethod\swith\sReline\s[^ ]+(?!\sand\s.+)\n + InputMethod:\sAbstract\sInputMethod\n RUBY_PLATFORM: .+\n \z }x @@ -112,12 +140,12 @@ module TestIRB IRB.conf[:USE_SINGLELINE] = true IRB.conf[:VERBOSE] = false workspace = IRB::WorkSpace.new(self) - irb = IRB::Irb.new(workspace) + irb = IRB::Irb.new(workspace, TestInputMethod.new([])) IRB.conf[:MAIN_CONTEXT] = irb.context expected = %r{ Ruby\sversion: .+\n IRB\sversion:\sirb .+\n - InputMethod:\sReadlineInputMethod\swith\s(?~.*\sand\s.+)\n + InputMethod:\sAbstract\sInputMethod\n RUBY_PLATFORM: .+\n \z }x @@ -128,32 +156,6 @@ module TestIRB IRB.const_set(:IRBRC_EXT, ext_backup) end - class TestInputMethod < ::IRB::InputMethod - attr_reader :list, :line_no - - def initialize(list = []) - super("test") - @line_no = 0 - @list = list - end - - def gets - @list[@line_no]&.tap {@line_no += 1} - end - - def eof? - @line_no >= @list.size - end - - def encoding - Encoding.default_external - end - - def reset - @line_no = 0 - end - end - def test_measure IRB.init_config(nil) IRB.conf[:PROMPT] = { @@ -372,5 +374,56 @@ module TestIRB /=> "bug17564"\n/, ], out) end + + def test_ls + input = TestInputMethod.new([ + "ls Object.new.tap { |o| o.instance_variable_set(:@a, 1) }\n", + ]) + IRB.init_config(nil) + workspace = IRB::WorkSpace.new(self) + IRB.conf[:VERBOSE] = false + irb = IRB::Irb.new(workspace, input) + IRB.conf[:MAIN_CONTEXT] = irb.context + irb.context.return_format = "=> %s\n" + out, err = capture_output do + irb.eval_input + end + assert_empty err + assert_match(/^instance variables:\s+@a\n/m, out) + end + + def test_show_source + input = TestInputMethod.new([ + "show_source 'IRB.conf'\n", + ]) + IRB.init_config(nil) + workspace = IRB::WorkSpace.new(self) + irb = IRB::Irb.new(workspace, input) + IRB.conf[:VERBOSE] = false + IRB.conf[:MAIN_CONTEXT] = irb.context + irb.context.return_format = "=> %s\n" + out, err = capture_output do + irb.eval_input + end + assert_empty err + assert_match(%r[/irb\.rb], out) + end + + def test_whereami + input = TestInputMethod.new([ + "whereami\n", + ]) + IRB.init_config(nil) + workspace = IRB::WorkSpace.new(self) + IRB.conf[:VERBOSE] = false + irb = IRB::Irb.new(workspace, input) + IRB.conf[:MAIN_CONTEXT] = irb.context + irb.context.return_format = "=> %s\n" + out, err = capture_output do + irb.eval_input + end + assert_empty err + assert_match(/^From: .+ @ line \d+ :\n/, out) + end end end diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb index 9976008124..a28ae06117 100644 --- a/test/irb/test_color.rb +++ b/test/irb/test_color.rb @@ -66,6 +66,7 @@ module TestIRB "\t" => "\t", # not ^I "foo(*%W(bar))" => "foo(*#{RED}#{BOLD}%W(#{CLEAR}#{RED}bar#{CLEAR}#{RED}#{BOLD})#{CLEAR})", "$stdout" => "#{GREEN}#{BOLD}$stdout#{CLEAR}", + "__END__" => "#{GREEN}__END__#{CLEAR}", } # specific to Ruby 2.7+ diff --git a/test/irb/test_color_printer.rb b/test/irb/test_color_printer.rb index 1b28837658..1afc7ccf55 100644 --- a/test/irb/test_color_printer.rb +++ b/test/irb/test_color_printer.rb @@ -34,6 +34,7 @@ module TestIRB end { 1 => "#{BLUE}#{BOLD}1#{CLEAR}\n", + "a\nb" => %[#{RED}#{BOLD}"#{CLEAR}#{RED}a\\nb#{CLEAR}#{RED}#{BOLD}"#{CLEAR}\n], IRBTestColorPrinter.new('test') => "#{GREEN}#<struct TestIRB::TestColorPrinter::IRBTestColorPrinter#{CLEAR} a#{GREEN}=#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{RED}test#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{GREEN}>#{CLEAR}\n", Ripper::Lexer.new('1').scan => "[#{GREEN}#<Ripper::Lexer::Elem:#{CLEAR} on_int@1:0 END token: #{RED}#{BOLD}\"#{CLEAR}#{RED}1#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{GREEN}>#{CLEAR}]\n", Class.new{define_method(:pretty_print){|q| q.text("[__FILE__, __LINE__, __ENCODING__]")}}.new => "[#{CYAN}#{BOLD}__FILE__#{CLEAR}, #{CYAN}#{BOLD}__LINE__#{CLEAR}, #{CYAN}#{BOLD}__ENCODING__#{CLEAR}]\n", diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index 984453d059..535690ae22 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -55,5 +55,33 @@ module TestIRB namespace = IRB::InputCompletor.retrieve_completion_data("1.positive?", bind: binding, doc_namespace: true) assert_equal "Integer.positive?", namespace end + + def test_complete_require + candidates = IRB::InputCompletor::CompletionProc.("'irb", "require ", "") + %w['irb/init 'irb/ruby-lex].each do |word| + assert_include candidates, word + end + # Test cache + candidates = IRB::InputCompletor::CompletionProc.("'irb", "require ", "") + %w['irb/init 'irb/ruby-lex].each do |word| + assert_include candidates, word + end + end + + def test_complete_require_relative + candidates = Dir.chdir(__dir__ + "/../..") do + IRB::InputCompletor::CompletionProc.("'lib/irb", "require_relative ", "") + end + %w['lib/irb/init 'lib/irb/ruby-lex].each do |word| + assert_include candidates, word + end + # Test cache + candidates = Dir.chdir(__dir__ + "/../..") do + IRB::InputCompletor::CompletionProc.("'lib/irb", "require_relative ", "") + end + %w['lib/irb/init 'lib/irb/ruby-lex].each do |word| + assert_include candidates, word + end + end end end diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index 392a6afa9a..81b7fe8679 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -127,11 +127,43 @@ module TestIRB INPUT end + def test_history_concurrent_use + omit "Skip Editline" if /EditLine/n.match(Readline::VERSION) + IRB.conf[:SAVE_HISTORY] = 1 + assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT) do |history_file| + exit + 5 + exit + EXPECTED_HISTORY + 1 + 2 + 3 + 4 + INITIAL_HISTORY + 5 + exit + INPUT + assert_history(<<~EXPECTED_HISTORY2, <<~INITIAL_HISTORY2, <<~INPUT2) + exit + EXPECTED_HISTORY2 + 1 + 2 + 3 + 4 + INITIAL_HISTORY2 + 5 + exit + INPUT2 + File.utime(File.atime(history_file), File.mtime(history_file) + 2, history_file) + end + end + private def assert_history(expected_history, initial_irb_history, input) backup_verbose, $VERBOSE = $VERBOSE, nil backup_home = ENV["HOME"] + backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME") IRB.conf[:LC_MESSAGES] = IRB::Locale.new actual_history = nil Dir.mktmpdir("test_irb_history_#{$$}") do |tmpdir| @@ -143,6 +175,11 @@ module TestIRB io = TestInputMethod.new io.class::HISTORY.clear io.load_history + if block_given? + history = io.class::HISTORY.dup + yield IRB.rc_file("_history") + io.class::HISTORY.replace(history) + end io.class::HISTORY.concat(input.split) io.save_history @@ -160,6 +197,7 @@ module TestIRB ensure $VERBOSE = backup_verbose ENV["HOME"] = backup_home + ENV["XDG_CONFIG_HOME"] = backup_xdg_config_home end def with_temp_stdio diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index 83b4b5a543..2c50b5da3a 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -64,6 +64,12 @@ module TestIRB ENV["IRBRC"] = backup_irbrc end + def test_recovery_sigint + bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] + status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e binding.irb;loop{Process.kill("SIGINT",$$)} -- -f --], "exit\n", //, //) + Process.kill("SIGKILL", status.pid) if !status.exited? && !status.stopped? && !status.signaled? + end + private def with_argv(argv) diff --git a/test/irb/test_ruby_lex.rb b/test/irb/test_ruby_lex.rb index a45ca668b9..556afbd776 100644 --- a/test/irb/test_ruby_lex.rb +++ b/test/irb/test_ruby_lex.rb @@ -136,6 +136,20 @@ module TestIRB end end + def test_endless_range_at_end_of_line + if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.6.0') + skip 'Endless range is available in 2.6.0 or later' + end + input_with_prompt = [ + PromptRow.new('001:0: :> ', %q(a = 3..)), + PromptRow.new('002:0: :* ', %q()), + ] + + lines = input_with_prompt.map(&:content) + expected_prompt_list = input_with_prompt.map(&:prompt) + assert_dynamic_prompt(lines, expected_prompt_list) + end + def test_incomplete_coding_magic_comment input_with_correct_indents = [ Row.new(%q(#coding:u), nil, 0), @@ -544,8 +558,7 @@ module TestIRB skip 'This test needs Ripper::Lexer#scan to take broken tokens' end - ruby_lex = RubyLex.new - tokens = ruby_lex.ripper_lex_without_warning('%wwww') + tokens = RubyLex.ripper_lex_without_warning('%wwww') pos_to_index = {} tokens.each_with_index { |t, i| assert_nil(pos_to_index[t[0]], "There is already another token in the position of #{t.inspect}.") @@ -558,8 +571,7 @@ module TestIRB skip 'This test needs Ripper::Lexer#scan to take broken tokens' end - ruby_lex = RubyLex.new - tokens = ruby_lex.ripper_lex_without_warning(<<~EOC.chomp) + tokens = RubyLex.ripper_lex_without_warning(<<~EOC.chomp) def foo %wwww end diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb new file mode 100644 index 0000000000..8f55b38a93 --- /dev/null +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -0,0 +1,165 @@ +require 'irb' + +begin + require 'yamatanooroti' + + class IRB::TestRendering < Yamatanooroti::TestCase + def setup + @pwd = Dir.pwd + suffix = '%010d' % Random.rand(0..65535) + @tmpdir = File.join(File.expand_path(Dir.tmpdir), "test_irb_#{$$}_#{suffix}") + begin + Dir.mkdir(@tmpdir) + rescue Errno::EEXIST + FileUtils.rm_rf(@tmpdir) + Dir.mkdir(@tmpdir) + end + @irbrc_backup = ENV['IRBRC'] + @irbrc_file = ENV['IRBRC'] = File.join(@tmpdir, 'temporaty_irbrc') + File.unlink(@irbrc_file) if File.exist?(@irbrc_file) + end + + def teardown + FileUtils.rm_rf(@tmpdir) + ENV['IRBRC'] = @irbrc_backup + ENV.delete('RELINE_TEST_PROMPT') if ENV['RELINE_TEST_PROMPT'] + end + + def test_launch + write_irbrc <<~'LINES' + puts 'start IRB' + LINES + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib -I#{@pwd}/../reline/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + write(<<~EOC) + 'Hello, World!' + EOC + close + assert_screen(<<~EOC) + start IRB + irb(main):001:0> 'Hello, World!' + => "Hello, World!" + irb(main):002:0> + EOC + end + + def test_multiline_paste + write_irbrc <<~'LINES' + puts 'start IRB' + LINES + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib -I#{@pwd}/../reline/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + write(<<~EOC) + class A + def inspect; '#<A>'; end + def a; self; end + def b; true; end + end + + a = A.new + + a + .a + .b + EOC + close + assert_screen(<<~EOC) + start IRB + irb(main):001:1* class A + irb(main):002:1* def inspect; '#<A>'; end + irb(main):003:1* def a; self; end + irb(main):004:1* def b; true; end + irb(main):005:0> end + => :b + irb(main):006:0> + irb(main):007:0> a = A.new + => #<A> + irb(main):008:0> + irb(main):009:0> a + irb(main):010:0> .a + irb(main):011:0> .b + => true + irb(main):012:0> + EOC + end + + def test_evaluate_each_toplevel_statement_by_multiline_paste + write_irbrc <<~'LINES' + puts 'start IRB' + LINES + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib -I#{@pwd}/../reline/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + write(<<~EOC) + class A + def inspect; '#<A>'; end + def b; self; end + def c; true; end + end + + a = A.new + + a + .b + # aaa + .c + + (a) + &.b() + + + class A def b; self; end; def c; true; end; end; + a = A.new + a + .b + # aaa + .c + (a) + &.b() + EOC + close + assert_screen(<<~EOC) + start IRB + irb(main):001:1* class A + irb(main):002:1* def inspect; '#<A>'; end + irb(main):003:1* def b; self; end + irb(main):004:1* def c; true; end + irb(main):005:0> end + => :c + irb(main):006:0> + irb(main):007:0> a = A.new + => #<A> + irb(main):008:0> + irb(main):009:0> a + irb(main):010:0> .b + irb(main):011:0> # aaa + irb(main):012:0> .c + => true + irb(main):013:0> + irb(main):014:0> (a) + irb(main):015:0> &.b() + => #<A> + irb(main):016:0> + irb(main):017:0> + irb(main):018:0> class A def b; self; end; def c; true; end; end; + => :c + irb(main):019:0> a = A.new + => #<A> + irb(main):020:0> a + irb(main):021:0> .b + irb(main):022:0> # aaa + irb(main):023:0> .c + => true + irb(main):024:0> (a) + irb(main):025:0> &.b() + => #<A> + irb(main):026:0> + EOC + end + + private def write_irbrc(content) + File.open(@irbrc_file, 'w') do |f| + f.write content + end + end + end +rescue LoadError, NameError + # On Ruby repository, this test suit doesn't run because Ruby repo doesn't + # have the yamatanooroti gem. +end diff --git a/test/mkmf/test_install.rb b/test/mkmf/test_install.rb new file mode 100644 index 0000000000..7f8c603d42 --- /dev/null +++ b/test/mkmf/test_install.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: false +require_relative 'base' + +class TestMkmf + class TestInstall < TestMkmf + def test_install_dirs + Dir.mktmpdir do |dir| + File.write(dir+"/extconf.rb", "require 'mkmf'; create_makefile('test')") + all_assertions do |a| + a.foreach( + ["site"], + ["vendor", "--vendor"], + ) do |dest, *options| + assert_ruby_status(["-C", dir, "extconf.rb", *options]) + mf = File.read(dir+"/Makefile") + a.foreach( + ["RUBYCOMMONDIR", "$(#{dest}dir)$(target_prefix)"], + ["RUBYLIBDIR", "$(#{dest}libdir)$(target_prefix)"], + ["RUBYARCHDIR", "$(#{dest}archdir)$(target_prefix)"], + ["HDRDIR", "$(#{dest}hdrdir)$(target_prefix)"], + ["ARCHHDRDIR", "$(#{dest}archhdrdir)$(target_prefix)"], + ) do |(var, path)| + assert_equal path, mf[/^#{var}\s*=\s*(.*)$/, 1] + end + end + end + end + end + end +end diff --git a/test/monitor/test_monitor.rb b/test/monitor/test_monitor.rb index 734b639d4c..0f17d58f71 100644 --- a/test/monitor/test_monitor.rb +++ b/test/monitor/test_monitor.rb @@ -10,6 +10,13 @@ class TestMonitor < Test::Unit::TestCase @monitor = Monitor.new end + def test_enter_in_different_fibers + @monitor.enter + Fiber.new { + assert_equal false, @monitor.try_enter + }.resume + end + def test_enter ary = [] queue = Queue.new diff --git a/test/net/ftp/test_ftp.rb b/test/net/ftp/test_ftp.rb index 023e79435a..a480da4a4f 100644 --- a/test/net/ftp/test_ftp.rb +++ b/test/net/ftp/test_ftp.rb @@ -61,7 +61,7 @@ class FTPTest < Test::Unit::TestCase end def test_parse227 - ftp = Net::FTP.new + ftp = Net::FTP.new(nil, use_pasv_ip: true) host, port = ftp.send(:parse227, "227 Entering Passive Mode (192,168,0,1,12,34)") assert_equal("192.168.0.1", host) assert_equal(3106, port) @@ -80,6 +80,14 @@ class FTPTest < Test::Unit::TestCase assert_raise(Net::FTPProtoError) do ftp.send(:parse227, "227 ) foo bar (") end + + ftp = Net::FTP.new + sock = OpenStruct.new + sock.remote_address = OpenStruct.new + sock.remote_address.ip_address = "10.0.0.1" + ftp.instance_variable_set(:@bare_sock, sock) + host, port = ftp.send(:parse227, "227 Entering Passive Mode (192,168,0,1,12,34)") + assert_equal("10.0.0.1", host) end def test_parse228 @@ -425,7 +433,7 @@ class FTPTest < Test::Unit::TestCase end conn.print(l, "\r\n") end - rescue Errno::EPIPE + rescue Errno::EPIPE, Errno::ECONNRESET ensure assert_nil($!) conn.close @@ -882,6 +890,40 @@ class FTPTest < Test::Unit::TestCase end end + def test_getbinaryfile_error + commands = [] + server = create_ftp_server { |sock| + sock.print("220 (test_ftp).\r\n") + commands.push(sock.gets) + sock.print("331 Please specify the password.\r\n") + commands.push(sock.gets) + sock.print("230 Login successful.\r\n") + commands.push(sock.gets) + sock.print("200 Switching to Binary mode.\r\n") + line = sock.gets + commands.push(line) + sock.print("450 No Dice\r\n") + } + begin + begin + ftp = Net::FTP.new + ftp.passive = true + ftp.read_timeout *= 5 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # for --jit-wait + ftp.connect(SERVER_ADDR, server.port) + ftp.login + assert_match(/\AUSER /, commands.shift) + assert_match(/\APASS /, commands.shift) + assert_equal("TYPE I\r\n", commands.shift) + assert_raise(Net::FTPTempError) {ftp.getbinaryfile("foo", nil)} + assert_match(/\A(PASV|EPSV)\r\n/, commands.shift) + ensure + ftp.close if ftp + end + ensure + server.close + end + end + def test_storbinary commands = [] binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 @@ -1935,7 +1977,7 @@ EOF assert_equal(nil, commands.shift) # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h. # See https://github.com/openssl/openssl/pull/5967 for details. - if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h/ + if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h|LibreSSL/ assert_equal(true, session_reused_for_data_connection) end ensure @@ -2019,7 +2061,7 @@ EOF assert_equal("RETR foo\r\n", commands.shift) assert_equal(nil, commands.shift) # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h. - if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h/ + if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h|LibreSSL/ assert_equal(true, session_reused_for_data_connection) end ensure @@ -2474,10 +2516,172 @@ EOF end end + def test_time_parser + s = "20371231000000" + assert_equal(Time.utc(2037, 12, 31, 0, 0, 0), + Net::FTP::TIME_PARSER[s]) + s = "20371231000000.123456" + assert_equal(Time.utc(2037, 12, 31, 0, 0, 0, 123456), + Net::FTP::TIME_PARSER[s]) + s = "20371231000000." + "9" * 999999 + assert_equal(Time.utc(2037, 12, 31, 0, 0, 0, + 99999999999999999r / 100000000000), + Net::FTP::TIME_PARSER[s]) + e = assert_raise(Net::FTPProtoError) { + Net::FTP::TIME_PARSER["x" * 999999] + } + assert_equal("invalid time-val: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...", e.message) + end + + def test_ignore_pasv_ip + commands = [] + binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 + server = create_ftp_server(nil, "127.0.0.1") { |sock| + sock.print("220 (test_ftp).\r\n") + commands.push(sock.gets) + sock.print("331 Please specify the password.\r\n") + commands.push(sock.gets) + sock.print("230 Login successful.\r\n") + commands.push(sock.gets) + sock.print("200 Switching to Binary mode.\r\n") + line = sock.gets + commands.push(line) + data_server = TCPServer.new("127.0.0.1", 0) + port = data_server.local_address.ip_port + sock.printf("227 Entering Passive Mode (999,0,0,1,%s).\r\n", + port.divmod(256).join(",")) + commands.push(sock.gets) + sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") + conn = data_server.accept + binary_data.scan(/.{1,1024}/nm) do |s| + conn.print(s) + end + conn.shutdown(Socket::SHUT_WR) + conn.read + conn.close + data_server.close + sock.print("226 Transfer complete.\r\n") + } + begin + begin + ftp = Net::FTP.new + ftp.passive = true + ftp.read_timeout *= 5 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # for --jit-wait + ftp.connect("127.0.0.1", server.port) + ftp.login + assert_match(/\AUSER /, commands.shift) + assert_match(/\APASS /, commands.shift) + assert_equal("TYPE I\r\n", commands.shift) + buf = ftp.getbinaryfile("foo", nil) + assert_equal(binary_data, buf) + assert_equal(Encoding::ASCII_8BIT, buf.encoding) + assert_equal("PASV\r\n", commands.shift) + assert_equal("RETR foo\r\n", commands.shift) + assert_equal(nil, commands.shift) + ensure + ftp.close if ftp + end + ensure + server.close + end + end + + def test_use_pasv_ip + commands = [] + binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 + server = create_ftp_server(nil, "127.0.0.1") { |sock| + sock.print("220 (test_ftp).\r\n") + commands.push(sock.gets) + sock.print("331 Please specify the password.\r\n") + commands.push(sock.gets) + sock.print("230 Login successful.\r\n") + commands.push(sock.gets) + sock.print("200 Switching to Binary mode.\r\n") + line = sock.gets + commands.push(line) + data_server = TCPServer.new("127.0.0.1", 0) + port = data_server.local_address.ip_port + sock.printf("227 Entering Passive Mode (127,0,0,1,%s).\r\n", + port.divmod(256).join(",")) + commands.push(sock.gets) + sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") + conn = data_server.accept + binary_data.scan(/.{1,1024}/nm) do |s| + conn.print(s) + end + conn.shutdown(Socket::SHUT_WR) + conn.read + conn.close + data_server.close + sock.print("226 Transfer complete.\r\n") + } + begin + begin + ftp = Net::FTP.new + ftp.passive = true + ftp.use_pasv_ip = true + ftp.read_timeout *= 5 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # for --jit-wait + ftp.connect("127.0.0.1", server.port) + ftp.login + assert_match(/\AUSER /, commands.shift) + assert_match(/\APASS /, commands.shift) + assert_equal("TYPE I\r\n", commands.shift) + buf = ftp.getbinaryfile("foo", nil) + assert_equal(binary_data, buf) + assert_equal(Encoding::ASCII_8BIT, buf.encoding) + assert_equal("PASV\r\n", commands.shift) + assert_equal("RETR foo\r\n", commands.shift) + assert_equal(nil, commands.shift) + ensure + ftp.close if ftp + end + ensure + server.close + end + end + + def test_use_pasv_invalid_ip + commands = [] + binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 + server = create_ftp_server(nil, "127.0.0.1") { |sock| + sock.print("220 (test_ftp).\r\n") + commands.push(sock.gets) + sock.print("331 Please specify the password.\r\n") + commands.push(sock.gets) + sock.print("230 Login successful.\r\n") + commands.push(sock.gets) + sock.print("200 Switching to Binary mode.\r\n") + line = sock.gets + commands.push(line) + sock.print("227 Entering Passive Mode (999,0,0,1,48,57).\r\n") + commands.push(sock.gets) + } + begin + begin + ftp = Net::FTP.new + ftp.passive = true + ftp.use_pasv_ip = true + ftp.read_timeout *= 5 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # for --jit-wait + ftp.connect("127.0.0.1", server.port) + ftp.login + assert_match(/\AUSER /, commands.shift) + assert_match(/\APASS /, commands.shift) + assert_equal("TYPE I\r\n", commands.shift) + assert_raise(SocketError) do + ftp.getbinaryfile("foo", nil) + end + ensure + ftp.close if ftp + end + ensure + server.close + end + end + private - def create_ftp_server(sleep_time = nil) - server = TCPServer.new(SERVER_ADDR, 0) + def create_ftp_server(sleep_time = nil, addr = SERVER_ADDR) + server = TCPServer.new(addr, 0) @thread = Thread.start do if sleep_time sleep(sleep_time) diff --git a/test/net/http/test_https.rb b/test/net/http/test_https.rb index e9aee15bd3..7b97e39586 100644 --- a/test/net/http/test_https.rb +++ b/test/net/http/test_https.rb @@ -143,6 +143,11 @@ class TestNetHTTPS < Test::Unit::TestCase # See https://github.com/openssl/openssl/pull/5967 for details. skip if OpenSSL::OPENSSL_LIBRARY_VERSION =~ /OpenSSL 1.1.0h/ + # FIXME: GitHub Actions for MinGW failed. Maybe it's because of OpenSSL on MiNGW + if /mingw/ =~ RUBY_PLATFORM + skip "Skip net/https test using openssl on MinGW" + end + http = Net::HTTP.new("localhost", config("port")) http.use_ssl = true http.cert_store = TEST_STORE diff --git a/test/net/imap/test_imap.rb b/test/net/imap/test_imap.rb index 8b924b524e..85fb71d440 100644 --- a/test/net/imap/test_imap.rb +++ b/test/net/imap/test_imap.rb @@ -127,6 +127,16 @@ class IMAPTest < Test::Unit::TestCase imap.disconnect end end + + def test_starttls_stripping + starttls_stripping_test do |port| + imap = Net::IMAP.new("localhost", :port => port) + assert_raise(Net::IMAP::UnknownResponseError) do + imap.starttls(:ca_file => CA_FILE) + end + imap + end + end end def start_server @@ -834,6 +844,27 @@ EOF end end + def starttls_stripping_test + server = create_tcp_server + port = server.addr[1] + start_server do + sock = server.accept + begin + sock.print("* OK test server\r\n") + sock.gets + sock.print("RUBY0001 BUG unhandled command\r\n") + ensure + sock.close + server.close + end + end + begin + imap = yield(port) + ensure + imap.disconnect if imap && !imap.disconnected? + end + end + def create_tcp_server return TCPServer.new(server_addr, 0) end diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb index 6b956e6d14..5fd41134ba 100644 --- a/test/objspace/test_objspace.rb +++ b/test/objspace/test_objspace.rb @@ -243,6 +243,19 @@ class TestObjSpace < Test::Unit::TestCase GC.enable end + def test_trace_object_allocations_gc_stress + prev = GC.stress + GC.stress = true + + ObjectSpace.trace_object_allocations{ + proc{} + } + + assert true # success + ensure + GC.stress = prev + end + def test_dump_flags info = ObjectSpace.dump("foo".freeze) assert_match(/"wb_protected":true, "old":true/, info) diff --git a/test/openssl/test_cipher.rb b/test/openssl/test_cipher.rb index 178f5aba0e..ef8c4f3a4a 100644 --- a/test/openssl/test_cipher.rb +++ b/test/openssl/test_cipher.rb @@ -174,6 +174,54 @@ class OpenSSL::TestCipher < OpenSSL::TestCase assert_not_predicate(cipher, :authenticated?) end + def test_aes_ccm + # RFC 3610 Section 8, Test Case 1 + key = ["c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"].pack("H*") + iv = ["00000003020100a0a1a2a3a4a5"].pack("H*") + aad = ["0001020304050607"].pack("H*") + pt = ["08090a0b0c0d0e0f101112131415161718191a1b1c1d1e"].pack("H*") + ct = ["588c979a61c663d2f066d0c2c0f989806d5f6b61dac384"].pack("H*") + tag = ["17e8d12cfdf926e0"].pack("H*") + + kwargs = {auth_tag_len: 8, iv_len: 13, key: key, iv: iv} + cipher = new_encryptor("aes-128-ccm", **kwargs, ccm_data_len: pt.length, auth_data: aad) + assert_equal ct, cipher.update(pt) << cipher.final + assert_equal tag, cipher.auth_tag + cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag, auth_data: aad) + assert_equal pt, cipher.update(ct) << cipher.final + + # truncated tag is accepted + cipher = new_encryptor("aes-128-ccm", **kwargs, ccm_data_len: pt.length, auth_data: aad) + assert_equal ct, cipher.update(pt) << cipher.final + assert_equal tag[0, 8], cipher.auth_tag(8) + cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag[0, 8], auth_data: aad) + assert_equal pt, cipher.update(ct) << cipher.final + + # wrong tag is rejected + tag2 = tag.dup + tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff) + cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag2, auth_data: aad) + assert_raise(OpenSSL::Cipher::CipherError) { cipher.update(ct) } + + # wrong aad is rejected + aad2 = aad[0..-2] << aad[-1].succ + cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag, auth_data: aad2) + assert_raise(OpenSSL::Cipher::CipherError) { cipher.update(ct) } + + # wrong ciphertext is rejected + ct2 = ct[0..-2] << ct[-1].succ + cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct2.length, auth_tag: tag, auth_data: aad) + assert_raise(OpenSSL::Cipher::CipherError) { cipher.update(ct2) } + rescue OpenSSL::Cipher::CipherError + if /mingw/i =~ RUBY_PLATFORM + omit "skip on OpenSSL::Cipher::CipherError from 'ccm_data_len=': Maybe it's because of OpenSSL in MinGW" + else + raise + end + end if has_cipher?("aes-128-ccm") && + OpenSSL::Cipher.new("aes-128-ccm").authenticated? && + OpenSSL::OPENSSL_VERSION_NUMBER >= 0x1010103f # version >= 1.1.1c + def test_aes_gcm # GCM spec Appendix B Test Case 4 key = ["feffe9928665731c6d6a8f9467308308"].pack("H*") diff --git a/test/openssl/test_config.rb b/test/openssl/test_config.rb index f65392c18d..e041c1a3ba 100644 --- a/test/openssl/test_config.rb +++ b/test/openssl/test_config.rb @@ -61,14 +61,14 @@ foo\\bar::foo\\bar = baz [default1 default2]\t\t # space is allowed in section name fo =b ar # space allowed in value [emptysection] - [doller ] + [dollar ] foo=bar bar = $(foo) baz = 123$(default::bar)456${foo}798 qux = ${baz} quxx = $qux.$qux __EOC__ - assert_equal(['default', 'default1 default2', 'doller', 'emptysection', 'foo', 'foo\\bar'], c.sections.sort) + assert_equal(['default', 'default1 default2', 'dollar', 'emptysection', 'foo', 'foo\\bar'], c.sections.sort) assert_equal(['', 'a', 'bar', 'baz', 'd', 'dq', 'dq2', 'esc', 'foo\\bar', 'sq'], c['default'].keys.sort) assert_equal('c', c['default']['']) assert_equal('', c['default']['a']) @@ -84,12 +84,12 @@ __EOC__ assert_equal('baz', c['foo\\bar']['foo\\bar']) assert_equal('b ar', c['default1 default2']['fo']) - # dolloer - assert_equal('bar', c['doller']['foo']) - assert_equal('bar', c['doller']['bar']) - assert_equal('123baz456bar798', c['doller']['baz']) - assert_equal('123baz456bar798', c['doller']['qux']) - assert_equal('123baz456bar798.123baz456bar798', c['doller']['quxx']) + # dollar + assert_equal('bar', c['dollar']['foo']) + assert_equal('bar', c['dollar']['bar']) + assert_equal('123baz456bar798', c['dollar']['baz']) + assert_equal('123baz456bar798', c['dollar']['qux']) + assert_equal('123baz456bar798.123baz456bar798', c['dollar']['quxx']) excn = assert_raise(OpenSSL::ConfigError) do OpenSSL::Config.parse("foo = $bar") diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index 6095d545b5..2d9ee7bc70 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -257,7 +257,10 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase def test_client_auth_success vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT - start_server(verify_mode: vflag) { |port| + start_server(verify_mode: vflag, + ctx_proc: proc { |ctx| + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl?(3, 2, 0) + }) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.key = @cli_key ctx.cert = @cli_cert @@ -303,6 +306,8 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_client_ca + pend "LibreSSL 3.2 has broken client CA support" if libressl?(3, 2, 0) + ctx_proc = Proc.new do |ctx| ctx.client_ca = [@ca_cert] end @@ -481,6 +486,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase }) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE + ctx.max_version = :TLS1_2 if libressl?(3, 2, 0) && !libressl?(3, 3, 0) server_connect(port, ctx) { |ssl| ssl.puts "abc"; ssl.gets @@ -868,11 +874,13 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase def test_verify_hostname_on_connect ctx_proc = proc { |ctx| + san = "DNS:a.example.com,DNS:*.b.example.com" + san += ",DNS:c*.example.com,DNS:d.*.example.com" unless libressl?(3, 2, 2) exts = [ ["keyUsage", "keyEncipherment,digitalSignature", true], - ["subjectAltName", "DNS:a.example.com,DNS:*.b.example.com," \ - "DNS:c*.example.com,DNS:d.*.example.com"], + ["subjectAltName", san], ] + ctx.cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key) ctx.key = @svr_key } @@ -893,6 +901,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ["cx.example.com", true], ["d.x.example.com", false], ].each do |name, expected_ok| + next if name.start_with?('cx') if libressl?(3, 2, 2) begin sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) @@ -1581,12 +1590,13 @@ end end end - def test_ecdh_curves + def test_ecdh_curves_tls12 pend "EC is disabled" unless defined?(OpenSSL::PKey::EC) ctx_proc = -> ctx { # Enable both ECDHE (~ TLS 1.2) cipher suites and TLS 1.3 - ctx.ciphers = "DEFAULT:!kRSA:!kEDH" + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + ctx.ciphers = "kEECDH" ctx.ecdh_curves = "P-384:P-521" } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| @@ -1595,13 +1605,9 @@ end server_connect(port, ctx) { |ssl| cs = ssl.cipher[0] - if /\ATLS/ =~ cs # Is TLS 1.3 is used? + assert_match (/\AECDH/), cs + if ssl.respond_to?(:tmp_key) assert_equal "secp384r1", ssl.tmp_key.group.curve_name - else - assert_match (/\AECDH/), cs - if ssl.respond_to?(:tmp_key) - assert_equal "secp384r1", ssl.tmp_key.group.curve_name - end end ssl.puts "abc"; assert_equal "abc\n", ssl.gets } @@ -1625,6 +1631,26 @@ end end end + def test_ecdh_curves_tls13 + pend "EC is disabled" unless defined?(OpenSSL::PKey::EC) + pend "TLS 1.3 not supported" unless tls13_supported? + + ctx_proc = -> ctx { + # Assume TLS 1.3 is enabled and chosen by default + ctx.ecdh_curves = "P-384:P-521" + } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.ecdh_curves = "P-256:P-384" # disable P-521 + + server_connect(port, ctx) { |ssl| + assert_equal "TLSv1.3", ssl.ssl_version + assert_equal "secp384r1", ssl.tmp_key.group.curve_name + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + } + end + end + def test_security_level ctx = OpenSSL::SSL::SSLContext.new begin diff --git a/test/openssl/test_ssl_session.rb b/test/openssl/test_ssl_session.rb index 89726d4463..a98efdae2a 100644 --- a/test/openssl/test_ssl_session.rb +++ b/test/openssl/test_ssl_session.rb @@ -122,6 +122,7 @@ __EOS__ ctx.options &= ~OpenSSL::SSL::OP_NO_TICKET # Disable server-side session cache which is enabled by default ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_OFF + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl?(3, 2, 0) } start_server(ctx_proc: ctx_proc) do |port| sess1 = server_connect_with_session(port, nil, nil) { |ssl| diff --git a/test/openssl/test_ts.rb b/test/openssl/test_ts.rb index 6e9c30894b..7cb1a1fe8e 100644 --- a/test/openssl/test_ts.rb +++ b/test/openssl/test_ts.rb @@ -181,6 +181,12 @@ _end_of_pem_ assert_equal(42, qer2.nonce) end + def test_request_invalid_asn1 + assert_raise(OpenSSL::Timestamp::TimestampError) do + OpenSSL::Timestamp::Request.new("*" * 44) + end + end + def test_response_constants assert_equal(0, OpenSSL::Timestamp::Response::GRANTED) assert_equal(1, OpenSSL::Timestamp::Response::GRANTED_WITH_MODS) @@ -222,6 +228,11 @@ _end_of_pem_ assert_equal(token.to_der, resp.token.to_der) end + def test_response_failure_info + resp = OpenSSL::Timestamp::Response.new("0\"0 \x02\x01\x020\x17\f\x15Invalid TimeStampReq.\x03\x02\x06\x80") + assert_equal(:BAD_ALG, resp.failure_info) + end + def test_response_mandatory_fields fac = OpenSSL::Timestamp::Factory.new req = OpenSSL::Timestamp::Request.new @@ -333,6 +344,12 @@ _end_of_pem_ end end + def test_response_invalid_asn1 + assert_raise(OpenSSL::Timestamp::TimestampError) do + OpenSSL::Timestamp::Response.new("*" * 44) + end + end + def test_no_cert_requested req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" @@ -585,6 +602,12 @@ _end_of_pem_ assert_equal(123, info.nonce) end + def test_token_info_invalid_asn1 + assert_raise(OpenSSL::Timestamp::TimestampError) do + OpenSSL::Timestamp::TokenInfo.new("*" * 44) + end + end + private def assert_cert expected, actual diff --git a/test/openssl/test_x509store.rb b/test/openssl/test_x509store.rb index 1cbc73d539..e9602e3434 100644 --- a/test/openssl/test_x509store.rb +++ b/test/openssl/test_x509store.rb @@ -66,7 +66,7 @@ class OpenSSL::TestX509Store < OpenSSL::TestCase ee1_cert = issue_cert(@ee1, @dsa256, 10, ee_exts, ca2_cert, @rsa1024) ee2_cert = issue_cert(@ee2, @dsa512, 20, ee_exts, ca2_cert, @rsa1024) ee3_cert = issue_cert(@ee2, @dsa512, 30, ee_exts, ca2_cert, @rsa1024, - not_before: now-100, not_after: now-50) + not_before: now-100, not_after: now-1) ee4_cert = issue_cert(@ee2, @dsa512, 40, ee_exts, ca2_cert, @rsa1024, not_before: now+1000, not_after: now+2000,) @@ -128,7 +128,7 @@ class OpenSSL::TestX509Store < OpenSSL::TestCase assert_equal(@ee2.to_der, chain[0].subject.to_der) assert_equal(@ca2.to_der, chain[1].subject.to_der) assert_equal(@ca1.to_der, chain[2].subject.to_der) - assert_equal(false, store.verify(ee3_cert), "now=#{now.inspect} Time.now=#{Time.now.inspect} store=#{store.inspect} ee3_cert=#{ee3_cert.inspect}") + assert_equal(false, store.verify(ee3_cert)) assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error) assert_match(/expire/i, store.error_string) assert_equal(false, store.verify(ee4_cert)) diff --git a/test/openssl/utils.rb b/test/openssl/utils.rb index 3776fbac4e..ee734d98a4 100644 --- a/test/openssl/utils.rb +++ b/test/openssl/utils.rb @@ -199,6 +199,14 @@ class OpenSSL::SSLTestCase < OpenSSL::TestCase rescue end + def tls13_supported? + return false unless defined?(OpenSSL::SSL::TLS1_3_VERSION) + ctx = OpenSSL::SSL::SSLContext.new + ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION + true + rescue + end + def readwrite_loop(ctx, ssl) while line = ssl.gets ssl.write(line) diff --git a/test/optparse/test_acceptable.rb b/test/optparse/test_acceptable.rb index 5c3fbdb10c..12f7886538 100644 --- a/test/optparse/test_acceptable.rb +++ b/test/optparse/test_acceptable.rb @@ -196,4 +196,3 @@ class TestOptionParser::Acceptable < TestOptionParser end end - diff --git a/test/optparse/test_optparse.rb b/test/optparse/test_optparse.rb index e4aeb07aac..5f5ea183b0 100644 --- a/test/optparse/test_optparse.rb +++ b/test/optparse/test_optparse.rb @@ -75,4 +75,34 @@ class TestOptionParser < Test::Unit::TestCase assert_equal({host: "localhost", port: 8000, verbose: true}, result) assert_equal(true, @verbose) end + + def test_require_exact + @opt.def_option('-F', '--zrs=IRS', 'zrs') + %w(--zrs --zr --z -zfoo -z -F -Ffoo).each do |arg| + result = {} + @opt.parse([arg, 'foo'], into: result) + assert_equal({zrs: 'foo'}, result) + end + + @opt.require_exact = true + %w(--zrs -F -Ffoo).each do |arg| + result = {} + @opt.parse([arg, 'foo'], into: result) + assert_equal({zrs: 'foo'}, result) + end + + assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(--zr foo))} + assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(--z foo))} + assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(-zrs foo))} + assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(-zr foo))} + assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(-z foo))} + end + + def test_nonopt_pattern + @opt.def_option(/^[^-]/) do |arg| + assert(false, "Never gets called") + end + e = assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(-t))} + assert_equal(["-t"], e.args) + end end diff --git a/test/psych/helper.rb b/test/psych/helper.rb index 9348457958..0643139d8c 100644 --- a/test/psych/helper.rb +++ b/test/psych/helper.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'minitest/autorun' +require 'test/unit' require 'stringio' require 'tempfile' require 'date' @@ -7,13 +7,7 @@ require 'date' require 'psych' module Psych - superclass = if defined?(Minitest::Test) - Minitest::Test - else - MiniTest::Unit::TestCase - end - - class TestCase < superclass + class TestCase < Test::Unit::TestCase def self.suppress_warning verbose, $VERBOSE = $VERBOSE, nil yield @@ -47,24 +41,30 @@ module Psych # Convert between Psych and the object to verify correct parsing and # emitting # - def assert_to_yaml( obj, yaml ) - assert_equal( obj, Psych::load( yaml ) ) + def assert_to_yaml( obj, yaml, loader = :load ) + assert_equal( obj, Psych.send(loader, yaml) ) assert_equal( obj, Psych::parse( yaml ).transform ) - assert_equal( obj, Psych::load( obj.to_yaml ) ) + assert_equal( obj, Psych.send(loader, obj.to_yaml) ) assert_equal( obj, Psych::parse( obj.to_yaml ).transform ) - assert_equal( obj, Psych::load( + assert_equal( obj, Psych.send(loader, obj.to_yaml( :UseVersion => true, :UseHeader => true, :SortKeys => true ) )) + rescue Psych::DisallowedClass, Psych::BadAlias + assert_to_yaml obj, yaml, :unsafe_load end # # Test parser only # def assert_parse_only( obj, yaml ) - assert_equal( obj, Psych::load( yaml ) ) - assert_equal( obj, Psych::parse( yaml ).transform ) + begin + assert_equal obj, Psych::load( yaml ) + rescue Psych::DisallowedClass, Psych::BadAlias + assert_equal obj, Psych::unsafe_load( yaml ) + end + assert_equal obj, Psych::parse( yaml ).transform end def assert_cycle( obj ) @@ -75,9 +75,15 @@ module Psych assert_nil Psych::load(Psych.dump(obj)) assert_nil Psych::load(obj.to_yaml) else - assert_equal(obj, Psych.load(v.tree.yaml)) - assert_equal(obj, Psych::load(Psych.dump(obj))) - assert_equal(obj, Psych::load(obj.to_yaml)) + begin + assert_equal(obj, Psych.load(v.tree.yaml)) + assert_equal(obj, Psych::load(Psych.dump(obj))) + assert_equal(obj, Psych::load(obj.to_yaml)) + rescue Psych::DisallowedClass, Psych::BadAlias + assert_equal(obj, Psych.unsafe_load(v.tree.yaml)) + assert_equal(obj, Psych::unsafe_load(Psych.dump(obj))) + assert_equal(obj, Psych::unsafe_load(obj.to_yaml)) + end end end diff --git a/test/psych/test_alias_and_anchor.rb b/test/psych/test_alias_and_anchor.rb index 91c09dfdfa..81ebd66bed 100644 --- a/test/psych/test_alias_and_anchor.rb +++ b/test/psych/test_alias_and_anchor.rb @@ -19,7 +19,7 @@ module Psych - *id001 - *id001 EOYAML - result = Psych.load yaml + result = Psych.unsafe_load yaml result.each {|el| assert_same(result[0], el) } end @@ -33,7 +33,7 @@ EOYAML - *id001 EOYAML - result = Psych.load yaml + result = Psych.unsafe_load yaml result.each do |el| assert_same(result[0], el) assert_equal('test1', el.var1) @@ -50,7 +50,7 @@ EOYAML - *id001 - *id001 EOYAML - result = Psych.load yaml + result = Psych.unsafe_load yaml result.each do |el| assert_same(result[0], el) assert_equal('test', el.var1) @@ -62,7 +62,7 @@ EOYAML original = [o,o,o] yaml = Psych.dump original - result = Psych.load yaml + result = Psych.unsafe_load yaml result.each {|el| assert_same(result[0], el) } end @@ -73,7 +73,7 @@ EOYAML original = [o,o,o] yaml = Psych.dump original - result = Psych.load yaml + result = Psych.unsafe_load yaml result.each do |el| assert_same(result[0], el) assert_equal('test1', el.var1) @@ -87,7 +87,7 @@ EOYAML original = [o,o,o] yaml = Psych.dump original - result = Psych.load yaml + result = Psych.unsafe_load yaml result.each do |el| assert_same(result[0], el) assert_equal('test', el.var1) diff --git a/test/psych/test_array.rb b/test/psych/test_array.rb index f2bbdcab88..28b76da785 100644 --- a/test/psych/test_array.rb +++ b/test/psych/test_array.rb @@ -24,7 +24,7 @@ module Psych def test_another_subclass_with_attributes y = Y.new.tap {|o| o.val = 1} y << "foo" << "bar" - y = Psych.load Psych.dump y + y = Psych.unsafe_load Psych.dump y assert_equal %w{foo bar}, y assert_equal Y, y.class @@ -42,13 +42,13 @@ module Psych end def test_subclass_with_attributes - y = Psych.load Psych.dump Y.new.tap {|o| o.val = 1} + y = Psych.unsafe_load Psych.dump Y.new.tap {|o| o.val = 1} assert_equal Y, y.class assert_equal 1, y.val end def test_backwards_with_syck - x = Psych.load "--- !seq:#{X.name} []\n\n" + x = Psych.unsafe_load "--- !seq:#{X.name} []\n\n" assert_equal X, x.class end diff --git a/test/psych/test_class.rb b/test/psych/test_class.rb index 71f7ec31fd..faa504c7e2 100644 --- a/test/psych/test_class.rb +++ b/test/psych/test_class.rb @@ -7,13 +7,13 @@ module Psych end def test_cycle_anonymous_class - assert_raises(::TypeError) do + assert_raise(::TypeError) do assert_cycle(Class.new) end end def test_cycle_anonymous_module - assert_raises(::TypeError) do + assert_raise(::TypeError) do assert_cycle(Module.new) end end diff --git a/test/psych/test_coder.rb b/test/psych/test_coder.rb index 5ea8cab966..b2be0a4109 100644 --- a/test/psych/test_coder.rb +++ b/test/psych/test_coder.rb @@ -112,9 +112,19 @@ module Psych end end + class CustomEncode + def initialize(**opts) + @opts = opts + end + + def encode_with(coder) + @opts.each { |k,v| coder.public_send :"#{k}=", v } + end + end + def test_self_referential x = Referential.new - copy = Psych.load Psych.dump x + copy = Psych.unsafe_load Psych.dump x assert_equal copy, copy.a end @@ -153,23 +163,23 @@ module Psych end def test_represent_map - thing = Psych.load(Psych.dump(RepresentWithMap.new)) + thing = Psych.unsafe_load(Psych.dump(RepresentWithMap.new)) assert_equal({ "string" => 'a', :symbol => 'b' }, thing.map) end def test_represent_sequence - thing = Psych.load(Psych.dump(RepresentWithSeq.new)) + thing = Psych.unsafe_load(Psych.dump(RepresentWithSeq.new)) assert_equal %w{ foo bar }, thing.seq end def test_represent_with_init - thing = Psych.load(Psych.dump(RepresentWithInit.new)) + thing = Psych.unsafe_load(Psych.dump(RepresentWithInit.new)) assert_equal 'bar', thing.str end def test_represent! assert_match(/foo/, Psych.dump(Represent.new)) - assert_instance_of(Represent, Psych.load(Psych.dump(Represent.new))) + assert_instance_of(Represent, Psych.unsafe_load(Psych.dump(Represent.new))) end def test_scalar_coder @@ -179,7 +189,7 @@ module Psych def test_load_dumped_tagging foo = InitApi.new - bar = Psych.load(Psych.dump(foo)) + bar = Psych.unsafe_load(Psych.dump(foo)) assert_equal false, bar.implicit assert_equal "!ruby/object:Psych::TestCoder::InitApi", bar.tag assert_equal Psych::Nodes::Mapping::BLOCK, bar.style @@ -198,10 +208,121 @@ module Psych def test_dump_init_with foo = InitApi.new - bar = Psych.load(Psych.dump(foo)) + bar = Psych.unsafe_load(Psych.dump(foo)) assert_equal foo.a, bar.a assert_equal foo.b, bar.b assert_nil bar.c end + + def test_coder_style_map_default + foo = Psych.dump a: 1, b: 2 + assert_equal "---\n:a: 1\n:b: 2\n", foo + end + + def test_coder_style_map_any + foo = Psych.dump CustomEncode.new \ + map: {a: 1, b: 2}, + style: Psych::Nodes::Mapping::ANY, + tag: nil + assert_equal "---\n:a: 1\n:b: 2\n", foo + end + + def test_coder_style_map_block + foo = Psych.dump CustomEncode.new \ + map: {a: 1, b: 2}, + style: Psych::Nodes::Mapping::BLOCK, + tag: nil + assert_equal "---\n:a: 1\n:b: 2\n", foo + end + + def test_coder_style_map_flow + foo = Psych.dump CustomEncode.new \ + map: { a: 1, b: 2 }, + style: Psych::Nodes::Mapping::FLOW, + tag: nil + assert_equal "--- {! ':a': 1, ! ':b': 2}\n", foo + end + + def test_coder_style_seq_default + foo = Psych.dump [ 1, 2, 3 ] + assert_equal "---\n- 1\n- 2\n- 3\n", foo + end + + def test_coder_style_seq_any + foo = Psych.dump CustomEncode.new \ + seq: [ 1, 2, 3 ], + style: Psych::Nodes::Sequence::ANY, + tag: nil + assert_equal "---\n- 1\n- 2\n- 3\n", foo + end + + def test_coder_style_seq_block + foo = Psych.dump CustomEncode.new \ + seq: [ 1, 2, 3 ], + style: Psych::Nodes::Sequence::BLOCK, + tag: nil + assert_equal "---\n- 1\n- 2\n- 3\n", foo + end + + def test_coder_style_seq_flow + foo = Psych.dump CustomEncode.new \ + seq: [ 1, 2, 3 ], + style: Psych::Nodes::Sequence::FLOW, + tag: nil + assert_equal "--- [1, 2, 3]\n", foo + end + + def test_coder_style_scalar_default + foo = Psych.dump 'some scalar' + assert_match(/\A--- some scalar\n(?:\.\.\.\n)?\z/, foo) + end + + def test_coder_style_scalar_any + foo = Psych.dump CustomEncode.new \ + scalar: 'some scalar', + style: Psych::Nodes::Scalar::ANY, + tag: nil + assert_match(/\A--- some scalar\n(?:\.\.\.\n)?\z/, foo) + end + + def test_coder_style_scalar_plain + foo = Psych.dump CustomEncode.new \ + scalar: 'some scalar', + style: Psych::Nodes::Scalar::PLAIN, + tag: nil + assert_match(/\A--- some scalar\n(?:\.\.\.\n)?\z/, foo) + end + + def test_coder_style_scalar_single_quoted + foo = Psych.dump CustomEncode.new \ + scalar: 'some scalar', + style: Psych::Nodes::Scalar::SINGLE_QUOTED, + tag: nil + assert_equal "--- ! 'some scalar'\n", foo + end + + def test_coder_style_scalar_double_quoted + foo = Psych.dump CustomEncode.new \ + scalar: 'some scalar', + style: Psych::Nodes::Scalar::DOUBLE_QUOTED, + tag: nil + assert_equal %Q'--- ! "some scalar"\n', foo + end + + def test_coder_style_scalar_literal + foo = Psych.dump CustomEncode.new \ + scalar: 'some scalar', + style: Psych::Nodes::Scalar::LITERAL, + tag: nil + assert_equal "--- ! |-\n some scalar\n", foo + end + + def test_coder_style_scalar_folded + foo = Psych.dump CustomEncode.new \ + scalar: 'some scalar', + style: Psych::Nodes::Scalar::FOLDED, + tag: nil + assert_equal "--- ! >-\n some scalar\n", foo + end end end diff --git a/test/psych/test_date_time.rb b/test/psych/test_date_time.rb index f73f34628f..6f1e8b509e 100644 --- a/test/psych/test_date_time.rb +++ b/test/psych/test_date_time.rb @@ -22,7 +22,7 @@ module Psych def test_timezone_offset times = [Time.new(2017, 4, 13, 12, 0, 0, "+09:00"), Time.new(2017, 4, 13, 12, 0, 0, "-05:00")] - cycled = Psych::load(Psych.dump times) + cycled = Psych::unsafe_load(Psych.dump times) assert_match(/12:00:00 \+0900/, cycled.first.to_s) assert_match(/12:00:00 -0500/, cycled.last.to_s) end @@ -39,7 +39,7 @@ module Psych def test_datetime_timezone_offset times = [DateTime.new(2017, 4, 13, 12, 0, 0, "+09:00"), DateTime.new(2017, 4, 13, 12, 0, 0, "-05:00")] - cycled = Psych::load(Psych.dump times) + cycled = Psych::unsafe_load(Psych.dump times) assert_match(/12:00:00\+09:00/, cycled.first.to_s) assert_match(/12:00:00-05:00/, cycled.last.to_s) end diff --git a/test/psych/test_deprecated.rb b/test/psych/test_deprecated.rb index 624f4379a6..af3379909a 100644 --- a/test/psych/test_deprecated.rb +++ b/test/psych/test_deprecated.rb @@ -41,7 +41,7 @@ module Psych def test_recursive_quick_emit_encode_with qeew = QuickEmitterEncodeWith.new hash = { :qe => qeew } - hash2 = Psych.load Psych.dump hash + hash2 = Psych.unsafe_load Psych.dump hash qe = hash2[:qe] assert_equal qeew.name, qe.name @@ -72,7 +72,7 @@ module Psych # receive the yaml_initialize call. def test_yaml_initialize_and_init_with hash = { :yi => YamlInitAndInitWith.new } - hash2 = Psych.load Psych.dump hash + hash2 = Psych.unsafe_load Psych.dump hash yi = hash2[:yi] assert_equal 'TGIF!', yi.name diff --git a/test/psych/test_document.rb b/test/psych/test_document.rb index a88dd32f0d..cf3b7001fc 100644 --- a/test/psych/test_document.rb +++ b/test/psych/test_document.rb @@ -30,7 +30,7 @@ module Psych end def test_emit_bad_tag - assert_raises(RuntimeError) do + assert_raise(RuntimeError) do @doc.tag_directives = [['!']] @stream.yaml end diff --git a/test/psych/test_emitter.rb b/test/psych/test_emitter.rb index 52d5e9d1c1..506d72241c 100644 --- a/test/psych/test_emitter.rb +++ b/test/psych/test_emitter.rb @@ -40,7 +40,7 @@ module Psych end def test_start_stream_arg_error - assert_raises(TypeError) do + assert_raise(TypeError) do @emitter.start_stream 'asdfasdf' end end @@ -56,7 +56,7 @@ module Psych [[], [nil,nil], false], [[1,1], [[nil, "tag:TALOS"]], 0], ].each do |args| - assert_raises(TypeError) do + assert_raise(TypeError) do @emitter.start_document(*args) end end @@ -73,7 +73,7 @@ module Psych ['foo', nil, nil, false, true, :foo], [nil, nil, nil, false, true, 1], ].each do |args| - assert_raises(TypeError) do + assert_raise(TypeError) do @emitter.scalar(*args) end end @@ -83,11 +83,11 @@ module Psych @emitter.start_stream Psych::Nodes::Stream::UTF8 @emitter.start_document [], [], false - assert_raises(TypeError) do + assert_raise(TypeError) do @emitter.start_sequence(nil, Object.new, true, 1) end - assert_raises(TypeError) do + assert_raise(TypeError) do @emitter.start_sequence(nil, nil, true, :foo) end end diff --git a/test/psych/test_encoding.rb b/test/psych/test_encoding.rb index ef6653142f..e5831c9045 100644 --- a/test/psych/test_encoding.rb +++ b/test/psych/test_encoding.rb @@ -63,7 +63,7 @@ module Psych # If the external encoding isn't utf8, utf16le, or utf16be, we cannot # process the file. File.open(t.path, 'r', :encoding => 'SHIFT_JIS') do |f| - assert_raises Psych::SyntaxError do + assert_raise Psych::SyntaxError do Psych.load(f) end end @@ -121,7 +121,7 @@ module Psych def test_emit_alias @emitter.start_stream Psych::Parser::UTF8 @emitter.start_document [], [], true - e = assert_raises(RuntimeError) do + e = assert_raise(RuntimeError) do @emitter.alias 'ドラãˆã‚‚ã‚“'.encode('EUC-JP') end assert_match(/alias value/, e.message) diff --git a/test/psych/test_exception.rb b/test/psych/test_exception.rb index 78601d09c7..d2ae76a7d2 100644 --- a/test/psych/test_exception.rb +++ b/test/psych/test_exception.rb @@ -33,42 +33,42 @@ module Psych def test_backtrace err = make_ex - new_err = Psych.load(Psych.dump(err)) + new_err = Psych.unsafe_load(Psych.dump(err)) assert_equal err.backtrace, new_err.backtrace end def test_naming_exception err = String.xxx rescue $! - new_err = Psych.load(Psych.dump(err)) + new_err = Psych.unsafe_load(Psych.dump(err)) assert_equal err.message, new_err.message end def test_load_takes_file - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.load '--- `' end assert_nil ex.file - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.load '--- `', filename: 'meow' end assert_equal 'meow', ex.file # deprecated interface - ex = assert_raises(Psych::SyntaxError) do - Psych.load '--- `', 'deprecated' + ex = assert_raise(Psych::SyntaxError) do + Psych.unsafe_load '--- `', 'deprecated' end assert_equal 'deprecated', ex.file end def test_psych_parse_stream_takes_file - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.parse_stream '--- `' end assert_nil ex.file assert_match '(<unknown>)', ex.message - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.parse_stream '--- `', filename: 'omg!' end assert_equal 'omg!', ex.file @@ -76,19 +76,19 @@ module Psych end def test_load_stream_takes_file - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.load_stream '--- `' end assert_nil ex.file assert_match '(<unknown>)', ex.message - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.load_stream '--- `', filename: 'omg!' end assert_equal 'omg!', ex.file # deprecated interface - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.load_stream '--- `', 'deprecated' end assert_equal 'deprecated', ex.file @@ -99,7 +99,7 @@ module Psych t.binmode t.write '--- `' t.close - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.parse_file t.path end assert_equal t.path, ex.file @@ -111,7 +111,7 @@ module Psych t.binmode t.write '--- `' t.close - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.load_file t.path end assert_equal t.path, ex.file @@ -123,7 +123,7 @@ module Psych t.binmode t.write '--- `' t.close - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.safe_load_file t.path end assert_equal t.path, ex.file @@ -131,26 +131,26 @@ module Psych end def test_psych_parse_takes_file - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.parse '--- `' end assert_match '(<unknown>)', ex.message assert_nil ex.file - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.parse '--- `', filename: 'omg!' end assert_match 'omg!', ex.message # deprecated interface - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.parse '--- `', 'deprecated' end assert_match 'deprecated', ex.message end def test_attributes - e = assert_raises(Psych::SyntaxError) { + e = assert_raise(Psych::SyntaxError) { Psych.load '--- `foo' } @@ -165,7 +165,7 @@ module Psych end def test_convert - w = Psych.load(Psych.dump(@wups)) + w = Psych.unsafe_load(Psych.dump(@wups)) assert_equal @wups.message, w.message assert_equal @wups.backtrace, w.backtrace assert_equal 1, w.foo diff --git a/test/psych/test_hash.rb b/test/psych/test_hash.rb index ba11b827da..5374781339 100644 --- a/test/psych/test_hash.rb +++ b/test/psych/test_hash.rb @@ -39,7 +39,7 @@ module Psych def test_hash_with_ivar t1 = HashWithIvar.new t1[:foo] = :bar - t2 = Psych.load(Psych.dump(t1)) + t2 = Psych.unsafe_load(Psych.dump(t1)) assert_equal t1, t2 assert_cycle t1 end @@ -54,14 +54,14 @@ module Psych def test_custom_initialized a = [1,2,3,4,5] t1 = HashWithCustomInit.new(a) - t2 = Psych.load(Psych.dump(t1)) + t2 = Psych.unsafe_load(Psych.dump(t1)) assert_equal t1, t2 assert_cycle t1 end def test_custom_initialize_no_ivar t1 = HashWithCustomInitNoIvar.new(nil) - t2 = Psych.load(Psych.dump(t1)) + t2 = Psych.unsafe_load(Psych.dump(t1)) assert_equal t1, t2 assert_cycle t1 end @@ -70,25 +70,25 @@ module Psych x = X.new x[:a] = 'b' x.instance_variable_set :@foo, 'bar' - dup = Psych.load Psych.dump x + dup = Psych.unsafe_load Psych.dump x assert_cycle x assert_equal 'bar', dup.instance_variable_get(:@foo) assert_equal X, dup.class end def test_load_with_class_syck_compatibility - hash = Psych.load "--- !ruby/object:Hash\n:user_id: 7\n:username: Lucas\n" + hash = Psych.unsafe_load "--- !ruby/object:Hash\n:user_id: 7\n:username: Lucas\n" assert_equal({ user_id: 7, username: 'Lucas'}, hash) end def test_empty_subclass assert_match "!ruby/hash:#{X}", Psych.dump(X.new) - x = Psych.load Psych.dump X.new + x = Psych.unsafe_load Psych.dump X.new assert_equal X, x.class end def test_map - x = Psych.load "--- !map:#{X} { }\n" + x = Psych.unsafe_load "--- !map:#{X} { }\n" assert_equal X, x.class end @@ -102,7 +102,7 @@ module Psych end def test_ref_append - hash = Psych.load(<<-eoyml) + hash = Psych.unsafe_load(<<-eoyml) --- foo: &foo hello: world @@ -114,7 +114,7 @@ eoyml def test_key_deduplication unless String.method_defined?(:-@) && (-("a" * 20)).equal?((-("a" * 20))) - skip "This Ruby implementation doesn't support string deduplication" + pend "This Ruby implementation doesn't support string deduplication" end hashes = Psych.load(<<-eoyml) diff --git a/test/psych/test_marshalable.rb b/test/psych/test_marshalable.rb index b1f4a837f5..74ee902887 100644 --- a/test/psych/test_marshalable.rb +++ b/test/psych/test_marshalable.rb @@ -6,7 +6,7 @@ module Psych class TestMarshalable < TestCase def test_objects_defining_marshal_dump_and_marshal_load_can_be_dumped sd = SimpleDelegator.new(1) - loaded = Psych.load(Psych.dump(sd)) + loaded = Psych.unsafe_load(Psych.dump(sd)) assert_instance_of(SimpleDelegator, loaded) assert_equal(sd, loaded) @@ -46,7 +46,15 @@ module Psych def test_init_with_takes_priority_over_marshal_methods obj = PsychCustomMarshalable.new(1) - loaded = Psych.load(Psych.dump(obj)) + loaded = Psych.unsafe_load(Psych.dump(obj)) + + assert(PsychCustomMarshalable === loaded) + assert_equal(2, loaded.foo) + end + + def test_init_symbolize_names + obj = PsychCustomMarshalable.new(1) + loaded = Psych.unsafe_load(Psych.dump(obj), symbolize_names: true) assert(PsychCustomMarshalable === loaded) assert_equal(2, loaded.foo) diff --git a/test/psych/test_merge_keys.rb b/test/psych/test_merge_keys.rb index 08ffe58fa8..dcf4f1fce3 100644 --- a/test/psych/test_merge_keys.rb +++ b/test/psych/test_merge_keys.rb @@ -34,7 +34,7 @@ map: end def test_explicit_string - doc = Psych.load <<-eoyml + doc = Psych.unsafe_load <<-eoyml a: &me { hello: world } b: { !!str '<<': *me } eoyml @@ -55,7 +55,7 @@ product: !ruby/object:#{Product.name} <<: *foo eoyml - hash = Psych.load s + hash = Psych.unsafe_load s assert_equal({"bar" => 10}, hash["foo"]) product = hash["product"] assert_equal 10, product.bar @@ -67,7 +67,7 @@ defaults: &defaults development: <<: *defaults eoyml - assert_equal({'<<' => nil }, Psych.load(yaml)['development']) + assert_equal({'<<' => nil }, Psych.unsafe_load(yaml)['development']) end def test_merge_array @@ -77,7 +77,7 @@ foo: &hello baz: <<: *hello eoyml - assert_equal({'<<' => [1]}, Psych.load(yaml)['baz']) + assert_equal({'<<' => [1]}, Psych.unsafe_load(yaml)['baz']) end def test_merge_is_not_partial @@ -89,9 +89,9 @@ foo: &hello baz: <<: [*hello, *default] eoyml - doc = Psych.load yaml + doc = Psych.unsafe_load yaml refute doc['baz'].key? 'hello' - assert_equal({'<<' => [[1], {"hello"=>"world"}]}, Psych.load(yaml)['baz']) + assert_equal({'<<' => [[1], {"hello"=>"world"}]}, Psych.unsafe_load(yaml)['baz']) end def test_merge_seq_nil @@ -100,7 +100,7 @@ foo: &hello baz: <<: [*hello] eoyml - assert_equal({'<<' => [nil]}, Psych.load(yaml)['baz']) + assert_equal({'<<' => [nil]}, Psych.unsafe_load(yaml)['baz']) end def test_bad_seq_merge @@ -109,7 +109,7 @@ defaults: &defaults [1, 2, 3] development: <<: *defaults eoyml - assert_equal({'<<' => [1,2,3]}, Psych.load(yaml)['development']) + assert_equal({'<<' => [1,2,3]}, Psych.unsafe_load(yaml)['development']) end def test_missing_merge_key @@ -117,7 +117,7 @@ development: bar: << : *foo eoyml - exp = assert_raises(Psych::BadAlias) { Psych.load yaml } + exp = assert_raise(Psych::BadAlias) { Psych.load yaml } assert_match 'foo', exp.message end @@ -134,7 +134,7 @@ bar: hash = { "foo" => { "hello" => "world"}, "bar" => { "hello" => "world", "baz" => "boo" } } - assert_equal hash, Psych.load(yaml) + assert_equal hash, Psych.unsafe_load(yaml) end def test_multiple_maps @@ -159,7 +159,7 @@ bar: 'label' => 'center/big' } - assert_equal hash, Psych.load(yaml)[4] + assert_equal hash, Psych.unsafe_load(yaml)[4] end def test_override @@ -185,7 +185,7 @@ bar: 'label' => 'center/big' } - assert_equal hash, Psych.load(yaml)[4] + assert_equal hash, Psych.unsafe_load(yaml)[4] end end end diff --git a/test/psych/test_object.rb b/test/psych/test_object.rb index f1c61451d0..0faf6b244d 100644 --- a/test/psych/test_object.rb +++ b/test/psych/test_object.rb @@ -28,7 +28,7 @@ module Psych def test_tag_round_trip tag = Tagged.new - tag2 = Psych.load(Psych.dump(tag)) + tag2 = Psych.unsafe_load(Psych.dump(tag)) assert_equal tag.baz, tag2.baz assert_instance_of(Tagged, tag2) end @@ -36,7 +36,7 @@ module Psych def test_cyclic_references foo = Foo.new(nil) foo.parent = foo - loaded = Psych.load Psych.dump foo + loaded = Psych.unsafe_load Psych.dump foo assert_instance_of(Foo, loaded) assert_equal loaded, loaded.parent diff --git a/test/psych/test_object_references.rb b/test/psych/test_object_references.rb index ca69c7d288..269d72242e 100644 --- a/test/psych/test_object_references.rb +++ b/test/psych/test_object_references.rb @@ -34,12 +34,16 @@ module Psych def assert_reference_trip obj yml = Psych.dump([obj, obj]) assert_match(/\*-?\d+/, yml) - data = Psych.load yml + begin + data = Psych.load yml + rescue Psych::DisallowedClass + data = Psych.unsafe_load yml + end assert_equal data.first.object_id, data.last.object_id end def test_float_references - data = Psych.load <<-eoyml + data = Psych.unsafe_load <<-eoyml ---\s - &name 1.2 - *name @@ -49,7 +53,7 @@ module Psych end def test_binary_references - data = Psych.load <<-eoyml + data = Psych.unsafe_load <<-eoyml --- - &name !binary |- aGVsbG8gd29ybGQh @@ -60,7 +64,7 @@ module Psych end def test_regexp_references - data = Psych.load <<-eoyml + data = Psych.unsafe_load <<-eoyml ---\s - &name !ruby/regexp /pattern/i - *name diff --git a/test/psych/test_omap.rb b/test/psych/test_omap.rb index 98636ded97..6de0286406 100644 --- a/test/psych/test_omap.rb +++ b/test/psych/test_omap.rb @@ -4,7 +4,7 @@ require_relative 'helper' module Psych class TestOmap < TestCase def test_parse_as_map - o = Psych.load "--- !!omap\na: 1\nb: 2" + o = Psych.unsafe_load "--- !!omap\na: 1\nb: 2" assert_kind_of Psych::Omap, o assert_equal 1, o['a'] assert_equal 2, o['b'] @@ -14,7 +14,7 @@ module Psych map = Psych::Omap.new map['foo'] = 'bar' map['self'] = map - assert_equal(map, Psych.load(Psych.dump(map))) + assert_equal(map, Psych.unsafe_load(Psych.dump(map))) end def test_keys diff --git a/test/psych/test_parser.rb b/test/psych/test_parser.rb index e8225dabb6..3604e7c985 100644 --- a/test/psych/test_parser.rb +++ b/test/psych/test_parser.rb @@ -63,7 +63,7 @@ module Psych parser = Psych::Parser.new klass.new 2.times { - assert_raises(RuntimeError, method.to_s) do + assert_raise(RuntimeError, method.to_s) do parser.parse yaml end } @@ -77,7 +77,7 @@ module Psych end def test_filename - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do @parser.parse '--- `', 'omg!' end assert_match 'omg!', ex.message @@ -180,7 +180,7 @@ module Psych def o.external_encoding; nil end def o.read len; self end - assert_raises(TypeError) do + assert_raise(TypeError) do @parser.parse o end end @@ -193,23 +193,23 @@ module Psych end def test_syntax_error - assert_raises(Psych::SyntaxError) do + assert_raise(Psych::SyntaxError) do @parser.parse("---\n\"foo\"\n\"bar\"\n") end end def test_syntax_error_twice - assert_raises(Psych::SyntaxError) do + assert_raise(Psych::SyntaxError) do @parser.parse("---\n\"foo\"\n\"bar\"\n") end - assert_raises(Psych::SyntaxError) do + assert_raise(Psych::SyntaxError) do @parser.parse("---\n\"foo\"\n\"bar\"\n") end end def test_syntax_error_has_path_for_string - e = assert_raises(Psych::SyntaxError) do + e = assert_raise(Psych::SyntaxError) do @parser.parse("---\n\"foo\"\n\"bar\"\n") end assert_match '(<unknown>):', e.message @@ -219,7 +219,7 @@ module Psych io = StringIO.new "---\n\"foo\"\n\"bar\"\n" def io.path; "hello!"; end - e = assert_raises(Psych::SyntaxError) do + e = assert_raise(Psych::SyntaxError) do @parser.parse(io) end assert_match "(#{io.path}):", e.message diff --git a/test/psych/test_psych.rb b/test/psych/test_psych.rb index 30612ded8f..912bcb9a78 100644 --- a/test/psych/test_psych.rb +++ b/test/psych/test_psych.rb @@ -16,7 +16,7 @@ class TestPsych < Psych::TestCase end def test_line_width_invalid - assert_raises(ArgumentError) { Psych.dump('x', { :line_width => -2 }) } + assert_raise(ArgumentError) { Psych.dump('x', { :line_width => -2 }) } end def test_line_width_no_limit @@ -61,7 +61,7 @@ class TestPsych < Psych::TestCase end def test_load_argument_error - assert_raises(TypeError) do + assert_raise(TypeError) do Psych.load nil end end @@ -75,7 +75,7 @@ class TestPsych < Psych::TestCase end def test_parse_raises_on_bad_input - assert_raises(Psych::SyntaxError) { Psych.parse("--- `") } + assert_raise(Psych::SyntaxError) { Psych.parse("--- `") } end def test_parse_with_fallback @@ -83,8 +83,8 @@ class TestPsych < Psych::TestCase end def test_non_existing_class_on_deserialize - e = assert_raises(ArgumentError) do - Psych.load("--- !ruby/object:NonExistent\nfoo: 1") + e = assert_raise(ArgumentError) do + Psych.unsafe_load("--- !ruby/object:NonExistent\nfoo: 1") end assert_equal 'undefined class/module NonExistent', e.message end @@ -143,7 +143,7 @@ class TestPsych < Psych::TestCase end def test_load_stream_raises_on_bad_input - assert_raises(Psych::SyntaxError) { Psych.load_stream("--- `") } + assert_raise(Psych::SyntaxError) { Psych.load_stream("--- `") } end def test_parse_stream @@ -175,7 +175,7 @@ class TestPsych < Psych::TestCase end def test_parse_stream_raises_on_bad_input - assert_raises(Psych::SyntaxError) { Psych.parse_stream("--- `") } + assert_raise(Psych::SyntaxError) { Psych.parse_stream("--- `") } end def test_add_builtin_type @@ -214,7 +214,7 @@ class TestPsych < Psych::TestCase def test_load_freeze_deduplication unless String.method_defined?(:-@) && (-("a" * 20)).equal?((-("a" * 20))) - skip "This Ruby implementation doesn't support string deduplication" + pend "This Ruby implementation doesn't support string deduplication" end data = Psych.load("--- ['a']", freeze: true) @@ -222,28 +222,28 @@ class TestPsych < Psych::TestCase end def test_load_default_fallback - assert_equal false, Psych.load("") + assert_equal false, Psych.unsafe_load("") end def test_load_with_fallback - assert_equal 42, Psych.load("", "file", fallback: 42) + assert_equal 42, Psych.load("", filename: "file", fallback: 42) end def test_load_with_fallback_nil_or_false - assert_nil Psych.load("", "file", fallback: nil) - assert_equal false, Psych.load("", "file", fallback: false) + assert_nil Psych.load("", filename: "file", fallback: nil) + assert_equal false, Psych.load("", filename: "file", fallback: false) end def test_load_with_fallback_hash - assert_equal Hash.new, Psych.load("", "file", fallback: Hash.new) + assert_equal Hash.new, Psych.load("", filename: "file", fallback: Hash.new) end def test_load_with_fallback_for_nil - assert_nil Psych.load("--- null", "file", fallback: 42) + assert_nil Psych.unsafe_load("--- null", "file", fallback: 42) end def test_load_with_fallback_for_false - assert_equal false, Psych.load("--- false", "file", fallback: 42) + assert_equal false, Psych.unsafe_load("--- false", "file", fallback: 42) end def test_load_file @@ -278,7 +278,7 @@ class TestPsych < Psych::TestCase def test_load_file_default_fallback Tempfile.create(['empty', 'yml']) {|t| - assert_equal false, Psych.load_file(t.path) + assert_equal false, Psych.unsafe_load_file(t.path) } end @@ -325,7 +325,7 @@ class TestPsych < Psych::TestCase t.write("--- !ruby/range\nbegin: 0\nend: 42\nexcl: false\n") t.close assert_equal 0..42, Psych.safe_load_file(t.path, permitted_classes: [Range]) - assert_raises(Psych::DisallowedClass) { + assert_raise(Psych::DisallowedClass) { Psych.safe_load_file(t.path) } } @@ -347,9 +347,9 @@ class TestPsych < Psych::TestCase end def test_degenerate_strings - assert_equal false, Psych.load(' ') + assert_equal false, Psych.unsafe_load(' ') assert_equal false, Psych.parse(' ') - assert_equal false, Psych.load('') + assert_equal false, Psych.unsafe_load('') assert_equal false, Psych.parse('') end @@ -371,17 +371,18 @@ class TestPsych < Psych::TestCase yaml = <<-eoyml foo: bar: baz + 1: 2 hoge: - fuga: piyo eoyml result = Psych.load(yaml) - assert_equal result, { "foo" => { "bar" => "baz"}, "hoge" => [{ "fuga" => "piyo" }] } + assert_equal result, { "foo" => { "bar" => "baz", 1 => 2 }, "hoge" => [{ "fuga" => "piyo" }] } result = Psych.load(yaml, symbolize_names: true) - assert_equal result, { foo: { bar: "baz" }, hoge: [{ fuga: "piyo" }] } + assert_equal result, { foo: { bar: "baz", 1 => 2 }, hoge: [{ fuga: "piyo" }] } result = Psych.safe_load(yaml, symbolize_names: true) - assert_equal result, { foo: { bar: "baz" }, hoge: [{ fuga: "piyo" }] } + assert_equal result, { foo: { bar: "baz", 1 => 2 }, hoge: [{ fuga: "piyo" }] } end end diff --git a/test/psych/test_ractor.rb b/test/psych/test_ractor.rb index c6bed7ce69..1b0d810609 100644 --- a/test/psych/test_ractor.rb +++ b/test/psych/test_ractor.rb @@ -6,7 +6,7 @@ class TestPsychRactor < Test::Unit::TestCase assert_ractor(<<~RUBY, require_relative: 'helper') obj = {foo: [42]} obj2 = Ractor.new(obj) do |obj| - Psych.load(Psych.dump(obj)) + Psych.unsafe_load(Psych.dump(obj)) end.take assert_equal obj, obj2 RUBY @@ -47,4 +47,4 @@ class TestPsychRactor < Test::Unit::TestCase assert_equal true, r RUBY end -end if defined?(Test::Unit::TestCase) +end if defined?(Ractor) diff --git a/test/psych/test_safe_load.rb b/test/psych/test_safe_load.rb index e3972712fc..d13ce7c722 100644 --- a/test/psych/test_safe_load.rb +++ b/test/psych/test_safe_load.rb @@ -22,7 +22,7 @@ module Psych def test_no_recursion x = [] x << x - assert_raises(Psych::BadAlias) do + assert_raise(Psych::BadAlias) do Psych.safe_load Psych.dump(x) end end @@ -37,7 +37,7 @@ module Psych def test_permitted_symbol yml = Psych.dump :foo - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do Psych.safe_load yml end assert_equal( @@ -54,15 +54,15 @@ module Psych end def test_symbol - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do assert_safe_cycle :foo end - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do Psych.safe_load '--- !ruby/symbol foo', permitted_classes: [] end # deprecated interface - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do Psych.safe_load '--- !ruby/symbol foo', [] end @@ -75,16 +75,16 @@ module Psych end def test_foo - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do Psych.safe_load '--- !ruby/object:Foo {}', permitted_classes: [Foo] end # deprecated interface - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do Psych.safe_load '--- !ruby/object:Foo {}', [Foo] end - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do assert_safe_cycle Foo.new end assert_kind_of(Foo, Psych.safe_load(Psych.dump(Foo.new), permitted_classes: [Foo])) @@ -96,7 +96,7 @@ module Psych X = Struct.new(:x) def test_struct_depends_on_sym assert_safe_cycle(X.new, permitted_classes: [X, Symbol]) - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do cycle X.new, permitted_classes: [X] end end @@ -107,14 +107,14 @@ module Psych foo: bar eoyml - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do Psych.safe_load(<<-eoyml, permitted_classes: [Struct]) --- !ruby/struct foo: bar eoyml end - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do Psych.safe_load(<<-eoyml, permitted_classes: [Symbol]) --- !ruby/struct foo: bar @@ -128,14 +128,14 @@ module Psych foo: bar eoyml - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do Psych.safe_load(<<-eoyml, [Struct]) --- !ruby/struct foo: bar eoyml end - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do Psych.safe_load(<<-eoyml, [Symbol]) --- !ruby/struct foo: bar @@ -152,7 +152,7 @@ module Psych end def test_safe_load_raises_on_bad_input - assert_raises(Psych::SyntaxError) { Psych.safe_load("--- `") } + assert_raise(Psych::SyntaxError) { Psych.safe_load("--- `") } end private diff --git a/test/psych/test_serialize_subclasses.rb b/test/psych/test_serialize_subclasses.rb index 8e1d0d354d..344c79b3ef 100644 --- a/test/psych/test_serialize_subclasses.rb +++ b/test/psych/test_serialize_subclasses.rb @@ -17,7 +17,7 @@ module Psych def test_some_object so = SomeObject.new('foo', [1,2,3]) - assert_equal so, Psych.load(Psych.dump(so)) + assert_equal so, Psych.unsafe_load(Psych.dump(so)) end class StructSubclass < Struct.new(:foo) @@ -33,7 +33,7 @@ module Psych def test_struct_subclass so = StructSubclass.new('foo', [1,2,3]) - assert_equal so, Psych.load(Psych.dump(so)) + assert_equal so, Psych.unsafe_load(Psych.dump(so)) end end end diff --git a/test/psych/test_set.rb b/test/psych/test_set.rb index 5690957eff..87944d839e 100644 --- a/test/psych/test_set.rb +++ b/test/psych/test_set.rb @@ -21,7 +21,7 @@ module Psych ### # FIXME: Syck should also support !!set as shorthand def test_load_from_yaml - loaded = Psych.load(<<-eoyml) + loaded = Psych.unsafe_load(<<-eoyml) --- !set foo: bar bar: baz @@ -30,11 +30,11 @@ bar: baz end def test_loaded_class - assert_instance_of(Psych::Set, Psych.load(Psych.dump(@set))) + assert_instance_of(Psych::Set, Psych.unsafe_load(Psych.dump(@set))) end def test_set_shorthand - loaded = Psych.load(<<-eoyml) + loaded = Psych.unsafe_load(<<-eoyml) --- !!set foo: bar bar: baz diff --git a/test/psych/test_string.rb b/test/psych/test_string.rb index 973f38b9c2..20ab79c05a 100644 --- a/test/psych/test_string.rb +++ b/test/psych/test_string.rb @@ -104,7 +104,7 @@ module Psych end def test_string_subclass_with_anchor - y = Psych.load <<-eoyml + y = Psych.unsafe_load <<-eoyml --- body: string: &70121654388580 !ruby/string @@ -116,7 +116,7 @@ body: end def test_self_referential_string - y = Psych.load <<-eoyml + y = Psych.unsafe_load <<-eoyml --- string: &70121654388580 !ruby/string str: ! 'foo' @@ -129,32 +129,32 @@ string: &70121654388580 !ruby/string end def test_another_subclass_with_attributes - y = Psych.load Psych.dump Y.new("foo").tap {|o| o.val = 1} + y = Psych.unsafe_load Psych.dump Y.new("foo").tap {|o| o.val = 1} assert_equal "foo", y assert_equal Y, y.class assert_equal 1, y.val end def test_backwards_with_syck - x = Psych.load "--- !str:#{X.name} foo\n\n" + x = Psych.unsafe_load "--- !str:#{X.name} foo\n\n" assert_equal X, x.class assert_equal 'foo', x end def test_empty_subclass assert_match "!ruby/string:#{X}", Psych.dump(X.new) - x = Psych.load Psych.dump X.new + x = Psych.unsafe_load Psych.dump X.new assert_equal X, x.class end def test_empty_character_subclass assert_match "!ruby/string:#{Z}", Psych.dump(Z.new) - x = Psych.load Psych.dump Z.new + x = Psych.unsafe_load Psych.dump Z.new assert_equal Z, x.class end def test_subclass_with_attributes - y = Psych.load Psych.dump Y.new.tap {|o| o.val = 1} + y = Psych.unsafe_load Psych.dump Y.new.tap {|o| o.val = 1} assert_equal Y, y.class assert_equal 1, y.val end diff --git a/test/psych/test_struct.rb b/test/psych/test_struct.rb index 721df44216..1479798b1f 100644 --- a/test/psych/test_struct.rb +++ b/test/psych/test_struct.rb @@ -22,7 +22,7 @@ module Psych ss = StructSubclass.new(nil, 'foo') ss.foo = ss - loaded = Psych.load(Psych.dump(ss)) + loaded = Psych.unsafe_load(Psych.dump(ss)) assert_instance_of(StructSubclass, loaded.foo) assert_equal(ss, loaded) @@ -30,14 +30,14 @@ module Psych def test_roundtrip thing = PsychStructWithIvar.new('bar') - struct = Psych.load(Psych.dump(thing)) + struct = Psych.unsafe_load(Psych.dump(thing)) assert_equal 'hello', struct.bar assert_equal 'bar', struct.foo end def test_load - obj = Psych.load(<<-eoyml) + obj = Psych.unsafe_load(<<-eoyml) --- !ruby/struct:PsychStructWithIvar :foo: bar :@bar: hello diff --git a/test/psych/test_yaml.rb b/test/psych/test_yaml.rb index 84cbe268fa..e12b9769fe 100644 --- a/test/psych/test_yaml.rb +++ b/test/psych/test_yaml.rb @@ -17,7 +17,7 @@ class Psych_Unit_Tests < Psych::TestCase end def test_y_method - assert_raises(NoMethodError) do + assert_raise(NoMethodError) do OpenStruct.new.y 1 end end @@ -573,7 +573,7 @@ EOY end def test_spec_root_mapping - y = Psych::load( <<EOY + y = Psych::unsafe_load( <<EOY # This stream is an example of a top-level mapping. invoice : 34843 date : 2001-01-23 @@ -1077,7 +1077,7 @@ EOY # Read Psych dumped by the ruby 1.8.3. assert_to_yaml( Rational(1, 2), "!ruby/object:Rational 1/2\n" ) - assert_raises( ArgumentError ) { Psych.load("!ruby/object:Rational INVALID/RATIONAL\n") } + assert_raise( ArgumentError ) { Psych.unsafe_load("!ruby/object:Rational INVALID/RATIONAL\n") } end def test_ruby_complex @@ -1089,7 +1089,7 @@ EOY # Read Psych dumped by the ruby 1.8.3. assert_to_yaml( Complex(3, 4), "!ruby/object:Complex 3+4i\n" ) - assert_raises( ArgumentError ) { Psych.load("!ruby/object:Complex INVALID+COMPLEXi\n") } + assert_raise( ArgumentError ) { Psych.unsafe_load("!ruby/object:Complex INVALID+COMPLEXi\n") } end def test_emitting_indicators @@ -1209,7 +1209,7 @@ EOY def test_circular_references a = []; a[0] = a; a[1] = a inspect_str = "[[...], [...]]" - assert_equal( inspect_str, Psych::load(Psych.dump(a)).inspect ) + assert_equal( inspect_str, Psych::unsafe_load(Psych.dump(a)).inspect ) end # @@ -1264,11 +1264,11 @@ EOY end def test_date_out_of_range - Psych::load('1900-01-01T00:00:00+00:00') + Psych::unsafe_load('1900-01-01T00:00:00+00:00') end def test_normal_exit - Psych.load("2000-01-01 00:00:00.#{"0"*1000} +00:00\n") + Psych.unsafe_load("2000-01-01 00:00:00.#{"0"*1000} +00:00\n") # '[ruby-core:13735]' end diff --git a/test/psych/test_yaml_special_cases.rb b/test/psych/test_yaml_special_cases.rb index 4501704030..205457bcae 100644 --- a/test/psych/test_yaml_special_cases.rb +++ b/test/psych/test_yaml_special_cases.rb @@ -13,7 +13,7 @@ module Psych def test_empty_string s = "" - assert_equal false, Psych.load(s) + assert_equal false, Psych.unsafe_load(s) assert_equal [], Psych.load_stream(s) assert_equal false, Psych.parse(s) assert_equal [], Psych.parse_stream(s).transform @@ -58,8 +58,8 @@ module Psych def test_NaN s = ".NaN" - assert Float::NAN, Psych.load(s).nan? - assert [Float::NAN], Psych.load_stream(s).first.nan? + assert Psych.load(s).nan? + assert Psych.load_stream(s).first.nan? assert Psych.parse(s).transform.nan? assert Psych.parse_stream(s).transform.first.nan? assert Psych.safe_load(s).nan? diff --git a/test/psych/test_yamlstore.rb b/test/psych/test_yamlstore.rb index d1e927cefe..1a1be3700e 100644 --- a/test/psych/test_yamlstore.rb +++ b/test/psych/test_yamlstore.rb @@ -4,7 +4,22 @@ require 'yaml/store' require 'tmpdir' module Psych - Psych::Store = YAML::Store unless defined?(Psych::Store) + class YAML::Store + alias :old_load :load + + def load(content) + table = YAML.load(content, fallback: false) + if table == false + {} + else + table + end + end + end + + unless defined?(Psych::Store) + Psych::Store = YAML::Store + end class YAMLStoreTest < TestCase def setup @@ -24,61 +39,61 @@ module Psych def test_opening_new_file_in_readonly_mode_should_result_in_empty_values @yamlstore.transaction(true) do - assert_nil @yamlstore[:foo] - assert_nil @yamlstore[:bar] + assert_nil @yamlstore["foo"] + assert_nil @yamlstore["bar"] end end def test_opening_new_file_in_readwrite_mode_should_result_in_empty_values @yamlstore.transaction do - assert_nil @yamlstore[:foo] - assert_nil @yamlstore[:bar] + assert_nil @yamlstore["foo"] + assert_nil @yamlstore["bar"] end end def test_data_should_be_loaded_correctly_when_in_readonly_mode @yamlstore.transaction do - @yamlstore[:foo] = "bar" + @yamlstore["foo"] = "bar" end @yamlstore.transaction(true) do - assert_equal "bar", @yamlstore[:foo] + assert_equal "bar", @yamlstore["foo"] end end def test_data_should_be_loaded_correctly_when_in_readwrite_mode @yamlstore.transaction do - @yamlstore[:foo] = "bar" + @yamlstore["foo"] = "bar" end @yamlstore.transaction do - assert_equal "bar", @yamlstore[:foo] + assert_equal "bar", @yamlstore["foo"] end end def test_changes_after_commit_are_discarded @yamlstore.transaction do - @yamlstore[:foo] = "bar" + @yamlstore["foo"] = "bar" @yamlstore.commit - @yamlstore[:foo] = "baz" + @yamlstore["foo"] = "baz" end @yamlstore.transaction(true) do - assert_equal "bar", @yamlstore[:foo] + assert_equal "bar", @yamlstore["foo"] end end def test_changes_are_not_written_on_abort @yamlstore.transaction do - @yamlstore[:foo] = "bar" + @yamlstore["foo"] = "bar" @yamlstore.abort end @yamlstore.transaction(true) do - assert_nil @yamlstore[:foo] + assert_nil @yamlstore["foo"] end end def test_writing_inside_readonly_transaction_raises_error - assert_raises(PStore::Error) do + assert_raise(PStore::Error) do @yamlstore.transaction(true) do - @yamlstore[:foo] = "bar" + @yamlstore["foo"] = "bar" end end end diff --git a/test/psych/visitors/test_to_ruby.rb b/test/psych/visitors/test_to_ruby.rb index e1a0056a61..3d4608b903 100644 --- a/test/psych/visitors/test_to_ruby.rb +++ b/test/psych/visitors/test_to_ruby.rb @@ -20,13 +20,13 @@ module Psych end def test_tz_00_00_loads_without_error - assert Psych.load('1900-01-01T00:00:00+00:00') + assert Psych.unsafe_load('1900-01-01T00:00:00+00:00') end def test_legacy_struct Struct.send(:remove_const, :AWESOME) if Struct.const_defined?(:AWESOME) foo = Struct.new('AWESOME', :bar) - assert_equal foo.new('baz'), Psych.load(<<-eoyml) + assert_equal foo.new('baz'), Psych.unsafe_load(<<-eoyml) !ruby/struct:AWESOME bar: baz eoyml diff --git a/test/psych/visitors/test_yaml_tree.rb b/test/psych/visitors/test_yaml_tree.rb index 69885ee9c6..6a9dbc1ec3 100644 --- a/test/psych/visitors/test_yaml_tree.rb +++ b/test/psych/visitors/test_yaml_tree.rb @@ -62,19 +62,19 @@ module Psych def test_struct_anon s = Struct.new(:foo).new('bar') - obj = Psych.load(Psych.dump(s)) + obj = Psych.unsafe_load(Psych.dump(s)) assert_equal s.foo, obj.foo end def test_override_method s = Struct.new(:method).new('override') - obj = Psych.load(Psych.dump(s)) + obj = Psych.unsafe_load(Psych.dump(s)) assert_equal s.method, obj.method end def test_exception ex = Exception.new 'foo' - loaded = Psych.load(Psych.dump(ex)) + loaded = Psych.unsafe_load(Psych.dump(ex)) assert_equal ex.message, loaded.message assert_equal ex.class, loaded.class @@ -88,7 +88,7 @@ module Psych def test_time t = Time.now - assert_equal t, Psych.load(Psych.dump(t)) + assert_equal t, Psych.unsafe_load(Psych.dump(t)) end def test_date @@ -127,11 +127,11 @@ module Psych end def test_anon_class - assert_raises(TypeError) do + assert_raise(TypeError) do @v.accept Class.new end - assert_raises(TypeError) do + assert_raise(TypeError) do Psych.dump(Class.new) end end diff --git a/test/rdoc/test_rdoc_markdown.rb b/test/rdoc/test_rdoc_markdown.rb index 9c7a406224..ad53e9473c 100644 --- a/test/rdoc/test_rdoc_markdown.rb +++ b/test/rdoc/test_rdoc_markdown.rb @@ -143,7 +143,7 @@ a block quote end def test_parse_code_github - doc = parse <<-MD + doc = <<-MD Example: ``` @@ -156,11 +156,25 @@ code goes here para("Example:"), verb("code goes here\n")) - assert_equal expected, doc + assert_equal expected, parse(doc) + assert_equal expected, parse(doc.sub(/^\n/, '')) + + @parser.github = false + + expected = + doc(para("Example:"), + para("<code>\n""code goes here\n</code>")) + + assert_equal expected, parse(doc) + + expected = + doc(para("Example:\n<code>\n""code goes here\n</code>")) + + assert_equal expected, parse(doc.sub(/^\n/, '')) end def test_parse_code_github_format - doc = parse <<-MD + doc = <<-MD Example: ``` ruby @@ -176,7 +190,21 @@ code goes here para("Example:"), code) - assert_equal expected, doc + assert_equal expected, parse(doc) + assert_equal expected, parse(doc.sub(/^\n/, '')) + + @parser.github = false + + expected = + doc(para("Example:"), + para("<code>ruby\n""code goes here\n</code>")) + + assert_equal expected, parse(doc) + + expected = + doc(para("Example:\n<code>ruby\n""code goes here\n</code>")) + + assert_equal expected, parse(doc.sub(/^\n/, '')) end def test_parse_definition_list @@ -1012,6 +1040,29 @@ and an extra note.[^2] assert_equal expected, doc end + def test_gfm_table + doc = parse <<~MD + | | |compare-ruby|built-ruby| + |------|:----------------|-----------:|---------:| + |test | 1 | 11.618M| 10.757M| + | | | 1.08x| -| + |test | 10 | 1.849M| 1.723M| + | | | 1.07x| -| + MD + + head = ["", "", "compare-ruby", "built-ruby"] + align = [nil, :left, :right, :right] + body = [ + ["test", "1", "11.618M", "10.757M"], + ["", "", "1.08x", "-"], + ["test", "10", "1.849M", "1.723M"], + ["", "", "1.07x", "-"], + ] + expected = doc(@RM::Table.new(head, align, body)) + + assert_equal expected, doc + end + def parse text @parser.parse text end diff --git a/test/rdoc/test_rdoc_markup_attribute_manager.rb b/test/rdoc/test_rdoc_markup_attribute_manager.rb index a180666867..944364ba89 100644 --- a/test/rdoc/test_rdoc_markup_attribute_manager.rb +++ b/test/rdoc/test_rdoc_markup_attribute_manager.rb @@ -172,22 +172,25 @@ class TestRDocMarkupAttributeManager < RDoc::TestCase def test_convert_attrs str = '+foo+'.dup - attrs = RDoc::Markup::AttrSpan.new str.length + attrs = RDoc::Markup::AttrSpan.new str.length, @am.exclusive_bitmap + @am.convert_attrs str, attrs, true @am.convert_attrs str, attrs assert_equal "\000foo\000", str str = '+:foo:+'.dup - attrs = RDoc::Markup::AttrSpan.new str.length + attrs = RDoc::Markup::AttrSpan.new str.length, @am.exclusive_bitmap + @am.convert_attrs str, attrs, true @am.convert_attrs str, attrs assert_equal "\000:foo:\000", str str = '+x-y+'.dup - attrs = RDoc::Markup::AttrSpan.new str.length + attrs = RDoc::Markup::AttrSpan.new str.length, @am.exclusive_bitmap + @am.convert_attrs str, attrs, true @am.convert_attrs str, attrs assert_equal "\000x-y\000", str @@ -243,6 +246,22 @@ class TestRDocMarkupAttributeManager < RDoc::TestCase output('unhandled <p>tag</p> unchanged') end + def test_exclude_tag + assert_equal '<CODE>aaa</CODE>[:symbol]', output('+aaa+[:symbol]') + assert_equal '<CODE>aaa[:symbol]</CODE>', output('+aaa[:symbol]+') + assert_equal 'aaa[:symbol]', output('aaa[:symbol]') + assert_equal '<B><CODE>index</CODE></B>', output('<b><tt>index</tt></b>') + end + + def test_exclude_tag_flow + assert_equal [@tt_on, "aaa", @tt_off, "[:symbol]"], + @am.flow("+aaa+[:symbol]") + assert_equal [@tt_on, "aaa[:symbol]", @tt_off], + @am.flow("+aaa[:symbol]+") + assert_equal ["aaa[:symbol]"], + @am.flow("aaa[:symbol]") + end + def test_html_like_em_bold assert_equal ["cat ", @em_on, "and ", @em_to_bold, "dog", @bold_off], @am.flow("cat <i>and </i><b>dog</b>") diff --git a/test/rdoc/test_rdoc_markup_to_html.rb b/test/rdoc/test_rdoc_markup_to_html.rb index fb94269064..29da968abc 100644 --- a/test/rdoc/test_rdoc_markup_to_html.rb +++ b/test/rdoc/test_rdoc_markup_to_html.rb @@ -704,6 +704,17 @@ EXPECTED assert_equal "\n<p><a href=\"irc://irc.freenode.net/#ruby-lang\">ruby-lang</a></p>\n", result end + def test_convert_with_exclude_tag + assert_equal "\n<p><code>aaa</code>[:symbol]</p>\n", @to.convert('+aaa+[:symbol]') + assert_equal "\n<p><code>aaa[:symbol]</code></p>\n", @to.convert('+aaa[:symbol]+') + assert_equal "\n<p><a href=\":symbol\">aaa</a></p>\n", @to.convert('aaa[:symbol]') + end + + def test_convert_underscore_adjacent_to_code + assert_equal "\n<p><code>aaa</code>_</p>\n", @to.convert(%q{+aaa+_}) + assert_equal "\n<p>`<code>i386-mswin32_</code><em>MSRTVERSION</em>'</p>\n", @to.convert(%q{`+i386-mswin32_+_MSRTVERSION_'}) + end + def test_gen_url assert_equal '<a href="example">example</a>', @to.gen_url('link:example', 'example') @@ -727,6 +738,27 @@ EXPECTED assert_equal '<img src="https://example.com/image.png" />', @to.gen_url('https://example.com/image.png', 'ignored') end + def test_gen_url_rdoc_file + assert_equal '<a href="doc/example_rdoc.html">example</a>', + @to.gen_url('doc/example.rdoc', 'example') + assert_equal '<a href="../ex_doc/example_rdoc.html">example</a>', + @to.gen_url('../ex.doc/example.rdoc', 'example') + end + + def test_gen_url_md_file + assert_equal '<a href="doc/example_md.html">example</a>', + @to.gen_url('doc/example.md', 'example') + assert_equal '<a href="../ex_doc/example_md.html">example</a>', + @to.gen_url('../ex.doc/example.md', 'example') + end + + def test_gen_url_rb_file + assert_equal '<a href="doc/example_rb.html">example</a>', + @to.gen_url('doc/example.rb', 'example') + assert_equal '<a href="../ex_doc/example_rb.html">example</a>', + @to.gen_url('../ex.doc/example.rb', 'example') + end + def test_handle_regexp_HYPERLINK_link target = RDoc::Markup::RegexpHandling.new 0, 'link:README.txt' diff --git a/test/rdoc/test_rdoc_options.rb b/test/rdoc/test_rdoc_options.rb index 140c4afc9b..f547f5bff3 100644 --- a/test/rdoc/test_rdoc_options.rb +++ b/test/rdoc/test_rdoc_options.rb @@ -145,7 +145,7 @@ class TestRDocOptions < RDoc::TestCase @options.encoding = Encoding::IBM437 - options = YAML.load YAML.dump @options + options = YAML.safe_load(YAML.dump(@options), permitted_classes: [RDoc::Options, Symbol]) assert_equal Encoding::IBM437, options.encoding end @@ -161,7 +161,7 @@ rdoc_include: - /etc YAML - options = YAML.load yaml + options = YAML.safe_load(yaml, permitted_classes: [RDoc::Options, Symbol]) assert_empty options.rdoc_include assert_empty options.static_path @@ -749,7 +749,7 @@ rdoc_include: assert File.exist? '.rdoc_options' - assert_equal @options, YAML.load(File.read('.rdoc_options')) + assert_equal @options, YAML.safe_load(File.read('.rdoc_options'), permitted_classes: [RDoc::Options, Symbol]) end end diff --git a/test/rdoc/test_rdoc_parser_changelog.rb b/test/rdoc/test_rdoc_parser_changelog.rb index d93cb7deca..6584840572 100644 --- a/test/rdoc/test_rdoc_parser_changelog.rb +++ b/test/rdoc/test_rdoc_parser_changelog.rb @@ -212,6 +212,8 @@ Mon Dec 3 20:28:02 2012 Koichi Sasada <ko1@atdot.net> change condition of using `opt_send_simple'. More method invocations can be simple. +commit\ 8187228de0142d3ac7950b7d977c2849e934c637 + Other note that will be ignored ChangeLog @@ -270,6 +272,24 @@ Other note that will be ignored assert_equal expected, parser.parse_entries end + def test_parse_entries_git + parser = util_parser <<-ChangeLog +commit\ 709bed2afaee50e2ce803f87bf1ee8291bea41e3 + Author: git <svn-admin@ruby-lang.org> + Date: 2021-01-21 01:03:52 +0900 + + * 2021-01-21 [ci skip] +ChangeLog + + expected = [ + [ "709bed2afaee50e2ce80", + [ "git", "svn-admin@ruby-lang.org", + "2021-01-21 01:03:52 +0900", + "* 2021-01-21 [ci skip]\n"]]] + + assert_equal expected, parser.parse_entries + end + def test_scan parser = util_parser <<-ChangeLog Tue Dec 4 08:32:10 2012 Eric Hodel <drbrain@segment7.net> @@ -309,10 +329,157 @@ Mon Dec 3 20:37:22 2012 Koichi Sasada <ko1@atdot.net> assert_equal expected, @top_level.comment end + def test_scan_git + parser = util_parser <<-ChangeLog +commit\ 38816887962ec167ee46acf500f68df5c3013163 +Author: git <svn-admin@ruby-lang.org> +Date: Sun Jan 24 14:35:51 2021 +0900 + + * 2021-01-24 [ci skip] + +commit\ db7d0b89f6eca66cc7eb155c95f9123133da1ffc +Author: git <svn-admin@ruby-lang.org> +Date: Sat, 23 Jan 2021 06:01:39 +0900 + + * 2021-01-23 [ci skip] + +commit\ a3efbda7128ef20b55505b32d1608ea48f80af4a +Author: git <svn-admin@ruby-lang.org> +Date: 2021-01-22T02:49:39+09:00 + + * 2021-01-22 [ci skip] + +commit\ 709bed2afaee50e2ce803f87bf1ee8291bea41e3 + Author: git <svn-admin@ruby-lang.org> + Date: 2021-01-21 01:03:52 +0900 + + * 2021-01-21 [ci skip] + +commit\ a8dc5156e183489c5121fb1759bda5d9406d9175 + Author: git <svn-admin@ruby-lang.org> + Date: 2021-01-20 01:58:26 +0900 + + * 2021-01-20 [ci skip] + +commit\ de5f8a92d5001799bedb3b1a271a2d9b23c6c8fb + Author: Masataka Pocke Kuwabara <kuwabara@pocke.me> + Date: 2021-01-01 14:25:08 +0900 + + Make args info for RubyVM::AST to available on endless method without parens + + Problem + === + + Arguments information is missing for endless method without parens. + For example: + + ```ruby + # ok + ``` + + It causes an error if a program expects `args` node exists. + + Solution + === + + Call `new_args` on this case. +ChangeLog + + parser.scan + + expected = doc( + head(1, File.basename(@tempfile.path)), + blank_line, + head(2, '2021-01-24'), + blank_line, + log_entry(nil, '38816887962ec167ee46', + 'git', 'svn-admin@ruby-lang.org', 'Sun Jan 24 14:35:51 2021 +0900', + [list(:BULLET, item(nil, para('2021-01-24 [ci skip]')))]), + head(2, '2021-01-23'), + blank_line, + log_entry(nil, 'db7d0b89f6eca66cc7eb', + 'git', 'svn-admin@ruby-lang.org', 'Sat, 23 Jan 2021 06:01:39 +0900', + [list(:BULLET, item(nil, para('2021-01-23 [ci skip]')))]), + head(2, '2021-01-22'), + blank_line, + log_entry(nil, 'a3efbda7128ef20b5550', + 'git', 'svn-admin@ruby-lang.org', '2021-01-22T02:49:39+09:00', + [list(:BULLET, item(nil, para('2021-01-22 [ci skip]')))]), + head(2, '2021-01-21'), + blank_line, + log_entry(nil, '709bed2afaee50e2ce80', + 'git', 'svn-admin@ruby-lang.org', '2021-01-21 01:03:52 +0900', + [list(:BULLET, item(nil, para('2021-01-21 [ci skip]')))]), + head(2, '2021-01-20'), + blank_line, + log_entry(nil, 'a8dc5156e183489c5121', + 'git', 'svn-admin@ruby-lang.org', '2021-01-20 01:58:26 +0900', + [list(:BULLET, item(nil, para('2021-01-20 [ci skip]')))]), + head(2, '2021-01-01'), + blank_line, + log_entry(nil, 'de5f8a92d5001799bedb', + 'Masataka Pocke Kuwabara', 'kuwabara@pocke.me', '2021-01-01 14:25:08 +0900', + [head(4, 'Make args info for RubyVM::AST to available on endless method without parens'), + head(5, 'Problem'), + para("Arguments information is missing for endless method without parens.\n" + + "For example:"), + verb("# ok\n").tap {|v| v.format = :ruby}, + para('It causes an error if a program expects <code>args</code> node exists.'), + head(5, 'Solution'), + para('Call <code>new_args</code> on this case.')])) + + expected.file = @top_level + + assert_equal expected, @top_level.comment + end + + def test_scan_git_commit_date + parser = util_parser <<-ChangeLog +commit\ ee1e690a2df901adb279d7a63fbd92c64e0a5ae6 + Author: Igor Zubkov <igor.zubkov@gmail.com> + AuthorDate: 2016-10-25 03:56:11 +0900 + Commit: Nobuyoshi Nakada <nobu@ruby-lang.org> + CommitDate: 2021-01-07 13:40:42 +0900 + + We don't need "require 'uri'" after "require 'net/http'". + +commit\ 4d0985a7bd8f591dff4b430e288bfd83af782e51 + Author: git <svn-admin@ruby-lang.org> + AuthorDate: 2021-01-07 10:21:34 +0900 + Commit: git <svn-admin@ruby-lang.org> + CommitDate: 2021-01-07 10:21:34 +0900 + + * 2021-01-07 [ci skip] +ChangeLog + + parser.scan + + expected = doc( + head(1, File.basename(@tempfile.path)), + blank_line, + head(2, "2021-01-07"), + blank_line, + log_entry(nil, 'ee1e690a2df901adb279', + 'Igor Zubkov', 'igor.zubkov@gmail.com', + '2016-10-25 03:56:11 +0900', + [head(4, %[We don't need "require 'uri'" after "require 'net/http'".])]), + log_entry(nil, '4d0985a7bd8f591dff4b', + 'git', 'svn-admin@ruby-lang.org', + '2021-01-07 10:21:34 +0900', + [list(:BULLET, item(nil, para("2021-01-07 [ci skip]")))])) + + expected.file = @top_level + + assert_equal expected, @top_level.comment + end + def util_parser content = '' RDoc::Parser::ChangeLog.new \ @top_level, @tempfile.path, content, @options, @stats end + def log_entry(*a) + RDoc::Parser::ChangeLog::Git::LogEntry.new(*a) + end end diff --git a/test/rdoc/test_rdoc_rdoc.rb b/test/rdoc/test_rdoc_rdoc.rb index f7d9b8659f..7b84bb698a 100644 --- a/test/rdoc/test_rdoc_rdoc.rb +++ b/test/rdoc/test_rdoc_rdoc.rb @@ -133,6 +133,29 @@ class TestRDocRDoc < RDoc::TestCase end end + def test_load_options_empty_file + temp_dir do + File.open '.rdoc_options', 'w' do |io| + end + + options = @rdoc.load_options + + assert_equal 'rdoc', options.markup + end + end + + def test_load_options_partial_override + temp_dir do + File.open '.rdoc_options', 'w' do |io| + io.write "markup: Markdown" + end + + options = @rdoc.load_options + + assert_equal 'Markdown', options.markup + end + end + def load_options_no_file temp_dir do options = @rdoc.load_options @@ -433,6 +456,18 @@ class TestRDocRDoc < RDoc::TestCase end end + def test_remove_unparseable_CVE_2021_31799 + temp_dir do + file_list = ['| touch evil.txt && echo tags'] + file_list.each do |f| + FileUtils.touch f rescue omit + end + + assert_equal file_list, @rdoc.remove_unparseable(file_list) + assert_equal file_list, Dir.children('.') + end + end + def test_setup_output_dir Dir.mktmpdir {|d| path = File.join d, 'testdir' diff --git a/test/rdoc/test_rdoc_rubygems_hook.rb b/test/rdoc/test_rdoc_rubygems_hook.rb index 7d59577d97..959214b474 100644 --- a/test/rdoc/test_rdoc_rubygems_hook.rb +++ b/test/rdoc/test_rdoc_rubygems_hook.rb @@ -1,31 +1,62 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require "rubygems" +require "fileutils" +require "tmpdir" require 'rdoc/rubygems_hook' +require "test/unit" -class TestRDocRubygemsHook < Gem::TestCase - +class TestRDocRubygemsHook < Test::Unit::TestCase def setup - super - - @a = util_spec 'a', 2 do |s| + @a = Gem::Specification.new do |s| + s.platform = Gem::Platform::RUBY + s.name = "a" + s.version = 2 s.rdoc_options = %w[--main MyTitle] s.extra_rdoc_files = %w[README] end - - write_file File.join(@tempdir, 'lib', 'a.rb') - write_file File.join(@tempdir, 'README') - - install_gem @a + @tempdir = File.realpath(Dir.mktmpdir("test_rubygems_hook_")) + + @orig_envs = %w[ + GEM_VENDOR + GEMRC + XDG_CACHE_HOME + XDG_CONFIG_HOME + XDG_DATA_HOME + SOURCE_DATE_EPOCH + BUNDLER_VERSION + HOME + RDOCOPT + ].map {|e| [e, ENV.delete(e)]}.to_h + ENV["HOME"] = @tempdir + + Gem.configuration = nil + + @a.instance_variable_set(:@doc_dir, File.join(@tempdir, "doc")) + @a.instance_variable_set(:@gem_dir, File.join(@tempdir, "a-2")) + @a.instance_variable_set(:@full_gem_path, File.join(@tempdir, "a-2")) + @a.loaded_from = File.join(@tempdir, 'a-2', 'a-2.gemspec') + + FileUtils.mkdir_p File.join(@tempdir, 'a-2', 'lib') + FileUtils.touch File.join(@tempdir, 'a-2', 'lib', 'a.rb') + FileUtils.touch File.join(@tempdir, 'a-2', 'README') @hook = RDoc::RubygemsHook.new @a begin RDoc::RubygemsHook.load_rdoc rescue Gem::DocumentError => e - skip e.message + omit e.message end + @old_ui = Gem::DefaultUserInteraction.ui + Gem::DefaultUserInteraction.ui = Gem::SilentUI.new + end - Gem.configuration[:rdoc] = nil + def teardown + ui = Gem::DefaultUserInteraction.ui + Gem::DefaultUserInteraction.ui = @old_ui + FileUtils.rm_rf @tempdir + ui.close + ENV.update(@orig_envs) end def test_initialize @@ -78,9 +109,11 @@ class TestRDocRubygemsHook < Gem::TestCase refute rdoc.options.hyperlink_all assert_equal Pathname(@a.full_gem_path), rdoc.options.root - assert_equal %w[README lib], rdoc.options.files.sort assert_equal 'MyTitle', rdoc.store.main + + omit "skip rdoc rubygems integration test: maybe some fixes are required in rubygems." + assert_equal %w[README lib], rdoc.options.files.sort end def test_generate_all @@ -99,9 +132,11 @@ class TestRDocRubygemsHook < Gem::TestCase refute rdoc.options.hyperlink_all assert_equal Pathname(@a.full_gem_path), rdoc.options.root - assert_equal %w[README lib], rdoc.options.files.sort assert_equal 'MyTitle', rdoc.store.main + + omit "skip rdoc rubygems integration test: maybe some fixes are required in rubygems." + assert_equal %w[README lib], rdoc.options.files.sort end def test_generate_configuration_rdoc_array @@ -165,8 +200,8 @@ class TestRDocRubygemsHook < Gem::TestCase @hook.generate - refute_path_exists File.join(@a.doc_dir('rdoc'), 'index.html') - assert_path_exists File.join(@a.doc_dir('ri'), 'cache.ri') + assert_path_not_exist File.join(@a.doc_dir('rdoc'), 'index.html') + assert_path_exist File.join(@a.doc_dir('ri'), 'cache.ri') end def test_generate_no_overwrite @@ -176,8 +211,8 @@ class TestRDocRubygemsHook < Gem::TestCase @hook.generate - refute_path_exists File.join(@a.doc_dir('rdoc'), 'index.html') - refute_path_exists File.join(@a.doc_dir('ri'), 'cache.ri') + assert_path_not_exist File.join(@a.doc_dir('rdoc'), 'index.html') + assert_path_not_exist File.join(@a.doc_dir('ri'), 'cache.ri') end def test_new_rdoc @@ -201,17 +236,17 @@ class TestRDocRubygemsHook < Gem::TestCase refute @hook.rdoc_installed? refute @hook.ri_installed? - assert_path_exists @a.doc_dir + assert_path_exist @a.doc_dir end def test_remove_unwritable - skip 'chmod not supported' if Gem.win_platform? - skip "assumes that euid is not root" if Process.euid == 0 + omit 'chmod not supported' if Gem.win_platform? + omit "assumes that euid is not root" if Process.euid == 0 FileUtils.mkdir_p @a.base_dir FileUtils.chmod 0, @a.base_dir - e = assert_raises Gem::FilePermissionError do + e = assert_raise Gem::FilePermissionError do @hook.remove end @@ -231,17 +266,17 @@ class TestRDocRubygemsHook < Gem::TestCase def test_setup @hook.setup - assert_path_exists @a.doc_dir + assert_path_exist @a.doc_dir end def test_setup_unwritable - skip 'chmod not supported' if Gem.win_platform? - skip "assumes that euid is not root" if Process.euid == 0 + omit 'chmod not supported' if Gem.win_platform? + omit "assumes that euid is not root" if Process.euid == 0 FileUtils.mkdir_p @a.doc_dir FileUtils.chmod 0, @a.doc_dir - e = assert_raises Gem::FilePermissionError do + e = assert_raise Gem::FilePermissionError do @hook.setup end diff --git a/test/rdoc/test_rdoc_top_level.rb b/test/rdoc/test_rdoc_top_level.rb index e396791ab8..a954fde981 100644 --- a/test/rdoc/test_rdoc_top_level.rb +++ b/test/rdoc/test_rdoc_top_level.rb @@ -157,6 +157,9 @@ class TestRDocTopLevel < XrefTestCase def test_http_url assert_equal 'prefix/path/top_level_rb.html', @top_level.http_url('prefix') + + other_level = @store.add_file 'path.other/level.rb' + assert_equal 'prefix/path_other/level_rb.html', other_level.http_url('prefix') end def test_last_modified diff --git a/test/reline/test_reline.rb b/test/reline/test_reline.rb index d2de4690d5..0f32ec4421 100644 --- a/test/reline/test_reline.rb +++ b/test/reline/test_reline.rb @@ -65,6 +65,8 @@ class Reline::Test < Reline::TestCase Reline.completer_word_break_characters = "[".encode(Encoding::ASCII) assert_equal("[", Reline.completer_word_break_characters) assert_equal(get_reline_encoding, Reline.completer_word_break_characters.encoding) + + assert_nothing_raised { Reline.completer_word_break_characters = '' } ensure Reline.completer_word_break_characters = completer_word_break_characters end @@ -89,6 +91,8 @@ class Reline::Test < Reline::TestCase Reline.completer_quote_characters = "`".encode(Encoding::ASCII) assert_equal("`", Reline.completer_quote_characters) assert_equal(get_reline_encoding, Reline.completer_quote_characters.encoding) + + assert_nothing_raised { Reline.completer_quote_characters = '' } ensure Reline.completer_quote_characters = completer_quote_characters end diff --git a/test/reline/test_string_processing.rb b/test/reline/test_string_processing.rb index e76fa384f2..0e0ee9cc04 100644 --- a/test/reline/test_string_processing.rb +++ b/test/reline/test_string_processing.rb @@ -20,4 +20,58 @@ class Reline::LineEditor::StringProcessingTest < Reline::TestCase width = @line_editor.send(:calculate_width, "\1\e[31m\2RubyColor\1\e[34m\2 default string \1\e[m\2>", true) assert_equal('RubyColor default string >'.size, width) end + + def test_completion_proc_with_preposing_and_postposing + buf = ['def hoge', ' puts :aaa', 'end'] + + @line_editor.instance_variable_set(:@is_multiline, true) + @line_editor.instance_variable_set(:@buffer_of_lines, buf) + @line_editor.instance_variable_set(:@line, buf[1]) + @line_editor.instance_variable_set(:@byte_pointer, 3) + @line_editor.instance_variable_set(:@cursor, 3) + @line_editor.instance_variable_set(:@cursor_max, 11) + @line_editor.instance_variable_set(:@line_index, 1) + @line_editor.instance_variable_set(:@completion_proc, proc { |target| + assert_equal('p', target) + }) + @line_editor.__send__(:call_completion_proc) + + @line_editor.instance_variable_set(:@is_multiline, true) + @line_editor.instance_variable_set(:@buffer_of_lines, buf) + @line_editor.instance_variable_set(:@line, buf[1]) + @line_editor.instance_variable_set(:@byte_pointer, 6) + @line_editor.instance_variable_set(:@cursor, 6) + @line_editor.instance_variable_set(:@cursor_max, 11) + @line_editor.instance_variable_set(:@line_index, 1) + @line_editor.instance_variable_set(:@completion_proc, proc { |target, pre, post| + assert_equal('puts', target) + assert_equal("def hoge\n ", pre) + assert_equal(" :aaa\nend", post) + }) + @line_editor.__send__(:call_completion_proc) + + @line_editor.instance_variable_set(:@line, buf[0]) + @line_editor.instance_variable_set(:@byte_pointer, 6) + @line_editor.instance_variable_set(:@cursor, 6) + @line_editor.instance_variable_set(:@cursor_max, 8) + @line_editor.instance_variable_set(:@line_index, 0) + @line_editor.instance_variable_set(:@completion_proc, proc { |target, pre, post| + assert_equal('ho', target) + assert_equal('def ', pre) + assert_equal("ge\n puts :aaa\nend", post) + }) + @line_editor.__send__(:call_completion_proc) + + @line_editor.instance_variable_set(:@line, buf[2]) + @line_editor.instance_variable_set(:@byte_pointer, 1) + @line_editor.instance_variable_set(:@cursor, 1) + @line_editor.instance_variable_set(:@cursor_max, 3) + @line_editor.instance_variable_set(:@line_index, 2) + @line_editor.instance_variable_set(:@completion_proc, proc { |target, pre, post| + assert_equal('e', target) + assert_equal("def hoge\n puts :aaa\n", pre) + assert_equal('nd', post) + }) + @line_editor.__send__(:call_completion_proc) + end end diff --git a/test/reline/test_within_pipe.rb b/test/reline/test_within_pipe.rb index 70a0e0a5de..36a2f1e805 100644 --- a/test/reline/test_within_pipe.rb +++ b/test/reline/test_within_pipe.rb @@ -8,7 +8,7 @@ class Reline::WithinPipeTest < Reline::TestCase @reader, @output_writer = IO.pipe((RELINE_TEST_ENCODING rescue Encoding.default_external)) @output = Reline.output = @output_writer @config = Reline.send(:core).config - @config.keyseq_timeout *= 600 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # for --jit-wait CI + @config.keyseq_timeout *= 600 if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? # for --jit-wait CI @line_editor = Reline.send(:core).line_editor end @@ -59,4 +59,17 @@ class Reline::WithinPipeTest < Reline::TestCase @writer.write("abcde\C-b\C-b\C-b\C-x\C-d\C-x\C-h\C-x\C-v\C-a\C-f\C-f EF\C-x\C-t gh\C-x\M-t\C-b\C-b\C-b\C-b\C-b\C-b\C-b\C-b\C-x\M-u\C-x\M-l\C-x\M-c\n") assert_equal "a\C-aDE gh Fe", Reline.readmultiline(&proc{ true }) end + + def test_delete_text_in_multiline + @writer.write("abc\ndef\nxyz\n") + result = Reline.readmultiline(&proc{ |str| + if str.include?('xyz') + Reline.delete_text + true + else + false + end + }) + assert_equal "abc\ndef", result + end end diff --git a/test/reline/yamatanooroti/test_rendering.rb b/test/reline/yamatanooroti/test_rendering.rb index 6f9a14de67..13693e7c4d 100644 --- a/test/reline/yamatanooroti/test_rendering.rb +++ b/test/reline/yamatanooroti/test_rendering.rb @@ -719,6 +719,17 @@ begin EOC end + def test_reset_rest_height_when_clear_screen + start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') + write("\n\n\n\C-l3\n") + close + assert_screen(<<~EOC) + prompt> 3 + => 3 + prompt> + EOC + end + private def write_inputrc(content) File.open(@inputrc_file, 'w') do |f| f.write content diff --git a/test/rinda/test_rinda.rb b/test/rinda/test_rinda.rb index 00e1ba7877..f155e88de1 100644 --- a/test/rinda/test_rinda.rb +++ b/test/rinda/test_rinda.rb @@ -402,7 +402,7 @@ module TupleSpaceTestModule end def test_cancel_02 - skip 'this test is unstable with --jit-wait' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? + skip 'this test is unstable with --jit-wait' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? entry = @ts.write([:removeme, 1]) assert_equal([[:removeme, 1]], @ts.read_all([nil, nil])) entry.cancel @@ -662,7 +662,7 @@ class TestRingServer < Test::Unit::TestCase end def test_do_reply_local - skip 'timeout-based test becomes unstable with --jit-wait' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? + skip 'timeout-based test becomes unstable with --jit-wait' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? with_timeout(30) {_test_do_reply_local} end diff --git a/test/ripper/test_lexer.rb b/test/ripper/test_lexer.rb index 3eaeb8fefa..975976a381 100644 --- a/test/ripper/test_lexer.rb +++ b/test/ripper/test_lexer.rb @@ -215,4 +215,16 @@ class TestRipper::Lexer < Test::Unit::TestCase end end end + + def test_lex_with_syntax_error_and_heredo + bug = '[Bug #17644]' + s = <<~EOF + foo + end + <<~EOS + bar + EOS + EOF + assert_equal([[5, 0], :on_heredoc_end, "EOS\n", state(:EXPR_BEG)], Ripper.lex(s).last, bug) + end end diff --git a/test/ruby/test_alias.rb b/test/ruby/test_alias.rb index 271d552bf5..99f2223b49 100644 --- a/test/ruby/test_alias.rb +++ b/test/ruby/test_alias.rb @@ -253,4 +253,33 @@ class TestAlias < Test::Unit::TestCase assert_equal(:foo, k.instance_method(:bar).original_name) assert_equal(:foo, name) end + + def test_alias_suppressing_redefinition + assert_in_out_err(%w[-w], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class A + def foo; end + alias foo foo + def foo; end + end + end; + end + + def test_alias_memory_leak + assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", rss: true) + begin; + class A + 500.times do + 1000.times do |i| + define_method(:"foo_#{i}") {} + + alias :"foo_#{i}" :"foo_#{i}" + + remove_method :"foo_#{i}" + end + GC.start + end + end + end; + end end diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb index 522b58e214..30bf13795c 100644 --- a/test/ruby/test_array.rb +++ b/test/ruby/test_array.rb @@ -654,6 +654,15 @@ class TestArray < Test::Unit::TestCase assert_raise(TypeError) { [0].concat(:foo) } assert_raise(FrozenError) { [0].freeze.concat(:foo) } + + a = @cls[nil] + def (x = Object.new).to_ary + ary = Array.new(2) + ary << [] << [] << :ok + end + EnvUtil.under_gc_stress {a.concat(x)} + GC.start + assert_equal(:ok, a.last) end def test_count @@ -754,6 +763,15 @@ class TestArray < Test::Unit::TestCase a = @cls[ 5, 6, 7, 8, 9, 10 ] assert_equal(9, a.delete_if {|i| break i if i > 8; i < 7}) assert_equal(@cls[7, 8, 9, 10], a, bug2545) + + assert_raise(FrozenError) do + a = @cls[1, 2, 3, 42] + a.delete_if do + a.freeze + true + end + end + assert_equal(@cls[1, 2, 3, 42], a) end def test_dup @@ -1322,6 +1340,15 @@ class TestArray < Test::Unit::TestCase a = @cls[ 5, 6, 7, 8, 9, 10 ] assert_equal(9, a.reject! {|i| break i if i > 8; i < 7}) assert_equal(@cls[7, 8, 9, 10], a, bug2545) + + assert_raise(FrozenError) do + a = @cls[1, 2, 3, 42] + a.reject! do + a.freeze + true + end + end + assert_equal(@cls[1, 2, 3, 42], a) end def test_shared_array_reject! @@ -1545,6 +1572,8 @@ class TestArray < Test::Unit::TestCase assert_nil(a.slice(10, -3)) assert_equal @cls[], a.slice(10..7) + + assert_equal([100], a.slice(-1, 1_000_000_000)) end def test_slice! @@ -1593,6 +1622,21 @@ class TestArray < Test::Unit::TestCase assert_raise(ArgumentError) { @cls[1].slice!(0, 0, 0) } end + def test_slice_out_of_range! + a = @cls[*(1..100).to_a] + + assert_nil(a.clone.slice!(-101..-1)) + assert_nil(a.clone.slice!(-101..)) + + # assert_raise_with_message(RangeError, "((-101..-1).%(2)) out of range") { a.clone.slice!((-101..-1)%2) } + # assert_raise_with_message(RangeError, "((-101..).%(2)) out of range") { a.clone.slice!((-101..)%2) } + + assert_nil(a.clone.slice!(10, -3)) + assert_equal @cls[], a.clone.slice!(10..7) + + assert_equal([100], a.clone.slice!(-1, 1_000_000_000)) + end + def test_sort a = @cls[ 4, 1, 2, 3 ] assert_equal(@cls[1, 2, 3, 4], a.sort) @@ -2599,6 +2643,15 @@ class TestArray < Test::Unit::TestCase a = @cls[ 1, 2, 3, 4, 5 ] a.select! {|i| a.clear if i == 5; false } assert_equal(0, a.size, bug13053) + + assert_raise(FrozenError) do + a = @cls[1, 2, 3, 42] + a.select! do + a.freeze + false + end + end + assert_equal(@cls[1, 2, 3, 42], a) end # also select! @@ -2614,6 +2667,15 @@ class TestArray < Test::Unit::TestCase a = @cls[ 1, 2, 3, 4, 5 ] assert_equal(a, a.keep_if { |i| i > 3 }) assert_equal(@cls[4, 5], a) + + assert_raise(FrozenError) do + a = @cls[1, 2, 3, 42] + a.keep_if do + a.freeze + false + end + end + assert_equal(@cls[1, 2, 3, 42], a) end def test_filter diff --git a/test/ruby/test_backtrace.rb b/test/ruby/test_backtrace.rb index 00c96b3b9f..aa79db24cb 100644 --- a/test/ruby/test_backtrace.rb +++ b/test/ruby/test_backtrace.rb @@ -138,10 +138,66 @@ class TestBacktrace < Test::Unit::TestCase rec[m] end + def test_caller_with_limit + x = nil + c = Class.new do + define_method(:bar) do + x = caller(1, 1) + end + end + [c.new].group_by(&:bar) + assert_equal 1, x.length + assert_equal caller(0), caller(0, nil) + end + def test_caller_with_nil_length assert_equal caller(0), caller(0, nil) end + def test_caller_locations_first_label + def self.label + caller_locations.first.label + end + + def self.label_caller + label + end + + assert_equal 'label_caller', label_caller + + [1].group_by do + assert_equal 'label_caller', label_caller + end + end + + def test_caller_limit_cfunc_iseq_no_pc + def self.a; [1].group_by { b } end + def self.b + [ + caller_locations(2, 1).first.base_label, + caller_locations(3, 1).first.base_label + ] + end + assert_equal({["each", "group_by"]=>[1]}, a) + end + + def test_caller_location_inspect_cfunc_iseq_no_pc + def self.foo + @res = caller_locations(2, 1).inspect + end + @line = __LINE__ + 1 + 1.times.map { 1.times.map { foo } } + assert_equal("[\"#{__FILE__}:#{@line}:in `times'\"]", @res) + end + + def test_caller_location_path_cfunc_iseq_no_pc + def self.foo + @res = caller_locations(2, 1)[0].path + end + 1.times.map { 1.times.map { foo } } + assert_equal(__FILE__, @res) + end + def test_caller_locations cs = caller(0); locs = caller_locations(0).map{|loc| loc.to_s diff --git a/test/ruby/test_enum.rb b/test/ruby/test_enum.rb index 126b100b03..c527191eff 100644 --- a/test/ruby/test_enum.rb +++ b/test/ruby/test_enum.rb @@ -134,6 +134,11 @@ class TestEnumerable < Test::Unit::TestCase assert_equal([1, 2, 3, 1, 2], @obj.to_a) end + def test_to_a_keywords + def @obj.each(foo:) yield foo end + assert_equal([1], @obj.to_a(foo: 1)) + end + def test_to_a_size_symbol sym = Object.new class << sym @@ -248,11 +253,13 @@ class TestEnumerable < Test::Unit::TestCase assert_equal(15, [3, 5, 7].inject(:+)) assert_float_equal(15.0, [3, 5, 7.0].inject(:+)) assert_equal(2*FIXNUM_MAX, Array.new(2, FIXNUM_MAX).inject(:+)) + assert_equal(3*FIXNUM_MAX, Array.new(3, FIXNUM_MAX).inject(:+)) assert_equal(2*(FIXNUM_MAX+1), Array.new(2, FIXNUM_MAX+1).inject(:+)) assert_equal(10*FIXNUM_MAX, Array.new(10, FIXNUM_MAX).inject(:+)) assert_equal(0, ([FIXNUM_MAX, 1, -FIXNUM_MAX, -1]*10).inject(:+)) assert_equal(FIXNUM_MAX*10, ([FIXNUM_MAX+1, -1]*10).inject(:+)) assert_equal(2*FIXNUM_MIN, Array.new(2, FIXNUM_MIN).inject(:+)) + assert_equal(3*FIXNUM_MIN, Array.new(3, FIXNUM_MIN).inject(:+)) assert_equal((FIXNUM_MAX+1).to_f, [FIXNUM_MAX, 1, 0.0].inject(:+)) assert_float_equal(10.0, [3.0, 5].inject(2.0, :+)) assert_float_equal((FIXNUM_MAX+1).to_f, [0.0, FIXNUM_MAX+1].inject(:+)) @@ -414,6 +421,17 @@ class TestEnumerable < Test::Unit::TestCase empty.first empty.block.call end; + + bug18475 = '[ruby-dev:107059]' + assert_in_out_err([], <<-'end;', [], /unexpected break/, bug18475) + e = Enumerator.new do |g| + Thread.new do + g << 1 + end.join + end + + e.first + end; end def test_sort diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb index 087bcda5ac..6ab72e7954 100644 --- a/test/ruby/test_exception.rb +++ b/test/ruby/test_exception.rb @@ -181,6 +181,27 @@ class TestException < Test::Unit::TestCase } end + def test_catch_throw_in_require_cant_be_rescued + bug18562 = '[ruby-core:107403]' + Tempfile.create(["dep", ".rb"]) {|t| + t.puts("throw :extdep, 42") + t.close + + rescue_all = Class.new(Exception) + def rescue_all.===(_) + raise "should not reach here" + end + + v = assert_throw(:extdep, bug18562) do + require t.path + rescue rescue_all => e + assert(false, "should not reach here") + end + + assert_equal(42, v, bug18562) + } + end + def test_throw_false bug12743 = '[ruby-core:77229] [Bug #12743]' Thread.start { @@ -1100,6 +1121,14 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status| assert_empty warning end + def test_undef_Warning_warn + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + Warning.undef_method(:warn) + assert_raise(NoMethodError) { warn "" } + end; + end + def test_undefined_backtrace assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") begin; diff --git a/test/ruby/test_fiber.rb b/test/ruby/test_fiber.rb index e0cc59bd3c..9e994c7349 100644 --- a/test/ruby/test_fiber.rb +++ b/test/ruby/test_fiber.rb @@ -397,7 +397,7 @@ class TestFiber < Test::Unit::TestCase Fiber.new {}.transfer Fiber.new { Fiber.yield } end - exit!(0) + exit!(true) end }.transfer _, status = Process.waitpid2(xpid) @@ -406,8 +406,13 @@ class TestFiber < Test::Unit::TestCase end.resume end pid, status = Process.waitpid2(pid) - assert_equal(0, status.exitstatus, bug5700) - assert_equal(false, status.signaled?, bug5700) + assert_not_predicate(status, :signaled?, bug5700) + assert_predicate(status, :success?, bug5700) + + pid = Fiber.new {fork}.resume + pid, status = Process.waitpid2(pid) + assert_not_predicate(status, :signaled?) + assert_predicate(status, :success?) end def test_exit_in_fiber diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb index e2314424b3..b9e2a05b8a 100644 --- a/test/ruby/test_file_exhaustive.rb +++ b/test/ruby/test_file_exhaustive.rb @@ -160,9 +160,7 @@ class TestFileExhaustive < Test::Unit::TestCase end def chardev - return @chardev if defined? @chardev - @chardev = File::NULL == "/dev/null" ? "/dev/null" : nil - @chardev + File::NULL end def blockdev @@ -319,7 +317,7 @@ class TestFileExhaustive < Test::Unit::TestCase assert_file.not_chardev?(regular_file) assert_file.not_chardev?(utf8_file) assert_file.not_chardev?(nofile) - assert_file.chardev?(chardev) if chardev + assert_file.chardev?(chardev) end def test_exist_p @@ -1481,6 +1479,31 @@ class TestFileExhaustive < Test::Unit::TestCase assert_equal(File.executable?(f), test(?x, f)) assert_equal(File.executable_real?(f), test(?X, f)) assert_equal(File.zero?(f), test(?z, f)) + + stat = File.stat(f) + assert_equal(stat.atime, File.atime(f), f) + assert_equal(stat.ctime, File.ctime(f), f) + assert_equal(stat.mtime, File.mtime(f), f) + assert_equal(stat.blockdev?, File.blockdev?(f), f) + assert_equal(stat.chardev?, File.chardev?(f), f) + assert_equal(stat.directory?, File.directory?(f), f) + assert_equal(stat.file?, File.file?(f), f) + assert_equal(stat.setgid?, File.setgid?(f), f) + assert_equal(stat.grpowned?, File.grpowned?(f), f) + assert_equal(stat.sticky?, File.sticky?(f), f) + assert_equal(File.lstat(f).symlink?, File.symlink?(f), f) + assert_equal(stat.owned?, File.owned?(f), f) + assert_equal(stat.pipe?, File.pipe?(f), f) + assert_equal(stat.readable?, File.readable?(f), f) + assert_equal(stat.readable_real?, File.readable_real?(f), f) + assert_equal(stat.size?, File.size?(f), f) + assert_equal(stat.socket?, File.socket?(f), f) + assert_equal(stat.setuid?, File.setuid?(f), f) + assert_equal(stat.writable?, File.writable?(f), f) + assert_equal(stat.writable_real?, File.writable_real?(f), f) + assert_equal(stat.executable?, File.executable?(f), f) + assert_equal(stat.executable_real?, File.executable_real?(f), f) + assert_equal(stat.zero?, File.zero?(f), f) end assert_equal(false, test(?-, @dir, fn1)) assert_equal(true, test(?-, fn1, fn1)) @@ -1590,7 +1613,7 @@ class TestFileExhaustive < Test::Unit::TestCase def test_stat_chardev_p assert_not_predicate(File::Stat.new(@dir), :chardev?) assert_not_predicate(File::Stat.new(regular_file), :chardev?) - assert_predicate(File::Stat.new(chardev), :chardev?) if chardev + assert_predicate(File::Stat.new(chardev), :chardev?) end def test_stat_readable_p diff --git a/test/ruby/test_float.rb b/test/ruby/test_float.rb index fbf0d87f8e..b218b72db5 100644 --- a/test/ruby/test_float.rb +++ b/test/ruby/test_float.rb @@ -171,6 +171,24 @@ class TestFloat < Test::Unit::TestCase assert_raise(ArgumentError, n += z + "A") {Float(n)} assert_raise(ArgumentError, n += z + ".0") {Float(n)} end + + x = nil + 2000.times do + x = Float("0x"+"0"*30) + break unless x == 0.0 + end + assert_equal(0.0, x, ->{"%a" % x}) + x = nil + 2000.times do + begin + x = Float("0x1."+"0"*270) + rescue ArgumentError => e + raise unless /"0x1\.0{270}"/ =~ e.message + else + break + end + end + assert_nil(x, ->{"%a" % x}) end def test_divmod diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index 1f75a34cac..01df198b0d 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -321,7 +321,7 @@ class TestGc < Test::Unit::TestCase base_length = GC.stat[:heap_eden_pages] (base_length * 500).times{ 'a' } GC.start - assert_in_epsilon base_length, (v = GC.stat[:heap_eden_pages]), 1/8r, + assert_in_epsilon base_length, (v = GC.stat[:heap_eden_pages]), 1/4r, "invalid heap expanding (base_length: #{base_length}, GC.stat[:heap_eden_pages]: #{v})" a = [] diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb index 62d8b3f836..d217776a2c 100644 --- a/test/ruby/test_hash.rb +++ b/test/ruby/test_hash.rb @@ -419,6 +419,15 @@ class TestHash < Test::Unit::TestCase true } assert_equal(base.size, n) + + h = base.dup + assert_raise(FrozenError) do + h.delete_if do + h.freeze + true + end + end + assert_equal(base.dup, h) end def test_keep_if @@ -426,6 +435,14 @@ class TestHash < Test::Unit::TestCase assert_equal({3=>4,5=>6}, h.keep_if {|k, v| k + v >= 7 }) h = @cls[1=>2,3=>4,5=>6] assert_equal({1=>2,3=>4,5=>6}, h.keep_if{true}) + h = @cls[1=>2,3=>4,5=>6] + assert_raise(FrozenError) do + h.keep_if do + h.freeze + false + end + end + assert_equal(@cls[1=>2,3=>4,5=>6], h) end def test_compact @@ -722,6 +739,15 @@ class TestHash < Test::Unit::TestCase h = base.dup assert_equal(h3, h.reject! {|k,v| v }) assert_equal(h3, h) + + h = base.dup + assert_raise(FrozenError) do + h.reject! do + h.freeze + true + end + end + assert_equal(base.dup, h) end def test_replace @@ -985,6 +1011,19 @@ class TestHash < Test::Unit::TestCase assert_equal("FOO", h.shift) end + def test_shift_for_empty_hash + # [ruby-dev:51159] + h = @cls[] + 100.times{|n| + while h.size < n + k = Random.rand 0..1<<30 + h[k] = 1 + end + 0 while h.shift + assert_equal({}, h) + } + end + def test_reject_bang2 assert_equal({1=>2}, @cls[1=>2,3=>4].reject! {|k, v| k + v == 7 }) assert_nil(@cls[1=>2,3=>4].reject! {|k, v| k == 5 }) @@ -1025,6 +1064,14 @@ class TestHash < Test::Unit::TestCase assert_equal({3=>4,5=>6}, h) h = @cls[1=>2,3=>4,5=>6] assert_equal(nil, h.select!{true}) + h = @cls[1=>2,3=>4,5=>6] + assert_raise(FrozenError) do + h.select! do + h.freeze + false + end + end + assert_equal(@cls[1=>2,3=>4,5=>6], h) end def test_slice @@ -1077,6 +1124,14 @@ class TestHash < Test::Unit::TestCase assert_equal({3=>4,5=>6}, h) h = @cls[1=>2,3=>4,5=>6] assert_equal(nil, h.filter!{true}) + h = @cls[1=>2,3=>4,5=>6] + assert_raise(FrozenError) do + h.filter! do + h.freeze + false + end + end + assert_equal(@cls[1=>2,3=>4,5=>6], h) end def test_clear2 @@ -1674,6 +1729,10 @@ class TestHash < Test::Unit::TestCase x.transform_keys! {|k| -k } assert_equal([-1, :a, 1, :b], x.flatten) + x = @cls[a: 1, b: 2, c: 3] + x.transform_keys! { |k| k == :b && break } + assert_equal({false => 1, b: 2, c: 3}, x) + x = @cls[true => :a, false => :b] x.transform_keys! {|k| !k } assert_equal([false, :a, true, :b], x.flatten) @@ -1710,8 +1769,21 @@ class TestHash < Test::Unit::TestCase assert_same(x, y) x = @cls[a: 1, b: 2, c: 3] + x.transform_values! { |v| v == 2 && break } + assert_equal({a: false, b: 2, c: 3}, x) + + x = @cls[a: 1, b: 2, c: 3] y = x.transform_values!.with_index {|v, i| "#{v}.#{i}" } assert_equal(%w(1.0 2.1 3.2), y.values_at(:a, :b, :c)) + + x = @cls[a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10] + assert_raise(FrozenError) do + x.transform_values!() do |v| + x.freeze if v == 2 + v.succ + end + end + assert_equal(@cls[a: 2, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], x) end def test_broken_hash_value diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index 9f9318eaf7..e178e7579b 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -394,6 +394,24 @@ class TestIO < Test::Unit::TestCase } end + def test_each_byte_closed + pipe(proc do |w| + w << "abc def" + w.close + end, proc do |r| + assert_raise(IOError) do + r.each_byte {|byte| r.close if byte == 32 } + end + end) + make_tempfile {|t| + File.open(t, 'rt') {|f| + assert_raise(IOError) do + f.each_byte {|c| f.close if c == 10} + end + } + } + end + def test_each_codepoint make_tempfile {|t| bug2959 = '[ruby-core:28650]' @@ -405,6 +423,24 @@ class TestIO < Test::Unit::TestCase } end + def test_each_codepoint_closed + pipe(proc do |w| + w.print("abc def") + w.close + end, proc do |r| + assert_raise(IOError) do + r.each_codepoint {|c| r.close if c == 32} + end + end) + make_tempfile {|t| + File.open(t, 'rt') {|f| + assert_raise(IOError) do + f.each_codepoint {|c| f.close if c == 10} + end + } + } + end + def test_rubydev33072 t = make_tempfile path = t.path @@ -440,6 +476,18 @@ class TestIO < Test::Unit::TestCase } end + def test_copy_stream_append_to_nonempty + with_srccontent("foobar") {|src, content| + preface = 'preface' + File.write('dst', preface) + File.open('dst', 'ab') do |dst| + ret = IO.copy_stream(src, dst) + assert_equal(content.bytesize, ret) + assert_equal(preface + content, File.read("dst")) + end + } + end + def test_copy_stream_smaller with_srccontent {|src, content| @@ -1446,6 +1494,13 @@ class TestIO < Test::Unit::TestCase end) end + def test_readpartial_zero_size + File.open(IO::NULL) do |r| + assert_empty(r.readpartial(0, s = "01234567")) + assert_empty(s) + end + end + def test_readpartial_buffer_error with_pipe do |r, w| s = "" @@ -1491,6 +1546,13 @@ class TestIO < Test::Unit::TestCase end) end + def test_read_zero_size + File.open(IO::NULL) do |r| + assert_empty(r.read(0, s = "01234567")) + assert_empty(s) + end + end + def test_read_buffer_error with_pipe do |r, w| s = "" @@ -1528,6 +1590,13 @@ class TestIO < Test::Unit::TestCase } end + def test_read_nonblock_zero_size + File.open(IO::NULL) do |r| + assert_empty(r.read_nonblock(0, s = "01234567")) + assert_empty(s) + end + end + def test_write_nonblock_simple_no_exceptions pipe(proc do |w| w.write_nonblock('1', exception: false) diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb index e3ca1ba926..08340c662c 100644 --- a/test/ruby/test_iseq.rb +++ b/test/ruby/test_iseq.rb @@ -82,6 +82,16 @@ class TestISeq < Test::Unit::TestCase end; end if defined?(RubyVM::InstructionSequence.load) + def test_lambda_with_ractor_roundtrip + iseq = compile(<<~EOF) + x = 42 + y = lambda { x } + Ractor.make_shareable(y) + y.call + EOF + assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval) + end + def test_disasm_encoding src = "\u{3042} = 1; \u{3042}; \u{3043}" asm = compile(src).disasm diff --git a/test/ruby/test_jit.rb b/test/ruby/test_jit.rb index 3a38b1a998..be3550033d 100644 --- a/test/ruby/test_jit.rb +++ b/test/ruby/test_jit.rb @@ -857,6 +857,18 @@ class TestJIT < Test::Unit::TestCase end; end + def test_inlined_getconstant + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 1, min_calls: 2) + begin; + FOO = 1 + def const + FOO + end + print const + print const + end; + end + def test_attr_reader assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "4nil\nnil\n6", success_count: 2, min_calls: 2) begin; diff --git a/test/ruby/test_lazy_enumerator.rb b/test/ruby/test_lazy_enumerator.rb index 3f5a05555b..2116d0ee31 100644 --- a/test/ruby/test_lazy_enumerator.rb +++ b/test/ruby/test_lazy_enumerator.rb @@ -682,4 +682,8 @@ EOS ary = (0..Float::INFINITY).lazy.with_index.take(2).to_a assert_equal([[0, 0], [1, 1]], ary) end + + def test_with_index_size + assert_equal(3, Enumerator::Lazy.new([1, 2, 3], 3){|y, v| y << v}.with_index.size) + end end diff --git a/test/ruby/test_marshal.rb b/test/ruby/test_marshal.rb index ef8b261321..ae6e8708ee 100644 --- a/test/ruby/test_marshal.rb +++ b/test/ruby/test_marshal.rb @@ -672,6 +672,23 @@ class TestMarshal < Test::Unit::TestCase assert_equal(['X', 'X'], Marshal.load(Marshal.dump(obj), ->(v) { v == str ? v.upcase : v })) end + def test_marshal_proc_string_encoding + string = "foo" + payload = Marshal.dump(string) + Marshal.load(payload, ->(v) { + if v.is_a?(String) + assert_equal(string, v) + assert_equal(string.encoding, v.encoding) + end + v + }) + end + + def test_marshal_proc_freeze + object = { foo: [42, "bar"] } + assert_equal object, Marshal.load(Marshal.dump(object), :freeze.to_proc) + end + def test_marshal_load_extended_class_crash assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") begin; @@ -785,8 +802,22 @@ class TestMarshal < Test::Unit::TestCase def test_marshal_with_ruby2_keywords_hash flagged_hash = ruby2_keywords_hash(key: 42) - hash = Marshal.load(Marshal.dump(flagged_hash)) + data = Marshal.dump(flagged_hash) + hash = Marshal.load(data) assert_equal(42, ruby2_keywords_test(*[hash])) + + hash2 = Marshal.load(data.sub(/\x06K(?=T\z)/, "\x08KEY")) + assert_raise(ArgumentError, /\(given 1, expected 0\)/) { + ruby2_keywords_test(*[hash2]) + } + end + + def test_invalid_byte_sequence_symbol + data = Marshal.dump(:K) + data = data.sub(/:\x06K/, "I\\&\x06:\x0dencoding\"\x0dUTF-16LE") + assert_raise(ArgumentError, /UTF-16LE: "\\x4B"/) { + Marshal.load(data) + } end def exception_test @@ -819,4 +850,16 @@ class TestMarshal < Test::Unit::TestCase assert_nil(e2.backtrace_locations) # temporal end end + + class TestMarshalFreezeProc < Test::Unit::TestCase + include MarshalTestLib + + def encode(o) + Marshal.dump(o) + end + + def decode(s) + Marshal.load(s, :freeze.to_proc) + end + end end diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index cc7421b700..0bd5dc63dd 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -1168,6 +1168,19 @@ class TestMethod < Test::Unit::TestCase assert_nil(m.super_method) end + # Bug 17780 + def test_super_method_module_alias + m = Module.new do + def foo + end + alias :f :foo + end + + method = m.instance_method(:f) + super_method = method.super_method + assert_nil(super_method) + end + def rest_parameter(*rest) rest end @@ -1290,6 +1303,21 @@ class TestMethod < Test::Unit::TestCase end; end + def test_override_optimized_method_on_class_using_prepend + assert_separately(%w(--disable-gems), <<-'end;', timeout: 30) + # Bug #17725 [ruby-core:102884] + $VERBOSE = nil + String.prepend(Module.new) + class String + def + other + 'blah blah' + end + end + + assert_equal('blah blah', 'a' + 'b') + end; + end + def test_eqq assert_operator(0.method(:<), :===, 5) assert_not_operator(0.method(:<), :===, -5) diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 43f0c51753..5db6732fee 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -2237,6 +2237,137 @@ class TestModule < Test::Unit::TestCase assert_equal(0, 1 / 2) end + def test_visibility_after_refine_and_visibility_change_with_origin_class + m = Module.new + c = Class.new do + def x; :x end + end + c.prepend(m) + Module.new do + refine c do + def x; :y end + end + end + + o1 = c.new + o2 = c.new + assert_equal(:x, o1.public_send(:x)) + assert_equal(:x, o2.public_send(:x)) + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_multiple_refine_and_visibility_change_with_origin_class + m = Module.new + c = Class.new do + def x; :x end + end + c.prepend(m) + Module.new do + refine c do + def x; :y end + end + end + Module.new do + refine c do + def x; :z end + end + end + + o1 = c.new + o2 = c.new + assert_equal(:x, o1.public_send(:x)) + assert_equal(:x, o2.public_send(:x)) + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_refine_and_visibility_change_without_origin_class + c = Class.new do + def x; :x end + end + Module.new do + refine c do + def x; :y end + end + end + o1 = c.new + o2 = c.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_multiple_refine_and_visibility_change_without_origin_class + c = Class.new do + def x; :x end + end + Module.new do + refine c do + def x; :y end + end + end + Module.new do + refine c do + def x; :z end + end + end + o1 = c.new + o2 = c.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_refine_and_visibility_change_with_superclass + c = Class.new do + def x; :x end + end + sc = Class.new(c) + Module.new do + refine sc do + def x; :y end + end + end + o1 = sc.new + o2 = sc.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_multiple_refine_and_visibility_change_with_superclass + c = Class.new do + def x; :x end + end + sc = Class.new(c) + Module.new do + refine sc do + def x; :y end + end + end + Module.new do + refine sc do + def x; :z end + end + end + o1 = sc.new + o2 = sc.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + def test_prepend_visibility bug8005 = '[ruby-core:53106] [Bug #8005]' c = Class.new do diff --git a/test/ruby/test_nomethod_error.rb b/test/ruby/test_nomethod_error.rb index 170a6c6c57..8b81052905 100644 --- a/test/ruby/test_nomethod_error.rb +++ b/test/ruby/test_nomethod_error.rb @@ -90,4 +90,20 @@ class TestNoMethodError < Test::Unit::TestCase str.__send__(id) end end + + def test_to_s + pre = Module.new do + def name + BasicObject.new + end + end + mod = Module.new + mod.singleton_class.prepend(pre) + + err = assert_raise(NoMethodError) do + mod.this_method_does_not_exist + end + + assert_match(/undefined method.+this_method_does_not_exist.+for.+Module/, err.to_s) + end end diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb index 5f638afa01..d316a00b47 100644 --- a/test/ruby/test_parse.rb +++ b/test/ruby/test_parse.rb @@ -562,6 +562,21 @@ class TestParse < Test::Unit::TestCase assert_syntax_error("\"\\M-\x01\"", 'Invalid escape character syntax') assert_syntax_error("\"\\M-\\C-\x01\"", 'Invalid escape character syntax') assert_syntax_error("\"\\C-\\M-\x01\"", 'Invalid escape character syntax') + + e = assert_syntax_error('"\c\u0000"', 'Invalid escape character syntax') + assert_equal(' ^~~~'"\n", e.message.lines.last) + e = assert_syntax_error('"\c\U0000"', 'Invalid escape character syntax') + assert_equal(' ^~~~'"\n", e.message.lines.last) + + e = assert_syntax_error('"\C-\u0000"', 'Invalid escape character syntax') + assert_equal(' ^~~~~'"\n", e.message.lines.last) + e = assert_syntax_error('"\C-\U0000"', 'Invalid escape character syntax') + assert_equal(' ^~~~~'"\n", e.message.lines.last) + + e = assert_syntax_error('"\M-\u0000"', 'Invalid escape character syntax') + assert_equal(' ^~~~~'"\n", e.message.lines.last) + e = assert_syntax_error('"\M-\U0000"', 'Invalid escape character syntax') + assert_equal(' ^~~~~'"\n", e.message.lines.last) end def test_question diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb index c364de46e4..ed31faec42 100644 --- a/test/ruby/test_refinement.rb +++ b/test/ruby/test_refinement.rb @@ -2488,6 +2488,77 @@ class TestRefinement < Test::Unit::TestCase } end + def test_defining_after_cached + klass = Class.new + refinement = Module.new { refine(klass) { def foo; end } } + klass.new.foo rescue nil # cache the refinement method entry + klass.define_method(:foo) { 42 } + assert_equal(42, klass.new.foo) + end + + # [Bug #17806] + def test_two_refinements_for_prepended_class + assert_normal_exit %q{ + module R1 + refine Hash do + def foo; :r1; end + end + end + + class Hash + prepend(Module.new) + end + + class Hash + def foo; end + end + + {}.method(:foo) # put it on pCMC + + module R2 + refine Hash do + def foo; :r2; end + end + end + + {}.foo + } + end + + # [Bug #17806] + def test_redefining_refined_for_prepended_class + klass = Class.new { def foo; end } + _refinement = Module.new do + refine(klass) { def foo; :refined; end } + end + klass.prepend(Module.new) + klass.new.foo # cache foo + klass.define_method(:foo) { :second } + assert_equal(:second, klass.new.foo) + end + + class Bug17822 + module Ext + refine(Bug17822) do + def foo = :refined + end + end + + private(def foo = :not_refined) + + module Client + using Ext + def self.call_foo + Bug17822.new.foo + end + end + end + + # [Bug #17822] + def test_privatizing_refined_method + assert_equal(:refined, Bug17822::Client.call_foo) + end + private def eval_using(mod, s) diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb index 495c9de919..679a013cf0 100644 --- a/test/ruby/test_regexp.rb +++ b/test/ruby/test_regexp.rb @@ -265,6 +265,27 @@ class TestRegexp < Test::Unit::TestCase assert_equal(re, re.match("foo").regexp) end + def test_match_lambda_multithread + bug17507 = "[ruby-core:101901]" + str = "a-x-foo-bar-baz-z-b" + + worker = lambda do + m = /foo-([A-Za-z0-9_\.]+)-baz/.match(str) + assert_equal("bar", m[1], bug17507) + + # These two lines are needed to trigger the bug + File.exist? "/tmp" + str.gsub(/foo-bar-baz/, "foo-abc-baz") + end + + def self. threaded_test(worker) + 6.times.map {Thread.new {10_000.times {worker.call}}}.each(&:join) + end + + # The bug only occurs in a method calling a block/proc/lambda + threaded_test(worker) + end + def test_source bug5484 = '[ruby-core:40364]' assert_equal('', //.source) @@ -694,11 +715,16 @@ class TestRegexp < Test::Unit::TestCase test = proc {|&blk| "abc".sub("a", ""); blk.call($~) } bug10877 = '[ruby-core:68209] [Bug #10877]' + bug18160 = '[Bug #18160]' test.call {|m| assert_raise_with_message(IndexError, /foo/, bug10877) {m["foo"]} } key = "\u{3042}" [Encoding::UTF_8, Encoding::Shift_JIS, Encoding::EUC_JP].each do |enc| idx = key.encode(enc) - test.call {|m| assert_raise_with_message(IndexError, /#{idx}/, bug10877) {m[idx]} } + pat = /#{idx}/ + test.call {|m| assert_raise_with_message(IndexError, pat, bug10877) {m[idx]} } + test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.offset(idx)} } + test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.begin(idx)} } + test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.end(idx)} } end test.call {|m| assert_equal(/a/, m.regexp) } test.call {|m| assert_equal("abc", m.string) } @@ -1312,6 +1338,21 @@ class TestRegexp < Test::Unit::TestCase assert_nil($1) end + def test_backref_overrun + assert_raise_with_message(SyntaxError, /invalid backref number/) do + eval(%["".match(/(())(?<X>)((?(90000)))/)]) + end + end + + def test_invalid_group + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + assert_raise_with_message(RegexpError, /invalid conditional pattern/) do + Regexp.new("((?(1)x|x|)x)+") + end + end; + end + # This assertion is for porting x2() tests in testpy.py of Onigmo. def assert_match_at(re, str, positions, msg = nil) re = Regexp.new(re) unless re.is_a?(Regexp) diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb index 30c07b9e5c..9b658286c9 100644 --- a/test/ruby/test_require.rb +++ b/test/ruby/test_require.rb @@ -371,15 +371,15 @@ class TestRequire < Test::Unit::TestCase bug = '[ruby-list:49994] path in ospath' base = "test_load\u{3042 3044 3046 3048 304a}".encode(Encoding::Windows_31J) path = nil - Tempfile.create([base, ".rb"]) do |t| - path = t.path - + Dir.mktmpdir do |dir| + path = File.join(dir, base+".rb") assert_raise_with_message(LoadError, /#{base}/) { - load(File.join(File.dirname(path), base)) + load(File.join(dir, base)) } - t.puts "warn 'ok'" - t.close + File.open(path, "w+b") do |t| + t.puts "warn 'ok'" + end assert_include(path, base) assert_warn("ok\n", bug) { assert_nothing_raised(LoadError, bug) { @@ -531,6 +531,28 @@ class TestRequire < Test::Unit::TestCase $".replace(features) end + def test_default_loaded_features_encoding + Dir.mktmpdir {|tmp| + Dir.mkdir("#{tmp}/1") + Dir.mkdir("#{tmp}/2") + File.write("#{tmp}/1/bug18191-1.rb", "") + File.write("#{tmp}/2/bug18191-2.rb", "") + assert_separately(%W[-Eutf-8 -I#{tmp}/1 -], "#{<<~"begin;"}\n#{<<~'end;'}") + tmp = #{tmp.dump}"/2" + begin; + $:.unshift(tmp) + require "bug18191-1" + require "bug18191-2" + encs = [Encoding::US_ASCII, Encoding.find("filesystem")] + message = -> { + require "pp" + {filesystem: encs[1], **$".group_by(&:encoding)}.pretty_inspect + } + assert($".all? {|n| encs.include?(n.encoding)}, message) + end; + } + end + def test_require_changed_current_dir bug7158 = '[ruby-core:47970]' Dir.mktmpdir {|tmp| @@ -839,6 +861,23 @@ class TestRequire < Test::Unit::TestCase } end + def test_provide_in_required_file + paths, loaded = $:.dup, $".dup + Dir.mktmpdir do |tmp| + provide = File.realdirpath("provide.rb", tmp) + File.write(File.join(tmp, "target.rb"), "raise __FILE__\n") + File.write(provide, '$" << '"'target.rb'\n") + $:.replace([tmp]) + assert(require("provide")) + assert(!require("target")) + assert_equal($".pop, provide) + assert_equal($".pop, "target.rb") + end + ensure + $:.replace(paths) + $".replace(loaded) + end + if defined?($LOAD_PATH.resolve_feature_path) def test_resolve_feature_path paths, loaded = $:.dup, $".dup diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index b7831948f0..0d0f4b4d7e 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -1075,6 +1075,11 @@ class TestRubyOptions < Test::Unit::TestCase end end + def test_rubylib_invalid_encoding + env = {"RUBYLIB"=>"\xFF", "LOCALE"=>"en_US.UTF-8", "LC_ALL"=>"en_US.UTF-8"} + assert_ruby_status([env, "-e;"]) + end + def test_null_script skip "#{IO::NULL} is not a character device" unless File.chardev?(IO::NULL) assert_in_out_err([IO::NULL], success: true) diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb index 7821a221dc..2180c31d57 100644 --- a/test/ruby/test_settracefunc.rb +++ b/test/ruby/test_settracefunc.rb @@ -564,6 +564,16 @@ class TestSetTraceFunc < Test::Unit::TestCase } end + # Bug #18264 + def test_tracpoint_memory_leak + assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true) +code = proc { TracePoint.new(:line) { } } +1_000.times(&code) +PREP +1_000_000.times(&code) +CODE + end + def trace_by_set_trace_func events = [] trace = nil @@ -2324,4 +2334,19 @@ class TestSetTraceFunc < Test::Unit::TestCase EOS assert_equal [:return, :unpack], event end + + def test_while_in_while + lines = [] + + TracePoint.new(:line){|tp| + next unless target_thread? + lines << tp.lineno + }.enable{ + n = 3 + while n > 0 + n -= 1 while n > 0 + end + } + assert_equal [__LINE__ - 5, __LINE__ - 4, __LINE__ - 3], lines, 'Bug #17868' + end end diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 4814872789..57e7dae18f 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -105,6 +105,16 @@ PREP CODE end + # Bug #18154 + def test_initialize_nofree_memory_leak + assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true) +code = proc {0.to_s.__send__(:initialize, capacity: 10000)} +1_000.times(&code) +PREP +100_000.times(&code) +CODE + end + def test_AREF # '[]' assert_equal("A", S("AooBar")[0]) assert_equal("B", S("FooBaB")[-1]) @@ -1852,6 +1862,7 @@ CODE def test_strip assert_equal(S("x"), S(" x ").strip) assert_equal(S("x"), S(" \n\r\t x \t\r\n\n ").strip) + assert_equal(S("x"), S("\x00x\x00").strip) assert_equal("0b0 ".force_encoding("UTF-16BE"), "\x00 0b0 ".force_encoding("UTF-16BE").strip) @@ -1870,6 +1881,10 @@ CODE assert_equal(S("x"), a.strip!) assert_equal(S("x"), a) + a = S("\x00x\x00") + assert_equal(S("x"), a.strip!) + assert_equal(S("x"), a) + a = S("x") assert_nil(a.strip!) assert_equal(S("x") ,a) @@ -2703,6 +2718,7 @@ CODE def test_rstrip assert_equal(" hello", " hello ".rstrip) assert_equal("\u3042", "\u3042 ".rstrip) + assert_equal("\u3042", "\u3042\u0000".rstrip) assert_raise(Encoding::CompatibilityError) { "\u3042".encode("ISO-2022-JP").rstrip } end @@ -2723,12 +2739,17 @@ CODE assert_equal(nil, s4.rstrip!) assert_equal("\u3042", s4) + s5 = S("\u3042\u0000") + assert_equal("\u3042", s5.rstrip!) + assert_equal("\u3042", s5) + assert_raise(Encoding::CompatibilityError) { "\u3042".encode("ISO-2022-JP").rstrip! } end def test_lstrip assert_equal("hello ", " hello ".lstrip) assert_equal("\u3042", " \u3042".lstrip) + assert_equal("hello ", "\x00hello ".lstrip) end def test_lstrip_bang @@ -2747,6 +2768,11 @@ CODE s4 = S("\u3042") assert_equal(nil, s4.lstrip!) assert_equal("\u3042", s4) + + s5 = S("\u0000\u3042") + assert_equal("\u3042", s5.lstrip!) + assert_equal("\u3042", s5) + end def test_delete_prefix diff --git a/test/ruby/test_super.rb b/test/ruby/test_super.rb index d94f4679d3..3afde9b0e3 100644 --- a/test/ruby/test_super.rb +++ b/test/ruby/test_super.rb @@ -521,6 +521,37 @@ class TestSuper < Test::Unit::TestCase assert_equal(%w[B A], result, bug9721) end + # [Bug #18329] + def test_super_missing_prepended_module + a = Module.new do + def probe(*methods) + prepend(probing_module(methods)) + end + + def probing_module(methods) + Module.new do + methods.each do |method| + define_method(method) do |*args, **kwargs, &block| + super(*args, **kwargs, &block) + end + end + end + end + end + + b = Class.new do + extend a + + probe :danger!, :missing + + def danger!; end + end + + o = b.new + o.danger! + 2.times { o.missing rescue NoMethodError } + end + def test_from_eval bug10263 = '[ruby-core:65122] [Bug #10263a]' a = Class.new do diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index e289eea2c2..a0cdb5b6fd 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -1730,6 +1730,21 @@ eom assert_equal [[4, 1, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} end + def test_cdhash + assert_separately([], <<-RUBY) + n = case 1 when 2r then false else true end + assert_equal(n, true, '[ruby-core:103759] [Bug #17854]') + RUBY + assert_separately([], <<-RUBY) + n = case 3/2r when 1.5r then true else false end + assert_equal(n, true, '[ruby-core:103759] [Bug #17854]') + RUBY + assert_separately([], <<-RUBY) + n = case 1i when 1i then true else false end + assert_equal(n, true, '[ruby-core:103759] [Bug #17854]') + RUBY + end + private def not_label(x) @result = x; @not_label ||= nil end diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index 3852cb7020..9643a61cb6 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -1146,7 +1146,9 @@ q.pop env = {} env['RUBY_THREAD_VM_STACK_SIZE'] = vm_stack_size.to_s if vm_stack_size env['RUBY_THREAD_MACHINE_STACK_SIZE'] = machine_stack_size.to_s if machine_stack_size - out, = EnvUtil.invoke_ruby([env, '-e', script], '', true, true) + out, err, status = EnvUtil.invoke_ruby([env, '-e', script], '', true, true) + assert_not_predicate(status, :signaled?, err) + use_length ? out.length : out end diff --git a/test/ruby/test_time_tz.rb b/test/ruby/test_time_tz.rb index dade0bff16..1502985200 100644 --- a/test/ruby/test_time_tz.rb +++ b/test/ruby/test_time_tz.rb @@ -7,9 +7,9 @@ class TestTimeTZ < Test::Unit::TestCase has_lisbon_tz = true force_tz_test = ENV["RUBY_FORCE_TIME_TZ_TEST"] == "yes" case RUBY_PLATFORM - when /linux/ + when /darwin|linux/ force_tz_test = true - when /darwin|freebsd|openbsd/ + when /freebsd|openbsd/ has_lisbon_tz = false force_tz_test = true end @@ -95,6 +95,9 @@ class TestTimeTZ < Test::Unit::TestCase CORRECT_KIRITIMATI_SKIP_1994 = with_tz("Pacific/Kiritimati") { Time.local(1994, 12, 31, 0, 0, 0).year == 1995 } + CORRECT_SINGAPORE_1982 = with_tz("Asia/Singapore") { + "2022g" if Time.local(1981, 12, 31, 23, 59, 59).utc_offset == 8*3600 + } def time_to_s(t) t.to_s @@ -140,9 +143,12 @@ class TestTimeTZ < Test::Unit::TestCase def test_asia_singapore with_tz(tz="Asia/Singapore") { - assert_time_constructor(tz, "1981-12-31 23:59:59 +0730", :local, [1981,12,31,23,59,59]) - assert_time_constructor(tz, "1982-01-01 00:30:00 +0800", :local, [1982,1,1,0,0,0]) - assert_time_constructor(tz, "1982-01-01 00:59:59 +0800", :local, [1982,1,1,0,29,59]) + assert_time_constructor(tz, "1981-12-31 23:29:59 +0730", :local, [1981,12,31,23,29,59]) + if CORRECT_SINGAPORE_1982 + assert_time_constructor(tz, "1982-01-01 00:00:00 +0800", :local, [1981,12,31,23,30,00]) + assert_time_constructor(tz, "1982-01-01 00:00:00 +0800", :local, [1982,1,1,0,0,0]) + assert_time_constructor(tz, "1982-01-01 00:29:59 +0800", :local, [1982,1,1,0,29,59]) + end assert_time_constructor(tz, "1982-01-01 00:30:00 +0800", :local, [1982,1,1,0,30,0]) } end @@ -196,7 +202,7 @@ class TestTimeTZ < Test::Unit::TestCase def test_europe_lisbon with_tz("Europe/Lisbon") { - assert_equal("LMT", Time.new(-0x1_0000_0000_0000_0000).zone) + assert_include(%w"LMT CET", Time.new(-0x1_0000_0000_0000_0000).zone) } end if has_lisbon_tz @@ -448,9 +454,12 @@ America/Managua Fri Jan 1 06:00:00 1993 UTC = Fri Jan 1 01:00:00 1993 EST isd America/Managua Wed Jan 1 04:59:59 1997 UTC = Tue Dec 31 23:59:59 1996 EST isdst=0 gmtoff=-18000 America/Managua Wed Jan 1 05:00:00 1997 UTC = Tue Dec 31 23:00:00 1996 CST isdst=0 gmtoff=-21600 Asia/Singapore Sun Aug 8 16:30:00 1965 UTC = Mon Aug 9 00:00:00 1965 SGT isdst=0 gmtoff=27000 -Asia/Singapore Thu Dec 31 16:29:59 1981 UTC = Thu Dec 31 23:59:59 1981 SGT isdst=0 gmtoff=27000 +Asia/Singapore Thu Dec 31 15:59:59 1981 UTC = Thu Dec 31 23:29:59 1981 SGT isdst=0 gmtoff=27000 Asia/Singapore Thu Dec 31 16:30:00 1981 UTC = Fri Jan 1 00:30:00 1982 SGT isdst=0 gmtoff=28800 End + gen_zdump_test <<'End' if CORRECT_SINGAPORE_1982 +Asia/Singapore Thu Dec 31 16:00:00 1981 UTC = Fri Jan 1 00:00:00 1982 SGT isdst=0 gmtoff=28800 +End gen_zdump_test CORRECT_TOKYO_DST_1951 ? <<'End' + (CORRECT_TOKYO_DST_1951 < "2018f" ? <<'2018e' : <<'2018f') : <<'End' Asia/Tokyo Sat May 5 14:59:59 1951 UTC = Sat May 5 23:59:59 1951 JST isdst=0 gmtoff=32400 Asia/Tokyo Sat May 5 15:00:00 1951 UTC = Sun May 6 01:00:00 1951 JDT isdst=1 gmtoff=36000 diff --git a/test/ruby/test_transcode.rb b/test/ruby/test_transcode.rb index 04c8248697..c8b0034e06 100644 --- a/test/ruby/test_transcode.rb +++ b/test/ruby/test_transcode.rb @@ -126,6 +126,28 @@ class TestTranscode < Test::Unit::TestCase assert_equal("D\xFCrst".force_encoding('iso-8859-2'), "D\xFCrst".encode('iso-8859-2', 'iso-8859-1')) end + def test_encode_xml_multibyte + encodings = %w'UTF-8 UTF-16LE UTF-16BE UTF-32LE UTF-32BE' + encodings.each do |src_enc| + encodings.each do |dst_enc| + escaped = "<>".encode(src_enc).encode(dst_enc, :xml=>:text) + assert_equal("<>", escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :text") + + escaped = '<">'.encode(src_enc).encode(dst_enc, :xml=>:attr) + assert_equal('"<">"', escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :attr") + + escaped = "<>".encode(src_enc).force_encoding("UTF-8").encode(dst_enc, src_enc, :xml=>:text) + assert_equal("<>", escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :text") + + escaped = '<">'.encode(src_enc).force_encoding("UTF-8").encode(dst_enc, src_enc, :xml=>:attr) + assert_equal('"<">"', escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :attr") + end + end + # regression test; U+6E7F (湿) uses the same bytes in ISO-2022-JP as "<>" + assert_equal( "<>\u6E7F", "<>\u6E7F".encode("ISO-2022-JP").encode("ISO-2022-JP", :xml=>:text).encode("UTF-8")) + assert_equal("\"<>\u6E7F\"", "<>\u6E7F".encode("ISO-2022-JP").encode("ISO-2022-JP", :xml=>:attr).encode("UTF-8")) + end + def test_ascii_range encodings = [ 'US-ASCII', 'ASCII-8BIT', diff --git a/test/ruby/test_weakmap.rb b/test/ruby/test_weakmap.rb index 3b9eef770a..46d8b50c03 100644 --- a/test/ruby/test_weakmap.rb +++ b/test/ruby/test_weakmap.rb @@ -73,6 +73,15 @@ class TestWeakMap < Test::Unit::TestCase @wm.inspect) end + def test_inspect_garbage + 1000.times do |i| + @wm[i] = Object.new + @wm.inspect + end + assert_match(/\A\#<#{@wm.class.name}:[^:]++:(?:\s\d+\s=>\s\#<(?:Object|collected):[^:<>]*+>(?:,|>\z))+/, + @wm.inspect) + end + def test_each m = __callee__[/test_(.*)/, 1] x1 = Object.new diff --git a/test/rubygems/data/null-type.gemspec.rz b/test/rubygems/data/null-type.gemspec.rz Binary files differindex bad99289d1..2134fcde29 100644 --- a/test/rubygems/data/null-type.gemspec.rz +++ b/test/rubygems/data/null-type.gemspec.rz diff --git a/test/rubygems/encrypted_private_key.pem b/test/rubygems/encrypted_private_key.pem index 868f332b7c..d9667689a6 100644 --- a/test/rubygems/encrypted_private_key.pem +++ b/test/rubygems/encrypted_private_key.pem @@ -1,30 +1,30 @@ -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED -DEK-Info: DES-CBC,4E38D58B5A059DB6 +DEK-Info: AES-256-CBC,CB6FD0B173EF450C6EE21A01DD785C1D -IgWLfnHVnkErKkhysrUMoE0ubkRDtJXZv9KR02jGGFk/kGqWyTqPk08uzhwVNM+l -eOk0qfPykkJM3KZgqTsD6xfA1D5WqFp5mLoFXVVTn9I3acSZsqOY0FweCipwdVpI -x+9Fl+v62kIW06dOjyWLE1abed9hHiXesGGsD87/RJSywy4OBxOcrhR1fJLK4ElR -ya0UzI7rWnmZMChjaZBssfzT1DR79/dARXhon2m5EiIJDjMpc8BKGYlQy5RHCHwA -cnrhUTTvsggZbQtmLZ/yVx8FSJ273XpYR0pmwbw4j1R+zeXQRK5MroBnCfOGcYa7 -rmpERmDW3VAuxXR20SUAGdo1XOMTDe1uLbaotn6e56pXghIaYROTPS+HsuOkAZGY -OYWEkUoyog4l4n+h/C1umFfTFGvKNATLgDugONFvTw/PLbjvl+sWMy2QfqH0MlNB -DIUPxhEVCFD9oB4nfB86WDAmPp1DH9/IBet/21kbQ2eTIzakTdG3XiC+xzAQRu68 -EOCTbasFWGxlCix66gt4xWMLksEg8UhWSpjS3/HsifrKyNMB8sfUFYmZmOYMW4mf -NuEtpBL3AdHNObN8nQ75HfehukzNpbYVRsLzWrVgtxvXHVpnvoCCpCvQBMHeRZxK -6m028mhH1m6yYE/uGFiRKLrN7BKAttbUiqnGgVIg/lQQilFWwylxQ6aXqJGmNgxa -oihzWZRlXivIhhrM7VMnLoKAF/YfmWpP3zahGpBQGfObtPtm44R0ezXPdtsivnyu -CmFOPGzRNMKZtH/lwVhuIIK3AFIGDsRRP9ySN4YfjQZnTdu2sRlxBnANP9m8W9T2 -p+C4zVkDYAbsuWq2HpHwsdL8gqIiXeptsHLqkNw+ulSSLyeBCgM9fpV3RsNGjwqu -k8QLb1CYp2VX46CE8UKvOd/nyFnEsD+EAc3WangEwA41m2IaXcbs9Au7xsG9oacZ -DrxlJVNxlxO9YyP9dNOTfP0fHIiygKQQY2aU3y3oRneu7ogYES5V2mUNH7cYUWVL -CHPXAoUXJErvDQ/opW2DroA9Eqv9sST6WqBf6LXRcWU0ntfzcFUbEqgmCmB7Cbu2 -8udEn6iWilQahLyDoAShLkU7+Tk78Z1c6RuqjyY4VboZPzxrTYK8YIXzwX+jj9bG -KIIGS5eghK185+AjlwtzJ7MBdoL323YIik6uOZluhnJHLaxjxUXGa1VqDgsyqGi7 -ISRMTpVTrbR+UtoEi4ZhMjobtFUr7lGkt24VkXwBKdoyryj4RPHGdp7Tf6XDJufQ -+KKhqt8QrpOTPiMskFN2disOSF5/YZCmtT84nkhU7Hf1lkQ2kfx1zfNk0GqYYXOW -zHOAczy8gWBRetDMnhRYohDzQGWn//b+2Wr2n1RD8D9kyjMRhpFMYfQGfRcuPGjW -91k/T0XFcjcjeZPL9s+HITmrh7zg5WxbCfTEp91j3Oy1bns196SY77TE0BzUsqR2 -geJggcUMEfyvHiiCMtijmSSD9nf8tNIxLVL8Jaf1coA6e1CrlHnYAu2f/Q3GIcvU -EEEmw+cZRwsk4fffYzh5psxxGdXKBv1KcQ/CeBhZL0WJsCp2y5oxwg== +KqHn2Df8hSuwNE+W+60MnGtc6xpoXmF3iN25iVwcN67krYn+N6cBhjFeXwXccYwJ +2gHSu4iEK9Qe32vK0yuv8N9h/fmsabZl0TotnEem/pqO5T8W4LxyK+Rw0s6RB30S +C+mUisRADTanAxyBxsNU8xR8OAUNMAAxV1me6It0W2lfNE3t5jg/Kr0NWMoRUNRx +dkE6WlD5D8jBeC3QdZ6OuE7QXOCEAWAjcFMc0d1WJq2t2r3TrLVfTH7EOoRyvL1H +rrFRx/dEW1UJfM6P11wB5R0nhg3rDXF7oDFszjwO/3tzARke0NZuN37l301lYRl1 +aolO6sShJLa0Ml/TgNcJw0S6rc6a1Z52gTfQKztKcL1UX4HLZg75zKmn6qfatMBC +iXn+pQRYNsOPQ5h4r7lBBqvuV+gBw+rN768tYpZ2/YVDaygxETHcZAFCdAw/JNbP +d0XPIbP79NRrCgzSo58LKQGuOQf3Hh0vp1YS+MilMtm/eogoj1enSPM+ymStHRwG +i+D00xCQ6blSOZ2eUUBJXt11YzP22GYnv+XTR/5kGKkTIvoRMfd+39bQyR32IEv2 +Z+yweAGQInD94eifT9ObbIayJ47y01KP0+Vj6hz4RCFsmJKsYiai5JiKlmf7lV9w +7zH3TtCOx/xSyomesXVRkqvFkdyeguU72kXc5tiMPaDXGCOeV0GWyR1GU1DUX9/K +60E7ym0Wx77WGMKk2fkirZzBdOeliyCRUXd7ccN2rBCjTwtjAUIk27lwzdUaTUv7 +EmjauDvSMFtir58c+zjlLmBaSQOzKcj0KXMp0Oucls9bD85WGGbGyzGhTa0AZ+/+ +cCEJt7RAwW0kTEO/uO+BAZe/zBoi9ek+QBn54FK3E7CXfS4Oi9Qbc3fwlVyTlVmz +ZGrCncO0TIVGErFWK24Z7lX8rBnk8enfnamrPfKtwn4LG9aDfhSj8DtisjlRUVT5 +chDQ+CCi9rh3wXh28lyS+nXJ3yFidCzRgcsc3PpN/c4DNRggZc+C/KDw+J2FW+8Y +p65OliBQHQcG0PnCa2xRyCGevytPG0rfNDgyaY33dPEo90mBLVcwLbzGiSGBHgFl +pr8A/rqbnFpRO39NYbACeRFCqPpzyzfARCCcjcDoFrENdIaJui0fjlBkoV3B/KiK +EVjDcgwt1HAtz8bV2YJ+OpQbhD7E90e2vTRMuXAH21Ygo32VOS0LRlCRc9ZyZW4z +PTyO/6a+FbXZ1zhVJxu/0bmBERZ14WVmWq56oxQav8knpxYeYPgpEmIZnrHnJ1Ko +UoXcc8Hy4NKtaBmDcaF8TCobNsRZTxO/htqpdyNsOrBSsnX2kP5D/O1l1vuVYi1/ +RYfUqL9dvGzvfsFuuDDjDlQ/fIA6pFzJV3fy4KJHlF1r33qaE/lNMdpKljBwvUII +Vog4cGmzxssqK5q9kuogcuyeOuFODjBNW4qt0WylSi9bwwy3ZwaZLRqhngz6+tCV +Jp45Gk881XiVe3aVU0l+4DmJJ9/5vwqjH5Vo/GJqFU6gzB+Zv/0plYeNkuE0Xo2z +ecdxnGKVPl42q44lvczjDw2KX0ahxQrfrbcl48//zR295u9POzCL97d6zpioI2NR -----END RSA PRIVATE KEY----- diff --git a/lib/rubygems/test_case.rb b/test/rubygems/helper.rb index e2763561c6..a09d0783f6 100644 --- a/lib/rubygems/test_case.rb +++ b/test/rubygems/helper.rb @@ -12,36 +12,17 @@ if File.exist?(bundler_gemspec) end begin - gem 'minitest', '~> 5.13' + gem 'test-unit', '~> 3.0' rescue Gem::LoadError end -begin - require 'simplecov' - SimpleCov.start do - add_filter "/test/" - add_filter "/bundler/" - add_filter "/lib/rubygems/resolver/molinillo" - end -rescue LoadError -end - if File.exist?(bundler_gemspec) require_relative '../../bundler/lib/bundler' else require 'bundler' end -# Enable server plugin needed for bisection -if ENV["RG_BISECT_SERVER_PLUGIN"] - require ENV["RG_BISECT_SERVER_PLUGIN"] - - Minitest.extensions << "server" -end - -ENV["MT_NO_PLUGINS"] = "true" - -require 'minitest/autorun' +require 'test/unit' ENV["JARS_SKIP"] = "true" if Gem.java_platform? # avoid unnecessary and noisy `jar-dependencies` post install hook @@ -61,32 +42,28 @@ require 'rubygems/mock_gem_ui' module Gem ## - # Allows setting the gem path searcher. This method is available when - # requiring 'rubygems/test_case' + # Allows setting the gem path searcher. def self.searcher=(searcher) @searcher = searcher end ## - # Allows toggling Windows behavior. This method is available when requiring - # 'rubygems/test_case' + # Allows toggling Windows behavior. def self.win_platform=(val) @@win_platform = val end ## - # Allows setting path to Ruby. This method is available when requiring - # 'rubygems/test_case' + # Allows setting path to Ruby. def self.ruby=(ruby) @ruby = ruby end ## - # When rubygems/test_case is required the default user interaction is a - # MockGemUi. + # Sets the default user interaction to a MockGemUi. module DefaultUserInteraction @ui = Gem::MockGemUi.new @@ -97,8 +74,7 @@ require "rubygems/command" class Gem::Command ## - # Allows resetting the hash of specific args per command. This method is - # available when requiring 'rubygems/test_case' + # Allows resetting the hash of specific args per command. def self.specific_extra_args_hash=(value) @specific_extra_args_hash = value @@ -111,7 +87,7 @@ end # and uninstall gems, fetch remote gems through a stub fetcher and be assured # your normal set of gems is not affected. -class Gem::TestCase < Minitest::Test +class Gem::TestCase < Test::Unit::TestCase extend Gem::Deprecate attr_accessor :fetcher # :nodoc: @@ -120,8 +96,6 @@ class Gem::TestCase < Minitest::Test attr_accessor :uri # :nodoc: - TEST_PATH = ENV.fetch('RUBYGEMS_TEST_PATH', File.expand_path('../../../test/rubygems', __FILE__)) - def assert_activate(expected, *specs) specs.each do |spec| case spec @@ -140,11 +114,48 @@ class Gem::TestCase < Minitest::Test end def assert_directory_exists(path, msg = nil) - msg = message(msg) { "Expected path '#{path}' to be a directory" } - assert_path_exists path + msg = build_message(msg, "Expected path '#{path}' to be a directory") + assert_path_exist path assert File.directory?(path), msg end + # https://github.com/seattlerb/minitest/blob/21d9e804b63c619f602f3f4ece6c71b48974707a/lib/minitest/assertions.rb#L188 + def _synchronize + yield + end + + # https://github.com/seattlerb/minitest/blob/21d9e804b63c619f602f3f4ece6c71b48974707a/lib/minitest/assertions.rb#L546 + def capture_subprocess_io + _synchronize do + begin + require "tempfile" + + captured_stdout, captured_stderr = Tempfile.new("out"), Tempfile.new("err") + + orig_stdout, orig_stderr = $stdout.dup, $stderr.dup + $stdout.reopen captured_stdout + $stderr.reopen captured_stderr + + yield + + $stdout.rewind + $stderr.rewind + + return captured_stdout.read, captured_stderr.read + ensure + captured_stdout.unlink + captured_stderr.unlink + $stdout.reopen orig_stdout + $stderr.reopen orig_stderr + + orig_stdout.close + orig_stderr.close + captured_stdout.close + captured_stderr.close + end + end + end + ## # Sets the ENABLE_SHARED entry in RbConfig::CONFIG to +value+ and restores # the original value when the block ends @@ -236,16 +247,14 @@ class Gem::TestCase < Minitest::Test output.scan(/^#{Regexp.escape make_command}(?:[[:blank:]].*)?$/) end - def parse_make_command_line(line) - command, *args = line.shellsplit + def parse_make_command_line_targets(line) + args = line.sub(/^#{Regexp.escape make_command}/, "").shellsplit targets = [] - macros = {} args.each do |arg| case arg when /\A(\w+)=/ - macros[$1] = $' else targets << arg end @@ -253,35 +262,30 @@ class Gem::TestCase < Minitest::Test targets << '' if targets.empty? - { - :command => command, - :targets => targets, - :macros => macros, - } + targets end def assert_contains_make_command(target, output, msg = nil) if output.match(/\n/) - msg = message(msg) do + msg = build_message(msg, "Expected output containing make command \"%s\", but was \n\nBEGIN_OF_OUTPUT\n%sEND_OF_OUTPUT" % [ ('%s %s' % [make_command, target]).rstrip, output, ] - end + ) else - msg = message(msg) do - 'Expected make command "%s": %s' % [ + msg = build_message(msg, + 'Expected make command "%s", but was "%s"' % [ ('%s %s' % [make_command, target]).rstrip, output, ] - end + ) end assert scan_make_command_lines(output).any? {|line| - make = parse_make_command_line(line) + targets = parse_make_command_line_targets(line) - if make[:targets].include?(target) - yield make, line if block_given? + if targets.include?(target) true else false @@ -377,6 +381,7 @@ class Gem::TestCase < Minitest::Test ENV['GEM_PRIVATE_KEY_PASSPHRASE'] = PRIVATE_KEY_PASSPHRASE + Gem.instance_variable_set(:@default_specifications_dir, nil) if Gem.java_platform? @orig_default_gem_home = RbConfig::CONFIG['default_gem_home'] RbConfig::CONFIG['default_gem_home'] = @gemhome @@ -387,6 +392,14 @@ class Gem::TestCase < Minitest::Test @orig_bindir = RbConfig::CONFIG["bindir"] RbConfig::CONFIG["bindir"] = File.join @gemhome, "bin" + @orig_sitelibdir = RbConfig::CONFIG["sitelibdir"] + new_sitelibdir = @orig_sitelibdir.sub(RbConfig::CONFIG["prefix"], @gemhome) + $LOAD_PATH.insert(Gem.load_path_insert_index, new_sitelibdir) + RbConfig::CONFIG["sitelibdir"] = new_sitelibdir + + @orig_mandir = RbConfig::CONFIG["mandir"] + RbConfig::CONFIG["mandir"] = File.join @gemhome, "share", "man" + Gem::Specification.unresolved_deps.clear Gem.use_paths(@gemhome) @@ -458,15 +471,17 @@ class Gem::TestCase < Minitest::Test Gem.ruby = @orig_ruby if @orig_ruby + RbConfig::CONFIG['mandir'] = @orig_mandir + RbConfig::CONFIG['sitelibdir'] = @orig_sitelibdir RbConfig::CONFIG['bindir'] = @orig_bindir + Gem.instance_variable_set :@default_specifications_dir, nil if Gem.java_platform? RbConfig::CONFIG['default_gem_home'] = @orig_default_gem_home else Gem.instance_variable_set :@default_dir, nil end - Gem::Specification._clear_load_cache Gem::Specification.unresolved_deps.clear Gem::refresh @@ -525,6 +540,10 @@ class Gem::TestCase < Minitest::Test Gem.pre_uninstall_hooks.clear end + def without_any_upwards_gemfiles + ENV["BUNDLE_GEMFILE"] = File.join(@tempdir, "Gemfile") + end + ## # A git_gem is used with a gem dependencies file. The gem created here # has no files, just a gem specification for the given +name+ and +version+. @@ -664,6 +683,28 @@ class Gem::TestCase < Minitest::Test path end + ## + # Load a YAML string, the psych 3 way + + def load_yaml(yaml) + if YAML.respond_to?(:unsafe_load) + YAML.unsafe_load(yaml) + else + YAML.load(yaml) + end + end + + ## + # Load a YAML file, the psych 3 way + + def load_yaml_file(file) + if YAML.respond_to?(:unsafe_load_file) + YAML.unsafe_load_file(file) + else + YAML.load_file(file) + end + end + def all_spec_names Gem::Specification.map(&:full_name) end @@ -778,16 +819,6 @@ class Gem::TestCase < Minitest::Test Gem::Specification.unresolved_deps.values.map(&:to_s).sort end - def save_loaded_features - old_loaded_features = $LOADED_FEATURES.dup - yield - ensure - prefix = File.dirname(__FILE__) + "/" - new_features = ($LOADED_FEATURES - old_loaded_features) - old_loaded_features.concat(new_features.select {|f| f.rindex(prefix, 0) }) - $LOADED_FEATURES.replace old_loaded_features - end - def new_default_spec(name, version, deps = nil, *files) spec = util_spec name, version, deps @@ -1045,19 +1076,23 @@ Also, a list: @fetcher.data["#{@gem_repo}latest_specs.#{v}.gz"] = l_zip @fetcher.data["#{@gem_repo}prerelease_specs.#{v}.gz"] = p_zip - v = Gem.marshal_version - - all_specs.each do |spec| - path = "#{@gem_repo}quick/Marshal.#{v}/#{spec.original_name}.gemspec.rz" - data = Marshal.dump spec - data_deflate = Zlib::Deflate.deflate data - @fetcher.data[path] = data_deflate - end + write_marshalled_gemspecs(*all_specs) end nil # force errors end + def write_marshalled_gemspecs(*all_specs) + v = Gem.marshal_version + + all_specs.each do |spec| + path = "#{@gem_repo}quick/Marshal.#{v}/#{spec.original_name}.gemspec.rz" + data = Marshal.dump spec + data_deflate = Zlib::Deflate.deflate data + @fetcher.data[path] = data_deflate + end + end + ## # Deflates +data+ @@ -1250,7 +1285,11 @@ Also, a list: end def ruby_with_rubygems_in_load_path - [Gem.ruby, "-I", File.expand_path("..", __dir__)] + [Gem.ruby, "-I", rubygems_path] + end + + def rubygems_path + $LOAD_PATH.find{|p| p == File.dirname($LOADED_FEATURES.find{|f| f.end_with?("/rubygems.rb") }) } end def with_clean_path_to_ruby @@ -1281,8 +1320,8 @@ Also, a list: end end - @@good_rake = "#{rubybin} #{escape_path(TEST_PATH, 'good_rake.rb')}" - @@bad_rake = "#{rubybin} #{escape_path(TEST_PATH, 'bad_rake.rb')}" + @@good_rake = "#{rubybin} #{escape_path(__dir__, 'good_rake.rb')}" + @@bad_rake = "#{rubybin} #{escape_path(__dir__, 'bad_rake.rb')}" ## # Construct a new Gem::Dependency. @@ -1469,30 +1508,30 @@ Also, a list: def self.cert_path(cert_name) if 32 == (Time.at(2**32) rescue 32) - cert_file = "#{TEST_PATH}/#{cert_name}_cert_32.pem" + cert_file = "#{__dir__}/#{cert_name}_cert_32.pem" return cert_file if File.exist? cert_file end - "#{TEST_PATH}/#{cert_name}_cert.pem" + "#{__dir__}/#{cert_name}_cert.pem" end ## - # Loads an RSA private key named +key_name+ with +passphrase+ in <tt>test/rubygems/</tt> + # Loads a private key named +key_name+ with +passphrase+ in <tt>test/rubygems/</tt> def self.load_key(key_name, passphrase = nil) key_file = key_path key_name key = File.read key_file - OpenSSL::PKey::RSA.new key, passphrase + OpenSSL::PKey.read key, passphrase end ## # Returns the path to the key named +key_name+ from <tt>test/rubygems</tt> def self.key_path(key_name) - "#{TEST_PATH}/#{key_name}_key.pem" + "#{__dir__}/#{key_name}_key.pem" end # :stopdoc: @@ -1519,4 +1558,38 @@ Also, a list: end if Gem::HAVE_OPENSSL end -require 'rubygems/test_utilities' +# https://github.com/seattlerb/minitest/blob/13c48a03d84a2a87855a4de0c959f96800100357/lib/minitest/mock.rb#L192 +class Object + def stub(name, val_or_callable, *block_args) + new_name = "__minitest_stub__#{name}" + + metaclass = class << self; self; end + + if respond_to? name and not methods.map(&:to_s).include? name.to_s + metaclass.send :define_method, name do |*args| + super(*args) + end + end + + metaclass.send :alias_method, new_name, name + + metaclass.send :define_method, name do |*args, &blk| + if val_or_callable.respond_to? :call + val_or_callable.call(*args, &blk) + else + blk.call(*block_args) if blk + val_or_callable + end + end + + metaclass.send(:ruby2_keywords, name) if metaclass.respond_to?(:ruby2_keywords, true) + + yield self + ensure + metaclass.send :undef_method, name + metaclass.send :alias_method, name, new_name + metaclass.send :undef_method, new_name + end +end + +require_relative 'utilities' diff --git a/lib/rubygems/installer_test_case.rb b/test/rubygems/installer_test_case.rb index 416dac7be6..824ac53a82 100644 --- a/lib/rubygems/installer_test_case.rb +++ b/test/rubygems/installer_test_case.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/installer' class Gem::Installer diff --git a/lib/rubygems/package/tar_test_case.rb b/test/rubygems/package/tar_test_case.rb index 1161d0a5a8..99f503a23f 100644 --- a/lib/rubygems/package/tar_test_case.rb +++ b/test/rubygems/package/tar_test_case.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative '../helper' require 'rubygems/package' ## diff --git a/test/rubygems/packages/ascii_binder-0.1.10.1.gem b/test/rubygems/packages/ascii_binder-0.1.10.1.gem Binary files differnew file mode 100644 index 0000000000..19c505395e --- /dev/null +++ b/test/rubygems/packages/ascii_binder-0.1.10.1.gem diff --git a/test/rubygems/packages/ill-formatted-platform-1.0.0.10.gem b/test/rubygems/packages/ill-formatted-platform-1.0.0.10.gem Binary files differnew file mode 100644 index 0000000000..58a13535c2 --- /dev/null +++ b/test/rubygems/packages/ill-formatted-platform-1.0.0.10.gem diff --git a/test/rubygems/private_ec_key.pem b/test/rubygems/private_ec_key.pem new file mode 100644 index 0000000000..5d855d0dfc --- /dev/null +++ b/test/rubygems/private_ec_key.pem @@ -0,0 +1,9 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,4107F98A374CB8EC18F1AA4EA4B6A0DB + +BRklFxJGcz7gqQYxek8TZkt8qbPhB0FSR6nyw3SYuio/2tlT9ohs74mlK3EbG9Lt +Y4OquJbksBFmoB7fIoM4vnuIZ0Eoz2ooxn9tjhBtqJ3mVscYXwZmA3UDUWDMlviQ +Fu37OpikQv4TFA1jlmUK0LM8xmUCfUeLl0kHD17lFsz2gkO2kwg8mn/YUMOIaDOu +EnnmxbAwnZBpemQkQfpTt2mYL9gu3CcMt5gokBuGDxY= +-----END EC PRIVATE KEY----- diff --git a/test/rubygems/test_bundled_ca.rb b/test/rubygems/test_bundled_ca.rb index 6973758c4c..fff5904aba 100644 --- a/test/rubygems/test_bundled_ca.rb +++ b/test/rubygems/test_bundled_ca.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'net/http' require 'rubygems/openssl' @@ -26,14 +26,14 @@ class TestBundledCA < Gem::TestCase end def assert_https(host) - self.assertions += 1 + assert true http = Net::HTTP.new(host, 443) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_PEER http.cert_store = bundled_certificate_store http.get('/') rescue Errno::ENOENT, Errno::ETIMEDOUT, SocketError - skip "#{host} seems offline, I can't tell whether ssl would work." + pend "#{host} seems offline, I can't tell whether ssl would work." rescue OpenSSL::SSL::SSLError => e # Only fail for certificate verification errors if e.message =~ /certificate verify failed/ diff --git a/test/rubygems/test_config.rb b/test/rubygems/test_config.rb index 015e2b1d8a..4d64f16fca 100644 --- a/test/rubygems/test_config.rb +++ b/test/rubygems/test_config.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems' require 'shellwords' diff --git a/test/rubygems/test_deprecate.rb b/test/rubygems/test_deprecate.rb index 5f8eef76cb..a619eccb37 100644 --- a/test/rubygems/test_deprecate.rb +++ b/test/rubygems/test_deprecate.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/deprecate' class TestDeprecate < Gem::TestCase @@ -49,6 +49,22 @@ class TestDeprecate < Gem::TestCase @message = "bar" end rubygems_deprecate :foo, :bar + + def foo_arg(msg) + @message = "foo" + msg + end + def bar_arg(msg) + @message = "bar" + msg + end + rubygems_deprecate :foo_arg, :bar_arg + + def foo_kwarg(message:) + @message = "foo" + message + end + def bar_kwarg(message:) + @message = "bar" + message + end + rubygems_deprecate :foo_kwarg, :bar_kwarg end class OtherThing @@ -61,24 +77,48 @@ class TestDeprecate < Gem::TestCase @message = "bar" end deprecate :foo, :bar, 2099, 3 + + def foo_arg(msg) + @message = "foo" + msg + end + def bar_arg(msg) + @message = "bar" + msg + end + deprecate :foo_arg, :bar_arg, 2099, 3 + + def foo_kwarg(message:) + @message = "foo" + message + end + def bar_kwarg(message:) + @message = "bar" + message + end + deprecate :foo_kwarg, :bar_kwarg, 2099, 3 end def test_deprecated_method_calls_the_old_method - capture_io do + capture_output do thing = Thing.new thing.foo assert_equal "foo", thing.message + thing.foo_arg("msg") + assert_equal "foomsg", thing.message + thing.foo_kwarg(message: "msg") + assert_equal "foomsg", thing.message end end def test_deprecated_method_outputs_a_warning - out, err = capture_io do + out, err = capture_output do thing = Thing.new thing.foo + thing.foo_arg("msg") + thing.foo_kwarg(message: "msg") end assert_equal "", out assert_match(/Thing#foo is deprecated; use bar instead\./, err) + assert_match(/Thing#foo_arg is deprecated; use bar_arg instead\./, err) + assert_match(/Thing#foo_kwarg is deprecated; use bar_kwarg instead\./, err) assert_match(/in Rubygems [0-9]+/, err) end @@ -101,13 +141,17 @@ class TestDeprecate < Gem::TestCase end def test_deprecated_method_outputs_a_warning_old_way - out, err = capture_io do + out, err = capture_output do thing = OtherThing.new thing.foo + thing.foo_arg("msg") + thing.foo_kwarg(message: "msg") end assert_equal "", out - assert_match(/Thing#foo is deprecated; use bar instead\./, err) - assert_match(/on or after 2099-03-01/, err) + assert_match(/OtherThing#foo is deprecated; use bar instead\./, err) + assert_match(/OtherThing#foo_arg is deprecated; use bar_arg instead\./, err) + assert_match(/OtherThing#foo_kwarg is deprecated; use bar_kwarg instead\./, err) + assert_match(/on or after 2099-03/, err) end end diff --git a/test/rubygems/test_gem.rb b/test/rubygems/test_gem.rb index 1c6d790b25..832701a173 100644 --- a/test/rubygems/test_gem.rb +++ b/test/rubygems/test_gem.rb @@ -1,5 +1,5 @@ # coding: US-ASCII -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems' require 'rubygems/command' require 'rubygems/installer' @@ -19,79 +19,72 @@ class TestGem < Gem::TestCase common_installer_setup - ENV.delete 'RUBYGEMS_GEMDEPS' @additional = %w[a b].map {|d| File.join @tempdir, d } util_remove_interrupt_command end def test_self_finish_resolve - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 1" - b2 = util_spec "b", "2", "c" => ">= 2" - c1 = util_spec "c", "1" - c2 = util_spec "c", "2" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 1" + b2 = util_spec "b", "2", "c" => ">= 2" + c1 = util_spec "c", "1" + c2 = util_spec "c", "2" - install_specs c1, c2, b1, b2, a1 + install_specs c1, c2, b1, b2, a1 - a1.activate + a1.activate - assert_equal %w[a-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + assert_equal %w[a-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - Gem.finish_resolve + Gem.finish_resolve - assert_equal %w[a-1 b-2 c-2], loaded_spec_names - assert_equal [], unresolved_names - end + assert_equal %w[a-1 b-2 c-2], loaded_spec_names + assert_equal [], unresolved_names end def test_self_finish_resolve_wtf - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0", "d" => "> 0" # this - b1 = util_spec "b", "1", { "c" => ">= 1" }, "lib/b.rb" # this - b2 = util_spec "b", "2", { "c" => ">= 2" }, "lib/b.rb" - c1 = util_spec "c", "1" # this - c2 = util_spec "c", "2" - d1 = util_spec "d", "1", { "c" => "< 2" }, "lib/d.rb" - d2 = util_spec "d", "2", { "c" => "< 2" }, "lib/d.rb" # this + a1 = util_spec "a", "1", "b" => "> 0", "d" => "> 0" # this + b1 = util_spec "b", "1", { "c" => ">= 1" }, "lib/b.rb" # this + b2 = util_spec "b", "2", { "c" => ">= 2" }, "lib/b.rb" + c1 = util_spec "c", "1" # this + c2 = util_spec "c", "2" + d1 = util_spec "d", "1", { "c" => "< 2" }, "lib/d.rb" + d2 = util_spec "d", "2", { "c" => "< 2" }, "lib/d.rb" # this - install_specs c1, c2, b1, b2, d1, d2, a1 + install_specs c1, c2, b1, b2, d1, d2, a1 - a1.activate + a1.activate - assert_equal %w[a-1], loaded_spec_names - assert_equal ["b (> 0)", "d (> 0)"], unresolved_names + assert_equal %w[a-1], loaded_spec_names + assert_equal ["b (> 0)", "d (> 0)"], unresolved_names - Gem.finish_resolve + Gem.finish_resolve - assert_equal %w[a-1 b-1 c-1 d-2], loaded_spec_names - assert_equal [], unresolved_names - end + assert_equal %w[a-1 b-1 c-1 d-2], loaded_spec_names + assert_equal [], unresolved_names end def test_self_finish_resolve_respects_loaded_specs - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 1" - b2 = util_spec "b", "2", "c" => ">= 2" - c1 = util_spec "c", "1" - c2 = util_spec "c", "2" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 1" + b2 = util_spec "b", "2", "c" => ">= 2" + c1 = util_spec "c", "1" + c2 = util_spec "c", "2" - install_specs c1, c2, b1, b2, a1 + install_specs c1, c2, b1, b2, a1 - a1.activate - c1.activate + a1.activate + c1.activate - assert_equal %w[a-1 c-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + assert_equal %w[a-1 c-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - Gem.finish_resolve + Gem.finish_resolve - assert_equal %w[a-1 b-1 c-1], loaded_spec_names - assert_equal [], unresolved_names - end + assert_equal %w[a-1 b-1 c-1], loaded_spec_names + assert_equal [], unresolved_names end def test_self_install @@ -106,7 +99,7 @@ class TestGem < Gem::TestCase assert_equal %w[a-1], installed.map {|spec| spec.full_name } - assert_path_exists File.join(gemhome2, 'gems', 'a-1') + assert_path_exist File.join(gemhome2, 'gems', 'a-1') end def test_self_install_in_rescue @@ -211,25 +204,21 @@ class TestGem < Gem::TestCase end def test_require_missing - save_loaded_features do - assert_raises ::LoadError do - require "test_require_missing" - end + assert_raise ::LoadError do + require "test_require_missing" end end def test_require_does_not_glob - save_loaded_features do - a1 = util_spec "a", "1", nil, "lib/a1.rb" - - install_specs a1 + a1 = util_spec "a", "1", nil, "lib/a1.rb" - assert_raises ::LoadError do - require "a*" - end + install_specs a1 - assert_equal [], loaded_spec_names + assert_raise ::LoadError do + require "a*" end + + assert_equal [], loaded_spec_names end def test_self_bin_path_active @@ -261,7 +250,7 @@ class TestGem < Gem::TestCase end def test_self_activate_bin_path_no_exec_name - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do Gem.activate_bin_path 'a' end @@ -342,7 +331,7 @@ class TestGem < Gem::TestCase # c2 is missing, and b2 which has it as a dependency will be activated, so we should get an error about the orphaned dependency - e = assert_raises Gem::UnsatisfiableDependencyError do + e = assert_raise Gem::UnsatisfiableDependencyError do load Gem.activate_bin_path("a", "exec", ">= 0") end @@ -390,7 +379,7 @@ class TestGem < Gem::TestCase File.open("Gemfile", "w") {|f| f.puts('source "https://rubygems.org"') } - e = assert_raises Gem::GemNotFoundException do + e = assert_raise Gem::GemNotFoundException do load Gem.activate_bin_path("bundler", "bundle", ">= 0.a") end @@ -487,7 +476,7 @@ class TestGem < Gem::TestCase File.open("Gemfile", "w") {|f| f.puts('source "https://rubygems.org"') } - e = assert_raises Gem::GemNotFoundException do + e = assert_raise Gem::GemNotFoundException do load Gem.activate_bin_path("bundler", "bundle", "= 2.2.8") end @@ -495,7 +484,7 @@ class TestGem < Gem::TestCase end def test_self_bin_path_no_exec_name - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do Gem.bin_path 'a' end @@ -516,20 +505,20 @@ class TestGem < Gem::TestCase util_spec 'a', '2' do |s| s.executables = ['exec'] end - assert_raises(Gem::GemNotFoundException) do + assert_raise(Gem::GemNotFoundException) do Gem.bin_path('a', 'other', '2') end end def test_self_bin_path_no_bin_file util_spec 'a', '1' - assert_raises(ArgumentError) do + assert_raise(ArgumentError) do Gem.bin_path('a', nil, '1') end end def test_self_bin_path_not_found - assert_raises(Gem::GemNotFoundException) do + assert_raise(Gem::GemNotFoundException) do Gem.bin_path('non-existent', 'blah') end end @@ -540,7 +529,6 @@ class TestGem < Gem::TestCase s.executables = [] end install_specs spec - # Should not find a-10's non-abin (bug) assert_equal @abin_path, Gem.bin_path('a', 'abin') end @@ -596,7 +584,7 @@ class TestGem < Gem::TestCase end def test_self_datadir_nonexistent_package - assert_raises(Gem::MissingSpecError) do + assert_raise(Gem::MissingSpecError) do Gem::Specification.find_by_name("xyzzy").datadir end end @@ -664,22 +652,22 @@ class TestGem < Gem::TestCase end def test_self_use_gemdeps - rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], '-' + with_rubygems_gemdeps('-') do + FileUtils.mkdir_p 'detect/a/b' + FileUtils.mkdir_p 'detect/a/Isolate' - FileUtils.mkdir_p 'detect/a/b' - FileUtils.mkdir_p 'detect/a/Isolate' + FileUtils.touch 'detect/Isolate' - FileUtils.touch 'detect/Isolate' + begin + Dir.chdir 'detect/a/b' - begin - Dir.chdir 'detect/a/b' + Gem.use_gemdeps - assert_equal add_bundler_full_name([]), Gem.use_gemdeps.map(&:full_name) - ensure - Dir.chdir @tempdir + assert_equal add_bundler_full_name([]), loaded_spec_names + ensure + Dir.chdir @tempdir + end end - ensure - ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps end def test_self_dir @@ -692,12 +680,12 @@ class TestGem < Gem::TestCase Gem.ensure_gem_subdirectories @gemhome - assert_path_exists File.join @gemhome, 'build_info' - assert_path_exists File.join @gemhome, 'cache' - assert_path_exists File.join @gemhome, 'doc' - assert_path_exists File.join @gemhome, 'extensions' - assert_path_exists File.join @gemhome, 'gems' - assert_path_exists File.join @gemhome, 'specifications' + assert_path_exist File.join @gemhome, 'build_info' + assert_path_exist File.join @gemhome, 'cache' + assert_path_exist File.join @gemhome, 'doc' + assert_path_exist File.join @gemhome, 'extensions' + assert_path_exist File.join @gemhome, 'gems' + assert_path_exist File.join @gemhome, 'specifications' end def test_self_ensure_gem_directories_permissions @@ -1058,7 +1046,7 @@ class TestGem < Gem::TestCase assert_equal ["\xCF", "\x80"], Gem.read_binary('test').chars.to_a - skip 'chmod not supported' if Gem.win_platform? + pend 'chmod not supported' if Gem.win_platform? begin File.chmod 0444, 'test' @@ -1144,7 +1132,7 @@ class TestGem < Gem::TestCase assert_equal Gem::Requirement.create('>= 1.2.3'), Gem.env_requirement('foo') assert_equal Gem::Requirement.create('1.2.3'), Gem.env_requirement('bAr') - assert_raises(Gem::Requirement::BadRequirementError) { Gem.env_requirement('baz') } + assert_raise(Gem::Requirement::BadRequirementError) { Gem.env_requirement('baz') } assert_equal Gem::Requirement.default, Gem.env_requirement('qux') end @@ -1349,7 +1337,7 @@ class TestGem < Gem::TestCase io.puts '# a_file.rb' end - e = assert_raises Gem::MissingSpecError do + e = assert_raise Gem::MissingSpecError do Gem.try_activate 'a_file' end @@ -1370,7 +1358,7 @@ class TestGem < Gem::TestCase io.puts '# a_file.rb' end - e = assert_raises Gem::MissingSpecError do + e = assert_raise Gem::MissingSpecError do Gem.try_activate 'a_file' end @@ -1389,7 +1377,7 @@ class TestGem < Gem::TestCase io.write spec.to_ruby_for_cache end - _, err = capture_io do + _, err = capture_output do refute Gem.try_activate 'nonexistent' end @@ -1414,7 +1402,7 @@ class TestGem < Gem::TestCase end def test_setting_paths_does_not_warn_about_unknown_keys - stdout, stderr = capture_io do + stdout, stderr = capture_output do Gem.paths = { 'foo' => [], 'bar' => Object.new, 'GEM_HOME' => Gem.paths.home, @@ -1432,7 +1420,7 @@ class TestGem < Gem::TestCase end def test_deprecated_paths= - stdout, stderr = capture_io do + stdout, stderr = capture_output do Gem.paths = { 'GEM_HOME' => Gem.paths.home, 'GEM_PATH' => [Gem.paths.home, 'foo'] } end @@ -1485,24 +1473,22 @@ class TestGem < Gem::TestCase end def test_self_needs_picks_up_unresolved_deps - save_loaded_features do - a = util_spec "a", "1" - b = util_spec "b", "1", "c" => nil - c = util_spec "c", "2" - d = util_spec "d", "1", {'e' => '= 1'}, "lib/d#{$$}.rb" - e = util_spec "e", "1" - - install_specs a, c, b, e, d + a = util_spec "a", "1" + b = util_spec "b", "1", "c" => nil + c = util_spec "c", "2" + d = util_spec "d", "1", {'e' => '= 1'}, "lib/d#{$$}.rb" + e = util_spec "e", "1" - Gem.needs do |r| - r.gem "a" - r.gem "b", "= 1" + install_specs a, c, b, e, d - require "d#{$$}" - end + Gem.needs do |r| + r.gem "a" + r.gem "b", "= 1" - assert_equal %w[a-1 b-1 c-2 d-1 e-1], loaded_spec_names + require "d#{$$}" end + + assert_equal %w[a-1 b-1 c-2 d-1 e-1], loaded_spec_names end def test_self_gunzip @@ -1580,6 +1566,31 @@ class TestGem < Gem::TestCase assert_equal %w[plugin], PLUGINS_LOADED end + def test_load_user_installed_plugins + plugin_path = File.join "lib", "rubygems_plugin.rb" + + Dir.chdir @tempdir do + FileUtils.mkdir_p 'lib' + File.open plugin_path, "w" do |fp| + fp.puts "class TestGem; PLUGINS_LOADED << 'plugin'; end" + end + + foo = util_spec 'foo', '1' do |s| + s.files << plugin_path + end + + install_gem_user foo + end + + Gem.paths = { "GEM_PATH" => [Gem.dir, Gem.user_dir].join(File::PATH_SEPARATOR) } + + gem 'foo' + + Gem.load_plugins + + assert_equal %w[plugin], PLUGINS_LOADED + end + def test_load_env_plugins with_plugin('load') { Gem.load_env_plugins } assert_equal :loaded, TEST_PLUGIN_LOAD rescue nil @@ -1689,11 +1700,11 @@ class TestGem < Gem::TestCase f.puts "gem 'c'" end - ENV['RUBYGEMS_GEMDEPS'] = path - - Gem.use_gemdeps + with_rubygems_gemdeps(path) do + Gem.use_gemdeps - assert_equal add_bundler_full_name(%W[a-1 b-1 c-1]), loaded_spec_names + assert_equal add_bundler_full_name(%W[a-1 b-1 c-1]), loaded_spec_names + end end def test_auto_activation_of_used_gemdeps_file @@ -1711,10 +1722,13 @@ class TestGem < Gem::TestCase f.puts "gem 'c'" end - ENV['RUBYGEMS_GEMDEPS'] = "-" + with_rubygems_gemdeps("-") do + expected_specs = [a, b, util_spec("bundler", Bundler::VERSION), c].compact.map(&:full_name) - expected_specs = [a, b, util_spec("bundler", Bundler::VERSION), c].compact - assert_equal expected_specs, Gem.use_gemdeps.sort_by {|s| s.name } + Gem.use_gemdeps + + assert_equal expected_specs, loaded_spec_names + end end BUNDLER_LIB_PATH = File.expand_path $LOAD_PATH.find {|lp| File.file?(File.join(lp, "bundler.rb")) } @@ -1726,10 +1740,18 @@ class TestGem < Gem::TestCase names end - def test_looks_for_gemdeps_files_automatically_on_start - skip "Requiring bundler messes things up" if Gem.java_platform? + def test_looks_for_gemdeps_files_automatically_from_binstubs + pend "Requiring bundler messes things up" if Gem.java_platform? + + a = util_spec "a", "1" do |s| + s.executables = %w[foo] + s.bindir = "exe" + end + + write_file File.join(@tempdir, 'exe', 'foo') do |fp| + fp.puts "puts Gem.loaded_specs.values.map(&:full_name).sort" + end - a = util_spec "a", "1", nil, "lib/a.rb" b = util_spec "b", "1", nil, "lib/b.rb" c = util_spec "c", "1", nil, "lib/c.rb" @@ -1741,31 +1763,44 @@ class TestGem < Gem::TestCase install_gem c, :install_dir => path ENV['GEM_PATH'] = path - ENV['RUBYGEMS_GEMDEPS'] = "-" - path = File.join @tempdir, "gem.deps.rb" - cmd = [*ruby_with_rubygems_in_load_path, - "-I#{BUNDLER_LIB_PATH}"] - cmd << "-eputs Gem.loaded_specs.values.map(&:full_name).sort" + with_rubygems_gemdeps("-") do + new_PATH = [File.join(path, "bin"), ENV["PATH"]].join(File::PATH_SEPARATOR) + new_RUBYOPT = "-I#{rubygems_path} -I#{BUNDLER_LIB_PATH}" - File.open path, "w" do |f| - f.puts "gem 'a'" - end - out0 = IO.popen(cmd, &:read).split(/\n/) + path = File.join @tempdir, "gem.deps.rb" - File.open path, "a" do |f| - f.puts "gem 'b'" - f.puts "gem 'c'" - end - out = IO.popen(cmd, &:read).split(/\n/) + File.open path, "w" do |f| + f.puts "gem 'a'" + end + out0 = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do + IO.popen("foo", &:read).split(/\n/) + end + + File.open path, "a" do |f| + f.puts "gem 'b'" + f.puts "gem 'c'" + end + out = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do + IO.popen("foo", &:read).split(/\n/) + end - assert_equal ["b-1", "c-1"], out - out0 + assert_equal ["b-1", "c-1"], out - out0 + end end - def test_looks_for_gemdeps_files_automatically_on_start_in_parent_dir - skip "Requiring bundler messes things up" if Gem.java_platform? + def test_looks_for_gemdeps_files_automatically_from_binstubs_in_parent_dir + pend "Requiring bundler messes things up" if Gem.java_platform? + + a = util_spec "a", "1" do |s| + s.executables = %w[foo] + s.bindir = "exe" + end + + write_file File.join(@tempdir, 'exe', 'foo') do |fp| + fp.puts "puts Gem.loaded_specs.values.map(&:full_name).sort" + end - a = util_spec "a", "1", nil, "lib/a.rb" b = util_spec "b", "1", nil, "lib/b.rb" c = util_spec "c", "1", nil, "lib/c.rb" @@ -1777,29 +1812,34 @@ class TestGem < Gem::TestCase install_gem c, :install_dir => path ENV['GEM_PATH'] = path - ENV['RUBYGEMS_GEMDEPS'] = "-" - Dir.mkdir "sub1" + with_rubygems_gemdeps("-") do + Dir.mkdir "sub1" - path = File.join @tempdir, "gem.deps.rb" - cmd = [*ruby_with_rubygems_in_load_path, "-Csub1", - "-I#{BUNDLER_LIB_PATH}"] - cmd << "-eputs Gem.loaded_specs.values.map(&:full_name).sort" + new_PATH = [File.join(path, "bin"), ENV["PATH"]].join(File::PATH_SEPARATOR) + new_RUBYOPT = "-I#{rubygems_path} -I#{BUNDLER_LIB_PATH}" - File.open path, "w" do |f| - f.puts "gem 'a'" - end - out0 = IO.popen(cmd, &:read).split(/\n/) + path = File.join @tempdir, "gem.deps.rb" - File.open path, "a" do |f| - f.puts "gem 'b'" - f.puts "gem 'c'" - end - out = IO.popen(cmd, &:read).split(/\n/) + File.open path, "w" do |f| + f.puts "gem 'a'" + end + out0 = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do + IO.popen("foo", :chdir => "sub1", &:read).split(/\n/) + end - Dir.rmdir "sub1" + File.open path, "a" do |f| + f.puts "gem 'b'" + f.puts "gem 'c'" + end + out = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do + IO.popen("foo", :chdir => "sub1", &:read).split(/\n/) + end + + Dir.rmdir "sub1" - assert_equal ["b-1", "c-1"], out - out0 + assert_equal ["b-1", "c-1"], out - out0 + end end def test_register_default_spec @@ -1863,25 +1903,23 @@ class TestGem < Gem::TestCase end def test_use_gemdeps_ENV - rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], nil - - spec = util_spec 'a', 1 + with_rubygems_gemdeps(nil) do + spec = util_spec 'a', 1 - refute spec.activated? + refute spec.activated? - File.open 'gem.deps.rb', 'w' do |io| - io.write 'gem "a"' - end + File.open 'gem.deps.rb', 'w' do |io| + io.write 'gem "a"' + end - Gem.use_gemdeps + Gem.use_gemdeps - refute spec.activated? - ensure - ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps + refute spec.activated? + end end def test_use_gemdeps_argument_missing - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do Gem.use_gemdeps 'gem.deps.rb' end @@ -1890,114 +1928,96 @@ class TestGem < Gem::TestCase end def test_use_gemdeps_argument_missing_match_ENV - rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = - ENV['RUBYGEMS_GEMDEPS'], 'gem.deps.rb' + with_rubygems_gemdeps('gem.deps.rb') do + e = assert_raise ArgumentError do + Gem.use_gemdeps 'gem.deps.rb' + end - e = assert_raises ArgumentError do - Gem.use_gemdeps 'gem.deps.rb' + assert_equal 'Unable to find gem dependencies file at gem.deps.rb', + e.message end - - assert_equal 'Unable to find gem dependencies file at gem.deps.rb', - e.message - ensure - ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps end def test_use_gemdeps_automatic - rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], '-' + with_rubygems_gemdeps('-') do + spec = util_spec 'a', 1 + install_specs spec + spec = Gem::Specification.find {|s| s == spec } - spec = util_spec 'a', 1 - install_specs spec - spec = Gem::Specification.find {|s| s == spec } - - refute spec.activated? + refute spec.activated? - File.open 'Gemfile', 'w' do |io| - io.write 'gem "a"' - end + File.open 'Gemfile', 'w' do |io| + io.write 'gem "a"' + end - Gem.use_gemdeps + Gem.use_gemdeps - assert_equal add_bundler_full_name(%W[a-1]), loaded_spec_names - ensure - ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps + assert_equal add_bundler_full_name(%W[a-1]), loaded_spec_names + end end def test_use_gemdeps_automatic_missing - rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], '-' + with_rubygems_gemdeps('-') do + Gem.use_gemdeps - Gem.use_gemdeps - - assert true # count - ensure - ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps + assert true # count + end end def test_use_gemdeps_disabled - rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], '' - - spec = util_spec 'a', 1 + with_rubygems_gemdeps('') do + spec = util_spec 'a', 1 - refute spec.activated? + refute spec.activated? - File.open 'gem.deps.rb', 'w' do |io| - io.write 'gem "a"' - end + File.open 'gem.deps.rb', 'w' do |io| + io.write 'gem "a"' + end - Gem.use_gemdeps + Gem.use_gemdeps - refute spec.activated? - ensure - ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps + refute spec.activated? + end end def test_use_gemdeps_missing_gem - rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], 'x' - - File.open 'x', 'w' do |io| - io.write 'gem "a"' - end - - platform = Bundler::GemHelpers.generic_local_platform - if platform == Gem::Platform::RUBY - platform = '' - else - platform = " #{platform}" - end + with_rubygems_gemdeps('x') do + File.open 'x', 'w' do |io| + io.write 'gem "a"' + end - expected = <<-EXPECTED -Could not find gem 'a#{platform}' in any of the gem sources listed in your Gemfile. -You may need to `gem install -g` to install missing gems + expected = <<-EXPECTED +Could not find gem 'a' in locally installed gems. +You may need to `bundle install` to install missing gems - EXPECTED + EXPECTED - Gem::Deprecate.skip_during do - assert_output nil, expected do - Gem.use_gemdeps + Gem::Deprecate.skip_during do + actual_stdout, actual_stderr = capture_output do + Gem.use_gemdeps + end + assert_empty actual_stdout + assert_equal(expected, actual_stderr) end end - ensure - ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps end def test_use_gemdeps_specific - rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], 'x' + with_rubygems_gemdeps('x') do + spec = util_spec 'a', 1 + install_specs spec - spec = util_spec 'a', 1 - install_specs spec + spec = Gem::Specification.find {|s| s == spec } + refute spec.activated? - spec = Gem::Specification.find {|s| s == spec } - refute spec.activated? - - File.open 'x', 'w' do |io| - io.write 'gem "a"' - end + File.open 'x', 'w' do |io| + io.write 'gem "a"' + end - Gem.use_gemdeps + Gem.use_gemdeps - assert_equal add_bundler_full_name(%W[a-1]), loaded_spec_names - ensure - ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps + assert_equal add_bundler_full_name(%W[a-1]), loaded_spec_names + end end def test_operating_system_defaults @@ -2079,7 +2099,7 @@ You may need to `gem install -g` to install missing gems refute_includes $LOAD_PATH, test_plugin_path $LOAD_PATH.unshift test_plugin_path - capture_io do + capture_output do yield end ensure @@ -2115,4 +2135,22 @@ You may need to `gem install -g` to install missing gems def util_cache_dir File.join Gem.dir, "cache" end + + def with_path_and_rubyopt(path_value, rubyopt_value) + path, ENV['PATH'] = ENV['PATH'], path_value + rubyopt, ENV['RUBYOPT'] = ENV['RUBYOPT'], rubyopt_value + + yield + ensure + ENV['PATH'] = path + ENV['RUBYOPT'] = rubyopt + end + + def with_rubygems_gemdeps(value) + rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], value + + yield + ensure + ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps + end end diff --git a/test/rubygems/test_gem_available_set.rb b/test/rubygems/test_gem_available_set.rb index dd2816acc6..c4164d77f2 100644 --- a/test/rubygems/test_gem_available_set.rb +++ b/test/rubygems/test_gem_available_set.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/available_set' require 'rubygems/security' diff --git a/test/rubygems/test_gem_bundler_version_finder.rb b/test/rubygems/test_gem_bundler_version_finder.rb index 4372356db8..e971e6ae29 100644 --- a/test/rubygems/test_gem_bundler_version_finder.rb +++ b/test/rubygems/test_gem_bundler_version_finder.rb @@ -1,19 +1,17 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemBundlerVersionFinder < Gem::TestCase def setup super @argv = ARGV.dup - @env = ENV.to_hash.clone - ENV.delete("BUNDLER_VERSION") @dollar_0 = $0 + without_any_upwards_gemfiles end def teardown ARGV.replace @argv - ENV.replace @env $0 = @dollar_0 super @@ -78,8 +76,8 @@ class TestGemBundlerVersionFinder < Gem::TestCase end def test_deleted_directory - skip "Cannot perform this test on windows" if win_platform? - skip "Cannot perform this test on Solaris" if /solaris/ =~ RUBY_PLATFORM + pend "Cannot perform this test on windows" if win_platform? + pend "Cannot perform this test on Solaris" if /solaris/ =~ RUBY_PLATFORM require "tmpdir" orig_dir = Dir.pwd diff --git a/test/rubygems/test_gem_command.rb b/test/rubygems/test_gem_command.rb index 2f87d9cc8d..65b9b040b7 100644 --- a/test/rubygems/test_gem_command.rb +++ b/test/rubygems/test_gem_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/command' class Gem::Command @@ -118,7 +118,7 @@ class TestGemCommand < Gem::TestCase use_ui @ui do @cmd.when_invoked { true } - ex = assert_raises OptionParser::InvalidOption do + ex = assert_raise Gem::OptionParser::InvalidOption do @cmd.invoke('-zzz') end @@ -189,6 +189,18 @@ class TestGemCommand < Gem::TestCase assert_match %r{Usage: gem doit}, @ui.output end + def test_add_option + assert_nothing_raised RuntimeError do + @cmd.add_option('--force', 'skip validation of the spec') {|v,o| } + end + end + + def test_add_option_with_empty + assert_raise RuntimeError, "Do not pass an empty string in opts" do + @cmd.add_option('', 'skip validation of the spec') {|v,o| } + end + end + def test_option_recognition @cmd.add_option('-h', '--help [COMMAND]', 'Get help on COMMAND') do |value, options| options[:help] = true diff --git a/test/rubygems/test_gem_command_manager.rb b/test/rubygems/test_gem_command_manager.rb index d4471b0b63..ee71fc7c18 100644 --- a/test/rubygems/test_gem_command_manager.rb +++ b/test/rubygems/test_gem_command_manager.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/command_manager' class TestGemCommandManager < Gem::TestCase @@ -22,7 +22,7 @@ class TestGemCommandManager < Gem::TestCase end def test_find_command_ambiguous - e = assert_raises Gem::CommandLineError do + e = assert_raise Gem::CommandLineError do @command_manager.find_command 'u' end @@ -36,6 +36,18 @@ class TestGemCommandManager < Gem::TestCase assert_kind_of Gem::Commands::InstallCommand, command end + def test_find_login_alias_command + command = @command_manager.find_command 'login' + + assert_kind_of Gem::Commands::SigninCommand, command + end + + def test_find_logout_alias_comamnd + command = @command_manager.find_command 'logout' + + assert_kind_of Gem::Commands::SignoutCommand, command + end + def test_find_command_ambiguous_exact ins_command = Class.new Gem::Commands.send :const_set, :InsCommand, ins_command @@ -50,7 +62,7 @@ class TestGemCommandManager < Gem::TestCase end def test_find_command_unknown - e = assert_raises Gem::CommandLineError do + e = assert_raise Gem::CommandLineError do @command_manager.find_command 'xyz' end @@ -65,7 +77,7 @@ class TestGemCommandManager < Gem::TestCase @command_manager.register_command :interrupt use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @command_manager.run %w[interrupt] end assert_equal '', ui.output @@ -82,7 +94,7 @@ class TestGemCommandManager < Gem::TestCase @command_manager.register_command :crash use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @command_manager.run %w[crash] end assert_equal '', ui.output @@ -96,7 +108,7 @@ class TestGemCommandManager < Gem::TestCase def test_process_args_bad_arg use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @command_manager.process_args %w[--bad-arg] end end diff --git a/test/rubygems/test_gem_commands_build_command.rb b/test/rubygems/test_gem_commands_build_command.rb index fe537780be..1304beb580 100644 --- a/test/rubygems/test_gem_commands_build_command.rb +++ b/test/rubygems/test_gem_commands_build_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/build_command' require 'rubygems/package' @@ -126,7 +126,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase use_ui @ui do Dir.chdir @tempdir do - assert_raises Gem::InvalidSpecificationException do + assert_raise Gem::InvalidSpecificationException do @cmd.execute end end @@ -180,7 +180,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase use_ui @ui do Dir.chdir @tempdir do - assert_raises Gem::InvalidSpecificationException do + assert_raise Gem::InvalidSpecificationException do @cmd.execute end end @@ -208,8 +208,8 @@ class TestGemCommandsBuildCommand < Gem::TestCase @cmd.options[:args] = [gemspec_file] out, err = use_ui @ui do - capture_io do - assert_raises Gem::MockGemUi::TermError do + capture_output do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -225,7 +225,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase def test_execute_missing_file @cmd.options[:args] = %w[some_gem] use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -329,7 +329,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase @cmd.options[:args] = ["*.gemspec"] use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -527,7 +527,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase use_ui @ui do Dir.chdir(gemspec_dir) do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -582,7 +582,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase end def test_build_signed_gem - skip 'openssl is missing' unless Gem::HAVE_OPENSSL && !java_platform? + pend 'openssl is missing' unless Gem::HAVE_OPENSSL && !java_platform? trust_dir = Gem::Security.trust_dir @@ -609,7 +609,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase end def test_build_signed_gem_with_cert_expiration_length_days - skip 'openssl is missing' unless Gem::HAVE_OPENSSL && !java_platform? + pend 'openssl is missing' unless Gem::HAVE_OPENSSL && !java_platform? gem_path = File.join Gem.user_home, ".gem" Dir.mkdir gem_path @@ -653,7 +653,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase end def test_build_auto_resign_cert - skip 'openssl is missing' unless Gem::HAVE_OPENSSL && !java_platform? + pend 'openssl is missing' unless Gem::HAVE_OPENSSL && !java_platform? gem_path = File.join Gem.user_home, ".gem" Dir.mkdir gem_path diff --git a/test/rubygems/test_gem_commands_cert_command.rb b/test/rubygems/test_gem_commands_cert_command.rb index 19867bf37a..901bf5aed6 100644 --- a/test/rubygems/test_gem_commands_cert_command.rb +++ b/test/rubygems/test_gem_commands_cert_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/cert_command' unless Gem::HAVE_OPENSSL @@ -14,9 +14,10 @@ class TestGemCommandsCertCommand < Gem::TestCase ALTERNATE_CERT = load_cert 'alternate' EXPIRED_PUBLIC_CERT = load_cert 'expired' - ALTERNATE_KEY_FILE = key_path 'alternate' - PRIVATE_KEY_FILE = key_path 'private' - PUBLIC_KEY_FILE = key_path 'public' + ALTERNATE_KEY_FILE = key_path 'alternate' + PRIVATE_KEY_FILE = key_path 'private' + PRIVATE_EC_KEY_FILE = key_path 'private_ec' + PUBLIC_KEY_FILE = key_path 'public' ALTERNATE_CERT_FILE = cert_path 'alternate' CHILD_CERT_FILE = cert_path 'child' @@ -54,7 +55,7 @@ class TestGemCommandsCertCommand < Gem::TestCase assert_equal PUBLIC_CERT.to_pem, match.first.to_pem assert_equal @trust_dir.cert_path(PUBLIC_CERT), match.last - assert_raises StopIteration do + assert_raise StopIteration do matches.next end end @@ -69,7 +70,7 @@ class TestGemCommandsCertCommand < Gem::TestCase assert_equal ALTERNATE_CERT.to_pem, match.first.to_pem assert_equal @trust_dir.cert_path(ALTERNATE_CERT), match.last - assert_raises StopIteration do + assert_raise StopIteration do matches.next end end @@ -83,7 +84,7 @@ class TestGemCommandsCertCommand < Gem::TestCase cert_path = @trust_dir.cert_path PUBLIC_CERT - assert_path_exists cert_path + assert_path_exist cert_path assert_equal "Added '/CN=nobody/DC=example'\n", @ui.output assert_empty @ui.error @@ -138,8 +139,44 @@ Added '/CN=alternate/DC=example' assert_empty output assert_empty @build_ui.error - assert_path_exists File.join(@tempdir, 'gem-private_key.pem') - assert_path_exists File.join(@tempdir, 'gem-public_cert.pem') + assert_path_exist File.join(@tempdir, 'gem-private_key.pem') + assert_path_exist File.join(@tempdir, 'gem-public_cert.pem') + end + + def test_execute_build_key_algorithm_ec_key + passphrase = 'Foo bar' + + @cmd.handle_options %W[--build nobody@example.com --key-algorithm ec] + + @build_ui = Gem::MockGemUi.new "#{passphrase}\n#{passphrase}" + + use_ui @build_ui do + @cmd.execute + end + + output = @build_ui.output.squeeze("\n").split "\n" + + assert_equal "Passphrase for your Private Key: ", + output.shift + assert_equal "Please repeat the passphrase for your Private Key: ", + output.shift + assert_equal "Certificate: #{File.join @tempdir, 'gem-public_cert.pem'}", + output.shift + assert_equal "Private Key: #{File.join @tempdir, 'gem-private_key.pem'}", + output.shift + + assert_equal "Don't forget to move the key file to somewhere private!", + output.shift + + assert_empty output + assert_empty @build_ui.error + + assert_path_exist File.join(@tempdir, 'gem-private_key.pem') + + cert_path = File.join(@tempdir, 'gem-public_cert.pem') + assert_path_exist cert_path + cert = OpenSSL::X509::Certificate.new(File.read(cert_path)) + assert cert.public_key.is_a? OpenSSL::PKey::EC end def test_execute_build_bad_email_address @@ -152,15 +189,15 @@ Added '/CN=alternate/DC=example' use_ui @build_ui do - e = assert_raises Gem::CommandLineError do + e = assert_raise Gem::CommandLineError do @cmd.execute end assert_equal "Invalid email address #{email}", e.message - refute_path_exists File.join(@tempdir, 'gem-private_key.pem') - refute_path_exists File.join(@tempdir, 'gem-public_cert.pem') + assert_path_not_exist File.join(@tempdir, 'gem-private_key.pem') + assert_path_not_exist File.join(@tempdir, 'gem-public_cert.pem') end end @@ -195,8 +232,8 @@ Added '/CN=alternate/DC=example' assert_empty output assert_empty @build_ui.error - assert_path_exists File.join(@tempdir, 'gem-private_key.pem') - assert_path_exists File.join(@tempdir, 'gem-public_cert.pem') + assert_path_exist File.join(@tempdir, 'gem-private_key.pem') + assert_path_exist File.join(@tempdir, 'gem-public_cert.pem') pem = File.read("#{@tempdir}/gem-public_cert.pem") cert = OpenSSL::X509::Certificate.new(pem) @@ -214,7 +251,7 @@ Added '/CN=alternate/DC=example' @build_ui = Gem::MockGemUi.new "#{passphrase}\n#{passphrase_confirmation}" use_ui @build_ui do - e = assert_raises Gem::CommandLineError do + e = assert_raise Gem::CommandLineError do @cmd.execute end @@ -232,8 +269,8 @@ Added '/CN=alternate/DC=example' end - refute_path_exists File.join(@tempdir, 'gem-private_key.pem') - refute_path_exists File.join(@tempdir, 'gem-public_cert.pem') + assert_path_not_exist File.join(@tempdir, 'gem-private_key.pem') + assert_path_not_exist File.join(@tempdir, 'gem-public_cert.pem') end def test_execute_build_key @@ -254,8 +291,8 @@ Added '/CN=alternate/DC=example' assert_empty output assert_empty @ui.error - assert_path_exists File.join(@tempdir, 'gem-public_cert.pem') - refute_path_exists File.join(@tempdir, 'gem-private_key.pem') + assert_path_exist File.join(@tempdir, 'gem-public_cert.pem') + assert_path_not_exist File.join(@tempdir, 'gem-private_key.pem') end def test_execute_build_encrypted_key @@ -276,7 +313,29 @@ Added '/CN=alternate/DC=example' assert_empty output assert_empty @ui.error - assert_path_exists File.join(@tempdir, 'gem-public_cert.pem') + assert_path_exist File.join(@tempdir, 'gem-public_cert.pem') + end + + def test_execute_build_ec_key + @cmd.handle_options %W[ + --build nobody@example.com + --private-key #{PRIVATE_EC_KEY_FILE} + ] + + use_ui @ui do + @cmd.execute + end + + output = @ui.output.split "\n" + + assert_equal "Certificate: #{File.join @tempdir, 'gem-public_cert.pem'}", + output.shift + + assert_empty output + assert_empty @ui.error + + assert_path_exist File.join(@tempdir, 'gem-public_cert.pem') + assert_path_not_exist File.join(@tempdir, 'gem-private_key.pem') end def test_execute_certificate @@ -346,7 +405,7 @@ Added '/CN=alternate/DC=example' cert_path = @trust_dir.cert_path PUBLIC_CERT - assert_path_exists cert_path + assert_path_exist cert_path @cmd.handle_options %W[--remove nobody] @@ -357,7 +416,7 @@ Added '/CN=alternate/DC=example' assert_equal "Removed '/CN=nobody/DC=example'\n", @ui.output assert_equal '', @ui.error - refute_path_exists cert_path + assert_path_not_exist cert_path end def test_execute_remove_multiple @@ -367,8 +426,8 @@ Added '/CN=alternate/DC=example' public_path = @trust_dir.cert_path PUBLIC_CERT alternate_path = @trust_dir.cert_path ALTERNATE_CERT - assert_path_exists public_path - assert_path_exists alternate_path + assert_path_exist public_path + assert_path_exist alternate_path @cmd.handle_options %W[--remove example] @@ -384,8 +443,8 @@ Removed '/CN=nobody/DC=example' assert_equal expected, @ui.output assert_equal '', @ui.error - refute_path_exists public_path - refute_path_exists alternate_path + assert_path_not_exist public_path + assert_path_not_exist alternate_path end def test_execute_remove_twice @@ -395,8 +454,8 @@ Removed '/CN=nobody/DC=example' public_path = @trust_dir.cert_path PUBLIC_CERT alternate_path = @trust_dir.cert_path ALTERNATE_CERT - assert_path_exists public_path - assert_path_exists alternate_path + assert_path_exist public_path + assert_path_exist alternate_path @cmd.handle_options %W[--remove nobody --remove alternate] @@ -412,8 +471,8 @@ Removed '/CN=alternate/DC=example' assert_equal expected, @ui.output assert_equal '', @ui.error - refute_path_exists public_path - refute_path_exists alternate_path + assert_path_not_exist public_path + assert_path_not_exist alternate_path end def test_execute_sign @@ -552,7 +611,7 @@ Removed '/CN=alternate/DC=example' @cmd.handle_options %W[--sign #{path}] use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -580,7 +639,7 @@ ERROR: --certificate not specified and ~/.gem/gem-public_cert.pem does not exis @cmd.handle_options %W[--sign #{path}] use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -686,7 +745,7 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis def test_handle_options_add_bad nonexistent = File.join @tempdir, 'nonexistent' - e = assert_raises OptionParser::InvalidArgument do + e = assert_raise Gem::OptionParser::InvalidArgument do @cmd.handle_options %W[--add #{nonexistent}] end @@ -696,7 +755,7 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis bad = File.join @tempdir, 'bad' FileUtils.touch bad - e = assert_raises OptionParser::InvalidArgument do + e = assert_raise Gem::OptionParser::InvalidArgument do @cmd.handle_options %W[--add #{bad}] end @@ -706,7 +765,7 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis def test_handle_options_certificate nonexistent = File.join @tempdir, 'nonexistent' - e = assert_raises OptionParser::InvalidArgument do + e = assert_raise Gem::OptionParser::InvalidArgument do @cmd.handle_options %W[--certificate #{nonexistent}] end @@ -716,7 +775,7 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis bad = File.join @tempdir, 'bad' FileUtils.touch bad - e = assert_raises OptionParser::InvalidArgument do + e = assert_raise Gem::OptionParser::InvalidArgument do @cmd.handle_options %W[--certificate #{bad}] end @@ -727,7 +786,7 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis def test_handle_options_key_bad nonexistent = File.join @tempdir, 'nonexistent' - e = assert_raises OptionParser::InvalidArgument do + e = assert_raise Gem::OptionParser::InvalidArgument do @cmd.handle_options %W[--private-key #{nonexistent}] end @@ -738,14 +797,14 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis bad = File.join @tempdir, 'bad' FileUtils.touch bad - e = assert_raises OptionParser::InvalidArgument do + e = assert_raise Gem::OptionParser::InvalidArgument do @cmd.handle_options %W[--private-key #{bad}] end - assert_equal "invalid argument: --private-key #{bad}: invalid RSA key", + assert_equal "invalid argument: --private-key #{bad}: invalid RSA, DSA, or EC key", e.message - e = assert_raises OptionParser::InvalidArgument do + e = assert_raise Gem::OptionParser::InvalidArgument do @cmd.handle_options %W[--private-key #{PUBLIC_KEY_FILE}] end @@ -792,7 +851,7 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis def test_handle_options_sign_nonexistent nonexistent = File.join @tempdir, 'nonexistent' - e = assert_raises OptionParser::InvalidArgument do + e = assert_raise Gem::OptionParser::InvalidArgument do @cmd.handle_options %W[ --private-key #{ALTERNATE_KEY_FILE} diff --git a/test/rubygems/test_gem_commands_check_command.rb b/test/rubygems/test_gem_commands_check_command.rb index c922e40eab..f280b060c4 100644 --- a/test/rubygems/test_gem_commands_check_command.rb +++ b/test/rubygems/test_gem_commands_check_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/check_command' class TestGemCommandsCheckCommand < Gem::TestCase @@ -50,18 +50,18 @@ class TestGemCommandsCheckCommand < Gem::TestCase FileUtils.rm b.spec_file - assert_path_exists b.gem_dir - refute_path_exists b.spec_file + assert_path_exist b.gem_dir + assert_path_not_exist b.spec_file Gem.use_paths @gemhome - capture_io do + capture_output do use_ui @ui do @cmd.doctor end end - refute_path_exists b.gem_dir - refute_path_exists b.spec_file + assert_path_not_exist b.gem_dir + assert_path_not_exist b.spec_file end end diff --git a/test/rubygems/test_gem_commands_cleanup_command.rb b/test/rubygems/test_gem_commands_cleanup_command.rb index d937a5e549..fc98998012 100644 --- a/test/rubygems/test_gem_commands_cleanup_command.rb +++ b/test/rubygems/test_gem_commands_cleanup_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/cleanup_command' require 'rubygems/installer' @@ -62,7 +62,7 @@ class TestGemCommandsCleanupCommand < Gem::TestCase @cmd.execute - refute_path_exists @a_1.gem_dir + assert_path_not_exist @a_1.gem_dir end def test_execute_all_dependencies @@ -81,8 +81,8 @@ class TestGemCommandsCleanupCommand < Gem::TestCase @cmd.execute - refute_path_exists @a_1.gem_dir - refute_path_exists @b_1.gem_dir + assert_path_not_exist @a_1.gem_dir + assert_path_not_exist @b_1.gem_dir end def test_execute_dev_dependencies @@ -101,7 +101,7 @@ class TestGemCommandsCleanupCommand < Gem::TestCase @cmd.execute - assert_path_exists @a_1.gem_dir + assert_path_exist @a_1.gem_dir end def test_execute_without_dev_dependencies @@ -120,7 +120,7 @@ class TestGemCommandsCleanupCommand < Gem::TestCase @cmd.execute - refute_path_exists @a_1.gem_dir + assert_path_not_exist @a_1.gem_dir end def test_execute_all @@ -143,8 +143,8 @@ class TestGemCommandsCleanupCommand < Gem::TestCase assert_equal @gemhome, Gem.dir, 'GEM_HOME' assert_equal [@gemhome, gemhome2], Gem.path.sort, 'GEM_PATH' - refute_path_exists @a_1.gem_dir - refute_path_exists @b_1.gem_dir + assert_path_not_exist @a_1.gem_dir + assert_path_not_exist @b_1.gem_dir end def test_execute_all_user @@ -153,15 +153,15 @@ class TestGemCommandsCleanupCommand < Gem::TestCase Gem::Specification.dirs = [Gem.dir, Gem.user_dir] - assert_path_exists @a_1.gem_dir - assert_path_exists @a_1_1.gem_dir + assert_path_exist @a_1.gem_dir + assert_path_exist @a_1_1.gem_dir @cmd.options[:args] = %w[a] @cmd.execute - refute_path_exists @a_1.gem_dir - refute_path_exists @a_1_1.gem_dir + assert_path_not_exist @a_1.gem_dir + assert_path_not_exist @a_1_1.gem_dir end def test_execute_all_user_no_sudo @@ -172,15 +172,15 @@ class TestGemCommandsCleanupCommand < Gem::TestCase Gem::Specification.dirs = [Gem.dir, Gem.user_dir] - assert_path_exists @a_1.gem_dir - assert_path_exists @a_1_1.gem_dir + assert_path_exist @a_1.gem_dir + assert_path_exist @a_1_1.gem_dir @cmd.options[:args] = %w[a] @cmd.execute - assert_path_exists @a_1.gem_dir - assert_path_exists @a_1_1.gem_dir + assert_path_exist @a_1.gem_dir + assert_path_exist @a_1_1.gem_dir ensure FileUtils.chmod 0755, @gemhome end unless win_platform? || Process.uid.zero? @@ -191,7 +191,7 @@ class TestGemCommandsCleanupCommand < Gem::TestCase @cmd.execute - assert_path_exists @a_1.gem_dir + assert_path_exist @a_1.gem_dir end def test_execute_keeps_older_versions_with_deps @@ -210,7 +210,7 @@ class TestGemCommandsCleanupCommand < Gem::TestCase @cmd.execute - assert_path_exists @b_1.gem_dir + assert_path_exist @b_1.gem_dir end def test_execute_ignore_default_gem_verbose @@ -257,9 +257,9 @@ class TestGemCommandsCleanupCommand < Gem::TestCase @cmd.execute - assert_path_exists c_1.gem_dir - refute_path_exists d_1.gem_dir - refute_path_exists e_1.gem_dir + assert_path_exist c_1.gem_dir + assert_path_not_exist d_1.gem_dir + assert_path_not_exist e_1.gem_dir end def test_execute_user_install @@ -282,10 +282,10 @@ class TestGemCommandsCleanupCommand < Gem::TestCase @cmd.execute - refute_path_exists c_1.gem_dir - assert_path_exists c_2.gem_dir + assert_path_not_exist c_1.gem_dir + assert_path_exist c_2.gem_dir - assert_path_exists d_1.gem_dir - assert_path_exists d_2.gem_dir + assert_path_exist d_1.gem_dir + assert_path_exist d_2.gem_dir end end diff --git a/test/rubygems/test_gem_commands_contents_command.rb b/test/rubygems/test_gem_commands_contents_command.rb index 7c89c67dd4..d79174717b 100644 --- a/test/rubygems/test_gem_commands_contents_command.rb +++ b/test/rubygems/test_gem_commands_contents_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/contents_command' class TestGemCommandsContentsCommand < Gem::TestCase @@ -50,7 +50,7 @@ class TestGemCommandsContentsCommand < Gem::TestCase def test_execute_bad_gem @cmd.options[:args] = %w[foo] - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @ui do @cmd.execute end @@ -94,7 +94,7 @@ class TestGemCommandsContentsCommand < Gem::TestCase def test_execute_missing_single @cmd.options[:args] = %w[foo] - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @ui do @cmd.execute end @@ -110,7 +110,7 @@ class TestGemCommandsContentsCommand < Gem::TestCase gem 'foo', 1 - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @ui do @cmd.execute end diff --git a/test/rubygems/test_gem_commands_dependency_command.rb b/test/rubygems/test_gem_commands_dependency_command.rb index 11d7f8017a..13c7c065b5 100644 --- a/test/rubygems/test_gem_commands_dependency_command.rb +++ b/test/rubygems/test_gem_commands_dependency_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/dependency_command' class TestGemCommandsDependencyCommand < Gem::TestCase @@ -64,7 +64,7 @@ Gem x-2 def test_execute_no_match @cmd.options[:args] = %w[foo] - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @stub_ui do @cmd.execute end @@ -155,7 +155,7 @@ Gem foo-2 @cmd.options[:reverse_dependencies] = true @cmd.options[:domain] = :remote - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @stub_ui do @cmd.execute end diff --git a/test/rubygems/test_gem_commands_environment_command.rb b/test/rubygems/test_gem_commands_environment_command.rb index a3edeb69bd..2bf80d8d85 100644 --- a/test/rubygems/test_gem_commands_environment_command.rb +++ b/test/rubygems/test_gem_commands_environment_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/environment_command' class TestGemCommandsEnvironmentCommand < Gem::TestCase @@ -109,7 +109,7 @@ class TestGemCommandsEnvironmentCommand < Gem::TestCase def test_execute_unknown @cmd.send :handle_options, %w[unknown] - assert_raises Gem::CommandLineError do + assert_raise Gem::CommandLineError do use_ui @ui do @cmd.execute end diff --git a/test/rubygems/test_gem_commands_fetch_command.rb b/test/rubygems/test_gem_commands_fetch_command.rb index dfe0d91726..c745648d56 100644 --- a/test/rubygems/test_gem_commands_fetch_command.rb +++ b/test/rubygems/test_gem_commands_fetch_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/package' require 'rubygems/security' require 'rubygems/commands/fetch_command' @@ -16,7 +16,7 @@ class TestGemCommandsFetchCommand < Gem::TestCase fetcher.gem 'a', 2 end - refute_path_exists File.join(@tempdir, 'cache'), 'sanity check' + assert_path_not_exist File.join(@tempdir, 'cache'), 'sanity check' @cmd.options[:args] = %w[a] @@ -28,9 +28,9 @@ class TestGemCommandsFetchCommand < Gem::TestCase a2 = specs['a-2'] - assert_path_exists(File.join(@tempdir, a2.file_name), + assert_path_exist(File.join(@tempdir, a2.file_name), "#{a2.full_name} not fetched") - refute_path_exists File.join(@tempdir, 'cache'), + assert_path_not_exist File.join(@tempdir, 'cache'), 'gem repository directories must not be created' end @@ -40,7 +40,7 @@ class TestGemCommandsFetchCommand < Gem::TestCase fetcher.gem 'a', 2 end - refute_path_exists File.join(@tempdir, 'cache'), 'sanity check' + assert_path_not_exist File.join(@tempdir, 'cache'), 'sanity check' @cmd.options[:args] = %w[a] @cmd.options[:version] = req('>= 0.1') @@ -52,9 +52,9 @@ class TestGemCommandsFetchCommand < Gem::TestCase end a2 = specs['a-2'] - assert_path_exists(File.join(@tempdir, a2.file_name), + assert_path_exist(File.join(@tempdir, a2.file_name), "#{a2.full_name} not fetched") - refute_path_exists File.join(@tempdir, 'cache'), + assert_path_not_exist File.join(@tempdir, 'cache'), 'gem repository directories must not be created' end @@ -75,10 +75,46 @@ class TestGemCommandsFetchCommand < Gem::TestCase a2 = specs['a-2'] - assert_path_exists(File.join(@tempdir, a2.file_name), + assert_path_exist(File.join(@tempdir, a2.file_name), "#{a2.full_name} not fetched") end + def test_execute_platform + a2_spec, a2 = util_gem("a", "2") + + a2_universal_darwin_spec, a2_universal_darwin = util_gem("a", "2") do |s| + s.platform = 'universal-darwin' + end + + Gem::RemoteFetcher.fetcher = @fetcher = Gem::FakeFetcher.new + + write_marshalled_gemspecs(a2_spec, a2_universal_darwin_spec) + + @cmd.options[:args] = %w[a] + + @fetcher.data["#{@gem_repo}latest_specs.#{Gem.marshal_version}.gz"] = util_gzip(Marshal.dump([ + Gem::NameTuple.new(a2_spec.name, a2_spec.version, a2_spec.platform), + Gem::NameTuple.new(a2_universal_darwin_spec.name, a2_universal_darwin_spec.version, a2_universal_darwin_spec.platform), + ])) + + @fetcher.data["#{@gem_repo}gems/#{a2_spec.file_name}"] = Gem.read_binary(a2) + FileUtils.cp a2, a2_spec.cache_file + + @fetcher.data["#{@gem_repo}gems/#{a2_universal_darwin_spec.file_name}"] = Gem.read_binary(a2_universal_darwin) + FileUtils.cp a2_universal_darwin, a2_universal_darwin_spec.cache_file + + util_set_arch 'arm64-darwin20' do + use_ui @ui do + Dir.chdir @tempdir do + @cmd.execute + end + end + end + + assert_path_exist(File.join(@tempdir, a2_universal_darwin_spec.file_name), + "#{a2_universal_darwin_spec.full_name} not fetched") + end + def test_execute_specific_prerelease specs = spec_fetcher do |fetcher| fetcher.gem 'a', 2 @@ -97,7 +133,7 @@ class TestGemCommandsFetchCommand < Gem::TestCase a2_pre = specs['a-2.a'] - assert_path_exists(File.join(@tempdir, a2_pre.file_name), + assert_path_exist(File.join(@tempdir, a2_pre.file_name), "#{a2_pre.full_name} not fetched") end @@ -118,7 +154,7 @@ class TestGemCommandsFetchCommand < Gem::TestCase a1 = specs['a-1'] - assert_path_exists(File.join(@tempdir, a1.file_name), + assert_path_exist(File.join(@tempdir, a1.file_name), "#{a1.full_name} not fetched") end end diff --git a/test/rubygems/test_gem_commands_generate_index_command.rb b/test/rubygems/test_gem_commands_generate_index_command.rb index fc1317a49d..5b7b37a446 100644 --- a/test/rubygems/test_gem_commands_generate_index_command.rb +++ b/test/rubygems/test_gem_commands_generate_index_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/indexer' require 'rubygems/commands/generate_index_command' diff --git a/test/rubygems/test_gem_commands_help_command.rb b/test/rubygems/test_gem_commands_help_command.rb index 8d20563a60..a70dd770e1 100644 --- a/test/rubygems/test_gem_commands_help_command.rb +++ b/test/rubygems/test_gem_commands_help_command.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true require "rubygems" -require "rubygems/test_case" +require_relative "helper" require "rubygems/commands/help_command" require "rubygems/package" require "rubygems/command_manager" @@ -35,6 +35,13 @@ class TestGemCommandsHelpCommand < Gem::TestCase end end + def test_gem_help_build + util_gem 'build' do |out, err| + assert_match(/-C PATH *Run as if gem build was started in <PATH>/, out) + assert_equal '', err + end + end + def test_gem_help_commands mgr = Gem::CommandManager.new @@ -48,7 +55,7 @@ class TestGemCommandsHelpCommand < Gem::TestCase if Gem::HAVE_OPENSSL assert_empty err - refute_match 'No command found for ', out + refute_match %r{No command found for }, out end end end diff --git a/test/rubygems/test_gem_commands_info_command.rb b/test/rubygems/test_gem_commands_info_command.rb index 6d67b567c7..462075f98c 100644 --- a/test/rubygems/test_gem_commands_info_command.rb +++ b/test/rubygems/test_gem_commands_info_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/info_command' class TestGemCommandsInfoCommand < Gem::TestCase diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index c8015f9985..535180983b 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/install_command' require 'rubygems/request_set' require 'rubygems/rdoc' @@ -37,7 +37,7 @@ class TestGemCommandsInstallCommand < Gem::TestCase @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -59,7 +59,7 @@ class TestGemCommandsInstallCommand < Gem::TestCase assert @cmd.options[:version].satisfied_by?(a2_pre.version) use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -82,7 +82,7 @@ class TestGemCommandsInstallCommand < Gem::TestCase orig_dir = Dir.pwd begin Dir.chdir @tempdir - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end ensure @@ -110,7 +110,7 @@ class TestGemCommandsInstallCommand < Gem::TestCase orig_dir = Dir.pwd begin Dir.chdir @tempdir - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do @cmd.execute end assert_equal 2, e.exit_code @@ -142,7 +142,7 @@ ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in a orig_dir = Dir.pwd begin Dir.chdir orig_dir - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end ensure @@ -173,7 +173,7 @@ ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in a begin Dir.chdir @tempdir FileUtils.rm_r [@gemhome, "gems"] - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end ensure @@ -187,8 +187,8 @@ ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in a end def test_execute_no_user_install - skip 'skipped on MS Windows (chmod has no effect)' if win_platform? - skip 'skipped in root privilege' if Process.uid.zero? + pend 'skipped on MS Windows (chmod has no effect)' if win_platform? + pend 'skipped in root privilege' if Process.uid.zero? specs = spec_fetcher do |fetcher| fetcher.gem 'a', 2 @@ -207,7 +207,7 @@ ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in a FileUtils.chmod 0555, @gemhome Dir.chdir @tempdir - assert_raises Gem::FilePermissionError do + assert_raise Gem::FilePermissionError do @cmd.execute end ensure @@ -225,7 +225,7 @@ ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in a @cmd.options[:args] = %w[no_such_gem] use_ui @ui do - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do @cmd.execute end assert_equal 2, e.exit_code @@ -244,7 +244,7 @@ ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in a @cmd.options[:args] = %w[no_such_gem] use_ui @ui do - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do @cmd.execute end assert_equal 2, e.exit_code @@ -257,7 +257,7 @@ ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in a def test_execute_no_gem @cmd.options[:args] = %w[] - assert_raises Gem::CommandLineError do + assert_raise Gem::CommandLineError do @cmd.execute end end @@ -268,7 +268,7 @@ ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in a @cmd.options[:args] = %w[nonexistent] use_ui @ui do - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do @cmd.execute end assert_equal 2, e.exit_code @@ -285,7 +285,7 @@ ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in a @cmd.options[:args] = ['foo'] use_ui @ui do - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do @cmd.execute end @@ -301,7 +301,7 @@ ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in a def test_execute_http_proxy use_ui @ui do - e = assert_raises ArgumentError, @ui.error do + e = assert_raise ArgumentError, @ui.error do @cmd.handle_options %w[-p=foo.bar.com] end @@ -327,7 +327,7 @@ ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in a @cmd.options[:args] = %w[nonexistent] use_ui @ui do - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do @cmd.execute end assert_equal 2, e.exit_code @@ -351,7 +351,7 @@ ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in a @cmd.options[:suggest_alternate] = false use_ui @ui do - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do @cmd.execute end @@ -376,7 +376,7 @@ ERROR: Could not find a valid gem 'nonexistent_with_hint' (>= 0) in any reposit @cmd.options[:args] = [misspelled] use_ui @ui do - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do @cmd.execute end @@ -401,7 +401,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = [misspelled] use_ui @ui do - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do @cmd.execute end @@ -423,7 +423,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:install_dir] = "whatever" use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -443,7 +443,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -461,7 +461,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -478,7 +478,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a:1] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -496,7 +496,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -526,7 +526,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -547,7 +547,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -565,7 +565,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -585,7 +585,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -605,7 +605,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -625,7 +625,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -657,7 +657,7 @@ ERROR: Possible alternatives: non_existent_with_hint begin Dir.chdir @tempdir - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end ensure @@ -667,8 +667,8 @@ ERROR: Possible alternatives: non_existent_with_hint wait_for_child_process_to_exit - assert_path_exists File.join(a2.doc_dir, 'ri') - assert_path_exists File.join(a2.doc_dir, 'rdoc') + assert_path_exist File.join(a2.doc_dir, 'ri') + assert_path_exist File.join(a2.doc_dir, 'rdoc') end def test_execute_rdoc_with_path @@ -694,7 +694,7 @@ ERROR: Possible alternatives: non_existent_with_hint begin Dir.chdir @tempdir - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end ensure @@ -704,7 +704,7 @@ ERROR: Possible alternatives: non_existent_with_hint wait_for_child_process_to_exit - assert_path_exists 'whatever/doc/a-2', 'documentation not installed' + assert_path_exist 'whatever/doc/a-2', 'documentation not installed' end def test_execute_saves_build_args @@ -730,7 +730,7 @@ ERROR: Possible alternatives: non_existent_with_hint begin Dir.chdir @tempdir - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end ensure @@ -739,7 +739,7 @@ ERROR: Possible alternatives: non_existent_with_hint end path = a2.build_info_file - assert_path_exists path + assert_path_exist path assert_equal args, a2.build_args end @@ -752,7 +752,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -772,7 +772,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -812,7 +812,7 @@ ERROR: Possible alternatives: non_existent_with_hint use_ui @ui do Dir.chdir @tempdir do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -844,7 +844,7 @@ ERROR: Possible alternatives: non_existent_with_hint orig_dir = Dir.pwd begin Dir.chdir @tempdir - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end ensure @@ -862,7 +862,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:version] = Gem::Requirement.new("> 1") use_ui @ui do - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do @cmd.execute end @@ -889,7 +889,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a:1 b:1] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -912,7 +912,7 @@ ERROR: Possible alternatives: non_existent_with_hint orig_dir = Dir.pwd begin Dir.chdir @tempdir - assert_raises Gem::MockGemUi::SystemExitException do + assert_raise Gem::MockGemUi::SystemExitException do @cmd.execute end ensure @@ -1007,7 +1007,7 @@ ERROR: Possible alternatives: non_existent_with_hint orig_dir = Dir.pwd begin Dir.chdir @tempdir - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do @cmd.execute end ensure @@ -1029,7 +1029,7 @@ ERROR: Possible alternatives: non_existent_with_hint orig_dir = Dir.pwd begin Dir.chdir @tempdir - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do @cmd.execute end ensure @@ -1052,7 +1052,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -1067,6 +1067,31 @@ ERROR: Possible alternatives: non_existent_with_hint assert_equal x, e end + def test_redact_credentials_from_uri_on_warning + spec_fetcher do |fetcher| + fetcher.download 'a', 2 + end + + Gem.sources << "http://username:SECURE_TOKEN@nonexistent.example" + + @cmd.options[:args] = %w[a] + + use_ui @ui do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do + @cmd.execute + end + end + + assert_equal %w[a-2], @cmd.installed_specs.map {|spec| spec.full_name } + + assert_match "1 gem installed", @ui.output + + e = @ui.error + + x = "WARNING: Unable to pull data from 'http://username:REDACTED@nonexistent.example': no data for http://username:REDACTED@nonexistent.example/specs.4.8.gz (http://username:REDACTED@nonexistent.example/specs.4.8.gz)\n" + assert_equal x, e + end + def test_execute_uses_from_a_gemdeps spec_fetcher do |fetcher| fetcher.gem 'a', 2 @@ -1079,7 +1104,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:gemdeps] = @gemdeps use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -1103,7 +1128,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:gemdeps] = @gemdeps use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -1128,7 +1153,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:gemdeps] = @gemdeps use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -1150,7 +1175,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:gemdeps] = @gemdeps use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -1173,7 +1198,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:gemdeps] = @gemdeps use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -1200,7 +1225,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:gemdeps] = @gemdeps use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -1227,7 +1252,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:gemdeps] = @gemdeps use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -1259,7 +1284,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:gemdeps] = @gemdeps use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -1294,7 +1319,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:gemdeps] = @gemdeps use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -1379,7 +1404,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -1406,7 +1431,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -1435,7 +1460,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end @@ -1465,7 +1490,7 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end end diff --git a/test/rubygems/test_gem_commands_list_command.rb b/test/rubygems/test_gem_commands_list_command.rb index 87da8dbd48..d8cffce7a3 100644 --- a/test/rubygems/test_gem_commands_list_command.rb +++ b/test/rubygems/test_gem_commands_list_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/list_command' class TestGemCommandsListCommand < Gem::TestCase @@ -20,7 +20,7 @@ class TestGemCommandsListCommand < Gem::TestCase def test_execute_installed @cmd.handle_options %w[c --installed] - assert_raises Gem::MockGemUi::SystemExitException do + assert_raise Gem::MockGemUi::SystemExitException do use_ui @ui do @cmd.execute end diff --git a/test/rubygems/test_gem_commands_lock_command.rb b/test/rubygems/test_gem_commands_lock_command.rb index 3612778293..f8afca1e29 100644 --- a/test/rubygems/test_gem_commands_lock_command.rb +++ b/test/rubygems/test_gem_commands_lock_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/lock_command' class TestGemCommandsLockCommand < Gem::TestCase @@ -55,7 +55,7 @@ gem 'd', '= 1' def test_execute_strict @cmd.handle_options %w[c-1 --strict] - e = assert_raises Gem::Exception do + e = assert_raise Gem::Exception do use_ui @ui do @cmd.execute end diff --git a/test/rubygems/test_gem_commands_mirror.rb b/test/rubygems/test_gem_commands_mirror.rb index 6b2b7d11bb..470f1c30fa 100644 --- a/test/rubygems/test_gem_commands_mirror.rb +++ b/test/rubygems/test_gem_commands_mirror.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/mirror_command' class TestGemCommandsMirrorCommand < Gem::TestCase diff --git a/test/rubygems/test_gem_commands_open_command.rb b/test/rubygems/test_gem_commands_open_command.rb index d3c665217f..8447f7ea35 100644 --- a/test/rubygems/test_gem_commands_open_command.rb +++ b/test/rubygems/test_gem_commands_open_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/open_command' class TestGemCommandsOpenCommand < Gem::TestCase @@ -25,16 +25,15 @@ class TestGemCommandsOpenCommand < Gem::TestCase gem 'foo', '1.0.0' spec = gem 'foo', '1.0.1' - mock = Minitest::Mock.new - mock.expect(:call, true, [spec.full_gem_path]) - Dir.stub(:chdir, mock) do - use_ui @ui do - @cmd.execute + assert_nothing_raised Gem::MockGemUi::TermError do + Dir.stub(:chdir, spec.full_gem_path) do + use_ui @ui do + @cmd.execute + end end end - assert mock.verify assert_equal "", @ui.error end @@ -44,7 +43,7 @@ class TestGemCommandsOpenCommand < Gem::TestCase gem "foo", "5.0" - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @ui do @cmd.execute end @@ -57,7 +56,7 @@ class TestGemCommandsOpenCommand < Gem::TestCase def test_execute_bad_gem @cmd.options[:args] = %w[foo] - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @ui do @cmd.execute end @@ -86,7 +85,7 @@ class TestGemCommandsOpenCommand < Gem::TestCase gem("foo", "1.0") - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @ui do @cmd.execute end diff --git a/test/rubygems/test_gem_commands_outdated_command.rb b/test/rubygems/test_gem_commands_outdated_command.rb index c4af421f5d..dc5c40a782 100644 --- a/test/rubygems/test_gem_commands_outdated_command.rb +++ b/test/rubygems/test_gem_commands_outdated_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/outdated_command' class TestGemCommandsOutdatedCommand < Gem::TestCase diff --git a/test/rubygems/test_gem_commands_owner_command.rb b/test/rubygems/test_gem_commands_owner_command.rb index 4280fedff3..5b06b628c2 100644 --- a/test/rubygems/test_gem_commands_owner_command.rb +++ b/test/rubygems/test_gem_commands_owner_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/owner_command' class TestGemCommandsOwnerCommand < Gem::TestCase @@ -53,7 +53,7 @@ EOF end def test_show_owners_dont_load_objects - skip "testing a psych-only API" unless defined?(::Psych::DisallowedClass) + pend "testing a psych-only API" unless defined?(::Psych::DisallowedClass) response = <<EOF --- @@ -68,7 +68,7 @@ EOF @stub_fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners.yaml"] = [response, 200, 'OK'] - assert_raises Psych::DisallowedClass do + assert_raise Psych::DisallowedClass do use_ui @ui do @cmd.show_owners("freewill") end @@ -109,7 +109,7 @@ EOF response = "You don't have permission to push to this gem" @stub_fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners.yaml"] = [response, 403, 'Forbidden'] - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @stub_ui do @cmd.show_owners("freewill") end diff --git a/test/rubygems/test_gem_commands_pristine_command.rb b/test/rubygems/test_gem_commands_pristine_command.rb index 59f34af249..f4000f4657 100644 --- a/test/rubygems/test_gem_commands_pristine_command.rb +++ b/test/rubygems/test_gem_commands_pristine_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/pristine_command' class TestGemCommandsPristineCommand < Gem::TestCase @@ -155,7 +155,7 @@ class TestGemCommandsPristineCommand < Gem::TestCase @cmd.execute end - assert_path_exists gem_exec + assert_path_exist gem_exec ruby_exec = sprintf Gem.default_exec_format, 'ruby' @@ -356,10 +356,10 @@ class TestGemCommandsPristineCommand < Gem::TestCase assert_equal "Restored #{b.full_name}", out.shift assert_empty out, out.inspect - assert_path_exists File.join(@gemhome, "gems", 'a-2') - refute_path_exists File.join(gemhome2, "gems", 'a-2') - assert_path_exists File.join(gemhome2, "gems", 'b-2') - refute_path_exists File.join(@gemhome, "gems", 'b-2') + assert_path_exist File.join(@gemhome, "gems", 'a-2') + assert_path_not_exist File.join(gemhome2, "gems", 'a-2') + assert_path_exist File.join(gemhome2, "gems", 'b-2') + assert_path_not_exist File.join(@gemhome, "gems", 'b-2') end def test_execute_missing_cache_gem @@ -434,21 +434,21 @@ class TestGemCommandsPristineCommand < Gem::TestCase assert_empty out, out.inspect assert_empty @ui.error - assert_path_exists File.join(@gemhome, "cache", 'a-1.gem') - refute_path_exists File.join(gemhome2, "cache", 'a-2.gem') - assert_path_exists File.join(@gemhome, "gems", 'a-1') - refute_path_exists File.join(gemhome2, "gems", 'a-1') + assert_path_exist File.join(@gemhome, "cache", 'a-1.gem') + assert_path_not_exist File.join(gemhome2, "cache", 'a-2.gem') + assert_path_exist File.join(@gemhome, "gems", 'a-1') + assert_path_not_exist File.join(gemhome2, "gems", 'a-1') - assert_path_exists File.join(gemhome2, "cache", 'b-1.gem') - refute_path_exists File.join(@gemhome, "cache", 'b-2.gem') - assert_path_exists File.join(gemhome2, "gems", 'b-1') - refute_path_exists File.join(@gemhome, "gems", 'b-1') + assert_path_exist File.join(gemhome2, "cache", 'b-1.gem') + assert_path_not_exist File.join(@gemhome, "cache", 'b-2.gem') + assert_path_exist File.join(gemhome2, "gems", 'b-1') + assert_path_not_exist File.join(@gemhome, "gems", 'b-1') end def test_execute_no_gem @cmd.options[:args] = %w[] - e = assert_raises Gem::CommandLineError do + e = assert_raise Gem::CommandLineError do use_ui @ui do @cmd.execute end diff --git a/test/rubygems/test_gem_commands_push_command.rb b/test/rubygems/test_gem_commands_push_command.rb index 55dd51e6ad..fa3968ffce 100644 --- a/test/rubygems/test_gem_commands_push_command.rb +++ b/test/rubygems/test_gem_commands_push_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/push_command' class TestGemCommandsPushCommand < Gem::TestCase @@ -123,7 +123,7 @@ class TestGemCommandsPushCommand < Gem::TestCase Gem.configuration.disable_default_gem_server = true response = "You must specify a gem server" - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @ui do @cmd.send_gem(@path) end @@ -155,7 +155,6 @@ class TestGemCommandsPushCommand < Gem::TestCase @host => @api_key, } - FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path File.open Gem.configuration.credentials_path, 'w' do |f| f.write keys.to_yaml end @@ -190,7 +189,6 @@ class TestGemCommandsPushCommand < Gem::TestCase @host => @api_key, } - FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path File.open Gem.configuration.credentials_path, 'w' do |f| f.write keys.to_yaml end @@ -232,7 +230,6 @@ class TestGemCommandsPushCommand < Gem::TestCase :rubygems_api_key => @api_key, } - FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path File.open Gem.configuration.credentials_path, 'w' do |f| f.write keys.to_yaml end @@ -252,7 +249,7 @@ class TestGemCommandsPushCommand < Gem::TestCase response = %(ERROR: "#{@host}" is not allowed by the gemspec, which only allows "https://privategemserver.example") - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do send_battery end @@ -274,7 +271,6 @@ class TestGemCommandsPushCommand < Gem::TestCase @host => @api_key, } - FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path File.open Gem.configuration.credentials_path, 'w' do |f| f.write keys.to_yaml end @@ -284,7 +280,7 @@ class TestGemCommandsPushCommand < Gem::TestCase response = "ERROR: \"#{@host}\" is not allowed by the gemspec, which only allows \"#{push_host}\"" - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do send_battery end @@ -305,7 +301,6 @@ class TestGemCommandsPushCommand < Gem::TestCase host => api_key, } - FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path File.open Gem.configuration.credentials_path, 'w' do |f| f.write keys.to_yaml end @@ -332,7 +327,7 @@ class TestGemCommandsPushCommand < Gem::TestCase def test_raises_error_with_no_arguments def @cmd.sign_in(*); end - assert_raises Gem::CommandLineError do + assert_raise Gem::CommandLineError do @cmd.execute end end @@ -342,7 +337,7 @@ class TestGemCommandsPushCommand < Gem::TestCase @fetcher.data["#{@host}/api/v1/gems"] = [response, 403, 'Forbidden'] @cmd.instance_variable_set :@host, @host - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @ui do @cmd.send_gem(@path) end @@ -392,7 +387,7 @@ class TestGemCommandsPushCommand < Gem::TestCase @fetcher.data["#{Gem.host}/api/v1/gems"] = [response, 401, 'Unauthorized'] @otp_ui = Gem::MockGemUi.new "111111\n" - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @otp_ui do @cmd.send_gem(@path) end diff --git a/test/rubygems/test_gem_commands_query_command.rb b/test/rubygems/test_gem_commands_query_command.rb index a21bc690fb..0cc88b1685 100644 --- a/test/rubygems/test_gem_commands_query_command.rb +++ b/test/rubygems/test_gem_commands_query_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/query_command' module TestGemCommandsQueryCommandSetup @@ -213,7 +213,7 @@ pl (1) def test_execute_installed @cmd.handle_options %w[-n a --installed] - assert_raises Gem::MockGemUi::SystemExitException do + assert_raise Gem::MockGemUi::SystemExitException do use_ui @stub_ui do @cmd.execute end @@ -226,7 +226,7 @@ pl (1) def test_execute_installed_inverse @cmd.handle_options %w[-n a --no-installed] - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do use_ui @stub_ui do @cmd.execute end @@ -241,7 +241,7 @@ pl (1) def test_execute_installed_inverse_not_installed @cmd.handle_options %w[-n not_installed --no-installed] - assert_raises Gem::MockGemUi::SystemExitException do + assert_raise Gem::MockGemUi::SystemExitException do use_ui @stub_ui do @cmd.execute end @@ -254,7 +254,7 @@ pl (1) def test_execute_installed_no_name @cmd.handle_options %w[--installed] - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do use_ui @stub_ui do @cmd.execute end @@ -269,7 +269,7 @@ pl (1) def test_execute_installed_not_installed @cmd.handle_options %w[-n not_installed --installed] - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do use_ui @stub_ui do @cmd.execute end @@ -284,7 +284,7 @@ pl (1) def test_execute_installed_version @cmd.handle_options %w[-n a --installed --version 2] - assert_raises Gem::MockGemUi::SystemExitException do + assert_raise Gem::MockGemUi::SystemExitException do use_ui @stub_ui do @cmd.execute end @@ -297,7 +297,7 @@ pl (1) def test_execute_installed_version_not_installed @cmd.handle_options %w[-n c --installed --version 2] - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do use_ui @stub_ui do @cmd.execute end diff --git a/test/rubygems/test_gem_commands_search_command.rb b/test/rubygems/test_gem_commands_search_command.rb index 17693e6837..6397dbd4d4 100644 --- a/test/rubygems/test_gem_commands_search_command.rb +++ b/test/rubygems/test_gem_commands_search_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/search_command' class TestGemCommandsSearchCommand < Gem::TestCase diff --git a/test/rubygems/test_gem_commands_server_command.rb b/test/rubygems/test_gem_commands_server_command.rb index 89bdce05cd..d5cd4d13bd 100644 --- a/test/rubygems/test_gem_commands_server_command.rb +++ b/test/rubygems/test_gem_commands_server_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/server_command' class TestGemCommandsServerCommand < Gem::TestCase @@ -40,18 +40,18 @@ class TestGemCommandsServerCommand < Gem::TestCase begin @cmd.send :handle_options, %w[-p discard] assert_equal 9, @cmd.options[:port] - rescue OptionParser::InvalidArgument + rescue Gem::OptionParser::InvalidArgument # for container environment on GitHub Actions end - e = assert_raises OptionParser::InvalidArgument do + e = assert_raise Gem::OptionParser::InvalidArgument do @cmd.send :handle_options, %w[-p nonexistent] end assert_equal 'invalid argument: -p nonexistent: no such named service', e.message - e = assert_raises OptionParser::InvalidArgument do + e = assert_raise Gem::OptionParser::InvalidArgument do @cmd.send :handle_options, %w[-p 65536] end diff --git a/test/rubygems/test_gem_commands_setup_command.rb b/test/rubygems/test_gem_commands_setup_command.rb index 29850c9074..5cf94a1dc9 100644 --- a/test/rubygems/test_gem_commands_setup_command.rb +++ b/test/rubygems/test_gem_commands_setup_command.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/setup_command' class TestGemCommandsSetupCommand < Gem::TestCase @@ -14,14 +14,12 @@ class TestGemCommandsSetupCommand < Gem::TestCase def setup super - @install_dir = File.join @tempdir, 'install' @cmd = Gem::Commands::SetupCommand.new - @cmd.options[:prefix] = @install_dir filelist = %w[ bin/gem lib/rubygems.rb - lib/rubygems/test_case.rb + lib/rubygems/requirement.rb lib/rubygems/ssl_certs/rubygems.org/foo.pem bundler/exe/bundle bundler/exe/bundler @@ -48,7 +46,7 @@ class TestGemCommandsSetupCommand < Gem::TestCase io.puts gemspec.to_ruby end - open(File.join(Gem.default_specifications_dir, "bundler-1.15.4.gemspec"), 'w') do |io| + File.open(File.join(Gem.default_specifications_dir, "bundler-1.15.4.gemspec"), 'w') do |io| gemspec.version = "1.15.4" io.puts gemspec.to_ruby end @@ -158,8 +156,23 @@ class TestGemCommandsSetupCommand < Gem::TestCase assert_match %r{\A#!\s*#{bin_env}#{ruby_exec}}, File.read(gem_bin_path) end + def test_destdir_flag_does_not_try_to_write_to_the_default_gem_home + FileUtils.chmod "-w", File.join(@gemhome, "plugins") + + destdir = File.join(@tempdir, 'foo') + + @cmd.options[:destdir] = destdir + @cmd.execute + + spec = Gem::Specification.load("bundler/bundler.gemspec") + + spec.executables.each do |e| + assert_path_exist File.join destdir, @gemhome.gsub(/^[a-zA-Z]:/, ''), 'gems', spec.full_name, spec.bindir, e + end + end + def test_files_in - assert_equal %w[rubygems.rb rubygems/ssl_certs/rubygems.org/foo.pem rubygems/test_case.rb], + assert_equal %w[rubygems.rb rubygems/requirement.rb rubygems/ssl_certs/rubygems.org/foo.pem], @cmd.files_in('lib').sort end @@ -169,14 +182,14 @@ class TestGemCommandsSetupCommand < Gem::TestCase Dir.mktmpdir 'lib' do |dir| @cmd.install_lib dir - assert_path_exists File.join(dir, 'rubygems.rb') - assert_path_exists File.join(dir, 'rubygems/ssl_certs/rubygems.org/foo.pem') + assert_path_exist File.join(dir, 'rubygems.rb') + assert_path_exist File.join(dir, 'rubygems/ssl_certs/rubygems.org/foo.pem') - assert_path_exists File.join(dir, 'bundler.rb') - assert_path_exists File.join(dir, 'bundler/b.rb') + assert_path_exist File.join(dir, 'bundler.rb') + assert_path_exist File.join(dir, 'bundler/b.rb') - assert_path_exists File.join(dir, 'bundler/templates/.circleci/config.yml') unless RUBY_ENGINE == "truffleruby" # https://github.com/oracle/truffleruby/issues/2116 - assert_path_exists File.join(dir, 'bundler/templates/.travis.yml') + assert_path_exist File.join(dir, 'bundler/templates/.circleci/config.yml') + assert_path_exist File.join(dir, 'bundler/templates/.travis.yml') end end @@ -192,27 +205,27 @@ class TestGemCommandsSetupCommand < Gem::TestCase spec.executables.each do |e| if Gem.win_platform? - assert_path_exists File.join(bin_dir, "#{e}.bat") + assert_path_exist File.join(bin_dir, "#{e}.bat") end - assert_path_exists File.join bin_dir, e + assert_path_exist File.join bin_dir, e end default_dir = Gem.default_specifications_dir # expect to remove other versions of bundler gemspecs on default specification directory. - refute_path_exists File.join(default_dir, "bundler-1.15.4.gemspec") - assert_path_exists File.join(default_dir, "bundler-#{BUNDLER_VERS}.gemspec") + assert_path_not_exist File.join(default_dir, "bundler-1.15.4.gemspec") + assert_path_exist File.join(default_dir, "bundler-#{BUNDLER_VERS}.gemspec") # expect to not remove bundler-* gemspecs. - assert_path_exists File.join(Gem.dir, "specifications", "bundler-audit-1.0.0.gemspec") + assert_path_exist File.join(Gem.dir, "specifications", "bundler-audit-1.0.0.gemspec") # expect to remove normal gem that was same version. because it's promoted default gems. - refute_path_exists File.join(Gem.dir, "specifications", "bundler-#{BUNDLER_VERS}.gemspec") + assert_path_not_exist File.join(Gem.dir, "specifications", "bundler-#{BUNDLER_VERS}.gemspec") - assert_path_exists "#{Gem.dir}/gems/bundler-#{BUNDLER_VERS}" - assert_path_exists "#{Gem.dir}/gems/bundler-1.15.4" - assert_path_exists "#{Gem.dir}/gems/bundler-audit-1.0.0" + assert_path_exist "#{Gem.dir}/gems/bundler-#{BUNDLER_VERS}" + assert_path_exist "#{Gem.dir}/gems/bundler-1.15.4" + assert_path_exist "#{Gem.dir}/gems/bundler-audit-1.0.0" end def test_install_default_bundler_gem_with_force_flag @@ -227,27 +240,62 @@ class TestGemCommandsSetupCommand < Gem::TestCase f.puts 'echo "hello"' end - bindir(bin_dir) do - @cmd.options[:force] = true - - @cmd.install_default_bundler_gem bin_dir + @cmd.options[:force] = true - bundler_spec = Gem::Specification.load("bundler/bundler.gemspec") - default_spec_path = File.join(Gem.default_specifications_dir, "#{bundler_spec.full_name}.gemspec") - spec = Gem::Specification.load(default_spec_path) + @cmd.install_default_bundler_gem bin_dir - spec.executables.each do |e| - if Gem.win_platform? - assert_path_exists File.join(bin_dir, "#{e}.bat") - end + bundler_spec = Gem::Specification.load("bundler/bundler.gemspec") + default_spec_path = File.join(Gem.default_specifications_dir, "#{bundler_spec.full_name}.gemspec") + spec = Gem::Specification.load(default_spec_path) - assert_path_exists File.join bin_dir, e + spec.executables.each do |e| + if Gem.win_platform? + assert_path_exist File.join(bin_dir, "#{e}.bat") end + + assert_path_exist File.join bin_dir, e + end + end + + def test_install_default_bundler_gem_with_destdir_flag + @cmd.extend FileUtils + + FileUtils.chmod "-w", @gemhome + + destdir = File.join(@tempdir, 'foo') + bin_dir = File.join(destdir, 'bin') + + @cmd.options[:destdir] = destdir + + @cmd.install_default_bundler_gem bin_dir + + spec = Gem::Specification.load("bundler/bundler.gemspec") + + spec.executables.each do |e| + assert_path_exist File.join destdir, @gemhome.gsub(/^[a-zA-Z]:/, ''), 'gems', spec.full_name, spec.bindir, e + end + end + + def test_install_default_bundler_gem_with_destdir_and_prefix_flags + @cmd.extend FileUtils + + destdir = File.join(@tempdir, 'foo') + bin_dir = File.join(destdir, 'bin') + + @cmd.options[:destdir] = destdir + @cmd.options[:prefix] = "/" + + @cmd.install_default_bundler_gem bin_dir + + spec = Gem::Specification.load("bundler/bundler.gemspec") + + spec.executables.each do |e| + assert_path_exist File.join destdir, 'gems', spec.full_name, spec.bindir, e end end def test_remove_old_lib_files - lib = File.join @install_dir, 'lib' + lib = RbConfig::CONFIG["sitelibdir"] lib_rubygems = File.join lib, 'rubygems' lib_bundler = File.join lib, 'bundler' lib_rubygems_defaults = File.join lib_rubygems, 'defaults' @@ -272,13 +320,13 @@ class TestGemCommandsSetupCommand < Gem::TestCase @cmd.remove_old_lib_files lib - files_that_go.each {|file| refute_path_exists(file) unless file == old_bundler_ci && RUBY_ENGINE == "truffleruby" } # https://github.com/oracle/truffleruby/issues/2116 + files_that_go.each {|file| assert_path_not_exist(file) unless file == old_bundler_ci } - files_that_stay.each {|file| assert_path_exists file } + files_that_stay.each {|file| assert_path_exist file } end def test_remove_old_man_files - man = File.join @install_dir, 'man' + man = File.join RbConfig::CONFIG['mandir'], 'man' ruby_1 = File.join man, 'man1', 'ruby.1' bundle_b_1 = File.join man, 'man1', 'bundle-b.1' @@ -295,9 +343,9 @@ class TestGemCommandsSetupCommand < Gem::TestCase @cmd.remove_old_man_files man - files_that_go.each {|file| refute_path_exists file } + files_that_go.each {|file| assert_path_not_exist file } - files_that_stay.each {|file| assert_path_exists file } + files_that_stay.each {|file| assert_path_exist file } end def test_show_release_notes @@ -384,14 +432,14 @@ class TestGemCommandsSetupCommand < Gem::TestCase end def default_gem_bin_path - File.join @install_dir, 'bin', 'gem' + File.join RbConfig::CONFIG['bindir'], 'gem' end def default_bundle_bin_path - File.join @install_dir, 'bin', 'bundle' + File.join RbConfig::CONFIG['bindir'], 'bundle' end def default_bundler_bin_path - File.join @install_dir, 'bin', 'bundler' + File.join RbConfig::CONFIG['bindir'], 'bundler' end end unless Gem.java_platform? diff --git a/test/rubygems/test_gem_commands_signin_command.rb b/test/rubygems/test_gem_commands_signin_command.rb index f8262466b1..0f856a53ba 100644 --- a/test/rubygems/test_gem_commands_signin_command.rb +++ b/test/rubygems/test_gem_commands_signin_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/signin_command' require 'rubygems/installer' @@ -26,14 +26,21 @@ class TestGemCommandsSigninCommand < Gem::TestCase assert_match %r{Signed in.}, sign_in_ui.output end + def test_execute_when_not_already_signed_in_and_not_preexisting_credentials_folder + FileUtils.rm Gem.configuration.credentials_path + + sign_in_ui = util_capture { @cmd.execute } + assert_match %r{Signed in.}, sign_in_ui.output + end + def test_execute_when_already_signed_in_with_same_host host = 'http://some-gemcutter-compatible-host.org' util_capture(nil, host) { @cmd.execute } - old_credentials = YAML.load_file Gem.configuration.credentials_path + old_credentials = load_yaml_file Gem.configuration.credentials_path util_capture(nil, host) { @cmd.execute } - new_credentials = YAML.load_file Gem.configuration.credentials_path + new_credentials = load_yaml_file Gem.configuration.credentials_path assert_equal old_credentials[host], new_credentials[host] end @@ -45,7 +52,7 @@ class TestGemCommandsSigninCommand < Gem::TestCase host = 'http://some-gemcutter-compatible-host.org' util_capture(nil, host, api_key) { @cmd.execute } - credentials = YAML.load_file Gem.configuration.credentials_path + credentials = load_yaml_file Gem.configuration.credentials_path assert_equal credentials[:rubygems_api_key], api_key @@ -60,7 +67,7 @@ class TestGemCommandsSigninCommand < Gem::TestCase assert_match %r{Signed in.}, sign_in_ui.output api_key = 'a5fdbb6ba150cbb83aad2bb2fede64cf040453903' - credentials = YAML.load_file Gem.configuration.credentials_path + credentials = load_yaml_file Gem.configuration.credentials_path assert_equal api_key, credentials[host] end @@ -68,12 +75,12 @@ class TestGemCommandsSigninCommand < Gem::TestCase util_capture { @cmd.execute } api_key = 'a5fdbb6ba150cbb83aad2bb2fede64cf040453903' - credentials = YAML.load_file Gem.configuration.credentials_path + credentials = load_yaml_file Gem.configuration.credentials_path assert_equal api_key, credentials[:rubygems_api_key] end - def test_excute_with_key_name_and_scope + def test_execute_with_key_name_and_scope email = 'you@example.com' password = 'secret' api_key = '1234' @@ -94,7 +101,7 @@ class TestGemCommandsSigninCommand < Gem::TestCase assert_match "show_dashboard [y/N]", key_name_ui.output assert_equal "name=test-key&push_rubygem=true", fetcher.last_request.body - credentials = YAML.load_file Gem.configuration.credentials_path + credentials = load_yaml_file Gem.configuration.credentials_path assert_equal api_key, credentials[:rubygems_api_key] end diff --git a/test/rubygems/test_gem_commands_signout_command.rb b/test/rubygems/test_gem_commands_signout_command.rb index 20389d0537..aa6300b6ab 100644 --- a/test/rubygems/test_gem_commands_signout_command.rb +++ b/test/rubygems/test_gem_commands_signout_command.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/signout_command' require 'rubygems/installer' diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb index 59acfb1ed6..7bca0f3803 100644 --- a/test/rubygems/test_gem_commands_sources_command.rb +++ b/test/rubygems/test_gem_commands_sources_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/sources_command' class TestGemCommandsSourcesCommand < Gem::TestCase @@ -162,7 +162,7 @@ class TestGemCommandsSourcesCommand < Gem::TestCase use_ui ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -188,7 +188,7 @@ class TestGemCommandsSourcesCommand < Gem::TestCase @cmd.handle_options %w[--add http://beta-gems.example.com] use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -299,7 +299,7 @@ source http://gems.example.com/ already present in the cache ui = Gem::MockGemUi.new "n" use_ui ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -367,7 +367,7 @@ source http://gems.example.com/ already present in the cache ui = Gem::MockGemUi.new "n" use_ui ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -385,7 +385,7 @@ source http://gems.example.com/ already present in the cache @cmd.handle_options %w[--add beta-gems.example.com] use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end diff --git a/test/rubygems/test_gem_commands_specification_command.rb b/test/rubygems/test_gem_commands_specification_command.rb index 732278eb6f..c8cb7df32e 100644 --- a/test/rubygems/test_gem_commands_specification_command.rb +++ b/test/rubygems/test_gem_commands_specification_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/specification_command' class TestGemCommandsSpecificationCommand < Gem::TestCase @@ -51,7 +51,7 @@ class TestGemCommandsSpecificationCommand < Gem::TestCase @cmd.options[:all] = true @cmd.options[:version] = "1" - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @ui do @cmd.execute end @@ -64,7 +64,7 @@ class TestGemCommandsSpecificationCommand < Gem::TestCase def test_execute_bad_name @cmd.options[:args] = %w[foo] - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @ui do @cmd.execute end @@ -78,7 +78,7 @@ class TestGemCommandsSpecificationCommand < Gem::TestCase @cmd.options[:args] = %w[foo] @cmd.options[:version] = "1.3.2" - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @ui do @cmd.execute end @@ -114,7 +114,7 @@ class TestGemCommandsSpecificationCommand < Gem::TestCase @cmd.execute end - assert_equal "foo", YAML.load(@ui.output) + assert_equal "foo", load_yaml(@ui.output) end def test_execute_file @@ -230,7 +230,7 @@ class TestGemCommandsSpecificationCommand < Gem::TestCase assert_match %r{\A--- !ruby/object:Gem::Specification}, @ui.output assert_match %r{name: foo}, @ui.output - spec = YAML.load @ui.output + spec = load_yaml @ui.output assert_equal Gem::Version.new("2.0.0"), spec.version end @@ -252,7 +252,7 @@ class TestGemCommandsSpecificationCommand < Gem::TestCase assert_match %r{\A--- !ruby/object:Gem::Specification}, @ui.output assert_match %r{name: foo}, @ui.output - spec = YAML.load @ui.output + spec = load_yaml @ui.output assert_equal Gem::Version.new("2.0.1.pre"), spec.version end diff --git a/test/rubygems/test_gem_commands_stale_command.rb b/test/rubygems/test_gem_commands_stale_command.rb index 0aa7f243e2..83bd3e5def 100644 --- a/test/rubygems/test_gem_commands_stale_command.rb +++ b/test/rubygems/test_gem_commands_stale_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/stale_command' class TestGemCommandsStaleCommand < Gem::TestCase diff --git a/test/rubygems/test_gem_commands_uninstall_command.rb b/test/rubygems/test_gem_commands_uninstall_command.rb index 03ce600cc4..5bd2c40d59 100644 --- a/test/rubygems/test_gem_commands_uninstall_command.rb +++ b/test/rubygems/test_gem_commands_uninstall_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/installer_test_case' +require_relative 'installer_test_case' require 'rubygems/commands/uninstall_command' class TestGemCommandsUninstallCommand < Gem::InstallerTestCase @@ -377,7 +377,7 @@ class TestGemCommandsUninstallCommand < Gem::InstallerTestCase @cmd.options[:args] = ['a:4'] - e = assert_raises Gem::InstallError do + e = assert_raise Gem::InstallError do use_ui ui do @cmd.execute end @@ -420,7 +420,7 @@ WARNING: Use your OS package manager to uninstall vendor gems @cmd.options[:version] = Gem::Requirement.new("> 1") use_ui @ui do - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do @cmd.execute end @@ -436,7 +436,7 @@ WARNING: Use your OS package manager to uninstall vendor gems def test_handle_options_vendor_missing vendordir(nil) do - e = assert_raises OptionParser::InvalidOption do + e = assert_raise Gem::OptionParser::InvalidOption do @cmd.handle_options %w[--vendor] end @@ -477,7 +477,7 @@ WARNING: Use your OS package manager to uninstall vendor gems e = nil @cmd.stub :uninstall, uninstall_exception do use_ui @ui do - e = assert_raises Gem::MockGemUi::TermError do + e = assert_raise Gem::MockGemUi::TermError do @cmd.execute end end diff --git a/test/rubygems/test_gem_commands_unpack_command.rb b/test/rubygems/test_gem_commands_unpack_command.rb index e1fea0f0ff..55369f1eeb 100644 --- a/test/rubygems/test_gem_commands_unpack_command.rb +++ b/test/rubygems/test_gem_commands_unpack_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/unpack_command' class TestGemCommandsUnpackCommand < Gem::TestCase @@ -151,7 +151,7 @@ class TestGemCommandsUnpackCommand < Gem::TestCase end def test_execute_sudo - skip 'Cannot perform this test on windows (chmod)' if win_platform? + pend 'Cannot perform this test on windows (chmod)' if win_platform? util_make_gems @@ -210,7 +210,7 @@ class TestGemCommandsUnpackCommand < Gem::TestCase end end - assert_path_exists File.join(@tempdir, foo_spec.full_name) + assert_path_exist File.join(@tempdir, foo_spec.full_name) end def test_handle_options_metadata diff --git a/test/rubygems/test_gem_commands_update_command.rb b/test/rubygems/test_gem_commands_update_command.rb index 749e9bee20..a7ddddf2c1 100644 --- a/test/rubygems/test_gem_commands_update_command.rb +++ b/test/rubygems/test_gem_commands_update_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/update_command' class TestGemCommandsUpdateCommand < Gem::TestCase @@ -95,7 +95,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase @cmd.options[:args] = [] @cmd.options[:system] = true - assert_raises Gem::MockGemUi::SystemExitException do + assert_raise Gem::MockGemUi::SystemExitException do use_ui @ui do @cmd.execute end @@ -168,14 +168,23 @@ class TestGemCommandsUpdateCommand < Gem::TestCase @cmd.options[:args] = [] @cmd.options[:system] = "2.5.1" - assert_raises Gem::MockGemUi::TermError do + oldest_version_mod = Module.new do + def oldest_supported_version + Gem::Version.new("2.5.2") + end + private :oldest_supported_version + end + + @cmd.extend(oldest_version_mod) + + assert_raise Gem::MockGemUi::TermError do use_ui @ui do @cmd.execute end end assert_empty @ui.output - assert_equal "ERROR: rubygems 2.5.1 is not supported. The oldest supported version is 2.5.2\n", @ui.error + assert_equal "ERROR: rubygems 2.5.1 is not supported on #{RUBY_VERSION}. The oldest version supported by this ruby is 2.5.2\n", @ui.error end def test_execute_system_specific_older_than_3_2_removes_plugins_dir @@ -185,6 +194,15 @@ class TestGemCommandsUpdateCommand < Gem::TestCase end end + oldest_version_mod = Module.new do + def oldest_supported_version + Gem::Version.new("2.5.2") + end + private :oldest_supported_version + end + + @cmd.extend(oldest_version_mod) + @cmd.options[:args] = [] @cmd.options[:system] = "3.1" @@ -193,7 +211,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase @cmd.execute - refute_path_exists Gem.plugindir, "Plugins folder not removed when updating rubygems to pre-3.2" + assert_path_not_exist Gem.plugindir, "Plugins folder not removed when updating rubygems to pre-3.2" end def test_execute_system_specific_newer_than_or_equal_to_3_2_leaves_plugins_dir_alone @@ -203,6 +221,15 @@ class TestGemCommandsUpdateCommand < Gem::TestCase end end + oldest_version_mod = Module.new do + def oldest_supported_version + Gem::Version.new("2.5.2") + end + private :oldest_supported_version + end + + @cmd.extend(oldest_version_mod) + @cmd.options[:args] = [] @cmd.options[:system] = "3.2.a" @@ -212,8 +239,8 @@ class TestGemCommandsUpdateCommand < Gem::TestCase @cmd.execute - assert_path_exists Gem.plugindir, "Plugin folder removed when updating rubygems to post-3.2" - assert_path_exists plugin_file, "Plugin removed when updating rubygems to post-3.2" + assert_path_exist Gem.plugindir, "Plugin folder removed when updating rubygems to post-3.2" + assert_path_exist plugin_file, "Plugin removed when updating rubygems to post-3.2" end def test_execute_system_specifically_to_latest_version @@ -246,7 +273,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase @cmd.options[:args] = %w[gem] @cmd.options[:system] = true - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @ui do @cmd.execute end @@ -264,7 +291,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase @cmd.options[:args] = [] @cmd.options[:system] = true - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do use_ui @ui do @cmd.execute end @@ -357,7 +384,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase a2 = @specs['a-2'] - assert_path_exists File.join(a2.doc_dir, 'rdoc') + assert_path_exist File.join(a2.doc_dir, 'rdoc') end def test_execute_named @@ -503,7 +530,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase def test_fetch_remote_gems_error Gem.sources.replace %w[http://nonexistent.example] - assert_raises Gem::RemoteFetcher::FetchError do + assert_raise Gem::RemoteFetcher::FetchError do @cmd.fetch_remote_gems @specs['a-1'] end end @@ -561,7 +588,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase end def test_handle_options_system_non_version - assert_raises ArgumentError do + assert_raise ArgumentError do @cmd.handle_options %w[--system non-version] end end diff --git a/test/rubygems/test_gem_commands_which_command.rb b/test/rubygems/test_gem_commands_which_command.rb index b67bf040d4..a398dc5708 100644 --- a/test/rubygems/test_gem_commands_which_command.rb +++ b/test/rubygems/test_gem_commands_which_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/which_command' class TestGemCommandsWhichCommand < Gem::TestCase @@ -26,7 +26,7 @@ class TestGemCommandsWhichCommand < Gem::TestCase @cmd.handle_options %w[directory] use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -44,7 +44,7 @@ class TestGemCommandsWhichCommand < Gem::TestCase @cmd.handle_options %w[foo_bar missinglib] use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end @@ -58,7 +58,7 @@ class TestGemCommandsWhichCommand < Gem::TestCase @cmd.handle_options %w[missinglib] use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.execute end end diff --git a/test/rubygems/test_gem_commands_yank_command.rb b/test/rubygems/test_gem_commands_yank_command.rb index 3046655aa8..b798eb3689 100644 --- a/test/rubygems/test_gem_commands_yank_command.rb +++ b/test/rubygems/test_gem_commands_yank_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/commands/yank_command' class TestGemCommandsYankCommand < Gem::TestCase @@ -35,7 +35,7 @@ class TestGemCommandsYankCommand < Gem::TestCase def test_handle_options_missing_argument %w[-v --version -p --platform].each do |option| - assert_raises OptionParser::MissingArgument do + assert_raise Gem::OptionParser::MissingArgument do @cmd.handle_options %W[a #{option}] end end diff --git a/test/rubygems/test_gem_config_file.rb b/test/rubygems/test_gem_config_file.rb index ddc35a9594..32375e6936 100644 --- a/test/rubygems/test_gem_config_file.rb +++ b/test/rubygems/test_gem_config_file.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/config_file' class TestGemConfigFile < Gem::TestCase @@ -41,6 +41,7 @@ class TestGemConfigFile < Gem::TestCase assert_equal true, @cfg.verbose assert_equal [@gem_repo], Gem.sources assert_equal 365, @cfg.cert_expiration_length_days + assert_equal false, @cfg.ipv4_fallback_enabled File.open @temp_conf, 'w' do |fp| fp.puts ":backtrace: true" @@ -56,6 +57,7 @@ class TestGemConfigFile < Gem::TestCase fp.puts ":ssl_verify_mode: 0" fp.puts ":ssl_ca_cert: /etc/ssl/certs" fp.puts ":cert_expiration_length_days: 28" + fp.puts ":ipv4_fallback_enabled: true" end util_config_file @@ -70,6 +72,14 @@ class TestGemConfigFile < Gem::TestCase assert_equal 0, @cfg.ssl_verify_mode assert_equal '/etc/ssl/certs', @cfg.ssl_ca_cert assert_equal 28, @cfg.cert_expiration_length_days + assert_equal true, @cfg.ipv4_fallback_enabled + end + + def test_initialize_ipv4_fallback_enabled_env + ENV['IPV4_FALLBACK_ENABLED'] = 'true' + util_config_file %W[--config-file #{@temp_conf}] + + assert_equal true, @cfg.ipv4_fallback_enabled end def test_initialize_handle_arguments_config_file @@ -185,14 +195,14 @@ class TestGemConfigFile < Gem::TestCase end def test_check_credentials_permissions - skip 'chmod not supported' if win_platform? + pend 'chmod not supported' if win_platform? @cfg.rubygems_api_key = 'x' File.chmod 0644, @cfg.credentials_path use_ui @ui do - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cfg.load_api_keys end end @@ -243,7 +253,7 @@ if you believe they were disclosed to a third party. args = %w[--debug] - _, err = capture_io do + _, err = capture_output do @cfg.handle_arguments args end @@ -312,13 +322,13 @@ if you believe they were disclosed to a third party. end def test_load_api_keys_bad_permission - skip 'chmod not supported' if win_platform? + pend 'chmod not supported' if win_platform? @cfg.rubygems_api_key = 'x' File.chmod 0644, @cfg.credentials_path - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cfg.load_api_keys end end @@ -344,7 +354,7 @@ if you believe they were disclosed to a third party. :rubygems_api_key => 'x', } - assert_equal expected, YAML.load_file(@cfg.credentials_path) + assert_equal expected, load_yaml_file(@cfg.credentials_path) unless win_platform? stat = File.stat @cfg.credentials_path @@ -354,13 +364,13 @@ if you believe they were disclosed to a third party. end def test_rubygems_api_key_equals_bad_permission - skip 'chmod not supported' if win_platform? + pend 'chmod not supported' if win_platform? @cfg.rubygems_api_key = 'x' File.chmod 0644, @cfg.credentials_path - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cfg.rubygems_api_key = 'y' end @@ -368,7 +378,7 @@ if you believe they were disclosed to a third party. :rubygems_api_key => 'x', } - assert_equal expected, YAML.load_file(@cfg.credentials_path) + assert_equal expected, load_yaml_file(@cfg.credentials_path) stat = File.stat @cfg.credentials_path diff --git a/test/rubygems/test_gem_dependency.rb b/test/rubygems/test_gem_dependency.rb index 7ddeafedce..1ca0fc378c 100644 --- a/test/rubygems/test_gem_dependency.rb +++ b/test/rubygems/test_gem_dependency.rb @@ -1,8 +1,14 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/dependency' class TestGemDependency < Gem::TestCase + def setup + super + + without_any_upwards_gemfiles + end + def test_initialize d = dep "pkg", "> 1.0" @@ -11,7 +17,7 @@ class TestGemDependency < Gem::TestCase end def test_initialize_type_bad - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do Gem::Dependency.new 'monkey' => '1.0' end @@ -43,7 +49,7 @@ class TestGemDependency < Gem::TestCase assert_equal :runtime, dep("pkg").type assert_equal :development, dep("pkg", [], :development).type - assert_raises ArgumentError do + assert_raise ArgumentError do dep "pkg", :sometimes end end @@ -248,7 +254,7 @@ class TestGemDependency < Gem::TestCase a = dep 'a' b = dep 'b' - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do a.merge b end @@ -330,7 +336,7 @@ class TestGemDependency < Gem::TestCase dep = Gem::Dependency.new "a", "= 2.0" - e = assert_raises Gem::MissingSpecVersionError do + e = assert_raise Gem::MissingSpecVersionError do dep.to_specs end @@ -353,7 +359,7 @@ class TestGemDependency < Gem::TestCase assert_equal [b, b_1], dep.to_specs Gem::BundlerVersionFinder.stub(:bundler_version_with_reason, ["3.5", "reason"]) do - e = assert_raises Gem::MissingSpecVersionError do + e = assert_raise Gem::MissingSpecVersionError do dep.to_specs end @@ -377,7 +383,7 @@ class TestGemDependency < Gem::TestCase dep = Gem::Dependency.new "b", "= 2.0" - e = assert_raises Gem::MissingSpecError do + e = assert_raise Gem::MissingSpecError do dep.to_specs end diff --git a/test/rubygems/test_gem_dependency_installer.rb b/test/rubygems/test_gem_dependency_installer.rb index c62a3f355a..9cbdcefea4 100644 --- a/test/rubygems/test_gem_dependency_installer.rb +++ b/test/rubygems/test_gem_dependency_installer.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/dependency_installer' require 'rubygems/security' @@ -113,7 +113,7 @@ class TestGemDependencyInstaller < Gem::TestCase dep = Gem::Dependency.new "p" inst = Gem::DependencyInstaller.new - assert_raises Gem::UnsatisfiableDependencyError do + assert_raise Gem::UnsatisfiableDependencyError do inst.install dep end @@ -392,7 +392,7 @@ class TestGemDependencyInstaller < Gem::TestCase assert_equal %w[f-1], inst.installed_gems.map {|s| s.full_name } - assert_path_exists e1.extension_dir + assert_path_exist e1.extension_dir end def test_install_dependency_old @@ -712,7 +712,7 @@ class TestGemDependencyInstaller < Gem::TestCase inst = nil Dir.chdir @tempdir do - e = assert_raises Gem::UnsatisfiableDependencyError do + e = assert_raise Gem::UnsatisfiableDependencyError do inst = Gem::DependencyInstaller.new :domain => :local inst.install 'b' end @@ -883,7 +883,7 @@ class TestGemDependencyInstaller < Gem::TestCase policy = Gem::Security::HighSecurity inst = Gem::DependencyInstaller.new :security_policy => policy - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do inst.install 'b' end diff --git a/test/rubygems/test_gem_dependency_list.rb b/test/rubygems/test_gem_dependency_list.rb index 097e680596..15c50de199 100644 --- a/test/rubygems/test_gem_dependency_list.rb +++ b/test/rubygems/test_gem_dependency_list.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/dependency_list' class TestGemDependencyList < Gem::TestCase diff --git a/test/rubygems/test_gem_dependency_resolution_error.rb b/test/rubygems/test_gem_dependency_resolution_error.rb index 5321f7031c..0e4a2fe31a 100644 --- a/test/rubygems/test_gem_dependency_resolution_error.rb +++ b/test/rubygems/test_gem_dependency_resolution_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemDependencyResolutionError < Gem::TestCase def setup diff --git a/test/rubygems/test_gem_doctor.rb b/test/rubygems/test_gem_doctor.rb index 1cef52234e..583c735dd3 100644 --- a/test/rubygems/test_gem_doctor.rb +++ b/test/rubygems/test_gem_doctor.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/doctor' class TestGemDoctor < Gem::TestCase @@ -27,31 +27,31 @@ class TestGemDoctor < Gem::TestCase io.write 'this will raise an exception when evaluated.' end - assert_path_exists File.join(a.gem_dir, 'Rakefile') - assert_path_exists File.join(a.gem_dir, 'lib', 'a.rb') + assert_path_exist File.join(a.gem_dir, 'Rakefile') + assert_path_exist File.join(a.gem_dir, 'lib', 'a.rb') - assert_path_exists b.gem_dir - refute_path_exists b.spec_file + assert_path_exist b.gem_dir + assert_path_not_exist b.spec_file - assert_path_exists c.gem_dir - assert_path_exists c.spec_file + assert_path_exist c.gem_dir + assert_path_exist c.spec_file doctor = Gem::Doctor.new @gemhome - capture_io do + capture_output do use_ui @ui do doctor.doctor end end - assert_path_exists File.join(a.gem_dir, 'Rakefile') - assert_path_exists File.join(a.gem_dir, 'lib', 'a.rb') + assert_path_exist File.join(a.gem_dir, 'Rakefile') + assert_path_exist File.join(a.gem_dir, 'lib', 'a.rb') - refute_path_exists b.gem_dir - refute_path_exists b.spec_file + assert_path_not_exist b.gem_dir + assert_path_not_exist b.spec_file - refute_path_exists c.gem_dir - refute_path_exists c.spec_file + assert_path_not_exist c.gem_dir + assert_path_not_exist c.spec_file expected = <<-OUTPUT Checking #{@gemhome} @@ -80,31 +80,31 @@ Removed directory gems/c-2 io.write 'this will raise an exception when evaluated.' end - assert_path_exists File.join(a.gem_dir, 'Rakefile') - assert_path_exists File.join(a.gem_dir, 'lib', 'a.rb') + assert_path_exist File.join(a.gem_dir, 'Rakefile') + assert_path_exist File.join(a.gem_dir, 'lib', 'a.rb') - assert_path_exists b.gem_dir - refute_path_exists b.spec_file + assert_path_exist b.gem_dir + assert_path_not_exist b.spec_file - assert_path_exists c.gem_dir - assert_path_exists c.spec_file + assert_path_exist c.gem_dir + assert_path_exist c.spec_file doctor = Gem::Doctor.new @gemhome, true - capture_io do + capture_output do use_ui @ui do doctor.doctor end end - assert_path_exists File.join(a.gem_dir, 'Rakefile') - assert_path_exists File.join(a.gem_dir, 'lib', 'a.rb') + assert_path_exist File.join(a.gem_dir, 'Rakefile') + assert_path_exist File.join(a.gem_dir, 'lib', 'a.rb') - assert_path_exists b.gem_dir - refute_path_exists b.spec_file + assert_path_exist b.gem_dir + assert_path_not_exist b.spec_file - assert_path_exists c.gem_dir - assert_path_exists c.spec_file + assert_path_exist c.gem_dir + assert_path_exist c.spec_file expected = <<-OUTPUT Checking #{@gemhome} @@ -127,13 +127,13 @@ Extra directory gems/c-2 doctor = Gem::Doctor.new @tempdir - capture_io do + capture_output do use_ui @ui do doctor.doctor end end - assert_path_exists other_dir + assert_path_exist other_dir expected = <<-OUTPUT Checking #{@tempdir} @@ -163,13 +163,13 @@ This directory does not appear to be a RubyGems repository, skipping doctor = Gem::Doctor.new @gemhome - capture_io do + capture_output do use_ui @ui do doctor.doctor end end - # refute_path_exists bad_plugin + # assert_path_not_exist bad_plugin expected = <<-OUTPUT Checking #{@gemhome} diff --git a/test/rubygems/test_gem_ext_builder.rb b/test/rubygems/test_gem_ext_builder.rb index 6bebfa7a03..165194510e 100644 --- a/test/rubygems/test_gem_ext_builder.rb +++ b/test/rubygems/test_gem_ext_builder.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/ext' require 'rubygems/installer' @@ -106,6 +106,7 @@ install: end def test_build_extensions + pend if /mswin/ =~ RUBY_PLATFORM && ENV.key?('GITHUB_ACTIONS') # not working from the beginning @spec.extensions << 'ext/extconf.rb' ext_dir = File.join @spec.gem_dir, 'ext' @@ -132,15 +133,16 @@ install: @builder.build_extensions end - assert_path_exists @spec.extension_dir - assert_path_exists @spec.gem_build_complete_path - assert_path_exists File.join @spec.extension_dir, 'gem_make.out' - assert_path_exists File.join @spec.extension_dir, 'a.rb' - assert_path_exists File.join @spec.gem_dir, 'lib', 'a.rb' - assert_path_exists File.join @spec.gem_dir, 'lib', 'a', 'b.rb' + assert_path_exist @spec.extension_dir + assert_path_exist @spec.gem_build_complete_path + assert_path_exist File.join @spec.extension_dir, 'gem_make.out' + assert_path_exist File.join @spec.extension_dir, 'a.rb' + assert_path_exist File.join @spec.gem_dir, 'lib', 'a.rb' + assert_path_exist File.join @spec.gem_dir, 'lib', 'a', 'b.rb' end def test_build_extensions_with_gemhome_with_space + pend if /mswin/ =~ RUBY_PLATFORM && ENV.key?('GITHUB_ACTIONS') # not working from the beginning new_gemhome = File.join @tempdir, 'gem home' File.rename(@gemhome, new_gemhome) @gemhome = new_gemhome @@ -161,6 +163,7 @@ install: false end end + pend if /mswin/ =~ RUBY_PLATFORM && ENV.key?('GITHUB_ACTIONS') # not working from the beginning @spec.extensions << 'ext/extconf.rb' @@ -188,12 +191,12 @@ install: @builder.build_extensions end - assert_path_exists @spec.extension_dir - assert_path_exists @spec.gem_build_complete_path - assert_path_exists File.join @spec.extension_dir, 'gem_make.out' - assert_path_exists File.join @spec.extension_dir, 'a.rb' - refute_path_exists File.join @spec.gem_dir, 'lib', 'a.rb' - refute_path_exists File.join @spec.gem_dir, 'lib', 'a', 'b.rb' + assert_path_exist @spec.extension_dir + assert_path_exist @spec.gem_build_complete_path + assert_path_exist File.join @spec.extension_dir, 'gem_make.out' + assert_path_exist File.join @spec.extension_dir, 'a.rb' + assert_path_not_exist File.join @spec.gem_dir, 'lib', 'a.rb' + assert_path_not_exist File.join @spec.gem_dir, 'lib', 'a', 'b.rb' ensure class << Gem remove_method :install_extension_in_lib @@ -210,7 +213,7 @@ install: assert_equal '', @ui.output assert_equal '', @ui.error - refute_path_exists File.join @spec.extension_dir, 'gem_make.out' + assert_path_not_exist File.join @spec.extension_dir, 'gem_make.out' end def test_build_extensions_rebuild_failure @@ -219,13 +222,13 @@ install: @spec.extensions << nil - assert_raises Gem::Ext::BuildError do + assert_raise Gem::Ext::BuildError do use_ui @ui do @builder.build_extensions end end - refute_path_exists @spec.gem_build_complete_path + assert_path_not_exist @spec.gem_build_complete_path end def test_build_extensions_extconf_bad @@ -235,7 +238,7 @@ install: FileUtils.mkdir_p @spec.gem_dir - e = assert_raises Gem::Ext::BuildError do + e = assert_raise Gem::Ext::BuildError do use_ui @ui do @builder.build_extensions end @@ -251,7 +254,7 @@ install: assert_match %r{#{Regexp.escape Gem.ruby} .* extconf\.rb}, cmd_make_out assert_match %r{: No such file}, cmd_make_out - refute_path_exists @spec.gem_build_complete_path + assert_path_not_exist @spec.gem_build_complete_path assert_equal cwd, Dir.pwd end @@ -261,7 +264,7 @@ install: gem_make_out = File.join @spec.extension_dir, 'gem_make.out' @spec.extensions << nil - e = assert_raises Gem::Ext::BuildError do + e = assert_raise Gem::Ext::BuildError do use_ui @ui do @builder.build_extensions end @@ -273,7 +276,7 @@ install: assert_equal "No builder for extension ''\n", File.read(gem_make_out) - refute_path_exists @spec.gem_build_complete_path + assert_path_not_exist @spec.gem_build_complete_path ensure FileUtils.rm_f gem_make_out end @@ -308,7 +311,7 @@ install: path = File.join @spec.gem_dir, "extconf_args" assert_equal args.inspect, File.read(path).strip - assert_path_exists @spec.extension_dir + assert_path_exist @spec.extension_dir end def test_initialize diff --git a/test/rubygems/test_gem_ext_cmake_builder.rb b/test/rubygems/test_gem_ext_cmake_builder.rb index dffe4a7fb3..5ab82c545f 100644 --- a/test/rubygems/test_gem_ext_cmake_builder.rb +++ b/test/rubygems/test_gem_ext_cmake_builder.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/ext' class TestGemExtCmakeBuilder < Gem::TestCase @@ -7,13 +7,13 @@ class TestGemExtCmakeBuilder < Gem::TestCase super # Details: https://github.com/rubygems/rubygems/issues/1270#issuecomment-177368340 - skip "CmakeBuilder doesn't work on Windows." if Gem.win_platform? + pend "CmakeBuilder doesn't work on Windows." if Gem.win_platform? begin _, status = Open3.capture2e('cmake') - skip 'cmake not present' unless status.success? + pend 'cmake not present' unless status.success? rescue Errno::ENOENT - skip 'cmake not present' + pend 'cmake not present' end @ext = File.join @tempdir, 'ext' @@ -50,7 +50,7 @@ install (FILES test.txt DESTINATION bin) def test_self_build_fail output = [] - error = assert_raises Gem::InstallError do + error = assert_raise Gem::InstallError do Gem::Ext::CmakeBuilder.build nil, @dest_path, output, [], nil, @ext end diff --git a/test/rubygems/test_gem_ext_configure_builder.rb b/test/rubygems/test_gem_ext_configure_builder.rb index 6b11b0c2ab..76ccfe2dc4 100644 --- a/test/rubygems/test_gem_ext_configure_builder.rb +++ b/test/rubygems/test_gem_ext_configure_builder.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/ext' class TestGemExtConfigureBuilder < Gem::TestCase @@ -17,7 +17,7 @@ class TestGemExtConfigureBuilder < Gem::TestCase end def test_self_build - skip("test_self_build skipped on MS Windows (VC++)") if vc_windows? + pend("test_self_build skipped on MS Windows (VC++)") if vc_windows? File.open File.join(@ext, './configure'), 'w' do |configure| configure.puts "#!/bin/sh\necho \"#{@makefile_body}\" > Makefile" @@ -42,10 +42,10 @@ class TestGemExtConfigureBuilder < Gem::TestCase end def test_self_build_fail - skip("test_self_build_fail skipped on MS Windows (VC++)") if vc_windows? + pend("test_self_build_fail skipped on MS Windows (VC++)") if vc_windows? output = [] - error = assert_raises Gem::InstallError do + error = assert_raise Gem::InstallError do Gem::Ext::ConfigureBuilder.build nil, @dest_path, output, [], nil, @ext end @@ -62,7 +62,7 @@ class TestGemExtConfigureBuilder < Gem::TestCase def test_self_build_has_makefile if vc_windows? && !nmake_found? - skip("test_self_build_has_makefile skipped - nmake not found") + pend("test_self_build_has_makefile skipped - nmake not found") end File.open File.join(@ext, 'Makefile'), 'w' do |makefile| diff --git a/test/rubygems/test_gem_ext_ext_conf_builder.rb b/test/rubygems/test_gem_ext_ext_conf_builder.rb index 21fe27166b..10a544cbbc 100644 --- a/test/rubygems/test_gem_ext_ext_conf_builder.rb +++ b/test/rubygems/test_gem_ext_ext_conf_builder.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/ext' class TestGemExtExtConfBuilder < Gem::TestCase @@ -16,11 +16,11 @@ class TestGemExtExtConfBuilder < Gem::TestCase def test_class_build if java_platform? - skip("failing on jruby") + pend("failing on jruby") end if vc_windows? && !nmake_found? - skip("test_class_build skipped - nmake not found") + pend("test_class_build skipped - nmake not found") end File.open File.join(@ext, 'extconf.rb'), 'w' do |extconf| @@ -41,11 +41,12 @@ class TestGemExtExtConfBuilder < Gem::TestCase assert_contains_make_command '', output[7] assert_contains_make_command 'install', output[10] assert_empty Dir.glob(File.join(@ext, 'siteconf*.rb')) + assert_empty Dir.glob(File.join(@ext, '.gem.*')) end def test_class_build_rbconfig_make_prog if java_platform? - skip("failing on jruby") + pend("failing on jruby") end configure_args do @@ -65,12 +66,15 @@ class TestGemExtExtConfBuilder < Gem::TestCase end end - def test_class_build_env_make - env_make = ENV.delete 'MAKE' + def test_class_build_env_MAKE + env_make = ENV.delete 'make' + ENV['make'] = nil + + env_MAKE = ENV.delete 'MAKE' ENV['MAKE'] = 'anothermake' if java_platform? - skip("failing on jruby") + pend("failing on jruby") end configure_args '' do @@ -80,7 +84,7 @@ class TestGemExtExtConfBuilder < Gem::TestCase output = [] - assert_raises Gem::InstallError do + assert_raise Gem::InstallError do Gem::Ext::ExtConfBuilder.build 'extconf.rb', @dest_path, output, [], nil, @ext end @@ -88,12 +92,13 @@ class TestGemExtExtConfBuilder < Gem::TestCase assert_contains_make_command 'clean', output[4] end ensure - ENV['MAKE'] = env_make + ENV['MAKE'] = env_MAKE + ENV['make'] = env_make end def test_class_build_extconf_fail if vc_windows? && !nmake_found? - skip("test_class_build_extconf_fail skipped - nmake not found") + pend("test_class_build_extconf_fail skipped - nmake not found") end File.open File.join(@ext, 'extconf.rb'), 'w' do |extconf| @@ -104,7 +109,7 @@ class TestGemExtExtConfBuilder < Gem::TestCase output = [] - error = assert_raises Gem::InstallError do + error = assert_raise Gem::InstallError do Gem::Ext::ExtConfBuilder.build 'extconf.rb', @dest_path, output, [], nil, @ext end @@ -114,12 +119,12 @@ class TestGemExtExtConfBuilder < Gem::TestCase assert_match(File.join(@dest_path, 'mkmf.log'), output[4]) assert_includes(output, "To see why this extension failed to compile, please check the mkmf.log which can be found here:\n") - assert_path_exists File.join @dest_path, 'mkmf.log' + assert_path_exist File.join @dest_path, 'mkmf.log' end def test_class_build_extconf_success_without_warning if vc_windows? && !nmake_found? - skip("test_class_build_extconf_fail skipped - nmake not found") + pend("test_class_build_extconf_fail skipped - nmake not found") end File.open File.join(@ext, 'extconf.rb'), 'w' do |extconf| @@ -134,12 +139,12 @@ class TestGemExtExtConfBuilder < Gem::TestCase refute_includes(output, "To see why this extension failed to compile, please check the mkmf.log which can be found here:\n") - assert_path_exists File.join @dest_path, 'mkmf.log' + assert_path_exist File.join @dest_path, 'mkmf.log' end def test_class_build_unconventional if vc_windows? && !nmake_found? - skip("test_class_build skipped - nmake not found") + pend("test_class_build skipped - nmake not found") end File.open File.join(@ext, 'extconf.rb'), 'w' do |extconf| @@ -180,7 +185,7 @@ end def test_class_make if vc_windows? && !nmake_found? - skip("test_class_make skipped - nmake not found") + pend("test_class_make skipped - nmake not found") end output = [] @@ -202,7 +207,7 @@ end end def test_class_make_no_Makefile - error = assert_raises Gem::InstallError do + error = assert_raise Gem::InstallError do Gem::Ext::ExtConfBuilder.make @ext, ['output'], @ext end diff --git a/test/rubygems/test_gem_ext_rake_builder.rb b/test/rubygems/test_gem_ext_rake_builder.rb index 094581890a..3d8922eed5 100644 --- a/test/rubygems/test_gem_ext_rake_builder.rb +++ b/test/rubygems/test_gem_ext_rake_builder.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/ext' class TestGemExtRakeBuilder < Gem::TestCase @@ -48,6 +48,8 @@ class TestGemExtRakeBuilder < Gem::TestCase end def test_class_no_openssl_override + pend 'openssl is missing' unless Gem::HAVE_OPENSSL + create_temp_mkrf_file('task :default') rake = util_spec 'rake' do |s| @@ -90,7 +92,7 @@ class TestGemExtRakeBuilder < Gem::TestCase output = [] build_rake_in(false) do |rake| - error = assert_raises Gem::InstallError do + error = assert_raise Gem::InstallError do Gem::Ext::RakeBuilder.build "mkrf_conf.rb", @dest_path, output, [], nil, @ext end diff --git a/test/rubygems/test_gem_gem_runner.rb b/test/rubygems/test_gem_gem_runner.rb index 8df11ecebc..6f5361cf2f 100644 --- a/test/rubygems/test_gem_gem_runner.rb +++ b/test/rubygems/test_gem_gem_runner.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemGemRunner < Gem::TestCase def setup diff --git a/test/rubygems/test_gem_gemcutter_utilities.rb b/test/rubygems/test_gem_gemcutter_utilities.rb index 3290a3a908..0bcd1504e9 100644 --- a/test/rubygems/test_gem_gemcutter_utilities.rb +++ b/test/rubygems/test_gem_gemcutter_utilities.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems' require 'rubygems/command' require 'rubygems/gemcutter_utilities' @@ -14,6 +14,7 @@ class TestGemGemcutterUtilities < Gem::TestCase Gem.configuration.disable_default_gem_server = nil ENV['RUBYGEMS_HOST'] = nil + ENV['GEM_HOST_OTP_CODE'] = nil Gem.configuration.rubygems_api_key = nil @cmd = Gem::Command.new '', 'summary' @@ -22,6 +23,7 @@ class TestGemGemcutterUtilities < Gem::TestCase def teardown ENV['RUBYGEMS_HOST'] = nil + ENV['GEM_HOST_OTP_CODE'] = nil Gem.configuration.rubygems_api_key = nil credential_teardown @@ -35,8 +37,6 @@ class TestGemGemcutterUtilities < Gem::TestCase "http://rubygems.engineyard.com" => "EYKEY", } - FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path - File.open Gem.configuration.credentials_path, 'w' do |f| f.write keys.to_yaml end @@ -50,7 +50,6 @@ class TestGemGemcutterUtilities < Gem::TestCase def test_api_key keys = { :rubygems_api_key => 'KEY' } - FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path File.open Gem.configuration.credentials_path, 'w' do |f| f.write keys.to_yaml @@ -63,7 +62,6 @@ class TestGemGemcutterUtilities < Gem::TestCase def test_api_key_override keys = { :rubygems_api_key => 'KEY', :other => 'OTHER' } - FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path File.open Gem.configuration.credentials_path, 'w' do |f| f.write keys.to_yaml @@ -101,7 +99,7 @@ class TestGemGemcutterUtilities < Gem::TestCase assert @fetcher.last_request["authorization"] assert_match %r{Signed in.}, @sign_in_ui.output - credentials = YAML.load_file Gem.configuration.credentials_path + credentials = load_yaml_file Gem.configuration.credentials_path assert_equal api_key, credentials[:rubygems_api_key] end @@ -115,7 +113,7 @@ class TestGemGemcutterUtilities < Gem::TestCase assert @fetcher.last_request["authorization"] assert_match %r{Signed in.}, @sign_in_ui.output - credentials = YAML.load_file Gem.configuration.credentials_path + credentials = load_yaml_file Gem.configuration.credentials_path assert_equal api_key, credentials['http://example.com'] end @@ -129,7 +127,7 @@ class TestGemGemcutterUtilities < Gem::TestCase assert @fetcher.last_request["authorization"] assert_match %r{Signed in.}, @sign_in_ui.output - credentials = YAML.load_file Gem.configuration.credentials_path + credentials = load_yaml_file Gem.configuration.credentials_path assert_equal api_key, credentials[:rubygems_api_key] end @@ -142,7 +140,7 @@ class TestGemGemcutterUtilities < Gem::TestCase assert @fetcher.last_request["authorization"] assert_match %r{Signed in.}, @sign_in_ui.output - credentials = YAML.load_file Gem.configuration.credentials_path + credentials = load_yaml_file Gem.configuration.credentials_path assert_equal api_key, credentials['http://example.com'] end @@ -168,7 +166,6 @@ class TestGemGemcutterUtilities < Gem::TestCase api_key = 'a5fdbb6ba150cbb83aad2bb2fede64cf040453903' other_api_key = 'f46dbb18bb6a9c97cdc61b5b85c186a17403cdcbf' - FileUtils.mkdir_p File.dirname(Gem.configuration.credentials_path) File.open Gem.configuration.credentials_path, 'w' do |f| f.write Hash[:other_api_key, other_api_key].to_yaml end @@ -177,13 +174,13 @@ class TestGemGemcutterUtilities < Gem::TestCase assert_match %r{Enter your RubyGems.org credentials.}, @sign_in_ui.output assert_match %r{Signed in.}, @sign_in_ui.output - credentials = YAML.load_file Gem.configuration.credentials_path + credentials = load_yaml_file Gem.configuration.credentials_path assert_equal api_key, credentials[:rubygems_api_key] assert_equal other_api_key, credentials[:other_api_key] end def test_sign_in_with_bad_credentials - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do util_sign_in ['Access Denied.', 403, 'Forbidden'] end @@ -191,6 +188,16 @@ class TestGemGemcutterUtilities < Gem::TestCase assert_match %r{Access Denied.}, @sign_in_ui.output end + def test_signin_with_env_otp_code + ENV['GEM_HOST_OTP_CODE'] = '111111' + api_key = 'a5fdbb6ba150cbb83aad2bb2fede64cf040453903' + + util_sign_in [api_key, 200, 'OK'] + + assert_match 'Signed in with API key:', @sign_in_ui.output + assert_equal '111111', @fetcher.last_request['OTP'] + end + def test_sign_in_with_correct_otp_code api_key = 'a5fdbb6ba150cbb83aad2bb2fede64cf040453903' response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry." @@ -209,7 +216,7 @@ class TestGemGemcutterUtilities < Gem::TestCase def test_sign_in_with_incorrect_otp_code response = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry." - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do util_sign_in [response, 401, 'Unauthorized'], nil, [], "111111\n" end @@ -246,7 +253,6 @@ class TestGemGemcutterUtilities < Gem::TestCase def test_verify_api_key keys = {:other => 'a5fdbb6ba150cbb83aad2bb2fede64cf040453903'} - FileUtils.mkdir_p File.dirname(Gem.configuration.credentials_path) File.open Gem.configuration.credentials_path, 'w' do |f| f.write keys.to_yaml end @@ -257,7 +263,7 @@ class TestGemGemcutterUtilities < Gem::TestCase end def test_verify_missing_api_key - assert_raises Gem::MockGemUi::TermError do + assert_raise Gem::MockGemUi::TermError do @cmd.verify_api_key :missing end end diff --git a/test/rubygems/test_gem_impossible_dependencies_error.rb b/test/rubygems/test_gem_impossible_dependencies_error.rb index e4fe6ef77c..971be151df 100644 --- a/test/rubygems/test_gem_impossible_dependencies_error.rb +++ b/test/rubygems/test_gem_impossible_dependencies_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemImpossibleDependenciesError < Gem::TestCase def test_message_conflict diff --git a/test/rubygems/test_gem_indexer.rb b/test/rubygems/test_gem_indexer.rb index adc83dd8fb..6653f29adf 100644 --- a/test/rubygems/test_gem_indexer.rb +++ b/test/rubygems/test_gem_indexer.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/indexer' class TestGemIndexer < Gem::TestCase diff --git a/test/rubygems/test_gem_install_update_options.rb b/test/rubygems/test_gem_install_update_options.rb index b4528dba17..a499c2be3b 100644 --- a/test/rubygems/test_gem_install_update_options.rb +++ b/test/rubygems/test_gem_install_update_options.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/installer_test_case' +require_relative 'installer_test_case' require 'rubygems/install_update_options' require 'rubygems/command' require 'rubygems/dependency_installer' @@ -92,7 +92,7 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase end def test_security_policy - skip 'openssl is missing' unless Gem::HAVE_OPENSSL + pend 'openssl is missing' unless Gem::HAVE_OPENSSL @cmd.handle_options %w[-P HighSecurity] @@ -100,11 +100,11 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase end def test_security_policy_unknown - skip 'openssl is missing' unless Gem::HAVE_OPENSSL + pend 'openssl is missing' unless Gem::HAVE_OPENSSL @cmd.add_install_update_options - e = assert_raises OptionParser::InvalidArgument do + e = assert_raise Gem::OptionParser::InvalidArgument do @cmd.handle_options %w[-P UnknownSecurity] end assert_includes e.message, "UnknownSecurity" @@ -124,8 +124,8 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase @installer = Gem::Installer.at @gem, @cmd.options @installer.install - assert_path_exists File.join(Gem.user_dir, 'gems') - assert_path_exists File.join(Gem.user_dir, 'gems', @spec.full_name) + assert_path_exist File.join(Gem.user_dir, 'gems') + assert_path_exist File.join(Gem.user_dir, 'gems', @spec.full_name) end def test_user_install_disabled_read_only @@ -137,9 +137,9 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase @gem = @spec.cache_file if win_platform? - skip('test_user_install_disabled_read_only test skipped on MS Windows') + pend('test_user_install_disabled_read_only test skipped on MS Windows') elsif Process.uid.zero? - skip('test_user_install_disabled_read_only test skipped in root privilege') + pend('test_user_install_disabled_read_only test skipped in root privilege') else @cmd.handle_options %w[--no-user-install] @@ -150,7 +150,7 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase Gem.use_paths @gemhome, @userhome - assert_raises(Gem::FilePermissionError) do + assert_raise(Gem::FilePermissionError) do Gem::Installer.at(@gem, @cmd.options).install end end @@ -169,7 +169,7 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase def test_vendor_missing vendordir(nil) do - e = assert_raises OptionParser::InvalidOption do + e = assert_raise Gem::OptionParser::InvalidOption do @cmd.handle_options %w[--vendor] end diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb index 5652d86331..dae2b070d5 100644 --- a/test/rubygems/test_gem_installer.rb +++ b/test/rubygems/test_gem_installer.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/installer_test_case' +require_relative 'installer_test_case' class TestGemInstaller < Gem::InstallerTestCase def setup @@ -33,6 +33,8 @@ class TestGemInstaller < Gem::InstallerTestCase require 'rubygems' +Gem.use_gemdeps + version = \">= 0.a\" str = ARGV.first @@ -76,7 +78,7 @@ end installer.generate_bin installed_exec = File.join util_inst_bindir, 'executable' - assert_path_exists installed_exec + assert_path_exist installed_exec wrapper = File.read installed_exec assert_match %r{generated by RubyGems}, wrapper @@ -90,7 +92,7 @@ end ui = Gem::MockGemUi.new "n\n" use_ui ui do - e = assert_raises Gem::InstallError do + e = assert_raise Gem::InstallError do installer.generate_bin end @@ -134,7 +136,7 @@ gem 'other', version installer.generate_bin # should not raise installed_exec = File.join util_inst_bindir, 'foo-executable-bar' - assert_path_exists installed_exec + assert_path_exist installed_exec wrapper = File.read installed_exec assert_match %r{generated by RubyGems}, wrapper @@ -150,7 +152,7 @@ gem 'other', version ui = Gem::MockGemUi.new "n\n" use_ui ui do - e = assert_raises Gem::InstallError do + e = assert_raise Gem::InstallError do installer.generate_bin end @@ -169,7 +171,7 @@ gem 'other', version installer.generate_bin installed_exec = File.join util_inst_bindir, 'executable' - assert_path_exists installed_exec + assert_path_exist installed_exec wrapper = File.read installed_exec assert_match %r{generated by RubyGems}, wrapper @@ -184,7 +186,7 @@ gem 'other', version installer.generate_bin installed_exec = File.join util_inst_bindir, 'executable' - assert_path_exists installed_exec + assert_path_exist installed_exec wrapper = File.read installed_exec assert_match %r{generated by RubyGems}, wrapper @@ -222,7 +224,7 @@ gem 'other', version end def test_check_that_user_bin_dir_is_in_path_tilde - skip "Tilde is PATH is not supported under MS Windows" if win_platform? + pend "Tilde is PATH is not supported under MS Windows" if win_platform? orig_PATH, ENV['PATH'] = ENV['PATH'], [ENV['PATH'], '~/bin'].join(File::PATH_SEPARATOR) @@ -264,7 +266,7 @@ gem 'other', version assert installer.ensure_dependency(@spec, dep) dep = Gem::Dependency.new 'b', '> 2' - e = assert_raises Gem::InstallError do + e = assert_raise Gem::InstallError do installer.ensure_dependency @spec, dep end @@ -278,7 +280,7 @@ gem 'other', version installer = Gem::Installer.at a_gem - e = assert_raises Gem::InstallError do + e = assert_raise Gem::InstallError do installer.ensure_loadable_spec end @@ -286,8 +288,35 @@ gem 'other', version "(SyntaxError)", e.message end + def test_ensure_no_race_conditions_between_installing_and_loading_gemspecs + a, a_gem = util_gem 'a', 2 + + Gem::Installer.at(a_gem).install + + t1 = Thread.new do + 5.times do + Gem::Installer.at(a_gem).install + sleep 0.1 + end + end + + t2 = Thread.new do + _, err = capture_output do + 20.times do + Gem::Specification.load(a.spec_file) + Gem::Specification.send(:clear_load_cache) + end + end + + assert_empty err + end + + t1.join + t2.join + end + def test_ensure_loadable_spec_security_policy - skip 'openssl is missing' unless Gem::HAVE_OPENSSL + pend 'openssl is missing' unless Gem::HAVE_OPENSSL _, a_gem = util_gem 'a', 2 do |s| s.add_dependency 'garbage ~> 5' @@ -296,7 +325,7 @@ gem 'other', version policy = Gem::Security::HighSecurity installer = Gem::Installer.at a_gem, :security_policy => policy - assert_raises Gem::Security::Exception do + assert_raise Gem::Security::Exception do installer.ensure_loadable_spec end end @@ -306,7 +335,7 @@ gem 'other', version installer.extract_files - assert_path_exists File.join @spec.gem_dir, 'bin/executable' + assert_path_exist File.join @spec.gem_dir, 'bin/executable' end def test_generate_bin_bindir @@ -329,7 +358,7 @@ gem 'other', version assert_directory_exists util_inst_bindir installed_exec = File.join(util_inst_bindir, 'executable') - assert_path_exists installed_exec + assert_path_exist installed_exec assert_equal mask, File.stat(installed_exec).mode unless win_platform? wrapper = File.read installed_exec @@ -372,7 +401,7 @@ gem 'other', version installer.generate_bin assert_directory_exists util_inst_bindir installed_exec = File.join util_inst_bindir, 'executable' - assert_path_exists installed_exec + assert_path_exist installed_exec assert_equal mask, File.stat(installed_exec).mode unless win_platform? wrapper = File.read installed_exec @@ -391,7 +420,7 @@ gem 'other', version installer.generate_bin assert_directory_exists util_inst_bindir installed_exec = File.join util_inst_bindir, 'foo-executable-bar' - assert_path_exists installed_exec + assert_path_exist installed_exec ensure Gem::Installer.exec_format = nil end @@ -407,7 +436,7 @@ gem 'other', version installer.generate_bin assert_directory_exists util_inst_bindir installed_exec = File.join util_inst_bindir, 'executable' - assert_path_exists installed_exec + assert_path_exist installed_exec ensure Gem::Installer.exec_format = nil end @@ -431,7 +460,7 @@ gem 'other', version installer.generate_bin installed_exec = File.join("#{@gemhome}2", "bin", 'executable') - assert_path_exists installed_exec + assert_path_exist installed_exec assert_equal mask, File.stat(installed_exec).mode unless win_platform? wrapper = File.read installed_exec @@ -446,7 +475,7 @@ gem 'other', version installer.wrappers = true installer.generate_bin - refute_path_exists util_inst_bindir, 'bin dir was created when not needed' + assert_path_not_exist util_inst_bindir, 'bin dir was created when not needed' end def test_generate_bin_script_no_perms @@ -458,13 +487,13 @@ gem 'other', version Dir.mkdir util_inst_bindir if win_platform? - skip('test_generate_bin_script_no_perms skipped on MS Windows') + pend('test_generate_bin_script_no_perms skipped on MS Windows') elsif Process.uid.zero? - skip('test_generate_bin_script_no_perms skipped in root privilege') + pend('test_generate_bin_script_no_perms skipped in root privilege') else FileUtils.chmod 0000, util_inst_bindir - assert_raises Gem::FilePermissionError do + assert_raise Gem::FilePermissionError do installer.generate_bin end end @@ -488,7 +517,7 @@ gem 'other', version installer.generate_bin installed_exec = File.join @gemhome, 'bin', 'executable' - assert_path_exists installed_exec + assert_path_exist installed_exec assert_equal mask, File.stat(installed_exec).mode unless win_platform? wrapper = File.read installed_exec @@ -515,7 +544,7 @@ gem 'other', version installer.generate_bin assert_directory_exists util_inst_bindir - assert_path_exists installed_exec + assert_path_exist installed_exec assert_equal mask, File.stat(installed_exec).mode unless win_platform? assert_match %r{generated by RubyGems}, File.read(installed_exec) @@ -525,7 +554,7 @@ gem 'other', version end def test_generate_bin_symlink - skip "Symlinks not supported or not enabled" unless symlink_supported? + pend "Symlinks not supported or not enabled" unless symlink_supported? installer = setup_base_installer @@ -549,7 +578,7 @@ gem 'other', version installer.wrappers = false installer.generate_bin - refute_path_exists util_inst_bindir + assert_path_not_exist util_inst_bindir end def test_generate_bin_symlink_no_perms @@ -562,13 +591,13 @@ gem 'other', version Dir.mkdir util_inst_bindir if win_platform? - skip('test_generate_bin_symlink_no_perms skipped on MS Windows') + pend('test_generate_bin_symlink_no_perms skipped on MS Windows') elsif Process.uid.zero? - skip('test_user_install_disabled_read_only test skipped in root privilege') + pend('test_user_install_disabled_read_only test skipped in root privilege') else FileUtils.chmod 0000, util_inst_bindir - assert_raises Gem::FilePermissionError do + assert_raise Gem::FilePermissionError do installer.generate_bin end end @@ -577,7 +606,7 @@ gem 'other', version end def test_generate_bin_symlink_update_newer - skip "Symlinks not supported or not enabled" unless symlink_supported? + pend "Symlinks not supported or not enabled" unless symlink_supported? installer = setup_base_installer @@ -609,7 +638,7 @@ gem 'other', version end def test_generate_bin_symlink_update_older - skip "Symlinks not supported or not enabled" unless symlink_supported? + pend "Symlinks not supported or not enabled" unless symlink_supported? installer = setup_base_installer @@ -647,7 +676,7 @@ gem 'other', version end def test_generate_bin_symlink_update_remove_wrapper - skip "Symlinks not supported or not enabled" unless symlink_supported? + pend "Symlinks not supported or not enabled" unless symlink_supported? installer = setup_base_installer @@ -658,7 +687,7 @@ gem 'other', version installer.generate_bin installed_exec = File.join util_inst_bindir, 'executable' - assert_path_exists installed_exec + assert_path_exist installed_exec @spec = Gem::Specification.new do |s| s.files = ['lib/code.rb'] @@ -701,7 +730,7 @@ gem 'other', version assert_directory_exists util_inst_bindir installed_exec = File.join(util_inst_bindir, 'executable') - assert_path_exists installed_exec + assert_path_exist installed_exec if symlink_supported? assert File.symlink?(installed_exec) @@ -720,7 +749,7 @@ gem 'other', version end def test_generate_bin_uses_default_shebang - skip "Symlinks not supported or not enabled" unless symlink_supported? + pend "Symlinks not supported or not enabled" unless symlink_supported? installer = setup_base_installer @@ -730,11 +759,30 @@ gem 'other', version installer.generate_bin default_shebang = Gem.ruby - shebang_line = open("#{@gemhome}/bin/executable") {|f| f.readlines.first } + shebang_line = File.open("#{@gemhome}/bin/executable") {|f| f.readlines.first } assert_match(/\A#!/, shebang_line) assert_match(/#{default_shebang}/, shebang_line) end + def test_generate_bin_with_dangling_symlink + gem_with_dangling_symlink = File.expand_path("packages/ascii_binder-0.1.10.1.gem", __dir__) + + installer = Gem::Installer.at( + gem_with_dangling_symlink, + :user_install => false, + :force => true + ) + + build_rake_in do + use_ui @ui do + installer.install + end + end + + assert_match %r{bin/ascii_binder` is dangling symlink pointing to `bin/asciibinder`}, @ui.error + assert_empty @ui.output + end + def test_generate_plugins installer = util_setup_installer do |spec| write_file File.join(@tempdir, 'lib', 'rubygems_plugin.rb') do |io| @@ -926,22 +974,19 @@ gem 'other', version spec_file = File.join @gemhome, 'specifications', @spec.spec_name Gem.pre_install do - refute_path_exists cache_file, 'cache file must not exist yet' - refute_path_exists spec_file, 'spec file must not exist yet' + assert_path_not_exist cache_file, 'cache file must not exist yet' true end Gem.post_build do - assert_path_exists gemdir, 'gem install dir must exist' - assert_path_exists rakefile, 'gem executable must exist' - refute_path_exists stub_exe, 'gem executable must not exist' - refute_path_exists spec_file, 'spec file must not exist yet' + assert_path_exist gemdir, 'gem install dir must exist' + assert_path_exist rakefile, 'gem executable must exist' + assert_path_not_exist stub_exe, 'gem executable must not exist' true end Gem.post_install do - assert_path_exists cache_file, 'cache file must exist' - assert_path_exists spec_file, 'spec file must exist' + assert_path_exist cache_file, 'cache file must exist' end @newspec = nil @@ -952,21 +997,21 @@ gem 'other', version end assert_equal @spec, @newspec - assert_path_exists gemdir - assert_path_exists stub_exe, 'gem executable must exist' + assert_path_exist gemdir + assert_path_exist stub_exe, 'gem executable must exist' exe = File.join gemdir, 'bin', 'executable' - assert_path_exists exe + assert_path_exist exe exe_mode = File.stat(exe).mode & 0111 assert_equal 0111, exe_mode, "0%o" % exe_mode unless win_platform? - assert_path_exists File.join gemdir, 'lib', 'code.rb' + assert_path_exist File.join gemdir, 'lib', 'code.rb' - assert_path_exists rakefile + assert_path_exist rakefile assert_equal spec_file, @newspec.loaded_from - assert_path_exists spec_file + assert_path_exist spec_file assert_same installer, @post_build_hook_arg assert_same installer, @post_install_hook_arg @@ -989,7 +1034,7 @@ gem 'other', version exe = File.join gemdir, 'bin', 'executable' - e = assert_raises RuntimeError do + e = assert_raise RuntimeError do instance_eval File.read(exe) end @@ -1035,7 +1080,7 @@ gem 'other', version end end - e = assert_raises RuntimeError do + e = assert_raise RuntimeError do instance_eval File.read(old_bin_file) end @@ -1063,7 +1108,7 @@ gem 'other', version begin Gem::Specification.reset - e = assert_raises Gem::GemNotFoundException do + e = assert_raise Gem::GemNotFoundException do instance_eval File.read(exe) end ensure @@ -1080,7 +1125,7 @@ gem 'other', version exe = File.join @gemhome, 'bin', 'executable' - assert_path_exists exe, "default gem's executable not installed" + assert_path_exist exe, "default gem's executable not installed" installer = util_setup_installer do |spec| spec.name = 'default' @@ -1098,7 +1143,7 @@ gem 'other', version end end - e = assert_raises RuntimeError do + e = assert_raise RuntimeError do instance_eval File.read(exe) end @@ -1125,7 +1170,7 @@ gem 'other', version begin Gem::Specification.reset - e = assert_raises RuntimeError do + e = assert_raise RuntimeError do instance_eval File.read(exe) end ensure @@ -1145,7 +1190,7 @@ gem 'other', version end gemdir = File.join(@gemhome, 'gems', @spec.full_name) - assert_path_exists File.join gemdir, 'lib', 'code.rb' + assert_path_exist File.join gemdir, 'lib', 'code.rb' installer = util_setup_installer @@ -1168,8 +1213,8 @@ gem 'other', version end end - assert_path_exists File.join gemdir, 'lib', 'other.rb' - refute_path_exists File.join gemdir, 'lib', 'code.rb', + assert_path_exist File.join gemdir, 'lib', 'other.rb' + assert_path_not_exist File.join gemdir, 'lib', 'code.rb', "code.rb from prior install of same gem shouldn't remain here" end @@ -1184,7 +1229,7 @@ gem 'other', version end gem_dir = File.join(@gemhome, 'gems', 'missing_dep-1') - assert_path_exists gem_dir + assert_path_exist gem_dir end def test_install_build_root @@ -1211,19 +1256,23 @@ gem 'other', version assert_directory_exists File.join(Gem.dir, 'doc') assert_directory_exists File.join(Gem.dir, 'specifications') - assert_path_exists File.join @gemhome, 'cache', @spec.file_name - assert_path_exists File.join @gemhome, 'specifications', @spec.spec_name + assert_path_exist File.join @gemhome, 'cache', @spec.file_name + assert_path_exist File.join @gemhome, 'specifications', @spec.spec_name end def test_install_post_build_false - installer = setup_base_installer + @spec = util_spec 'a' + + util_build_gem @spec + + installer = util_installer @spec, @gemhome Gem.post_build do false end use_ui @ui do - e = assert_raises Gem::InstallError do + e = assert_raise Gem::InstallError do installer.install end @@ -1233,10 +1282,10 @@ gem 'other', version end spec_file = File.join @gemhome, 'specifications', @spec.spec_name - refute_path_exists spec_file + assert_path_not_exist spec_file gem_dir = File.join @gemhome, 'gems', @spec.full_name - refute_path_exists gem_dir + assert_path_not_exist gem_dir end def test_install_post_build_nil @@ -1251,21 +1300,25 @@ gem 'other', version end spec_file = File.join @gemhome, 'specifications', @spec.spec_name - assert_path_exists spec_file + assert_path_exist spec_file gem_dir = File.join @gemhome, 'gems', @spec.full_name - assert_path_exists gem_dir + assert_path_exist gem_dir end def test_install_pre_install_false - installer = setup_base_installer + @spec = util_spec 'a' + + util_build_gem @spec + + installer = util_installer @spec, @gemhome Gem.pre_install do false end use_ui @ui do - e = assert_raises Gem::InstallError do + e = assert_raise Gem::InstallError do installer.install end @@ -1275,7 +1328,7 @@ gem 'other', version end spec_file = File.join @gemhome, 'specifications', @spec.spec_name - refute_path_exists spec_file + assert_path_not_exist spec_file end def test_install_pre_install_nil @@ -1290,7 +1343,7 @@ gem 'other', version end spec_file = File.join @gemhome, 'specifications', @spec.spec_name - assert_path_exists spec_file + assert_path_exist spec_file end def test_install_with_message @@ -1344,7 +1397,7 @@ gem 'other', version expected_makefile = File.join gemhome2, 'gems', @spec.full_name, 'Makefile' - assert_path_exists expected_makefile + assert_path_exist expected_makefile end def test_install_extension_dir_is_removed_on_reinstall @@ -1373,7 +1426,7 @@ gem 'other', version write_file should_be_removed do |io| io.write "DELETE ME ON REINSTALL" end - assert_path_exists should_be_removed + assert_path_exist should_be_removed # reinstall the gem, this is also the same as pristine use_ui @ui do @@ -1381,7 +1434,7 @@ gem 'other', version installer.install end - refute_path_exists should_be_removed + assert_path_not_exist should_be_removed end def test_install_user_extension_dir @@ -1409,13 +1462,13 @@ gem 'other', version expected_makefile = File.join Gem.user_dir, 'gems', @spec.full_name, 'Makefile' - assert_path_exists expected_makefile - assert_path_exists expected_extension_dir - refute_path_exists File.join expected_extension_dir, 'gem_make.out' + assert_path_exist expected_makefile + assert_path_exist expected_extension_dir + assert_path_not_exist File.join expected_extension_dir, 'gem_make.out' end def test_find_lib_file_after_install - skip "extensions don't quite work on jruby" if Gem.java_platform? + pend "extensions don't quite work on jruby" if Gem.java_platform? @spec = setup_base_spec @spec.extensions << "extconf.rb" @@ -1461,7 +1514,8 @@ gem 'other', version end def test_install_extension_and_script - skip "Makefile creation crashes on jruby" if Gem.java_platform? + pend "Makefile creation crashes on jruby" if Gem.java_platform? + pend if /mswin/ =~ RUBY_PLATFORM && ENV.key?('GITHUB_ACTIONS') # not working from the beginning @spec = setup_base_spec @spec.extensions << "extconf.rb" @@ -1489,65 +1543,67 @@ gem 'other', version RUBY end - refute_path_exists File.join @spec.gem_dir, rb - refute_path_exists File.join @spec.gem_dir, rb2 + assert_path_not_exist File.join @spec.gem_dir, rb + assert_path_not_exist File.join @spec.gem_dir, rb2 use_ui @ui do path = Gem::Package.build @spec installer = Gem::Installer.at path installer.install end - assert_path_exists File.join @spec.gem_dir, rb - assert_path_exists File.join @spec.gem_dir, rb2 + assert_path_exist File.join @spec.gem_dir, rb + assert_path_exist File.join @spec.gem_dir, rb2 end def test_install_extension_flat - skip "extensions don't quite work on jruby" if Gem.java_platform? + pend "extensions don't quite work on jruby" if Gem.java_platform? - @spec = setup_base_spec - @spec.require_paths = ["."] + begin + @spec = setup_base_spec + @spec.require_paths = ["."] - @spec.extensions << "extconf.rb" + @spec.extensions << "extconf.rb" - write_file File.join(@tempdir, "extconf.rb") do |io| - io.write <<-RUBY - require "mkmf" + write_file File.join(@tempdir, "extconf.rb") do |io| + io.write <<-RUBY + require "mkmf" - CONFIG['CC'] = '$(TOUCH) $@ ||' - CONFIG['LDSHARED'] = '$(TOUCH) $@ ||' - $ruby = '#{Gem.ruby}' + CONFIG['CC'] = '$(TOUCH) $@ ||' + CONFIG['LDSHARED'] = '$(TOUCH) $@ ||' + $ruby = '#{Gem.ruby}' - create_makefile("#{@spec.name}") - RUBY - end + create_makefile("#{@spec.name}") + RUBY + end - # empty depend file for no auto dependencies - @spec.files += %W[depend #{@spec.name}.c].each do |file| - write_file File.join(@tempdir, file) - end + # empty depend file for no auto dependencies + @spec.files += %W[depend #{@spec.name}.c].each do |file| + write_file File.join(@tempdir, file) + end - so = File.join(@spec.gem_dir, "#{@spec.name}.#{RbConfig::CONFIG["DLEXT"]}") - refute_path_exists so - use_ui @ui do - path = Gem::Package.build @spec + so = File.join(@spec.gem_dir, "#{@spec.name}.#{RbConfig::CONFIG["DLEXT"]}") + assert_path_not_exist so + use_ui @ui do + path = Gem::Package.build @spec - installer = Gem::Installer.at path - installer.install - end - assert_path_exists so - rescue - puts '-' * 78 - puts File.read File.join(@gemhome, 'gems', 'a-2', 'Makefile') - puts '-' * 78 + installer = Gem::Installer.at path + installer.install + end + assert_path_exist so + rescue + puts '-' * 78 + puts File.read File.join(@gemhome, 'gems', 'a-2', 'Makefile') + puts '-' * 78 - path = File.join(@gemhome, 'gems', 'a-2', 'gem_make.out') + path = File.join(@gemhome, 'gems', 'a-2', 'gem_make.out') - if File.exist?(path) - puts File.read(path) - puts '-' * 78 - end + if File.exist?(path) + puts File.read(path) + puts '-' * 78 + end - raise + raise + end end def test_installation_satisfies_dependency_eh @@ -1580,7 +1636,7 @@ gem 'other', version installer.force = false use_ui @ui do - assert_raises Gem::InstallError do + assert_raise Gem::InstallError do installer.install end end @@ -1639,7 +1695,7 @@ gem 'other', version use_ui @ui do installer = Gem::Installer.at gem - e = assert_raises Gem::InstallError do + e = assert_raise Gem::InstallError do installer.pre_install_checks end assert_equal '#<Gem::Specification name=../malicious version=1> has an invalid name', e.message @@ -1659,7 +1715,7 @@ gem 'other', version use_ui @ui do installer = Gem::Installer.at gem - e = assert_raises Gem::InstallError do + e = assert_raise Gem::InstallError do installer.pre_install_checks end assert_equal "#<Gem::Specification name=malicious\n::Object.const_set(:FROM_EVAL, true)# version=1> has an invalid name", e.message @@ -1681,7 +1737,7 @@ gem 'other', version use_ui @ui do installer = Gem::Installer.at gem - e = assert_raises Gem::InstallError do + e = assert_raise Gem::InstallError do installer.pre_install_checks end assert_equal "#<Gem::Specification name=malicious version=1> has an invalid require_paths", e.message @@ -1689,7 +1745,7 @@ gem 'other', version end def test_pre_install_checks_malicious_extensions_before_eval - skip "mswin environment disallow to create file contained the carriage return code." if Gem.win_platform? + pend "mswin environment disallow to create file contained the carriage return code." if Gem.win_platform? spec = util_spec "malicious", '1' def spec.full_name # so the spec is buildable @@ -1704,7 +1760,7 @@ gem 'other', version use_ui @ui do installer = Gem::Installer.at gem - e = assert_raises Gem::InstallError do + e = assert_raise Gem::InstallError do installer.pre_install_checks end assert_equal "#<Gem::Specification name=malicious version=1> has an invalid extensions", e.message @@ -1725,7 +1781,7 @@ gem 'other', version use_ui @ui do installer = Gem::Installer.at gem - e = assert_raises Gem::InstallError do + e = assert_raise Gem::InstallError do installer.pre_install_checks end assert_equal "#<Gem::Specification name=malicious version=1> has an invalid specification_version", e.message @@ -1747,13 +1803,33 @@ gem 'other', version use_ui @ui do installer = Gem::Installer.at gem installer.ignore_dependencies = true - e = assert_raises Gem::InstallError do + e = assert_raise Gem::InstallError do installer.pre_install_checks end assert_equal "#<Gem::Specification name=malicious version=1> has an invalid dependencies", e.message end end + def test_pre_install_checks_malicious_platform_before_eval + gem_with_ill_formated_platform = File.expand_path("packages/ill-formatted-platform-1.0.0.10.gem", __dir__) + + installer = Gem::Installer.at( + gem_with_ill_formated_platform, + :install_dir => @gem_home, + :user_install => false, + :force => true + ) + + use_ui @ui do + e = assert_raise Gem::InstallError do + installer.pre_install_checks + end + + assert_equal "x86-mswin32\n system('id > /tmp/nyangawa')# is an invalid platform", e.message + assert_empty @ui.output + end + end + def test_shebang installer = setup_base_installer @@ -1971,14 +2047,14 @@ gem 'other', version installer.unpack dest end - assert_path_exists File.join dest, 'lib', 'code.rb' - assert_path_exists File.join dest, 'bin', 'executable' + assert_path_exist File.join dest, 'lib', 'code.rb' + assert_path_exist File.join dest, 'bin', 'executable' end def test_write_build_info_file installer = setup_base_installer - refute_path_exists @spec.build_info_file + assert_path_not_exist @spec.build_info_file installer.build_args = %w[ --with-libyaml-dir /usr/local/Cellar/libyaml/0.1.4 @@ -1986,7 +2062,7 @@ gem 'other', version installer.write_build_info_file - assert_path_exists @spec.build_info_file + assert_path_exist @spec.build_info_file expected = "--with-libyaml-dir\n/usr/local/Cellar/libyaml/0.1.4\n" @@ -1996,11 +2072,11 @@ gem 'other', version def test_write_build_info_file_empty installer = setup_base_installer - refute_path_exists @spec.build_info_file + assert_path_not_exist @spec.build_info_file installer.write_build_info_file - refute_path_exists @spec.build_info_file + assert_path_not_exist @spec.build_info_file end def test_write_build_info_file_install_dir @@ -2013,8 +2089,8 @@ gem 'other', version installer.write_build_info_file - refute_path_exists @spec.build_info_file - assert_path_exists \ + assert_path_not_exist @spec.build_info_file + assert_path_exist \ File.join("#{@gemhome}2", 'build_info', "#{@spec.full_name}.info") end @@ -2024,27 +2100,27 @@ gem 'other', version gem = File.join @gemhome, @spec.file_name FileUtils.mv cache_file, gem - refute_path_exists cache_file + assert_path_not_exist cache_file installer = Gem::Installer.at gem installer.gem_home = @gemhome installer.write_cache_file - assert_path_exists cache_file + assert_path_exist cache_file end def test_write_spec @spec = setup_base_spec FileUtils.rm @spec.spec_file - refute_path_exists @spec.spec_file + assert_path_not_exist @spec.spec_file installer = Gem::Installer.for_spec @spec installer.gem_home = @gemhome installer.write_spec - assert_path_exists @spec.spec_file + assert_path_exist @spec.spec_file loaded = Gem::Specification.load @spec.spec_file @@ -2056,7 +2132,7 @@ gem 'other', version def test_write_spec_writes_cached_spec @spec = setup_base_spec FileUtils.rm @spec.spec_file - refute_path_exists @spec.spec_file + assert_path_not_exist @spec.spec_file @spec.files = %w[a.rb b.rb c.rb] @@ -2099,7 +2175,7 @@ gem 'other', version assert_directory_exists File.join(@spec.gem_dir, 'bin') installed_exec = File.join @spec.gem_dir, 'bin', 'executable' - assert_path_exists installed_exec + assert_path_exist installed_exec assert_directory_exists File.join(Gem.default_dir, 'specifications') assert_directory_exists File.join(Gem.default_dir, 'specifications', 'default') @@ -2111,7 +2187,7 @@ gem 'other', version assert_directory_exists util_inst_bindir installed_exec = File.join util_inst_bindir, 'executable' - assert_path_exists installed_exec + assert_path_exist installed_exec wrapper = File.read installed_exec @@ -2137,7 +2213,7 @@ gem 'other', version assert_directory_exists util_inst_bindir installed_exec = File.join util_inst_bindir, 'executable' - assert_path_exists installed_exec + assert_path_exist installed_exec wrapper = File.read installed_exec assert_match %r{generated by RubyGems}, wrapper @@ -2163,7 +2239,7 @@ gem 'other', version assert_directory_exists File.join(@spec.gem_dir, 'exe') installed_exec = File.join @spec.gem_dir, 'exe', 'executable' - assert_path_exists installed_exec + assert_path_exist installed_exec assert_directory_exists File.join(Gem.default_dir, 'specifications') assert_directory_exists File.join(Gem.default_dir, 'specifications', 'default') diff --git a/test/rubygems/test_gem_local_remote_options.rb b/test/rubygems/test_gem_local_remote_options.rb index 93059ae731..948d1e3981 100644 --- a/test/rubygems/test_gem_local_remote_options.rb +++ b/test/rubygems/test_gem_local_remote_options.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/local_remote_options' require 'rubygems/command' @@ -123,7 +123,7 @@ class TestGemLocalRemoteOptions < Gem::TestCase s1 = 'htp://more-gems.example.com' - assert_raises ArgumentError do + assert_raise ArgumentError do @cmd.handle_options %W[--source #{s1}] end diff --git a/test/rubygems/test_gem_name_tuple.rb b/test/rubygems/test_gem_name_tuple.rb index 92cfe6c1d2..d87db9bc45 100644 --- a/test/rubygems/test_gem_name_tuple.rb +++ b/test/rubygems/test_gem_name_tuple.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/name_tuple' class TestGemNameTuple < Gem::TestCase diff --git a/test/rubygems/test_gem_package.rb b/test/rubygems/test_gem_package.rb index fd28f9a2a5..48dcbee9f1 100644 --- a/test/rubygems/test_gem_package.rb +++ b/test/rubygems/test_gem_package.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'rubygems/package/tar_test_case' -require 'digest' +require_relative 'package/tar_test_case' +require 'rubygems/openssl' class TestGemPackage < Gem::Package::TarTestCase def setup @@ -22,7 +22,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_class_new_old_format - skip "jruby can't require the simple_gem file" if Gem.java_platform? + pend "jruby can't require the simple_gem file" if Gem.java_platform? require_relative "simple_gem" File.open 'old_format.gem', 'wb' do |io| io.write SIMPLE_GEM @@ -84,21 +84,21 @@ class TestGemPackage < Gem::Package::TarTestCase io.write spec.to_yaml end - metadata_sha256 = Digest::SHA256.hexdigest s.string - metadata_sha512 = Digest::SHA512.hexdigest s.string + metadata_sha256 = OpenSSL::Digest::SHA256.hexdigest s.string + metadata_sha512 = OpenSSL::Digest::SHA512.hexdigest s.string expected = { 'SHA512' => { 'metadata.gz' => metadata_sha512, - 'data.tar.gz' => Digest::SHA512.hexdigest(tar), + 'data.tar.gz' => OpenSSL::Digest::SHA512.hexdigest(tar), }, 'SHA256' => { 'metadata.gz' => metadata_sha256, - 'data.tar.gz' => Digest::SHA256.hexdigest(tar), + 'data.tar.gz' => OpenSSL::Digest::SHA256.hexdigest(tar), }, } - assert_equal expected, YAML.load(checksums) + assert_equal expected, load_yaml(checksums) end def test_build_time_uses_source_date_epoch @@ -190,7 +190,7 @@ class TestGemPackage < Gem::Package::TarTestCase File.symlink('../lib/code.rb', 'lib/code_sym2.rb') rescue Errno::EACCES => e if win_platform? - skip "symlink - must be admin with no UAC on Windows" + pend "symlink - must be admin with no UAC on Windows" else raise e end @@ -218,7 +218,7 @@ class TestGemPackage < Gem::Package::TarTestCase end assert_equal %w[lib/code.rb], files - assert_equal [{'lib/code_sym.rb' => 'lib/code.rb'}, {'lib/code_sym2.rb' => '../lib/code.rb'}], symlinks + assert_equal [{'lib/code_sym.rb' => 'code.rb'}, {'lib/code_sym2.rb' => '../lib/code.rb'}], symlinks end def test_build @@ -240,7 +240,7 @@ class TestGemPackage < Gem::Package::TarTestCase package.build assert_equal Gem::VERSION, spec.rubygems_version - assert_path_exists spec.file_name + assert_path_exist spec.file_name reader = Gem::Package.new spec.file_name assert_equal spec, reader.spec @@ -252,7 +252,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_build_auto_signed - skip 'openssl is missing' unless Gem::HAVE_OPENSSL + pend 'openssl is missing' unless Gem::HAVE_OPENSSL FileUtils.mkdir_p File.join(Gem.user_home, '.gem') @@ -279,7 +279,7 @@ class TestGemPackage < Gem::Package::TarTestCase package.build assert_equal Gem::VERSION, spec.rubygems_version - assert_path_exists spec.file_name + assert_path_exist spec.file_name reader = Gem::Package.new spec.file_name assert reader.verify @@ -295,7 +295,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_build_auto_signed_encrypted_key - skip 'openssl is missing' unless Gem::HAVE_OPENSSL + pend 'openssl is missing' unless Gem::HAVE_OPENSSL FileUtils.mkdir_p File.join(Gem.user_home, '.gem') @@ -322,7 +322,7 @@ class TestGemPackage < Gem::Package::TarTestCase package.build assert_equal Gem::VERSION, spec.rubygems_version - assert_path_exists spec.file_name + assert_path_exist spec.file_name reader = Gem::Package.new spec.file_name assert reader.verify @@ -343,7 +343,7 @@ class TestGemPackage < Gem::Package::TarTestCase package = Gem::Package.new spec.file_name package.spec = spec - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do package.build end @@ -356,7 +356,7 @@ class TestGemPackage < Gem::Package::TarTestCase package = Gem::Package.new spec.file_name package.spec = spec - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do package.build true, true end @@ -364,7 +364,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_build_signed - skip 'openssl is missing' unless Gem::HAVE_OPENSSL + pend 'openssl is missing' unless Gem::HAVE_OPENSSL spec = Gem::Specification.new 'build', '1' spec.summary = 'build' @@ -385,7 +385,7 @@ class TestGemPackage < Gem::Package::TarTestCase package.build assert_equal Gem::VERSION, spec.rubygems_version - assert_path_exists spec.file_name + assert_path_exist spec.file_name reader = Gem::Package.new spec.file_name assert reader.verify @@ -401,7 +401,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_build_signed_encrypted_key - skip 'openssl is missing' unless Gem::HAVE_OPENSSL + pend 'openssl is missing' unless Gem::HAVE_OPENSSL spec = Gem::Specification.new 'build', '1' spec.summary = 'build' @@ -422,7 +422,7 @@ class TestGemPackage < Gem::Package::TarTestCase package.build assert_equal Gem::VERSION, spec.rubygems_version - assert_path_exists spec.file_name + assert_path_exist spec.file_name reader = Gem::Package.new spec.file_name assert reader.verify @@ -476,7 +476,7 @@ class TestGemPackage < Gem::Package::TarTestCase package.extract_files @destination extracted = File.join @destination, 'lib/code.rb' - assert_path_exists extracted + assert_path_exist extracted mask = 0100666 & (~File.umask) @@ -507,7 +507,7 @@ class TestGemPackage < Gem::Package::TarTestCase package.extract_files @destination - assert_path_exists @destination + assert_path_exist @destination end def test_extract_tar_gz_absolute @@ -519,7 +519,7 @@ class TestGemPackage < Gem::Package::TarTestCase end end - e = assert_raises Gem::Package::PathError do + e = assert_raise Gem::Package::PathError do package.extract_tar_gz tgz_io, @destination end @@ -543,14 +543,14 @@ class TestGemPackage < Gem::Package::TarTestCase package.extract_tar_gz tgz_io, @destination rescue Errno::EACCES => e if win_platform? - skip "symlink - must be admin with no UAC on Windows" + pend "symlink - must be admin with no UAC on Windows" else raise e end end extracted = File.join @destination, 'lib/foo.rb' - assert_path_exists extracted + assert_path_exist extracted assert_equal '../relative.rb', File.readlink(extracted) assert_equal 'hi', @@ -574,18 +574,19 @@ class TestGemPackage < Gem::Package::TarTestCase destination_subdir = File.join @destination, 'subdir' FileUtils.mkdir_p destination_subdir - e = assert_raises(Gem::Package::PathError, Errno::EACCES) do + expected_exceptions = win_platform? ? [Gem::Package::SymlinkError, Errno::EACCES] : [Gem::Package::SymlinkError] + + e = assert_raise(*expected_exceptions) do package.extract_tar_gz tgz_io, destination_subdir end - if Gem::Package::PathError === e - assert_equal("installing into parent path lib/link/outside.txt of " + - "#{destination_subdir} is not allowed", e.message) - elsif win_platform? - skip "symlink - must be admin with no UAC on Windows" - else - raise e - end + pend "symlink - must be admin with no UAC on Windows" if Errno::EACCES === e + + assert_equal("installing symlink 'lib/link' pointing to parent path #{@destination} of " + + "#{destination_subdir} is not allowed", e.message) + + assert_path_not_exist File.join(@destination, "outside.txt") + assert_path_not_exist File.join(destination_subdir, "lib/link") end def test_extract_symlink_parent_doesnt_delete_user_dir @@ -601,27 +602,27 @@ class TestGemPackage < Gem::Package::TarTestCase destination_user_subdir = File.join destination_user_dir, 'dir' FileUtils.mkdir_p destination_user_subdir - skip "TMPDIR seems too long to add it as symlink into tar" if destination_user_dir.size > 90 + pend "TMPDIR seems too long to add it as symlink into tar" if destination_user_dir.size > 90 tgz_io = util_tar_gz do |tar| tar.add_symlink 'link', destination_user_dir, 16877 tar.add_symlink 'link/dir', '.', 16877 end - e = assert_raises(Gem::Package::PathError, Errno::EACCES) do + expected_exceptions = win_platform? ? [Gem::Package::SymlinkError, Errno::EACCES] : [Gem::Package::SymlinkError] + + e = assert_raise(*expected_exceptions) do package.extract_tar_gz tgz_io, destination_subdir end - assert_path_exists destination_user_subdir + pend "symlink - must be admin with no UAC on Windows" if Errno::EACCES === e - if Gem::Package::PathError === e - assert_equal("installing into parent path #{destination_user_subdir} of " + - "#{destination_subdir} is not allowed", e.message) - elsif win_platform? - skip "symlink - must be admin with no UAC on Windows" - else - raise e - end + assert_equal("installing symlink 'link' pointing to parent path #{destination_user_dir} of " + + "#{destination_subdir} is not allowed", e.message) + + assert_path_exist destination_user_subdir + assert_path_not_exist File.join(destination_subdir, "link/dir") + assert_path_not_exist File.join(destination_subdir, "link") end def test_extract_tar_gz_directory @@ -638,10 +639,10 @@ class TestGemPackage < Gem::Package::TarTestCase package.extract_tar_gz tgz_io, @destination extracted = File.join @destination, 'lib/foo.rb' - assert_path_exists extracted + assert_path_exist extracted extracted = File.join @destination, 'lib/foo' - assert_path_exists extracted + assert_path_exist extracted end def test_extract_tar_gz_dot_slash @@ -656,7 +657,7 @@ class TestGemPackage < Gem::Package::TarTestCase package.extract_tar_gz tgz_io, @destination extracted = File.join @destination, 'dot_slash.rb' - assert_path_exists extracted + assert_path_exist extracted end def test_extract_tar_gz_dot_file @@ -671,7 +672,7 @@ class TestGemPackage < Gem::Package::TarTestCase package.extract_tar_gz tgz_io, @destination extracted = File.join @destination, '.dot_file.rb' - assert_path_exists extracted + assert_path_exist extracted end if Gem.win_platform? @@ -687,7 +688,7 @@ class TestGemPackage < Gem::Package::TarTestCase package.extract_tar_gz tgz_io, @destination.upcase extracted = File.join @destination, 'foo/file.rb' - assert_path_exists extracted + assert_path_exist extracted end end @@ -706,7 +707,7 @@ class TestGemPackage < Gem::Package::TarTestCase def test_install_location_absolute package = Gem::Package.new @gem - e = assert_raises Gem::Package::PathError do + e = assert_raise Gem::Package::PathError do package.install_location '/absolute.rb', @destination end @@ -747,7 +748,7 @@ class TestGemPackage < Gem::Package::TarTestCase def test_install_location_relative package = Gem::Package.new @gem - e = assert_raises Gem::Package::PathError do + e = assert_raise Gem::Package::PathError do package.install_location '../relative.rb', @destination end @@ -762,7 +763,7 @@ class TestGemPackage < Gem::Package::TarTestCase filename = "../#{File.basename(@destination)}suffix.rb" - e = assert_raises Gem::Package::PathError do + e = assert_raise Gem::Package::PathError do package.install_location filename, @destination end @@ -832,7 +833,7 @@ class TestGemPackage < Gem::Package::TarTestCase package = Gem::Package.new 'mismatch.gem' - e = assert_raises Gem::Package::FormatError do + e = assert_raise Gem::Package::FormatError do package.verify end @@ -856,7 +857,7 @@ class TestGemPackage < Gem::Package::TarTestCase io.write metadata_gz end - digest = Digest::SHA1.new + digest = OpenSSL::Digest::SHA1.new digest << metadata_gz checksums = { @@ -886,7 +887,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_verify_corrupt - skip "jruby strips the null byte and does not think it's corrupt" if Gem.java_platform? + pend "jruby strips the null byte and does not think it's corrupt" if Gem.java_platform? tf = Tempfile.open 'corrupt' do |io| data = Gem::Util.gzip 'a' * 10 io.write \ @@ -896,7 +897,7 @@ class TestGemPackage < Gem::Package::TarTestCase package = Gem::Package.new io.path - e = assert_raises Gem::Package::FormatError do + e = assert_raise Gem::Package::FormatError do package.verify end @@ -912,7 +913,7 @@ class TestGemPackage < Gem::Package::TarTestCase package = Gem::Package.new 'empty.gem' - e = assert_raises Gem::Package::FormatError do + e = assert_raise Gem::Package::FormatError do package.verify end @@ -922,7 +923,7 @@ class TestGemPackage < Gem::Package::TarTestCase def test_verify_nonexistent package = Gem::Package.new 'nonexistent.gem' - e = assert_raises Gem::Package::FormatError do + e = assert_raise Gem::Package::FormatError do package.verify end @@ -937,7 +938,7 @@ class TestGemPackage < Gem::Package::TarTestCase build = Gem::Package.new @gem build.spec = @spec build.setup_signer - open @gem, 'wb' do |gem_io| + File.open @gem, 'wb' do |gem_io| Gem::Package::TarWriter.new gem_io do |gem| build.add_metadata gem build.add_contents gem @@ -949,7 +950,7 @@ class TestGemPackage < Gem::Package::TarTestCase package = Gem::Package.new @gem - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do package.verify end @@ -957,12 +958,12 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_verify_security_policy - skip 'openssl is missing' unless Gem::HAVE_OPENSSL + pend 'openssl is missing' unless Gem::HAVE_OPENSSL package = Gem::Package.new @gem package.security_policy = Gem::Security::HighSecurity - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do package.verify end @@ -974,7 +975,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_verify_security_policy_low_security - skip 'openssl is missing' unless Gem::HAVE_OPENSSL + pend 'openssl is missing' unless Gem::HAVE_OPENSSL @spec.cert_chain = [PUBLIC_CERT.to_pem] @spec.signing_key = PRIVATE_KEY @@ -994,7 +995,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_verify_security_policy_checksum_missing - skip 'openssl is missing' unless Gem::HAVE_OPENSSL + pend 'openssl is missing' unless Gem::HAVE_OPENSSL @spec.cert_chain = [PUBLIC_CERT.to_pem] @spec.signing_key = PRIVATE_KEY @@ -1015,7 +1016,7 @@ class TestGemPackage < Gem::Package::TarTestCase bogus_data = Gem::Util.gzip 'hello' fake_signer = Class.new do def digest_name; 'SHA512'; end - def digest_algorithm; Digest(:SHA512).new; end + def digest_algorithm; OpenSSL::Digest(:SHA512).new; end def key; 'key'; end def sign(*); 'fake_sig'; end end @@ -1032,7 +1033,7 @@ class TestGemPackage < Gem::Package::TarTestCase package = Gem::Package.new @gem package.security_policy = Gem::Security::HighSecurity - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do package.verify end @@ -1049,7 +1050,7 @@ class TestGemPackage < Gem::Package::TarTestCase package = Gem::Package.new 'bad.gem' - e = assert_raises Gem::Package::FormatError do + e = assert_raise Gem::Package::FormatError do package.verify end @@ -1068,8 +1069,8 @@ class TestGemPackage < Gem::Package::TarTestCase _, err = use_ui @ui do e = nil - out_err = capture_io do - e = assert_raises ArgumentError do + out_err = capture_output do + e = assert_raise ArgumentError do package.verify_entry entry end end @@ -1140,11 +1141,18 @@ class TestGemPackage < Gem::Package::TarTestCase def test_spec_from_io_raises_gem_error_for_io_not_at_start io = StringIO.new Gem.read_binary @gem io.read(1) - assert_raises(Gem::Package::Error) do + assert_raise(Gem::Package::Error) do Gem::Package.new io end end + def test_contents_from_io + io = StringIO.new Gem.read_binary @gem + package = Gem::Package.new io + + assert_equal %w[lib/code.rb], package.contents + end + def util_tar tar_io = StringIO.new diff --git a/test/rubygems/test_gem_package_old.rb b/test/rubygems/test_gem_package_old.rb index 8c4c20006b..945340a96a 100644 --- a/test/rubygems/test_gem_package_old.rb +++ b/test/rubygems/test_gem_package_old.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' unless Gem.java_platform? # jruby can't require the simple_gem file require 'rubygems/simple_gem' @@ -23,11 +23,11 @@ unless Gem.java_platform? # jruby can't require the simple_gem file end def test_contents_security_policy - skip 'openssl is missing' unless Gem::HAVE_OPENSSL + pend 'openssl is missing' unless Gem::HAVE_OPENSSL @package.security_policy = Gem::Security::AlmostNoSecurity - assert_raises Gem::Security::Exception do + assert_raise Gem::Security::Exception do @package.contents end end @@ -36,7 +36,7 @@ unless Gem.java_platform? # jruby can't require the simple_gem file @package.extract_files @destination extracted = File.join @destination, 'lib/foo.rb' - assert_path_exists extracted + assert_path_exist extracted mask = 0100644 & (~File.umask) @@ -44,11 +44,11 @@ unless Gem.java_platform? # jruby can't require the simple_gem file end def test_extract_files_security_policy - skip 'openssl is missing' unless Gem::HAVE_OPENSSL + pend 'openssl is missing' unless Gem::HAVE_OPENSSL @package.security_policy = Gem::Security::AlmostNoSecurity - assert_raises Gem::Security::Exception do + assert_raise Gem::Security::Exception do @package.extract_files @destination end end @@ -58,17 +58,17 @@ unless Gem.java_platform? # jruby can't require the simple_gem file end def test_spec_security_policy - skip 'openssl is missing' unless Gem::HAVE_OPENSSL + pend 'openssl is missing' unless Gem::HAVE_OPENSSL @package.security_policy = Gem::Security::AlmostNoSecurity - assert_raises Gem::Security::Exception do + assert_raise Gem::Security::Exception do @package.spec end end def test_verify - skip 'openssl is missing' unless Gem::HAVE_OPENSSL + pend 'openssl is missing' unless Gem::HAVE_OPENSSL assert @package.verify @@ -78,7 +78,7 @@ unless Gem.java_platform? # jruby can't require the simple_gem file @package.security_policy = Gem::Security::AlmostNoSecurity - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @package.verify end diff --git a/test/rubygems/test_gem_package_tar_header.rb b/test/rubygems/test_gem_package_tar_header.rb index da4f5506e9..0e72a72d6a 100644 --- a/test/rubygems/test_gem_package_tar_header.rb +++ b/test/rubygems/test_gem_package_tar_header.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/package/tar_test_case' +require_relative 'package/tar_test_case' require 'rubygems/package' class TestGemPackageTarHeader < Gem::Package::TarTestCase @@ -57,19 +57,19 @@ class TestGemPackageTarHeader < Gem::Package::TarTestCase end def test_initialize_bad - assert_raises ArgumentError do + assert_raise ArgumentError do Gem::Package::TarHeader.new :name => '', :size => '', :mode => '' end - assert_raises ArgumentError do + assert_raise ArgumentError do Gem::Package::TarHeader.new :name => '', :size => '', :prefix => '' end - assert_raises ArgumentError do + assert_raise ArgumentError do Gem::Package::TarHeader.new :name => '', :prefix => '', :mode => '' end - assert_raises ArgumentError do + assert_raise ArgumentError do Gem::Package::TarHeader.new :prefix => '', :size => '', :mode => '' end end @@ -156,7 +156,7 @@ group\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 # overwrite the size field header_s[124, 12] = val io = TempIO.new header_s - assert_raises ArgumentError do + assert_raise ArgumentError do Gem::Package::TarHeader.from io end io.close! diff --git a/test/rubygems/test_gem_package_tar_reader.rb b/test/rubygems/test_gem_package_tar_reader.rb index 05b3ac56a6..277b552f1b 100644 --- a/test/rubygems/test_gem_package_tar_reader.rb +++ b/test/rubygems/test_gem_package_tar_reader.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/package/tar_test_case' +require_relative 'package/tar_test_case' require 'rubygems/package' class TestGemPackageTarReader < Gem::Package::TarTestCase diff --git a/test/rubygems/test_gem_package_tar_reader_entry.rb b/test/rubygems/test_gem_package_tar_reader_entry.rb index 3003e51ac8..1be5870146 100644 --- a/test/rubygems/test_gem_package_tar_reader_entry.rb +++ b/test/rubygems/test_gem_package_tar_reader_entry.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/package/tar_test_case' +require_relative 'package/tar_test_case' require 'rubygems/package' class TestGemPackageTarReaderEntry < Gem::Package::TarTestCase @@ -42,19 +42,19 @@ class TestGemPackageTarReaderEntry < Gem::Package::TarTestCase assert @entry.bytes_read - e = assert_raises(IOError) { @entry.eof? } + e = assert_raise(IOError) { @entry.eof? } assert_equal 'closed Gem::Package::TarReader::Entry', e.message - e = assert_raises(IOError) { @entry.getc } + e = assert_raise(IOError) { @entry.getc } assert_equal 'closed Gem::Package::TarReader::Entry', e.message - e = assert_raises(IOError) { @entry.pos } + e = assert_raise(IOError) { @entry.pos } assert_equal 'closed Gem::Package::TarReader::Entry', e.message - e = assert_raises(IOError) { @entry.read } + e = assert_raise(IOError) { @entry.read } assert_equal 'closed Gem::Package::TarReader::Entry', e.message - e = assert_raises(IOError) { @entry.rewind } + e = assert_raise(IOError) { @entry.rewind } assert_equal 'closed Gem::Package::TarReader::Entry', e.message end @@ -75,10 +75,10 @@ class TestGemPackageTarReaderEntry < Gem::Package::TarTestCase end def test_full_name_null - skip "jruby strips the null byte and does not think it's corrupt" if Gem.java_platform? + pend "jruby strips the null byte and does not think it's corrupt" if Gem.java_platform? @entry.header.prefix << "\000" - e = assert_raises Gem::Package::TarInvalidError do + e = assert_raise Gem::Package::TarInvalidError do @entry.full_name end @@ -134,7 +134,7 @@ class TestGemPackageTarReaderEntry < Gem::Package::TarTestCase end def test_readpartial - assert_raises(EOFError) do + assert_raise(EOFError) do @entry.read(@contents.size) @entry.readpartial(1) end diff --git a/test/rubygems/test_gem_package_tar_writer.rb b/test/rubygems/test_gem_package_tar_writer.rb index 25dac5f148..31a91fa21a 100644 --- a/test/rubygems/test_gem_package_tar_writer.rb +++ b/test/rubygems/test_gem_package_tar_writer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -require 'rubygems/package/tar_test_case' +require_relative 'package/tar_test_case' require 'rubygems/package/tar_writer' -require 'minitest/mock' class TestGemPackageTarWriter < Gem::Package::TarTestCase def setup @@ -117,7 +116,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase end def test_add_file_signer - skip 'openssl is missing' unless Gem::HAVE_OPENSSL + pend 'openssl is missing' unless Gem::HAVE_OPENSSL signer = Gem::Security::Signer.new PRIVATE_KEY, [PUBLIC_CERT] @@ -150,13 +149,12 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase signer = Gem::Security::Signer.new nil, nil Time.stub :now, Time.at(1458518157) do - @tar_writer.add_file_signed 'x', 0644, signer do |io| io.write 'a' * 10 end assert_headers_equal(tar_file_header('x', '', 0644, 10, Time.now), - @io.string[0, 512]) + @io.string[0, 512]) end assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] @@ -170,11 +168,11 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase end assert_headers_equal(tar_file_header('x', '', 0644, 10, Time.now), - @io.string[0, 512]) - end + @io.string[0, 512]) - assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] - assert_equal 1024, @io.pos + assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] + assert_equal 1024, @io.pos + end end def test_add_file_simple_source_date_epoch @@ -185,7 +183,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase end assert_headers_equal(tar_file_header('x', '', 0644, 10, Time.at(ENV["SOURCE_DATE_EPOCH"].to_i).utc), - @io.string[0, 512]) + @io.string[0, 512]) end end @@ -194,7 +192,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase @tar_writer.add_file_simple 'x', 0, 100 assert_headers_equal tar_file_header('x', '', 0, 100, Time.now), - @io.string[0, 512] + @io.string[0, 512] end assert_equal "\0" * 512, @io.string[512, 512] @@ -209,7 +207,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase end def test_add_file_simple_size - assert_raises Gem::Package::TarWriter::FileOverflow do + assert_raise Gem::Package::TarWriter::FileOverflow do @tar_writer.add_file_simple("lib/foo/bar", 0, 10) do |io| io.write "1" * 11 end @@ -221,27 +219,27 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase assert_equal "\0" * 1024, @io.string - e = assert_raises IOError do + e = assert_raise IOError do @tar_writer.close end assert_equal 'closed Gem::Package::TarWriter', e.message - e = assert_raises IOError do + e = assert_raise IOError do @tar_writer.flush end assert_equal 'closed Gem::Package::TarWriter', e.message - e = assert_raises IOError do + e = assert_raise IOError do @tar_writer.add_file 'x', 0 end assert_equal 'closed Gem::Package::TarWriter', e.message - e = assert_raises IOError do + e = assert_raise IOError do @tar_writer.add_file_simple 'x', 0, 0 end assert_equal 'closed Gem::Package::TarWriter', e.message - e = assert_raises IOError do + e = assert_raise IOError do @tar_writer.mkdir 'x', 0 end assert_equal 'closed Gem::Package::TarWriter', e.message @@ -297,7 +295,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase assert_equal ['b' * 100, 'a'], @tar_writer.split_name(name) name = File.join 'a', 'b' * 101 - exception = assert_raises Gem::Package::TooLongFileName do + exception = assert_raise Gem::Package::TooLongFileName do @tar_writer.split_name name end assert_includes exception.message, name @@ -305,7 +303,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase # note, GNU tar 1.28 is unable to handle this case too, # tested with "tar --format=ustar -cPf /tmp/foo.tartar -- /aaaaaa....a" name = '/' + 'a' * 100 - exception = assert_raises Gem::Package::TooLongFileName do + exception = assert_raise Gem::Package::TooLongFileName do @tar_writer.split_name name end assert_includes exception.message, name @@ -316,7 +314,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase assert_equal ['b', 'a' * 155], @tar_writer.split_name(name) name = File.join 'a' * 156, 'b' - exception = assert_raises Gem::Package::TooLongFileName do + exception = assert_raise Gem::Package::TooLongFileName do @tar_writer.split_name name end assert_includes exception.message, name @@ -324,7 +322,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase def test_split_name_too_long_total name = 'a' * 257 - exception = assert_raises Gem::Package::TooLongFileName do + exception = assert_raise Gem::Package::TooLongFileName do @tar_writer.split_name name end assert_includes exception.message, name diff --git a/test/rubygems/test_gem_package_task.rb b/test/rubygems/test_gem_package_task.rb index ee9b8d44d4..0cedfe56eb 100644 --- a/test/rubygems/test_gem_package_task.rb +++ b/test/rubygems/test_gem_package_task.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems' begin @@ -40,7 +40,7 @@ class TestGemPackageTask < Gem::TestCase Rake.application['package'].invoke - assert_path_exists 'pkg/pkgr-1.2.3.gem' + assert_path_exist 'pkg/pkgr-1.2.3.gem' end ensure RakeFileUtils.verbose_flag = original_rake_fileutils_verbosity @@ -56,7 +56,7 @@ class TestGemPackageTask < Gem::TestCase g.summary = 'summary' end - _, err = capture_io do + _, err = capture_output do Rake.application = Rake::Application.new pkg = Gem::PackageTask.new(gem) do |p| diff --git a/test/rubygems/test_gem_path_support.rb b/test/rubygems/test_gem_path_support.rb index f24041a2d8..88a3cc29b9 100644 --- a/test/rubygems/test_gem_path_support.rb +++ b/test/rubygems/test_gem_path_support.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems' require 'fileutils' @@ -130,7 +130,7 @@ class TestGemPathSupport < Gem::TestCase begin File.symlink(dir, symlink) rescue NotImplementedError, SystemCallError - skip 'symlinks not supported' + pend 'symlinks not supported' end not_existing = "#{@tempdir}/does_not_exist" path = "#{symlink}#{File::PATH_SEPARATOR}#{not_existing}" diff --git a/test/rubygems/test_gem_platform.rb b/test/rubygems/test_gem_platform.rb index 84754402ad..8029035db1 100644 --- a/test/rubygems/test_gem_platform.rb +++ b/test/rubygems/test_gem_platform.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/platform' require 'rbconfig' @@ -122,6 +122,7 @@ class TestGemPlatform < Gem::TestCase 'i586-linux-gnu' => ['x86', 'linux', nil], 'i386-linux-gnu' => ['x86', 'linux', nil], 'i386-mingw32' => ['x86', 'mingw32', nil], + 'x64-mingw-ucrt' => ['x64', 'mingw', 'ucrt'], 'i386-mswin32' => ['x86', 'mswin32', nil], 'i386-mswin32_80' => ['x86', 'mswin32', '80'], 'i386-mswin32-80' => ['x86', 'mswin32', '80'], diff --git a/test/rubygems/test_gem_rdoc.rb b/test/rubygems/test_gem_rdoc.rb index 13fd4ba40b..7b8ad07b27 100644 --- a/test/rubygems/test_gem_rdoc.rb +++ b/test/rubygems/test_gem_rdoc.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true require 'rubygems' -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/rdoc' class TestGemRDoc < Gem::TestCase @@ -24,7 +24,7 @@ class TestGemRDoc < Gem::TestCase begin Gem::RDoc.load_rdoc rescue Gem::DocumentError => e - skip e.message + pend e.message end Gem.configuration[:rdoc] = nil @@ -84,16 +84,16 @@ class TestGemRDoc < Gem::TestCase refute @hook.rdoc_installed? refute @hook.ri_installed? - assert_path_exists @a.doc_dir + assert_path_exist @a.doc_dir end def test_remove_unwritable - skip 'chmod not supported' if Gem.win_platform? - skip 'skipped in root privilege' if Process.uid.zero? + pend 'chmod not supported' if Gem.win_platform? + pend 'skipped in root privilege' if Process.uid.zero? FileUtils.mkdir_p @a.base_dir FileUtils.chmod 0, @a.base_dir - e = assert_raises Gem::FilePermissionError do + e = assert_raise Gem::FilePermissionError do @hook.remove end @@ -113,16 +113,16 @@ class TestGemRDoc < Gem::TestCase def test_setup @hook.setup - assert_path_exists @a.doc_dir + assert_path_exist @a.doc_dir end def test_setup_unwritable - skip 'chmod not supported' if Gem.win_platform? - skip 'skipped in root privilege' if Process.uid.zero? + pend 'chmod not supported' if Gem.win_platform? + pend 'skipped in root privilege' if Process.uid.zero? FileUtils.mkdir_p @a.doc_dir FileUtils.chmod 0, @a.doc_dir - e = assert_raises Gem::FilePermissionError do + e = assert_raise Gem::FilePermissionError do @hook.setup end diff --git a/test/rubygems/test_gem_remote_fetcher.rb b/test/rubygems/test_gem_remote_fetcher.rb index 1c88e8d3e8..5ce420b91a 100644 --- a/test/rubygems/test_gem_remote_fetcher.rb +++ b/test/rubygems/test_gem_remote_fetcher.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'webrick' require 'webrick/https' if Gem::HAVE_OPENSSL @@ -10,7 +10,6 @@ end require 'rubygems/remote_fetcher' require 'rubygems/package' -require 'minitest/mock' # = Testing Proxy Settings # @@ -146,7 +145,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== fetcher = Gem::RemoteFetcher.new nil @fetcher = fetcher - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do @fetcher.fetch_path("gems.example.com/yaml", nil, true) end @@ -184,7 +183,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== assert_equal 'hello', data - refute_path_exists path + assert_path_not_exist path end def util_fuck_with_fetcher(data, blow = false) @@ -242,6 +241,36 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== assert File.exist?(a1_cache_gem) end + def test_download_with_token + a1_data = nil + File.open @a1_gem, 'rb' do |fp| + a1_data = fp.read + end + + fetcher = util_fuck_with_fetcher a1_data + + a1_cache_gem = @a1.cache_file + assert_equal a1_cache_gem, fetcher.download(@a1, 'http://token@gems.example.com') + assert_equal("http://token@gems.example.com/gems/a-1.gem", + fetcher.instance_variable_get(:@test_arg).to_s) + assert File.exist?(a1_cache_gem) + end + + def test_download_with_x_oauth_basic + a1_data = nil + File.open @a1_gem, 'rb' do |fp| + a1_data = fp.read + end + + fetcher = util_fuck_with_fetcher a1_data + + a1_cache_gem = @a1.cache_file + assert_equal a1_cache_gem, fetcher.download(@a1, 'http://token:x-oauth-basic@gems.example.com') + assert_equal("http://token:x-oauth-basic@gems.example.com/gems/a-1.gem", + fetcher.instance_variable_get(:@test_arg).to_s) + assert File.exist?(a1_cache_gem) + end + def test_download_with_encoded_auth a1_data = nil File.open @a1_gem, 'rb' do |fp| @@ -390,7 +419,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== def test_download_unsupported inst = Gem::RemoteFetcher.fetcher - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do inst.download @a1, 'ftp://gems.rubyforge.org' end @@ -451,7 +480,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== url = 'http://example.com/uri' - e = assert_raises Gem::RemoteFetcher::FetchError do + e = assert_raise Gem::RemoteFetcher::FetchError do fetcher.fetch_path url end @@ -469,7 +498,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== url = 'http://example.com/uri' - e = assert_raises Gem::RemoteFetcher::FetchError do + e = assert_raise Gem::RemoteFetcher::FetchError do fetcher.fetch_path url end @@ -487,7 +516,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== url = 'http://example.com/uri' - e = assert_raises Gem::RemoteFetcher::FetchError do + e = assert_raise Gem::RemoteFetcher::FetchError do fetcher.fetch_path url end @@ -496,6 +525,44 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== assert_equal url, e.uri end + def test_fetch_path_timeout_error + fetcher = Gem::RemoteFetcher.new nil + @fetcher = fetcher + + def fetcher.fetch_http(uri, mtime = nil, head = nil) + raise Timeout::Error, 'timed out' + end + + url = 'http://example.com/uri' + + e = assert_raise Gem::RemoteFetcher::FetchError do + fetcher.fetch_path url + end + + assert_match %r{Timeout::Error: timed out \(#{Regexp.escape url}\)\z}, + e.message + assert_equal url, e.uri + end + + def test_fetch_path_getaddrinfo_error + fetcher = Gem::RemoteFetcher.new nil + @fetcher = fetcher + + def fetcher.fetch_http(uri, mtime = nil, head = nil) + raise SocketError, 'getaddrinfo: nodename nor servname provided' + end + + url = 'http://example.com/uri' + + e = assert_raise Gem::RemoteFetcher::FetchError do + fetcher.fetch_path url + end + + assert_match %r{SocketError: getaddrinfo: nodename nor servname provided \(#{Regexp.escape url}\)\z}, + e.message + assert_equal url, e.uri + end + def test_fetch_path_openssl_ssl_sslerror fetcher = Gem::RemoteFetcher.new nil @fetcher = fetcher @@ -506,7 +573,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== url = 'http://example.com/uri' - e = assert_raises Gem::RemoteFetcher::FetchError do + e = assert_raise Gem::RemoteFetcher::FetchError do fetcher.fetch_path url end @@ -596,7 +663,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== res end - e = assert_raises Gem::RemoteFetcher::FetchError do + e = assert_raise Gem::RemoteFetcher::FetchError do fetcher.fetch_http URI.parse(url) end @@ -613,7 +680,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== res end - e = assert_raises Gem::RemoteFetcher::FetchError do + e = assert_raise Gem::RemoteFetcher::FetchError do fetcher.fetch_http URI.parse(url) end @@ -798,7 +865,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== fetcher = Gem::RemoteFetcher.new nil @fetcher = fetcher - e = assert_raises Gem::RemoteFetcher::FetchError do + e = assert_raise Gem::RemoteFetcher::FetchError do fetcher.fetch_s3 URI.parse(url) end @@ -914,7 +981,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== with_configured_fetcher( ":ssl_ca_cert: #{temp_ca_cert}\n" + ":ssl_client_cert: #{temp_client_cert}\n") do |fetcher| - assert_raises Gem::RemoteFetcher::FetchError do + assert_raise Gem::RemoteFetcher::FetchError do fetcher.fetch_path("https://localhost:#{ssl_server.config[:Port]}/yaml") end end @@ -923,7 +990,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== def test_do_not_allow_insecure_ssl_connection_by_default ssl_server = start_ssl_server with_configured_fetcher do |fetcher| - assert_raises Gem::RemoteFetcher::FetchError do + assert_raise Gem::RemoteFetcher::FetchError do fetcher.fetch_path("https://localhost:#{ssl_server.config[:Port]}/yaml") end end @@ -943,7 +1010,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== "redirecting to non-https resource: #{@server_uri} (https://localhost:#{ssl_server.config[:Port]}/insecure_redirect?to=#{@server_uri})" with_configured_fetcher(":ssl_ca_cert: #{temp_ca_cert}") do |fetcher| - err = assert_raises Gem::RemoteFetcher::FetchError do + err = assert_raise Gem::RemoteFetcher::FetchError do fetcher.fetch_path("https://localhost:#{ssl_server.config[:Port]}/insecure_redirect?to=#{@server_uri}") end @@ -956,7 +1023,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== temp_ca_cert = nil with_configured_fetcher(":ssl_ca_cert: #{temp_ca_cert}") do |fetcher| - assert_raises Gem::RemoteFetcher::FetchError do + assert_raise Gem::RemoteFetcher::FetchError do fetcher.fetch_path("https://localhost:#{ssl_server.config[:Port]}") end end @@ -1046,7 +1113,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== end def start_ssl_server(config = {}) - skip "starting this test server fails randomly on jruby" if Gem.java_platform? + pend "starting this test server fails randomly on jruby" if Gem.java_platform? null_logger = NilLog.new server = WEBrick::HTTPServer.new({ diff --git a/test/rubygems/test_gem_request.rb b/test/rubygems/test_gem_request.rb index 47e5f97074..47654f6fa4 100644 --- a/test/rubygems/test_gem_request.rb +++ b/test/rubygems/test_gem_request.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/request' require 'ostruct' require 'base64' @@ -185,7 +185,7 @@ class TestGemRequest < Gem::TestCase end def test_fetch - uri = URI.parse "#{@gem_repo}/specs.#{Gem.marshal_version}" + uri = Gem::Uri.new(URI.parse "#{@gem_repo}/specs.#{Gem.marshal_version}") response = util_stub_net_http(:body => :junk, :code => 200) do @request = make_request(uri, Net::HTTP::Get, nil, nil) @@ -197,31 +197,57 @@ class TestGemRequest < Gem::TestCase end def test_fetch_basic_auth - uri = URI.parse "https://user:pass@example.rubygems/specs.#{Gem.marshal_version}" + Gem.configuration.verbose = :really + uri = Gem::Uri.new(URI.parse "https://user:pass@example.rubygems/specs.#{Gem.marshal_version}") conn = util_stub_net_http(:body => :junk, :code => 200) do |c| - @request = make_request(uri, Net::HTTP::Get, nil, nil) - @request.fetch + use_ui @ui do + @request = make_request(uri, Net::HTTP::Get, nil, nil) + @request.fetch + end c end auth_header = conn.payload['Authorization'] assert_equal "Basic #{Base64.encode64('user:pass')}".strip, auth_header + assert_includes @ui.output, "GET https://user:REDACTED@example.rubygems/specs.#{Gem.marshal_version}" end def test_fetch_basic_auth_encoded - uri = URI.parse "https://user:%7BDEScede%7Dpass@example.rubygems/specs.#{Gem.marshal_version}" + Gem.configuration.verbose = :really + uri = Gem::Uri.new(URI.parse "https://user:%7BDEScede%7Dpass@example.rubygems/specs.#{Gem.marshal_version}") + conn = util_stub_net_http(:body => :junk, :code => 200) do |c| - @request = make_request(uri, Net::HTTP::Get, nil, nil) - @request.fetch + use_ui @ui do + @request = make_request(uri, Net::HTTP::Get, nil, nil) + @request.fetch + end c end auth_header = conn.payload['Authorization'] assert_equal "Basic #{Base64.encode64('user:{DEScede}pass')}".strip, auth_header + assert_includes @ui.output, "GET https://user:REDACTED@example.rubygems/specs.#{Gem.marshal_version}" + end + + def test_fetch_basic_oauth_encoded + Gem.configuration.verbose = :really + uri = Gem::Uri.new(URI.parse "https://%7BDEScede%7Dpass:x-oauth-basic@example.rubygems/specs.#{Gem.marshal_version}") + + conn = util_stub_net_http(:body => :junk, :code => 200) do |c| + use_ui @ui do + @request = make_request(uri, Net::HTTP::Get, nil, nil) + @request.fetch + end + c + end + + auth_header = conn.payload['Authorization'] + assert_equal "Basic #{Base64.encode64('{DEScede}pass:x-oauth-basic')}".strip, auth_header + assert_includes @ui.output, "GET https://REDACTED:x-oauth-basic@example.rubygems/specs.#{Gem.marshal_version}" end def test_fetch_head - uri = URI.parse "#{@gem_repo}/specs.#{Gem.marshal_version}" + uri = Gem::Uri.new(URI.parse "#{@gem_repo}/specs.#{Gem.marshal_version}") response = util_stub_net_http(:body => '', :code => 200) do |conn| @request = make_request(uri, Net::HTTP::Get, nil, nil) @request.fetch @@ -232,7 +258,7 @@ class TestGemRequest < Gem::TestCase end def test_fetch_unmodified - uri = URI.parse "#{@gem_repo}/specs.#{Gem.marshal_version}" + uri = Gem::Uri.new(URI.parse "#{@gem_repo}/specs.#{Gem.marshal_version}") t = Time.utc(2013, 1, 2, 3, 4, 5) conn, response = util_stub_net_http(:body => '', :code => 304) do |c| @request = make_request(uri, Net::HTTP::Get, t, nil) @@ -327,31 +353,37 @@ class TestGemRequest < Gem::TestCase end def test_verify_certificate - skip if Gem.java_platform? + pend if Gem.java_platform? + + error_number = OpenSSL::X509::V_ERR_OUT_OF_MEM + store = OpenSSL::X509::Store.new context = OpenSSL::X509::StoreContext.new store - context.error = OpenSSL::X509::V_ERR_OUT_OF_MEM + context.error = error_number use_ui @ui do Gem::Request.verify_certificate context end - assert_equal "ERROR: SSL verification error at depth 0: out of memory (17)\n", + assert_equal "ERROR: SSL verification error at depth 0: out of memory (#{error_number})\n", @ui.error end def test_verify_certificate_extra_message - skip if Gem.java_platform? + pend if Gem.java_platform? + + error_number = OpenSSL::X509::V_ERR_INVALID_CA + store = OpenSSL::X509::Store.new context = OpenSSL::X509::StoreContext.new store - context.error = OpenSSL::X509::V_ERR_INVALID_CA + context.error = error_number use_ui @ui do Gem::Request.verify_certificate context end expected = <<-ERROR -ERROR: SSL verification error at depth 0: invalid CA certificate (24) +ERROR: SSL verification error at depth 0: invalid CA certificate (#{error_number}) ERROR: Certificate is an invalid CA certificate ERROR diff --git a/test/rubygems/test_gem_request_connection_pools.rb b/test/rubygems/test_gem_request_connection_pools.rb index 2bd2d28469..c5e7cf2ed4 100644 --- a/test/rubygems/test_gem_request_connection_pools.rb +++ b/test/rubygems/test_gem_request_connection_pools.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/request' require 'timeout' @@ -140,7 +140,7 @@ class TestGemRequestConnectionPool < Gem::TestCase pool.checkout Thread.new do - assert_raises(Timeout::Error) do + assert_raise(Timeout::Error) do Timeout.timeout(1) do pool.checkout end diff --git a/test/rubygems/test_gem_request_set.rb b/test/rubygems/test_gem_request_set.rb index 54ae7720c0..e2fe98b9bf 100644 --- a/test/rubygems/test_gem_request_set.rb +++ b/test/rubygems/test_gem_request_set.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/request_set' class TestGemRequestSet < Gem::TestCase @@ -64,8 +64,8 @@ class TestGemRequestSet < Gem::TestCase end assert_includes installed, 'a-2' - assert_path_exists File.join @gemhome, 'gems', 'a-2' - assert_path_exists 'gem.deps.rb.lock' + assert_path_exist File.join @gemhome, 'gems', 'a-2' + assert_path_exist 'gem.deps.rb.lock' assert rs.remote refute done_installing_ran @@ -87,9 +87,10 @@ Gems to install: a-2 EXPECTED - assert_output expected do + actual, _ = capture_output do rs.install_from_gemdeps :gemdeps => io.path, :explain => true end + assert_equal(expected, actual) end end @@ -99,7 +100,7 @@ Gems to install: end util_clear_gems - refute_path_exists File.join Gem.dir, 'gems', 'a-2' + assert_path_not_exist File.join Gem.dir, 'gems', 'a-2' rs = Gem::RequestSet.new installed = [] @@ -118,7 +119,7 @@ Gems to install: end assert_includes installed, 'a-2' - refute_path_exists File.join Gem.dir, 'gems', 'a-2' + assert_path_not_exist File.join Gem.dir, 'gems', 'a-2' end def test_install_from_gemdeps_local @@ -132,7 +133,7 @@ Gems to install: io.puts 'gem "a"' io.flush - assert_raises Gem::UnsatisfiableDependencyError do + assert_raise Gem::UnsatisfiableDependencyError do rs.install_from_gemdeps :gemdeps => io.path, :domain => :local end end @@ -178,8 +179,8 @@ DEPENDENCIES assert_includes installed, 'b-1' assert_includes installed, 'a-1' - assert_path_exists File.join @gemhome, 'specifications', 'a-1.gemspec' - assert_path_exists File.join @gemhome, 'specifications', 'b-1.gemspec' + assert_path_exist File.join @gemhome, 'specifications', 'a-1.gemspec' + assert_path_exist File.join @gemhome, 'specifications', 'b-1.gemspec' end def test_install_from_gemdeps_complex_dependencies @@ -231,7 +232,7 @@ end assert_includes installed, 'z-1.0.3' - assert_path_exists File.join @gemhome, 'specifications', 'z-1.0.3.gemspec' + assert_path_exist File.join @gemhome, 'specifications', 'z-1.0.3.gemspec' end def test_install_from_gemdeps_version_mismatch @@ -443,7 +444,7 @@ ruby "0" set = StaticSet.new [a1, a2] - assert_raises Gem::UnsatisfiableDependencyError do + assert_raise Gem::UnsatisfiableDependencyError do rs.resolve set end end @@ -524,8 +525,8 @@ ruby "0" assert_equal %w[b-1 a-1], installers.map {|installer| installer.spec.full_name } - assert_path_exists File.join @gemhome, 'specifications', 'a-1.gemspec' - assert_path_exists File.join @gemhome, 'specifications', 'b-1.gemspec' + assert_path_exist File.join @gemhome, 'specifications', 'a-1.gemspec' + assert_path_exist File.join @gemhome, 'specifications', 'b-1.gemspec' assert_equal %w[b-1 a-1], installed.map {|s| s.full_name } @@ -547,8 +548,8 @@ ruby "0" assert_equal @tempdir, ENV['GEM_HOME'] end - assert_path_exists File.join @tempdir, 'specifications', 'a-1.gemspec' - assert_path_exists File.join @tempdir, 'specifications', 'b-1.gemspec' + assert_path_exist File.join @tempdir, 'specifications', 'a-1.gemspec' + assert_path_exist File.join @tempdir, 'specifications', 'b-1.gemspec' assert_equal %w[b-1 a-1], installed.map {|s| s.full_name } end diff --git a/test/rubygems/test_gem_request_set_gem_dependency_api.rb b/test/rubygems/test_gem_request_set_gem_dependency_api.rb index 2a9663959c..cc4f99df91 100644 --- a/test/rubygems/test_gem_request_set_gem_dependency_api.rb +++ b/test/rubygems/test_gem_request_set_gem_dependency_api.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/request_set' class TestGemRequestSetGemDependencyAPI < Gem::TestCase @@ -81,7 +81,7 @@ class TestGemRequestSetGemDependencyAPI < Gem::TestCase def test_gem_duplicate @gda.gem 'a' - _, err = capture_io do + _, err = capture_output do @gda.gem 'a' end @@ -129,7 +129,7 @@ class TestGemRequestSetGemDependencyAPI < Gem::TestCase end def test_gem_git_branch - _, err = capture_io do + _, err = capture_output do @gda.gem 'a', :git => 'git/a', :branch => 'other', :tag => 'v1' end expected = "Gem dependencies file gem.deps.rb includes git reference for both ref/branch and tag but only ref/branch is used." @@ -150,7 +150,7 @@ class TestGemRequestSetGemDependencyAPI < Gem::TestCase end def test_gem_git_ref - _, err = capture_io do + _, err = capture_output do @gda.gem 'a', :git => 'git/a', :ref => 'abcd123', :branch => 'other' end expected = "Gem dependencies file gem.deps.rb includes git reference for both ref and branch but only ref is used." @@ -388,7 +388,7 @@ class TestGemRequestSetGemDependencyAPI < Gem::TestCase end def test_gem_platforms_unknown - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do @gda.gem 'a', :platforms => :unknown end @@ -455,7 +455,7 @@ class TestGemRequestSetGemDependencyAPI < Gem::TestCase gda = @GDA.new @set, nil gda.gem name - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do gda.gem name, :path => directory end @@ -466,7 +466,7 @@ class TestGemRequestSetGemDependencyAPI < Gem::TestCase gda.instance_variable_set :@vendor_set, @vendor_set gda.gem name, :path => directory - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do gda.gem name end @@ -517,8 +517,8 @@ class TestGemRequestSetGemDependencyAPI < Gem::TestCase def test_gemspec_bad FileUtils.touch 'a.gemspec' - e = assert_raises ArgumentError do - capture_io do + e = assert_raise ArgumentError do + capture_output do @gda.gemspec end end @@ -550,7 +550,7 @@ class TestGemRequestSetGemDependencyAPI < Gem::TestCase s.add_dependency 'c', 3 end - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do @gda.gemspec end @@ -582,7 +582,7 @@ class TestGemRequestSetGemDependencyAPI < Gem::TestCase end def test_gemspec_none - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do @gda.gemspec end @@ -658,14 +658,14 @@ end gda.send :pin_gem_source, 'a' gda.send :pin_gem_source, 'a' - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do gda.send :pin_gem_source, 'a', :path, 'vendor/a' end assert_equal "duplicate source path: vendor/a for gem a", e.message - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do gda.send :pin_gem_source, 'a', :git, 'git://example/repo.git' end @@ -770,7 +770,7 @@ end def test_ruby_engine_mismatch_engine with_engine_version 'ruby', '2.0.0' do - e = assert_raises Gem::RubyVersionMismatch do + e = assert_raise Gem::RubyVersionMismatch do @gda.ruby RUBY_VERSION, :engine => 'jruby', :engine_version => '1.7.4' end @@ -781,7 +781,7 @@ end def test_ruby_engine_mismatch_version with_engine_version 'jruby', '1.7.6' do - e = assert_raises Gem::RubyVersionMismatch do + e = assert_raise Gem::RubyVersionMismatch do @gda.ruby RUBY_VERSION, :engine => 'jruby', :engine_version => '1.7.4' end @@ -791,7 +791,7 @@ end end def test_ruby_engine_no_engine_version - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do @gda.ruby RUBY_VERSION, :engine => 'jruby' end @@ -800,7 +800,7 @@ end end def test_ruby_mismatch - e = assert_raises Gem::RubyVersionMismatch do + e = assert_raise Gem::RubyVersionMismatch do @gda.ruby '1.8.0' end diff --git a/test/rubygems/test_gem_request_set_lockfile.rb b/test/rubygems/test_gem_request_set_lockfile.rb index 44a47f2e00..690b632d9b 100644 --- a/test/rubygems/test_gem_request_set_lockfile.rb +++ b/test/rubygems/test_gem_request_set_lockfile.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/request_set' require 'rubygems/request_set/lockfile' @@ -443,7 +443,7 @@ DEPENDENCIES gem_deps_lock_file = "#{@gem_deps_file}.lock" - assert_path_exists gem_deps_lock_file + assert_path_exist gem_deps_lock_file refute_empty File.read gem_deps_lock_file end @@ -457,11 +457,11 @@ DEPENDENCIES io.write 'hello' end - assert_raises Gem::UnsatisfiableDependencyError do + assert_raise Gem::UnsatisfiableDependencyError do lockfile.write end - assert_path_exists gem_deps_lock_file + assert_path_exist gem_deps_lock_file assert_equal 'hello', File.read(gem_deps_lock_file) end diff --git a/test/rubygems/test_gem_request_set_lockfile_parser.rb b/test/rubygems/test_gem_request_set_lockfile_parser.rb index 9a42d81d0e..4007c3a69c 100644 --- a/test/rubygems/test_gem_request_set_lockfile_parser.rb +++ b/test/rubygems/test_gem_request_set_lockfile_parser.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/request_set' require 'rubygems/request_set/lockfile' require 'rubygems/request_set/lockfile/tokenizer' @@ -25,7 +25,7 @@ class TestGemRequestSetLockfileParser < Gem::TestCase tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "foo", filename, 1, 0 parser = tokenizer.make_parser nil, nil - e = assert_raises Gem::RequestSet::Lockfile::ParseError do + e = assert_raise Gem::RequestSet::Lockfile::ParseError do parser.get :section end @@ -52,7 +52,7 @@ class TestGemRequestSetLockfileParser < Gem::TestCase tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "x", filename, 1 parser = tokenizer.make_parser nil, nil - e = assert_raises Gem::RequestSet::Lockfile::ParseError do + e = assert_raise Gem::RequestSet::Lockfile::ParseError do parser.get :text, 'y' end @@ -518,7 +518,7 @@ DEPENDENCIES end def test_parse_missing - assert_raises(Errno::ENOENT) do + assert_raise(Errno::ENOENT) do parse_lockfile @set, [] end diff --git a/test/rubygems/test_gem_request_set_lockfile_tokenizer.rb b/test/rubygems/test_gem_request_set_lockfile_tokenizer.rb index b485b2c0b7..f779c33012 100644 --- a/test/rubygems/test_gem_request_set_lockfile_tokenizer.rb +++ b/test/rubygems/test_gem_request_set_lockfile_tokenizer.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/request_set' require 'rubygems/request_set/lockfile' require 'rubygems/request_set/lockfile/tokenizer' @@ -193,7 +193,7 @@ DEPENDENCIES def test_tokenize_conflict_markers write_lockfile '<<<<<<<' - e = assert_raises Gem::RequestSet::Lockfile::ParseError do + e = assert_raise Gem::RequestSet::Lockfile::ParseError do tokenize_lockfile end @@ -202,7 +202,7 @@ DEPENDENCIES write_lockfile '|||||||' - e = assert_raises Gem::RequestSet::Lockfile::ParseError do + e = assert_raise Gem::RequestSet::Lockfile::ParseError do tokenize_lockfile end @@ -211,7 +211,7 @@ DEPENDENCIES write_lockfile '=======' - e = assert_raises Gem::RequestSet::Lockfile::ParseError do + e = assert_raise Gem::RequestSet::Lockfile::ParseError do tokenize_lockfile end @@ -220,7 +220,7 @@ DEPENDENCIES write_lockfile '>>>>>>>' - e = assert_raises Gem::RequestSet::Lockfile::ParseError do + e = assert_raise Gem::RequestSet::Lockfile::ParseError do tokenize_lockfile end diff --git a/test/rubygems/test_gem_requirement.rb b/test/rubygems/test_gem_requirement.rb index aa4c57ac27..b20b3cc0dc 100644 --- a/test/rubygems/test_gem_requirement.rb +++ b/test/rubygems/test_gem_requirement.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require "rubygems/requirement" class TestGemRequirement < Gem::TestCase @@ -97,7 +97,7 @@ class TestGemRequirement < Gem::TestCase '= junk', '1..2', ].each do |bad| - e = assert_raises Gem::Requirement::BadRequirementError do + e = assert_raise Gem::Requirement::BadRequirementError do Gem::Requirement.parse bad end @@ -128,7 +128,7 @@ class TestGemRequirement < Gem::TestCase refute_satisfied_by "1.2", r assert_satisfied_by "1.3", r - assert_raises ArgumentError do + assert_raise ArgumentError do assert_satisfied_by nil, r end end @@ -140,7 +140,7 @@ class TestGemRequirement < Gem::TestCase assert_satisfied_by "1.2", r refute_satisfied_by "1.3", r - assert_raises ArgumentError do + assert_raise ArgumentError do assert_satisfied_by nil, r end end @@ -152,7 +152,7 @@ class TestGemRequirement < Gem::TestCase assert_satisfied_by "1.2", r refute_satisfied_by "1.3", r - assert_raises ArgumentError do + assert_raise ArgumentError do assert_satisfied_by nil, r end end @@ -164,7 +164,7 @@ class TestGemRequirement < Gem::TestCase refute_satisfied_by "1.2", r assert_satisfied_by "1.3", r - assert_raises ArgumentError do + assert_raise ArgumentError do r.satisfied_by? nil end end @@ -176,7 +176,7 @@ class TestGemRequirement < Gem::TestCase assert_satisfied_by "1.2", r assert_satisfied_by "1.3", r - assert_raises ArgumentError do + assert_raise ArgumentError do r.satisfied_by? nil end end @@ -188,7 +188,7 @@ class TestGemRequirement < Gem::TestCase assert_satisfied_by "1.2", r refute_satisfied_by "1.3", r - assert_raises ArgumentError do + assert_raise ArgumentError do r.satisfied_by? nil end end @@ -200,7 +200,7 @@ class TestGemRequirement < Gem::TestCase refute_satisfied_by "1.2", r refute_satisfied_by "1.3", r - assert_raises ArgumentError do + assert_raise ArgumentError do r.satisfied_by? nil end end @@ -212,7 +212,7 @@ class TestGemRequirement < Gem::TestCase assert_satisfied_by "1.2", r refute_satisfied_by "1.3", r - assert_raises ArgumentError do + assert_raise ArgumentError do r.satisfied_by? nil end end @@ -224,7 +224,7 @@ class TestGemRequirement < Gem::TestCase assert_satisfied_by "1.2", r assert_satisfied_by "1.3", r - assert_raises ArgumentError do + assert_raise ArgumentError do r.satisfied_by? nil end end @@ -282,18 +282,18 @@ class TestGemRequirement < Gem::TestCase def test_illformed_requirements [ ">>> 1.3.5", "> blah" ].each do |rq| - assert_raises Gem::Requirement::BadRequirementError, "req [#{rq}] should fail" do + assert_raise Gem::Requirement::BadRequirementError, "req [#{rq}] should fail" do Gem::Requirement.new rq end end end def test_satisfied_by_eh_non_versions - assert_raises ArgumentError do + assert_raise ArgumentError do req(">= 0").satisfied_by? Object.new end - assert_raises ArgumentError do + assert_raise ArgumentError do req(">= 0").satisfied_by? Gem::Requirement.default end end @@ -423,6 +423,40 @@ class TestGemRequirement < Gem::TestCase assert_requirement_hash_equal "1", "1.0.0" end + class Exploit < RuntimeError + end + + def self.exploit(arg) + raise Exploit, "arg = #{arg}" + end + + def test_marshal_load_attack + wa = Net::WriteAdapter.allocate + wa.instance_variable_set(:@socket, self.class) + wa.instance_variable_set(:@method_id, :exploit) + request_set = Gem::RequestSet.allocate + request_set.instance_variable_set(:@git_set, "id") + request_set.instance_variable_set(:@sets, wa) + wa = Net::WriteAdapter.allocate + wa.instance_variable_set(:@socket, request_set) + wa.instance_variable_set(:@method_id, :resolve) + ent = Gem::Package::TarReader::Entry.allocate + ent.instance_variable_set(:@read, 0) + ent.instance_variable_set(:@header, "aaa") + io = Net::BufferedIO.allocate + io.instance_variable_set(:@io, ent) + io.instance_variable_set(:@debug_output, wa) + reader = Gem::Package::TarReader.allocate + reader.instance_variable_set(:@io, io) + requirement = Gem::Requirement.allocate + requirement.instance_variable_set(:@requirements, reader) + m = [Gem::SpecFetcher, Gem::Installer, requirement] + e = assert_raise(TypeError) do + Marshal.load(Marshal.dump(m)) + end + assert_equal(e.message, "wrong @requirements") + end + # Assert that two requirements are equal. Handles Gem::Requirements, # strings, arrays, numbers, and versions. diff --git a/test/rubygems/test_gem_resolver.rb b/test/rubygems/test_gem_resolver.rb index 09feeac55b..ea9f9049ce 100644 --- a/test/rubygems/test_gem_resolver.rb +++ b/test/rubygems/test_gem_resolver.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolver < Gem::TestCase def setup @@ -76,7 +76,7 @@ class TestGemResolver < Gem::TestCase assert_same index_set, composed - e = assert_raises ArgumentError do + e = assert_raise ArgumentError do @DR.compose_sets nil end @@ -266,14 +266,14 @@ class TestGemResolver < Gem::TestCase res = Gem::Resolver.new [a_dep], Gem::Resolver::IndexSet.new - e = assert_raises Gem::UnsatisfiableDepedencyError do + e = assert_raise Gem::UnsatisfiableDependencyError do res.resolve end refute_empty e.errors end - def test_no_overlap_specificly + def test_no_overlap_specifically a = util_spec "a", '1' b = util_spec "b", "1" @@ -445,7 +445,7 @@ class TestGemResolver < Gem::TestCase r = Gem::Resolver.new([ad, bd], s) - e = assert_raises Gem::DependencyResolutionError do + e = assert_raise Gem::DependencyResolutionError do r.resolve end @@ -469,7 +469,7 @@ class TestGemResolver < Gem::TestCase r = Gem::Resolver.new([ad], set) - e = assert_raises Gem::UnsatisfiableDepedencyError do + e = assert_raise Gem::UnsatisfiableDependencyError do r.resolve end @@ -486,7 +486,7 @@ class TestGemResolver < Gem::TestCase r = Gem::Resolver.new([ad], set(a1)) - e = assert_raises Gem::UnsatisfiableDepedencyError do + e = assert_raise Gem::UnsatisfiableDependencyError do r.resolve end @@ -499,7 +499,7 @@ class TestGemResolver < Gem::TestCase r = Gem::Resolver.new([ad], set(a1)) - e = assert_raises Gem::UnsatisfiableDepedencyError do + e = assert_raise Gem::UnsatisfiableDependencyError do r.resolve end @@ -516,7 +516,7 @@ class TestGemResolver < Gem::TestCase r = Gem::Resolver.new([ad], set(a1)) - e = assert_raises Gem::UnsatisfiableDepedencyError do + e = assert_raise Gem::UnsatisfiableDependencyError do r.resolve end @@ -539,7 +539,7 @@ class TestGemResolver < Gem::TestCase r = Gem::Resolver.new([ad, bd], s) - e = assert_raises Gem::DependencyResolutionError do + e = assert_raise Gem::DependencyResolutionError do r.resolve end @@ -611,7 +611,7 @@ class TestGemResolver < Gem::TestCase r = Gem::Resolver.new([d1, d2, d3], s) - assert_raises Gem::DependencyResolutionError do + assert_raise Gem::DependencyResolutionError do r.resolve end end @@ -629,7 +629,7 @@ class TestGemResolver < Gem::TestCase r = Gem::Resolver.new [a_dep, b_dep], s - assert_raises Gem::DependencyResolutionError do + assert_raise Gem::DependencyResolutionError do r.resolve end end @@ -781,7 +781,7 @@ class TestGemResolver < Gem::TestCase r = Gem::Resolver.new([ad], set(a1)) - e = assert_raises Gem::UnsatisfiableDepedencyError do + e = assert_raise Gem::UnsatisfiableDependencyError do r.resolve end diff --git a/test/rubygems/test_gem_resolver_activation_request.rb b/test/rubygems/test_gem_resolver_activation_request.rb index f973c5956d..c7b726a230 100644 --- a/test/rubygems/test_gem_resolver_activation_request.rb +++ b/test/rubygems/test_gem_resolver_activation_request.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverActivationRequest < Gem::TestCase def setup diff --git a/test/rubygems/test_gem_resolver_api_set.rb b/test/rubygems/test_gem_resolver_api_set.rb index aa17ec6f3a..c3db25d7aa 100644 --- a/test/rubygems/test_gem_resolver_api_set.rb +++ b/test/rubygems/test_gem_resolver_api_set.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverAPISet < Gem::TestCase def setup diff --git a/test/rubygems/test_gem_resolver_api_specification.rb b/test/rubygems/test_gem_resolver_api_specification.rb index e9ba4ae481..3f9b81868f 100644 --- a/test/rubygems/test_gem_resolver_api_specification.rb +++ b/test/rubygems/test_gem_resolver_api_specification.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverAPISpecification < Gem::TestCase def test_initialize diff --git a/test/rubygems/test_gem_resolver_best_set.rb b/test/rubygems/test_gem_resolver_best_set.rb index 019ca70499..0e279d16a8 100644 --- a/test/rubygems/test_gem_resolver_best_set.rb +++ b/test/rubygems/test_gem_resolver_best_set.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverBestSet < Gem::TestCase def setup @@ -126,7 +126,7 @@ class TestGemResolverBestSet < Gem::TestCase error = Gem::RemoteFetcher::FetchError.new 'bogus', @gem_repo - e = assert_raises Gem::RemoteFetcher::FetchError do + e = assert_raise Gem::RemoteFetcher::FetchError do set.replace_failed_api_set error end diff --git a/test/rubygems/test_gem_resolver_composed_set.rb b/test/rubygems/test_gem_resolver_composed_set.rb index 7c8ae004a3..405753a373 100644 --- a/test/rubygems/test_gem_resolver_composed_set.rb +++ b/test/rubygems/test_gem_resolver_composed_set.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverComposedSet < Gem::TestCase def test_errors diff --git a/test/rubygems/test_gem_resolver_conflict.rb b/test/rubygems/test_gem_resolver_conflict.rb index ff5fe9bae3..1d46e69c3f 100644 --- a/test/rubygems/test_gem_resolver_conflict.rb +++ b/test/rubygems/test_gem_resolver_conflict.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverConflict < Gem::TestCase def test_explanation diff --git a/test/rubygems/test_gem_resolver_dependency_request.rb b/test/rubygems/test_gem_resolver_dependency_request.rb index a8ddc8362b..cea0a7952d 100644 --- a/test/rubygems/test_gem_resolver_dependency_request.rb +++ b/test/rubygems/test_gem_resolver_dependency_request.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverDependencyRequest < Gem::TestCase def setup diff --git a/test/rubygems/test_gem_resolver_git_set.rb b/test/rubygems/test_gem_resolver_git_set.rb index 6d048b3772..145cd6c7df 100644 --- a/test/rubygems/test_gem_resolver_git_set.rb +++ b/test/rubygems/test_gem_resolver_git_set.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverGitSet < Gem::TestCase def setup @@ -51,7 +51,7 @@ class TestGemResolverGitSet < Gem::TestCase assert @set.need_submodules[repository] - refute_path_exists spec.source.repo_cache_dir + assert_path_not_exist spec.source.repo_cache_dir end def test_find_all diff --git a/test/rubygems/test_gem_resolver_git_specification.rb b/test/rubygems/test_gem_resolver_git_specification.rb index 4283e02765..857452c159 100644 --- a/test/rubygems/test_gem_resolver_git_specification.rb +++ b/test/rubygems/test_gem_resolver_git_specification.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/installer' class TestGemResolverGitSpecification < Gem::TestCase @@ -62,7 +62,8 @@ class TestGemResolverGitSpecification < Gem::TestCase # functional test for Gem::Ext::Builder def test_install_extension - skip if Gem.java_platform? + pend if Gem.java_platform? + pend if /mswin/ =~ RUBY_PLATFORM && ENV.key?('GITHUB_ACTIONS') # not working from the beginning name, _, repository, = git_gem 'a', 1 do |s| s.extensions << 'ext/extconf.rb' end @@ -91,7 +92,7 @@ class TestGemResolverGitSpecification < Gem::TestCase git_spec.install({}) - assert_path_exists File.join git_spec.spec.extension_dir, 'b.rb' + assert_path_exist File.join git_spec.spec.extension_dir, 'b.rb' end def test_install_installed diff --git a/test/rubygems/test_gem_resolver_index_set.rb b/test/rubygems/test_gem_resolver_index_set.rb index 2de766f60a..ddae0c2b82 100644 --- a/test/rubygems/test_gem_resolver_index_set.rb +++ b/test/rubygems/test_gem_resolver_index_set.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverIndexSet < Gem::TestCase def setup diff --git a/test/rubygems/test_gem_resolver_index_specification.rb b/test/rubygems/test_gem_resolver_index_specification.rb index 702d26777b..ef9c17034e 100644 --- a/test/rubygems/test_gem_resolver_index_specification.rb +++ b/test/rubygems/test_gem_resolver_index_specification.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/available_set' class TestGemResolverIndexSpecification < Gem::TestCase @@ -46,7 +46,7 @@ class TestGemResolverIndexSpecification < Gem::TestCase called = installer end - assert_path_exists File.join @gemhome, 'specifications', 'a-2.gemspec' + assert_path_exist File.join @gemhome, 'specifications', 'a-2.gemspec' assert_kind_of Gem::Installer, called end diff --git a/test/rubygems/test_gem_resolver_installed_specification.rb b/test/rubygems/test_gem_resolver_installed_specification.rb index b102f98d00..0e5ea02fb9 100644 --- a/test/rubygems/test_gem_resolver_installed_specification.rb +++ b/test/rubygems/test_gem_resolver_installed_specification.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverInstalledSpecification < Gem::TestCase def setup diff --git a/test/rubygems/test_gem_resolver_installer_set.rb b/test/rubygems/test_gem_resolver_installer_set.rb index 76c9c04a3c..928a16b9d3 100644 --- a/test/rubygems/test_gem_resolver_installer_set.rb +++ b/test/rubygems/test_gem_resolver_installer_set.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverInstallerSet < Gem::TestCase def test_add_always_install @@ -16,7 +16,7 @@ class TestGemResolverInstallerSet < Gem::TestCase assert_equal %w[a-2], set.always_install.map {|s| s.full_name } - e = assert_raises Gem::UnsatisfiableDependencyError do + e = assert_raise Gem::UnsatisfiableDependencyError do set.add_always_install dep('b') end @@ -29,7 +29,7 @@ class TestGemResolverInstallerSet < Gem::TestCase set = Gem::Resolver::InstallerSet.new :both - e = assert_raises Gem::UnsatisfiableDependencyError do + e = assert_raise Gem::UnsatisfiableDependencyError do set.add_always_install dep 'a' end @@ -64,6 +64,24 @@ class TestGemResolverInstallerSet < Gem::TestCase assert_equal %w[a-1], set.always_install.map {|s| s.full_name } end + def test_add_always_install_prerelease_github_problem + spec_fetcher do |fetcher| + fetcher.gem 'a', 1 + end + + # Github has an issue in which it will generate a misleading prerelease output in its RubyGems server API and + # returns a 0 version for the gem while it doesn't exist. + @fetcher.data["#{@gem_repo}prerelease_specs.#{Gem.marshal_version}.gz"] = util_gzip(Marshal.dump([ + Gem::NameTuple.new('a', Gem::Version.new(0), 'ruby'), + ])) + + set = Gem::Resolver::InstallerSet.new :both + + set.add_always_install dep('a') + + assert_equal %w[a-1], set.always_install.map {|s| s.full_name } + end + def test_add_always_install_prerelease_only spec_fetcher do |fetcher| fetcher.gem 'a', '3.a' @@ -71,7 +89,7 @@ class TestGemResolverInstallerSet < Gem::TestCase set = Gem::Resolver::InstallerSet.new :both - assert_raises Gem::UnsatisfiableDependencyError do + assert_raise Gem::UnsatisfiableDependencyError do set.add_always_install dep('a') end end @@ -192,7 +210,7 @@ class TestGemResolverInstallerSet < Gem::TestCase def (set.remote_set).prefetch(_) raise "called" end - assert_raises(RuntimeError){ set.prefetch(nil) } + assert_raise(RuntimeError){ set.prefetch(nil) } set = Gem::Resolver::InstallerSet.new :local def (set.remote_set).prefetch(_) diff --git a/test/rubygems/test_gem_resolver_local_specification.rb b/test/rubygems/test_gem_resolver_local_specification.rb index 0dcc436b29..c11f736128 100644 --- a/test/rubygems/test_gem_resolver_local_specification.rb +++ b/test/rubygems/test_gem_resolver_local_specification.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/available_set' class TestGemResolverLocalSpecification < Gem::TestCase @@ -24,7 +24,7 @@ class TestGemResolverLocalSpecification < Gem::TestCase called = installer end - assert_path_exists File.join @gemhome, 'specifications', 'a-2.gemspec' + assert_path_exist File.join @gemhome, 'specifications', 'a-2.gemspec' assert_kind_of Gem::Installer, called end diff --git a/test/rubygems/test_gem_resolver_lock_set.rb b/test/rubygems/test_gem_resolver_lock_set.rb index cb03e0b5f8..dc7767a3f8 100644 --- a/test/rubygems/test_gem_resolver_lock_set.rb +++ b/test/rubygems/test_gem_resolver_lock_set.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverLockSet < Gem::TestCase def setup diff --git a/test/rubygems/test_gem_resolver_lock_specification.rb b/test/rubygems/test_gem_resolver_lock_specification.rb index 07654a9164..46c8e5edd6 100644 --- a/test/rubygems/test_gem_resolver_lock_specification.rb +++ b/test/rubygems/test_gem_resolver_lock_specification.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/installer' require 'rubygems/resolver' diff --git a/test/rubygems/test_gem_resolver_requirement_list.rb b/test/rubygems/test_gem_resolver_requirement_list.rb index af8a77ef4b..806c387669 100644 --- a/test/rubygems/test_gem_resolver_requirement_list.rb +++ b/test/rubygems/test_gem_resolver_requirement_list.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverRequirementList < Gem::TestCase def setup diff --git a/test/rubygems/test_gem_resolver_specification.rb b/test/rubygems/test_gem_resolver_specification.rb index e395c473ab..9f28c1c0cf 100644 --- a/test/rubygems/test_gem_resolver_specification.rb +++ b/test/rubygems/test_gem_resolver_specification.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverSpecification < Gem::TestCase class TestSpec < Gem::Resolver::Specification @@ -26,7 +26,7 @@ class TestGemResolverSpecification < Gem::TestCase a_spec.install :install_dir => gemhome - assert_path_exists File.join gemhome, 'gems', a.full_name + assert_path_exist File.join gemhome, 'gems', a.full_name expected = File.join gemhome, 'specifications', a.spec_name diff --git a/test/rubygems/test_gem_resolver_vendor_set.rb b/test/rubygems/test_gem_resolver_vendor_set.rb index d14b877b45..e16d43ac1a 100644 --- a/test/rubygems/test_gem_resolver_vendor_set.rb +++ b/test/rubygems/test_gem_resolver_vendor_set.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverVendorSet < Gem::TestCase def setup @@ -27,7 +27,7 @@ class TestGemResolverVendorSet < Gem::TestCase FileUtils.rm_r directory - e = assert_raises Gem::GemNotFoundException do + e = assert_raise Gem::GemNotFoundException do @set.add_vendor_gem name, directory end @@ -74,7 +74,7 @@ class TestGemResolverVendorSet < Gem::TestCase def test_load_spec error = Object.const_defined?(:KeyError) ? KeyError : IndexError - assert_raises error do + assert_raise error do @set.load_spec 'b', v(1), Gem::Platform::RUBY, nil end end diff --git a/test/rubygems/test_gem_resolver_vendor_specification.rb b/test/rubygems/test_gem_resolver_vendor_specification.rb index dbf0c58e0b..93382ccd81 100644 --- a/test/rubygems/test_gem_resolver_vendor_specification.rb +++ b/test/rubygems/test_gem_resolver_vendor_specification.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemResolverVendorSpecification < Gem::TestCase def setup diff --git a/test/rubygems/test_gem_security.rb b/test/rubygems/test_gem_security.rb index 92f9e55b21..d04bd4a8bd 100644 --- a/test/rubygems/test_gem_security.rb +++ b/test/rubygems/test_gem_security.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/security' unless Gem::HAVE_OPENSSL @@ -12,6 +12,7 @@ end class TestGemSecurity < Gem::TestCase CHILD_KEY = load_key 'child' + EC_KEY = load_key 'private_ec', 'Foo bar' ALTERNATE_CERT = load_cert 'child' CHILD_CERT = load_cert 'child' @@ -103,11 +104,38 @@ class TestGemSecurity < Gem::TestCase end def test_class_create_key - key = @SEC.create_key 1024 + key = @SEC.create_key 'rsa' assert_kind_of OpenSSL::PKey::RSA, key end + def test_class_create_key_downcases + key = @SEC.create_key 'DSA' + + assert_kind_of OpenSSL::PKey::DSA, key + end + + def test_class_create_key_raises_unknown_algorithm + e = assert_raise Gem::Security::Exception do + @SEC.create_key 'NOT_RSA' + end + + assert_equal "NOT_RSA algorithm not found. RSA, DSA, and EC algorithms are supported.", + e.message + end + + def test_class_get_public_key_rsa + pkey_pem = PRIVATE_KEY.public_key.to_pem + + assert_equal pkey_pem, @SEC.get_public_key(PRIVATE_KEY).to_pem + end + + def test_class_get_public_key_ec + pkey = @SEC.get_public_key(EC_KEY) + + assert_respond_to pkey, :to_pem + end + def test_class_email_to_name assert_equal '/CN=nobody/DC=example', @SEC.email_to_name('nobody@example').to_s @@ -135,7 +163,7 @@ class TestGemSecurity < Gem::TestCase end def test_class_re_sign_not_self_signed - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do Gem::Security.re_sign CHILD_CERT, CHILD_KEY end @@ -149,7 +177,7 @@ class TestGemSecurity < Gem::TestCase end def test_class_re_sign_wrong_key - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do Gem::Security.re_sign ALTERNATE_CERT, PRIVATE_KEY end @@ -259,13 +287,13 @@ class TestGemSecurity < Gem::TestCase end def test_class_write - key = @SEC.create_key 1024 + key = @SEC.create_key 'rsa' path = File.join @tempdir, 'test-private_key.pem' @SEC.write key, path - assert_path_exists path + assert_path_exist path key_from_file = File.read path @@ -273,7 +301,7 @@ class TestGemSecurity < Gem::TestCase end def test_class_write_encrypted - key = @SEC.create_key 1024 + key = @SEC.create_key 'rsa' path = File.join @tempdir, 'test-private_encrypted_key.pem' @@ -281,7 +309,7 @@ class TestGemSecurity < Gem::TestCase @SEC.write key, path, 0600, passphrase - assert_path_exists path + assert_path_exist path key_from_file = OpenSSL::PKey::RSA.new File.read(path), passphrase @@ -289,7 +317,7 @@ class TestGemSecurity < Gem::TestCase end def test_class_write_encrypted_cipher - key = @SEC.create_key 1024 + key = @SEC.create_key 'rsa' path = File.join @tempdir, 'test-private_encrypted__with_non_default_cipher_key.pem' @@ -299,7 +327,7 @@ class TestGemSecurity < Gem::TestCase @SEC.write key, path, 0600, passphrase, cipher - assert_path_exists path + assert_path_exist path key_file_contents = File.read(path) diff --git a/test/rubygems/test_gem_security_policy.rb b/test/rubygems/test_gem_security_policy.rb index 85e4590655..515b8ea00b 100644 --- a/test/rubygems/test_gem_security_policy.rb +++ b/test/rubygems/test_gem_security_policy.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' unless Gem::HAVE_OPENSSL warn 'Skipping Gem::Security::Policy tests. openssl not found.' @@ -77,7 +77,7 @@ class TestGemSecurityPolicy < Gem::TestCase invalid = digest 'hello!' - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @almost_no.check_data PUBLIC_KEY, @digest, signature, invalid end @@ -91,7 +91,7 @@ class TestGemSecurityPolicy < Gem::TestCase end def test_check_chain_empty_chain - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @chain.check_chain [], Time.now end @@ -101,7 +101,7 @@ class TestGemSecurityPolicy < Gem::TestCase def test_check_chain_invalid chain = [PUBLIC_CERT, CHILD_CERT, INVALIDCHILD_CERT] - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @chain.check_chain chain, Time.now end @@ -111,7 +111,7 @@ class TestGemSecurityPolicy < Gem::TestCase end def test_check_chain_no_chain - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @chain.check_chain nil, Time.now end @@ -123,7 +123,7 @@ class TestGemSecurityPolicy < Gem::TestCase end def test_check_cert_expired - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @low.check_cert EXPIRED_CERT, nil, Time.now end @@ -133,7 +133,7 @@ class TestGemSecurityPolicy < Gem::TestCase end def test_check_cert_future - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @low.check_cert FUTURE_CERT, nil, Time.now end @@ -143,7 +143,7 @@ class TestGemSecurityPolicy < Gem::TestCase end def test_check_cert_invalid_issuer - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @low.check_cert INVALID_ISSUER_CERT, PUBLIC_CERT, Time.now end @@ -157,7 +157,7 @@ class TestGemSecurityPolicy < Gem::TestCase end def test_check_cert_no_signer - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @high.check_cert(nil, nil, Time.now) end @@ -171,7 +171,7 @@ class TestGemSecurityPolicy < Gem::TestCase def test_check_key_no_signer assert @almost_no.check_key(nil, nil) - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @high.check_key(nil, nil) end @@ -179,7 +179,7 @@ class TestGemSecurityPolicy < Gem::TestCase end def test_check_key_wrong_key - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @almost_no.check_key(PUBLIC_CERT, ALTERNATE_KEY) end @@ -194,7 +194,7 @@ class TestGemSecurityPolicy < Gem::TestCase end def test_check_root_empty_chain - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @chain.check_root [], Time.now end @@ -204,7 +204,7 @@ class TestGemSecurityPolicy < Gem::TestCase def test_check_root_invalid_signer chain = [INVALID_SIGNER_CERT] - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @chain.check_root chain, Time.now end @@ -216,7 +216,7 @@ class TestGemSecurityPolicy < Gem::TestCase def test_check_root_not_self_signed chain = [INVALID_ISSUER_CERT] - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @chain.check_root chain, Time.now end @@ -226,7 +226,7 @@ class TestGemSecurityPolicy < Gem::TestCase end def test_check_root_no_chain - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @chain.check_root nil, Time.now end @@ -246,7 +246,7 @@ class TestGemSecurityPolicy < Gem::TestCase end def test_check_trust_empty_chain - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @chain.check_trust [], @digest, @trust_dir end @@ -256,7 +256,7 @@ class TestGemSecurityPolicy < Gem::TestCase def test_check_trust_mismatch Gem::Security.trust_dir.trust_cert PUBLIC_CERT - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @high.check_trust [WRONG_KEY_CERT], @digest, @trust_dir end @@ -265,7 +265,7 @@ class TestGemSecurityPolicy < Gem::TestCase end def test_check_trust_no_chain - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @chain.check_trust nil, @digest, @trust_dir end @@ -273,7 +273,7 @@ class TestGemSecurityPolicy < Gem::TestCase end def test_check_trust_no_trust - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @high.check_trust [PUBLIC_CERT], @digest, @trust_dir end @@ -281,7 +281,7 @@ class TestGemSecurityPolicy < Gem::TestCase end def test_check_trust_no_trust_child - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @high.check_trust [PUBLIC_CERT, CHILD_CERT], @digest, @trust_dir end @@ -315,7 +315,7 @@ class TestGemSecurityPolicy < Gem::TestCase _, signatures = dummy_signatures - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @almost_no.verify [PUBLIC_CERT], nil, {}, signatures end @@ -327,7 +327,7 @@ class TestGemSecurityPolicy < Gem::TestCase _, signatures = dummy_signatures - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @no.verify [PUBLIC_CERT], nil, {}, signatures end @@ -345,7 +345,7 @@ class TestGemSecurityPolicy < Gem::TestCase assert_match "WARNING: some_gem is not signed\n", @ui.error - assert_raises Gem::Security::Exception do + assert_raise Gem::Security::Exception do @high.verify [PUBLIC_CERT], nil, digests, {} end end @@ -370,7 +370,7 @@ class TestGemSecurityPolicy < Gem::TestCase signatures[1] = PRIVATE_KEY.sign @digest.new, data.digest - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @almost_no.verify [PUBLIC_CERT], nil, digests, signatures end @@ -387,7 +387,7 @@ class TestGemSecurityPolicy < Gem::TestCase assert_equal "WARNING: email:nobody@example is not trusted for some_gem\n", @ui.error - assert_raises Gem::Security::Exception do + assert_raise Gem::Security::Exception do @medium.verify [PUBLIC_CERT], nil, digests, signatures end end @@ -402,7 +402,7 @@ class TestGemSecurityPolicy < Gem::TestCase signature = PRIVATE_KEY.sign 'sha512', data.digest signatures = { 0 => signature } - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @almost_no.verify [PUBLIC_CERT], nil, digests, signatures end @@ -486,7 +486,7 @@ class TestGemSecurityPolicy < Gem::TestCase signatures['metadata.gz'] = PRIVATE_KEY.sign @digest.new, metadata_gz_digest.digest - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @high.verify_signatures @spec, digests, signatures end @@ -509,7 +509,7 @@ class TestGemSecurityPolicy < Gem::TestCase digests = package.digest s digests[Gem::Security::DIGEST_NAME]['data.tar.gz'] = @digest.hexdigest 'hello' - assert_raises Gem::Security::Exception do + assert_raise Gem::Security::Exception do @high.verify_signatures @spec, digests, {} end end diff --git a/test/rubygems/test_gem_security_signer.rb b/test/rubygems/test_gem_security_signer.rb index 8a09f97f26..7bd6510b50 100644 --- a/test/rubygems/test_gem_security_signer.rb +++ b/test/rubygems/test_gem_security_signer.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' unless Gem::HAVE_OPENSSL warn 'Skipping Gem::Security::Signer tests. openssl not found.' @@ -41,7 +41,7 @@ class TestGemSecuritySigner < Gem::TestCase end def test_initialize_cert_chain_invalid - assert_raises OpenSSL::X509::CertificateError do + assert_raise OpenSSL::X509::CertificateError do Gem::Security::Signer.new nil, ['garbage'] end end @@ -134,7 +134,7 @@ toqvglr0kdbknSRRjBVLK6tsgr07aLT9gNP7mTW2PA== def test_sign_expired signer = Gem::Security::Signer.new PRIVATE_KEY, [EXPIRED_CERT] - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do signer.sign 'hello' end @@ -142,7 +142,7 @@ toqvglr0kdbknSRRjBVLK6tsgr07aLT9gNP7mTW2PA== end def test_sign_expired_auto_update - skip if Gem.java_platform? + pend if Gem.java_platform? FileUtils.mkdir_p File.join(Gem.user_home, '.gem'), :mode => 0700 private_key_path = File.join(Gem.user_home, '.gem', 'gem-private_key.pem') @@ -165,7 +165,7 @@ toqvglr0kdbknSRRjBVLK6tsgr07aLT9gNP7mTW2PA== expired_path = File.join Gem.user_home, '.gem', "gem-public_cert.pem.expired.#{expiry}" - assert_path_exists expired_path + assert_path_exist expired_path assert_equal EXPIRED_CERT.to_pem, File.read(expired_path) end @@ -186,7 +186,7 @@ toqvglr0kdbknSRRjBVLK6tsgr07aLT9gNP7mTW2PA== signer = Gem::Security::Signer.new PRIVATE_KEY, [EXPIRED_CERT] - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do signer.sign 'hello' end @@ -202,7 +202,7 @@ toqvglr0kdbknSRRjBVLK6tsgr07aLT9gNP7mTW2PA== def test_sign_wrong_key signer = Gem::Security::Signer.new ALTERNATE_KEY, [PUBLIC_CERT] - assert_raises Gem::Security::Exception do + assert_raise Gem::Security::Exception do signer.sign 'hello' end end @@ -210,7 +210,7 @@ toqvglr0kdbknSRRjBVLK6tsgr07aLT9gNP7mTW2PA== def test_sign_no_certs signer = Gem::Security::Signer.new ALTERNATE_KEY, [] - assert_raises Gem::Security::Exception do + assert_raise Gem::Security::Exception do signer.sign 'hello' end end diff --git a/test/rubygems/test_gem_security_trust_dir.rb b/test/rubygems/test_gem_security_trust_dir.rb index 201de9d36b..fc88c84865 100644 --- a/test/rubygems/test_gem_security_trust_dir.rb +++ b/test/rubygems/test_gem_security_trust_dir.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' unless Gem::HAVE_OPENSSL warn 'Skipping Gem::Security::TrustDir tests. openssl not found.' @@ -53,7 +53,7 @@ class TestGemSecurityTrustDir < Gem::TestCase trusted = @trust_dir.cert_path PUBLIC_CERT - assert_path_exists trusted + assert_path_exist trusted mask = 0100600 & (~File.umask) @@ -63,11 +63,11 @@ class TestGemSecurityTrustDir < Gem::TestCase end def test_verify - refute_path_exists @dest_dir + assert_path_not_exist @dest_dir @trust_dir.verify - assert_path_exists @dest_dir + assert_path_exist @dest_dir mask = 040700 & (~File.umask) mask |= 0200000 if /aix/ =~ RUBY_PLATFORM @@ -78,7 +78,7 @@ class TestGemSecurityTrustDir < Gem::TestCase def test_verify_file FileUtils.touch @dest_dir - e = assert_raises Gem::Security::Exception do + e = assert_raise Gem::Security::Exception do @trust_dir.verify end diff --git a/test/rubygems/test_gem_server.rb b/test/rubygems/test_gem_server.rb index 0e283da5a4..f6aa99fb02 100644 --- a/test/rubygems/test_gem_server.rb +++ b/test/rubygems/test_gem_server.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/server' require 'stringio' @@ -133,7 +133,7 @@ class TestGemServer < Gem::TestCase def test_listen util_listen - capture_io do + capture_output do @server.listen end @@ -143,7 +143,7 @@ class TestGemServer < Gem::TestCase def test_listen_addresses util_listen - capture_io do + capture_output do @server.listen %w[a b] end @@ -365,7 +365,7 @@ class TestGemServer < Gem::TestCase specs_dir = File.join dir, 'specifications' FileUtils.mkdir_p specs_dir - open File.join(specs_dir, spec.spec_name), 'w' do |io| + File.open File.join(specs_dir, spec.spec_name), 'w' do |io| io.write spec.to_ruby end @@ -420,7 +420,7 @@ class TestGemServer < Gem::TestCase specs_dir = File.join dir, 'specifications' FileUtils.mkdir_p specs_dir - open File.join(specs_dir, spec.spec_name), 'w' do |io| + File.open File.join(specs_dir, spec.spec_name), 'w' do |io| io.write spec.to_ruby end @@ -475,7 +475,7 @@ class TestGemServer < Gem::TestCase specs_dir = File.join dir, 'specifications' FileUtils.mkdir_p specs_dir - open File.join(specs_dir, spec.spec_name), 'w' do |io| + File.open File.join(specs_dir, spec.spec_name), 'w' do |io| io.write spec.to_ruby end @@ -502,7 +502,7 @@ class TestGemServer < Gem::TestCase specs_dir = File.join dir, 'specifications' FileUtils.mkdir_p specs_dir - open File.join(specs_dir, spec.spec_name), 'w' do |io| + File.open File.join(specs_dir, spec.spec_name), 'w' do |io| io.write spec.to_ruby end diff --git a/test/rubygems/test_gem_silent_ui.rb b/test/rubygems/test_gem_silent_ui.rb index 3c4811aad5..355255fb48 100644 --- a/test/rubygems/test_gem_silent_ui.rb +++ b/test/rubygems/test_gem_silent_ui.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/user_interaction' require 'timeout' @@ -16,7 +16,7 @@ class TestGemSilentUI < Gem::TestCase def test_ask value = nil - out, err = capture_io do + out, err = capture_output do use_ui @sui do value = @sui.ask 'Problem?' end @@ -30,7 +30,7 @@ class TestGemSilentUI < Gem::TestCase def test_ask_for_password value = nil - out, err = capture_io do + out, err = capture_output do use_ui @sui do value = @sui.ask_for_password 'Problem?' end @@ -44,9 +44,9 @@ class TestGemSilentUI < Gem::TestCase def test_ask_yes_no value = nil - out, err = capture_io do + out, err = capture_output do use_ui @sui do - assert_raises(Gem::OperationNotSupportedError) do + assert_raise(Gem::OperationNotSupportedError) do @sui.ask_yes_no 'Problem?' end end @@ -55,7 +55,7 @@ class TestGemSilentUI < Gem::TestCase assert_empty out, 'No output' assert_empty err, 'No output' - out, err = capture_io do + out, err = capture_output do use_ui @sui do value = @sui.ask_yes_no 'Problem?', true end @@ -66,7 +66,7 @@ class TestGemSilentUI < Gem::TestCase assert value, 'Value is true' - out, err = capture_io do + out, err = capture_output do use_ui @sui do value = @sui.ask_yes_no 'Problem?', false end @@ -80,7 +80,7 @@ class TestGemSilentUI < Gem::TestCase def test_choose_from_list value = nil - out, err = capture_io do + out, err = capture_output do use_ui @sui do value = @sui.choose_from_list 'Problem?', %w[yes no] end @@ -93,7 +93,7 @@ class TestGemSilentUI < Gem::TestCase end def test_progress_reporter - out, err = capture_io do + out, err = capture_output do use_ui @sui do @sui.progress_reporter 10, 'hi' end @@ -104,7 +104,7 @@ class TestGemSilentUI < Gem::TestCase end def test_download_reporter - out, err = capture_io do + out, err = capture_output do use_ui @sui do @sui.download_reporter.fetch 'a.gem', 1024 end diff --git a/test/rubygems/test_gem_source.rb b/test/rubygems/test_gem_source.rb index d6c2ffa051..24312626f4 100644 --- a/test/rubygems/test_gem_source.rb +++ b/test/rubygems/test_gem_source.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/source' require 'rubygems/indexer' @@ -22,7 +22,7 @@ class TestGemSource < Gem::TestCase end def test_initialize_invalid_uri - assert_raises URI::InvalidURIError do + assert_raise URI::InvalidURIError do Gem::Source.new 'git@example:a.git' end end @@ -185,7 +185,7 @@ class TestGemSource < Gem::TestCase def test_load_specs_from_unavailable_uri src = Gem::Source.new("http://not-there.nothing") - assert_raises Gem::RemoteFetcher::FetchError do + assert_raise Gem::RemoteFetcher::FetchError do src.load_specs :latest end end diff --git a/test/rubygems/test_gem_source_fetch_problem.rb b/test/rubygems/test_gem_source_fetch_problem.rb index 1a0545a893..816407781d 100644 --- a/test/rubygems/test_gem_source_fetch_problem.rb +++ b/test/rubygems/test_gem_source_fetch_problem.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemSourceFetchProblem < Gem::TestCase def test_exception @@ -8,7 +8,7 @@ class TestGemSourceFetchProblem < Gem::TestCase sf = Gem::SourceFetchProblem.new source, error - e = assert_raises RuntimeError do + e = assert_raise RuntimeError do raise sf end @@ -23,4 +23,14 @@ class TestGemSourceFetchProblem < Gem::TestCase refute_match sf.wordy, 'secret' end + + def test_source_password_no_redacted + source = Gem::Source.new 'https://username:secret@gemsource.com' + error = RuntimeError.new 'test' + + sf = Gem::SourceFetchProblem.new source, error + sf.wordy + + assert_match 'secret', source.uri.to_s + end end diff --git a/test/rubygems/test_gem_source_git.rb b/test/rubygems/test_gem_source_git.rb index f5406d2215..36e904732b 100644 --- a/test/rubygems/test_gem_source_git.rb +++ b/test/rubygems/test_gem_source_git.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/source' class TestGemSourceGit < Gem::TestCase @@ -24,7 +24,7 @@ class TestGemSourceGit < Gem::TestCase def test_checkout @source.checkout - assert_path_exists File.join @source.install_dir, 'a.gemspec' + assert_path_exist File.join @source.install_dir, 'a.gemspec' end def test_checkout_master @@ -39,7 +39,7 @@ class TestGemSourceGit < Gem::TestCase @source.checkout - assert_path_exists File.join @source.install_dir, 'b.gemspec' + assert_path_exist File.join @source.install_dir, 'b.gemspec' end def test_checkout_local @@ -49,7 +49,7 @@ class TestGemSourceGit < Gem::TestCase install_dir = File.join Gem.dir, 'bundler', 'gems', "a-#{@head[0..11]}" - refute_path_exists File.join install_dir, 'a.gemspec' + assert_path_not_exist File.join install_dir, 'a.gemspec' end def test_checkout_local_cached @@ -59,10 +59,15 @@ class TestGemSourceGit < Gem::TestCase @source.checkout - assert_path_exists File.join @source.install_dir, 'a.gemspec' + assert_path_exist File.join @source.install_dir, 'a.gemspec' end def test_checkout_submodules + # We need to allow to checkout submodules with file:// protocol + # CVE-2022-39253 + # https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/ + system(@git, *%W[config --global protocol.file.allow always]) + source = Gem::Source::Git.new @name, @repository, 'master', true git_gem 'b' @@ -76,14 +81,14 @@ class TestGemSourceGit < Gem::TestCase source.checkout - assert_path_exists File.join source.install_dir, 'a.gemspec' - assert_path_exists File.join source.install_dir, 'b/b.gemspec' + assert_path_exist File.join source.install_dir, 'a.gemspec' + assert_path_exist File.join source.install_dir, 'b/b.gemspec' end def test_cache assert @source.cache - assert_path_exists @source.repo_cache_dir + assert_path_exist @source.repo_cache_dir Dir.chdir @source.repo_cache_dir do assert_equal @head, Gem::Util.popen(@git, 'rev-parse', 'master').strip @@ -95,7 +100,7 @@ class TestGemSourceGit < Gem::TestCase @source.cache - refute_path_exists @source.repo_cache_dir + assert_path_not_exist @source.repo_cache_dir end def test_dir_shortref @@ -186,7 +191,7 @@ class TestGemSourceGit < Gem::TestCase source.cache - e = assert_raises Gem::Exception do + e = assert_raise Gem::Exception do capture_subprocess_io { source.rev_parse } end @@ -240,7 +245,7 @@ class TestGemSourceGit < Gem::TestCase specs = nil - capture_io do + capture_output do specs = source.specs end @@ -275,7 +280,7 @@ class TestGemSourceGit < Gem::TestCase source = Gem::Source::Git.new @name, @repository, 'master', true source.remote = false - capture_io do + capture_output do assert_empty source.specs end end diff --git a/test/rubygems/test_gem_source_installed.rb b/test/rubygems/test_gem_source_installed.rb index 93aa4eb039..ef9b63e611 100644 --- a/test/rubygems/test_gem_source_installed.rb +++ b/test/rubygems/test_gem_source_installed.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/source' class TestGemSourceInstalled < Gem::TestCase diff --git a/test/rubygems/test_gem_source_list.rb b/test/rubygems/test_gem_source_list.rb index 7c60af3ff8..6ac5dbb2a6 100644 --- a/test/rubygems/test_gem_source_list.rb +++ b/test/rubygems/test_gem_source_list.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'rubygems' require 'rubygems/source_list' -require 'rubygems/test_case' +require_relative 'helper' class TestGemSourceList < Gem::TestCase def setup diff --git a/test/rubygems/test_gem_source_local.rb b/test/rubygems/test_gem_source_local.rb index 7417f8d111..2d4ddbc3a4 100644 --- a/test/rubygems/test_gem_source_local.rb +++ b/test/rubygems/test_gem_source_local.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/source' require 'fileutils' diff --git a/test/rubygems/test_gem_source_lock.rb b/test/rubygems/test_gem_source_lock.rb index 8c55926d94..5f916cdf7f 100644 --- a/test/rubygems/test_gem_source_lock.rb +++ b/test/rubygems/test_gem_source_lock.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemSourceLock < Gem::TestCase def test_fetch_spec diff --git a/test/rubygems/test_gem_source_specific_file.rb b/test/rubygems/test_gem_source_specific_file.rb index 003fbec81d..72ed993a88 100644 --- a/test/rubygems/test_gem_source_specific_file.rb +++ b/test/rubygems/test_gem_source_specific_file.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/source' class TestGemSourceSpecificFile < Gem::TestCase @@ -27,7 +27,7 @@ class TestGemSourceSpecificFile < Gem::TestCase end def test_fetch_spec_fails_on_unknown_name - assert_raises Gem::Exception do + assert_raise Gem::Exception do @sf.fetch_spec(nil) end end diff --git a/test/rubygems/test_gem_source_subpath_problem.rb b/test/rubygems/test_gem_source_subpath_problem.rb index b2289ea625..c37df39f0c 100644 --- a/test/rubygems/test_gem_source_subpath_problem.rb +++ b/test/rubygems/test_gem_source_subpath_problem.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/source' class TestGemSourceSubpathProblem < Gem::TestCase diff --git a/test/rubygems/test_gem_source_vendor.rb b/test/rubygems/test_gem_source_vendor.rb index 18a3f47f45..a5ffb0f223 100644 --- a/test/rubygems/test_gem_source_vendor.rb +++ b/test/rubygems/test_gem_source_vendor.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/source' class TestGemSourceVendor < Gem::TestCase diff --git a/test/rubygems/test_gem_spec_fetcher.rb b/test/rubygems/test_gem_spec_fetcher.rb index 9c756aacf3..afae46e120 100644 --- a/test/rubygems/test_gem_spec_fetcher.rb +++ b/test/rubygems/test_gem_spec_fetcher.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/spec_fetcher' class TestGemSpecFetcher < Gem::TestCase @@ -38,7 +38,7 @@ class TestGemSpecFetcher < Gem::TestCase end def test_initialize_unwritable_home_dir - skip 'chmod not supported' if Gem.win_platform? + pend 'chmod not supported' if Gem.win_platform? FileUtils.chmod 0000, Gem.user_home diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb index 29a3e74bfc..bfd0db347e 100644 --- a/test/rubygems/test_gem_specification.rb +++ b/test/rubygems/test_gem_specification.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true require 'benchmark' -require 'rubygems/test_case' +require_relative 'helper' require 'date' require 'pathname' require 'stringio' @@ -107,8 +107,6 @@ end end @current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION - - load 'rubygems/syck_hack.rb' end def test_self_find_active_stub_by_path @@ -129,265 +127,241 @@ end end def test_self_activate_ambiguous_direct - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec("b", "1", { "c" => ">= 1" }, "lib/d#{$$}.rb") - b2 = util_spec("b", "2", { "c" => ">= 2" }, "lib/d#{$$}.rb") - c1 = util_spec "c", "1" - c2 = util_spec "c", "2" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec("b", "1", { "c" => ">= 1" }, "lib/d#{$$}.rb") + b2 = util_spec("b", "2", { "c" => ">= 2" }, "lib/d#{$$}.rb") + c1 = util_spec "c", "1" + c2 = util_spec "c", "2" - Gem::Specification.reset - install_specs c1, c2, b1, b2, a1 + Gem::Specification.reset + install_specs c1, c2, b1, b2, a1 - a1.activate - assert_equal %w[a-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + a1.activate + assert_equal %w[a-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - require "d#{$$}" + require "d#{$$}" - assert_equal %w[a-1 b-2 c-2], loaded_spec_names - assert_equal [], unresolved_names - end + assert_equal %w[a-1 b-2 c-2], loaded_spec_names + assert_equal [], unresolved_names end def test_find_in_unresolved_tree_is_not_exponentiental - save_loaded_features do - num_of_pkg = 7 - num_of_version_per_pkg = 3 - packages = (0..num_of_pkg).map do |pkgi| - (0..num_of_version_per_pkg).map do |pkg_version| - deps = Hash[((pkgi + 1)..num_of_pkg).map do |deppkgi| - ["pkg#{deppkgi}", ">= 0"] - end] - util_spec "pkg#{pkgi}", pkg_version.to_s, deps - end + num_of_pkg = 7 + num_of_version_per_pkg = 3 + packages = (0..num_of_pkg).map do |pkgi| + (0..num_of_version_per_pkg).map do |pkg_version| + deps = Hash[((pkgi + 1)..num_of_pkg).map do |deppkgi| + ["pkg#{deppkgi}", ">= 0"] + end] + util_spec "pkg#{pkgi}", pkg_version.to_s, deps end - base = util_spec "pkg_base", "1", {"pkg0" => ">= 0"} + end + base = util_spec "pkg_base", "1", {"pkg0" => ">= 0"} - Gem::Specification.reset - install_specs(*packages.flatten.reverse) - install_specs base - base.activate + Gem::Specification.reset + install_specs(*packages.flatten.reverse) + install_specs base + base.activate - tms = Benchmark.measure do - assert_raises(LoadError) { require 'no_such_file_foo' } - end - assert_operator tms.total, :<=, 10 + tms = Benchmark.measure do + assert_raise(LoadError) { require 'no_such_file_foo' } end + assert_operator tms.total, :<=, 10 end def test_self_activate_ambiguous_indirect - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 1" - b2 = util_spec "b", "2", "c" => ">= 2" - c1 = util_spec "c", "1", nil, "lib/d#{$$}.rb" - c2 = util_spec "c", "2", nil, "lib/d#{$$}.rb" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 1" + b2 = util_spec "b", "2", "c" => ">= 2" + c1 = util_spec "c", "1", nil, "lib/d#{$$}.rb" + c2 = util_spec "c", "2", nil, "lib/d#{$$}.rb" - install_specs c1, c2, b1, b2, a1 + install_specs c1, c2, b1, b2, a1 - a1.activate - assert_equal %w[a-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + a1.activate + assert_equal %w[a-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - require "d#{$$}" + require "d#{$$}" - assert_equal %w[a-1 b-2 c-2], loaded_spec_names - assert_equal [], unresolved_names - end + assert_equal %w[a-1 b-2 c-2], loaded_spec_names + assert_equal [], unresolved_names end def test_self_activate_ambiguous_indirect_conflict - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - a2 = util_spec "a", "2", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 1" - b2 = util_spec "b", "2", "c" => ">= 2" - c1 = util_spec "c", "1", nil, "lib/d#{$$}.rb" - c2 = util_spec("c", "2", { "a" => "1" }, "lib/d#{$$}.rb") # conflicts with a-2 + a1 = util_spec "a", "1", "b" => "> 0" + a2 = util_spec "a", "2", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 1" + b2 = util_spec "b", "2", "c" => ">= 2" + c1 = util_spec "c", "1", nil, "lib/d#{$$}.rb" + c2 = util_spec("c", "2", { "a" => "1" }, "lib/d#{$$}.rb") # conflicts with a-2 - install_specs c1, b1, a1, a2, c2, b2 + install_specs c1, b1, a1, a2, c2, b2 - a2.activate - assert_equal %w[a-2], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + a2.activate + assert_equal %w[a-2], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - require "d#{$$}" + require "d#{$$}" - assert_equal %w[a-2 b-1 c-1], loaded_spec_names - assert_equal [], unresolved_names - end + assert_equal %w[a-2 b-1 c-1], loaded_spec_names + assert_equal [], unresolved_names end def test_self_activate_ambiguous_unrelated - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 1" - b2 = util_spec "b", "2", "c" => ">= 2" - c1 = util_spec "c", "1" - c2 = util_spec "c", "2" - d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 1" + b2 = util_spec "b", "2", "c" => ">= 2" + c1 = util_spec "c", "1" + c2 = util_spec "c", "2" + d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" - install_specs d1, c1, c2, b1, b2, a1 + install_specs d1, c1, c2, b1, b2, a1 - a1.activate - assert_equal %w[a-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + a1.activate + assert_equal %w[a-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - require "d#{$$}" + require "d#{$$}" - assert_equal %w[a-1 d-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names - end + assert_equal %w[a-1 d-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names end def test_require_should_prefer_latest_gem_level1 - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 0" # unresolved - b2 = util_spec "b", "2", "c" => ">= 0" - c1 = util_spec "c", "1", nil, "lib/c#{$$}.rb" # 1st level - c2 = util_spec "c", "2", nil, "lib/c#{$$}.rb" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 0" # unresolved + b2 = util_spec "b", "2", "c" => ">= 0" + c1 = util_spec "c", "1", nil, "lib/c#{$$}.rb" # 1st level + c2 = util_spec "c", "2", nil, "lib/c#{$$}.rb" - install_specs c1, c2, b1, b2, a1 + install_specs c1, c2, b1, b2, a1 - a1.activate + a1.activate - require "c#{$$}" + require "c#{$$}" - assert_equal %w[a-1 b-2 c-2], loaded_spec_names - end + assert_equal %w[a-1 b-2 c-2], loaded_spec_names end def test_require_should_prefer_latest_gem_level2 - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 0" # unresolved - b2 = util_spec "b", "2", "c" => ">= 0" - c1 = util_spec "c", "1", "d" => ">= 0" # 1st level - c2 = util_spec "c", "2", "d" => ">= 0" - d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level - d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 0" # unresolved + b2 = util_spec "b", "2", "c" => ">= 0" + c1 = util_spec "c", "1", "d" => ">= 0" # 1st level + c2 = util_spec "c", "2", "d" => ">= 0" + d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level + d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" - install_specs d1, d2, c1, c2, b1, b2, a1 + install_specs d1, d2, c1, c2, b1, b2, a1 - a1.activate + a1.activate - require "d#{$$}" + require "d#{$$}" - assert_equal %w[a-1 b-2 c-2 d-2], loaded_spec_names - end + assert_equal %w[a-1 b-2 c-2 d-2], loaded_spec_names end def test_require_finds_in_2nd_level_indirect - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 0" # unresolved - b2 = util_spec "b", "2", "c" => ">= 0" - c1 = util_spec "c", "1", "d" => "<= 2" # 1st level - c2 = util_spec "c", "2", "d" => "<= 2" - d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level - d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" - d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 0" # unresolved + b2 = util_spec "b", "2", "c" => ">= 0" + c1 = util_spec "c", "1", "d" => "<= 2" # 1st level + c2 = util_spec "c", "2", "d" => "<= 2" + d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level + d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" + d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" - install_specs d1, d2, d3, c1, c2, b1, b2, a1 + install_specs d1, d2, d3, c1, c2, b1, b2, a1 - a1.activate + a1.activate - require "d#{$$}" + require "d#{$$}" - assert_equal %w[a-1 b-2 c-2 d-2], loaded_spec_names - end + assert_equal %w[a-1 b-2 c-2 d-2], loaded_spec_names end def test_require_should_prefer_reachable_gems - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 0" # unresolved - b2 = util_spec "b", "2", "c" => ">= 0" - c1 = util_spec "c", "1", "d" => "<= 2" # 1st level - c2 = util_spec "c", "2", "d" => "<= 2" - d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level - d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" - d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" - e = util_spec "anti_d", "1", nil, "lib/d#{$$}.rb" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 0" # unresolved + b2 = util_spec "b", "2", "c" => ">= 0" + c1 = util_spec "c", "1", "d" => "<= 2" # 1st level + c2 = util_spec "c", "2", "d" => "<= 2" + d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level + d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" + d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" + e = util_spec "anti_d", "1", nil, "lib/d#{$$}.rb" + + install_specs d1, d2, d3, e, c1, c2, b1, b2, a1 - install_specs d1, d2, d3, e, c1, c2, b1, b2, a1 - - a1.activate + a1.activate - require "d#{$$}" + require "d#{$$}" - assert_equal %w[a-1 b-2 c-2 d-2], loaded_spec_names - end + assert_equal %w[a-1 b-2 c-2 d-2], loaded_spec_names end def test_require_should_not_conflict - save_loaded_features do - base = util_spec "0", "1", "A" => ">= 1" - a1 = util_spec "A", "1", {"c" => ">= 2", "b" => "> 0"}, "lib/a.rb" - a2 = util_spec "A", "2", {"c" => ">= 2", "b" => "> 0"}, "lib/a.rb" - b1 = util_spec "b", "1", {"c" => "= 1"}, "lib/d#{$$}.rb" - b2 = util_spec "b", "2", {"c" => "= 2"}, "lib/d#{$$}.rb" - c1 = util_spec "c", "1", {}, "lib/c.rb" - c2 = util_spec "c", "2", {}, "lib/c.rb" - c3 = util_spec "c", "3", {}, "lib/c.rb" - - install_specs c1, c2, c3, b1, b2, a1, a2, base - - base.activate - assert_equal %w[0-1], loaded_spec_names - assert_equal ["A (>= 1)"], unresolved_names + base = util_spec "0", "1", "A" => ">= 1" + a1 = util_spec "A", "1", {"c" => ">= 2", "b" => "> 0"}, "lib/a.rb" + a2 = util_spec "A", "2", {"c" => ">= 2", "b" => "> 0"}, "lib/a.rb" + b1 = util_spec "b", "1", {"c" => "= 1"}, "lib/d#{$$}.rb" + b2 = util_spec "b", "2", {"c" => "= 2"}, "lib/d#{$$}.rb" + c1 = util_spec "c", "1", {}, "lib/c.rb" + c2 = util_spec "c", "2", {}, "lib/c.rb" + c3 = util_spec "c", "3", {}, "lib/c.rb" - require "d#{$$}" + install_specs c1, c2, c3, b1, b2, a1, a2, base - assert_equal %w[0-1 A-2 b-2 c-2], loaded_spec_names - assert_equal [], unresolved_names - end + base.activate + assert_equal %w[0-1], loaded_spec_names + assert_equal ["A (>= 1)"], unresolved_names + + require "d#{$$}" + + assert_equal %w[0-1 A-2 b-2 c-2], loaded_spec_names + assert_equal [], unresolved_names end def test_inner_clonflict_in_indirect_gems - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 1" # unresolved - b2 = util_spec "b", "2", "c" => ">= 1", "d" => "< 3" - c1 = util_spec "c", "1", "d" => "<= 2" # 1st level - c2 = util_spec "c", "2", "d" => "<= 2" - c3 = util_spec "c", "3", "d" => "<= 3" - d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level - d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" - d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 1" # unresolved + b2 = util_spec "b", "2", "c" => ">= 1", "d" => "< 3" + c1 = util_spec "c", "1", "d" => "<= 2" # 1st level + c2 = util_spec "c", "2", "d" => "<= 2" + c3 = util_spec "c", "3", "d" => "<= 3" + d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level + d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" + d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" + + install_specs d1, d2, d3, c1, c2, c3, b1, b2, a1 - install_specs d1, d2, d3, c1, c2, c3, b1, b2, a1 + a1.activate - a1.activate + require "d#{$$}" - require "d#{$$}" - - assert_includes [%w[a-1 b-2 c-3 d-2],%w[a-1 b-2 d-2]], loaded_spec_names - end + assert_includes [%w[a-1 b-2 c-3 d-2],%w[a-1 b-2 d-2]], loaded_spec_names end def test_inner_clonflict_in_indirect_gems_reversed - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "xc" => ">= 1" # unresolved - b2 = util_spec "b", "2", "xc" => ">= 1", "d" => "< 3" - c1 = util_spec "xc", "1", "d" => "<= 3" # 1st level - c2 = util_spec "xc", "2", "d" => "<= 2" - c3 = util_spec "xc", "3", "d" => "<= 3" - d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level - d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" - d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" - - install_specs d1, d2, d3, c1, c2, c3, b1, b2, a1 + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "xc" => ">= 1" # unresolved + b2 = util_spec "b", "2", "xc" => ">= 1", "d" => "< 3" + c1 = util_spec "xc", "1", "d" => "<= 3" # 1st level + c2 = util_spec "xc", "2", "d" => "<= 2" + c3 = util_spec "xc", "3", "d" => "<= 3" + d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level + d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" + d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" + + install_specs d1, d2, d3, c1, c2, c3, b1, b2, a1 - a1.activate + a1.activate - require "d#{$$}" + require "d#{$$}" - assert_includes [%w[a-1 b-2 d-2 xc-3], %w[a-1 b-2 d-2]], loaded_spec_names - end + assert_includes [%w[a-1 b-2 d-2 xc-3], %w[a-1 b-2 d-2]], loaded_spec_names end ## @@ -406,7 +380,7 @@ end c = util_spec 'c', '1.0', 'b' => '= 2.0' install_specs b1, b2, c, a - e = assert_raises Gem::LoadError do + e = assert_raise Gem::LoadError do assert_activate nil, a, c, "b" end @@ -428,7 +402,7 @@ end install_specs b1, b2, c, a - e = assert_raises Gem::ConflictError do + e = assert_raise Gem::ConflictError do assert_activate nil, a, c, "b" end @@ -511,41 +485,37 @@ end install_specs b1, b2, a1 a1.activate - save_loaded_features do - require "b/c" - end + require "b/c" assert_equal %w[a-1 b-1], loaded_spec_names end def test_self_activate_via_require_wtf - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0", "d" => "> 0" # this - b1 = util_spec "b", "1", { "c" => ">= 1" }, "lib/b#{$$}.rb" - b2 = util_spec "b", "2", { "c" => ">= 2" }, "lib/b#{$$}.rb" # this - c1 = util_spec "c", "1" - c2 = util_spec "c", "2" # this - d1 = util_spec "d", "1", { "c" => "< 2" }, "lib/d#{$$}.rb" - d2 = util_spec "d", "2", { "c" => "< 2" }, "lib/d#{$$}.rb" # this + a1 = util_spec "a", "1", "b" => "> 0", "d" => "> 0" # this + b1 = util_spec "b", "1", { "c" => ">= 1" }, "lib/b#{$$}.rb" + b2 = util_spec "b", "2", { "c" => ">= 2" }, "lib/b#{$$}.rb" # this + c1 = util_spec "c", "1" + c2 = util_spec "c", "2" # this + d1 = util_spec "d", "1", { "c" => "< 2" }, "lib/d#{$$}.rb" + d2 = util_spec "d", "2", { "c" => "< 2" }, "lib/d#{$$}.rb" # this - install_specs c1, c2, b1, b2, d1, d2, a1 + install_specs c1, c2, b1, b2, d1, d2, a1 - a1.activate + a1.activate - assert_equal %w[a-1], loaded_spec_names - assert_equal ["b (> 0)", "d (> 0)"], unresolved_names + assert_equal %w[a-1], loaded_spec_names + assert_equal ["b (> 0)", "d (> 0)"], unresolved_names - require "b#{$$}" + require "b#{$$}" - e = assert_raises Gem::LoadError do - require "d#{$$}" - end + e = assert_raise Gem::LoadError do + require "d#{$$}" + end - assert_equal "unable to find a version of 'd' to activate", e.message + assert_equal "unable to find a version of 'd' to activate", e.message - assert_equal %w[a-1 b-2 c-2], loaded_spec_names - assert_equal ["d (> 0)"], unresolved_names - end + assert_equal %w[a-1 b-2 c-2], loaded_spec_names + assert_equal ["d (> 0)"], unresolved_names end def test_self_activate_deep_unambiguous @@ -673,7 +643,7 @@ end gem "b", "= 1.0" - assert_raises Gem::LoadError do + assert_raise Gem::LoadError do gem "b", "= 2.0" end end @@ -747,125 +717,6 @@ end spec.specification_version end - def test_self_from_yaml_syck_date_bug - # This is equivalent to (and totally valid) psych 1.0 output and - # causes parse errors on syck. - yaml = @a1.to_yaml - yaml.sub!(/^date:.*/, "date: 2011-04-26 00:00:00.000000000Z") - - spec = with_syck do - Gem::Specification.from_yaml yaml - end - - assert_kind_of Time, @a1.date - assert_kind_of Time, spec.date - end - - def test_self_from_yaml_syck_default_key_bug - # This is equivalent to (and totally valid) psych 1.0 output and - # causes parse errors on syck. - yaml = <<-YAML ---- !ruby/object:Gem::Specification -name: posix-spawn -version: !ruby/object:Gem::Version - version: 0.3.6 - prerelease: -dependencies: -- !ruby/object:Gem::Dependency - name: rake-compiler - requirement: &70243867725240 !ruby/object:Gem::Requirement - none: false - requirements: - - - = - - !ruby/object:Gem::Version - version: 0.7.6 - type: :development - prerelease: false - version_requirements: *70243867725240 -platform: ruby -files: [] -test_files: [] -bindir: - YAML - - spec = with_syck do - Gem::Specification.from_yaml yaml - end - - op = spec.dependencies.first.requirement.requirements.first.first - refute_kind_of YAML::Syck::DefaultKey, op - - refute_match %r{DefaultKey}, spec.to_ruby - end - - def test_self_from_yaml_cleans_up_defaultkey - yaml = <<-YAML ---- !ruby/object:Gem::Specification -name: posix-spawn -version: !ruby/object:Gem::Version - version: 0.3.6 - prerelease: -dependencies: -- !ruby/object:Gem::Dependency - name: rake-compiler - requirement: &70243867725240 !ruby/object:Gem::Requirement - none: false - requirements: - - - !ruby/object:YAML::Syck::DefaultKey {} - - - !ruby/object:Gem::Version - version: 0.7.6 - type: :development - prerelease: false - version_requirements: *70243867725240 -platform: ruby -files: [] -test_files: [] -bindir: - YAML - - spec = Gem::Specification.from_yaml yaml - - op = spec.dependencies.first.requirement.requirements.first.first - refute_kind_of YAML::Syck::DefaultKey, op - - refute_match %r{DefaultKey}, spec.to_ruby - end - - def test_self_from_yaml_cleans_up_defaultkey_from_newer_192 - yaml = <<-YAML ---- !ruby/object:Gem::Specification -name: posix-spawn -version: !ruby/object:Gem::Version - version: 0.3.6 - prerelease: -dependencies: -- !ruby/object:Gem::Dependency - name: rake-compiler - requirement: &70243867725240 !ruby/object:Gem::Requirement - none: false - requirements: - - - !ruby/object:Syck::DefaultKey {} - - - !ruby/object:Gem::Version - version: 0.7.6 - type: :development - prerelease: false - version_requirements: *70243867725240 -platform: ruby -files: [] -test_files: [] -bindir: - YAML - - spec = Gem::Specification.from_yaml yaml - - op = spec.dependencies.first.requirement.requirements.first.first - refute_kind_of YAML::Syck::DefaultKey, op - - refute_match %r{DefaultKey}, spec.to_ruby - end - def test_self_from_yaml_cleans_up_Date_objects yaml = <<-YAML --- !ruby/object:Gem::Specification @@ -973,7 +824,7 @@ dependencies: [] io.write @a2.to_ruby_for_cache end rescue Errno::EINVAL - skip "cannot create '#{full_path}' on this platform" + pend "cannot create '#{full_path}' on this platform" end spec = Gem::Specification.load full_path @@ -992,7 +843,7 @@ dependencies: [] io.write @a2.to_ruby_for_cache end rescue Errno::EINVAL - skip "cannot create '#{full_path}' on this platform" + pend "cannot create '#{full_path}' on this platform" end spec = Gem::Specification.load full_path @@ -1011,7 +862,7 @@ dependencies: [] io.write @a2.to_ruby_for_cache end rescue Errno::EINVAL - skip "cannot create '#{full_path}' on this platform" + pend "cannot create '#{full_path}' on this platform" end spec = Gem::Specification.load full_path @@ -1345,7 +1196,7 @@ dependencies: [] spec.instance_variable_set :@licenses, (class << (Object.new);self;end) spec.loaded_from = '/path/to/file' - e = assert_raises Gem::FormatException do + e = assert_raise Gem::FormatException do spec.dup end @@ -1451,7 +1302,7 @@ dependencies: [] end def test_build_args - skip "extensions don't quite work on jruby" if Gem.java_platform? + pend "extensions don't quite work on jruby" if Gem.java_platform? ext_spec assert_empty @ext.build_args @@ -1470,10 +1321,10 @@ dependencies: [] end def test_build_extensions - skip "extensions don't quite work on jruby" if Gem.java_platform? + pend "extensions don't quite work on jruby" if Gem.java_platform? ext_spec - refute_path_exists @ext.extension_dir, 'sanity check' + assert_path_not_exist @ext.extension_dir, 'sanity check' refute_empty @ext.extensions, 'sanity check' extconf_rb = File.join @ext.gem_dir, @ext.extensions.first @@ -1491,7 +1342,7 @@ dependencies: [] @ext.build_extensions - assert_path_exists @ext.extension_dir + assert_path_exist @ext.extension_dir end def test_default_spec_stub_is_marked_default @@ -1506,7 +1357,7 @@ dependencies: [] end def test_build_extensions_built - skip "extensions don't quite work on jruby" if Gem.java_platform? + pend "extensions don't quite work on jruby" if Gem.java_platform? ext_spec refute_empty @ext.extensions, 'sanity check' @@ -1520,7 +1371,7 @@ dependencies: [] @ext.build_extensions gem_make_out = File.join @ext.extension_dir, 'gem_make.out' - refute_path_exists gem_make_out + assert_path_not_exist gem_make_out end def test_build_extensions_default_gem @@ -1541,25 +1392,25 @@ dependencies: [] spec.build_extensions - refute_path_exists spec.extension_dir + assert_path_not_exist spec.extension_dir end def test_build_extensions_error - skip "extensions don't quite work on jruby" if Gem.java_platform? + pend "extensions don't quite work on jruby" if Gem.java_platform? ext_spec refute_empty @ext.extensions, 'sanity check' - assert_raises Gem::Ext::BuildError do + assert_raise Gem::Ext::BuildError do @ext.build_extensions end end def test_build_extensions_extensions_dir_unwritable - skip 'chmod not supported' if Gem.win_platform? - skip 'skipped in root privilege' if Process.uid.zero? + pend 'chmod not supported' if Gem.win_platform? + pend 'skipped in root privilege' if Process.uid.zero? - skip "extensions don't quite work on jruby" if Gem.java_platform? + pend "extensions don't quite work on jruby" if Gem.java_platform? ext_spec refute_empty @ext.extensions, 'sanity check' @@ -1582,7 +1433,7 @@ dependencies: [] FileUtils.chmod 0555, File.join(@ext.base_dir, 'extensions') @ext.build_extensions - refute_path_exists @ext.extension_dir + assert_path_not_exist @ext.extension_dir ensure unless ($DEBUG or win_platform? or Process.uid.zero? or Gem.java_platform?) FileUtils.chmod 0755, File.join(@ext.base_dir, 'extensions') @@ -1591,8 +1442,8 @@ dependencies: [] end def test_build_extensions_no_extensions_dir_unwritable - skip 'chmod not supported' if Gem.win_platform? - skip "extensions don't quite work on jruby" if Gem.java_platform? + pend 'chmod not supported' if Gem.win_platform? + pend "extensions don't quite work on jruby" if Gem.java_platform? ext_spec refute_empty @ext.extensions, 'sanity check' @@ -1616,36 +1467,22 @@ dependencies: [] @ext.build_extensions gem_make_out = File.join @ext.extension_dir, 'gem_make.out' - refute_path_exists gem_make_out + assert_path_not_exist gem_make_out ensure FileUtils.chmod 0755, @gemhome end def test_build_extensions_none - refute_path_exists @a1.extension_dir, 'sanity check' + assert_path_not_exist @a1.extension_dir, 'sanity check' assert_empty @a1.extensions, 'sanity check' @a1.build_extensions - refute_path_exists @a1.extension_dir - end - - def test_build_extensions_old - skip "extensions don't quite work on jruby" if Gem.java_platform? - ext_spec - - refute_empty @ext.extensions, 'sanity check' - - @ext.installed_by_version = v(0) - - @ext.build_extensions - - gem_make_out = File.join @ext.extension_dir, 'gem_make.out' - refute_path_exists gem_make_out + assert_path_not_exist @a1.extension_dir end def test_build_extensions_preview - skip "extensions don't quite work on jruby" if Gem.java_platform? + pend "extensions don't quite work on jruby" if Gem.java_platform? ext_spec extconf_rb = File.join @ext.gem_dir, @ext.extensions.first @@ -1668,7 +1505,7 @@ dependencies: [] @ext.build_extensions gem_make_out = File.join @ext.extension_dir, 'gem_make.out' - assert_path_exists gem_make_out + assert_path_exist gem_make_out end def test_contains_requirable_file_eh @@ -1680,10 +1517,10 @@ dependencies: [] end def test_contains_requirable_file_eh_extension - skip "extensions don't quite work on jruby" if Gem.java_platform? + pend "extensions don't quite work on jruby" if Gem.java_platform? ext_spec - _, err = capture_io do + _, err = capture_output do refute @ext.contains_requirable_file? 'nonexistent' end @@ -1696,7 +1533,7 @@ dependencies: [] def test_contains_requirable_file_eh_extension_java_platform ext_spec(platform: Gem::Platform.new("java")) - _, err = capture_io do + _, err = capture_output do refute @ext.contains_requirable_file? 'nonexistent' end @@ -1718,7 +1555,7 @@ dependencies: [] end def test_date_equals_string_bad - assert_raises Gem::InvalidSpecificationException do + assert_raise Gem::InvalidSpecificationException do @a1.date = '9/11/2003' end end @@ -2281,43 +2118,39 @@ dependencies: [] end def test_require_already_activated - save_loaded_features do - a1 = util_spec "a", "1", nil, "lib/d#{$$}.rb" + a1 = util_spec "a", "1", nil, "lib/d#{$$}.rb" - install_specs a1 # , a2, b1, b2, c1, c2 + install_specs a1 # , a2, b1, b2, c1, c2 - a1.activate - assert_equal %w[a-1], loaded_spec_names - assert_equal [], unresolved_names + a1.activate + assert_equal %w[a-1], loaded_spec_names + assert_equal [], unresolved_names - assert require "d#{$$}" + assert require "d#{$$}" - assert_equal %w[a-1], loaded_spec_names - assert_equal [], unresolved_names - end + assert_equal %w[a-1], loaded_spec_names + assert_equal [], unresolved_names end def test_require_already_activated_indirect_conflict - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - a2 = util_spec "a", "2", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 1" - b2 = util_spec "b", "2", "c" => ">= 2" - c1 = util_spec "c", "1", nil, "lib/d#{$$}.rb" - c2 = util_spec("c", "2", { "a" => "1" }, "lib/d#{$$}.rb") # conflicts with a-2 + a1 = util_spec "a", "1", "b" => "> 0" + a2 = util_spec "a", "2", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 1" + b2 = util_spec "b", "2", "c" => ">= 2" + c1 = util_spec "c", "1", nil, "lib/d#{$$}.rb" + c2 = util_spec("c", "2", { "a" => "1" }, "lib/d#{$$}.rb") # conflicts with a-2 - install_specs c1, b1, a1, a2, c2, b2 + install_specs c1, b1, a1, a2, c2, b2 - a1.activate - c1.activate - assert_equal %w[a-1 c-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + a1.activate + c1.activate + assert_equal %w[a-1 c-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - assert require "d#{$$}" + assert require "d#{$$}" - assert_equal %w[a-1 c-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names - end + assert_equal %w[a-1 c-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names end def test_requirements @@ -2449,7 +2282,7 @@ end def test_to_ruby_with_rsa_key require 'rubygems/openssl' - skip 'openssl is missing' unless defined?(OpenSSL::PKey::RSA) + pend 'openssl is missing' unless defined?(OpenSSL::PKey::RSA) rsa_key = OpenSSL::PKey::RSA.new(2048) @a2.signing_key = rsa_key @@ -2633,7 +2466,7 @@ end def test_to_yaml yaml_str = @a1.to_yaml - refute_match '!!null', yaml_str + refute_match %r{!!null}, yaml_str same_spec = Gem::Specification.from_yaml(yaml_str) @@ -2663,7 +2496,7 @@ end yaml_str = @a1.to_yaml - same_spec = YAML.load yaml_str + same_spec = load_yaml yaml_str assert_equal Gem::Platform.new('powerpc-darwin7'), same_spec.platform assert_equal 'powerpc-darwin7.9.0', same_spec.original_platform @@ -2704,7 +2537,7 @@ end assert_equal [], @a1.authors - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end @@ -2712,7 +2545,7 @@ end @a1.authors = ["#{f} (who is writing this software)"] - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end @@ -2720,7 +2553,7 @@ end @a1.authors = ["#{t} (who is writing this software)"] - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end @@ -2799,7 +2632,7 @@ end @a1.add_development_dependency 'c', '>= 1.2.3' use_ui @ui do - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end @@ -2903,7 +2736,7 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use: @a1.description = "#{f} (describe your package)" - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end @@ -2911,11 +2744,39 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use: @a1.description = "#{t} (describe your package)" - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end assert_equal %("#{f}" or "#{t}" is not a description), e.message + + # Adding #{f} anywhere after the start of the description should be fine. + @a1.description = "(some description) #{f}" + + assert_nothing_raised do + @a1.validate + end + + # Adding #{t} anywhere after the start of the description should be fine. + @a1.description = "(some description) #{t}" + + assert_nothing_raised do + @a1.validate + end + + # Adding #{f} at the start of the second or later line should be fine. + @a1.description = "(some description)\n#{f}" + + assert_nothing_raised do + @a1.validate + end + + # Adding #{t} at the start of the second or later line should be fine. + @a1.description = "(some description)\n#{t}" + + assert_nothing_raised do + @a1.validate + end end end @@ -2925,7 +2786,7 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use: Dir.chdir @tempdir do @a1.email = "FIxxxXME (your e-mail)".sub(/xxx/, "") - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end @@ -2933,7 +2794,7 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use: @a1.email = "#{t} (your e-mail)" - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end @@ -2942,7 +2803,7 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use: end def test_validate_empty - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do Gem::Specification.new.validate end @@ -2950,7 +2811,7 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use: end def test_validate_error - assert_raises Gem::InvalidSpecificationException do + assert_raise Gem::InvalidSpecificationException do use_ui @ui do Gem::Specification.new.validate end @@ -2980,12 +2841,12 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use: def test_validate_empty_require_paths if win_platform? - skip 'test_validate_empty_require_paths skipped on MS Windows (symlink)' + pend 'test_validate_empty_require_paths skipped on MS Windows (symlink)' else util_setup_validate @a1.require_paths = [] - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end @@ -2995,7 +2856,7 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use: end def test_validate_files - skip 'test_validate_files skipped on MS Windows (symlink)' if win_platform? + pend 'test_validate_files skipped on MS Windows (symlink)' if win_platform? util_setup_validate @a1.files += ['lib', 'lib2'] @@ -3035,9 +2896,11 @@ WARN: Clearing out unresolved specs. Try 'gem cleanup <gem>' Please report a bug if this causes problems. EXPECTED - assert_output nil, expected do + actual_stdout, actual_stderr = capture_output do specification.reset end + assert_empty actual_stdout + assert_equal(expected, actual_stderr) end def test_unresolved_specs_with_versions @@ -3066,16 +2929,20 @@ WARN: Clearing out unresolved specs. Try 'gem cleanup <gem>' Please report a bug if this causes problems. EXPECTED - assert_output nil, expected do + actual_stdout, actual_stderr = capture_output do specification.reset end + assert_empty actual_stdout + assert_equal(expected, actual_stderr) end def test_duplicate_runtime_dependency expected = "WARNING: duplicated b dependency [\"~> 3.0\", \"~> 3.0\"]\n" - assert_output nil, expected do + out, err = capture_output do @a1.add_runtime_dependency "b", "~> 3.0", "~> 3.0" end + assert_empty out + assert_equal(expected, err) end def set_orig(cls) @@ -3090,7 +2957,7 @@ Please report a bug if this causes problems. @a1.files = [@a1.file_name] - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end @@ -3122,7 +2989,7 @@ Please report a bug if this causes problems. @a1.homepage = 'over at my cool site' - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end @@ -3130,7 +2997,7 @@ Please report a bug if this causes problems. @a1.homepage = 'ftp://rubygems.org' - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end @@ -3199,6 +3066,17 @@ http://spdx.org/licenses or 'Nonstandard' for a nonstandard license. WARNING end + def test_validate_license_ref + util_setup_validate + + use_ui @ui do + @a1.licenses = ['LicenseRef-LICENSE.md'] + @a1.validate + end + + assert_empty @ui.error + end + def test_validate_license_values_plus util_setup_validate @@ -3319,7 +3197,7 @@ Did you mean 'Ruby'? def test_validate_name util_setup_validate - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.name = :json @a1.validate end @@ -3327,31 +3205,31 @@ Did you mean 'Ruby'? assert_equal 'invalid value for attribute name: ":json" must be a string', e.message @a1.name = [] - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end assert_equal "invalid value for attribute name: \"[]\" must be a string", e.message @a1.name = "" - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end assert_equal "invalid value for attribute name: \"\" must include at least one letter", e.message @a1.name = "12345" - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end assert_equal "invalid value for attribute name: \"12345\" must include at least one letter", e.message @a1.name = "../malicious" - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end assert_equal "invalid value for attribute name: \"../malicious\" can only include letters, numbers, dashes, and underscores", e.message @a1.name = "\ba\t" - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end assert_equal "invalid value for attribute name: \"\\ba\\t\" can only include letters, numbers, dashes, and underscores", e.message @@ -3368,7 +3246,7 @@ Did you mean 'Ruby'? spec = @a1.dup spec.instance_variable_set "@#{name}", nil - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do spec.validate end @@ -3378,7 +3256,7 @@ Did you mean 'Ruby'? end def test_validate_permissions - skip 'chmod not supported' if Gem.win_platform? + pend 'chmod not supported' if Gem.win_platform? util_setup_validate @@ -3397,7 +3275,7 @@ Did you mean 'Ruby'? end def test_validate_permissions_of_missing_file_non_packaging - skip 'chmod not supported' if Gem.win_platform? + pend 'chmod not supported' if Gem.win_platform? util_setup_validate @@ -3429,7 +3307,7 @@ Did you mean 'Ruby'? util_setup_validate @a1.rubygems_version = "3" - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end @@ -3443,7 +3321,7 @@ Did you mean 'Ruby'? Dir.chdir @tempdir do @a1.specification_version = '1.0' - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do use_ui @ui do @a1.validate end @@ -3468,7 +3346,7 @@ Did you mean 'Ruby'? @a1.summary = "#{f} (describe your package)" - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end @@ -3476,7 +3354,7 @@ Did you mean 'Ruby'? @a1.summary = "#{t} (describe your package)" - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @a1.validate end @@ -3529,7 +3407,7 @@ Did you mean 'Ruby'? specfile.write "raise 'boom'" specfile.close begin - capture_io do + capture_output do Gem::Specification.load(specfile.path) end rescue => e @@ -3599,7 +3477,7 @@ Did you mean 'Ruby'? s.metadata = { 1 => "fail" } end - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @m2.validate end @@ -3616,11 +3494,11 @@ Did you mean 'Ruby'? s.metadata = { ("x" * 129) => "fail" } end - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @m2.validate end - assert_equal "metadata key too large (129 > 128)", e.message + assert_equal "metadata key is too large (129 > 128)", e.message end end @@ -3633,11 +3511,11 @@ Did you mean 'Ruby'? s.metadata = { 'fail' => [] } end - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @m2.validate end - assert_equal "metadata values must be a String", e.message + assert_equal "metadata['fail'] value must be a String", e.message end end @@ -3650,11 +3528,11 @@ Did you mean 'Ruby'? s.metadata = { 'fail' => ("x" * 1025) } end - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @m2.validate end - assert_equal "metadata value too large (1025 > 1024)", e.message + assert_equal "metadata['fail'] value is too large (1025 > 1024)", e.message end end @@ -3667,7 +3545,7 @@ Did you mean 'Ruby'? s.metadata = { 'homepage_uri' => 'http:/example.com' } end - e = assert_raises Gem::InvalidSpecificationException do + e = assert_raise Gem::InvalidSpecificationException do @m2.validate end @@ -3707,7 +3585,7 @@ end end def test_missing_extensions_eh - skip "extensions don't quite work on jruby" if Gem.java_platform? + pend "extensions don't quite work on jruby" if Gem.java_platform? ext_spec assert @ext.missing_extensions? @@ -3737,18 +3615,6 @@ end refute spec.missing_extensions? end - def test_missing_extensions_eh_legacy - ext_spec - - @ext.installed_by_version = v '2.2.0.preview.2' - - assert @ext.missing_extensions? - - @ext.installed_by_version = v '2.2.0.preview.1' - - refute @ext.missing_extensions? - end - def test_missing_extensions_eh_none refute @a1.missing_extensions? end @@ -3786,7 +3652,7 @@ end assert Gem::Specification.find_by_name "a", "1" assert Gem::Specification.find_by_name "a", ">1" - assert_raises Gem::MissingSpecError do + assert_raise Gem::MissingSpecError do Gem::Specification.find_by_name "monkeys" end end @@ -3807,7 +3673,7 @@ end assert Gem::Specification.find_by_name "b" - assert_raises Gem::MissingSpecVersionError do + assert_raise Gem::MissingSpecVersionError do Gem::Specification.find_by_name "b", "1" end @@ -3880,49 +3746,6 @@ end end end - def with_syck - begin - verbose, $VERBOSE = $VERBOSE, nil - require "yaml" - old_engine = YAML::ENGINE.yamler - YAML::ENGINE.yamler = 'syck' - load 'rubygems/syck_hack.rb' - rescue NameError - # probably on 1.8, ignore - ensure - $VERBOSE = verbose - end - - yield - ensure - begin - YAML::ENGINE.yamler = old_engine - load 'rubygems/syck_hack.rb' - rescue NameError - # ignore - end - end - - def with_psych - begin - require "yaml" - old_engine = YAML::ENGINE.yamler - YAML::ENGINE.yamler = 'psych' - load 'rubygems/syck_hack.rb' - rescue NameError - # probably on 1.8, ignore - end - - yield - ensure - begin - YAML::ENGINE.yamler = old_engine - load 'rubygems/syck_hack.rb' - rescue NameError - # ignore - end - end - def silence_warnings old_verbose, $VERBOSE = $VERBOSE, false yield diff --git a/test/rubygems/test_gem_stream_ui.rb b/test/rubygems/test_gem_stream_ui.rb index a62e9ea0cf..dc245c342a 100644 --- a/test/rubygems/test_gem_stream_ui.rb +++ b/test/rubygems/test_gem_stream_ui.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/user_interaction' require 'timeout' @@ -90,7 +90,7 @@ class TestGemStreamUI < Gem::TestCase @in.tty = false Timeout.timeout(SHORT_TIMEOUT) do - assert_raises(Gem::OperationNotSupportedError) do + assert_raise(Gem::OperationNotSupportedError) do @sui.ask_yes_no("do coconuts migrate?") end end diff --git a/test/rubygems/test_gem_stub_specification.rb b/test/rubygems/test_gem_stub_specification.rb index 2ee94dcf8d..e008391ef7 100644 --- a/test/rubygems/test_gem_stub_specification.rb +++ b/test/rubygems/test_gem_stub_specification.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require "rubygems/test_case" +require_relative "helper" require "rubygems/stub_specification" class TestStubSpecification < Gem::TestCase @@ -65,9 +65,9 @@ class TestStubSpecification < Gem::TestCase end def test_contains_requirable_file_eh_extension - skip "I guess making the stub match the running platform should work" if Gem.java_platform? + pend "I guess making the stub match the running platform should work" if Gem.java_platform? stub_with_extension do |stub| - _, err = capture_io do + _, err = capture_output do refute stub.contains_requirable_file? 'nonexistent' end @@ -122,7 +122,7 @@ class TestStubSpecification < Gem::TestCase end def test_missing_extensions_eh - skip "I guess making the stub match the running platform should work" if Gem.java_platform? + pend "I guess making the stub match the running platform should work" if Gem.java_platform? stub = stub_with_extension do |s| extconf_rb = File.join s.gem_dir, s.extensions.first FileUtils.mkdir_p File.dirname extconf_rb @@ -189,7 +189,7 @@ class TestStubSpecification < Gem::TestCase def test_to_spec_missing_extensions stub = stub_with_extension - capture_io do + capture_output do stub.contains_requirable_file? 'nonexistent' end diff --git a/test/rubygems/test_gem_text.rb b/test/rubygems/test_gem_text.rb index d8e4b75f3a..069cfdc4e6 100644 --- a/test/rubygems/test_gem_text.rb +++ b/test/rubygems/test_gem_text.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require "rubygems/text" class TestGemText < Gem::TestCase diff --git a/test/rubygems/test_gem_uninstaller.rb b/test/rubygems/test_gem_uninstaller.rb index 1ca4991d1e..9e18972864 100644 --- a/test/rubygems/test_gem_uninstaller.rb +++ b/test/rubygems/test_gem_uninstaller.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/installer_test_case' +require_relative 'installer_test_case' require 'rubygems/uninstaller' class TestGemUninstaller < Gem::InstallerTestCase @@ -54,7 +54,7 @@ class TestGemUninstaller < Gem::InstallerTestCase uninstaller.remove_all [@spec] end - refute_path_exists @spec.gem_dir + assert_path_not_exist @spec.gem_dir end def test_remove_executables_force_keep @@ -138,7 +138,7 @@ class TestGemUninstaller < Gem::InstallerTestCase Dir.mkdir "#{@gemhome}2" uninstaller = Gem::Uninstaller.new nil, :install_dir => "#{@gemhome}2" - e = assert_raises Gem::GemNotInHomeException do + e = assert_raise Gem::GemNotInHomeException do use_ui ui do uninstaller.remove @spec end @@ -149,11 +149,11 @@ class TestGemUninstaller < Gem::InstallerTestCase assert_equal expected, e.message - assert_path_exists @spec.gem_dir + assert_path_exist @spec.gem_dir end def test_remove_symlinked_gem_home - skip "Symlinks not supported or not enabled" unless symlink_supported? + pend "Symlinks not supported or not enabled" unless symlink_supported? Dir.mktmpdir("gem_home") do |dir| symlinked_gem_home = "#{dir}/#{File.basename(@gemhome)}" @@ -166,7 +166,7 @@ class TestGemUninstaller < Gem::InstallerTestCase uninstaller.remove @spec end - refute_path_exists @spec.gem_dir + assert_path_not_exist @spec.gem_dir end end @@ -295,9 +295,16 @@ class TestGemUninstaller < Gem::InstallerTestCase uninstaller = Gem::Uninstaller.new spec.name, :executables => true - uninstaller.uninstall - - refute_path_exists spec.gem_dir + ui = Gem::MockGemUi.new "1\ny\n" + use_ui ui do + uninstaller.uninstall + end + expected = "Successfully uninstalled default-2\n" \ + "There was both a regular copy and a default copy of default-2. The " \ + "regular copy was successfully uninstalled, but the default copy " \ + "was left around because default gems can't be removed.\n" + assert_equal expected, ui.output + assert_path_not_exist spec.gem_dir end def test_uninstall_extension @@ -318,18 +325,18 @@ create_makefile '#{@spec.name}' installer.install end - assert_path_exists @spec.extension_dir, 'sanity check' + assert_path_exist @spec.extension_dir, 'sanity check' uninstaller = Gem::Uninstaller.new @spec.name, :executables => true uninstaller.uninstall - refute_path_exists @spec.extension_dir + assert_path_not_exist @spec.extension_dir end def test_uninstall_nonexistent uninstaller = Gem::Uninstaller.new 'bogus', :executables => true - e = assert_raises Gem::InstallError do + e = assert_raise Gem::InstallError do uninstaller.uninstall end @@ -351,7 +358,7 @@ create_makefile '#{@spec.name}' ui = Gem::MockGemUi.new "n\n" - assert_raises Gem::DependencyRemovalException do + assert_raise Gem::DependencyRemovalException do use_ui ui do uninstaller.uninstall end @@ -371,16 +378,16 @@ create_makefile '#{@spec.name}' gem_dir = File.join @user_spec.gem_dir Gem.pre_uninstall do - assert_path_exists gem_dir + assert_path_exist gem_dir end Gem.post_uninstall do - refute_path_exists gem_dir + assert_path_not_exist gem_dir end uninstaller.uninstall - refute_path_exists gem_dir + assert_path_not_exist gem_dir assert_same uninstaller, @pre_uninstall_hook_arg assert_same uninstaller, @post_uninstall_hook_arg @@ -392,7 +399,7 @@ create_makefile '#{@spec.name}' uninstaller = Gem::Uninstaller.new @spec.name, :executables => true - e = assert_raises Gem::InstallError do + e = assert_raise Gem::InstallError do uninstaller.uninstall end @@ -546,7 +553,7 @@ create_makefile '#{@spec.name}' un = Gem::Uninstaller.new('q', :abort_on_dependent => true) ui = Gem::MockGemUi.new("y\n") - assert_raises Gem::DependencyRemovalException do + assert_raise Gem::DependencyRemovalException do use_ui ui do un.uninstall end @@ -619,7 +626,7 @@ create_makefile '#{@spec.name}' end FileUtils.stub :rm_r, stub_rm_r do - assert_raises Gem::UninstallError do + assert_raise Gem::UninstallError do uninstaller.uninstall end end diff --git a/test/rubygems/test_gem_unsatisfiable_dependency_error.rb b/test/rubygems/test_gem_unsatisfiable_dependency_error.rb index 7950efb2a9..f9d30123f0 100644 --- a/test/rubygems/test_gem_unsatisfiable_dependency_error.rb +++ b/test/rubygems/test_gem_unsatisfiable_dependency_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestGemUnsatisfiableDependencyError < Gem::TestCase def setup diff --git a/test/rubygems/test_gem_uri.rb b/test/rubygems/test_gem_uri.rb new file mode 100644 index 0000000000..7fe572518b --- /dev/null +++ b/test/rubygems/test_gem_uri.rb @@ -0,0 +1,39 @@ +require_relative 'helper' +require 'rubygems/uri' + +class TestUri < Gem::TestCase + def test_to_s_not_string + assert_equal "not_a_uri", Gem::Uri.new(:not_a_uri).to_s + end + + def test_to_s_invalid_uri + assert_equal "https://www.example.com:80index", Gem::Uri.new("https://www.example.com:80index").to_s + end + + def test_redacted_with_user_pass + assert_equal "https://user:REDACTED@example.com", Gem::Uri.new("https://user:pass@example.com").redacted.to_s + end + + def test_redacted_with_token + assert_equal "https://REDACTED@example.com", Gem::Uri.new("https://token@example.com").redacted.to_s + end + + def test_redacted_with_user_x_oauth_basic + assert_equal "https://REDACTED:x-oauth-basic@example.com", Gem::Uri.new("https://token:x-oauth-basic@example.com").redacted.to_s + end + + def test_redacted_without_credential + assert_equal "https://www.example.com", Gem::Uri.new("https://www.example.com").redacted.to_s + end + + def test_redacted_with_invalid_uri + assert_equal "https://www.example.com:80index", Gem::Uri.new("https://www.example.com:80index").redacted.to_s + end + + def test_redacted_does_not_modify_uri + url = 'https://user:password@example.com' + uri = Gem::Uri.new(url) + assert_equal 'https://user:REDACTED@example.com', uri.redacted.to_s + assert_equal url, uri.to_s + end +end diff --git a/test/rubygems/test_gem_uri_formatter.rb b/test/rubygems/test_gem_uri_formatter.rb index debc7739cb..a41c9238ad 100644 --- a/test/rubygems/test_gem_uri_formatter.rb +++ b/test/rubygems/test_gem_uri_formatter.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/uri_formatter' class TestGemUriFormatter < Gem::TestCase diff --git a/test/rubygems/test_gem_util.rb b/test/rubygems/test_gem_util.rb index 7197f664e2..e0db5c283d 100644 --- a/test/rubygems/test_gem_util.rb +++ b/test/rubygems/test_gem_util.rb @@ -1,23 +1,25 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/util' class TestGemUtil < Gem::TestCase def test_class_popen - skip "popen with a block does not behave well on jruby" if Gem.java_platform? + pend "popen with a block does not behave well on jruby" if Gem.java_platform? assert_equal "0\n", Gem::Util.popen(*ruby_with_rubygems_in_load_path, '-e', 'p 0') - assert_raises Errno::ECHILD do + assert_raise Errno::ECHILD do Process.wait(-1) end end def test_silent_system - skip if Gem.java_platform? + pend if Gem.java_platform? Gem::Deprecate.skip_during do - assert_silent do + out, err = capture_output do Gem::Util.silent_system(*ruby_with_rubygems_in_load_path, '-e', 'puts "hello"; warn "hello"') end + assert_empty out + assert_empty err end end @@ -33,14 +35,14 @@ class TestGemUtil < Gem::TestCase end def test_traverse_parents_does_not_crash_on_permissions_error - skip 'skipped on MS Windows (chmod has no effect)' if win_platform? || java_platform? + pend 'skipped on MS Windows (chmod has no effect)' if win_platform? || java_platform? FileUtils.mkdir_p 'd/e/f' # remove 'execute' permission from "e" directory and make it # impossible to cd into it and its children FileUtils.chmod(0666, 'd/e') - skip 'skipped in root privilege' if Process.uid.zero? + pend 'skipped in root privilege' if Process.uid.zero? paths = Gem::Util.traverse_parents('d/e/f').to_a diff --git a/test/rubygems/test_gem_validator.rb b/test/rubygems/test_gem_validator.rb index 5158543fa9..8090776b4a 100644 --- a/test/rubygems/test_gem_validator.rb +++ b/test/rubygems/test_gem_validator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "rubygems/test_case" +require_relative "helper" require "rubygems/validator" class TestGemValidator < Gem::TestCase diff --git a/test/rubygems/test_gem_version.rb b/test/rubygems/test_gem_version.rb index 7b382809c9..422e1ee86c 100644 --- a/test/rubygems/test_gem_version.rb +++ b/test/rubygems/test_gem_version.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require "rubygems/version" -require "minitest/benchmark" - class TestGemVersion < Gem::TestCase class V < ::Gem::Version end @@ -47,9 +45,11 @@ class TestGemVersion < Gem::TestCase assert_equal false, Gem::Version.correct?("an incorrect version") expected = "nil versions are discouraged and will be deprecated in Rubygems 4\n" - assert_output nil, expected do + actual_stdout, actual_stderr = capture_output do Gem::Version.correct?(nil) end + assert_empty actual_stdout + assert_equal(expected, actual_stderr) end def test_class_new_subclass @@ -100,7 +100,7 @@ class TestGemVersion < Gem::TestCase invalid_versions << "2.3422222.222.222222222.22222.ads0as.dasd0.ddd2222.2.qd3e." invalid_versions.each do |invalid| - e = assert_raises ArgumentError, invalid do + e = assert_raise ArgumentError, invalid do Gem::Version.new invalid end @@ -108,15 +108,6 @@ class TestGemVersion < Gem::TestCase end end - def bench_anchored_version_pattern - assert_performance_linear 0.5 do |count| - version_string = count.times.map {|i| "0" * i.succ }.join(".") << "." - version_string =~ Gem::Version::ANCHORED_VERSION_PATTERN - end - rescue RegexpError - skip "It fails to allocate the memory for regex pattern of Gem::Version::ANCHORED_VERSION_PATTERN" - end - def test_empty_version ["", " ", " "].each do |empty| assert_equal "0", Gem::Version.new(empty).version diff --git a/test/rubygems/test_gem_version_option.rb b/test/rubygems/test_gem_version_option.rb index 49a8513dbe..74d7979e3a 100644 --- a/test/rubygems/test_gem_version_option.rb +++ b/test/rubygems/test_gem_version_option.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems/command' require 'rubygems/version_option' diff --git a/test/rubygems/test_kernel.rb b/test/rubygems/test_kernel.rb index 0fa36a0dec..dee36d05ee 100644 --- a/test/rubygems/test_kernel.rb +++ b/test/rubygems/test_kernel.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestKernel < Gem::TestCase def setup @@ -8,6 +8,8 @@ class TestKernel < Gem::TestCase @old_path = $:.dup util_make_gems + + without_any_upwards_gemfiles end def teardown @@ -38,7 +40,7 @@ class TestKernel < Gem::TestCase def test_gem_re_gem_mismatch assert gem('a', '=1') - assert_raises Gem::LoadError do + assert_raise Gem::LoadError do gem('a', '= 2') end @@ -65,7 +67,7 @@ class TestKernel < Gem::TestCase def test_gem_env_req ENV["GEM_REQUIREMENT_A"] = '~> 2.0' - assert_raises(Gem::MissingSpecVersionError) { gem('a', '= 1') } + assert_raise(Gem::MissingSpecVersionError) { gem('a', '= 1') } assert gem('a', '> 1') assert_equal @a2, Gem.loaded_specs['a'] end @@ -73,7 +75,7 @@ class TestKernel < Gem::TestCase def test_gem_conflicting assert gem('a', '= 1'), "Should load" - ex = assert_raises Gem::LoadError do + ex = assert_raise Gem::LoadError do gem 'a', '= 2' end @@ -120,7 +122,7 @@ class TestKernel < Gem::TestCase quick_gem 'bundler', '1' quick_gem 'bundler', '2.a' - e = assert_raises Gem::MissingSpecVersionError do + e = assert_raise Gem::MissingSpecVersionError do gem('bundler') end assert_match "Could not find 'bundler' (55) required by reason.", e.message diff --git a/test/rubygems/test_project_sanity.rb b/test/rubygems/test_project_sanity.rb index 831a2c00aa..e9e3bfd1ba 100644 --- a/test/rubygems/test_project_sanity.rb +++ b/test/rubygems/test_project_sanity.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require "rubygems/test_case" +require_relative "helper" require "open3" class TestProjectSanity < Gem::TestCase def test_manifest_is_up_to_date - skip unless File.exist?(File.expand_path("../../../Rakefile", __FILE__)) + pend unless File.exist?(File.expand_path("../../../Rakefile", __FILE__)) _, status = Open3.capture2e("rake check_manifest") diff --git a/test/rubygems/test_remote_fetch_error.rb b/test/rubygems/test_remote_fetch_error.rb index 29aaaa8adb..b9e58389d3 100644 --- a/test/rubygems/test_remote_fetch_error.rb +++ b/test/rubygems/test_remote_fetch_error.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' class TestRemoteFetchError < Gem::TestCase def test_password_redacted error = Gem::RemoteFetcher::FetchError.new('There was an error fetching', 'https://user:secret@gemsource.org') - refute_match 'secret', error.to_s + refute_match %r{secret}, error.to_s end def test_invalid_url diff --git a/test/rubygems/test_require.rb b/test/rubygems/test_require.rb index 8c7d06edc3..56774116a9 100644 --- a/test/rubygems/test_require.rb +++ b/test/rubygems/test_require.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/test_case' +require_relative 'helper' require 'rubygems' class TestGemRequire < Gem::TestCase @@ -24,16 +24,6 @@ class TestGemRequire < Gem::TestCase end end - def setup - super - - @old_loaded_features = $LOADED_FEATURES.dup - assert_raises LoadError do - require 'test_gem_require_a' - end - $LOADED_FEATURES.replace @old_loaded_features - end - def assert_require(path) assert require(path), "'#{path}' was already required" end @@ -88,8 +78,6 @@ class TestGemRequire < Gem::TestCase FileUtils.mkdir_p File.dirname c_rb File.open(c_rb, 'w') {|f| f.write "class Object; HELLO = 'world' end" } - lp = $LOAD_PATH.dup - # Pretend to provide a commandline argument that overrides a file in gem b $LOAD_PATH.unshift dash_i_arg @@ -98,7 +86,6 @@ class TestGemRequire < Gem::TestCase assert_equal "world", ::Object::HELLO assert_equal %w[a-1 b-1], loaded_spec_names ensure - $LOAD_PATH.replace lp Object.send :remove_const, :HELLO if Object.const_defined? :HELLO end @@ -132,8 +119,6 @@ class TestGemRequire < Gem::TestCase assert_require 'test_gem_require_a' - lp = $LOAD_PATH.dup - # Pretend to provide a commandline argument that overrides a file in gem b $LOAD_PATH.unshift dash_i_arg @@ -142,27 +127,20 @@ class TestGemRequire < Gem::TestCase assert_equal "world", ::Object::HELLO assert_equal %w[a-1 b-1], loaded_spec_names ensure - $LOAD_PATH.replace lp Object.send :remove_const, :HELLO if Object.const_defined? :HELLO end def test_dash_i_respects_default_library_extension_priority - skip "extensions don't quite work on jruby" if Gem.java_platform? - skip "not installed yet" unless RbConfig::TOPDIR + pend "extensions don't quite work on jruby" if Gem.java_platform? + pend "not installed yet" unless RbConfig::TOPDIR dash_i_ext_arg = util_install_extension_file('a') dash_i_lib_arg = util_install_ruby_file('a') - lp = $LOAD_PATH.dup - - begin - $LOAD_PATH.unshift dash_i_lib_arg - $LOAD_PATH.unshift dash_i_ext_arg - assert_require 'a' - assert_match(/a\.rb$/, $LOADED_FEATURES.last) - ensure - $LOAD_PATH.replace lp - end + $LOAD_PATH.unshift dash_i_lib_arg + $LOAD_PATH.unshift dash_i_ext_arg + assert_require 'a' + assert_match(/a\.rb$/, $LOADED_FEATURES.last) end def test_concurrent_require @@ -245,12 +223,12 @@ class TestGemRequire < Gem::TestCase end def test_activate_via_require_respects_loaded_files - skip "Not sure what's going on. If another spec creates a 'a' gem before + pend "Not sure what's going on. If another spec creates a 'a' gem before this test, somehow require will load the benchmark in b, and ignore that the stdlib one is already in $LOADED_FEATURES?. Reproducible by running the spaceship_specific_file test before this one" if java_platform? - skip "not installed yet" unless RbConfig::TOPDIR + pend "not installed yet" unless RbConfig::TOPDIR lib_dir = File.expand_path("../../lib", File.dirname(__FILE__)) rubylibdir = File.realdirpath(RbConfig::CONFIG["rubylibdir"]) @@ -359,7 +337,7 @@ class TestGemRequire < Gem::TestCase assert_equal %w[a-1 c-1], loaded_spec_names assert_equal ["b (> 0)", "x (> 0)"], unresolved_names - e = assert_raises(Gem::LoadError) do + e = assert_raise(Gem::LoadError) do require("ib") end @@ -382,7 +360,7 @@ class TestGemRequire < Gem::TestCase assert_equal %w[a-1 c-1], loaded_spec_names assert_equal ["b (> 0)"], unresolved_names - e = assert_raises(Gem::LoadError) do + e = assert_raise(Gem::LoadError) do require("ib") end @@ -466,8 +444,7 @@ class TestGemRequire < Gem::TestCase end def test_realworld_default_gem - testing_ruby_repo = !ENV["GEM_COMMAND"].nil? - skip "this test can't work under ruby-core setup" if testing_ruby_repo || java_platform? + omit "this test can't work under ruby-core setup" if testing_ruby_repo? cmd = <<-RUBY $stderr = $stdout @@ -480,8 +457,7 @@ class TestGemRequire < Gem::TestCase end def test_realworld_upgraded_default_gem - testing_ruby_repo = !ENV["GEM_COMMAND"].nil? - skip "this test can't work under ruby-core setup" if testing_ruby_repo + omit "this test can't work under ruby-core setup" if testing_ruby_repo? newer_json = util_spec("json", "999.99.9", nil, ["lib/json.rb"]) install_gem newer_json @@ -626,7 +602,7 @@ class TestGemRequire < Gem::TestCase b2a = util_spec('bundler', '2.a', nil, "lib/bundler/setup.rb") install_specs b1, b2a - e = assert_raises Gem::MissingSpecVersionError do + e = assert_raise Gem::MissingSpecVersionError do gem('bundler') end assert_match "Could not find 'bundler' (55) required by reason.", e.message @@ -679,8 +655,6 @@ class TestGemRequire < Gem::TestCase end def test_no_crash_when_overriding_warn_with_warning_module - skip "https://github.com/oracle/truffleruby/issues/2109" if RUBY_ENGINE == "truffleruby" - Dir.mktmpdir("warn_test") do |dir| File.write(dir + "/main.rb", "module Warning; def warn(str); super; end; end; warn 'Foo Bar'") _, err = capture_subprocess_io do @@ -722,6 +696,10 @@ class TestGemRequire < Gem::TestCase private + def testing_ruby_repo? + !ENV["GEM_COMMAND"].nil? + end + def silence_warnings old_verbose, $VERBOSE = $VERBOSE, false yield @@ -753,12 +731,12 @@ class TestGemRequire < Gem::TestCase spec.files += ["extconf.rb", "depend", "#{name}.c"] so = File.join(spec.gem_dir, "#{name}.#{RbConfig::CONFIG["DLEXT"]}") - refute_path_exists so + assert_path_not_exist so path = Gem::Package.build spec installer = Gem::Installer.at path installer.install - assert_path_exists so + assert_path_exist so spec.gem_dir end diff --git a/test/rubygems/test_rubygems.rb b/test/rubygems/test_rubygems.rb new file mode 100644 index 0000000000..493b9fdf4a --- /dev/null +++ b/test/rubygems/test_rubygems.rb @@ -0,0 +1,44 @@ +require_relative 'helper' + +class GemTest < Gem::TestCase + def test_rubygems_normal_behaviour + _ = Gem::Util.popen(*ruby_with_rubygems_in_load_path, '-e', "'require \"rubygems\"'", {:err => [:child, :out]}).strip + assert $?.success? + end + + def test_operating_system_other_exceptions + pend "does not apply to truffleruby" if RUBY_ENGINE == 'truffleruby' + + path = util_install_operating_system_rb <<-RUBY + intentionally_not_implemented_method + RUBY + + output = Gem::Util.popen(*ruby_with_rubygems_and_fake_operating_system_in_load_path(path), '-e', "'require \"rubygems\"'", {:err => [:child, :out]}).strip + assert !$?.success? + assert_includes output, "undefined local variable or method `intentionally_not_implemented_method'" + assert_includes output, "Loading the rubygems/defaults/operating_system.rb file caused an error. " \ + "This file is owned by your OS, not by rubygems upstream. " \ + "Please find out which OS package this file belongs to and follow the guidelines from your OS to report " \ + "the problem and ask for help." + end + + private + + def util_install_operating_system_rb(content) + dir_lib = Dir.mktmpdir("test_operating_system_lib", @tempdir) + dir_lib_arg = File.join dir_lib + + dir_lib_rubygems_defaults_arg = File.join dir_lib_arg, "lib", "rubygems", "defaults" + FileUtils.mkdir_p dir_lib_rubygems_defaults_arg + + operating_system_rb = File.join dir_lib_rubygems_defaults_arg, "operating_system.rb" + + File.open(operating_system_rb, 'w') {|f| f.write content } + + File.join dir_lib_arg, "lib" + end + + def ruby_with_rubygems_and_fake_operating_system_in_load_path(operating_system_path) + [Gem.ruby, "-I", operating_system_path, "-I" , $LOAD_PATH.find{|p| p == File.dirname($LOADED_FEATURES.find{|f| f.end_with?("/rubygems.rb") }) }] + end +end diff --git a/lib/rubygems/test_utilities.rb b/test/rubygems/utilities.rb index 1371ae9b14..20416fe70b 100644 --- a/lib/rubygems/test_utilities.rb +++ b/test/rubygems/utilities.rb @@ -76,7 +76,7 @@ class Gem::FakeFetcher def cache_update_path(uri, path = nil, update = true) if data = fetch_path(uri) - open(path, 'wb') {|io| io.write data } if path and update + File.open(path, 'wb') {|io| io.write data } if path and update data else Gem.read_binary(path) if path @@ -348,8 +348,6 @@ end # A StringIO duck-typed class that uses Tempfile instead of String as the # backing store. # -# This is available when rubygems/test_utilities is required. -#-- # This class was added to flush out problems in Rubinius' IO implementation. class TempIO < Tempfile diff --git a/test/socket/test_tcp.rb b/test/socket/test_tcp.rb index a9e2a417a5..9aa716f7ec 100644 --- a/test/socket/test_tcp.rb +++ b/test/socket/test_tcp.rb @@ -115,4 +115,29 @@ class TestSocket_TCPSocket < Test::Unit::TestCase assert_raise(IO::WaitReadable) { svr.accept_nonblock(exception: true) } } end + + def test_accept_multithread + attempts_count = 5 + server_threads_count = 3 + client_threads_count = 3 + + attempts_count.times do + server_threads = Array.new(server_threads_count) do + Thread.new do + TCPServer.open("localhost", 0) do |server| + accept_threads = Array.new(client_threads_count) do + Thread.new { server.accept.close } + end + client_threads = Array.new(client_threads_count) do + Thread.new { TCPSocket.open(server.addr[3], server.addr[1]) {} } + end + client_threads.each(&:join) + accept_threads.each(&:join) + end + end + end + + server_threads.each(&:join) + end + end end if defined?(TCPSocket) diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index a49326119f..144a9f4292 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -446,6 +446,15 @@ class TestStringIO < Test::Unit::TestCase f.close unless f.closed? end + def test_each_byte_closed + f = StringIO.new("1234") + assert_equal("1".ord, f.each_byte {|c| f.close; break c }) + f = StringIO.new("1234") + assert_raise(IOError) do + f.each_byte { f.close } + end + end + def test_getbyte f = StringIO.new("1234") assert_equal("1".ord, f.getbyte) @@ -520,11 +529,29 @@ class TestStringIO < Test::Unit::TestCase assert_equal(%w(1 2 3 4), f.each_char.to_a) end + def test_each_char_closed + f = StringIO.new("1234") + assert_equal("1", f.each_char {|c| f.close; break c }) + f = StringIO.new("1234") + assert_raise(IOError) do + f.each_char { f.close } + end + end + def test_each_codepoint f = StringIO.new("1234") assert_equal([49, 50, 51, 52], f.each_codepoint.to_a) end + def test_each_codepoint_closed + f = StringIO.new("1234") + assert_equal("1".ord, f.each_codepoint {|c| f.close; break c }) + f = StringIO.new("1234") + assert_raise(IOError) do + f.each_codepoint { f.close } + end + end + def test_each_codepoint_enumerator io = StringIO.new('ä½ å¥½Ð¿Ð¾Ñтроить') @@ -730,6 +757,15 @@ class TestStringIO < Test::Unit::TestCase assert_equal("b""\0""a", s.string) end + def test_ungetc_fill + count = 100 + s = StringIO.new + s.print 'a' * count + s.ungetc('b' * (count * 5)) + assert_equal((count * 5), s.string.size) + assert_match(/\Ab+\z/, s.string) + end + def test_ungetbyte_pos b = '\\b00010001 \\B00010001 \\b1 \\B1 \\b000100011' s = StringIO.new( b ) @@ -755,6 +791,15 @@ class TestStringIO < Test::Unit::TestCase assert_equal("b""\0""a", s.string) end + def test_ungetbyte_fill + count = 100 + s = StringIO.new + s.print 'a' * count + s.ungetbyte('b' * (count * 5)) + assert_equal((count * 5), s.string.size) + assert_match(/\Ab+\z/, s.string) + end + def test_frozen s = StringIO.new s.freeze @@ -798,18 +843,17 @@ class TestStringIO < Test::Unit::TestCase end def test_overflow - skip if RbConfig::SIZEOF["void*"] > RbConfig::SIZEOF["long"] + return if RbConfig::SIZEOF["void*"] > RbConfig::SIZEOF["long"] limit = RbConfig::LIMITS["INTPTR_MAX"] - 0x10 assert_separately(%w[-rstringio], "#{<<-"begin;"}\n#{<<-"end;"}") begin; limit = #{limit} ary = [] - while true + begin x = "a"*0x100000 break if [x].pack("p").unpack("i!")[0] < 0 ary << x - skip if ary.size > 100 - end + end while ary.size <= 100 s = StringIO.new(x) s.gets("xxx", limit) assert_equal(0x100000, s.pos) diff --git a/test/strscan/test_ractor.rb b/test/strscan/test_ractor.rb index 0d44242304..480c1ae8a6 100644 --- a/test/strscan/test_ractor.rb +++ b/test/strscan/test_ractor.rb @@ -3,7 +3,7 @@ require 'test/unit' class TestStringScannerRactor < Test::Unit::TestCase def setup - skip unless defined? Ractor + pend unless defined? Ractor end def test_ractor diff --git a/test/strscan/test_stringscanner.rb b/test/strscan/test_stringscanner.rb index 4b001b317e..6e30be1f7d 100644 --- a/test/strscan/test_stringscanner.rb +++ b/test/strscan/test_stringscanner.rb @@ -206,6 +206,23 @@ class TestStringScanner < Test::Unit::TestCase assert_equal 11, s.charpos end + def test_charpos_not_use_string_methods + string = +'abcädeföghi' + scanner = create_string_scanner(string) + + class << string + EnvUtil.suppress_warning do + undef_method(*instance_methods) + end + end + + assert_equal 0, scanner.charpos + assert_equal "abcä", scanner.scan_until(/ä/) + assert_equal 4, scanner.charpos + assert_equal "defö", scanner.scan_until(/ö/) + assert_equal 8, scanner.charpos + end + def test_concat s = create_string_scanner('a'.dup) s.scan(/a/) diff --git a/test/test_time.rb b/test/test_time.rb index ca20788aac..4f11048596 100644 --- a/test/test_time.rb +++ b/test/test_time.rb @@ -62,6 +62,15 @@ class TestTimeExtension < Test::Unit::TestCase # :nodoc: assert_equal(true, t.utc?) end + def test_rfc2822_nonlinear + pre = ->(n) {"0 Feb 00 00 :00" + " " * n} + assert_linear_performance([100, 500, 5000, 50_000], pre: pre) do |s| + assert_raise(ArgumentError) do + Time.rfc2822(s) + end + end + end + def test_encode_rfc2822 t = Time.utc(1) assert_equal("Mon, 01 Jan 0001 00:00:00 -0000", t.rfc2822) diff --git a/test/test_tmpdir.rb b/test/test_tmpdir.rb index c56fd5f401..7ef9f59b54 100644 --- a/test/test_tmpdir.rb +++ b/test/test_tmpdir.rb @@ -97,8 +97,10 @@ class TestTmpdir < Test::Unit::TestCase target = target.chomp('/') + '/' traversal_path = target.sub(/\A\w:/, '') # for DOSISH traversal_path = Array.new(target.count('/')-2, '..').join('/') + traversal_path - actual = yield traversal_path - assert_not_send([File.absolute_path(actual), :start_with?, target]) + [File::SEPARATOR, File::ALT_SEPARATOR].compact.each do |separator| + actual = yield traversal_path.tr('/', separator) + assert_not_send([File.absolute_path(actual), :start_with?, target]) + end end end end diff --git a/test/uri/test_common.rb b/test/uri/test_common.rb index 1afa35f93d..2b877c10d7 100644 --- a/test/uri/test_common.rb +++ b/test/uri/test_common.rb @@ -56,6 +56,17 @@ class TestCommon < Test::Unit::TestCase assert_raise(NoMethodError) { Object.new.URI("http://www.ruby-lang.org/") } end + def test_parse_timeout + pre = ->(n) { + 'https://example.com/dir/' + 'a' * (n * 100) + '/##.jpg' + } + assert_linear_performance((1..10).map {|i| i * 100}, pre: pre) do |uri| + assert_raise(URI::InvalidURIError) do + URI.parse(uri) + end + end + end + def test_encode_www_form_component assert_equal("%00+%21%22%23%24%25%26%27%28%29*%2B%2C-.%2F09%3A%3B%3C%3D%3E%3F%40" \ "AZ%5B%5C%5D%5E_%60az%7B%7C%7D%7E", diff --git a/test/uri/test_parser.rb b/test/uri/test_parser.rb index b13a26ca84..e2f50cf2b4 100644 --- a/test/uri/test_parser.rb +++ b/test/uri/test_parser.rb @@ -58,4 +58,33 @@ class URI::TestParser < Test::Unit::TestCase assert_equal("\u3042", p1.unescape('%e3%81%82'.force_encoding(Encoding::US_ASCII))) assert_equal("\xe3\x83\x90\xe3\x83\x90", p1.unescape("\xe3\x83\x90%e3%83%90")) end + + def test_split + assert_equal(["http", nil, "example.com", nil, nil, "", nil, nil, nil], URI.split("http://example.com")) + assert_equal(["http", nil, "[0::0]", nil, nil, "", nil, nil, nil], URI.split("http://[0::0]")) + assert_equal([nil, nil, "example.com", nil, nil, "", nil, nil, nil], URI.split("//example.com")) + assert_equal([nil, nil, "[0::0]", nil, nil, "", nil, nil, nil], URI.split("//[0::0]")) + end + + def test_rfc2822_parse_relative_uri + pre = ->(length) { + " " * length + "\0" + } + parser = URI::RFC2396_Parser.new + assert_linear_performance((1..5).map {|i| 10**i}, pre: pre) do |uri| + assert_raise(URI::InvalidURIError) do + parser.split(uri) + end + end + end + + def test_rfc3986_port_check + pre = ->(length) {"\t" * length + "a"} + uri = URI.parse("http://my.example.com") + assert_linear_performance((1..5).map {|i| 10**i}, pre: pre) do |port| + assert_raise(URI::InvalidComponentError) do + uri.port = port + end + end + end end diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index addd4270e1..c469d24dfc 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -3,6 +3,8 @@ require 'test/unit' require 'stringio' require 'tempfile' +require 'tmpdir' +require 'securerandom' begin require 'zlib' @@ -502,6 +504,72 @@ if defined? Zlib assert_raise(Zlib::StreamError) { z.set_dictionary("foo") } z.close end + + def test_multithread_deflate + zd = Zlib::Deflate.new + + s = "x" * 10000 + (0...10).map do |x| + Thread.new do + 1000.times { zd.deflate(s) } + end + end.each do |th| + th.join + end + ensure + zd&.finish + zd&.close + end + + def test_multithread_inflate + zi = Zlib::Inflate.new + + s = Zlib.deflate("x" * 10000) + (0...10).map do |x| + Thread.new do + 1000.times { zi.inflate(s) } + end + end.each do |th| + th.join + end + ensure + zi&.finish + zi&.close + end + + def test_recursive_deflate + original_gc_stress = GC.stress + GC.stress = true + zd = Zlib::Deflate.new + + s = SecureRandom.random_bytes(1024**2) + assert_raise(Zlib::InProgressError) do + zd.deflate(s) do + zd.deflate(s) + end + end + ensure + GC.stress = original_gc_stress + zd&.finish + zd&.close + end + + def test_recursive_inflate + original_gc_stress = GC.stress + GC.stress = true + zi = Zlib::Inflate.new + + s = Zlib.deflate(SecureRandom.random_bytes(1024**2)) + + assert_raise(Zlib::InProgressError) do + zi.inflate(s) do + zi.inflate(s) + end + end + ensure + GC.stress = original_gc_stress + zi&.close + end end class TestZlibGzipFile < Test::Unit::TestCase @@ -720,6 +788,26 @@ if defined? Zlib gz.close } end + + if defined? File::TMPFILE + def test_path_tmpfile + sio = StringIO.new("".dup, 'w') + gz = Zlib::GzipWriter.new(sio) + gz.write "hi" + gz.close + + File.open(Dir.mktmpdir, File::RDWR | File::TMPFILE) do |io| + io.write sio.string + io.rewind + + gz = Zlib::GzipWriter.new(io) + assert_raise(NoMethodError) { gz.path } + + gz = Zlib::GzipReader.new(io) + assert_raise(NoMethodError) { gz.path } + end + end + end end class TestZlibGzipReader < Test::Unit::TestCase @@ -424,13 +424,6 @@ rb_vm_gvl_destroy(rb_global_vm_lock_t *gvl) { gvl_release(gvl); gvl_destroy(gvl); - - if (0) { - rb_vm_t *vm = GET_VM(); - /* may be held by running threads */ - rb_native_mutex_destroy(&vm->waitpid_lock); - rb_native_mutex_destroy(&vm->workqueue_lock); - } } void @@ -546,12 +539,15 @@ terminate_all(rb_ractor_t *r, const rb_thread_t *main_thread) static void rb_threadptr_join_list_wakeup(rb_thread_t *thread) { - struct rb_waiting_list *join_list = thread->join_list; + while (thread->join_list) { + struct rb_waiting_list *join_list = thread->join_list; + + // Consume the entry from the join list: + thread->join_list = join_list->next; - while (join_list) { rb_thread_t *target_thread = join_list->thread; - if (target_thread->scheduler != Qnil) { + if (target_thread->scheduler != Qnil && rb_fiberptr_blocking(join_list->fiber) == 0) { rb_scheduler_unblock(target_thread->scheduler, target_thread->self, rb_fiberptr_self(join_list->fiber)); } else { rb_threadptr_interrupt(target_thread); @@ -564,25 +560,20 @@ rb_threadptr_join_list_wakeup(rb_thread_t *thread) break; } } - - join_list = join_list->next; } } void rb_threadptr_unlock_all_locking_mutexes(rb_thread_t *th) { - const char *err; - rb_mutex_t *mutex; - rb_mutex_t *mutexes = th->keeping_mutexes; + while (th->keeping_mutexes) { + rb_mutex_t *mutex = th->keeping_mutexes; + th->keeping_mutexes = mutex->next_mutex; + + /* rb_warn("mutex #<%p> remains to be locked by terminated thread", (void *)mutexes); */ - while (mutexes) { - mutex = mutexes; - /* rb_warn("mutex #<%p> remains to be locked by terminated thread", - (void *)mutexes); */ - mutexes = mutex->next_mutex; - err = rb_mutex_unlock_th(mutex, th, mutex->fiber); - if (err) rb_bug("invalid keeping_mutexes: %s", err); + const char *error_message = rb_mutex_unlock_th(mutex, th, mutex->fiber); + if (error_message) rb_bug("invalid keeping_mutexes: %s", error_message); } } @@ -640,6 +631,7 @@ thread_cleanup_func_before_exec(void *th_ptr) { rb_thread_t *th = th_ptr; th->status = THREAD_KILLED; + // The thread stack doesn't exist in the forked process: th->ec->machine.stack_start = th->ec->machine.stack_end = NULL; @@ -696,7 +688,7 @@ rb_vm_proc_local_ep(VALUE proc) VALUE rb_vm_invoke_proc_with_self(rb_execution_context_t *ec, rb_proc_t *proc, VALUE self, int argc, const VALUE *argv, int kw_splat, VALUE passed_block_handler); -static void +static VALUE thread_do_start_proc(rb_thread_t *th) { VALUE args = th->invoke_arg.proc.args; @@ -710,7 +702,6 @@ thread_do_start_proc(rb_thread_t *th) th->ec->root_lep = rb_vm_proc_local_ep(procval); th->ec->root_svar = Qfalse; - EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_THREAD_BEGIN, th->self, 0, 0, 0, Qundef); vm_check_ints_blocking(th->ec); if (th->invoke_type == thread_invoke_type_ractor_proc) { @@ -721,11 +712,12 @@ thread_do_start_proc(rb_thread_t *th) rb_ractor_receive_parameters(th->ec, th->ractor, args_len, (VALUE *)args_ptr); vm_check_ints_blocking(th->ec); - // kick thread - th->value = rb_vm_invoke_proc_with_self(th->ec, proc, self, - args_len, args_ptr, - th->invoke_arg.proc.kw_splat, - VM_BLOCK_HANDLER_NONE); + return rb_vm_invoke_proc_with_self( + th->ec, proc, self, + args_len, args_ptr, + th->invoke_arg.proc.kw_splat, + VM_BLOCK_HANDLER_NONE + ); } else { args_len = RARRAY_LENINT(args); @@ -741,17 +733,12 @@ thread_do_start_proc(rb_thread_t *th) vm_check_ints_blocking(th->ec); - // kick thread - th->value = rb_vm_invoke_proc(th->ec, proc, - args_len, args_ptr, - th->invoke_arg.proc.kw_splat, - VM_BLOCK_HANDLER_NONE); - } - - EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_THREAD_END, th->self, 0, 0, 0, Qundef); - - if (th->invoke_type == thread_invoke_type_ractor_proc) { - rb_ractor_atexit(th->ec, th->value); + return rb_vm_invoke_proc( + th->ec, proc, + args_len, args_ptr, + th->invoke_arg.proc.kw_splat, + VM_BLOCK_HANDLER_NONE + ); } } @@ -759,24 +746,42 @@ static void thread_do_start(rb_thread_t *th) { native_set_thread_name(th); + VALUE result = Qundef; + + EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_THREAD_BEGIN, th->self, 0, 0, 0, Qundef); switch (th->invoke_type) { case thread_invoke_type_proc: + result = thread_do_start_proc(th); + break; + case thread_invoke_type_ractor_proc: - thread_do_start_proc(th); + result = thread_do_start_proc(th); + rb_ractor_atexit(th->ec, result); break; + case thread_invoke_type_func: - th->value = (*th->invoke_arg.func.func)(th->invoke_arg.func.arg); + result = (*th->invoke_arg.func.func)(th->invoke_arg.func.arg); break; + case thread_invoke_type_none: rb_bug("unreachable"); } rb_scheduler_set(Qnil); + + th->value = result; + + EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_THREAD_END, th->self, 0, 0, 0, Qundef); } void rb_ec_clear_current_thread_trace_func(const rb_execution_context_t *ec); +// io.c +VALUE rb_io_prep_stdin(void); +VALUE rb_io_prep_stdout(void); +VALUE rb_io_prep_stderr(void); + static int thread_start_func_2(rb_thread_t *th, VALUE *stack_start) { @@ -799,6 +804,10 @@ thread_start_func_2(rb_thread_t *th, VALUE *stack_start) RB_VM_LOCK(); { rb_vm_ractor_blocking_cnt_dec(th->vm, th->ractor, __FILE__, __LINE__); + rb_ractor_t *r = th->ractor; + r->r_stdin = rb_io_prep_stdin(); + r->r_stdout = rb_io_prep_stdout(); + r->r_stderr = rb_io_prep_stderr(); } RB_VM_UNLOCK(); } @@ -814,87 +823,96 @@ thread_start_func_2(rb_thread_t *th, VALUE *stack_start) th->ec->machine.stack_start = STACK_DIR_UPPER(vm_stack + size, vm_stack); th->ec->machine.stack_maxsize -= size * sizeof(VALUE); - { - thread_debug("thread start (get lock): %p\n", (void *)th); + thread_debug("thread start (get lock): %p\n", (void *)th); - EC_PUSH_TAG(th->ec); - if ((state = EC_EXEC_TAG()) == TAG_NONE) { - SAVE_ROOT_JMPBUF(th, thread_do_start(th)); - } - else { - errinfo = th->ec->errinfo; + // Ensure that we are not joinable. + VM_ASSERT(th->value == Qundef); - if (state == TAG_FATAL) { - if (th->invoke_type == thread_invoke_type_ractor_proc) { - rb_ractor_atexit(th->ec, Qnil); - } - /* fatal error within this thread, need to stop whole script */ - } - else if (rb_obj_is_kind_of(errinfo, rb_eSystemExit)) { - /* exit on main_thread. */ - } - else { - if (th->report_on_exception) { - VALUE mesg = rb_thread_to_s(th->self); - rb_str_cat_cstr(mesg, " terminated with exception (report_on_exception is true):\n"); - rb_write_error_str(mesg); - rb_ec_error_print(th->ec, errinfo); - } + EC_PUSH_TAG(th->ec); - if (th->invoke_type == thread_invoke_type_ractor_proc) { - rb_ractor_atexit_exception(th->ec); - } + if ((state = EC_EXEC_TAG()) == TAG_NONE) { + SAVE_ROOT_JMPBUF(th, thread_do_start(th)); + } else { + errinfo = th->ec->errinfo; - if (th->vm->thread_abort_on_exception || - th->abort_on_exception || RTEST(ruby_debug)) { - /* exit on main_thread */ - } - else { - errinfo = Qnil; - } - } - th->value = Qnil; - } + VALUE exc = rb_vm_make_jump_tag_but_local_jump(state, Qundef); + if (!NIL_P(exc)) errinfo = exc; - if (th->invoke_type == thread_invoke_type_ractor_proc) { - rb_thread_terminate_all(th); - rb_ractor_teardown(th->ec); + if (state == TAG_FATAL) { + if (th->invoke_type == thread_invoke_type_ractor_proc) { + rb_ractor_atexit(th->ec, Qnil); + } + /* fatal error within this thread, need to stop whole script */ } + else if (rb_obj_is_kind_of(errinfo, rb_eSystemExit)) { + /* exit on main_thread. */ + } + else { + if (th->report_on_exception) { + VALUE mesg = rb_thread_to_s(th->self); + rb_str_cat_cstr(mesg, " terminated with exception (report_on_exception is true):\n"); + rb_write_error_str(mesg); + rb_ec_error_print(th->ec, errinfo); + } - th->status = THREAD_KILLED; - thread_debug("thread end: %p\n", (void *)th); + if (th->invoke_type == thread_invoke_type_ractor_proc) { + rb_ractor_atexit_exception(th->ec); + } - if (th->vm->ractor.main_thread == th) { - ruby_stop(0); - } + if (th->vm->thread_abort_on_exception || + th->abort_on_exception || RTEST(ruby_debug)) { + /* exit on main_thread */ + } + else { + errinfo = Qnil; + } + } + th->value = Qnil; + } - if (RB_TYPE_P(errinfo, T_OBJECT)) { - /* treat with normal error object */ - rb_threadptr_raise(ractor_main_th, 1, &errinfo); - } - EC_POP_TAG(); + // The thread is effectively finished and can be joined. + VM_ASSERT(th->value != Qundef); - rb_ec_clear_current_thread_trace_func(th->ec); + rb_threadptr_join_list_wakeup(th); + rb_threadptr_unlock_all_locking_mutexes(th); - /* locking_mutex must be Qfalse */ - if (th->locking_mutex != Qfalse) { - rb_bug("thread_start_func_2: locking_mutex must not be set (%p:%"PRIxVALUE")", - (void *)th, th->locking_mutex); - } + if (th->invoke_type == thread_invoke_type_ractor_proc) { + rb_thread_terminate_all(th); + rb_ractor_teardown(th->ec); + } - if (ractor_main_th->status == THREAD_KILLED && - th->ractor->threads.cnt <= 2 /* main thread and this thread */) { - /* I'm last thread. wake up main thread from rb_thread_terminate_all */ - rb_threadptr_interrupt(ractor_main_th); - } + th->status = THREAD_KILLED; + thread_debug("thread end: %p\n", (void *)th); + + if (th->vm->ractor.main_thread == th) { + ruby_stop(0); + } + + if (RB_TYPE_P(errinfo, T_OBJECT)) { + /* treat with normal error object */ + rb_threadptr_raise(ractor_main_th, 1, &errinfo); + } - rb_threadptr_join_list_wakeup(th); - rb_threadptr_unlock_all_locking_mutexes(th); - rb_check_deadlock(th->ractor); + EC_POP_TAG(); + + rb_ec_clear_current_thread_trace_func(th->ec); - rb_fiber_close(th->ec->fiber_ptr); + /* locking_mutex must be Qfalse */ + if (th->locking_mutex != Qfalse) { + rb_bug("thread_start_func_2: locking_mutex must not be set (%p:%"PRIxVALUE")", + (void *)th, th->locking_mutex); } + if (ractor_main_th->status == THREAD_KILLED && + th->ractor->threads.cnt <= 2 /* main thread and this thread */) { + /* I'm last thread. wake up main thread from rb_thread_terminate_all */ + rb_threadptr_interrupt(ractor_main_th); + } + + rb_check_deadlock(th->ractor); + + rb_fiber_close(th->ec->fiber_ptr); + thread_cleanup_func(th, FALSE); VM_ASSERT(th->ec->vm_stack == NULL); @@ -1151,6 +1169,12 @@ remove_from_join_list(VALUE arg) static rb_hrtime_t *double2hrtime(rb_hrtime_t *, double); +static int +thread_finished(rb_thread_t *th) +{ + return th->status == THREAD_KILLED || th->value != Qundef; +} + static VALUE thread_join_sleep(VALUE arg) { @@ -1177,7 +1201,7 @@ thread_join_sleep(VALUE arg) end = rb_hrtime_add(*limit, rb_hrtime_now()); } - while (target_th->status != THREAD_KILLED) { + while (!thread_finished(target_th)) { VALUE scheduler = rb_scheduler_current(); if (scheduler != Qnil) { @@ -3318,11 +3342,11 @@ rb_thread_status(VALUE thread) static VALUE rb_thread_alive_p(VALUE thread) { - if (rb_threadptr_dead(rb_thread_ptr(thread))) { - return Qfalse; + if (thread_finished(rb_thread_ptr(thread))) { + return Qfalse; } else { - return Qtrue; + return Qtrue; } } diff --git a/thread_pthread.c b/thread_pthread.c index 97879f559a..134d1875cb 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -1759,6 +1759,7 @@ ubf_timer_disarm(void) #if UBF_TIMER == UBF_TIMER_POSIX rb_atomic_t prev; + if (timer_posix.owner && timer_posix.owner != getpid()) return; prev = ATOMIC_CAS(timer_posix.state, RTIMER_ARMED, RTIMER_DISARM); switch (prev) { case RTIMER_DISARM: return; /* likely */ diff --git a/thread_sync.c b/thread_sync.c index 8c999e2164..26d5e6b686 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -32,7 +32,7 @@ sync_wakeup(struct list_head *head, long max) if (cur->th->status != THREAD_KILLED) { - if (cur->th->scheduler != Qnil) { + if (cur->th->scheduler != Qnil && rb_fiberptr_blocking(cur->fiber) == 0) { rb_scheduler_unblock(cur->th->scheduler, cur->self, rb_fiberptr_self(cur->fiber)); } else { rb_threadptr_interrupt(cur->th); @@ -437,7 +437,7 @@ rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) list_for_each_safe(&mutex->waitq, cur, next, node) { list_del_init(&cur->node); - if (cur->th->scheduler != Qnil) { + if (cur->th->scheduler != Qnil && rb_fiberptr_blocking(cur->fiber) == 0) { rb_scheduler_unblock(cur->th->scheduler, cur->self, rb_fiberptr_self(cur->fiber)); goto found; } else { @@ -560,7 +560,7 @@ rb_mutex_sleep(VALUE self, VALUE timeout) RUBY_VM_CHECK_INTS_BLOCKING(GET_EC()); time_t end = time(0) - beg; - return INT2FIX(end); + return TIMET2NUM(end); } /* diff --git a/tool/bundler/rubocop_gems.rb b/tool/bundler/rubocop_gems.rb new file mode 100644 index 0000000000..84cb226330 --- /dev/null +++ b/tool/bundler/rubocop_gems.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "rubocop", "~> 1.7" + +gem "minitest" +gem "rake" +gem "rake-compiler" +gem "rspec" +gem "test-unit" diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock new file mode 100644 index 0000000000..fdbbdbdcc3 --- /dev/null +++ b/tool/bundler/rubocop_gems.rb.lock @@ -0,0 +1,63 @@ +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.2) + diff-lcs (1.4.4) + minitest (5.14.4) + parallel (1.21.0) + parser (3.0.2.0) + ast (~> 2.4.1) + power_assert (2.0.1) + rainbow (3.0.0) + rake (13.0.6) + rake-compiler (1.1.1) + rake + regexp_parser (2.1.1) + rexml (3.2.5) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.1) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-support (3.10.3) + rubocop (1.23.0) + parallel (~> 1.10) + parser (>= 3.0.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml + rubocop-ast (>= 1.12.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.13.0) + parser (>= 3.0.1.1) + ruby-progressbar (1.11.0) + test-unit (3.5.1) + power_assert + unicode-display_width (2.1.0) + +PLATFORMS + arm64-darwin-20 + arm64-darwin-21 + universal-java-11 + x86_64-darwin-19 + x86_64-darwin-20 + x86_64-linux + +DEPENDENCIES + minitest + rake + rake-compiler + rspec + rubocop (~> 1.7) + test-unit + +BUNDLED WITH + 2.2.33 diff --git a/tool/bundler/standard_gems.rb b/tool/bundler/standard_gems.rb new file mode 100644 index 0000000000..1cd189742d --- /dev/null +++ b/tool/bundler/standard_gems.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "standard", "~> 1.0" + +gem "minitest" +gem "rake" +gem "rake-compiler" +gem "rspec" +gem "test-unit" diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock new file mode 100644 index 0000000000..af22080659 --- /dev/null +++ b/tool/bundler/standard_gems.rb.lock @@ -0,0 +1,69 @@ +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.2) + diff-lcs (1.4.4) + minitest (5.14.4) + parallel (1.21.0) + parser (3.0.2.0) + ast (~> 2.4.1) + power_assert (2.0.1) + rainbow (3.0.0) + rake (13.0.6) + rake-compiler (1.1.1) + rake + regexp_parser (2.1.1) + rexml (3.2.5) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.1) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-support (3.10.3) + rubocop (1.22.3) + parallel (~> 1.10) + parser (>= 3.0.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml + rubocop-ast (>= 1.12.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.13.0) + parser (>= 3.0.1.1) + rubocop-performance (1.11.5) + rubocop (>= 1.7.0, < 2.0) + rubocop-ast (>= 0.4.0) + ruby-progressbar (1.11.0) + standard (1.4.0) + rubocop (= 1.22.3) + rubocop-performance (= 1.11.5) + test-unit (3.5.1) + power_assert + unicode-display_width (2.1.0) + +PLATFORMS + arm64-darwin-20 + arm64-darwin-21 + universal-java-11 + x86_64-darwin-19 + x86_64-darwin-20 + x86_64-linux + +DEPENDENCIES + minitest + rake + rake-compiler + rspec + standard (~> 1.0) + test-unit + +BUNDLED WITH + 2.3.0.dev diff --git a/tool/bundler/test_gems.rb b/tool/bundler/test_gems.rb index ef40fc8d0e..215d23183e 100644 --- a/tool/bundler/test_gems.rb +++ b/tool/bundler/test_gems.rb @@ -3,11 +3,10 @@ source "https://rubygems.org" gem "rack", "2.0.8" +gem "webrick", "1.7.0" gem "rack-test", "~> 1.1" gem "artifice", "~> 0.6.0" gem "compact_index", "~> 0.13.0" gem "sinatra", "~> 2.0" gem "rake", "13.0.1" gem "builder", "~> 3.2" -# ruby-graphviz is used by the viz tests -gem "ruby-graphviz", "1.2.4" # for >= Ruby 2.3 diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index 095eac9f57..7c65d17837 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -13,18 +13,22 @@ GEM rack-test (1.1.0) rack (>= 1.0, < 3) rake (13.0.1) - ruby-graphviz (1.2.4) - ruby2_keywords (0.0.2) + ruby2_keywords (0.0.5) sinatra (2.0.8.1) mustermann (~> 1.0) rack (~> 2.0) rack-protection (= 2.0.8.1) tilt (~> 2.0) tilt (2.0.10) + webrick (1.7.0) PLATFORMS java ruby + universal-java-11 + x64-mingw32 + x86_64-darwin-20 + x86_64-linux DEPENDENCIES artifice (~> 0.6.0) @@ -33,8 +37,8 @@ DEPENDENCIES rack (= 2.0.8) rack-test (~> 1.1) rake (= 13.0.1) - ruby-graphviz (= 1.2.4) sinatra (~> 2.0) + webrick (= 1.7.0) BUNDLED WITH - 2.2.0.dev + 2.2.33 diff --git a/tool/fake.rb b/tool/fake.rb index 42174052e2..4946fd6234 100644 --- a/tool/fake.rb +++ b/tool/fake.rb @@ -43,6 +43,7 @@ prehook = proc do |extmk| $extout_prefix = '$(extout)$(target_prefix)/' config = RbConfig::CONFIG mkconfig = RbConfig::MAKEFILE_CONFIG + $builtruby ||= File.join(builddir, config['RUBY_INSTALL_NAME'] + config['EXEEXT']) RbConfig.fire_update!("builddir", builddir) RbConfig.fire_update!("buildlibdir", builddir) RbConfig.fire_update!("libdir", builddir) diff --git a/tool/leaked-globals b/tool/leaked-globals index 0fb9e657c9..083f658fb1 100755 --- a/tool/leaked-globals +++ b/tool/leaked-globals @@ -7,6 +7,8 @@ until ARGV.empty? SYMBOL_PREFIX = $1 when /\ANM=(.*)/ # may be multiple words NM = $1 + when /\APLATFORM=(.+)?/ + platform = $1 else break end @@ -16,15 +18,39 @@ end config = ARGV.shift count = 0 col = Colorize.new -REPLACE = File.read(config).scan(/\bAC_(?:REPLACE|CHECK)_FUNCS?\(\K\w+/) +config_code = File.read(config) +REPLACE = config_code.scan(/\bAC_(?:REPLACE|CHECK)_FUNCS?\((\w+)/).flatten +# REPLACE << 'memcmp' if /\bAC_FUNC_MEMCMP\b/ =~ config_code +REPLACE.push('main', 'DllMain') +if platform and !platform.empty? + begin + h = File.read(platform) + rescue Errno::ENOENT + else + REPLACE.concat( + h .gsub(%r[/\*.*?\*/]m, " ") # delete block comments + .gsub(%r[//.*], " ") # delete oneline comments + .gsub(/^\s*#.*(?:\\\n.*)*/, "") # delete preprocessor directives + .gsub(/(?:\A|;)\K\s*typedef\s.*?;/m, "") + .scan(/\b((?!rb_|DEPRECATED|_)\w+)\s*\(.*\);/) + .flatten) + end +end +missing = File.dirname(config) + "/missing/" +ARGV.reject! do |n| + unless (src = Dir.glob(missing + File.basename(n, ".*") + ".[cS]")).empty? + puts "Ignore #{n} because of #{src.map {|s| File.basename(s)}.join(', ')} under missing" + true + end +end print "Checking leaked global symbols..." STDOUT.flush -IO.foreach("|#{NM} -Pgp #{ARGV.join(' ')}") do |line| +IO.foreach("|#{NM} #{ARGV.join(' ')}") do |line| n, t, = line.split next unless /[A-TV-Z]/ =~ t next unless n.sub!(/^#{SYMBOL_PREFIX}/o, "") next if n.include?(".") - next if /\A(?:Init_|InitVM_|RUBY_|ruby_|rb_|[Oo]nig|dln_|mjit_|coroutine_|nu(?:comp|rat)_)/ =~ n + next if /\A(?:Init_|InitVM_|RUBY_|ruby_|rb_|[Oo]nig|dln_|mjit_|coroutine_)/ =~ n next if REPLACE.include?(n) puts col.fail("leaked") if count.zero? count += 1 diff --git a/tool/lib/minitest/unit.rb b/tool/lib/minitest/unit.rb index f70762e5d5..9f07313e1b 100644 --- a/tool/lib/minitest/unit.rb +++ b/tool/lib/minitest/unit.rb @@ -452,11 +452,15 @@ module MiniTest msg = message(msg) { "Expected path '#{path}' to exist" } assert File.exist?(path), msg end + alias assert_path_exist assert_path_exists + alias refute_path_not_exist assert_path_exists def refute_path_exists(path, msg = nil) msg = message(msg) { "Expected path '#{path}' to not exist" } refute File.exist?(path), msg end + alias refute_path_exist refute_path_exists + alias assert_path_not_exist refute_path_exists ## # Captures $stdout and $stderr into strings: diff --git a/tool/lib/test/unit.rb b/tool/lib/test/unit.rb index d76353cf0f..38369a1040 100644 --- a/tool/lib/test/unit.rb +++ b/tool/lib/test/unit.rb @@ -117,6 +117,9 @@ module Test filter = nil elsif negative.empty? and positive.size == 1 and pos_pat !~ positive[0] filter = positive[0] + unless /\A[A-Z]\w*(?:::[A-Z]\w*)*#/ =~ filter + filter = /##{Regexp.quote(filter)}\z/ + end else filter = Regexp.union(*positive.map! {|s| Regexp.new(s[pos_pat, 1] || "\\A#{Regexp.quote(s)}\\z")}) end diff --git a/tool/lib/test/unit/core_assertions.rb b/tool/lib/test/unit/core_assertions.rb index abd0e45035..29d0763132 100644 --- a/tool/lib/test/unit/core_assertions.rb +++ b/tool/lib/test/unit/core_assertions.rb @@ -743,6 +743,39 @@ eom end end + # Expect +seq+ to respond to +first+ and +each+ methods, e.g., + # Array, Range, Enumerator::ArithmeticSequence and other + # Enumerable-s, and each elements should be size factors. + # + # :yield: each elements of +seq+. + def assert_linear_performance(seq, rehearsal: nil, pre: ->(n) {n}) + first = seq.first + *arg = pre.call(first) + times = (0..(rehearsal || (2 * first))).map do + st = Process.clock_gettime(Process::CLOCK_MONOTONIC) + yield(*arg) + t = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - st) + assert_operator 0, :<=, t + t.nonzero? + end + times.compact! + tmin, tmax = times.minmax + tmax *= tmax / tmin + tmax = 10**Math.log10(tmax).ceil + + seq.each do |i| + next if i == first + t = tmax * i.fdiv(first) + *arg = pre.call(i) + message = "[#{i}]: in #{t}s" + Timeout.timeout(t, Timeout::Error, message) do + st = Process.clock_gettime(Process::CLOCK_MONOTONIC) + yield(*arg) + assert_operator (Process.clock_gettime(Process::CLOCK_MONOTONIC) - st), :<=, t, message + end + end + end + def diff(exp, act) require 'pp' q = PP.new(+"") diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb index 0c01abb0ac..c6d6cddded 100644 --- a/tool/lib/vcs.rb +++ b/tool/lib/vcs.rb @@ -52,9 +52,6 @@ module DebugSystem ret end end -module Kernel - prepend(DebugSystem) -end class VCS prepend(DebugSystem) if defined?(DebugSystem) diff --git a/tool/m4/ruby_check_builtin_setjmp.m4 b/tool/m4/ruby_check_builtin_setjmp.m4 index a4289e2e9d..008fd45911 100644 --- a/tool/m4/ruby_check_builtin_setjmp.m4 +++ b/tool/m4/ruby_check_builtin_setjmp.m4 @@ -8,18 +8,18 @@ AC_CACHE_CHECK(for __builtin_setjmp, ac_cv_func___builtin_setjmp, ac_cv_func___builtin_setjmp=no for cast in "" "(void **)"; do RUBY_WERROR_FLAG( - [AC_TRY_LINK([@%:@include <setjmp.h> + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[@%:@include <setjmp.h> @%:@include <stdio.h> jmp_buf jb; @%:@ifdef NORETURN NORETURN(void t(void)); @%:@endif void t(void) {__builtin_longjmp($cast jb, 1);} - int jump(void) {(void)(__builtin_setjmp($cast jb) ? 1 : 0); return 0;}], - [ + int jump(void) {(void)(__builtin_setjmp($cast jb) ? 1 : 0); return 0;}]], + [[ void (*volatile f)(void) = t; if (!jump()) printf("%d\n", f != 0); - ], + ]])], [ac_cv_func___builtin_setjmp="yes with cast ($cast)"]) ]) test "$ac_cv_func___builtin_setjmp" = no || break diff --git a/tool/m4/ruby_check_printf_prefix.m4 b/tool/m4/ruby_check_printf_prefix.m4 index 9007c18c0a..0415f9fa92 100644 --- a/tool/m4/ruby_check_printf_prefix.m4 +++ b/tool/m4/ruby_check_printf_prefix.m4 @@ -4,8 +4,7 @@ AC_CACHE_CHECK([for printf prefix for $1], [rb_cv_pri_prefix_]AS_TR_SH($1),[ [rb_cv_pri_prefix_]AS_TR_SH($1)=[NONE] RUBY_WERROR_FLAG(RUBY_APPEND_OPTIONS(CFLAGS, $rb_cv_wsuppress_flags) for pri in $2; do - AC_TRY_COMPILE( - [@%:@include <stdio.h> + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include <stdio.h> @%:@include <stddef.h> @%:@ifdef __GNUC__ @%:@if defined __MINGW_PRINTF_FORMAT @@ -18,9 +17,9 @@ AC_CACHE_CHECK([for printf prefix for $1], [rb_cv_pri_prefix_]AS_TR_SH($1),[ @%:@else @%:@define PRINTF_ARGS(decl, string_index, first_to_check) decl @%:@endif - PRINTF_ARGS(void test_sprintf(const char*, ...), 1, 2);], - [printf("%]${pri}[d", (]$1[)42); - test_sprintf("%]${pri}[d", (]$1[)42);], + PRINTF_ARGS(void test_sprintf(const char*, ...), 1, 2);]], + [[printf("%]${pri}[d", (]$1[)42); + test_sprintf("%]${pri}[d", (]$1[)42);]])], [rb_cv_pri_prefix_]AS_TR_SH($1)[=[$pri]; break]) done)]) AS_IF([test "[$rb_cv_pri_prefix_]AS_TR_SH($1)" != NONE], [ diff --git a/tool/m4/ruby_check_setjmp.m4 b/tool/m4/ruby_check_setjmp.m4 index 59f38581b8..66652984ea 100644 --- a/tool/m4/ruby_check_setjmp.m4 +++ b/tool/m4/ruby_check_setjmp.m4 @@ -2,14 +2,14 @@ # used for AC_ARG_WITH(setjmp-type) AC_DEFUN([RUBY_CHECK_SETJMP], [ AC_CACHE_CHECK([for ]$1[ as a macro or function], ac_cv_func_$1, - [AC_TRY_COMPILE([ + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @%:@include <setjmp.h> ]AC_INCLUDES_DEFAULT([$3])[ @%:@define JMPARGS_1 env @%:@define JMPARGS_2 env,1 @%:@define JMPARGS JMPARGS_]m4_ifval($2,2,1)[ -], - m4_ifval($2,$2,jmp_buf)[ env; $1(JMPARGS);], +]], + [m4_ifval($2,$2,jmp_buf)[ env; $1(JMPARGS);]])], ac_cv_func_$1=yes, ac_cv_func_$1=no)] ) diff --git a/tool/m4/ruby_check_sysconf.m4 b/tool/m4/ruby_check_sysconf.m4 index f6b247a16f..8324be6764 100644 --- a/tool/m4/ruby_check_sysconf.m4 +++ b/tool/m4/ruby_check_sysconf.m4 @@ -1,9 +1,9 @@ # -*- Autoconf -*- AC_DEFUN([RUBY_CHECK_SYSCONF], [dnl AC_CACHE_CHECK([whether _SC_$1 is supported], rb_cv_have_sc_[]m4_tolower($1), - [AC_TRY_COMPILE([#include <unistd.h> - ], - [_SC_$1 >= 0], + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <unistd.h> + ]], + [[_SC_$1 >= 0]])], rb_cv_have_sc_[]m4_tolower($1)=yes, rb_cv_have_sc_[]m4_tolower($1)=no) ]) diff --git a/tool/m4/ruby_cppoutfile.m4 b/tool/m4/ruby_cppoutfile.m4 index 7c81c4f354..495ae0aae4 100644 --- a/tool/m4/ruby_cppoutfile.m4 +++ b/tool/m4/ruby_cppoutfile.m4 @@ -4,8 +4,8 @@ AC_DEFUN([RUBY_CPPOUTFILE], [save_CPPFLAGS="$CPPFLAGS" CPPFLAGS='-o conftest-1.i' rb_cv_cppoutfile=no -AC_TRY_CPP([test-for-cppout], - [grep test-for-cppout conftest-1.i > /dev/null && rb_cv_cppoutfile=yes]) +AC_PREPROC_IFELSE([AC_LANG_SOURCE([[test-for-cppout]])], + [grep test-for-cppout conftest-1.i > /dev/null && rb_cv_cppoutfile=yes]) CPPFLAGS="$save_CPPFLAGS" rm -f conftest*]) AS_IF([test "$rb_cv_cppoutfile" = yes], [ diff --git a/tool/m4/ruby_decl_attribute.m4 b/tool/m4/ruby_decl_attribute.m4 index 3187b9be60..22358a079a 100644 --- a/tool/m4/ruby_decl_attribute.m4 +++ b/tool/m4/ruby_decl_attribute.m4 @@ -21,7 +21,7 @@ for mac in \ "__declspec(attrib_code) x" \ x; do m4_ifval([$4],mac="$mac"${rbcv_cond+" /* only if $rbcv_cond */"}) - AC_TRY_COMPILE( + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ m4_ifval([$4],${rbcv_cond+[@%:@if ]$rbcv_cond}) [@%:@define ]attrib[](attrib_params)[ $mac] m4_ifval([$4],${rbcv_cond+[@%:@else]} @@ -30,7 +30,7 @@ ${rbcv_cond+[@%:@endif]}) $6 @%:@define mesg ("") @%:@define san "address" - attrib[](attrib_params)[;], [], + attrib[](attrib_params)[;]], [[]])], [rbcv="$mac"; break]) done ])]) diff --git a/tool/m4/ruby_dtrace_available.m4 b/tool/m4/ruby_dtrace_available.m4 index 79586d152c..babffaffac 100644 --- a/tool/m4/ruby_dtrace_available.m4 +++ b/tool/m4/ruby_dtrace_available.m4 @@ -7,7 +7,7 @@ AC_DEFUN([RUBY_DTRACE_AVAILABLE], AS_FOR(opt, rb_dtrace_opt, ["-xnolibs" ""], [dnl AS_IF([$DTRACE opt -h -o conftest_provider.h -s conftest_provider.d >/dev/null 2>/dev/null], [], [continue]) - AC_TRY_COMPILE([@%:@include "conftest_provider.h"], [CONFTEST_FIRE();], + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include "conftest_provider.h"]], [[CONFTEST_FIRE();]])], [], [continue]) # DTrace is available on the system rb_cv_dtrace_available=yes${rb_dtrace_opt:+"(opt)"} diff --git a/tool/m4/ruby_dtrace_postprocess.m4 b/tool/m4/ruby_dtrace_postprocess.m4 index 9ef088b3f8..1cb651b481 100644 --- a/tool/m4/ruby_dtrace_postprocess.m4 +++ b/tool/m4/ruby_dtrace_postprocess.m4 @@ -12,7 +12,7 @@ _PROBES $DTRACE ${DTRACE_OPT} -h -o conftest_provider.h -s conftest_provider.d >/dev/null 2>/dev/null && : }], [ - AC_TRY_COMPILE([@%:@include "conftest_provider.h"], [CONFTEST_FIRE();], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include "conftest_provider.h"]], [[CONFTEST_FIRE();]])],[ AS_IF([{ cp -p conftest.${ac_objext} conftest.${ac_objext}.save && $DTRACE ${DTRACE_OPT} -G -s conftest_provider.d conftest.${ac_objext} 2>/dev/null && diff --git a/tool/m4/ruby_mingw32.m4 b/tool/m4/ruby_mingw32.m4 index f44fe5575c..76b95f02a8 100644 --- a/tool/m4/ruby_mingw32.m4 +++ b/tool/m4/ruby_mingw32.m4 @@ -3,11 +3,11 @@ AC_DEFUN([RUBY_MINGW32], [AS_CASE(["$host_os"], [cygwin*], [ AC_CACHE_CHECK(for mingw32 environment, rb_cv_mingw32, -[AC_TRY_CPP([ +[AC_PREPROC_IFELSE([AC_LANG_SOURCE([[ #ifndef __MINGW32__ # error #endif -], rb_cv_mingw32=yes,rb_cv_mingw32=no) +]])],[rb_cv_mingw32=yes],[rb_cv_mingw32=no]) rm -f conftest*]) AS_IF([test "$rb_cv_mingw32" = yes], [ target_os="mingw32" diff --git a/tool/m4/ruby_stack_grow_direction.m4 b/tool/m4/ruby_stack_grow_direction.m4 index 74ec219322..f5f93579a4 100644 --- a/tool/m4/ruby_stack_grow_direction.m4 +++ b/tool/m4/ruby_stack_grow_direction.m4 @@ -6,7 +6,7 @@ AS_CASE(["$1"], [m68*|x86*|x64|i?86|ppc*|sparc*|alpha*], [ $2=-1], [hppa*], [ $2=+1], [ - AC_TRY_RUN([ + AC_RUN_IFELSE([AC_LANG_SOURCE([[ /* recurse to get rid of inlining */ static int stack_growup_p(addr, n) @@ -23,7 +23,7 @@ int main() int x; return stack_growup_p(&x, 10); } -], $2=-1, $2=+1, $2=0) +]])],[$2=-1],[$2=+1],[$2=0]) ]) eval stack_grow_dir=\$$2]) eval $2=\$stack_grow_dir diff --git a/tool/m4/ruby_try_cflags.m4 b/tool/m4/ruby_try_cflags.m4 index 86ab80e1e6..8c9f22d50c 100644 --- a/tool/m4/ruby_try_cflags.m4 +++ b/tool/m4/ruby_try_cflags.m4 @@ -3,7 +3,7 @@ AC_DEFUN([RUBY_TRY_CFLAGS], [ AC_MSG_CHECKING([whether ]$1[ is accepted as CFLAGS]) RUBY_WERROR_FLAG([ CFLAGS="[$]CFLAGS $1" - AC_TRY_COMPILE([$4], [$5], + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[$4]], [[$5]])], [$2 AC_MSG_RESULT(yes)], [$3 diff --git a/tool/m4/ruby_try_cxxflags.m4 b/tool/m4/ruby_try_cxxflags.m4 index 5f84140670..265f79a450 100644 --- a/tool/m4/ruby_try_cxxflags.m4 +++ b/tool/m4/ruby_try_cxxflags.m4 @@ -5,7 +5,7 @@ AC_DEFUN([RUBY_TRY_CXXFLAGS], [ AC_MSG_CHECKING([whether ]$1[ is accepted as CXXFLAGS]) RUBY_WERROR_FLAG([ AC_LANG_PUSH([C++]) - AC_TRY_LINK([$4], [$5], + AC_LINK_IFELSE([AC_LANG_PROGRAM([[$4]], [[$5]])], [$2 AC_MSG_RESULT(yes)], [$3 diff --git a/tool/m4/ruby_try_ldflags.m4 b/tool/m4/ruby_try_ldflags.m4 index b275107ed9..d27940c7b2 100644 --- a/tool/m4/ruby_try_ldflags.m4 +++ b/tool/m4/ruby_try_ldflags.m4 @@ -4,7 +4,7 @@ AC_DEFUN([RUBY_TRY_LDFLAGS], [ LDFLAGS="[$]LDFLAGS $1" AC_MSG_CHECKING([whether $1 is accepted as LDFLAGS]) RUBY_WERROR_FLAG([ - AC_TRY_LINK([$4], [$5], + AC_LINK_IFELSE([AC_LANG_PROGRAM([[$4]], [[$5]])], [$2 AC_MSG_RESULT(yes)], [$3 diff --git a/tool/rbinstall.rb b/tool/rbinstall.rb index b49d91fc3f..7232865c9e 100755 --- a/tool/rbinstall.rb +++ b/tool/rbinstall.rb @@ -20,6 +20,7 @@ require 'fileutils' require 'shellwords' require 'optparse' require 'optparse/shellwords' +require 'pathname' require 'rubygems' begin require "zlib" @@ -701,6 +702,10 @@ module RbInstall when "lib" base = @base_dir prefix = base.sub(/lib\/.*?\z/, "") + "lib/" + # for lib/net/net-smtp.gemspec + if m = Pathname.new(@gemspec).basename(".gemspec").to_s.match(/.*\-(.*)\z/) + base = "#{@base_dir}/#{m[1]}" unless remove_prefix(prefix, @base_dir).include?(m[1]) + end end if base diff --git a/tool/ruby_vm/views/_mjit_compile_getinlinecache.erb b/tool/ruby_vm/views/_mjit_compile_getinlinecache.erb index 1b636bceb6..1acfdb7f0b 100644 --- a/tool/ruby_vm/views/_mjit_compile_getinlinecache.erb +++ b/tool/ruby_vm/views/_mjit_compile_getinlinecache.erb @@ -13,17 +13,10 @@ % # compiler: Capture IC values, locking getinlinecache struct iseq_inline_constant_cache_entry *ice = ic->entry; - if (ice == NULL) { - goto getinlinecache_cancel; - } - rb_serial_t ic_serial = ice->ic_serial; - const rb_cref_t *ic_cref = ice->ic_cref; - VALUE ic_value = ice->value; - - if (ic_serial && !status->compile_info->disable_const_cache) { + if (ice != NULL && ice->ic_serial && !status->compile_info->disable_const_cache) { % # JIT: Inline everything in IC, and cancel the slow path - fprintf(f, " if (vm_ic_hit_p((rb_serial_t)%"PRI_SERIALT_PREFIX"u, (const rb_cref_t *)0x%"PRIxVALUE", reg_cfp->ep)) {", ic_serial, (VALUE)ic_cref); - fprintf(f, " stack[%d] = 0x%"PRIxVALUE";\n", b->stack_size, ic_value); + fprintf(f, " if (vm_inlined_ic_hit_p(0x%"PRIxVALUE", 0x%"PRIxVALUE", (const rb_cref_t *)0x%"PRIxVALUE", %"PRI_SERIALT_PREFIX"u, reg_cfp->ep)) {", ice->flags, ice->value, (VALUE)ice->ic_cref, ice->ic_serial); + fprintf(f, " stack[%d] = 0x%"PRIxVALUE";\n", b->stack_size, ice->value); fprintf(f, " goto label_%d;\n", pos + insn_len(insn) + (int)dst); fprintf(f, " }"); fprintf(f, " else {"); @@ -36,4 +29,3 @@ b->stack_size += <%= insn.call_attribute('sp_inc') %>; break; } - getinlinecache_cancel:; diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 620215067d..77854a6a48 100644 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -94,11 +94,14 @@ def sync_default_gems(gem) cp_r(Dir.glob("#{upstream}/lib/rubygems*"), "lib") cp_r("#{upstream}/test/rubygems", "test") when "bundler" - rm_rf(%w[lib/bundler lib/bundler.rb libexec/bundler libexec/bundle spec/bundler] + Dir.glob("man/{bundle*,gemfile*}")) + rm_rf(%w[lib/bundler lib/bundler.rb libexec/bundler libexec/bundle spec/bundler tool/bundler/*] + Dir.glob("man/{bundle*,gemfile*}")) cp_r(Dir.glob("#{upstream}/bundler/lib/bundler*"), "lib") cp_r(Dir.glob("#{upstream}/bundler/exe/bundle*"), "libexec") cp_r("#{upstream}/bundler/bundler.gemspec", "lib/bundler") cp_r("#{upstream}/bundler/spec", "spec/bundler") + cp_r(Dir.glob("#{upstream}/bundler/tool/bundler/test_gems*"), "tool/bundler") + cp_r(Dir.glob("#{upstream}/bundler/tool/bundler/rubocop_gems*"), "tool/bundler") + cp_r(Dir.glob("#{upstream}/bundler/tool/bundler/standard_gems*"), "tool/bundler") cp_r(Dir.glob("#{upstream}/bundler/man/*.{1,5,1\.txt,5\.txt,ronn}"), "man") rm_rf(%w[spec/bundler/support/artifice/vcr_cassettes]) when "rdoc" diff --git a/tool/test-bundled-gems.rb b/tool/test-bundled-gems.rb index b9a1e559d4..48be1bef24 100644 --- a/tool/test-bundled-gems.rb +++ b/tool/test-bundled-gems.rb @@ -25,6 +25,8 @@ File.foreach("#{gem_dir}/bundled_gems") do |line| test_command << " stdlib_test validate" first_timeout *= 3 + elsif gem == "test-unit" + test_command = "#{ruby} -C #{gem_dir}/src/#{gem} test/run-test.rb" end puts test_command diff --git a/transcode.c b/transcode.c index a72afdc44b..d2abd9e0e5 100644 --- a/transcode.c +++ b/transcode.c @@ -2719,6 +2719,12 @@ str_transcode0(int argc, VALUE *argv, VALUE *self, int ecflags, VALUE ecopts) } } else { + if (senc && denc && !rb_enc_asciicompat(senc) && !rb_enc_asciicompat(denc)) { + rb_encoding *utf8 = rb_utf8_encoding(); + str = rb_str_conv_enc(str, senc, utf8); + senc = utf8; + sname = "UTF-8"; + } if (encoding_equal(sname, dname)) { sname = ""; dname = ""; @@ -29,6 +29,7 @@ #include "internal/sanitizers.h" #include "internal/util.h" #include "ruby/util.h" +#include "ruby_atomic.h" const char ruby_hexdigits[] = "0123456789abcdef0123456789ABCDEF"; #define hexdigit ruby_hexdigits @@ -10,13 +10,13 @@ */ # define RUBY_VERSION_MAJOR RUBY_API_VERSION_MAJOR # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR -#define RUBY_VERSION_TEENY 0 +#define RUBY_VERSION_TEENY 7 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 42 +#define RUBY_PATCHLEVEL 220 -#define RUBY_RELEASE_YEAR 2021 -#define RUBY_RELEASE_MONTH 2 -#define RUBY_RELEASE_DAY 16 +#define RUBY_RELEASE_YEAR 2024 +#define RUBY_RELEASE_MONTH 4 +#define RUBY_RELEASE_DAY 23 #include "ruby/version.h" @@ -1005,7 +1005,7 @@ env_copy(const VALUE *src_ep, VALUE read_only_variables) volatile VALUE prev_env = Qnil; if (read_only_variables) { - for (int i=0; i<RARRAY_LENINT(read_only_variables); i++) { + for (int i=RARRAY_LENINT(read_only_variables)-1; i>=0; i--) { ID id = SYM2ID(rb_str_intern(RARRAY_AREF(read_only_variables, i))); for (unsigned int j=0; j<src_env->iseq->body->local_table_size; j++) { @@ -1796,7 +1796,7 @@ rb_iter_break_value(VALUE val) /* optimization: redefine management */ -static st_table *vm_opt_method_table = 0; +static st_table *vm_opt_method_def_table = 0; static st_table *vm_opt_mid_table = 0; static int @@ -1850,9 +1850,8 @@ rb_vm_check_redefinition_opt_method(const rb_method_entry_t *me, VALUE klass) klass = RBASIC_CLASS(klass); } if (vm_redefinition_check_method_type(me->def)) { - if (st_lookup(vm_opt_method_table, (st_data_t)me, &bop)) { - int flag = vm_redefinition_check_flag(klass); - + if (st_lookup(vm_opt_method_def_table, (st_data_t)me->def, &bop)) { + int flag = vm_redefinition_check_flag(klass); ruby_vm_redefined_flag[bop] |= flag; } } @@ -1883,7 +1882,7 @@ add_opt_method(VALUE klass, ID mid, VALUE bop) const rb_method_entry_t *me = rb_method_entry_at(klass, mid); if (me && vm_redefinition_check_method_type(me->def)) { - st_insert(vm_opt_method_table, (st_data_t)me, (st_data_t)bop); + st_insert(vm_opt_method_def_table, (st_data_t)me->def, (st_data_t)bop); st_insert(vm_opt_mid_table, (st_data_t)mid, (st_data_t)Qtrue); } else { @@ -1897,7 +1896,7 @@ vm_init_redefined_flag(void) ID mid; VALUE bop; - vm_opt_method_table = st_init_numtable(); + vm_opt_method_def_table = st_init_numtable(); vm_opt_mid_table = st_init_numtable(); #define OP(mid_, bop_) (mid = id##mid_, bop = BOP_##bop_, ruby_vm_redefined_flag[bop] = 0) @@ -2589,6 +2588,18 @@ rb_vm_mark(void *ptr) rb_gc_mark_values(RUBY_NSIG, vm->trap_list.cmd); rb_id_table_foreach_values(vm->negative_cme_table, vm_mark_negative_cme, NULL); + for (i=0; i<VM_GLOBAL_CC_CACHE_TABLE_SIZE; i++) { + const struct rb_callcache *cc = vm->global_cc_cache_table[i]; + + if (cc != NULL) { + if (!vm_cc_invalidated_p(cc)) { + rb_gc_mark((VALUE)cc); + } + else { + vm->global_cc_cache_table[i] = NULL; + } + } + } mjit_mark(); } @@ -2653,6 +2664,8 @@ ruby_vm_destruct(rb_vm_t *vm) if (objspace) { rb_objspace_free(objspace); } + rb_native_mutex_destroy(&vm->waitpid_lock); + rb_native_mutex_destroy(&vm->workqueue_lock); /* after freeing objspace, you *can't* use ruby_xfree() */ ruby_mimfree(vm); ruby_current_vm_ptr = NULL; @@ -2795,6 +2808,11 @@ rb_execution_context_update(const rb_execution_context_t *ec) cfp->block_code = (void *)rb_gc_location((VALUE)cfp->block_code); if (!VM_ENV_LOCAL_P(ep)) { + const VALUE *prev_ep = VM_ENV_PREV_EP(ep); + if (VM_ENV_FLAGS(prev_ep, VM_ENV_FLAG_ESCAPED)) { + VM_FORCE_WRITE(&prev_ep[VM_ENV_DATA_INDEX_ENV], rb_gc_location(prev_ep[VM_ENV_DATA_INDEX_ENV])); + } + if (VM_ENV_FLAGS(ep, VM_ENV_FLAG_ESCAPED)) { VM_FORCE_WRITE(&ep[VM_ENV_DATA_INDEX_ENV], rb_gc_location(ep[VM_ENV_DATA_INDEX_ENV])); VM_FORCE_WRITE(&ep[VM_ENV_DATA_INDEX_ME_CREF], rb_gc_location(ep[VM_ENV_DATA_INDEX_ME_CREF])); @@ -2835,6 +2853,11 @@ rb_execution_context_mark(const rb_execution_context_t *ec) rb_gc_mark_movable((VALUE)cfp->block_code); if (!VM_ENV_LOCAL_P(ep)) { + const VALUE *prev_ep = VM_ENV_PREV_EP(ep); + if (VM_ENV_FLAGS(prev_ep, VM_ENV_FLAG_ESCAPED)) { + rb_gc_mark_movable(prev_ep[VM_ENV_DATA_INDEX_ENV]); + } + if (VM_ENV_FLAGS(ep, VM_ENV_FLAG_ESCAPED)) { rb_gc_mark_movable(ep[VM_ENV_DATA_INDEX_ENV]); rb_gc_mark(ep[VM_ENV_DATA_INDEX_ME_CREF]); @@ -3059,6 +3082,8 @@ th_init(rb_thread_t *th, VALUE self) th->thread_id_string[0] = '\0'; #endif + th->value = Qundef; + #if OPT_CALL_THREADED_CODE th->retval = Qundef; #endif @@ -3071,16 +3096,17 @@ static VALUE ruby_thread_init(VALUE self) { rb_thread_t *th = GET_THREAD(); - rb_thread_t *targe_th = rb_thread_ptr(self); + rb_thread_t *target_th = rb_thread_ptr(self); rb_vm_t *vm = th->vm; - targe_th->vm = vm; - th_init(targe_th, self); + target_th->vm = vm; + th_init(target_th, self); + + target_th->top_wrapper = 0; + target_th->top_self = rb_vm_top_self(); + target_th->ec->root_svar = Qfalse; + target_th->ractor = th->ractor; - targe_th->top_wrapper = 0; - targe_th->top_self = rb_vm_top_self(); - targe_th->ec->root_svar = Qfalse; - targe_th->ractor = th->ractor; return self; } @@ -3645,6 +3671,8 @@ Init_VM(void) * The Binding of the top level scope */ rb_define_global_const("TOPLEVEL_BINDING", rb_binding_new()); + + rb_objspace_gc_enable(vm->objspace); } vm_init_redefined_flag(); @@ -3710,8 +3738,6 @@ Init_vm_objects(void) vm->mark_object_ary = rb_ary_tmp_new(128); vm->loading_table = st_init_strtable(); vm->frozen_strings = st_init_table_with_size(&rb_fstring_hash_type, 10000); - - rb_objspace_gc_enable(vm->objspace); } /* top self */ @@ -710,8 +710,6 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co return opt_pc; } -void rb_backtrace_use_iseq_first_lineno_for_last_location(VALUE self); /* vm_backtrace.c */ - static void raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const VALUE exc) { diff --git a/vm_backtrace.c b/vm_backtrace.c index f2cc21294c..70c2ab7b8a 100644 --- a/vm_backtrace.c +++ b/vm_backtrace.c @@ -502,6 +502,9 @@ backtrace_size(const rb_execution_context_t *ec) return start_cfp - last_cfp + 1; } +static bool is_internal_location(const rb_control_frame_t *cfp); +static void bt_iter_skip_skip_internal(void *ptr, const rb_control_frame_t *cfp); + static int backtrace_each(const rb_execution_context_t *ec, ptrdiff_t from_last, @@ -515,7 +518,7 @@ backtrace_each(const rb_execution_context_t *ec, const rb_control_frame_t *last_cfp = ec->cfp; const rb_control_frame_t *start_cfp = RUBY_VM_END_CONTROL_FRAME(ec); const rb_control_frame_t *cfp; - ptrdiff_t size, i, last, start = 0; + ptrdiff_t size, real_size, i, j, last, start = 0; int ret = 0; // In the case the thread vm_stack or cfp is not initialized, there is no backtrace. @@ -539,10 +542,18 @@ backtrace_each(const rb_execution_context_t *ec, RUBY_VM_NEXT_CONTROL_FRAME(start_cfp)); /* skip top frames */ if (start_cfp < last_cfp) { - size = last = 0; + real_size = size = last = 0; } else { - size = start_cfp - last_cfp + 1; + /* Ensure we don't look at frames beyond the ones requested */ + for(; from_last > 0 && start_cfp >= last_cfp; from_last--) { + if (last_cfp->iseq && !last_cfp->pc) { + from_last++; + } + last_cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(last_cfp); + } + + real_size = size = start_cfp - last_cfp + 1; if (from_last > size) { size = last = 0; @@ -567,9 +578,45 @@ backtrace_each(const rb_execution_context_t *ec, init(arg, size); - /* SDR(); */ - for (i=0, cfp = start_cfp; i<last; i++, cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) { - if (i < start) { + /* If a limited number of frames is requested, scan the VM stack for + * from the current frame (after skipping the number of frames requested above) + * towards the earliest frame (start_cfp). Track the total number of frames + * and the number of frames that will be part of the backtrace. Start the + * scan at the oldest frame that should still be part of the backtrace. + * + * If the last frame in the backtrace is a cfunc frame, continue scanning + * till earliest frame to find the first iseq frame with pc, so that the + * location can be used for the trailing cfunc frames in the backtrace. + */ + if (start > 0 && num_frames >= 0 && num_frames < real_size) { + int found_frames = 0, total_frames = 0; + bool last_frame_cfunc = FALSE; + const rb_control_frame_t *new_start_cfp; + + for (cfp = last_cfp; found_frames < num_frames && start_cfp >= cfp; cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp), total_frames++) { + if ((cfp->iseq && cfp->pc) || RUBYVM_CFUNC_FRAME_P(cfp)) { + last_frame_cfunc = RUBYVM_CFUNC_FRAME_P(cfp); + found_frames++; + } + } + new_start_cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp); + if (iter_skip && (last_frame_cfunc || iter_skip == bt_iter_skip_skip_internal)) { + for (; start_cfp >= cfp; cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)) { + if (cfp->iseq && cfp->pc && (iter_skip != bt_iter_skip_skip_internal || !is_internal_location(cfp))) { + iter_skip(arg, cfp); + break; + } + } + } + + last = found_frames; + real_size = total_frames; + start = 0; + start_cfp = new_start_cfp; + } + + for (i=0, j=0, cfp = start_cfp; i<last && j<real_size; i++, j++, cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) { + if (j < start) { if (iter_skip) { iter_skip(arg, cfp); } @@ -578,9 +625,11 @@ backtrace_each(const rb_execution_context_t *ec, /* fprintf(stderr, "cfp: %d\n", (rb_control_frame_t *)(ec->vm_stack + ec->vm_stack_size) - cfp); */ if (cfp->iseq) { - if (cfp->pc) { - iter_iseq(arg, cfp); - } + if (cfp->pc) { + iter_iseq(arg, cfp); + } else { + i--; + } } else if (RUBYVM_CFUNC_FRAME_P(cfp)) { const rb_callable_method_entry_t *me = rb_vm_frame_method_entry(cfp); diff --git a/vm_callinfo.h b/vm_callinfo.h index 4ee6fa72e6..081b67d2b9 100644 --- a/vm_callinfo.h +++ b/vm_callinfo.h @@ -357,6 +357,17 @@ vm_cc_markable(const struct rb_callcache *cc) return FL_TEST_RAW((VALUE)cc, VM_CALLCACHE_UNMARKABLE) == 0; } +static inline bool +vm_cc_invalidated_p(const struct rb_callcache *cc) +{ + if (cc->klass && METHOD_ENTRY_INVALIDATED(vm_cc_cme(cc))) { + return false; + } + else { + return true; + } +} + // For MJIT. cc_cme is supposed to have inlined `vm_cc_cme(cc)`. static inline bool vm_cc_valid_p(const struct rb_callcache *cc, const rb_callable_method_entry_t *cc_cme, VALUE klass) @@ -376,18 +387,6 @@ extern const struct rb_callcache *rb_vm_empty_cc(void); /* callcache: mutate */ static inline void -vm_cc_cme_set(const struct rb_callcache *cc, const struct rb_callable_method_entry_struct *cme) -{ - VM_ASSERT(IMEMO_TYPE_P(cc, imemo_callcache)); - VM_ASSERT(cc != vm_cc_empty()); - VM_ASSERT(vm_cc_cme(cc) != NULL); - VM_ASSERT(vm_cc_cme(cc)->called_id == cme->called_id); - VM_ASSERT(!vm_cc_markable(cc)); // only used for vm_eval.c - - *((const struct rb_callable_method_entry_struct **)&cc->cme_) = cme; -} - -static inline void vm_cc_call_set(const struct rb_callcache *cc, vm_call_handler call) { VM_ASSERT(IMEMO_TYPE_P(cc, imemo_callcache)); @@ -136,7 +136,7 @@ void *rb_allocate_sigaltstack(void); void *rb_register_sigaltstack(void *); # define RB_ALTSTACK_INIT(var, altstack) var = rb_register_sigaltstack(altstack) -# define RB_ALTSTACK_FREE(var) xfree(var) +# define RB_ALTSTACK_FREE(var) free(var) # define RB_ALTSTACK(var) var #else /* noop */ # define RB_ALTSTACK_INIT(var, altstack) @@ -658,6 +658,11 @@ typedef struct rb_vm_struct { struct rb_id_table *negative_cme_table; +#ifndef VM_GLOBAL_CC_CACHE_TABLE_SIZE +#define VM_GLOBAL_CC_CACHE_TABLE_SIZE 1023 +#endif + const struct rb_callcache *global_cc_cache_table[VM_GLOBAL_CC_CACHE_TABLE_SIZE]; // vm_eval.c + #if USE_VM_CLOCK uint32_t clock; #endif @@ -1755,7 +1760,7 @@ RUBY_SYMBOL_EXPORT_END #define GET_VM() rb_current_vm() #define GET_RACTOR() rb_current_ractor() #define GET_THREAD() rb_current_thread() -#define GET_EC() rb_current_execution_context() +#define GET_EC() rb_current_execution_context(true) static inline rb_thread_t * rb_ec_thread_ptr(const rb_execution_context_t *ec) @@ -1789,7 +1794,7 @@ rb_ec_vm_ptr(const rb_execution_context_t *ec) } static inline rb_execution_context_t * -rb_current_execution_context(void) +rb_current_execution_context(bool expect_ec) { #ifdef RB_THREAD_LOCAL_SPECIFIER #if __APPLE__ @@ -1800,7 +1805,7 @@ rb_current_execution_context(void) #else rb_execution_context_t *ec = native_tls_get(ruby_current_ec_key); #endif - VM_ASSERT(ec != NULL); + VM_ASSERT(!expect_ec || ec != NULL); return ec; } @@ -982,7 +982,7 @@ rb_vm_bugreport(const void *ctx) enum {other_runtime_info = 0}; #endif const rb_vm_t *const vm = GET_VM(); - const rb_execution_context_t *ec = GET_EC(); + const rb_execution_context_t *ec = rb_current_execution_context(false); if (vm && ec) { SDR(); @@ -1019,36 +1019,38 @@ rb_vm_bugreport(const void *ctx) LIMITED_NAME_LENGTH(name), RSTRING_PTR(name)); fprintf(stderr, "\n"); } - fprintf(stderr, "* Loaded features:\n\n"); - for (i=0; i<RARRAY_LEN(vm->loaded_features); i++) { - name = RARRAY_AREF(vm->loaded_features, i); - if (RB_TYPE_P(name, T_STRING)) { - fprintf(stderr, " %4d %.*s\n", i, - LIMITED_NAME_LENGTH(name), RSTRING_PTR(name)); - } - else if (RB_TYPE_P(name, T_CLASS) || RB_TYPE_P(name, T_MODULE)) { - const char *const type = RB_TYPE_P(name, T_CLASS) ? - "class" : "module"; - name = rb_search_class_path(rb_class_real(name)); - if (!RB_TYPE_P(name, T_STRING)) { - fprintf(stderr, " %4d %s:<unnamed>\n", i, type); - continue; - } - fprintf(stderr, " %4d %s:%.*s\n", i, type, - LIMITED_NAME_LENGTH(name), RSTRING_PTR(name)); - } - else { - VALUE klass = rb_search_class_path(rb_obj_class(name)); - if (!RB_TYPE_P(klass, T_STRING)) { - fprintf(stderr, " %4d #<%p:%p>\n", i, - (void *)CLASS_OF(name), (void *)name); - continue; - } - fprintf(stderr, " %4d #<%.*s:%p>\n", i, - LIMITED_NAME_LENGTH(klass), RSTRING_PTR(klass), - (void *)name); - } - } + if (vm->loaded_features) { + fprintf(stderr, "* Loaded features:\n\n"); + for (i=0; i<RARRAY_LEN(vm->loaded_features); i++) { + name = RARRAY_AREF(vm->loaded_features, i); + if (RB_TYPE_P(name, T_STRING)) { + fprintf(stderr, " %4d %.*s\n", i, + LIMITED_NAME_LENGTH(name), RSTRING_PTR(name)); + } + else if (RB_TYPE_P(name, T_CLASS) || RB_TYPE_P(name, T_MODULE)) { + const char *const type = RB_TYPE_P(name, T_CLASS) ? + "class" : "module"; + name = rb_search_class_path(rb_class_real(name)); + if (!RB_TYPE_P(name, T_STRING)) { + fprintf(stderr, " %4d %s:<unnamed>\n", i, type); + continue; + } + fprintf(stderr, " %4d %s:%.*s\n", i, type, + LIMITED_NAME_LENGTH(name), RSTRING_PTR(name)); + } + else { + VALUE klass = rb_search_class_path(rb_obj_class(name)); + if (!RB_TYPE_P(klass, T_STRING)) { + fprintf(stderr, " %4d #<%p:%p>\n", i, + (void *)CLASS_OF(name), (void *)name); + continue; + } + fprintf(stderr, " %4d #<%.*s:%p>\n", i, + LIMITED_NAME_LENGTH(klass), RSTRING_PTR(klass), + (void *)name); + } + } + } fprintf(stderr, "\n"); } @@ -48,7 +48,7 @@ rb_vm_call0(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE struct rb_calling_info calling = { .ci = &VM_CI_ON_STACK(id, kw_splat ? VM_CALL_KW_SPLAT : 0, argc, NULL), .cc = &VM_CC_ON_STACK(Qfalse, vm_call_general, { 0 }, cme), - .block_handler = VM_BLOCK_HANDLER_NONE, + .block_handler = vm_passed_block_handler(ec), .recv = recv, .argc = argc, .kw_splat = kw_splat, @@ -57,13 +57,53 @@ rb_vm_call0(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE return vm_call0_body(ec, &calling, argv); } +static inline VALUE +vm_call0_cc(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, const struct rb_callcache *cc, int kw_splat) +{ + struct rb_calling_info calling = { + .ci = &VM_CI_ON_STACK(id, kw_splat ? VM_CALL_KW_SPLAT : 0, argc, NULL), + .cc = cc, + .block_handler = vm_passed_block_handler(ec), + .recv = recv, + .argc = argc, + .kw_splat = kw_splat, + }; + + return vm_call0_body(ec, &calling, argv); +} + +static VALUE +vm_call0_cme(rb_execution_context_t *ec, struct rb_calling_info *calling, const VALUE *argv, const rb_callable_method_entry_t *cme) +{ + calling->cc = &VM_CC_ON_STACK(Qfalse, vm_call_general, { 0 }, cme); + return vm_call0_body(ec, calling, argv); +} + +static VALUE +vm_call0_super(rb_execution_context_t *ec, struct rb_calling_info *calling, const VALUE *argv, VALUE klass, enum method_missing_reason ex) +{ + ID mid = vm_ci_mid(calling->ci); + klass = RCLASS_SUPER(klass); + + if (klass) { + const rb_callable_method_entry_t *cme = rb_callable_method_entry(klass, mid); + + if (cme) { + RUBY_VM_CHECK_INTS(ec); + return vm_call0_cme(ec, calling, argv, cme); + } + } + + vm_passed_block_handler_set(ec, calling->block_handler); + return method_missing(ec, calling->recv, mid, calling->argc, argv, ex, calling->kw_splat); +} + static VALUE vm_call0_cfunc_with_frame(rb_execution_context_t* ec, struct rb_calling_info *calling, const VALUE *argv) { const struct rb_callinfo *ci = calling->ci; - const struct rb_callcache *cc = calling->cc; VALUE val; - const rb_callable_method_entry_t *me = vm_cc_cme(cc); + const rb_callable_method_entry_t *me = vm_cc_cme(calling->cc); const rb_method_cfunc_t *cfunc = UNALIGNED_MEMBER_PTR(me->def, body.cfunc); int len = cfunc->argc; VALUE recv = calling->recv; @@ -115,12 +155,8 @@ vm_call0_body(rb_execution_context_t *ec, struct rb_calling_info *calling, const { const struct rb_callinfo *ci = calling->ci; const struct rb_callcache *cc = calling->cc; - VALUE ret; - calling->block_handler = vm_passed_block_handler(ec); - - again: switch (vm_cc_cme(cc)->def->type) { case VM_METHOD_TYPE_ISEQ: { @@ -169,38 +205,27 @@ vm_call0_body(rb_execution_context_t *ec, struct rb_calling_info *calling, const ret = vm_call_bmethod_body(ec, calling, argv); goto success; case VM_METHOD_TYPE_ZSUPER: + { + VALUE klass = RCLASS_ORIGIN(vm_cc_cme(cc)->defined_class); + return vm_call0_super(ec, calling, argv, klass, MISSING_SUPER); + } case VM_METHOD_TYPE_REFINED: { - const rb_method_type_t type = vm_cc_cme(cc)->def->type; - VALUE super_class = vm_cc_cme(cc)->defined_class; + const rb_callable_method_entry_t *cme = vm_cc_cme(cc); - if (type == VM_METHOD_TYPE_ZSUPER) { - super_class = RCLASS_ORIGIN(super_class); - } - else if (vm_cc_cme(cc)->def->body.refined.orig_me) { - vm_cc_cme_set(cc, refined_method_callable_without_refinement(vm_cc_cme(cc))); - goto again; - } - - super_class = RCLASS_SUPER(super_class); - if (super_class) { - vm_cc_cme_set(cc, rb_callable_method_entry(super_class, vm_ci_mid(ci))); - if (vm_cc_cme(cc)) { - RUBY_VM_CHECK_INTS(ec); - goto again; - } + if (cme->def->body.refined.orig_me) { + const rb_callable_method_entry_t *orig_cme = refined_method_callable_without_refinement(cme); + return vm_call0_cme(ec, calling, argv, orig_cme); } - enum method_missing_reason ex = (type == VM_METHOD_TYPE_ZSUPER) ? MISSING_SUPER : 0; - ret = method_missing(ec, calling->recv, vm_ci_mid(ci), calling->argc, argv, ex, calling->kw_splat); - goto success; + VALUE klass = cme->defined_class; + return vm_call0_super(ec, calling, argv, klass, 0); } case VM_METHOD_TYPE_ALIAS: - vm_cc_cme_set(cc, aliased_callable_method_entry(vm_cc_cme(cc))); - goto again; + return vm_call0_cme(ec, calling, argv, aliased_callable_method_entry(vm_cc_cme(cc))); case VM_METHOD_TYPE_MISSING: { - vm_passed_block_handler_set(ec, calling->block_handler); + vm_passed_block_handler_set(ec, calling->block_handler); return method_missing(ec, calling->recv, vm_ci_mid(ci), calling->argc, argv, MISSING_NOENTRY, calling->kw_splat); } @@ -302,9 +327,116 @@ stack_check(rb_execution_context_t *ec) #ifndef MJIT_HEADER +void +rb_check_stack_overflow(void) +{ +#ifndef RB_THREAD_LOCAL_SPECIFIER + if (!ruby_current_ec_key) return; +#endif + rb_execution_context_t *ec = GET_EC(); + if (ec) stack_check(ec); +} + +NORETURN(static void uncallable_object(VALUE recv, ID mid)); static inline const rb_callable_method_entry_t *rb_search_method_entry(VALUE recv, ID mid); static inline enum method_missing_reason rb_method_call_status(rb_execution_context_t *ec, const rb_callable_method_entry_t *me, call_type scope, VALUE self); +static const struct rb_callcache * +cc_new(VALUE klass, ID mid, int argc, const rb_callable_method_entry_t *cme) +{ + const struct rb_callcache *cc; + + RB_VM_LOCK_ENTER(); + { + struct rb_class_cc_entries *ccs; + struct rb_id_table *cc_tbl = RCLASS_CC_TBL(klass); + + if (rb_id_table_lookup(cc_tbl, mid, (VALUE*)&ccs)) { + // ok + } + else { + ccs = vm_ccs_create(klass, cme); + rb_id_table_insert(cc_tbl, mid, (VALUE)ccs); + } + + if (ccs->len > 0) { + cc = ccs->entries[0].cc; + } + else { + const struct rb_callinfo *ci = vm_ci_new(mid, 0, argc, false); // TODO: proper ci + cc = vm_cc_new(klass, cme, vm_call_general); + METHOD_ENTRY_CACHED_SET((struct rb_callable_method_entry_struct *)cme); + vm_ccs_push(klass, ccs, ci, cc); + } + } + RB_VM_LOCK_LEAVE(); + + return cc; +} + +static VALUE +gccct_hash(VALUE klass, ID mid) +{ + return (klass >> 3) ^ (VALUE)mid; +} + +NOINLINE(static const struct rb_callcache *gccct_method_search_slowpath(rb_vm_t *vm, VALUE klass, ID mid, int argc, unsigned int index)); + +static const struct rb_callcache * +gccct_method_search_slowpath(rb_vm_t *vm, VALUE klass, ID mid, int argc, unsigned int index) +{ + const rb_callable_method_entry_t *cme = rb_callable_method_entry(klass, mid); + const struct rb_callcache *cc; + + if (cme != NULL) { + cc = cc_new(klass, mid, argc, cme); + } + else { + cc = NULL; + } + + return vm->global_cc_cache_table[index] = cc; +} + +static inline const struct rb_callcache * +gccct_method_search(rb_execution_context_t *ec, VALUE recv, ID mid, int argc) +{ + VALUE klass; + + if (!SPECIAL_CONST_P(recv)) { + klass = RBASIC_CLASS(recv); + if (UNLIKELY(!klass)) uncallable_object(recv, mid); + } + else { + klass = CLASS_OF(recv); + } + + // search global method cache + unsigned int index = (unsigned int)(gccct_hash(klass, mid) % VM_GLOBAL_CC_CACHE_TABLE_SIZE); + rb_vm_t *vm = rb_ec_vm_ptr(ec); + const struct rb_callcache *cc = vm->global_cc_cache_table[index]; + + if (LIKELY(cc)) { + if (LIKELY(vm_cc_class_check(cc, klass))) { + const rb_callable_method_entry_t *cme = vm_cc_cme(cc); + if (LIKELY(!METHOD_ENTRY_INVALIDATED(cme) && + cme->called_id == mid)) { + + VM_ASSERT(vm_cc_cme(cc) == rb_callable_method_entry(klass, mid)); + RB_DEBUG_COUNTER_INC(gccct_hit); + + return cc; + } + } + } + else { + RB_DEBUG_COUNTER_INC(gccct_null); + } + + RB_DEBUG_COUNTER_INC(gccct_miss); + return gccct_method_search_slowpath(vm, klass, mid, argc, index); +} + /*! * \internal * calls the specified method. @@ -326,7 +458,6 @@ rb_call0(rb_execution_context_t *ec, VALUE recv, ID mid, int argc, const VALUE *argv, call_type call_scope, VALUE self) { - const rb_callable_method_entry_t *me; enum method_missing_reason call_status; call_type scope = call_scope; int kw_splat = RB_NO_KEYWORDS; @@ -344,21 +475,34 @@ rb_call0(rb_execution_context_t *ec, break; } + const struct rb_callcache *cc = gccct_method_search(ec, recv, mid, argc); + if (scope == CALL_PUBLIC) { RB_DEBUG_COUNTER_INC(call0_public); - me = rb_callable_method_entry_with_refinements(CLASS_OF(recv), mid, NULL); + + const rb_callable_method_entry_t *cc_cme = cc ? vm_cc_cme(cc) : NULL; + const rb_callable_method_entry_t *cme = callable_method_entry_refeinements0(CLASS_OF(recv), mid, NULL, true, cc_cme); + call_status = rb_method_call_status(ec, cme, scope, self); + + if (UNLIKELY(call_status != MISSING_NONE)) { + return method_missing(ec, recv, mid, argc, argv, call_status, kw_splat); + } + else if (UNLIKELY(cc_cme != cme)) { // refinement is solved + stack_check(ec); + return rb_vm_call_kw(ec, recv, mid, argc, argv, cme, kw_splat); + } } else { RB_DEBUG_COUNTER_INC(call0_other); - me = rb_search_method_entry(recv, mid); - } - call_status = rb_method_call_status(ec, me, scope, self); + call_status = rb_method_call_status(ec, cc ? vm_cc_cme(cc) : NULL, scope, self); - if (call_status != MISSING_NONE) { - return method_missing(ec, recv, mid, argc, argv, call_status, kw_splat); + if (UNLIKELY(call_status != MISSING_NONE)) { + return method_missing(ec, recv, mid, argc, argv, call_status, kw_splat); + } } + stack_check(ec); - return rb_vm_call_kw(ec, recv, mid, argc, argv, me, kw_splat); + return vm_call0_cc(ec, recv, mid, argc, argv, cc, kw_splat); } struct rescue_funcall_args { @@ -576,7 +720,6 @@ rb_type_str(enum ruby_value_type type) return NULL; } -NORETURN(static void uncallable_object(VALUE recv, ID mid)); static void uncallable_object(VALUE recv, ID mid) { @@ -850,6 +993,45 @@ method_missing(rb_execution_context_t *ec, VALUE obj, ID id, int argc, const VAL #ifndef MJIT_HEADER +static inline VALUE +rb_funcallv_scope(VALUE recv, ID mid, int argc, const VALUE *argv, call_type scope) +{ + rb_execution_context_t *ec = GET_EC(); + const struct rb_callcache *cc = gccct_method_search(ec, recv, mid, argc); + VALUE self = ec->cfp->self; + + if (LIKELY(cc) && + LIKELY(rb_method_call_status(ec, vm_cc_cme(cc), scope, self) == MISSING_NONE)) { + // fastpath + return vm_call0_cc(ec, recv, mid, argc, argv, cc, false); + } + else { + return rb_call0(ec, recv, mid, argc, argv, scope, self); + } +} + +#ifdef rb_funcallv +#undef rb_funcallv +#endif +/*! + * Calls a method + * \param recv receiver of the method + * \param mid an ID that represents the name of the method + * \param argc the number of arguments + * \param argv pointer to an array of method arguments + */ +VALUE +rb_funcallv(VALUE recv, ID mid, int argc, const VALUE *argv) +{ + return rb_funcallv_scope(recv, mid, argc, argv, CALL_FCALL); +} + +VALUE +rb_funcallv_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int kw_splat) +{ + return rb_call(recv, mid, argc, argv, kw_splat ? CALL_FCALL_KW : CALL_FCALL); +} + /*! * Calls a method * \param recv receiver of the method @@ -875,7 +1057,8 @@ rb_apply(VALUE recv, ID mid, VALUE args) } argv = ALLOCA_N(VALUE, argc); MEMCPY(argv, RARRAY_CONST_PTR_TRANSIENT(args), VALUE, argc); - return rb_call(recv, mid, argc, argv, CALL_FCALL); + + return rb_funcallv(recv, mid, argc, argv); } #ifdef rb_funcall @@ -911,29 +1094,7 @@ rb_funcall(VALUE recv, ID mid, int n, ...) else { argv = 0; } - return rb_call(recv, mid, n, argv, CALL_FCALL); -} - -#ifdef rb_funcallv -#undef rb_funcallv -#endif -/*! - * Calls a method - * \param recv receiver of the method - * \param mid an ID that represents the name of the method - * \param argc the number of arguments - * \param argv pointer to an array of method arguments - */ -VALUE -rb_funcallv(VALUE recv, ID mid, int argc, const VALUE *argv) -{ - return rb_call(recv, mid, argc, argv, CALL_FCALL); -} - -VALUE -rb_funcallv_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int kw_splat) -{ - return rb_call(recv, mid, argc, argv, kw_splat ? CALL_FCALL_KW : CALL_FCALL); + return rb_funcallv(recv, mid, n, argv); } /*! @@ -963,7 +1124,6 @@ rb_check_funcall_basic_kw(VALUE recv, ID mid, VALUE ancestor, int argc, const VA return Qundef; } - /*! * Calls a method. * @@ -976,7 +1136,7 @@ rb_check_funcall_basic_kw(VALUE recv, ID mid, VALUE ancestor, int argc, const VA VALUE rb_funcallv_public(VALUE recv, ID mid, int argc, const VALUE *argv) { - return rb_call(recv, mid, argc, argv, CALL_PUBLIC); + return rb_funcallv_scope(recv, mid, argc, argv, CALL_PUBLIC); } VALUE @@ -989,7 +1149,7 @@ VALUE rb_funcall_passing_block(VALUE recv, ID mid, int argc, const VALUE *argv) { PASS_PASSED_BLOCK_HANDLER(); - return rb_call(recv, mid, argc, argv, CALL_PUBLIC); + return rb_funcallv_public(recv, mid, argc, argv); } VALUE @@ -1006,7 +1166,7 @@ rb_funcall_with_block(VALUE recv, ID mid, int argc, const VALUE *argv, VALUE pas vm_passed_block_handler_set(GET_EC(), passed_procval); } - return rb_call(recv, mid, argc, argv, CALL_PUBLIC); + return rb_funcallv_public(recv, mid, argc, argv); } VALUE @@ -1181,10 +1341,10 @@ VALUE rb_yield(VALUE val) { if (val == Qundef) { - return rb_yield_0(0, 0); + return rb_yield_0(0, NULL); } else { - return rb_yield_1(val); + return rb_yield_0(1, &val); } } diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 6bbb4bca9b..757e75d66f 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -237,7 +237,7 @@ static bool vm_stack_canary_was_born = false; #ifndef MJIT_HEADER MJIT_FUNC_EXPORTED void -vm_check_canary(const rb_execution_context_t *ec, VALUE *sp) +rb_vm_check_canary(const rb_execution_context_t *ec, VALUE *sp) { const struct rb_control_frame_struct *reg_cfp = ec->cfp; const struct rb_iseq_struct *iseq; @@ -284,6 +284,7 @@ vm_check_canary(const rb_execution_context_t *ec, VALUE *sp) rb_bug("see above."); } #endif +#define vm_check_canary(ec, sp) rb_vm_check_canary(ec, sp) #else #define vm_check_canary(ec, sp) @@ -1805,7 +1806,8 @@ vm_search_method_fastpath(VALUE cd_owner, struct rb_call_data *cd, VALUE klass) #if OPT_INLINE_METHOD_CACHE if (LIKELY(vm_cc_class_check(cc, klass))) { - if (LIKELY(!METHOD_ENTRY_INVALIDATED(vm_cc_cme(cc)))) { + const struct rb_callable_method_entry_struct *cme = vm_cc_cme(cc); + if (LIKELY(cme && !METHOD_ENTRY_INVALIDATED(cme))) { VM_ASSERT(callable_method_entry_p(vm_cc_cme(cc))); RB_DEBUG_COUNTER_INC(mc_inline_hit); VM_ASSERT(vm_cc_cme(cc) == NULL || // not found @@ -1856,6 +1858,7 @@ check_cfunc(const rb_callable_method_entry_t *me, VALUE (*func)()) static inline int vm_method_cfunc_is(const rb_iseq_t *iseq, CALL_DATA cd, VALUE recv, VALUE (*func)()) { + VM_ASSERT(iseq != NULL); const struct rb_callcache *cc = vm_search_method((VALUE)iseq, cd, recv); return check_cfunc(vm_cc_cme(cc), func); } @@ -1892,7 +1895,7 @@ FLONUM_2_P(VALUE a, VALUE b) } static VALUE -opt_equality(const rb_iseq_t *cd_owner, VALUE recv, VALUE obj, CALL_DATA cd) +opt_equality_specialized(VALUE recv, VALUE obj) { if (FIXNUM_2_P(recv, obj) && EQ_UNREDEFINED_P(INTEGER)) { goto compare_by_identity; @@ -1904,7 +1907,7 @@ opt_equality(const rb_iseq_t *cd_owner, VALUE recv, VALUE obj, CALL_DATA cd) goto compare_by_identity; } else if (SPECIAL_CONST_P(recv)) { - goto compare_by_funcall; + // } else if (RBASIC_CLASS(recv) == rb_cFloat && RB_FLOAT_TYPE_P(obj) && EQ_UNREDEFINED_P(FLOAT)) { double a = RFLOAT_VALUE(recv); @@ -1934,11 +1937,7 @@ opt_equality(const rb_iseq_t *cd_owner, VALUE recv, VALUE obj, CALL_DATA cd) return rb_str_eql_internal(obj, recv); } } - - compare_by_funcall: - if (! vm_method_cfunc_is(cd_owner, cd, recv, rb_obj_equal)) { - return Qundef; - } + return Qundef; compare_by_identity: if (recv == obj) { @@ -1949,47 +1948,77 @@ opt_equality(const rb_iseq_t *cd_owner, VALUE recv, VALUE obj, CALL_DATA cd) } } +static VALUE +opt_equality(const rb_iseq_t *cd_owner, VALUE recv, VALUE obj, CALL_DATA cd) +{ + VM_ASSERT(cd_owner != NULL); + + VALUE val = opt_equality_specialized(recv, obj); + if (val != Qundef) return val; + + if (!vm_method_cfunc_is(cd_owner, cd, recv, rb_obj_equal)) { + return Qundef; + } + else { + if (recv == obj) { + return Qtrue; + } + else { + return Qfalse; + } + } +} + #undef EQ_UNREDEFINED_P #ifndef MJIT_HEADER -VALUE -rb_equal_opt(VALUE obj1, VALUE obj2) + +static inline const struct rb_callcache *gccct_method_search(rb_execution_context_t *ec, VALUE recv, ID mid, int argc); // vm_eval.c +NOINLINE(static VALUE opt_equality_by_mid_slowpath(VALUE recv, VALUE obj, ID mid)); + +static VALUE +opt_equality_by_mid_slowpath(VALUE recv, VALUE obj, ID mid) { - STATIC_ASSERT(idEq_is_embeddable, VM_CI_EMBEDDABLE_P(idEq, 0, 1, 0)); + const struct rb_callcache *cc = gccct_method_search(GET_EC(), recv, mid, 1); -#if USE_EMBED_CI - static struct rb_call_data cd = { - .ci = vm_ci_new_id(idEq, 0, 1, 0), - }; -#else - struct rb_call_data cd = { - .ci = &VM_CI_ON_STACK(idEq, 0, 1, 0), - }; -#endif + if (cc && check_cfunc(vm_cc_cme(cc), rb_obj_equal)) { + if (recv == obj) { + return Qtrue; + } + else { + return Qfalse; + } + } + else { + return Qundef; + } +} - cd.cc = &vm_empty_cc; - return opt_equality(NULL, obj1, obj2, &cd); +static VALUE +opt_equality_by_mid(VALUE recv, VALUE obj, ID mid) +{ + VALUE val = opt_equality_specialized(recv, obj); + if (val != Qundef) { + return val; + } + else { + return opt_equality_by_mid_slowpath(recv, obj, mid); + } } VALUE -rb_eql_opt(VALUE obj1, VALUE obj2) +rb_equal_opt(VALUE obj1, VALUE obj2) { - STATIC_ASSERT(idEqlP_is_embeddable, VM_CI_EMBEDDABLE_P(idEqlP, 0, 1, 0)); - -#if USE_EMBED_CI - static struct rb_call_data cd = { - .ci = vm_ci_new_id(idEqlP, 0, 1, 0), - }; -#else - struct rb_call_data cd = { - .ci = &VM_CI_ON_STACK(idEqlP, 0, 1, 0), - }; -#endif + return opt_equality_by_mid(obj1, obj2, idEq); +} - cd.cc = &vm_empty_cc; - return opt_equality(NULL, obj1, obj2, &cd); +VALUE +rb_eql_opt(VALUE obj1, VALUE obj2) +{ + return opt_equality_by_mid(obj1, obj2, idEqlP); } -#endif + +#endif // MJIT_HEADER extern VALUE rb_vm_call0(rb_execution_context_t *ec, VALUE, ID, int, const VALUE*, const rb_callable_method_entry_t *, int kw_splat); @@ -2362,6 +2391,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, { const struct rb_callinfo *ci = calling->ci; const struct rb_callcache *cc = calling->cc; + bool cacheable_ci = vm_ci_markable(ci); if (LIKELY(!(vm_ci_flag(ci) & VM_CALL_KW_SPLAT))) { if (LIKELY(rb_simple_iseq_p(iseq))) { @@ -2375,7 +2405,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, VM_ASSERT(ci == calling->ci); VM_ASSERT(cc == calling->cc); - CC_SET_FASTPATH(cc, vm_call_iseq_setup_func(ci, param_size, local_size), vm_call_iseq_optimizable_p(ci, cc)); + CC_SET_FASTPATH(cc, vm_call_iseq_setup_func(ci, param_size, local_size), cacheable_ci && vm_call_iseq_optimizable_p(ci, cc)); return 0; } else if (rb_iseq_only_optparam_p(iseq)) { @@ -2395,12 +2425,12 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, if (LIKELY(!(vm_ci_flag(ci) & VM_CALL_TAILCALL))) { CC_SET_FASTPATH(cc, vm_call_iseq_setup_normal_opt_start, !IS_ARGS_SPLAT(ci) && !IS_ARGS_KEYWORD(ci) && - METHOD_ENTRY_CACHEABLE(vm_cc_cme(cc))); + cacheable_ci && METHOD_ENTRY_CACHEABLE(vm_cc_cme(cc))); } else { CC_SET_FASTPATH(cc, vm_call_iseq_setup_tailcall_opt_start, !IS_ARGS_SPLAT(ci) && !IS_ARGS_KEYWORD(ci) && - METHOD_ENTRY_CACHEABLE(vm_cc_cme(cc))); + cacheable_ci && METHOD_ENTRY_CACHEABLE(vm_cc_cme(cc))); } /* initialize opt vars for self-references */ @@ -2428,7 +2458,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, args_setup_kw_parameters(ec, iseq, ci_kws, ci_kw_len, ci_keywords, klocals); CC_SET_FASTPATH(cc, vm_call_iseq_setup_kwparm_kwarg, - METHOD_ENTRY_CACHEABLE(vm_cc_cme(cc))); + cacheable_ci && METHOD_ENTRY_CACHEABLE(vm_cc_cme(cc))); return 0; } @@ -2441,7 +2471,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, if (klocals[kw_param->num] == INT2FIX(0)) { /* copy from default_values */ CC_SET_FASTPATH(cc, vm_call_iseq_setup_kwparm_nokwarg, - METHOD_ENTRY_CACHEABLE(vm_cc_cme(cc))); + cacheable_ci && METHOD_ENTRY_CACHEABLE(vm_cc_cme(cc))); } return 0; @@ -4612,19 +4642,26 @@ vm_opt_newarray_min(rb_num_t num, const VALUE *ptr) #define IMEMO_CONST_CACHE_SHAREABLE IMEMO_FL_USER0 -static int -vm_ic_hit_p(const struct iseq_inline_constant_cache_entry *ice, const VALUE *reg_ep) +// For MJIT inlining +static inline bool +vm_inlined_ic_hit_p(VALUE flags, VALUE value, const rb_cref_t *ic_cref, rb_serial_t ic_serial, const VALUE *reg_ep) { - VM_ASSERT(IMEMO_TYPE_P(ice, imemo_constcache)); - if (ice->ic_serial == GET_GLOBAL_CONSTANT_STATE() && - (FL_TEST_RAW((VALUE)ice, IMEMO_CONST_CACHE_SHAREABLE) || rb_ractor_main_p())) { + if (ic_serial == GET_GLOBAL_CONSTANT_STATE() && + ((flags & IMEMO_CONST_CACHE_SHAREABLE) || rb_ractor_main_p())) { - VM_ASSERT(FL_TEST_RAW((VALUE)ice, IMEMO_CONST_CACHE_SHAREABLE) ? rb_ractor_shareable_p(ice->value) : true); + VM_ASSERT((flags & IMEMO_CONST_CACHE_SHAREABLE) ? rb_ractor_shareable_p(value) : true); - return (ice->ic_cref == NULL || // no need to check CREF - ice->ic_cref == vm_get_cref(reg_ep)); + return (ic_cref == NULL || // no need to check CREF + ic_cref == vm_get_cref(reg_ep)); } - return FALSE; + return false; +} + +static bool +vm_ic_hit_p(const struct iseq_inline_constant_cache_entry *ice, const VALUE *reg_ep) +{ + VM_ASSERT(IMEMO_TYPE_P(ice, imemo_constcache)); + return vm_inlined_ic_hit_p(ice->flags, ice->value, ice->ic_cref, ice->ic_serial, reg_ep); } static void @@ -5342,7 +5379,7 @@ vm_trace(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp) #if VM_CHECK_MODE > 0 NORETURN( NOINLINE( COLDFUNC -void vm_canary_is_found_dead(enum ruby_vminsn_type i, VALUE c))); +void rb_vm_canary_is_found_dead(enum ruby_vminsn_type i, VALUE c))); void Init_vm_stack_canary(void) @@ -5357,7 +5394,7 @@ Init_vm_stack_canary(void) #ifndef MJIT_HEADER MJIT_FUNC_EXPORTED void -vm_canary_is_found_dead(enum ruby_vminsn_type i, VALUE c) +rb_vm_canary_is_found_dead(enum ruby_vminsn_type i, VALUE c) { /* Because a method has already been called, why not call * another one. */ diff --git a/vm_insnhelper.h b/vm_insnhelper.h index 56c4b96458..18e7056303 100644 --- a/vm_insnhelper.h +++ b/vm_insnhelper.h @@ -153,7 +153,7 @@ CC_SET_FASTPATH(const struct rb_callcache *cc, vm_call_handler func, bool enable *canary = Qfalse; /* cleanup */ \ } \ else { \ - vm_canary_is_found_dead(insn, *canary); \ + rb_vm_canary_is_found_dead(insn, *canary); \ } \ } #else diff --git a/vm_method.c b/vm_method.c index 51b8b6b07b..8ef69b6401 100644 --- a/vm_method.c +++ b/vm_method.c @@ -8,6 +8,7 @@ static int vm_redefinition_check_flag(VALUE klass); static void rb_vm_check_redefinition_opt_method(const rb_method_entry_t *me, VALUE klass); +static inline rb_method_entry_t *lookup_method_table(VALUE klass, ID id); #define object_id idObject_id #define added idMethod_added @@ -173,9 +174,17 @@ clear_method_cache_by_id_in_class(VALUE klass, ID mid) // invalidate cc by invalidating cc->cme VALUE owner = cme->owner; VM_ASSERT(BUILTIN_TYPE(owner) == T_CLASS); + VALUE klass_housing_cme; + if (cme->def->type == VM_METHOD_TYPE_REFINED && !cme->def->body.refined.orig_me) { + klass_housing_cme = owner; + } + else { + klass_housing_cme = RCLASS_ORIGIN(owner); + } + // replace the cme that will be invalid + VM_ASSERT(lookup_method_table(klass_housing_cme, mid) == (const rb_method_entry_t *)cme); const rb_method_entry_t *new_cme = rb_method_entry_clone((const rb_method_entry_t *)cme); - VALUE origin = RCLASS_ORIGIN(owner); - rb_method_table_insert(origin, RCLASS_M_TBL(origin), mid, new_cme); + rb_method_table_insert(klass_housing_cme, RCLASS_M_TBL(klass_housing_cme), mid, new_cme); } vm_me_invalidate_cache((rb_callable_method_entry_t *)cme); @@ -801,6 +810,7 @@ rb_method_entry_make(VALUE klass, ID mid, VALUE defined_class, rb_method_visibil if (RTEST(ruby_verbose) && type != VM_METHOD_TYPE_UNDEF && (old_def->alias_count == 0) && + (!old_def->no_redef_warning) && !make_refined && old_def->type != VM_METHOD_TYPE_UNDEF && old_def->type != VM_METHOD_TYPE_ZSUPER && @@ -914,7 +924,13 @@ method_entry_set(VALUE klass, ID mid, const rb_method_entry_t *me, rb_method_visibility_t visi, VALUE defined_class) { rb_method_entry_t *newme = rb_method_entry_make(klass, mid, defined_class, visi, - me->def->type, method_definition_addref(me->def), 0, NULL); + me->def->type, me->def, 0, NULL); + if (newme == me) { + me->def->no_redef_warning = TRUE; + } + else { + method_definition_addref(me->def); + } method_added(klass, mid); return newme; } @@ -960,7 +976,7 @@ rb_method_entry_at(VALUE klass, ID id) } static inline rb_method_entry_t* -search_method(VALUE klass, ID id, VALUE *defined_class_ptr) +search_method0(VALUE klass, ID id, VALUE *defined_class_ptr, bool skip_refined) { rb_method_entry_t *me = NULL; @@ -969,7 +985,10 @@ search_method(VALUE klass, ID id, VALUE *defined_class_ptr) for (; klass; klass = RCLASS_SUPER(klass)) { RB_DEBUG_COUNTER_INC(mc_search_super); if ((me = lookup_method_table(klass, id)) != 0) { - break; + if (!skip_refined || me->def->type != VM_METHOD_TYPE_REFINED || + me->def->body.refined.orig_me) { + break; + } } } @@ -981,6 +1000,12 @@ search_method(VALUE klass, ID id, VALUE *defined_class_ptr) return me; } +static inline rb_method_entry_t* +search_method(VALUE klass, ID id, VALUE *defined_class_ptr) +{ + return search_method0(klass, id, defined_class_ptr, false); +} + static rb_method_entry_t * search_method_protect(VALUE klass, ID id, VALUE *defined_class_ptr) { @@ -1186,11 +1211,10 @@ rb_method_entry_with_refinements(VALUE klass, ID id, VALUE *defined_class_ptr) } static const rb_callable_method_entry_t * -callable_method_entry_refeinements(VALUE klass, ID id, VALUE *defined_class_ptr, bool with_refinements) +callable_method_entry_refeinements0(VALUE klass, ID id, VALUE *defined_class_ptr, bool with_refinements, + const rb_callable_method_entry_t *cme) { - const rb_callable_method_entry_t *cme = callable_method_entry(klass, id, defined_class_ptr); - - if (cme == NULL || cme->def->type != VM_METHOD_TYPE_REFINED) { + if (cme == NULL || LIKELY(cme->def->type != VM_METHOD_TYPE_REFINED)) { return cme; } else { @@ -1200,6 +1224,13 @@ callable_method_entry_refeinements(VALUE klass, ID id, VALUE *defined_class_ptr, } } +static const rb_callable_method_entry_t * +callable_method_entry_refeinements(VALUE klass, ID id, VALUE *defined_class_ptr, bool with_refinements) +{ + const rb_callable_method_entry_t *cme = callable_method_entry(klass, id, defined_class_ptr); + return callable_method_entry_refeinements0(klass, id, defined_class_ptr, with_refinements, cme); +} + MJIT_FUNC_EXPORTED const rb_callable_method_entry_t * rb_callable_method_entry_with_refinements(VALUE klass, ID id, VALUE *defined_class_ptr) { @@ -1362,7 +1393,7 @@ rb_export_method(VALUE klass, ID name, rb_method_visibility_t visi) VALUE defined_class; VALUE origin_class = RCLASS_ORIGIN(klass); - me = search_method(origin_class, name, &defined_class); + me = search_method0(origin_class, name, &defined_class, true); if (!me && RB_TYPE_P(klass, T_MODULE)) { me = search_method(rb_cObject, name, &defined_class); @@ -1377,11 +1408,16 @@ rb_export_method(VALUE klass, ID name, rb_method_visibility_t visi) rb_vm_check_redefinition_opt_method(me, klass); if (klass == defined_class || origin_class == defined_class) { - METHOD_ENTRY_VISI_SET(me, visi); - - if (me->def->type == VM_METHOD_TYPE_REFINED && me->def->body.refined.orig_me) { - METHOD_ENTRY_VISI_SET((rb_method_entry_t *)me->def->body.refined.orig_me, visi); - } + if (me->def->type == VM_METHOD_TYPE_REFINED) { + // Refinement method entries should always be public because the refinement + // search is always performed. + if (me->def->body.refined.orig_me) { + METHOD_ENTRY_VISI_SET((rb_method_entry_t *)me->def->body.refined.orig_me, visi); + } + } + else { + METHOD_ENTRY_VISI_SET(me, visi); + } rb_clear_method_cache(klass, name); } else { @@ -1453,7 +1489,7 @@ static void scope_visibility_check(void) { /* Check for public/protected/private/module_function called inside a method */ - rb_control_frame_t *cfp = rb_current_execution_context()->cfp+1; + rb_control_frame_t *cfp = GET_EC()->cfp+1; if (cfp && cfp->iseq && cfp->iseq->body->type == ISEQ_TYPE_METHOD) { rb_warn("calling %s without arguments inside a method may not have the intended effect", rb_id2name(rb_frame_this_func())); @@ -14,7 +14,7 @@ vm_locked(rb_vm_t *vm) #if RUBY_DEBUG > 0 void -ASSERT_vm_locking(void) +RUBY_ASSERT_vm_locking(void) { if (rb_multi_ractor_p()) { rb_vm_t *vm = GET_VM(); @@ -23,7 +23,7 @@ ASSERT_vm_locking(void) } void -ASSERT_vm_unlocking(void) +RUBY_ASSERT_vm_unlocking(void) { if (rb_multi_ractor_p()) { rb_vm_t *vm = GET_VM(); @@ -126,8 +126,10 @@ rb_vm_lock_leave_cr(struct rb_ractor_struct *cr, unsigned int *levp, const char #define RB_VM_LOCK_LEAVE_NO_BARRIER() RB_VM_LOCK_LEAVE_LEV(&_lev); } #if RUBY_DEBUG > 0 -void ASSERT_vm_locking(void); -void ASSERT_vm_unlocking(void); +void RUBY_ASSERT_vm_locking(void); +void RUBY_ASSERT_vm_unlocking(void); +#define ASSERT_vm_locking() RUBY_ASSERT_vm_locking() +#define ASSERT_vm_unlocking() RUBY_ASSERT_vm_unlocking() #else #define ASSERT_vm_locking() #define ASSERT_vm_unlocking() diff --git a/vm_trace.c b/vm_trace.c index 06879812b3..6cbb5ed843 100644 --- a/vm_trace.c +++ b/vm_trace.c @@ -730,7 +730,7 @@ tp_memsize(const void *ptr) static const rb_data_type_t tp_data_type = { "tracepoint", - {tp_mark, RUBY_TYPED_NEVER_FREE, tp_memsize,}, + {tp_mark, RUBY_TYPED_DEFAULT_FREE, tp_memsize,}, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY }; @@ -1592,6 +1592,14 @@ postponed_job_register(rb_execution_context_t *ec, rb_vm_t *vm, return PJRR_SUCCESS; } +static rb_execution_context_t * +get_valid_ec(rb_vm_t *vm) +{ + rb_execution_context_t *ec = rb_current_execution_context(false); + if (ec == NULL) ec = rb_vm_main_ractor_ec(vm); + return ec; +} + /* * return 0 if job buffer is full * Async-signal-safe @@ -1599,8 +1607,8 @@ postponed_job_register(rb_execution_context_t *ec, rb_vm_t *vm, int rb_postponed_job_register(unsigned int flags, rb_postponed_job_func_t func, void *data) { - rb_execution_context_t *ec = GET_EC(); - rb_vm_t *vm = rb_ec_vm_ptr(ec); + rb_vm_t *vm = GET_VM(); + rb_execution_context_t *ec = get_valid_ec(vm); begin: switch (postponed_job_register(ec, vm, flags, func, data, MAX_POSTPONED_JOB, vm->postponed_job_index)) { @@ -1618,8 +1626,8 @@ rb_postponed_job_register(unsigned int flags, rb_postponed_job_func_t func, void int rb_postponed_job_register_one(unsigned int flags, rb_postponed_job_func_t func, void *data) { - rb_execution_context_t *ec = GET_EC(); - rb_vm_t *vm = rb_ec_vm_ptr(ec); + rb_vm_t *vm = GET_VM(); + rb_execution_context_t *ec = get_valid_ec(vm); rb_postponed_job_t *pjob; rb_atomic_t i, index; diff --git a/win32/win32.c b/win32/win32.c index cfc571f145..5fd7595414 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -696,9 +696,14 @@ rtc_error_handler(int e, const char *src, int line, const char *exe, const char #endif static CRITICAL_SECTION select_mutex; + +static CRITICAL_SECTION socklist_mutex; static st_table *socklist = NULL; + +static CRITICAL_SECTION conlist_mutex; static st_table *conlist = NULL; #define conlist_disabled ((st_table *)-1) + static char *uenvarea; /* License: Ruby's */ @@ -723,11 +728,13 @@ free_conlist(st_data_t key, st_data_t val, st_data_t arg) static void constat_delete(HANDLE h) { + EnterCriticalSection(&conlist_mutex); if (conlist && conlist != conlist_disabled) { st_data_t key = (st_data_t)h, val; st_delete(conlist, &key, &val); xfree((struct constat *)val); } + LeaveCriticalSection(&conlist_mutex); } /* License: Ruby's */ @@ -736,6 +743,8 @@ exit_handler(void) { WSACleanup(); DeleteCriticalSection(&select_mutex); + DeleteCriticalSection(&socklist_mutex); + DeleteCriticalSection(&conlist_mutex); if (uenvarea) { free(uenvarea); uenvarea = NULL; @@ -746,15 +755,20 @@ exit_handler(void) static void vm_exit_handler(ruby_vm_t *vm) { + EnterCriticalSection(&socklist_mutex); if (socklist) { st_free_table(socklist); socklist = NULL; } + LeaveCriticalSection(&socklist_mutex); + + EnterCriticalSection(&conlist_mutex); if (conlist && conlist != conlist_disabled) { st_foreach(conlist, free_conlist, 0); st_free_table(conlist); conlist = NULL; } + LeaveCriticalSection(&conlist_mutex); } /* License: Ruby's */ @@ -787,6 +801,8 @@ StartSockets(void) rb_fatal("could not find version 2 of winsock dll"); InitializeCriticalSection(&select_mutex); + InitializeCriticalSection(&socklist_mutex); + InitializeCriticalSection(&conlist_mutex); atexit(exit_handler); } @@ -799,11 +815,17 @@ StartSockets(void) static inline int socklist_insert(SOCKET sock, int flag) { + int ret; + + EnterCriticalSection(&socklist_mutex); if (!socklist) { socklist = st_init_numtable(); install_vm_exit_handler(); } - return st_insert(socklist, (st_data_t)sock, (st_data_t)flag); + ret = st_insert(socklist, (st_data_t)sock, (st_data_t)flag); + LeaveCriticalSection(&socklist_mutex); + + return ret; } /* License: Ruby's */ @@ -813,11 +835,15 @@ socklist_lookup(SOCKET sock, int *flagp) st_data_t data; int ret; - if (!socklist) - return 0; - ret = st_lookup(socklist, (st_data_t)sock, (st_data_t *)&data); - if (ret && flagp) - *flagp = (int)data; + EnterCriticalSection(&socklist_mutex); + if (socklist) { + ret = st_lookup(socklist, (st_data_t)sock, (st_data_t *)&data); + if (ret && flagp) + *flagp = (int)data; + } else { + ret = 0; + } + LeaveCriticalSection(&socklist_mutex); return ret; } @@ -830,17 +856,21 @@ socklist_delete(SOCKET *sockp, int *flagp) st_data_t data; int ret; - if (!socklist) - return 0; - key = (st_data_t)*sockp; - if (flagp) - data = (st_data_t)*flagp; - ret = st_delete(socklist, &key, &data); - if (ret) { - *sockp = (SOCKET)key; + EnterCriticalSection(&socklist_mutex); + if (socklist) { + key = (st_data_t)*sockp; if (flagp) - *flagp = (int)data; + data = (st_data_t)*flagp; + ret = st_delete(socklist, &key, &data); + if (ret) { + *sockp = (SOCKET)key; + if (flagp) + *flagp = (int)data; + } + } else { + ret = 0; } + LeaveCriticalSection(&socklist_mutex); return ret; } @@ -2693,7 +2723,7 @@ init_stdhandle(void) DWORD m; if (GetConsoleMode(h, &m)) { #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING -#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x200 +#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x4 #endif SetConsoleMode(h, m | ENABLE_VIRTUAL_TERMINAL_PROCESSING); } @@ -5539,10 +5569,8 @@ filetime_to_nsec(const FILETIME *ft) /* License: Ruby's */ static unsigned -fileattr_to_unixmode(DWORD attr, const WCHAR *path) +fileattr_to_unixmode(DWORD attr, const WCHAR *path, unsigned mode) { - unsigned mode = 0; - if (attr & FILE_ATTRIBUTE_READONLY) { mode |= S_IREAD; } @@ -5550,7 +5578,10 @@ fileattr_to_unixmode(DWORD attr, const WCHAR *path) mode |= S_IREAD | S_IWRITE | S_IWUSR; } - if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { + if (mode & S_IFMT) { + /* format is already set */ + } + else if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { if (rb_w32_reparse_symlink_p(path)) mode |= S_IFLNK | S_IEXEC; else @@ -5645,7 +5676,7 @@ stat_by_find(const WCHAR *path, struct stati128 *st) return -1; } FindClose(h); - st->st_mode = fileattr_to_unixmode(wfd.dwFileAttributes, path); + st->st_mode = fileattr_to_unixmode(wfd.dwFileAttributes, path, 0); st->st_atime = filetime_to_unixtime(&wfd.ftLastAccessTime); st->st_atimensec = filetime_to_nsec(&wfd.ftLastAccessTime); st->st_mtime = filetime_to_unixtime(&wfd.ftLastWriteTime); @@ -5680,6 +5711,15 @@ winnt_stat(const WCHAR *path, struct stati128 *st, BOOL lstat) if (f != INVALID_HANDLE_VALUE) { DWORD attr = stati128_handle(f, st); const DWORD len = get_final_path(f, finalname, numberof(finalname), 0); + unsigned mode = 0; + switch (GetFileType(f)) { + case FILE_TYPE_CHAR: + mode = S_IFCHR; + break; + case FILE_TYPE_PIPE: + mode = S_IFIFO; + break; + } CloseHandle(f); if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { /* TODO: size in which encoding? */ @@ -5691,7 +5731,7 @@ winnt_stat(const WCHAR *path, struct stati128 *st, BOOL lstat) if (attr & FILE_ATTRIBUTE_DIRECTORY) { if (check_valid_dir(path)) return -1; } - st->st_mode = fileattr_to_unixmode(attr, path); + st->st_mode = fileattr_to_unixmode(attr, path, mode); if (len) { finalname[min(len, numberof(finalname)-1)] = L'\0'; path = finalname; @@ -6555,32 +6595,36 @@ constat_handle(HANDLE h) { st_data_t data; struct constat *p; + + EnterCriticalSection(&conlist_mutex); if (!conlist) { if (console_emulator_p()) { conlist = conlist_disabled; - return NULL; - } - conlist = st_init_numtable(); - install_vm_exit_handler(); - } - else if (conlist == conlist_disabled) { - return NULL; - } - if (st_lookup(conlist, (st_data_t)h, &data)) { - p = (struct constat *)data; - } - else { - CONSOLE_SCREEN_BUFFER_INFO csbi; - p = ALLOC(struct constat); - p->vt100.state = constat_init; - p->vt100.attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED; - p->vt100.reverse = 0; - p->vt100.saved.X = p->vt100.saved.Y = 0; - if (GetConsoleScreenBufferInfo(h, &csbi)) { - p->vt100.attr = csbi.wAttributes; + } else { + conlist = st_init_numtable(); + install_vm_exit_handler(); + } + } + if (conlist != conlist_disabled) { + if (st_lookup(conlist, (st_data_t)h, &data)) { + p = (struct constat *)data; + } else { + CONSOLE_SCREEN_BUFFER_INFO csbi; + p = ALLOC(struct constat); + p->vt100.state = constat_init; + p->vt100.attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED; + p->vt100.reverse = 0; + p->vt100.saved.X = p->vt100.saved.Y = 0; + if (GetConsoleScreenBufferInfo(h, &csbi)) { + p->vt100.attr = csbi.wAttributes; + } + st_insert(conlist, (st_data_t)h, (st_data_t)p); } - st_insert(conlist, (st_data_t)h, (st_data_t)p); + } else { + p = NULL; } + LeaveCriticalSection(&conlist_mutex); + return p; } @@ -6590,10 +6634,16 @@ constat_reset(HANDLE h) { st_data_t data; struct constat *p; - if (!conlist || conlist == conlist_disabled) return; - if (!st_lookup(conlist, (st_data_t)h, &data)) return; - p = (struct constat *)data; - p->vt100.state = constat_init; + + EnterCriticalSection(&conlist_mutex); + if ( + conlist && conlist != conlist_disabled && + st_lookup(conlist, (st_data_t)h, &data) + ) { + p = (struct constat *)data; + p->vt100.state = constat_init; + } + LeaveCriticalSection(&conlist_mutex); } #define FOREGROUND_MASK (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY) @@ -7285,7 +7335,7 @@ rb_w32_write_console(uintptr_t strarg, int fd) break; } reslen = 0; - if (dwMode & 4) { /* ENABLE_VIRTUAL_TERMINAL_PROCESSING */ + if (dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) { if (!WriteConsoleW(handle, ptr, len, &reslen, NULL)) reslen = (DWORD)-1L; } |