git_statistics 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/git-statistics +2 -1
- data/bin/git_statistics +2 -1
- data/lib/git_statistics.rb +69 -21
- data/lib/git_statistics/branches.rb +35 -0
- data/lib/git_statistics/collector.rb +20 -45
- data/lib/git_statistics/commit_line_extractor.rb +8 -8
- data/lib/git_statistics/commits.rb +7 -4
- data/lib/git_statistics/formatters/console.rb +50 -47
- data/lib/git_statistics/initialize.rb +0 -3
- data/lib/git_statistics/log.rb +59 -0
- data/lib/git_statistics/pipe.rb +29 -0
- data/lib/git_statistics/utilities.rb +33 -23
- data/lib/git_statistics/version.rb +1 -1
- data/spec/branches_spec.rb +44 -0
- data/spec/collector_spec.rb +20 -41
- data/spec/commits_spec.rb +51 -47
- data/spec/formatters/console_spec.rb +25 -18
- data/spec/log_spec.rb +54 -0
- data/spec/pipe_spec.rb +59 -0
- data/spec/utilities_spec.rb +65 -63
- metadata +52 -9
- data/lib/git_statistics/core_ext/string.rb +0 -11
@@ -1,12 +1,9 @@
|
|
1
1
|
require 'json'
|
2
|
-
require 'trollop'
|
3
2
|
require 'grit'
|
4
3
|
require 'linguist'
|
5
|
-
require 'os'
|
6
4
|
require 'pathname'
|
7
5
|
|
8
6
|
# Must be required before all other files
|
9
|
-
require 'git_statistics/core_ext/string'
|
10
7
|
require 'git_statistics/blob'
|
11
8
|
require 'git_statistics/regex_matcher'
|
12
9
|
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'singleton'
|
3
|
+
|
4
|
+
module GitStatistics
|
5
|
+
class Log
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
attr_accessor :logger, :base_directory, :debugging
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@base_directory = File.expand_path("../..", __FILE__) + "/"
|
12
|
+
@debugging = false
|
13
|
+
@logger = Logger.new(STDOUT)
|
14
|
+
@logger.level = Logger::ERROR
|
15
|
+
@logger.formatter = proc do |sev, datetime, progname, msg|
|
16
|
+
"#{msg}\n"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.use_debug
|
21
|
+
instance.debugging = true
|
22
|
+
instance.logger.formatter = proc do |sev, datetime, progname, msg|
|
23
|
+
"#{sev} [#{progname}]: #{msg}\n"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Determine the file, method, line number of the caller
|
28
|
+
def self.parse_caller(message)
|
29
|
+
if /^(?<file>.+?):(?<line>\d+)(?::in `(?<method>.*)')?/ =~ message
|
30
|
+
file = Regexp.last_match[:file]
|
31
|
+
line = Regexp.last_match[:line]
|
32
|
+
method = Regexp.last_match[:method]
|
33
|
+
"#{file.sub(instance.base_directory, "")}:#{line}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.method_missing(method, *args, &blk)
|
38
|
+
if valid_method? method
|
39
|
+
instance.logger.progname = parse_caller(caller(1).first) if instance.debugging
|
40
|
+
instance.logger.send(method, *args, &blk)
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.respond_to_missing?(method, include_all=false)
|
47
|
+
if valid_method? method
|
48
|
+
true
|
49
|
+
else
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.valid_method?(method)
|
55
|
+
instance.logger.respond_to? method
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module GitStatistics
|
2
|
+
class Pipe
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(command)
|
6
|
+
@command = command
|
7
|
+
end
|
8
|
+
|
9
|
+
def command
|
10
|
+
@command.dup.gsub(/\A\|/i, '')
|
11
|
+
end
|
12
|
+
|
13
|
+
def each(&block)
|
14
|
+
lines.each(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def empty?
|
18
|
+
lines.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def lines
|
22
|
+
io.map { |line| line.strip.force_encoding("iso-8859-1").encode("utf-8") }
|
23
|
+
end
|
24
|
+
|
25
|
+
def io
|
26
|
+
open("|#{command} 2>/dev/null")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,17 +1,18 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
1
3
|
module GitStatistics
|
2
4
|
module Utilities
|
5
|
+
|
6
|
+
class NotInRepository < StandardError; end
|
7
|
+
|
3
8
|
def self.get_repository(path = Dir.pwd)
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
rescue
|
12
|
-
directory = directory.parent
|
13
|
-
end
|
14
|
-
end
|
9
|
+
ascender = Pathname.new(path).to_enum(:ascend)
|
10
|
+
repo_path = ascender.detect { |path| (path + '.git').exist? }
|
11
|
+
raise NotInRepository unless repo_path
|
12
|
+
Grit::Repo.new(repo_path.to_s)
|
13
|
+
rescue NotInRepository
|
14
|
+
Log.error "You must be within a Git project to run git-statistics."
|
15
|
+
exit 0
|
15
16
|
end
|
16
17
|
|
17
18
|
def self.max_length_in_list(list, max = nil)
|
@@ -22,10 +23,6 @@ module GitStatistics
|
|
22
23
|
max
|
23
24
|
end
|
24
25
|
|
25
|
-
def self.clean_string(string)
|
26
|
-
string.strip.force_encoding("iso-8859-1").encode("utf-8")
|
27
|
-
end
|
28
|
-
|
29
26
|
def self.split_old_new_file(old, new)
|
30
27
|
# Split the old and new chunks up (separted by the =>)
|
31
28
|
split_old = old.split('{')
|
@@ -84,11 +81,26 @@ module GitStatistics
|
|
84
81
|
end
|
85
82
|
|
86
83
|
def self.get_modified_time(file)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
time_at(
|
84
|
+
if os == :windows
|
85
|
+
raise "`stat` is not supported on the Windows operating system"
|
86
|
+
end
|
87
|
+
flags = os == :mac ? "-f %m" : "-c %Y"
|
88
|
+
time_at("stat #{flags} #{file}")
|
89
|
+
end
|
90
|
+
|
91
|
+
def os
|
92
|
+
case RbConfig::CONFIG['host_os']
|
93
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
94
|
+
:windows
|
95
|
+
when /darwin|mac os/
|
96
|
+
:mac
|
97
|
+
when /linux/
|
98
|
+
:linux
|
99
|
+
when /solaris|bsd/
|
100
|
+
:unix
|
101
|
+
else
|
102
|
+
:unknown
|
103
|
+
end
|
92
104
|
end
|
93
105
|
|
94
106
|
def self.time_at(cmd)
|
@@ -96,9 +108,7 @@ module GitStatistics
|
|
96
108
|
end
|
97
109
|
|
98
110
|
def self.number_of_matching_files(directory, pattern)
|
99
|
-
Dir.entries(directory)
|
100
|
-
.select { |file| file =~ pattern }
|
101
|
-
.size
|
111
|
+
Dir.entries(directory).grep(pattern).size
|
102
112
|
rescue SystemCallError
|
103
113
|
warn "No such directory #{File.expand_path(directory)}"
|
104
114
|
0
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include GitStatistics
|
3
|
+
|
4
|
+
describe Branches do
|
5
|
+
subject { described_class }
|
6
|
+
|
7
|
+
before do
|
8
|
+
subject.stub(:pipe) { fixture(branches) }
|
9
|
+
end
|
10
|
+
|
11
|
+
context "with many branches" do
|
12
|
+
let(:branches) {"git_many_branches.txt"}
|
13
|
+
its(:all) { should have(2).items }
|
14
|
+
its(:all) { should include "issue_2" }
|
15
|
+
its(:all) { should include "master" }
|
16
|
+
its(:current) { should == "issue_2" }
|
17
|
+
its(:detached?) { should be_false }
|
18
|
+
end
|
19
|
+
|
20
|
+
context "with zero branches" do
|
21
|
+
let(:branches) {"git_zero_branches.txt"}
|
22
|
+
its(:all) { should have(1).items }
|
23
|
+
its(:all) { should include "master" }
|
24
|
+
its(:current) { should == "master" }
|
25
|
+
its(:detached?) { should be_false }
|
26
|
+
end
|
27
|
+
|
28
|
+
context "with many branches in detached state" do
|
29
|
+
let(:branches) {"git_many_branches_detached_state.txt"}
|
30
|
+
its(:all) { should have(2).items }
|
31
|
+
its(:all) { should include "issue_2" }
|
32
|
+
its(:all) { should include "master" }
|
33
|
+
its(:current) { should == "(none)" }
|
34
|
+
its(:detached?) { should be_true }
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with zero branches in detached state" do
|
38
|
+
let(:branches) {"git_zero_branches_detached_state.txt"}
|
39
|
+
its(:all) { should have(1).items }
|
40
|
+
its(:all) { should include "master" }
|
41
|
+
its(:current) { should == "(none)" }
|
42
|
+
its(:detached?) { should be_true }
|
43
|
+
end
|
44
|
+
end
|
data/spec/collector_spec.rb
CHANGED
@@ -2,17 +2,14 @@ require 'spec_helper'
|
|
2
2
|
include GitStatistics
|
3
3
|
|
4
4
|
describe Collector do
|
5
|
-
let(:verbose) {false}
|
6
5
|
let(:limit) {100}
|
7
6
|
let(:fresh) {true}
|
8
7
|
let(:pretty) {false}
|
9
|
-
let(:collector) {Collector.new(
|
8
|
+
let(:collector) {Collector.new(limit, fresh, pretty)}
|
10
9
|
|
11
10
|
# Create buffer which is an array of cleaned lines
|
12
11
|
let(:buffer) {
|
13
|
-
fixture(fixture_file).
|
14
|
-
line.clean_for_authors
|
15
|
-
end
|
12
|
+
fixture(fixture_file).lines
|
16
13
|
}
|
17
14
|
|
18
15
|
describe "#collect" do
|
@@ -83,26 +80,8 @@ describe Collector do
|
|
83
80
|
end
|
84
81
|
end
|
85
82
|
|
86
|
-
describe "#collect_branches" do
|
87
|
-
let(:branches) {collector.collect_branches(fixture(fixture_file))}
|
88
|
-
|
89
|
-
context "with many branches" do
|
90
|
-
let(:fixture_file) {"git_many_branches.txt"}
|
91
|
-
it {branches.size.should == 2}
|
92
|
-
it {branches[0].should == "issue_2"}
|
93
|
-
it {branches[1].should == "master"}
|
94
|
-
end
|
95
|
-
|
96
|
-
context "with zero branches" do
|
97
|
-
let(:fixture_file) {"git_zero_branches.txt"}
|
98
|
-
it {branches.size.should == 1}
|
99
|
-
it {branches[0].should == "master"}
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
83
|
describe "#acquire_commit_data" do
|
104
|
-
let(:
|
105
|
-
let(:data) {collector.acquire_commit_data(input)}
|
84
|
+
let(:data) {collector.acquire_commit_data(buffer.first)}
|
106
85
|
|
107
86
|
context "no parent, first commit" do
|
108
87
|
let(:fixture_file) {"commit_buffer_information_first.txt"}
|
@@ -231,38 +210,38 @@ describe Collector do
|
|
231
210
|
end
|
232
211
|
|
233
212
|
describe "#fall_back_collect_commit" do
|
234
|
-
|
213
|
+
subject { collector.fall_back_collect_commit(sha) }
|
235
214
|
context "with valid sha" do
|
236
|
-
let(:fixture_file) {"commit_buffer_whole.txt"}
|
237
|
-
let(:sha) {"260bc61e2c42930d91f3503c5849b0a2351275cf"}
|
238
|
-
it {
|
215
|
+
let(:fixture_file) { "commit_buffer_whole.txt" }
|
216
|
+
let(:sha) { "260bc61e2c42930d91f3503c5849b0a2351275cf" }
|
217
|
+
it { should == buffer }
|
239
218
|
end
|
240
219
|
|
241
220
|
context "with invalid sha" do
|
242
|
-
let(:sha) {"111111aa111a11111a11aa11aaaa11a111111a11"}
|
243
|
-
it {
|
221
|
+
let(:sha) { "111111aa111a11111a11aa11aaaa11a111111a11" }
|
222
|
+
it { should be_empty }
|
244
223
|
end
|
245
224
|
end
|
246
225
|
|
247
226
|
describe "#get_blob" do
|
248
|
-
let(:sha) {"695b487432e8a1ede765b4e3efda088ab87a77f8"} # Commit within repository
|
249
|
-
|
227
|
+
let(:sha) { "695b487432e8a1ede765b4e3efda088ab87a77f8" } # Commit within repository
|
228
|
+
subject { collector.get_blob(sha, file) }
|
250
229
|
|
251
230
|
context "with valid blob" do
|
252
231
|
let(:file) {{:file => "Gemfile.lock"}}
|
253
|
-
it {
|
254
|
-
|
255
|
-
end
|
256
|
-
|
257
|
-
context "with invalid blob" do
|
258
|
-
let(:file) {{:file => "dir/nothing.rb"}}
|
259
|
-
it {blob.should.nil?}
|
232
|
+
it { should be_a Grit::Blob }
|
233
|
+
its(:name) { should == File.basename(file[:file]) }
|
260
234
|
end
|
261
235
|
|
262
236
|
context "with deleted file" do
|
263
237
|
let(:file) {{:file => "spec/collector_spec.rb"}}
|
264
|
-
it {
|
265
|
-
|
238
|
+
it { should be_a Grit::Blob }
|
239
|
+
its(:name) { should == File.basename(file[:file]) }
|
240
|
+
end
|
241
|
+
|
242
|
+
context "with invalid blob" do
|
243
|
+
let(:file) {{:file => "dir/nothing.rb"}}
|
244
|
+
it { should be_nil }
|
266
245
|
end
|
267
246
|
end
|
268
247
|
|
data/spec/commits_spec.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'fileutils'
|
2
3
|
include GitStatistics
|
3
4
|
|
4
5
|
describe Commits do
|
5
|
-
let(:verbose) {false}
|
6
6
|
let(:limit) {100}
|
7
7
|
let(:fresh) {true}
|
8
8
|
let(:pretty) {false}
|
9
|
-
let(:collector) {Collector.new(
|
9
|
+
let(:collector) {Collector.new(limit, fresh, pretty)}
|
10
10
|
|
11
11
|
let(:commits) {collector.commits}
|
12
12
|
|
13
13
|
let(:fixture_file) {"multiple_authors.json"}
|
14
|
-
let(:save_file) {collector.commits_path
|
14
|
+
let(:save_file) { File.join(collector.commits_path, "0.json") }
|
15
15
|
let(:email) {false}
|
16
16
|
let(:merge) {false}
|
17
17
|
let(:sort) {:commits}
|
@@ -21,56 +21,66 @@ describe Commits do
|
|
21
21
|
commits.author_top_n_type(sort)
|
22
22
|
end
|
23
23
|
|
24
|
+
describe "#files_in_path" do
|
25
|
+
let(:path) { '/tmp/example' }
|
26
|
+
subject { commits.files_in_path }
|
27
|
+
before do
|
28
|
+
FileUtils.mkdir_p(path)
|
29
|
+
Dir.chdir(path) do
|
30
|
+
FileUtils.touch '0.json'
|
31
|
+
FileUtils.touch '1.json'
|
32
|
+
end
|
33
|
+
commits.stub(:path) { path }
|
34
|
+
end
|
35
|
+
after do
|
36
|
+
FileUtils.rm_rf(path)
|
37
|
+
end
|
38
|
+
its(:count) { should == 2 }
|
39
|
+
it { should_not include '.' }
|
40
|
+
it { should_not include '..' }
|
41
|
+
end
|
42
|
+
|
24
43
|
describe "#flush_commits" do
|
25
|
-
let(:commits) {collector.commits.load(fixture(fixture_file))}
|
44
|
+
let(:commits) {collector.commits.load(fixture(fixture_file).file)}
|
45
|
+
|
46
|
+
def commit_size_changes_from(beginning, opts = {})
|
47
|
+
commits.size.should == beginning
|
48
|
+
commits.flush_commits(opts[:force] || false)
|
49
|
+
commits.size.should == opts[:to]
|
50
|
+
end
|
26
51
|
|
27
52
|
context "with commits exceeding limit" do
|
28
53
|
let(:limit) {2}
|
29
|
-
it
|
30
|
-
commits.size.should == 3
|
31
|
-
commits.flush_commits
|
32
|
-
commits.size.should == 0
|
33
|
-
end
|
54
|
+
it { commit_size_changes_from(3, to: 0) }
|
34
55
|
end
|
35
56
|
|
36
57
|
context "with commits equal to limit" do
|
37
58
|
let(:limit) {3}
|
38
|
-
it
|
39
|
-
commits.size.should == 3
|
40
|
-
commits.flush_commits
|
41
|
-
commits.size.should == 0
|
42
|
-
end
|
59
|
+
it { commit_size_changes_from(3, to: 0) }
|
43
60
|
end
|
44
61
|
|
45
62
|
context "with commits less than limit" do
|
46
63
|
let(:limit) {5}
|
47
|
-
it
|
48
|
-
commits.size.should == 3
|
49
|
-
commits.flush_commits
|
50
|
-
commits.size.should == 3
|
51
|
-
end
|
64
|
+
it { commit_size_changes_from(3, to: 3) }
|
52
65
|
end
|
53
66
|
|
54
67
|
context "with commits less than limit but forced" do
|
55
68
|
let(:limit) {5}
|
56
|
-
it
|
57
|
-
commits.size.should == 3
|
58
|
-
commits.flush_commits(true)
|
59
|
-
commits.size.should == 0
|
60
|
-
end
|
69
|
+
it { commit_size_changes_from(3, to: 0, force: true) }
|
61
70
|
end
|
62
71
|
end
|
63
72
|
|
64
73
|
describe "#process_commits" do
|
65
|
-
let(:commits) {collector.commits.load(fixture(fixture_file))}
|
74
|
+
let(:commits) {collector.commits.load(fixture(fixture_file).file)}
|
66
75
|
let(:type) {:author}
|
76
|
+
subject { commits.stats[author_name] }
|
77
|
+
|
78
|
+
before do
|
79
|
+
commits.process_commits(type, merge)
|
80
|
+
end
|
67
81
|
|
68
82
|
context "with merge" do
|
69
83
|
let(:merge) {true}
|
70
|
-
subject {
|
71
|
-
commits.process_commits(type, merge)
|
72
|
-
commits.stats[author_name]
|
73
|
-
}
|
74
84
|
|
75
85
|
context "on first author" do
|
76
86
|
let(:author_name) {"Kevin Jalbert"}
|
@@ -102,10 +112,6 @@ describe Commits do
|
|
102
112
|
|
103
113
|
context "without merge" do
|
104
114
|
let(:merge) {false}
|
105
|
-
subject {
|
106
|
-
commits.process_commits(type, merge)
|
107
|
-
commits.stats[author_name]
|
108
|
-
}
|
109
115
|
|
110
116
|
context "on first author" do
|
111
117
|
let(:author_name) {"Kevin Jalbert"}
|
@@ -136,11 +142,11 @@ describe Commits do
|
|
136
142
|
|
137
143
|
describe "#author_top_n_type" do
|
138
144
|
let(:sort) {:deletions}
|
145
|
+
subject {stats[author]}
|
139
146
|
|
140
147
|
context "with valid data" do
|
141
148
|
context "on first author" do
|
142
|
-
author
|
143
|
-
subject {stats[author]}
|
149
|
+
let(:author) { 'John Smith' }
|
144
150
|
it {stats.has_key?(author).should be_true}
|
145
151
|
it {subject[:commits].should == 1}
|
146
152
|
it {subject[:deletions].should == 16}
|
@@ -151,8 +157,7 @@ describe Commits do
|
|
151
157
|
end
|
152
158
|
|
153
159
|
context "on second author" do
|
154
|
-
author
|
155
|
-
subject {stats[author]}
|
160
|
+
let(:author) { "Kevin Jalbert" }
|
156
161
|
it {stats.has_key?(author).should be_true}
|
157
162
|
it {subject[:commits].should == 1}
|
158
163
|
it {subject[:additions].should == 73}
|
@@ -170,22 +175,22 @@ describe Commits do
|
|
170
175
|
|
171
176
|
context "with invalid type" do
|
172
177
|
let(:sort) {:wrong}
|
173
|
-
it {stats.should
|
178
|
+
it { stats.should be_nil }
|
174
179
|
end
|
175
180
|
|
176
181
|
context "with invalid data" do
|
177
182
|
let(:fixture_file) {nil}
|
178
|
-
it {stats.should
|
183
|
+
it { stats.should be_nil }
|
179
184
|
end
|
180
185
|
end
|
181
186
|
|
182
187
|
describe "#calculate_statistics" do
|
183
188
|
let(:fixture_file) {"single_author_pretty.json"}
|
189
|
+
subject {stats[author]}
|
184
190
|
|
185
191
|
context "with email" do
|
186
192
|
let(:email) {true}
|
187
|
-
author
|
188
|
-
subject {stats[author]}
|
193
|
+
let(:author) { "kevin.j.jalbert@gmail.com" }
|
189
194
|
|
190
195
|
it {stats.has_key?(author).should be_true}
|
191
196
|
it {subject[:commits].should == 1}
|
@@ -202,8 +207,7 @@ describe Commits do
|
|
202
207
|
|
203
208
|
context "with merge" do
|
204
209
|
let(:merge) {true}
|
205
|
-
author
|
206
|
-
subject {stats[author]}
|
210
|
+
let(:author) { 'Kevin Jalbert' }
|
207
211
|
|
208
212
|
it {stats.has_key?(author).should be_true}
|
209
213
|
it {subject[:commits].should == 2}
|
@@ -334,10 +338,10 @@ describe Commits do
|
|
334
338
|
let(:pretty) {true}
|
335
339
|
|
336
340
|
it do
|
337
|
-
commits.load(fixture(fixture_file))
|
341
|
+
commits.load(fixture(fixture_file).file)
|
338
342
|
commits.save("tmp.json", pretty)
|
339
343
|
|
340
|
-
same = FileUtils.compare_file("tmp.json", fixture(fixture_file))
|
344
|
+
same = FileUtils.compare_file("tmp.json", fixture(fixture_file).file)
|
341
345
|
FileUtils.remove_file("tmp.json")
|
342
346
|
|
343
347
|
same.should be_true
|
@@ -349,10 +353,10 @@ describe Commits do
|
|
349
353
|
let(:pretty) {false}
|
350
354
|
|
351
355
|
it do
|
352
|
-
commits.load(fixture(fixture_file))
|
356
|
+
commits.load(fixture(fixture_file).file)
|
353
357
|
commits.save("tmp.json", pretty)
|
354
358
|
|
355
|
-
same = FileUtils.compare_file("tmp.json", fixture(fixture_file))
|
359
|
+
same = FileUtils.compare_file("tmp.json", fixture(fixture_file).file)
|
356
360
|
FileUtils.remove_file("tmp.json")
|
357
361
|
|
358
362
|
same.should be_true
|