version: 2.1

parameters:
  run-backend-tests:
    type: boolean
    default: false

  run-migrations-tests:
    type: boolean
    default: false

  run-passenger-and-ruby-default-gem-tests:
    type: boolean
    default: false

  run-javascripts-linter:
    type: boolean
    default: false

  run-apidocs-linter:
    type: boolean
    default: false

  run-dist-linter:
    type: boolean
    default: false

  run-haml-linter:
    type: boolean
    default: false

images:
  - &common_frontend_config
    user: frontend
    environment:
      NOKOGIRI_USE_SYSTEM_LIBRARIES: 1

  - &mariadb
    image: registry.opensuse.org/obs/server/unstable/containers/containers/openbuildservice/mariadb:latest
    command: |
      /bin/bash -c 'echo -e "[mysqld]\ndatadir = /dev/shm" > /etc/my.cnf.d/obs.cnf && cp -a /var/lib/mysql/* /dev/shm && /usr/lib/mysql/mysql-systemd-helper start'
    name: db

  - &backend registry.opensuse.org/obs/server/unstable/containers/containers/openbuildservice/backend:latest

  - &frontend_minitest
    image: registry.opensuse.org/obs/server/unstable/containers/containers/openbuildservice/frontend-minitest:latest
    <<: *common_frontend_config
    environment:
      CI: 1

  - &frontend_base
    image: registry.opensuse.org/obs/server/unstable/containers/containers/openbuildservice/frontend-base:latest
    <<: *common_frontend_config

  - &frontend_features
    image: registry.opensuse.org/obs/server/unstable/containers/containers/openbuildservice/frontend-features:latest

aliases:
  - &restore_bundler_cache
    restore_cache:
      name: Restoring Bundle Cache
      key: bundle-{{ checksum "src/api/Gemfile.lock" }}

  - &install_dependencies
    name: install dependencies
    command: |
      cd src/api
      # TODO: Keep build options in sync with obs-bundled-gems.spec
      bundle config build.ffi --enable-system-libffi
      bundle config build.nokogiri --use-system-libraries
      bundle config build.sassc --disable-march-tune-native
      bundle config build.nio4r --with-cflags='-Wno-return-type'
      bundle config set --local path 'vendor/bundle'
      bundle install --jobs=4 --retry=3 --local

  - &save_bundler_cache
    save_cache:
      name: Saving Bundle Cache
      key: bundle-{{ checksum "src/api/Gemfile.lock" }}
      paths:
        - "src/api/vendor/bundle"

  - &init_git_submodule
    name: Init submodule
    command: git submodule update --init --recursive --remote

  - &create_test_db
    name: Create database
    working_directory: src/api
    command: bundle exec rake db:setup RAILS_ENV=test

  - &bootstrap_old_test_suite
    name: Setup application
    working_directory: src/api
    command: bundle exec rake dev:bootstrap[old_test_suite] RAILS_ENV=test FORCE_EXAMPLE_FILES=1

  - &compress_log_artifacts
    name: Compress Log Artifacts
    working_directory: src/api/log
    command: for i in *.log; do xz -T 0 -v $i; done
    when: always

  - &compress_capybara_artifacts
    name: Compress Capybara Artifacts
    command: |
      # The directory might not exist if nothing failed...
      mkdir -p src/api/tmp/capybara
      cd src/api/tmp/capybara
      export NUMBER_OF_FAILED_TESTS=`ls -1|wc -l`
      # Only compress if there are more than 20 tests failed...
      if test $NUMBER_OF_FAILED_TESTS -gt 40; then mkdir capybara; mv  -v *.png *.html capybara; tar cvJf capybara.tar.xz capybara; rm -rf capybara; fi
    when: always

  - &install_circle_cli
    name: Install CircleCI command line
    command: |
      curl -o circleci https://circle-downloads.s3.amazonaws.com/releases/build_agent_wrapper/circleci
      chmod +x circleci

  - &restore_rubocop_cache
    restore_cache:
      name: Restoring Rubocop Cache
      key: rubocop-{{ .Branch }}-{{ checksum "./src/api/Gemfile.lock" }}

  - &save_rubocop_cache
    save_cache:
      name: Saving Rubocop Cache
      key: rubocop-{{ .Branch }}-{{ checksum "./src/api/Gemfile.lock" }}
      paths:
        - src/api/tmp/rubocop_cache_root_dir
        - src/api/tmp/rubocop_cache_rails_dir

