cuesmash 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ module Cuesmash
5
+
6
+ #
7
+ # Provides a nice interface to cucumber, allowing
8
+ # us to run the cucumber test suite
9
+ #
10
+ # @author [alexfish]
11
+ #
12
+ class Cucumber
13
+
14
+ # Public: the output directory for the tests
15
+ attr_accessor :output
16
+
17
+ # Public: the output format for the tests
18
+ attr_accessor :format
19
+
20
+ #
21
+ # Create a new instance of Cucumber
22
+ # @param ios [String] The iOS version cucumber will run
23
+ # @param tags [Array] The tags cucumber will run with
24
+ #
25
+ def initialize(ios, tags)
26
+ @ios = ios
27
+ @tags = tags
28
+ end
29
+
30
+ #
31
+ # Run the cucumber tests
32
+ #
33
+ def test
34
+ started
35
+
36
+ status = nil
37
+ output = ""
38
+ Open3.popen3 command do |stdin, out, err, wait_thr|
39
+
40
+ [out, err].each do |stream|
41
+ Thread.new do
42
+ until (line = stream.gets).nil? do
43
+ Logger.info line
44
+ end
45
+ end
46
+ end
47
+
48
+ wait_thr.join
49
+ status = wait_thr.value.exitstatus
50
+ end
51
+
52
+ if status != 0
53
+ Logger.info "Cucumber failed"
54
+ # exit status
55
+ status
56
+ else
57
+ completed
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ #
64
+ # Output a nice message for starting
65
+ #
66
+ def started
67
+ Logger.info "Running Cucumber"
68
+ end
69
+
70
+ #
71
+ # Output a nice message for completing
72
+ #
73
+ def completed
74
+ Logger.info "Cucumber Completed 👌"
75
+ end
76
+
77
+ #
78
+ # Figure out what the cucumber command is and
79
+ # return it
80
+ #
81
+ # @return [String] The cucumber command string
82
+ def command
83
+ command = "cucumber"
84
+ command += simulator_arguments if @ios
85
+ command += " --format #{self.format}" if self.format
86
+ command += " --out #{self.output}" if self.output
87
+ command += @tags.to_a.empty? ? "" : tag_arguments
88
+ command += " -c"
89
+
90
+ Logger.debug "cucumber command == #{command}"
91
+
92
+ command
93
+ end
94
+
95
+ #
96
+ # Generate the --tags arguments for the cucumber
97
+ # command
98
+ #
99
+ # @return [String] The --tags commands ready to go
100
+ def tag_arguments
101
+ command = ""
102
+ @tags.each do |tag_set|
103
+ command = "" unless command
104
+ command += " --tags #{tag_set}"
105
+ end
106
+ command
107
+ end
108
+
109
+ #
110
+ # Generate the simulator version arguments that
111
+ # are best for certain versions of simulators
112
+ #
113
+ # @return [String] The simulator arguments
114
+ def simulator_arguments
115
+ if @ios
116
+ command = " DEVICE_TARGET='iPhone Retina (4-inch) - Simulator - iOS #{@ios}'"
117
+ end
118
+
119
+ command
120
+ end
121
+
122
+ end
123
+ end
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ module Cuesmash
5
+
6
+ #
7
+ # Provides an object to get information about the ios app that is being built.
8
+ #
9
+ # @author [jarod]
10
+ #
11
+ class IosApp
12
+
13
+ # Public: the path to the dir containing the built app i.e. /tmp/MyAppQWERQW/Build/Products/Debug-iphonesimulator/
14
+ attr_reader :app_dir
15
+
16
+ # Public: the full path including the built app i.e. /tmp/MyAppQWERQW/Build/Products/Debug-iphonesimulator/MyApp.app"
17
+ attr_reader :app_path
18
+
19
+ # Public: the app name i.e. MyApp.app
20
+ attr_reader :app_name
21
+
22
+ # Public: the xcode Derived Data temp directory
23
+ attr_reader :tmp_dir
24
+
25
+ #
26
+ # Create a new App instance
27
+ #
28
+ # @param file_name [String] The usually is the scheme of the xcode project
29
+ #
30
+ # @return [App] A app instance
31
+ def initialize(file_name:)
32
+ @app_name = "#{file_name}" << ".app"
33
+ @tmp_dir = Dir.mktmpdir(file_name)
34
+ @app_dir = "#{@tmp_dir}" << "/Build/Products/Debug-iphonesimulator/"
35
+ @app_path = "#{@app_dir}" << "#{@app_name}"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ module Cuesmash
5
+
6
+ #
7
+ # Does some fun stuff with Xcode plists, cuesmash needs to update
8
+ # the Xcode projects plist to trick the simulator into connecting
9
+ # to a sinatra server instead
10
+ #
11
+ # @author [alexfish]
12
+ #
13
+ class Plist
14
+ # include Logging
15
+
16
+ # Public: the Scheme the plist is related to
17
+ attr_accessor :scheme
18
+ attr_accessor :tmp_dir
19
+ attr_accessor :plist_name
20
+
21
+ #
22
+ # Create a new plist instance
23
+ # @param scheme [String] The scheme related to the plist
24
+ # @param app_path [String] The dir where the app is going to be placed.
25
+ # @param plist_name [String] Default: server_config. The name of the file with the server configurations.
26
+ #
27
+ # @return [Plist] A plist instance
28
+ def initialize(scheme, app_path, plist_name="server_config")
29
+ @scheme = scheme
30
+ @tmp_dir = tmp_dir
31
+ @plist_name = plist_name
32
+ @app_path = app_path
33
+ end
34
+
35
+ #
36
+ # Executes the plist tasks update and clear the old plists
37
+ #
38
+ def execute
39
+ started
40
+ update
41
+ clear
42
+
43
+ completed
44
+ end
45
+
46
+ private
47
+
48
+ #
49
+ # Output a nice message for starting
50
+ #
51
+ def started
52
+ Logger.info "Updating plist"
53
+ end
54
+
55
+ #
56
+ # Output a nice message for completing
57
+ #
58
+ def completed
59
+ Logger.info "Plist updated 👌"
60
+ end
61
+
62
+ #
63
+ # Update the Xcode applications server.plist file
64
+ # with sinatras port and URL
65
+ #
66
+ def update
67
+ plist_file = CFPropertyList::List.new(:file => server_plist_path)
68
+ plist = CFPropertyList.native_types(plist_file.value)
69
+
70
+ plist["url_preference"] = server_ip
71
+ plist["port_preference"] = Cuesmash::PORT
72
+
73
+ plist_file.value = CFPropertyList.guess(plist)
74
+ plist_file.save(server_plist_path, CFPropertyList::List::FORMAT_XML)
75
+ end
76
+
77
+ #
78
+ # Clear the existing plist from the iOS simulator
79
+ #
80
+ def clear
81
+ FileUtils.rm(simulator_plist_path, :force => true)
82
+ end
83
+
84
+ #
85
+ # The local IP address of the mock backend server
86
+ #
87
+ # @return [String] The mock backends IP
88
+ def server_ip
89
+ Socket.ip_address_list.find {|a| a.ipv4? && !a.ipv4_loopback?}.ip_address
90
+ end
91
+
92
+ #
93
+ # The path to the iOS simulators plist
94
+ #
95
+ # @return [String] The path to the plist
96
+ def simulator_plist_path
97
+ "#{File.expand_path('~')}/Library/Preferences/com.apple.iphonesimulator.plist"
98
+ end
99
+
100
+ #
101
+ # The path to the server config plist
102
+ #
103
+ # @return The full path to the server config plist
104
+ def server_plist_path
105
+ @app_path + "/#{@plist_name}.plist"
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require 'fileutils'
5
+ require 'uri'
6
+ require 'rest_client'
7
+ require 'json'
8
+ require 'open-uri'
9
+
10
+ module Cuesmash
11
+ class Setup
12
+ class << self
13
+ #
14
+ # Run the command line programs to setup appium. Based on steps from http://appium.io/slate/en/tutorial/ios.html?ruby#install-ruby
15
+ #
16
+ def setup
17
+ install_cucumber
18
+ create_features_dir
19
+ create_env_rb
20
+ install_appium_console
21
+ install_brew
22
+ install_node
23
+ create_travis_yml
24
+ create_scripts_dir
25
+ create_build_sh
26
+ create_gemfile
27
+ end
28
+
29
+ # TODO: the git checkouts needs to check to see if the dirs have already been
30
+ # checked out.
31
+
32
+ private
33
+
34
+ #
35
+ # Honestly all of these should be self evident as to what they do. If you still can't figure it ou
36
+ # come find me and I'll beat you with a ruby hammer.
37
+ #
38
+ def install_cucumber
39
+ command_runner(command: "gem install --no-rdoc --no-ri cucumber")
40
+ end
41
+
42
+ def create_features_dir
43
+ command_runner(command: "mkdir -p features/{support,step_definitions}")
44
+ end
45
+
46
+ def create_env_rb
47
+ download_gist(gist_id:"9fa5e495758463ee5340", final_file:"features/support/env.rb")
48
+ end
49
+
50
+ def install_appium_console
51
+ command_runner(command: "gem install --no-rdoc --no-ri appium_console")
52
+ end
53
+
54
+ def install_brew
55
+ brew_path = `which brew`
56
+ if brew_path == 0
57
+ command_runner(command: "ruby -e \"$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)\"")
58
+ else
59
+ Logger.info "Brew already installed."
60
+ end
61
+ end
62
+
63
+ def install_node
64
+ command_runner(command: "brew update; brew upgrade node; brew install node")
65
+ end
66
+
67
+ def create_travis_yml
68
+ download_gist(gist_id:"74cc418331bd81651746", final_file:".travis.yml")
69
+ end
70
+
71
+ def create_scripts_dir
72
+ puts "creating scripts dir"
73
+ command_runner(command: "mkdir -p scripts")
74
+ end
75
+
76
+ def create_build_sh
77
+ download_gist(gist_id:"8df9762a103c694f5773", final_file:"scripts/build.sh")
78
+ end
79
+
80
+ def create_gemfile
81
+ download_gist(gist_id:"ea786f1cf0fdbe0febb3", final_file:"Gemfile")
82
+ end
83
+
84
+ #
85
+ # Run the command line
86
+ # @param command: [String] The command line statement to run
87
+ #
88
+ def command_runner(command:)
89
+ status = nil
90
+ Logger.info "Starting: #{command}"
91
+ Open3.popen3 command do |stdin, out, err, wait_thr|
92
+ [out, err].each do |stream|
93
+ Thread.new do
94
+ until (line = stream.gets).nil? do
95
+ Logger.info line
96
+ end # until
97
+ end # Thread.new
98
+ end # each
99
+ wait_thr.join
100
+ status = wait_thr.value.exitstatus
101
+ end # Open3
102
+
103
+ if status != 0
104
+ Logger.info "Command failed: #{command}"
105
+ exit status
106
+ else
107
+ Logger.info "Finished: #{command}"
108
+ end
109
+ return
110
+ end # command_runner
111
+
112
+ #
113
+ # Download gists files without git.
114
+ # @param gist_id: [String] the gist id
115
+ # @param final_file: [String] where the final file gets saved
116
+ #
117
+ def download_gist(gist_id:, final_file:)
118
+ base_url = URI("https://api.github.com/gists/")
119
+ json = JSON.parse(RestClient.get(URI.join(base_url, gist_id).to_s))
120
+ file_name = json['files'].keys[0]
121
+ raw_url = json['files'][file_name]['raw_url']
122
+
123
+ open(final_file, 'wb') do |file|
124
+ file << open(raw_url).read
125
+ end
126
+ end # download_gist
127
+ end # class << self
128
+ end # Class Setup
129
+ end # Module Cuesmash
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require 'thor'
5
+ require 'cuesmash'
6
+ require 'cuesmash/setup'
7
+ require 'byebug'
8
+
9
+ module Cuesmash
10
+
11
+ CONFIG_FILE = '.cuesmash.yml'
12
+
13
+ class Start < Thor
14
+
15
+ desc 'init', 'set up the project'
16
+ def init
17
+ Cuesmash::Setup.setup
18
+ end
19
+
20
+ desc "test OPTIONS", "run the tests"
21
+ long_desc <<-LONGDESC
22
+ --tags -t the tags to pass to cucumber, for multiple tags pass one per tag\n
23
+ --output -o The output directory for the test report\n
24
+ --format -f The format of the test report\n
25
+ --server -s
26
+ LONGDESC
27
+ method_option :scheme, type: :string, aliases: "-s", desc: "the Xcode scheme to build"
28
+ method_option :tags, type: :array, aliases: "-t", desc: "the tags to pass to cucumber, for multiple tags pass one per tag"
29
+ method_option :debug, type: :boolean, default: false, aliases: "-d", desc: "turn on debug output."
30
+ # method_option :server, type: :string, aliases: "-r", desc: ""
31
+ def test
32
+
33
+ # load up the .cuesmash.yml config. If we don't find one then bail
34
+ if File.exists?(CONFIG_FILE)
35
+ config = YAML.load_file(CONFIG_FILE) if File.exists?(CONFIG_FILE)
36
+ else
37
+ say "There is no '.cuesmash.yml' file. Please create one and try again.", :red
38
+ return
39
+ end
40
+
41
+ config['devices'].each do |device, oses|
42
+ oses.each do |os|
43
+ say "\n============================\ntesting iOS #{os} on #{device}", :green
44
+ # byebug
45
+ Cuesmash::Command.execute(device: device, os: os, server: options[:server], tags: options[:tags], scheme: options[:scheme], debug: options[:debug])
46
+ end
47
+ end # device each
48
+ end # test
49
+
50
+ desc "demo FILE", "an example task"
51
+ method_option :delete, aliases: "-d", desc: "Delete the file after parsing it"
52
+ def demo
53
+ puts "options == #{options}"
54
+ end
55
+
56
+ end # Start class
57
+ end # module Cuesmash
data/lib/cuesmash.rb ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require 'optparse'
5
+ require "open3"
6
+ require 'cfpropertylist'
7
+ require 'find'
8
+ require 'socket'
9
+ require 'fileutils'
10
+ require 'tmpdir'
11
+ require 'logger'
12
+ require 'yaml'
13
+
14
+ require 'cuesmash/command'
15
+ require 'cuesmash/compiler'
16
+ require 'cuesmash/plist'
17
+ require 'cuesmash/cucumber'
18
+ require 'cuesmash/appium_text'
19
+ require 'cuesmash/iosapp'
20
+ require 'cuesmash/appium_server'
21
+
22
+ module Cuesmash
23
+ PORT = 4567
24
+
25
+ Logger = Logger.new(STDOUT)
26
+ Logger.level = ::Logger::INFO
27
+ Logger.formatter = proc do |serverity, time, progname, msg|
28
+ "\n#{msg.rstrip}"
29
+ end
30
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cuesmash::AppiumText do
4
+
5
+ describe "when creating a new instance" do
6
+ before(:all) do
7
+ @appiumtext = Cuesmash::AppiumText.new(platform_name: "iOS", device_name: "iPhone Simulator", platform_version: "7.1", app: "MyApp")
8
+ end
9
+
10
+ it "should have a platform name" do
11
+ @appiumtext.platform_name.should match("iOS")
12
+ end
13
+
14
+ it "should have a device name" do
15
+ @appiumtext.device_name.should match("iPhone Simulator")
16
+ end
17
+
18
+ it "should have a platform_version" do
19
+ @appiumtext.platform_version.should match("7.1")
20
+ end
21
+
22
+ it "should have an app name" do
23
+ @appiumtext.app.should match("MyApp")
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cuesmash::Command do
4
+
5
+ describe "when executing" do
6
+
7
+ before(:each) do
8
+ Cuesmash::Command.stub(:compile)
9
+ Cuesmash::Command.stub(:parse){nil}
10
+ end
11
+ end
12
+
13
+ describe "when starting a compile" do
14
+
15
+ before(:each) do
16
+ @mock = double(Cuesmash::Compiler)
17
+ @mock.stub(:compile)
18
+ Cuesmash::Compiler.stub(:new){@mock}
19
+ end
20
+
21
+ it "should set the compilers scheme and tmp dir" do
22
+ Cuesmash::Compiler.should_receive(:new).with("scheme", "/tmp")
23
+ Cuesmash::Command.compile("scheme", "/tmp")
24
+ end
25
+
26
+ it "should start compiling" do
27
+ @mock.should_receive(:compile)
28
+ Cuesmash::Command.compile("scheme", "/tmp")
29
+ end
30
+ end
31
+
32
+ describe "when updating the plist" do
33
+
34
+ before(:each ) do
35
+ @mock = double(Cuesmash::Plist)
36
+ @mock.stub(:execute)
37
+ Cuesmash::Plist.stub(:new){@mock}
38
+ end
39
+
40
+ it "should set the plists scheme" do
41
+ Cuesmash::Plist.should_receive(:new).with("scheme", "/tmp")
42
+ Cuesmash::Command.update_plist("scheme", "/tmp")
43
+ end
44
+
45
+ it "should execute the plist update" do
46
+ @mock.should_receive(:execute)
47
+ Cuesmash::Command.update_plist("scheme", "/tmp")
48
+ end
49
+
50
+ describe "when running the cucumber tests" do
51
+
52
+ before(:each ) do
53
+ @mock = double(Cuesmash::Cucumber)
54
+ @mock.stub(:test)
55
+ Cuesmash::Cucumber.stub(:new){@mock}
56
+ end
57
+
58
+ it "should set the cucumber ios version" do
59
+ Cuesmash::Cucumber.should_receive(:new).with("ios", anything)
60
+ Cuesmash::Command.run_tests("ios", "tags")
61
+ end
62
+
63
+ it "should set the cucumber tags" do
64
+ Cuesmash::Cucumber.should_receive(:new).with(anything, "tags")
65
+ Cuesmash::Command.run_tests("ios", "tags")
66
+ end
67
+
68
+ it "should set the format" do
69
+ @cucumber = Cuesmash::Cucumber.new
70
+ Cuesmash::Cucumber.stub(:new){@cucumber}
71
+
72
+ @cucumber.should_receive(:format=)
73
+ Cuesmash::Command.run_tests(nil, nil, "format")
74
+ end
75
+
76
+ it "should set the output" do
77
+ @cucumber = Cuesmash::Cucumber.new
78
+ Cuesmash::Cucumber.stub(:new){@cucumber}
79
+
80
+ @cucumber.should_receive(:output=)
81
+ Cuesmash::Command.run_tests(nil, nil, nil, "output")
82
+ end
83
+
84
+ it "should start the tests" do
85
+ @mock.should_receive(:test)
86
+ Cuesmash::Command.run_tests("ios", "tags")
87
+ end
88
+
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cuesmash::Compiler do
4
+
5
+ before(:each) do
6
+ Cuesmash::Compiler.any_instance.stub(:puts)
7
+ end
8
+
9
+ it "should have a scheme instance" do
10
+ compiler = Cuesmash::Compiler.new("scheme", "/tmp")
11
+ compiler.scheme.should match("scheme")
12
+ end
13
+
14
+ it "should have a tmp_dir instance" do
15
+ compiler = Cuesmash::Compiler.new("scheme", "/tmp")
16
+ compiler.tmp_dir.should match("/tmp")
17
+ end
18
+
19
+ describe "when generating the command" do
20
+
21
+ before(:each) do
22
+ Cuesmash::Compiler.any_instance.stub(:workspace)
23
+ @compiler = Cuesmash::Compiler.new("test-scheme", "/tmp")
24
+ end
25
+
26
+ it "should contain the scheme" do
27
+ @compiler.instance_eval{command}.should match(/test-scheme/)
28
+ end
29
+
30
+ it "should contain the tmp path" do
31
+ @compiler.instance_eval{command}.should match(/tmp/)
32
+ end
33
+ end
34
+
35
+ describe "when getting the workspacae" do
36
+
37
+ before(:each) do
38
+ @compiler = Cuesmash::Compiler.new(nil, "/tmp")
39
+ Dir.stub(:[]){["workspace-file"]}
40
+ end
41
+
42
+ it "should get the workspace from the current directory" do
43
+ @compiler.instance_eval{workspace}.should match(/workspace-file/)
44
+ end
45
+ end
46
+
47
+ describe "when compiling" do
48
+
49
+ before(:each) do
50
+ wait = double
51
+ @value = double
52
+ wait.stub(:value){@value}
53
+ wait.stub(:join)
54
+ Open3.stub(:popen3).and_yield(nil, nil, nil, wait)
55
+
56
+ @compiler = Cuesmash::Compiler.new(nil, "/tmp")
57
+ end
58
+
59
+ it "should complete if all is well" do
60
+ @value.stub(:exitstatus){0}
61
+ @compiler.compile do |complete|
62
+ complete.should equal(true)
63
+ end
64
+ end
65
+ end
66
+
67
+ end