run_loop_tcc 2.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|