diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c9c5c495cf..422671bb931 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,8 @@ jobs: - { path: python, name: python, ci_node_total: 2, ci_node_index: 1, ecosystem: pip } - { path: python, name: python_slow, ci_node_total: 2, ci_node_index: 0, ecosystem: pip } - { path: python, name: python_slow, ci_node_total: 2, ci_node_index: 1, ecosystem: pip } + - { path: swift, name: swift, ci_node_total: 2, ci_node_index: 0, ecosystem: swiftpm } + - { path: swift, name: swift, ci_node_total: 2, ci_node_index: 1, ecosystem: swiftpm } - { path: terraform, name: terraform, ecosystem: terraform } steps: @@ -160,6 +162,13 @@ jobs: - 'omnibus/**' - 'python/**' - '.github/workflows/ci.yml' + swift: + - Dockerfile.updater-core + - 'common/**' + - 'updater/Gemfil*' + - 'omnibus/**' + - 'swift/**' + - '.github/workflows/ci.yml' terraform: - Dockerfile.updater-core - 'common/**' diff --git a/Dockerfile.development b/Dockerfile.development index 6ddc77397db..cb71827a7f1 100644 --- a/Dockerfile.development +++ b/Dockerfile.development @@ -46,6 +46,7 @@ COPY --chown=dependabot:dependabot npm_and_yarn/Gemfile npm_and_yarn/dependabot- COPY --chown=dependabot:dependabot nuget/Gemfile nuget/dependabot-nuget.gemspec ${CODE_DIR}/nuget/ COPY --chown=dependabot:dependabot python/Gemfile python/dependabot-python.gemspec ${CODE_DIR}/python/ COPY --chown=dependabot:dependabot pub/Gemfile pub/dependabot-pub.gemspec ${CODE_DIR}/pub/ +COPY --chown=dependabot:dependabot swift/Gemfile swift/dependabot-swift.gemspec ${CODE_DIR}/swift/ COPY --chown=dependabot:dependabot terraform/Gemfile terraform/dependabot-terraform.gemspec ${CODE_DIR}/terraform/ # Prepare the updater project diff --git a/Dockerfile.updater-core b/Dockerfile.updater-core index 007bd49b37e..ccb8261f75d 100644 --- a/Dockerfile.updater-core +++ b/Dockerfile.updater-core @@ -76,10 +76,11 @@ COPY --chown=dependabot:dependabot npm_and_yarn/Gemfile npm_and_yarn/dependabot- COPY --chown=dependabot:dependabot nuget/Gemfile nuget/dependabot-nuget.gemspec nuget/ COPY --chown=dependabot:dependabot pub/Gemfile pub/dependabot-pub.gemspec pub/ COPY --chown=dependabot:dependabot python/Gemfile python/dependabot-python.gemspec python/ +COPY --chown=dependabot:dependabot swift/Gemfile swift/dependabot-swift.gemspec swift/ COPY --chown=dependabot:dependabot terraform/Gemfile terraform/dependabot-terraform.gemspec terraform/ # prevent having all the source in every ecosystem image -RUN for ecosystem in git_submodules terraform github_actions hex elm docker nuget maven gradle cargo composer go_modules python pub npm_and_yarn bundler; do \ +RUN for ecosystem in git_submodules terraform github_actions hex elm docker nuget maven gradle cargo composer go_modules python pub npm_and_yarn bundler swift; do \ mkdir -p $ecosystem/lib/dependabot; \ touch $ecosystem/lib/dependabot/$ecosystem.rb; \ done diff --git a/Rakefile b/Rakefile index 0a16d248c5e..5d668dbdeb3 100644 --- a/Rakefile +++ b/Rakefile @@ -31,6 +31,7 @@ GEMSPECS = %w( python/dependabot-python.gemspec pub/dependabot-pub.gemspec omnibus/dependabot-omnibus.gemspec + swift/dependabot-swift.gemspec ).freeze def run_command(command) diff --git a/bin/dry-run.rb b/bin/dry-run.rb index 7b9266083cc..cc566568903 100755 --- a/bin/dry-run.rb +++ b/bin/dry-run.rb @@ -33,6 +33,7 @@ # - docker # - terraform # - pub +# - Swift Package Manager # rubocop:disable Style/GlobalVars @@ -62,6 +63,7 @@ $LOAD_PATH << "./nuget/lib" $LOAD_PATH << "./python/lib" $LOAD_PATH << "./pub/lib" +$LOAD_PATH << "./swift/lib" $LOAD_PATH << "./terraform/lib" require "bundler" @@ -100,6 +102,7 @@ require "dependabot/nuget" require "dependabot/python" require "dependabot/pub" +require "dependabot/swift" require "dependabot/terraform" # GitHub credentials with write permission to the repo you want to update diff --git a/omnibus/dependabot-omnibus.gemspec b/omnibus/dependabot-omnibus.gemspec index 79760d579b5..3fdda0368e7 100644 --- a/omnibus/dependabot-omnibus.gemspec +++ b/omnibus/dependabot-omnibus.gemspec @@ -42,6 +42,7 @@ Gem::Specification.new do |spec| spec.add_dependency "dependabot-nuget", Dependabot::VERSION spec.add_dependency "dependabot-pub", Dependabot::VERSION spec.add_dependency "dependabot-python", Dependabot::VERSION + spec.add_dependency "dependabot-swift", Dependabot::VERSION spec.add_dependency "dependabot-terraform", Dependabot::VERSION common_gemspec.development_dependencies.each do |dep| diff --git a/omnibus/lib/dependabot/omnibus.rb b/omnibus/lib/dependabot/omnibus.rb index 215a08bc449..58b29d37c92 100644 --- a/omnibus/lib/dependabot/omnibus.rb +++ b/omnibus/lib/dependabot/omnibus.rb @@ -16,3 +16,4 @@ require "dependabot/npm_and_yarn" require "dependabot/bundler" require "dependabot/pub" +require "dependabot/swift" diff --git a/swift/.gitignore b/swift/.gitignore new file mode 100644 index 00000000000..9829a3f755c --- /dev/null +++ b/swift/.gitignore @@ -0,0 +1,6 @@ +/.bundle/ +/.env +/tmp +/dependabot-*.gem +Gemfile.lock +helpers/install-dir diff --git a/swift/.rubocop.yml b/swift/.rubocop.yml new file mode 100644 index 00000000000..a9f364a37fa --- /dev/null +++ b/swift/.rubocop.yml @@ -0,0 +1 @@ +inherit_from: ../omnibus/.rubocop.yml diff --git a/swift/Gemfile b/swift/Gemfile new file mode 100644 index 00000000000..430599a9e1b --- /dev/null +++ b/swift/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "dependabot-common", path: "../common" + +gemspec diff --git a/swift/README.md b/swift/README.md new file mode 100644 index 00000000000..1d5a4bbfd6a --- /dev/null +++ b/swift/README.md @@ -0,0 +1,23 @@ +## `dependabot-swift` + +Swift Package Manager support for [`dependabot-core`][core-repo]. + +### Running locally + +1. Install native helpers + ``` + $ export DEPENDABOT_NATIVE_HELPERS_PATH=$PWD/helpers/install-dir + $ helpers/build + ``` + +2. Install Ruby dependencies + ``` + $ bundle install + ``` + +3. Run tests + ``` + $ bundle exec rspec spec + ``` + +[core-repo]: https://github.com/dependabot/dependabot-core \ No newline at end of file diff --git a/swift/dependabot-swift.gemspec b/swift/dependabot-swift.gemspec new file mode 100644 index 00000000000..55fc710bbaa --- /dev/null +++ b/swift/dependabot-swift.gemspec @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "find" + +Gem::Specification.new do |spec| + common_gemspec = + Bundler.load_gemspec_uncached("../common/dependabot-common.gemspec") + + spec.name = "dependabot-terraform" + spec.summary = "Swift Package Manager support for dependabot" + spec.version = common_gemspec.version + spec.description = common_gemspec.description + + spec.author = common_gemspec.author + spec.email = common_gemspec.email + spec.homepage = common_gemspec.homepage + spec.license = common_gemspec.license + + spec.require_path = "lib" + spec.files = [] + + spec.required_ruby_version = common_gemspec.required_ruby_version + spec.required_rubygems_version = common_gemspec.required_ruby_version + + spec.add_dependency "dependabot-common", Dependabot::VERSION + + common_gemspec.development_dependencies.each do |dep| + spec.add_development_dependency dep.name, dep.requirement.to_s + end + + next unless File.exist?("../.gitignore") + + ignores = File.readlines("../.gitignore").grep(/\S+/).map(&:chomp) + + next unless File.directory?("lib") && File.directory?("helpers") + + prefix = "/" + File.basename(File.expand_path(__dir__)) + "/" + Find.find("lib", "helpers") do |path| + if ignores.any? { |i| File.fnmatch(i, prefix + path, File::FNM_DOTMATCH) } + Find.prune + else + spec.files << path unless File.directory?(path) + end + end +end \ No newline at end of file diff --git a/swift/lib/dependabot/swift.rb b/swift/lib/dependabot/swift.rb new file mode 100644 index 00000000000..0919eab7c21 --- /dev/null +++ b/swift/lib/dependabot/swift.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# These all need to be required so the various classes can be registered in a +# lookup table of package manager names to concrete classes. +require "dependabot/swift/package" +require "dependabot/swift/file_fetcher" +require "dependabot/swift/file_parser" +require "dependabot/swift/update_checker" +require "dependabot/swift/file_updater" +require "dependabot/swift/metadata_finder" +require "dependabot/swift/requirement" +require "dependabot/swift/version" + +require "dependabot/pull_request_creator/labeler" +Dependabot::PullRequestCreator::Labeler. + register_label_details("swift", name: "swift_package_manager", colour: "F05138") + +require "dependabot/dependency" +Dependabot::Dependency. + register_production_check("swift", ->(_) { true }) \ No newline at end of file diff --git a/swift/lib/dependabot/swift/file_fetcher.rb b/swift/lib/dependabot/swift/file_fetcher.rb new file mode 100644 index 00000000000..76e6227f6b7 --- /dev/null +++ b/swift/lib/dependabot/swift/file_fetcher.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "dependabot/file_fetchers" +require "dependabot/file_fetchers/base" +require "dependabot/swift/package" + +module Dependabot + module Swift + class FileFetcher < Dependabot::FileFetchers::Base + def self.required_files_in?(filenames) + filenames.include?("Package.swift") + end + + def self.required_files_message + "Repo must contain a Package.swift configuration file." + end + + private + + def fetch_files + fetched_files = [] + fetched_files << package_manifest if package_manifest + fetched_files << package_resolved if package_resolved + + check_required_files_present + + return fetched_files if fetched_files.any? + end + + def package_manifest + @package_manifest ||= fetch_file_from_host("Package.swift") + end + + def package_resolved + @package_resolved ||= fetch_file_from_host("Package.resolved") + end + + def check_required_files_present + return if package_manifest + + path = Pathname.new(File.join(directory, "Package.swift")). + cleanpath.to_path + raise Dependabot::DependencyFileNotFound, path + end + end + end +end + +Dependabot::FileFetchers. + register("swift", Dependabot::Swift::FileFetcher) \ No newline at end of file diff --git a/swift/lib/dependabot/swift/file_parser.rb b/swift/lib/dependabot/swift/file_parser.rb new file mode 100644 index 00000000000..e562087d01c --- /dev/null +++ b/swift/lib/dependabot/swift/file_parser.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +require "cgi" +require "excon" +require "nokogiri" +require "open3" +require "yaml" +require "shellwords" + +require "dependabot/dependency" +require "dependabot/file_parsers" +require "dependabot/file_parsers/base" +require "dependabot/git_commit_checker" +require "dependabot/shared_helpers" +require "dependabot/errors" + +module Dependabot + module Swift + class FileParser < Dependabot::FileParsers::Base + require "dependabot/file_parsers/base/dependency_set" + + def parse + dependency_set = DependencySet.new + + pins.each do |pin| + url = pin["repositoryURL"] + id = Dependabot::Swift::Package::Identifier.new(url) + + requirements = parse_declaration(declarations.find { |d| d["url"] == url }) + + if requirements.empty? && (version = pin.dig("state", "version")) + requirements << Gem::Requirement.new(version.to_s) + end + + dependency_set << Dependency.new( + name: id.normalized, + version: version, + package_manager: "swift", + requirements: requirements.map do |requirement| + { + requirement: requirement.to_s, + groups: ["dependencies"], + file: package_manifest_file.name, + source: { + type: "repository", + url: url + } + } + end + ) + end + + dependency_set.dependencies + end + + private + + def check_required_files + raise "No Package.swift!" unless package_manifest_file + raise "No Package.resolved!" unless package_resolved_file + end + + def declarations + dump_package(package_manifest_file)["dependencies"] + end + + def pins + JSON.parse(package_resolved_file.content).dig("object", "pins") + end + + def dump_package(manifest) + SharedHelpers.in_a_temporary_directory do |path| + File.write(manifest.name, manifest.content) + + command = Shellwords.join([ + "swift", "package", "dump-package", + "--skip-update", + "--package-path", path + ]) + + env = {} + stdout, _stderr, _status = Open3.capture3(env, command) + # handle_parser_error(path, stderr) unless status.success? + JSON.parse(stdout) + end + end + + def package_manifest_file + # TODO: Select version-specific manifest + @package_manifest_file ||= get_original_file("Package.swift") + end + + def package_resolved_file + @package_resolved_file ||= get_original_file("Package.resolved") + end + + def parse_declaration(declaration) + requirements = [] + declaration&.dig("requirement", "range")&.each do |range| + if (lower_bound = range["lowerBound"]) + requirements << Gem::Requirement.new(">= #{lower_bound}") + end + + if (upper_bound = range["upperBound"]) + requirements << Gem::Requirement.new("< #{upper_bound}") + end + end + requirements + end + end + end +end + +Dependabot::FileParsers. + register("swift", Dependabot::Swift::FileParser) \ No newline at end of file diff --git a/swift/lib/dependabot/swift/file_updater.rb b/swift/lib/dependabot/swift/file_updater.rb new file mode 100644 index 00000000000..846200fba36 --- /dev/null +++ b/swift/lib/dependabot/swift/file_updater.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "dependabot/file_updaters" +require "dependabot/file_updaters/base" +require "dependabot/errors" + +module Dependabot + module Swift + class FileUpdater < Dependabot::FileUpdaters::Base + def self.updated_files_regex + [ + /Package(@swift-\d(\.\d){0,2})?\.swift/, + /^Package\.resolved$/ + ] + end + + def updated_dependency_files + raise NotImplementedError + end + end + end +end + +Dependabot::FileUpdaters. + register("swift", Dependabot::Swift::FileUpdater) \ No newline at end of file diff --git a/swift/lib/dependabot/swift/metadata_finder.rb b/swift/lib/dependabot/swift/metadata_finder.rb new file mode 100644 index 00000000000..2501e02a0b1 --- /dev/null +++ b/swift/lib/dependabot/swift/metadata_finder.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "excon" +require "json" +require "dependabot/metadata_finders" +require "dependabot/metadata_finders/base" +require "dependabot/shared_helpers" + +module Dependabot + module Swift + class MetadataFinder < Dependabot::MetadataFinders::Base + private + + def look_up_source + case new_source_type + when "git" then find_source_from_git_url + when "registry" then find_source_from_registry + else raise "Unexpected source type: #{new_source_type}" + end + end + + def new_source_type + sources = + dependency.requirements.map { |r| r.fetch(:source) }.uniq.compact + + return "default" if sources.empty? + raise "Multiple sources! #{sources.join(', ')}" if sources.count > 1 + + sources.first[:type] || sources.first.fetch("type") + end + + def find_source_from_git_url + info = dependency.requirements.map { |r| r[:source] }.compact.first + + url = info[:url] || info.fetch("url") + Source.from_url(url) + end + + def find_source_from_registry + raise NotImplementedError + end + end + end +end + +Dependabot::MetadataFinders. + register("swift", Dependabot::Swift::MetadataFinder) \ No newline at end of file diff --git a/swift/lib/dependabot/swift/package.rb b/swift/lib/dependabot/swift/package.rb new file mode 100644 index 00000000000..97ab2ffa825 --- /dev/null +++ b/swift/lib/dependabot/swift/package.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "dependabot/swift/package/identifier" \ No newline at end of file diff --git a/swift/lib/dependabot/swift/package/identifier.rb b/swift/lib/dependabot/swift/package/identifier.rb new file mode 100644 index 00000000000..59a2425b589 --- /dev/null +++ b/swift/lib/dependabot/swift/package/identifier.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "uri" + +module Dependabot + module Swift + module Package + class Identifier + include Comparable + + SCOPE_REGEX = /[a-zA-Z\d](?:[a-zA-Z\d]|-(?=[a-zA-Z\d])){0,38}/.freeze + NAME_REGEX = /\p{XID_Start}\p{XID_Continue}{0,127}/.freeze + + attr_reader :scope, :name + + def initialize(string) + case string + when %r{\Ahttps?://(?:www\.)?github.com(?:\:\d+)?/#{SCOPE_REGEX}/#{NAME_REGEX}(?:\.git)?\z} + components = URI(string).path.split("/").reject(&:empty?) + @scope = components.first + @name = components.last.sub(/\.git$/, "") + when %r{\A(?:ssh\://)?git@github.com[/:]#{SCOPE_REGEX}/#{NAME_REGEX}(?:\.git)?\z} + components = string.sub(%r{(?:ssh\://)?git@github.com[:/]}, "").split("/") + @scope = components.first + @name = components.last.sub(/\.git$/, "") + when /%r{\A#{SCOPE_REGEX}\.#{NAME_REGEX}\z}/ + @scope, @name = string.split(".") + end + end + + def normalized + "#{@scope.downcase}.#{@name.unicode_normalize(:nfkc).downcase(:fold)}" + end + + def inspect + "#<#{self.class.name}:#{object_id} scope: #{@scope}, name: #{@name}>" + end + + def to_s + "#{scope}.#{name}" + end + + def <=>(other) + normalized <=> other.normalized + end + end + end + end +end \ No newline at end of file diff --git a/swift/lib/dependabot/swift/requirement.rb b/swift/lib/dependabot/swift/requirement.rb new file mode 100644 index 00000000000..061c97d1de1 --- /dev/null +++ b/swift/lib/dependabot/swift/requirement.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "dependabot/utils" +require "dependabot/swift/version" + +# Override all essential methods to make it possible to delegate all work to the +# dedicated Dart library for parsing and updating Dart version constraints. +module Dependabot + module Swift + class Requirement < Gem::Requirement + end + end +end + +Dependabot::Utils. + register_requirement_class("swift", Dependabot::Swift::Requirement) \ No newline at end of file diff --git a/swift/lib/dependabot/swift/update_checker.rb b/swift/lib/dependabot/swift/update_checker.rb new file mode 100644 index 00000000000..1d6b69a3984 --- /dev/null +++ b/swift/lib/dependabot/swift/update_checker.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "json" +require "yaml" + +require "dependabot/update_checkers" +require "dependabot/update_checkers/base" +require "dependabot/git_commit_checker" +require "dependabot/shared_helpers" +require "dependabot/swift/requirement" +require "dependabot/swift/version" + +module Dependabot + module Swift + class UpdateChecker < Dependabot::UpdateCheckers::Base + def latest_version + raise NotImplementedError + end + + def latest_resolvable_version + raise NotImplementedError + end + + def latest_resolvable_version_with_no_unlock + raise NotImplementedError + end + + def updated_requirements + raise NotImplementedError + end + end + end +end + +Dependabot::UpdateCheckers. + register("swift", Dependabot::Swift::UpdateChecker) \ No newline at end of file diff --git a/swift/lib/dependabot/swift/version.rb b/swift/lib/dependabot/swift/version.rb new file mode 100644 index 00000000000..f4409d2b07f --- /dev/null +++ b/swift/lib/dependabot/swift/version.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Dependabot + module Swift + class Version < Gem::Version + end + end + end + + Dependabot::Utils. + register_version_class("swift", Dependabot::Swift::Version) \ No newline at end of file diff --git a/swift/script/ci-test b/swift/script/ci-test new file mode 100644 index 00000000000..8ba165bea47 --- /dev/null +++ b/swift/script/ci-test @@ -0,0 +1,6 @@ +#!/bin/bash + +set -e + +bundle install +bundle exec parallel_test spec/ -n "$CI_NODE_TOTAL" --only-group "$CI_NODE_INDEX" --group-by filesize --type rspec diff --git a/swift/spec/dependabot/swift/file_fetcher_spec.rb b/swift/spec/dependabot/swift/file_fetcher_spec.rb new file mode 100644 index 00000000000..c61aa930a8a --- /dev/null +++ b/swift/spec/dependabot/swift/file_fetcher_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/swift/file_fetcher" +require_common_spec "file_fetchers/shared_examples_for_file_fetchers" + +RSpec.describe Dependabot::Swift::FileFetcher do + it_behaves_like "a dependency file fetcher" + + let(:directory) { "/" } + + let(:source) do + Dependabot::Source.new( + provider: "github", + repo: "mona/LinkedList", + directory: directory + ) + end + + let(:file_fetcher_instance) do + described_class.new(source: source, credentials: credentials) + end + + let(:github_url) { "https://api.github.com/" } + + let(:url) { github_url + "repos/mona/LinkedList/contents/" } + + let(:credentials) do + [{ + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "token" + }] + end + + before do + allow(file_fetcher_instance).to receive(:commit).and_return("sha") + end + + context "with a Package.swift file" do + before do + stub_request(:get, url + "?ref=sha"). + with(headers: { "Authorization" => "token token" }). + to_return( + status: 200, + headers: { "content-type" => "application/json" }, + body: fixture("github", "mona", "LinkedList", "repo.json") + ) + + %w(Package.swift Package.resolved).each do |filename| + stub_request(:get, File.join(url, "#{filename}?ref=sha")). + with(headers: { "Authorization" => "token token" }). + to_return( + status: 200, + headers: { "content-type" => "application/json" }, + body: fixture("github", "mona", "LinkedList", "#{filename}.json") + ) + end + end + + it "fetches the manifest and resolved files" do + expect(file_fetcher_instance.files.map(&:name)). + to match_array(%w(Package.swift Package.resolved)) + end + end + + context "with a directory that doesn't exist" do + let(:directory) { "/nonexistent" } + + before do + %w(Package.swift Package.resolved).each do |filename| + stub_request(:get, File.join(url, "nonexistent", "#{filename}?ref=sha")). + with(headers: { "Authorization" => "token token" }). + to_return( + status: 404, + headers: { "content-type" => "application/json" }, + body: fixture("github", "errors", "not_found.json") + ) + end + end + + it "raises a helpful error" do + expect { file_fetcher_instance.files }. + to raise_error(Dependabot::DependencyFileNotFound) + end + end +end \ No newline at end of file diff --git a/swift/spec/dependabot/swift/file_parser_spec.rb b/swift/spec/dependabot/swift/file_parser_spec.rb new file mode 100644 index 00000000000..e6e3a7def57 --- /dev/null +++ b/swift/spec/dependabot/swift/file_parser_spec.rb @@ -0,0 +1,209 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/dependency_file" +require "dependabot/source" +require "dependabot/swift/file_parser" +require_common_spec "file_parsers/shared_examples_for_file_parsers" + +RSpec.describe Dependabot::Swift::FileParser do + it_behaves_like "a dependency file parser" + + let(:parser) do + described_class.new(dependency_files: files, source: source) + end + + let(:source) do + Dependabot::Source.new( + provider: "github", + repo: "mona/Example", + directory: "/" + ) + end + + let(:files) do + [ + package_manifest_file, + package_resolved_file + ] + end + + let(:package_manifest_file) do + Dependabot::DependencyFile.new( + name: "Package.swift", + content: fixture("github", "mona", "Example", "Package.swift") + ) + end + + let(:package_resolved_file) do + Dependabot::DependencyFile.new( + name: "Package.resolved", + content: fixture("github", "mona", "Example", "Package.resolved") + ) + end + + describe "parse" do + subject(:dependencies) { parser.parse } + + context "with hosted sources" do + its(:length) { is_expected.to eq(7) } + + describe "the first dependency (https://github.com/mona/LinkedList.git from 1.2.0)" do + subject(:dependency) { dependencies[0] } + + let(:expected_requirements) do + [">= 1.2.0", "< 2.0.0"].map do |requirement| + { + requirement: requirement, + groups: ["dependencies"], + file: "Package.swift", + source: { + type: "repository", + url: "https://github.com/mona/LinkedList.git" + } + } + end + end + + it "has the right details" do + expect(dependency).to be_a(Dependabot::Dependency) + expect(dependency.name).to eq("mona.linkedlist") + expect(dependency.version).to eq("1.2.2") + expect(dependency.requirements).to eq(expected_requirements) + end + end + + describe "the second dependency (https://github.com/mona/Queue up to next major from 1.1.1)" do + subject(:dependency) { dependencies[1] } + + let(:expected_requirements) do + [">= 1.1.1", "< 2.0.0"].map do |requirement| + { + requirement: requirement, + groups: ["dependencies"], + file: "Package.swift", + source: { + type: "repository", + url: "https://github.com/mona/Queue" + } + } + end + end + + it "has the right details" do + expect(dependency).to be_a(Dependabot::Dependency) + expect(dependency.name).to eq("mona.queue") + expect(dependency.version).to eq("1.5.0") + expect(dependency.requirements).to eq(expected_requirements) + end + end + + describe "the third dependency (https://github.com/mona/HashMap.git up to next minor from 0.9.0)" do + subject(:dependency) { dependencies[2] } + + let(:expected_requirements) do + [">= 0.9.0", "< 0.10.0"].map do |requirement| + { + requirement: requirement, + groups: ["dependencies"], + file: "Package.swift", + source: { + type: "repository", + url: "https://github.com/mona/HashMap.git" + } + } + end + end + + it "has the right details" do + expect(dependency).to be_a(Dependabot::Dependency) + expect(dependency.name).to eq("mona.hashmap") + expect(dependency.version).to eq("0.9.9") + expect(dependency.requirements).to eq(expected_requirements) + end + end + + describe "the fourth dependency (git@github.com:mona/Matrix.git up to next minor from 2.0.1)" do + subject(:dependency) { dependencies[3] } + + let(:expected_requirements) do + [">= 2.0.1", "< 2.1.0"].map do |requirement| + { + requirement: requirement, + groups: ["dependencies"], + file: "Package.swift", + source: { + type: "repository", + url: "git@github.com:mona/Matrix.git" + } + } + end + end + + it "has the right details" do + expect(dependency).to be_a(Dependabot::Dependency) + expect(dependency.name).to eq("mona.matrix") + expect(dependency.version).to eq("2.0.1") + expect(dependency.requirements).to eq(expected_requirements) + end + end + + describe "the fifth dependency (ssh://git@github.com:mona/RedBlackTree.git up to next minor from 0.1.0)" do + subject(:dependency) { dependencies[4] } + + let(:expected_requirements) do + [">= 0.1.0", "< 0.2.0"].map do |requirement| + { + requirement: requirement, + groups: ["dependencies"], + file: "Package.swift", + source: { + type: "repository", + url: "ssh://git@github.com:mona/RedBlackTree.git" + } + } + end + end + + it "has the right details" do + expect(dependency).to be_a(Dependabot::Dependency) + expect(dependency.name).to eq("mona.redblacktree") + expect(dependency.version).to eq("0.1.10") + expect(dependency.requirements).to eq(expected_requirements) + end + end + + describe "the fifth dependency (https://github.com/mona/Heap on main branch)" do + subject(:dependency) { dependencies[5] } + + let(:expected_requirements) do + [] + end + + it "has the right details" do + expect(dependency).to be_a(Dependabot::Dependency) + expect(dependency.name).to eq("mona.heap") + expect(dependency.version).to eq(nil) + expect(dependency.requirements).to eq(expected_requirements) + end + end + + # rubocop:disable Layout/LineLength + describe "the seventh dependency (https://github.com:443/mona/Trie.git at revision f3b37c3ccf0a1559d4097e2eeb883801c4b8f510)" do + # rubocop:enable Layout/LineLength + subject(:dependency) { dependencies[6] } + + let(:expected_requirements) do + [] + end + + it "has the right details" do + expect(dependency).to be_a(Dependabot::Dependency) + expect(dependency.name).to eq("mona.trie") + expect(dependency.version).to eq(nil) + expect(dependency.requirements).to eq(expected_requirements) + end + end + end + end +end \ No newline at end of file diff --git a/swift/spec/dependabot/swift/file_updater_spec.rb b/swift/spec/dependabot/swift/file_updater_spec.rb new file mode 100644 index 00000000000..310c263f577 --- /dev/null +++ b/swift/spec/dependabot/swift/file_updater_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/dependency" +require "dependabot/dependency_file" +require "dependabot/swift/file_updater" +require_common_spec "file_updaters/shared_examples_for_file_updaters" + +# RSpec.describe Dependabot::Swift::FileUpdater do +# it_behaves_like "a dependency file updater" +# end \ No newline at end of file diff --git a/swift/spec/dependabot/swift/metadata_finder_spec.rb b/swift/spec/dependabot/swift/metadata_finder_spec.rb new file mode 100644 index 00000000000..62e2e2e0980 --- /dev/null +++ b/swift/spec/dependabot/swift/metadata_finder_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/dependency" +require "dependabot/swift/metadata_finder" +require_common_spec "metadata_finders/shared_examples_for_metadata_finders" + +# RSpec.describe Dependabot::Swift::MetadataFinder do +# it_behaves_like "a dependency metadata finder" +# end \ No newline at end of file diff --git a/swift/spec/dependabot/swift/update_checker_spec.rb b/swift/spec/dependabot/swift/update_checker_spec.rb new file mode 100644 index 00000000000..0c4f3133c04 --- /dev/null +++ b/swift/spec/dependabot/swift/update_checker_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/dependency" +require "dependabot/swift" +require "dependabot/shared_helpers" +require "dependabot/swift/update_checker" +require_common_spec "update_checkers/shared_examples_for_update_checkers" + +# RSpec.describe Dependabot::Swift::UpdateChecker do +# it_behaves_like "an update checker" +# end \ No newline at end of file diff --git a/swift/spec/dependabot/swift_spec.rb b/swift/spec/dependabot/swift_spec.rb new file mode 100644 index 00000000000..fe0fcc895d9 --- /dev/null +++ b/swift/spec/dependabot/swift_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/swift" +require_common_spec "shared_examples_for_autoloading" + +RSpec.describe Dependabot::Swift do + it_behaves_like "it registers the required classes", "swift" +end \ No newline at end of file diff --git a/swift/spec/spec_helper.rb b/swift/spec/spec_helper.rb new file mode 100644 index 00000000000..f188399ab07 --- /dev/null +++ b/swift/spec/spec_helper.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +def common_dir + @common_dir ||= Gem::Specification.find_by_name("dependabot-common").gem_dir + end + + def require_common_spec(path) + require "#{common_dir}/spec/dependabot/#{path}" + end + + require "#{common_dir}/spec/spec_helper.rb" + + if ENV["COVERAGE"] + # TODO: Bring branch coverage up + SimpleCov.minimum_coverage line: 80, branch: 65 + end + \ No newline at end of file