jobs:
  pipeline_settings:
    docker:
      - image: opensuse/leap:16.0
    steps:
      - run: echo "run-apidocs-linter is << pipeline.parameters.run-apidocs-linter >>"
      - run: echo "run-backend-tests is << pipeline.parameters.run-backend-tests >>"
      - run: echo "run-javascripts-linter is << pipeline.parameters.run-javascripts-linter >>"
      - run: echo "run-migrations-tests is << pipeline.parameters.run-migrations-tests >>"
      - run: echo "run-dist-linter is << pipeline.parameters.run-dist-linter >>"
      - run: echo "run-haml-linter is << pipeline.parameters.run-haml-linter >>"

  checkout_code:
    docker:
      - <<: *frontend_base
    steps:
      - checkout
      - *restore_bundler_cache
      - *restore_rubocop_cache
      - run: *install_dependencies
      - *save_bundler_cache
      - run: *install_circle_cli
      - run:
          name: Setup application
          working_directory: src/api
          command: bundle exec rake dev:prepare assets:precompile RAILS_ENV=test FORCE_EXAMPLE_FILES=1
      - persist_to_workspace:
          root: .
          paths:
            - .

  linters:
    docker:
      - <<: *frontend_base
      - <<: *mariadb
    steps:
      - attach_workspace:
         at: .
      - when:
          condition: << pipeline.parameters.run-javascripts-linter >>
          steps:
            - run:
                name: Install jshint
                command: sudo npm install -g jshint
            - run:
                name: Run jshint
                working_directory: src/api
                command: jshint app/assets/javascripts
      - when:
          condition: << pipeline.parameters.run-dist-linter >>
          steps:
            - run:
                name: Run dist linters
                command: make -C dist scripts_linters
      - when:
          condition: << pipeline.parameters.run-haml-linter >>
          steps:
            - run:
                name: Run HAML linter
                working_directory: src/api
                command: bundle exec rake dev:lint:haml
      - when:
          condition: << pipeline.parameters.run-apidocs-linter >>
          steps:
            - run:
                name: Run Documentation linter
                working_directory: src/api
                command: bundle exec rake dev:lint:apidocs
      - run:
          name: Run database consistency linter
          working_directory: src/api
          command: |
            bundle exec rake db:setup
            bundle exec database_consistency -c .database_consistency.todo.yml
      - run:
          name: Run rubocop
          working_directory: src/api
          command: bundle exec rake dev:lint:rubocop:all
      - *save_rubocop_cache

  rspec:
    parallelism: 3
    docker:
      - <<: *frontend_base
      - <<: *mariadb
    steps:
      - attach_workspace:
         at: .
      - run: *create_test_db
      - run: mkdir /home/frontend/rspec
      - run:
          name: Run rspec
          working_directory: src/api
          command: |
            pickfile=log/pick.$CIRCLE_NODE_INDEX.list
            # single out beta and db specs
            circleci tests glob 'spec/**/*_spec.rb' | grep -v 'spec/\(features\|db\)' > spec.list
            circleci tests split --total $CIRCLE_NODE_TOTAL --split-by=timings < spec.list > $pickfile
            bundle exec rspec --format documentation --format RspecJunitFormatter -o /home/frontend/rspec/rspec.xml $(cat $pickfile)
            # run DB tests at the end of node 0
            # see https://github.com/openSUSE/open-build-service/issues/4959
            if test "$CIRCLE_NODE_INDEX" = 0; then
              echo "Running data migration tests"
              # simulating parallel tests - otherwise one rspec call overwrites the previous
              export TEST_ENV_NUMBER=2
              export PARALLEL_TEST_GROUPS="RSpec"
              bundle exec rspec --format documentation --format RspecJunitFormatter \
                -o /home/frontend/rspec/rspec.db.xml $(circleci tests glob 'spec/db/**/*_spec.rb')
            fi
            mkdir coverage_results
            cp -R coverage/.resultset.json coverage_results/resultset-rspec-${CIRCLE_NODE_INDEX}.json
      - persist_to_workspace:
          root: .
          paths:
            - src/api/coverage_results
      - store_test_results:
          path: /home/frontend/rspec
      - run: *compress_log_artifacts
      - store_artifacts:
          path: ./src/api/log
          destination: log

  minitest:
    parallelism: 2
    docker:
      - <<: *frontend_minitest
      - <<: *mariadb
    steps:
      - attach_workspace:
         at: .
      - run: *init_git_submodule
      - run: *bootstrap_old_test_suite
      - run: mkdir /home/frontend/minitest
      - run:
          name: Run minitest
          environment:
            TESTOPTS: "--ci-dir=/home/frontend/minitest"
          working_directory: src/api
          command: |
            case $CIRCLE_NODE_INDEX in
              0)
                bundle exec rake test:api:group1
                ;;
              1)
                bundle exec rake test:api:group2
                ;;
            esac
            mkdir coverage_results
            cp -R coverage/.resultset.json coverage_results/resultset-minitest-${CIRCLE_NODE_INDEX}.json
      - persist_to_workspace:
          root: .
          paths:
            - src/api/coverage_results
      - store_test_results:
          path: /home/frontend/minitest
      - run: *compress_log_artifacts
      - store_artifacts:
          path: ./src/api/log/
          destination: log

  migrations_tests:
    docker:
      - <<: *frontend_base
      - <<: *mariadb
    steps:
      - attach_workspace:
         at: .
      - run: *init_git_submodule
      - run:
          name: Run migrations
          working_directory: src/api
          command: |
            bundle exec rake db:drop
            bundle exec rake db:create
            bundle exec rake db:migrate
            bundle exec rake db:seed
            bundle exec rake data:migrate

  passenger_and_ruby_default_gem_tests:
    docker:
      - <<: *frontend_base
    steps:
      - attach_workspace:
         at: .
      - run:
          name: Test rake/rack versions
          command: |
            make -C src/api test_rake
            make -C src/api test_rack
            make -C src/api test_base64_gem

  spider:
    parallelism: 2
    docker:
      - <<: *frontend_minitest
      - <<: *mariadb
    steps:
      - attach_workspace:
         at: .
      - run: *init_git_submodule
      - run: *bootstrap_old_test_suite
      - run:
          name: Run spider
          working_directory: src/api
          command: |
            case $CIRCLE_NODE_INDEX in
              0)
                bundle exec rails test test/functional/webui/spider_test.rb -n test_spider_anonymously
                echo "Test loading the application in production"
                bundle exec rails secret > config/secret.key
                DISABLE_DATABASE_ENVIRONMENT_CHECK=1 RAILS_ENV=production bundle exec rake db:setup zeitwerk:check
                rm config/secret.key
                ;;
              1)
                bundle exec rails test test/functional/webui/spider_test.rb -n test_spider_as_admin
                ;;
            esac
      - run: *compress_log_artifacts
      - store_artifacts:
          path: ./src/api/log/
          destination: log

  backend_test:
    docker:
      - image: *backend
    working_directory: /home/frontend/project
    steps:
      - checkout
      - run: *init_git_submodule
      - run:
          name: backend
          command: make -C src/backend test

  coverage:
    docker:
      - <<: *frontend_base
    steps:
      - attach_workspace:
         at: .
      - run:
          name: Merge coverage
          working_directory: src/api
          command: bundle exec rake ci:simplecov_ci_merge
      - run:
          name: Upload coverage to codecov
          working_directory: src/api
          command: |
            curl -LOs https://uploader.codecov.io/latest/linux/codecov
            chmod +x codecov
            ./codecov -v -f coverage/coverage.xml
      - run:
          name: Upload coverage to qlty.sh
          working_directory: src/api
          environment:
            QLTY_COVERAGE_TOKEN: qltcp_wbImki2Nq6aYQokf
          command: |
            curl -L https://qlty-releases.s3.amazonaws.com/qlty/latest/qlty-x86_64-unknown-linux-gnu.tar.xz > qlty.tar.xz
            tar -xf qlty.tar.xz qlty-x86_64-unknown-linux-gnu/qlty --strip-components 1
            ./qlty coverage transform --strip-prefix src/api/ --report-format cobertura coverage/coverage.xml
            ./qlty coverage publish coverage.jsonl
      - run:
          name: Compress coverage results
          working_directory: src/api
          command: |
            tar cvJf coverage_results.tar.xz coverage_results/*
      - store_artifacts:
          path: src/api/coverage_results.tar.xz

  feature:
    parallelism: 2
    docker:
      - <<: *frontend_features
      - <<: *mariadb
    steps:
      - attach_workspace:
         at: .
      - run: *create_test_db
      - run: mkdir /home/frontend/feature
      - run:
          name: Run rspec feature tests
          working_directory: src/api
          command: |
            if test "$CIRCLE_NODE_INDEX" = 0; then
              message="Running feature tests for desktop"
            else
              message="Running feature tests for mobile"
              export CAPYBARA_DRIVER='mobile'
            fi
            echo $message
            bundle exec rspec --format documentation --format RspecJunitFormatter -o /home/frontend/feature/feature.xml spec/features/
            mkdir coverage_results
            cp -R coverage/.resultset.json coverage_results/resultset-feature-${CIRCLE_NODE_INDEX}.json
      - persist_to_workspace:
          root: .
          paths:
            - src/api/coverage_results
      - store_test_results:
          path: /home/frontend/feature
      - run: *compress_log_artifacts
      - store_artifacts:
          path: ./src/api/log
          destination: log
      - run: *compress_capybara_artifacts
      - store_artifacts:
          path: ./src/api/tmp/capybara
          destination: capybara

  next_rails-linters:
    docker:
      - <<: *frontend_base
      - <<: *mariadb
    environment:
      BUNDLE_GEMFILE: Gemfile.next
      BUNDLE_CACHE_PATH: vendor/cache.next
    steps:
      - attach_workspace:
         at: .
      - run: *install_dependencies
      - run: *init_git_submodule
      - when:
          condition: << pipeline.parameters.run-javascripts-linter >>
          steps:
            - run:
                name: Install jshint
                command: sudo npm install -g jshint
            - run:
                name: Run jshint
                working_directory: src/api
                command: jshint app/assets/javascripts
      - when:
          condition: << pipeline.parameters.run-dist-linter >>
          steps:
            - run:
                name: Run dist linters
                command: make -C dist scripts_linters
      - when:
          condition: << pipeline.parameters.run-haml-linter >>
          steps:
            - run:
                name: Run HAML linter
                working_directory: src/api
                command: bundle exec rake dev:lint:haml
      - when:
          condition: << pipeline.parameters.run-apidocs-linter >>
          steps:
            - run:
                name: Run Documentation linter
                working_directory: src/api
                command: bundle exec rake dev:lint:apidocs
      - run:
          name: Run database consistency linter
          working_directory: src/api
          command: |
            bundle exec rake db:setup
            bundle exec database_consistency -c .database_consistency.todo.yml
      - run:
          name: Run rubocop
          working_directory: src/api
          command: bundle exec rake dev:lint:rubocop:all

  next_rails-rspec:
    docker:
      - <<: *frontend_features
      - <<: *mariadb
    environment:
      BUNDLE_GEMFILE: Gemfile.next
      BUNDLE_CACHE_PATH: vendor/cache.next
    steps:
      - attach_workspace:
         at: .
      - run: *install_dependencies
      - run: *init_git_submodule
      - run: *create_test_db
      - run:
          name: Run specs
          working_directory: src/api
          command: bundle exec rspec --format documentation --format RspecJunitFormatter -o /home/frontend/rspec/rspec.db.xml
      - store_test_results:
          path: /home/frontend/rspec
      - run: *compress_log_artifacts
      - store_artifacts:
          path: ./src/api/log
          destination: log
      - run: *compress_capybara_artifacts
      - store_artifacts:
          path: ./src/api/tmp/capybara
          destination: capybara

  next_rails-minitest:
    docker:
      - <<: *frontend_minitest
      - <<: *mariadb
    environment:
      BUNDLE_GEMFILE: Gemfile.next
      BUNDLE_CACHE_PATH: vendor/cache.next
    steps:
      - attach_workspace:
         at: .
      - run: *install_dependencies
      - run: *init_git_submodule
      - run: *bootstrap_old_test_suite
      - run:
          name: Run minitest test suite
          environment:
            TESTOPTS: "--ci-dir=/home/frontend/minitest"
          working_directory: src/api
          command: bundle exec rake test:api:group1 test:api:group2
      - store_test_results:
          path: /home/frontend/minitest
      - run: *compress_log_artifacts
      - store_artifacts:
          path: ./src/api/log/
          destination: log

workflows:
  version: 2

  migration_tests:
    when: << pipeline.parameters.run-migrations-tests >>
    jobs:
      - checkout_code
      - migrations_tests:
          requires:
            - checkout_code

  passenger_and_ruby_default_gem_tests:
    when: << pipeline.parameters.run-passenger-and-ruby-default-gem-tests >>
    jobs:
      - checkout_code
      - passenger_and_ruby_default_gem_tests:
          requires:
            - checkout_code

  backend_tests:
    when: << pipeline.parameters.run-backend-tests >>
    jobs:
      - backend_test

  next_rails:
    when:
      matches:
        pattern: "^next_rails-.*$"
        value: << pipeline.git.branch >>
    jobs:
      - checkout_code
      - next_rails-linters:
          requires:
            - checkout_code
      # next_rails jobs are split between RSpec and Minitest because they need a different setup
      - next_rails-rspec:
          requires:
            - next_rails-linters
      - next_rails-minitest:
          requires:
            - next_rails-linters

  test_all:
    jobs:
      - pipeline_settings
      - checkout_code
      - linters:
          requires:
            - checkout_code
      - rspec:
          requires:
            - linters
      - minitest:
          requires:
            - linters
      - spider:
          requires:
            - rspec
            - minitest
            - feature
      - coverage:
          requires:
            - rspec
            - minitest
            - feature
      - feature:
          requires:
            - linters
