run_loop_tcc 2.1.3
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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/bin/run-loop +19 -0
- data/lib/run_loop/abstract.rb +18 -0
- data/lib/run_loop/app.rb +372 -0
- data/lib/run_loop/cache/cache.rb +68 -0
- data/lib/run_loop/cli/cli.rb +48 -0
- data/lib/run_loop/cli/codesign.rb +24 -0
- data/lib/run_loop/cli/errors.rb +11 -0
- data/lib/run_loop/cli/instruments.rb +160 -0
- data/lib/run_loop/cli/locale.rb +31 -0
- data/lib/run_loop/cli/simctl.rb +257 -0
- data/lib/run_loop/cli/tcc.rb +139 -0
- data/lib/run_loop/codesign.rb +76 -0
- data/lib/run_loop/core.rb +902 -0
- data/lib/run_loop/core_simulator.rb +960 -0
- data/lib/run_loop/detect_aut/detect.rb +185 -0
- data/lib/run_loop/detect_aut/errors.rb +126 -0
- data/lib/run_loop/detect_aut/xamarin_studio.rb +46 -0
- data/lib/run_loop/detect_aut/xcode.rb +157 -0
- data/lib/run_loop/device.rb +722 -0
- data/lib/run_loop/device_agent/app/CBX-Runner.app.zip +0 -0
- data/lib/run_loop/device_agent/bin/xctestctl +0 -0
- data/lib/run_loop/device_agent/cbxrunner.rb +156 -0
- data/lib/run_loop/device_agent/frameworks/Frameworks.zip +0 -0
- data/lib/run_loop/device_agent/frameworks.rb +65 -0
- data/lib/run_loop/device_agent/ipa/CBX-Runner.app.zip +0 -0
- data/lib/run_loop/device_agent/launcher.rb +51 -0
- data/lib/run_loop/device_agent/xcodebuild.rb +91 -0
- data/lib/run_loop/device_agent/xctestctl.rb +109 -0
- data/lib/run_loop/directory.rb +179 -0
- data/lib/run_loop/dnssd.rb +148 -0
- data/lib/run_loop/dot_dir.rb +87 -0
- data/lib/run_loop/dylib_injector.rb +145 -0
- data/lib/run_loop/encoding.rb +56 -0
- data/lib/run_loop/environment.rb +361 -0
- data/lib/run_loop/fifo.rb +40 -0
- data/lib/run_loop/host_cache.rb +128 -0
- data/lib/run_loop/http/error.rb +15 -0
- data/lib/run_loop/http/request.rb +44 -0
- data/lib/run_loop/http/retriable_client.rb +166 -0
- data/lib/run_loop/http/server.rb +17 -0
- data/lib/run_loop/instruments.rb +436 -0
- data/lib/run_loop/ipa.rb +142 -0
- data/lib/run_loop/l10n.rb +93 -0
- data/lib/run_loop/language.rb +63 -0
- data/lib/run_loop/lipo.rb +132 -0
- data/lib/run_loop/lldb.rb +52 -0
- data/lib/run_loop/locale.rb +101 -0
- data/lib/run_loop/logging.rb +111 -0
- data/lib/run_loop/otool.rb +76 -0
- data/lib/run_loop/patches/awesome_print.rb +17 -0
- data/lib/run_loop/physical_device/life_cycle.rb +268 -0
- data/lib/run_loop/plist_buddy.rb +189 -0
- data/lib/run_loop/process_terminator.rb +128 -0
- data/lib/run_loop/process_waiter.rb +117 -0
- data/lib/run_loop/regex.rb +19 -0
- data/lib/run_loop/shell.rb +103 -0
- data/lib/run_loop/sim_control.rb +1264 -0
- data/lib/run_loop/simctl.rb +275 -0
- data/lib/run_loop/sqlite.rb +61 -0
- data/lib/run_loop/strings.rb +88 -0
- data/lib/run_loop/tcc/TCC.db +0 -0
- data/lib/run_loop/tcc/tcc.rb +240 -0
- data/lib/run_loop/template.rb +61 -0
- data/lib/run_loop/version.rb +182 -0
- data/lib/run_loop/xcode.rb +318 -0
- data/lib/run_loop/xcrun.rb +107 -0
- data/lib/run_loop/xcuitest.rb +550 -0
- data/lib/run_loop.rb +230 -0
- data/plists/simctl/com.apple.UIAutomation.plist +0 -0
- data/plists/simctl/com.apple.UIAutomationPlugIn.plist +0 -0
- data/scripts/calabash_script_uia.js +28184 -0
- data/scripts/lib/json2.min.js +26 -0
- data/scripts/lib/log.js +26 -0
- data/scripts/lib/on_alert.js +224 -0
- data/scripts/read-cmd.sh +2 -0
- data/scripts/run_dismiss_location.js +89 -0
- data/scripts/run_loop_basic.js +34 -0
- data/scripts/run_loop_fast_uia.js +188 -0
- data/scripts/run_loop_host.js +117 -0
- data/scripts/run_loop_shared_element.js +125 -0
- data/scripts/timeout3 +23 -0
- data/scripts/udidetect +0 -0
- data/vendor-licenses/FBSimulatorControl.LICENSE +30 -0
- data/vendor-licenses/xctestctl.LICENSE +32 -0
- metadata +443 -0
Binary file
|
Binary file
|
@@ -0,0 +1,156 @@
|
|
1
|
+
|
2
|
+
module RunLoop
|
3
|
+
# @!visibility private
|
4
|
+
module DeviceAgent
|
5
|
+
# @!visibility private
|
6
|
+
class CBXRunner
|
7
|
+
|
8
|
+
|
9
|
+
# @!visibility private
|
10
|
+
@@cbxdevice = nil
|
11
|
+
|
12
|
+
# @!visibility private
|
13
|
+
@@cbxsim = nil
|
14
|
+
|
15
|
+
# @!visibility private
|
16
|
+
def self.device_agent_dir
|
17
|
+
@@device_agent_dir ||= File.expand_path(File.dirname(__FILE__))
|
18
|
+
end
|
19
|
+
|
20
|
+
# @!visibility private
|
21
|
+
def self.detect_cbxsim
|
22
|
+
@@cbxsim ||= lambda do
|
23
|
+
from_env = RunLoop::Environment.cbxsim
|
24
|
+
|
25
|
+
if from_env
|
26
|
+
if File.exist?(from_env)
|
27
|
+
from_env
|
28
|
+
else
|
29
|
+
raise RuntimeError, %Q[
|
30
|
+
CBXSIM environment variable defined:
|
31
|
+
|
32
|
+
#{from_env}
|
33
|
+
|
34
|
+
but runner does not exist at that path.
|
35
|
+
]
|
36
|
+
end
|
37
|
+
else
|
38
|
+
self.default_cbxsim
|
39
|
+
end
|
40
|
+
end.call
|
41
|
+
end
|
42
|
+
|
43
|
+
# @!visibility private
|
44
|
+
def self.detect_cbxdevice
|
45
|
+
@@cbxdevice ||= lambda do
|
46
|
+
from_env = RunLoop::Environment.cbxdevice
|
47
|
+
|
48
|
+
if from_env
|
49
|
+
if File.exist?(from_env)
|
50
|
+
from_env
|
51
|
+
else
|
52
|
+
raise RuntimeError, %Q[
|
53
|
+
CBXDEVICE environment variable defined:
|
54
|
+
|
55
|
+
#{from_env}
|
56
|
+
|
57
|
+
but runner does not exist at that path.
|
58
|
+
]
|
59
|
+
end
|
60
|
+
else
|
61
|
+
self.default_cbxdevice
|
62
|
+
end
|
63
|
+
end.call
|
64
|
+
end
|
65
|
+
|
66
|
+
# @!visibility private
|
67
|
+
def self.default_cbxdevice
|
68
|
+
cbx = File.join(self.device_agent_dir, "ipa", "CBX-Runner.app")
|
69
|
+
|
70
|
+
if !File.exist?(cbx)
|
71
|
+
self.expand_runner_archive("#{cbx}.zip")
|
72
|
+
else
|
73
|
+
cbx
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# @!visibility private
|
78
|
+
def self.default_cbxsim
|
79
|
+
cbx = File.join(self.device_agent_dir, "app", "CBX-Runner.app")
|
80
|
+
|
81
|
+
if !File.exist?(cbx)
|
82
|
+
self.expand_runner_archive("#{cbx}.zip")
|
83
|
+
else
|
84
|
+
cbx
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# @!visibility private
|
89
|
+
# TODO move this behavior to shell.rb - should be able to call Shell.run_unix_command
|
90
|
+
def self.expand_runner_archive(archive)
|
91
|
+
shell = Class.new do
|
92
|
+
require "run_loop/shell"
|
93
|
+
include RunLoop::Shell
|
94
|
+
def to_s; "#<CBXRunner Shell>"; end
|
95
|
+
def inspect; to_s; end
|
96
|
+
end.new
|
97
|
+
|
98
|
+
dir = File.dirname(archive)
|
99
|
+
options = { :log_cmd => true }
|
100
|
+
Dir.chdir(dir) do
|
101
|
+
RunLoop.log_unix_cmd("cd #{dir}")
|
102
|
+
shell.run_unix_command(["unzip", File.basename(archive)], options)
|
103
|
+
end
|
104
|
+
File.join(dir, "CBX-Runner.app")
|
105
|
+
end
|
106
|
+
|
107
|
+
# @!visibility private
|
108
|
+
attr_reader :device
|
109
|
+
|
110
|
+
# @!visibility private
|
111
|
+
# @param [RunLoop::Device] device the target device
|
112
|
+
def initialize(device)
|
113
|
+
@device = device
|
114
|
+
end
|
115
|
+
|
116
|
+
# @!visibility private
|
117
|
+
def runner
|
118
|
+
@runner ||= lambda do
|
119
|
+
if device.physical_device?
|
120
|
+
RunLoop::DeviceAgent::CBXRunner.detect_cbxdevice
|
121
|
+
else
|
122
|
+
RunLoop::DeviceAgent::CBXRunner.detect_cbxsim
|
123
|
+
end
|
124
|
+
end.call
|
125
|
+
end
|
126
|
+
|
127
|
+
# @!visibility private
|
128
|
+
def tester
|
129
|
+
@tester ||= File.join(runner, "PlugIns", "CBX.xctest")
|
130
|
+
end
|
131
|
+
|
132
|
+
# @!visibility private
|
133
|
+
def version
|
134
|
+
@version ||= lambda do
|
135
|
+
short = pbuddy.plist_read("CFBundleShortVersionString", info_plist)
|
136
|
+
build = pbuddy.plist_read("CFBundleVersion", info_plist)
|
137
|
+
str = "#{short}.pre#{build}"
|
138
|
+
RunLoop::Version.new(str)
|
139
|
+
end.call
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
# @!visibility private
|
145
|
+
def info_plist
|
146
|
+
@info_plist ||= File.join(runner, "PlugIns", "CBX.xctest", "Info.plist")
|
147
|
+
end
|
148
|
+
|
149
|
+
# @!visibility private
|
150
|
+
def pbuddy
|
151
|
+
@pbuddy ||= RunLoop::PlistBuddy.new
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
Binary file
|
@@ -0,0 +1,65 @@
|
|
1
|
+
|
2
|
+
module RunLoop
|
3
|
+
# @!visibility private
|
4
|
+
module DeviceAgent
|
5
|
+
# @!visibility private
|
6
|
+
class Frameworks
|
7
|
+
require "singleton"
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
# @!visibility private
|
11
|
+
def install
|
12
|
+
if File.exist?(frameworks)
|
13
|
+
RunLoop.log_debug("#{frameworks} already exists; skipping install")
|
14
|
+
return true
|
15
|
+
end
|
16
|
+
|
17
|
+
RunLoop.log_debug("Installing Frameworks to #{target}")
|
18
|
+
|
19
|
+
options = { :log_cmd => true }
|
20
|
+
|
21
|
+
Dir.chdir(rootdir) do
|
22
|
+
RunLoop.log_unix_cmd("cd #{rootdir}")
|
23
|
+
shell.run_shell_command(["unzip", File.basename(zip)], options)
|
24
|
+
end
|
25
|
+
|
26
|
+
shell.run_shell_command(["cp", "-r", "#{frameworks}/*.framework", target], options)
|
27
|
+
shell.run_shell_command(["cp", "#{frameworks}/*LICENSE", target], options)
|
28
|
+
RunLoop.log_debug("Installed frameworks to #{target}")
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# @!visibility private
|
34
|
+
# TODO replace with include Shell
|
35
|
+
def shell
|
36
|
+
require "run_loop/shell"
|
37
|
+
Class.new do
|
38
|
+
include RunLoop::Shell
|
39
|
+
def to_s; "#<Frameworks Shell>"; end
|
40
|
+
def inspect; to_s; end
|
41
|
+
end.new
|
42
|
+
end
|
43
|
+
|
44
|
+
# @!visibility private
|
45
|
+
def target
|
46
|
+
@target ||= File.join(RunLoop::DotDir.directory, "Frameworks")
|
47
|
+
end
|
48
|
+
|
49
|
+
# @!visibility private
|
50
|
+
def frameworks
|
51
|
+
@frameworks ||= File.join(rootdir, "Frameworks")
|
52
|
+
end
|
53
|
+
|
54
|
+
# @!visibility private
|
55
|
+
def zip
|
56
|
+
@zip ||= File.join(rootdir, "Frameworks.zip")
|
57
|
+
end
|
58
|
+
|
59
|
+
# @!visibility private
|
60
|
+
def rootdir
|
61
|
+
@rootdir ||= File.expand_path(File.join(File.dirname(__FILE__), "frameworks"))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
Binary file
|
@@ -0,0 +1,51 @@
|
|
1
|
+
|
2
|
+
module RunLoop
|
3
|
+
# @!visibility private
|
4
|
+
module DeviceAgent
|
5
|
+
# @!visibility private
|
6
|
+
#
|
7
|
+
# An abstract base class for something that can launch the CBXRunner on a
|
8
|
+
# device. The CBXRunner is AKA the DeviceAgent.
|
9
|
+
class Launcher
|
10
|
+
require "run_loop/abstract"
|
11
|
+
include RunLoop::Abstract
|
12
|
+
|
13
|
+
# @!visibility private
|
14
|
+
attr_reader :device
|
15
|
+
|
16
|
+
# @!visibility private
|
17
|
+
# @param [RunLoop::Device] device where to launch the CBX-Runner
|
18
|
+
def initialize(device)
|
19
|
+
@device = device
|
20
|
+
|
21
|
+
if device.version < RunLoop::Version.new("9.0")
|
22
|
+
raise ArgumentError, %Q[
|
23
|
+
Invalid device:
|
24
|
+
|
25
|
+
#{device}
|
26
|
+
|
27
|
+
XCUITest is only available for iOS >= 9.0
|
28
|
+
]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @!visibility private
|
33
|
+
#
|
34
|
+
# Does whatever it takes to launch the CBX-Runner on the device.
|
35
|
+
def launch
|
36
|
+
abstract_method!
|
37
|
+
end
|
38
|
+
|
39
|
+
# @!visibility private
|
40
|
+
def self.dot_dir
|
41
|
+
path = File.join(RunLoop::DotDir.directory, "xcuitest")
|
42
|
+
|
43
|
+
if !File.exist?(path)
|
44
|
+
FileUtils.mkdir_p(path)
|
45
|
+
end
|
46
|
+
|
47
|
+
path
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
|
2
|
+
module RunLoop
|
3
|
+
|
4
|
+
# @!visibility private
|
5
|
+
module DeviceAgent
|
6
|
+
|
7
|
+
# @!visibility private
|
8
|
+
class Xcodebuild < RunLoop::DeviceAgent::Launcher
|
9
|
+
|
10
|
+
# @!visibility private
|
11
|
+
def self.log_file
|
12
|
+
path = File.join(Xcodebuild.dot_dir, "xcodebuild.log")
|
13
|
+
FileUtils.touch(path) if !File.exist?(path)
|
14
|
+
path
|
15
|
+
end
|
16
|
+
|
17
|
+
# @!visibility private
|
18
|
+
def to_s
|
19
|
+
"#<Xcodebuild #{workspace}>"
|
20
|
+
end
|
21
|
+
|
22
|
+
# @!visibility private
|
23
|
+
def inspect
|
24
|
+
to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
# @!visibility private
|
28
|
+
def launch
|
29
|
+
workspace
|
30
|
+
|
31
|
+
if device.simulator?
|
32
|
+
# quits the simulator
|
33
|
+
sim = CoreSimulator.new(device, "")
|
34
|
+
sim.launch_simulator
|
35
|
+
end
|
36
|
+
|
37
|
+
start = Time.now
|
38
|
+
RunLoop.log_debug("Waiting for CBX-Runner to build...")
|
39
|
+
pid = xcodebuild
|
40
|
+
RunLoop.log_debug("Took #{Time.now - start} seconds to build and launch CBX-Runner")
|
41
|
+
pid
|
42
|
+
end
|
43
|
+
|
44
|
+
# @!visibility private
|
45
|
+
def workspace
|
46
|
+
@workspace ||= lambda do
|
47
|
+
path = RunLoop::Environment.send(:cbxws)
|
48
|
+
if path
|
49
|
+
path
|
50
|
+
else
|
51
|
+
raise "The CBXWS env var is undefined. Are you a maintainer?"
|
52
|
+
end
|
53
|
+
end.call
|
54
|
+
end
|
55
|
+
|
56
|
+
# @!visibility private
|
57
|
+
def xcodebuild
|
58
|
+
env = {
|
59
|
+
"COMMAND_LINE_BUILD" => "1"
|
60
|
+
}
|
61
|
+
|
62
|
+
args = [
|
63
|
+
"xcrun",
|
64
|
+
"xcodebuild",
|
65
|
+
"-scheme", "CBXAppStub",
|
66
|
+
"-workspace", workspace,
|
67
|
+
"-config", "Debug",
|
68
|
+
"-destination",
|
69
|
+
"id=#{device.udid}",
|
70
|
+
"clean",
|
71
|
+
"test"
|
72
|
+
]
|
73
|
+
|
74
|
+
log_file = Xcodebuild.log_file
|
75
|
+
|
76
|
+
options = {
|
77
|
+
:out => log_file,
|
78
|
+
:err => log_file
|
79
|
+
}
|
80
|
+
|
81
|
+
command = "#{env.map.each { |k, v| "#{k}=#{v}" }.join(" ")} #{args.join(" ")}"
|
82
|
+
RunLoop.log_unix_cmd("#{command} >& #{log_file}")
|
83
|
+
|
84
|
+
pid = Process.spawn(env, *args, options)
|
85
|
+
Process.detach(pid)
|
86
|
+
pid.to_i
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
@@ -0,0 +1,109 @@
|
|
1
|
+
|
2
|
+
module RunLoop
|
3
|
+
# @!visibility private
|
4
|
+
module DeviceAgent
|
5
|
+
# @!visibility private
|
6
|
+
#
|
7
|
+
# A wrapper around the test-control binary.
|
8
|
+
class XCTestctl < RunLoop::DeviceAgent::Launcher
|
9
|
+
|
10
|
+
# @!visibility private
|
11
|
+
@@xctestctl = nil
|
12
|
+
|
13
|
+
# @!visibility private
|
14
|
+
def self.device_agent_dir
|
15
|
+
@@device_agent_dir ||= File.expand_path(File.dirname(__FILE__))
|
16
|
+
end
|
17
|
+
|
18
|
+
# @!visibility private
|
19
|
+
def self.xctestctl
|
20
|
+
@@xctestctl ||= lambda do
|
21
|
+
from_env = RunLoop::Environment.xctestctl
|
22
|
+
if from_env
|
23
|
+
if File.exist?(from_env)
|
24
|
+
RunLoop.log_debug("Using XCTESTCTL=#{from_env}")
|
25
|
+
from_env
|
26
|
+
else
|
27
|
+
raise RuntimeError, %Q[
|
28
|
+
XCTESTCTL environment variable defined:
|
29
|
+
|
30
|
+
#{from_env}
|
31
|
+
|
32
|
+
but binary does not exist at that path.
|
33
|
+
]
|
34
|
+
end
|
35
|
+
|
36
|
+
else
|
37
|
+
File.join(self.device_agent_dir, "bin", "xctestctl")
|
38
|
+
end
|
39
|
+
end.call
|
40
|
+
end
|
41
|
+
|
42
|
+
# @!visibility private
|
43
|
+
def to_s
|
44
|
+
"#<Testctl: #{XCTestctl.xctestctl}>"
|
45
|
+
end
|
46
|
+
|
47
|
+
# @!visibility private
|
48
|
+
def inspect
|
49
|
+
to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
# @!visibility private
|
53
|
+
def runner
|
54
|
+
@runner ||= RunLoop::DeviceAgent::CBXRunner.new(device)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @!visibility private
|
58
|
+
def self.log_file
|
59
|
+
path = File.join(Launcher.dot_dir, "xctestctl.log")
|
60
|
+
FileUtils.touch(path) if !File.exist?(path)
|
61
|
+
path
|
62
|
+
end
|
63
|
+
|
64
|
+
# @!visibility private
|
65
|
+
def launch
|
66
|
+
RunLoop::DeviceAgent::Frameworks.instance.install
|
67
|
+
|
68
|
+
if device.simulator?
|
69
|
+
cbxapp = RunLoop::App.new(runner.runner)
|
70
|
+
|
71
|
+
# quits the simulator
|
72
|
+
sim = CoreSimulator.new(device, cbxapp)
|
73
|
+
sim.install
|
74
|
+
end
|
75
|
+
|
76
|
+
cmd = RunLoop::DeviceAgent::XCTestctl.xctestctl
|
77
|
+
|
78
|
+
args = ["-r", runner.runner,
|
79
|
+
"-t", runner.tester,
|
80
|
+
"-d", device.udid]
|
81
|
+
|
82
|
+
if device.physical_device?
|
83
|
+
args << "-c"
|
84
|
+
args << RunLoop::Environment.codesign_identity
|
85
|
+
end
|
86
|
+
|
87
|
+
log_file = XCTestctl.log_file
|
88
|
+
FileUtils.rm_rf(log_file)
|
89
|
+
FileUtils.touch(log_file)
|
90
|
+
|
91
|
+
options = {:out => log_file, :err => log_file}
|
92
|
+
RunLoop.log_unix_cmd("#{cmd} #{args.join(" ")} >& #{log_file}")
|
93
|
+
|
94
|
+
# Gotta keep the xctestctl process alive or the connection
|
95
|
+
# to testmanagerd will fail.
|
96
|
+
pid = Process.spawn(cmd, *args, options)
|
97
|
+
Process.detach(pid)
|
98
|
+
|
99
|
+
if device.simulator?
|
100
|
+
device.simulator_wait_for_stable_state
|
101
|
+
end
|
102
|
+
|
103
|
+
pid.to_i
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
@@ -0,0 +1,179 @@
|
|
1
|
+
module RunLoop
|
2
|
+
|
3
|
+
# Class for performing operations on directories.
|
4
|
+
class Directory
|
5
|
+
require 'digest'
|
6
|
+
require 'openssl'
|
7
|
+
require 'pathname'
|
8
|
+
|
9
|
+
# Dir.glob ignores files that start with '.', but we often need to find
|
10
|
+
# dotted files and directories.
|
11
|
+
#
|
12
|
+
# Ruby 2.* does the right thing by ignoring '..' and '.'.
|
13
|
+
#
|
14
|
+
# Ruby < 2.0 includes '..' and '.' in results which causes problems for some
|
15
|
+
# of run-loop's internal methods. In particular `reset_app_sandbox`.
|
16
|
+
def self.recursive_glob_for_entries(base_dir)
|
17
|
+
Dir.glob("#{base_dir}/{**/.*,**/*}").select do |entry|
|
18
|
+
!(entry.end_with?('..') || entry.end_with?('.'))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Computes the digest of directory.
|
23
|
+
#
|
24
|
+
# @param path A path to a directory.
|
25
|
+
# @param options Control the behavior of the method.
|
26
|
+
# @option options :handle_errors_by (:raising) Controls what to do when
|
27
|
+
# File.read causes an error. The default behavior is to raise. Other
|
28
|
+
# options are: :logging and :ignoring. Logging will only happen if
|
29
|
+
# running in debug mode.
|
30
|
+
#
|
31
|
+
# @raise ArgumentError When `path` is not a directory or path does not exist.
|
32
|
+
# @raise ArgumentError When options[:handle_errors_by] has n unsupported value.
|
33
|
+
def self.directory_digest(path, options={})
|
34
|
+
default_options = {
|
35
|
+
:handle_errors_by => :raising
|
36
|
+
}
|
37
|
+
|
38
|
+
merged_options = default_options.merge(options)
|
39
|
+
handle_errors_by = merged_options[:handle_errors_by]
|
40
|
+
unless [:raising, :logging, :ignoring].include?(handle_errors_by)
|
41
|
+
raise ArgumentError,
|
42
|
+
%Q{Expected :handle_errors_by to be :raising, :logging, or :ignoring;
|
43
|
+
found '#{handle_errors_by}'
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
unless File.exist?(path)
|
48
|
+
raise ArgumentError, "Expected '#{path}' to exist"
|
49
|
+
end
|
50
|
+
|
51
|
+
unless File.directory?(path)
|
52
|
+
raise ArgumentError, "Expected '#{path}' to be a directory"
|
53
|
+
end
|
54
|
+
|
55
|
+
entries = self.recursive_glob_for_entries(path)
|
56
|
+
|
57
|
+
if entries.empty?
|
58
|
+
raise ArgumentError, "Expected a non-empty dir at '#{path}' found '#{entries}'"
|
59
|
+
end
|
60
|
+
|
61
|
+
debug = RunLoop::Environment.debug?
|
62
|
+
|
63
|
+
sha = OpenSSL::Digest::SHA256.new
|
64
|
+
entries.each do |file|
|
65
|
+
unless self.skip_file?(file, 'SHA1', debug)
|
66
|
+
begin
|
67
|
+
sha << File.read(file)
|
68
|
+
rescue => e
|
69
|
+
case handle_errors_by
|
70
|
+
when :logging
|
71
|
+
message =
|
72
|
+
%Q{RunLoop::Directory.directory_digest raised an error:
|
73
|
+
|
74
|
+
#{e}
|
75
|
+
|
76
|
+
while trying to find the SHA of this file:
|
77
|
+
|
78
|
+
#{file}
|
79
|
+
|
80
|
+
This is not a fatal error; it can be ignored.
|
81
|
+
}
|
82
|
+
RunLoop.log_debug(message)
|
83
|
+
when :raising
|
84
|
+
raise e.class, e.message
|
85
|
+
when :ignoring
|
86
|
+
# nop
|
87
|
+
else
|
88
|
+
# nop
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
sha.hexdigest
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.size(path, format)
|
97
|
+
|
98
|
+
allowed_formats = [:bytes, :kb, :mb, :gb]
|
99
|
+
unless allowed_formats.include?(format)
|
100
|
+
raise ArgumentError, "Expected '#{format}' to be one of #{allowed_formats.join(', ')}"
|
101
|
+
end
|
102
|
+
|
103
|
+
unless File.exist?(path)
|
104
|
+
raise ArgumentError, "Expected '#{path}' to exist"
|
105
|
+
end
|
106
|
+
|
107
|
+
unless File.directory?(path)
|
108
|
+
raise ArgumentError, "Expected '#{path}' to be a directory"
|
109
|
+
end
|
110
|
+
|
111
|
+
entries = self.recursive_glob_for_entries(path)
|
112
|
+
|
113
|
+
if entries.empty?
|
114
|
+
raise ArgumentError, "Expected a non-empty dir at '#{path}' found '#{entries}'"
|
115
|
+
end
|
116
|
+
|
117
|
+
size = self.iterate_for_size(entries)
|
118
|
+
|
119
|
+
case format
|
120
|
+
when :bytes
|
121
|
+
size
|
122
|
+
when :kb
|
123
|
+
size/1000.0
|
124
|
+
when :mb
|
125
|
+
size/1000.0/1000.0
|
126
|
+
when :gb
|
127
|
+
size/1000.0/1000.0/1000.0
|
128
|
+
else
|
129
|
+
# Not expected to reach this.
|
130
|
+
size
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def self.skip_file?(file, task, debug)
|
137
|
+
skip = false
|
138
|
+
if File.directory?(file)
|
139
|
+
# Skip directories
|
140
|
+
skip = true
|
141
|
+
elsif !Pathname.new(file).exist?
|
142
|
+
# Skip broken symlinks
|
143
|
+
skip = true
|
144
|
+
elsif !File.exist?(file)
|
145
|
+
# Skip files that don't exist
|
146
|
+
skip = true
|
147
|
+
else
|
148
|
+
case File.ftype(file)
|
149
|
+
when 'fifo'
|
150
|
+
RunLoop.log_warn("#{task} IS SKIPPING FIFO #{file}") if debug
|
151
|
+
skip = true
|
152
|
+
when 'socket'
|
153
|
+
RunLoop.log_warn("#{task} IS SKIPPING SOCKET #{file}") if debug
|
154
|
+
skip = true
|
155
|
+
when 'characterSpecial'
|
156
|
+
RunLoop.log_warn("#{task} IS SKIPPING CHAR SPECIAL #{file}") if debug
|
157
|
+
skip = true
|
158
|
+
when 'blockSpecial'
|
159
|
+
skip = true
|
160
|
+
RunLoop.log_warn("#{task} SKIPPING BLOCK SPECIAL #{file}") if debug
|
161
|
+
else
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
165
|
+
skip
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.iterate_for_size(entries)
|
169
|
+
debug = RunLoop::Environment.debug?
|
170
|
+
size = 0
|
171
|
+
entries.each do |file|
|
172
|
+
unless self.skip_file?(file, "SIZE", debug)
|
173
|
+
size = size + File.size(file)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
size
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|