swiftfake 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 51fa9134419ca08be43c69842e6d78ccb6ccbb0c
4
+ data.tar.gz: 2c153d41523de881c41ff5957d53bf6f6364260a
5
+ SHA512:
6
+ metadata.gz: 1b05f5f61147a2e31445941b2dcae2dfe8ae47a40596a8d2fa11326cf57c82dc7042072c248e34aef2ea9c2a8a75df6eb44d5750fcfa9f14e54ef8353e8f1193
7
+ data.tar.gz: b5b81a272d6d494782bb709478de6a87213b18d110254dfb2123241fc05512ad338d796a6294ca089139bb6bfe666e0054c6fa0c0f2f6427310a7cd9d83edaaa
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in swiftfake.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Swiftfake
2
+
3
+ Generate test fakes from Swift code. The fakes allow you to:
4
+
5
+ - Verify how many times a function was called
6
+ - Verify what arguments were received
7
+ - Return a canned value
8
+
9
+ ## Usage
10
+
11
+ Pass a Swift file path and the fake will be printed to STDOUT:
12
+
13
+ ```bash
14
+ swiftfake ./app/MySwiftClass.swift
15
+ ```
16
+
17
+ You could then pipe the output:
18
+
19
+ ```bash
20
+ # To clipboard
21
+ swiftfake ./app/MySwiftClass.swift | pbcopy
22
+
23
+ # To a file
24
+ swiftfake ./app/MySwiftClass.swift > ./test/FakeMySwiftClass.swift
25
+ ```
26
+
27
+ ## Requirements
28
+
29
+ - Ruby 2.1+ (run `ruby -v` to check)
30
+ - (SourceKitten)[https://github.com/jpsim/SourceKitten] (`brew install sourcekitten`)
31
+
32
+ ## Notes
33
+
34
+ This gem is still in an alpha state.
35
+
36
+ Roadmap:
37
+
38
+ - Copy across @import statements from source
39
+ - Fake Protocol implementations
40
+ - Implement Bright Futures support
41
+ - Handling multiple classes/protocols in the Swift source file
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "swiftfake"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/swiftfake ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "swiftfake"
4
+
5
+ input_file = ARGF.argv[0]
6
+ runner = Swiftfake::Runner.new(args: {input: input_file})
7
+ puts runner.run
@@ -0,0 +1,88 @@
1
+ module Swiftfake
2
+ module FunctionDecorator
3
+ def signature
4
+ "override #{full_name}"
5
+ end
6
+
7
+ def call_count
8
+ "#{name}CallCount"
9
+ end
10
+
11
+ def call_count_declaration
12
+ "#{var_prefix} #{call_count} = 0"
13
+ end
14
+
15
+ def has_args?
16
+ arguments.any?
17
+ end
18
+
19
+ def args_store_append
20
+ store_name = args_store_name
21
+
22
+ if arguments.count == 1
23
+ "#{store_name}.append(#{arguments.first.name})"
24
+ else
25
+ arg_names = arguments.map(&:name).join(", ")
26
+ tuple = "(#{arg_names})"
27
+ "#{store_name}.append(#{tuple})"
28
+ end
29
+ end
30
+
31
+ def args_store_declaration
32
+ "#{var_prefix} #{args_store_name} = [#{args_store_type}]()"
33
+ end
34
+
35
+ def returns?
36
+ !return_value.nil?
37
+ end
38
+
39
+ def return_value_declaration
40
+ "return #{return_value_var_name}"
41
+ end
42
+
43
+ def return_value_store_declaration
44
+ "#{var_prefix} #{return_value_var_name} = #{return_value_initialized}"
45
+ end
46
+
47
+ private
48
+
49
+ def var_prefix
50
+ full_name.include?("class") ? "static var" : "var"
51
+ end
52
+
53
+ def args_store_type
54
+ if arguments.count == 1
55
+ arguments.first.type
56
+ else
57
+ args_as_tuple
58
+ end
59
+ end
60
+
61
+ def args_store_name
62
+ "#{name}ArgsForCall"
63
+ end
64
+
65
+ def args_as_tuple
66
+ args = arguments
67
+ .map{ |a| "#{a.name}: #{a.type}" }
68
+ .join(', ')
69
+ "(#{args})"
70
+ end
71
+
72
+ def return_value_var_name
73
+ "#{name}ReturnValue"
74
+ end
75
+
76
+ def return_value_initialized
77
+ return "#{return_value}()" unless return_value_is_tuple?
78
+
79
+ tuple = return_value.gsub(')', '())')
80
+ tuple.gsub(',', '(),')
81
+ end
82
+
83
+ def return_value_is_tuple?
84
+ reg = /^\(.+,.+\)$/
85
+ (return_value =~ reg) == 0
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,31 @@
1
+ require 'swiftfake/function_decorator'
2
+
3
+ module Swiftfake
4
+ class Presenter
5
+ attr_reader :swift_class
6
+
7
+ def initialize(swift_class)
8
+ @swift_class = swift_class
9
+ end
10
+
11
+ def get_binding
12
+ binding()
13
+ end
14
+
15
+ def fake_class_signature
16
+ "#{swift_class.access} class Fake#{swift_class.name}: #{swift_class.name}"
17
+ end
18
+
19
+ def functions
20
+ @functions ||= swift_class.functions.map { |f| f.extend(FunctionDecorator) }
21
+ end
22
+
23
+ def functions_with_args
24
+ functions.select(&:has_args?)
25
+ end
26
+
27
+ def functions_with_return_value
28
+ functions.select(&:returns?)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ require 'erb'
2
+
3
+ module Swiftfake
4
+ class Renderer
5
+ def output(presenter)
6
+ erb = ERB.new(template, nil, '-')
7
+ erb.result(presenter.get_binding)
8
+ end
9
+
10
+ private
11
+
12
+ def template
13
+ path = File.expand_path("../../template.erb", __FILE__)
14
+ File.read(path)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,100 @@
1
+ require 'json'
2
+ require 'swiftfake/swift_class'
3
+ require 'swiftfake/swift_function'
4
+
5
+ module Swiftfake
6
+ class SourceKitParser
7
+
8
+ attr_reader :source_file, :structure_json
9
+
10
+ def parse(source_file, raw_structure_json)
11
+ @source_file = source_file
12
+ @structure_json = JSON.parse(raw_structure_json)
13
+
14
+ SwiftClass.new(
15
+ name: class_name,
16
+ access: access_level,
17
+ functions: parse_functions
18
+ )
19
+ end
20
+
21
+ private
22
+
23
+ def class_name
24
+ first_entity.fetch("key.name", nil)
25
+ end
26
+
27
+ def access_level
28
+ raw_accessibility = first_entity.fetch("key.accessibility", nil)
29
+ raw_accessibility.split('.').last unless raw_accessibility.nil?
30
+ end
31
+
32
+ def first_entity
33
+ structure_json
34
+ .fetch("key.substructure", [])
35
+ .fetch(0, {})
36
+ end
37
+
38
+ def parse_functions
39
+ function_declarations = first_entity
40
+ .fetch("key.substructure", [])
41
+ .select{ |part| part.fetch("key.kind", "").include? "function.method" }
42
+ .map{|method| find_raw_method_decl(method) }
43
+ .compact
44
+
45
+ function_declarations
46
+ .map {|f| FunctionParser.new.parse(f) }
47
+ .compact
48
+ end
49
+
50
+ def find_raw_method_decl(method)
51
+ start_offset = method["key.offset"]
52
+ end_offset = method["key.bodyoffset"]
53
+
54
+ return nil if start_offset.nil? || end_offset.nil?
55
+
56
+ end_offset -= 1
57
+ source_file[start_offset...end_offset]
58
+ end
59
+
60
+ class FunctionParser
61
+
62
+ def parse(function_line)
63
+ return nil unless can_override?(function_line)
64
+
65
+ /func (?<name>.*)\(/ =~ function_line
66
+ /(?<access>public|internal|private)/ =~ function_line
67
+ /->\s(?<return_value>.+)$/ =~ function_line
68
+
69
+ return_value.strip! unless return_value.nil?
70
+
71
+ SwiftFunction.new(
72
+ full_name: function_line.strip,
73
+ name: name,
74
+ access: access,
75
+ arguments: parse_args(function_line),
76
+ return_value: return_value
77
+ )
78
+ end
79
+
80
+ private
81
+
82
+ def can_override?(function_line)
83
+ !function_line.include?('final') && !function_line.include?('private')
84
+ end
85
+
86
+ def parse_args(function_line)
87
+ /func .*\((?<raw_args>.+)\)/ =~ function_line
88
+ return [] if raw_args.nil?
89
+
90
+ raw_args
91
+ .split(",")
92
+ .map { |raw|
93
+ /(?<name>[\w]+):\s(?<type>.+)/ =~ raw
94
+ SwiftFunction::Argument.new(name.strip, type.strip)
95
+ }
96
+ end
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,12 @@
1
+ require 'open3'
2
+
3
+ module Swiftfake
4
+ class SourceReader
5
+ def read_file(source_file)
6
+ source = File.read(source_file)
7
+ structure_json, status = Open3.capture2("sourcekitten structure --file #{source_file}")
8
+
9
+ [source, structure_json] if status.success?
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ module Swiftfake
2
+ class SwiftClass
3
+ attr_reader :name, :access, :functions
4
+
5
+ def initialize(name:, access:, functions:)
6
+ @name = name
7
+ @access = access
8
+ @functions = functions
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Swiftfake
2
+ class SwiftFunction
3
+ Argument = Struct.new(:name, :type)
4
+
5
+ attr_reader :full_name, :name, :access, :arguments, :return_value
6
+
7
+ def initialize(full_name:, name:, access:, arguments:, return_value:)
8
+ @full_name = full_name
9
+ @name = name
10
+ @access = access
11
+ @arguments = arguments
12
+ @return_value = return_value
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module Swiftfake
2
+ VERSION = "0.2.0"
3
+ end
data/lib/swiftfake.rb ADDED
@@ -0,0 +1,31 @@
1
+ require 'swiftfake/version'
2
+ require 'swiftfake/source_reader'
3
+ require 'swiftfake/source_kit_parser'
4
+ require 'swiftfake/presenter'
5
+ require 'swiftfake/renderer'
6
+
7
+ module Swiftfake
8
+
9
+ Config = Struct.new(:source_reader, :parser_klass, :presenter_klass, :renderer) do
10
+ def self.create(source_reader: SourceReader.new, parser_klass: SourceKitParser, presenter_klass: Presenter, renderer: Renderer.new)
11
+ self.new(source_reader, parser_klass, presenter_klass, renderer)
12
+ end
13
+ end
14
+
15
+ class Runner
16
+ attr_reader :args, :config
17
+
18
+ def initialize(args:, config: Config.create)
19
+ @args = args
20
+ @config = config
21
+ end
22
+
23
+ def run
24
+ source_file, structure_json = config.source_reader.read_file(args[:input])
25
+ parser = config.parser_klass.new
26
+ swift_class = parser.parse(source_file, structure_json)
27
+ presenter = config.presenter_klass.new(swift_class)
28
+ config.renderer.output(presenter)
29
+ end
30
+ end
31
+ end
data/lib/template.erb ADDED
@@ -0,0 +1,25 @@
1
+ <%= fake_class_signature %> {
2
+
3
+ <% functions.each do |f| -%>
4
+ <%= f.signature %> {
5
+ <%= f.call_count %> += 1
6
+ <% if f.has_args? %><%= f.args_store_append %><% end %>
7
+ <% if f.returns? %><%= f.return_value_declaration %><% end %>
8
+ }
9
+
10
+ <% end %>
11
+
12
+ // MARK: - Fake Helpers
13
+
14
+ <% functions.each do |f| -%>
15
+ <%= f.call_count_declaration %>
16
+ <% end %>
17
+
18
+ <% functions_with_args.each do |f| -%>
19
+ <%= f.args_store_declaration %>
20
+ <% end %>
21
+
22
+ <% functions_with_return_value.each do |f| -%>
23
+ <%= f.return_value_store_declaration %>
24
+ <% end %>
25
+ }
data/swiftfake.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'swiftfake/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "swiftfake"
8
+ spec.version = Swiftfake::VERSION
9
+ spec.authors = ["odlp"]
10
+ spec.email = ["oliverp@gmail.com"]
11
+
12
+ spec.summary = "Generate test fakes from Swift code."
13
+ spec.homepage = "https://github.com/odlp/swiftfake"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = ["swiftfake"]
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.11"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "rspec", "~> 3.0"
23
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: swiftfake
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - odlp
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-07-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description:
56
+ email:
57
+ - oliverp@gmail.com
58
+ executables:
59
+ - swiftfake
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - ".travis.yml"
66
+ - Gemfile
67
+ - README.md
68
+ - Rakefile
69
+ - bin/console
70
+ - bin/setup
71
+ - exe/swiftfake
72
+ - lib/swiftfake.rb
73
+ - lib/swiftfake/function_decorator.rb
74
+ - lib/swiftfake/presenter.rb
75
+ - lib/swiftfake/renderer.rb
76
+ - lib/swiftfake/source_kit_parser.rb
77
+ - lib/swiftfake/source_reader.rb
78
+ - lib/swiftfake/swift_class.rb
79
+ - lib/swiftfake/swift_function.rb
80
+ - lib/swiftfake/version.rb
81
+ - lib/template.erb
82
+ - swiftfake.gemspec
83
+ homepage: https://github.com/odlp/swiftfake
84
+ licenses: []
85
+ metadata: {}
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 2.5.1
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: Generate test fakes from Swift code.
106
+ test_files: []