mnogootex 0.2.1 → 1.0.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.
- checksums.yaml +5 -5
- data/.gitignore +3 -0
- data/.rspec +0 -2
- data/.rubocop.yml +12 -0
- data/.travis.yml +20 -3
- data/CODE_OF_CONDUCT.md +1 -1
- data/Gemfile +4 -3
- data/Guardfile +56 -0
- data/README.md +154 -16
- data/Rakefile +25 -4
- data/exe/mnogootex +2 -92
- data/lib/mnogootex/cfg.rb +62 -0
- data/lib/mnogootex/cli.rb +79 -0
- data/lib/mnogootex/core_ext.rb +11 -0
- data/lib/mnogootex/defaults.yml +6 -0
- data/lib/mnogootex/job/logger.rb +53 -0
- data/lib/mnogootex/job/porter.rb +45 -0
- data/lib/mnogootex/job/runner.rb +42 -0
- data/lib/mnogootex/job/warden.rb +99 -0
- data/lib/mnogootex/log/level.rb +17 -0
- data/lib/mnogootex/log/levels.yml +29 -0
- data/lib/mnogootex/log/line.rb +14 -0
- data/lib/mnogootex/log/matcher.rb +17 -0
- data/lib/mnogootex/log/matchers.yml +205 -0
- data/lib/mnogootex/log/processor.rb +115 -0
- data/lib/mnogootex/log.rb +23 -0
- data/lib/mnogootex/mnogoo.sh +21 -0
- data/lib/mnogootex/utils.rb +26 -0
- data/lib/mnogootex/version.rb +3 -1
- data/lib/mnogootex.rb +4 -4
- data/mnogootex.gemspec +41 -18
- data/spec/mnogootex/cfg_spec.rb +54 -0
- data/spec/mnogootex/job/porter_spec.rb +140 -0
- data/spec/mnogootex/job/runner_spec.rb +74 -0
- data/spec/mnogootex/log/processor_spec.rb +203 -0
- data/spec/mnogootex/utils_spec.rb +52 -0
- data/spec/spec_helper.rb +124 -0
- data/tty.gif +0 -0
- metadata +182 -20
- data/.gitmodules +0 -3
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/lib/mnogootex/configuration.rb +0 -46
- data/lib/mnogootex/job.rb +0 -75
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'digest'
|
4
|
+
|
5
|
+
module Mnogootex
|
6
|
+
module Utils
|
7
|
+
def self.short_md5(input)
|
8
|
+
[Digest::MD5.digest(input)]. # get 16 bytes of MD5
|
9
|
+
pack('m0'). # pack them into 22+2 base64 bytes (w/o trailing newline)
|
10
|
+
tr('+/', '-_'). # make then url/path-safe
|
11
|
+
chomp('==') # drop last 2 padding bytes
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.humanize_bytes(size)
|
15
|
+
%w[b Kb Mb Gb Tb Pb Eb Zb Yb].reduce(size) do |magnitude, unit|
|
16
|
+
break "#{magnitude}#{unit}" if magnitude < 1024
|
17
|
+
magnitude / 1024
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.dir_size(mask)
|
22
|
+
Dir.glob(Pathname.new(mask).join('**', '*')).
|
23
|
+
map! { |f| Pathname.new(f).size }.inject(:+) || 0
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/mnogootex/version.rb
CHANGED
data/lib/mnogootex.rb
CHANGED
data/mnogootex.gemspec
CHANGED
@@ -1,30 +1,53 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
lib = File.expand_path(
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
-
require
|
5
|
+
require 'mnogootex/version'
|
6
6
|
|
7
|
-
Gem::Specification.new do |spec|
|
8
|
-
spec.name =
|
7
|
+
Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
|
8
|
+
spec.name = 'mnogootex'
|
9
9
|
spec.version = Mnogootex::VERSION
|
10
|
-
spec.authors = [
|
11
|
-
spec.email = [
|
10
|
+
spec.authors = ['Paolo Brasolin']
|
11
|
+
spec.email = ['paolo.brasolin@gmail.com']
|
12
12
|
|
13
|
-
spec.summary =
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
spec.summary = <<~SUMMARY.tr("\n", ' ').squeeze(' ').strip
|
14
|
+
Многоꙮтех (mnogootex) is a utility that parallelizes compilation
|
15
|
+
of a LaTeX document using different classes and offers a
|
16
|
+
meaningfully filtered output.
|
17
|
+
SUMMARY
|
18
|
+
|
19
|
+
spec.description = <<~DESCRIPTION.tr("\n", ' ').squeeze(' ').strip
|
20
|
+
Многоꙮтех (mnogootex) is a utility that parallelizes compilation
|
21
|
+
of a LaTeX document using different classes and offers a
|
22
|
+
meaningfully filtered output.
|
23
|
+
The motivating use case is maintaining a single preamble while
|
24
|
+
submitting a paper to many journals using their outdated or crummy
|
25
|
+
document classes.
|
26
|
+
DESCRIPTION
|
27
|
+
|
28
|
+
spec.homepage = 'https://github.com/tetrapharmakon/mnogootex'
|
29
|
+
spec.license = 'MIT'
|
17
30
|
|
18
31
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
19
|
-
f.match(%r{^(test|
|
32
|
+
f.match(%r{^(test|mwe)/})
|
20
33
|
end
|
21
|
-
spec.bindir =
|
34
|
+
spec.bindir = 'exe'
|
22
35
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
-
spec.require_paths = [
|
36
|
+
spec.require_paths = ['lib']
|
24
37
|
|
25
|
-
spec.add_development_dependency
|
26
|
-
spec.add_development_dependency
|
27
|
-
spec.add_development_dependency
|
38
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
39
|
+
spec.add_development_dependency 'byebug', '~> 10.0'
|
40
|
+
spec.add_development_dependency 'dry-inflector', '~> 0.1.1'
|
41
|
+
spec.add_development_dependency 'guard', '~> 2.14'
|
42
|
+
spec.add_development_dependency 'guard-rspec', '~> 4.7'
|
43
|
+
spec.add_development_dependency 'mutant', '~> 0.8.14'
|
44
|
+
spec.add_development_dependency 'mutant-rspec', '~> 0.8.14'
|
45
|
+
spec.add_development_dependency 'rake', '~> 10.4'
|
46
|
+
spec.add_development_dependency 'rspec', '~> 3.6'
|
47
|
+
spec.add_development_dependency 'rubocop', '~> 0.52.1'
|
48
|
+
spec.add_development_dependency 'simplecov', '~> 0.15.1'
|
49
|
+
spec.add_development_dependency 'yard', '~> 0.9.12'
|
28
50
|
|
29
|
-
spec.add_dependency
|
51
|
+
spec.add_dependency 'colorize', '~> 0.8.1'
|
52
|
+
spec.add_dependency 'thor', '~> 0.20.0'
|
30
53
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
require 'yaml'
|
6
|
+
require 'tmpdir'
|
7
|
+
|
8
|
+
require 'mnogootex/cfg'
|
9
|
+
|
10
|
+
describe Mnogootex::Cfg do
|
11
|
+
describe 'load_descending' do
|
12
|
+
context 'fake file structure' do
|
13
|
+
subject { described_class.load_descending(pathname: Pathname.new('foobar'), basename: 'cfg.yml') }
|
14
|
+
|
15
|
+
it 'raises an error on loading a fake path' do
|
16
|
+
expect { subject }.to raise_exception Errno::ENOENT
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'real file structure' do
|
21
|
+
subject { described_class.load_descending(pathname: tmp_dir.join('A', 'B'), basename: 'cfg.yml') }
|
22
|
+
let(:defaults) { { default: true, winner: 'default' } }
|
23
|
+
let(:tmp_dir) { Pathname.new(Dir.mktmpdir).join('mnogootex-test') }
|
24
|
+
|
25
|
+
before do
|
26
|
+
tmp_dir.join('A', 'B').mkpath
|
27
|
+
tmp_dir.join('cfg.yml').write('') # NOTE: empty file
|
28
|
+
tmp_dir.join('A', 'cfg.yml').write({ parent: true, winner: 'parent', merged: { deep: true } }.to_yaml)
|
29
|
+
tmp_dir.join('A', 'B', 'cfg.yml').write({ child: true, winner: 'child', merged: {} }.to_yaml)
|
30
|
+
end
|
31
|
+
|
32
|
+
after do
|
33
|
+
tmp_dir.rmtree
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'merges paths' do
|
37
|
+
expect(subject).to include(:parent)
|
38
|
+
expect(subject).to include(:child)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'merges shallowly' do
|
42
|
+
expect(subject[:merged]).to_not include(:deep)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'privileges deeper paths' do
|
46
|
+
expect(subject[:winner]).to eq('child')
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'privileges non defaults' do
|
50
|
+
expect(subject[:winner]).to_not eq('default')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'mnogootex/job/porter'
|
5
|
+
|
6
|
+
require 'mnogootex/utils'
|
7
|
+
|
8
|
+
describe Mnogootex::Job::Porter do
|
9
|
+
# NOTE: using mktmpdir instead of tmpdir allows parallelization
|
10
|
+
let(:test_dir) { Pathname.new(Dir.mktmpdir).join('mnogootex-test') }
|
11
|
+
before { test_dir.mkpath }
|
12
|
+
after { test_dir.rmtree }
|
13
|
+
|
14
|
+
describe '.new' do
|
15
|
+
it 'requires a source' do
|
16
|
+
expect { described_class.new hid: '1', source_path: nil }.to raise_exception(TypeError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'requires source to exist' do
|
20
|
+
expect { described_class.new hid: '1', source_path: 'foobar' }.to raise_exception(Errno::ENOENT)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'accepts string as source' do
|
24
|
+
expect { described_class.new hid: '1', source_path: test_dir.to_s }.to_not raise_exception
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'accepts pathname as source' do
|
28
|
+
expect { described_class.new hid: '1', source_path: test_dir }.to_not raise_exception
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#target_dir' do
|
33
|
+
before do
|
34
|
+
test_dir.join('a').mkpath
|
35
|
+
test_dir.join('b').mkpath
|
36
|
+
test_dir.join('a', 'main.file').write('')
|
37
|
+
test_dir.join('b', 'main.file').write('')
|
38
|
+
end
|
39
|
+
let(:porter_a1) { described_class.new hid: '1', source_path: test_dir.join('a', 'main.file') }
|
40
|
+
let(:porter_a2) { described_class.new hid: '2', source_path: test_dir.join('a', 'main.file') }
|
41
|
+
let(:porter_b1) { described_class.new hid: '1', source_path: test_dir.join('b', 'main.file') }
|
42
|
+
|
43
|
+
it 'has a deterministic location' do
|
44
|
+
hash = Mnogootex::Utils.short_md5(test_dir.join('a', 'main.file').realpath.to_s)
|
45
|
+
expect(porter_a1.target_dir.to_s).
|
46
|
+
to match(%r{\A.+/mnogootex/#{hash}/#{1}\z})
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'discriminates by hid' do
|
50
|
+
expect(porter_a1.target_dir).to_not eq(porter_a2.target_dir)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'discriminates by source' do
|
54
|
+
expect(porter_a1.target_dir).to_not eq(porter_b1.target_dir)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#target_path' do
|
59
|
+
let(:source_path) { test_dir.join('a', 'main.file') }
|
60
|
+
let(:porter) { described_class.new hid: '1', source_path: source_path }
|
61
|
+
|
62
|
+
before do
|
63
|
+
source_path.dirname.mkpath
|
64
|
+
source_path.write('')
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'rescopes source basename into target dirname' do
|
68
|
+
expect(porter.target_path.basename).to eq(source_path.basename)
|
69
|
+
expect(porter.target_path.dirname).to eq(porter.target_dir)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#provide' do
|
74
|
+
let(:source_path) { test_dir.join('A', 'main.file') }
|
75
|
+
|
76
|
+
before do
|
77
|
+
test_dir.join('A', 'B').mkpath
|
78
|
+
test_dir.join('A', 'main.file').write('')
|
79
|
+
test_dir.join('A', '.mnogootex.yml').write('')
|
80
|
+
test_dir.join('A', '.dotfile').write('')
|
81
|
+
test_dir.join('A', 'B', 'ancillary.file').write('')
|
82
|
+
end
|
83
|
+
|
84
|
+
subject { described_class.new hid: 'job_id', source_path: source_path }
|
85
|
+
|
86
|
+
it 'creates target directory' do
|
87
|
+
subject.provide
|
88
|
+
expect(subject.target_dir).to be_directory
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'ignores configuration file' do
|
92
|
+
test_dir.join('A', '.mnogootex.yml').unlink
|
93
|
+
subject.provide
|
94
|
+
expect(subject.target_dir.join('.mnogootex.yml')).to_not exist
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'creates link to source' do
|
98
|
+
subject.provide
|
99
|
+
expect(subject.target_dir.join('.mnogootex.src').readlink).to eq(source_path.realpath)
|
100
|
+
end
|
101
|
+
|
102
|
+
def relative_subtree(pathname)
|
103
|
+
Pathname.glob(pathname.join('**', '{.*,*}')).map do |child|
|
104
|
+
child.relative_path_from(pathname)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'copies all source files' do
|
109
|
+
subject.provide
|
110
|
+
subject.target_dir.join('.mnogootex.src').unlink
|
111
|
+
source_path.dirname.join('.mnogootex.yml').unlink
|
112
|
+
# NOTE: unlinking so comparison is easier to write
|
113
|
+
expect(relative_subtree(source_path.dirname)).
|
114
|
+
to eq(relative_subtree(subject.target_dir))
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#clobber' do
|
119
|
+
let(:source_path) { test_dir.join('A', 'main.file') }
|
120
|
+
|
121
|
+
before do
|
122
|
+
test_dir.join('A', 'B').mkpath
|
123
|
+
test_dir.join('A', 'main.file').write('')
|
124
|
+
end
|
125
|
+
|
126
|
+
subject { described_class.new hid: 'job_id', source_path: source_path }
|
127
|
+
|
128
|
+
it 'cleans up target dir if it exists' do
|
129
|
+
subject.provide
|
130
|
+
expect(subject.target_dir).to exist
|
131
|
+
subject.clobber
|
132
|
+
expect(subject.target_dir).to_not exist
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'does nothing if target dir does not exist' do
|
136
|
+
expect(subject.target_dir).to_not exist
|
137
|
+
expect { subject.clobber }.to_not raise_exception
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'mnogootex/job/runner'
|
5
|
+
|
6
|
+
require 'tmpdir'
|
7
|
+
|
8
|
+
describe Mnogootex::Job::Runner do
|
9
|
+
let(:test_dir) { Pathname.new(Dir.mktmpdir) }
|
10
|
+
before { test_dir.mkpath }
|
11
|
+
after { test_dir.rmtree }
|
12
|
+
|
13
|
+
it 'executes commandline in given dir' do
|
14
|
+
runner = described_class.new(cl: 'pwd', chdir: test_dir)
|
15
|
+
expect(runner).to be_successful
|
16
|
+
expect(runner.log_lines.join.chomp).to eq(test_dir.realpath.to_s)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#alive?' do
|
20
|
+
it 'is true if thread is running' do
|
21
|
+
runner = described_class.new(cl: 'sleep 0.05', chdir: test_dir)
|
22
|
+
expect(runner).to be_alive
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'is false if thread is dead' do
|
26
|
+
runner = described_class.new(cl: ':', chdir: test_dir)
|
27
|
+
sleep 0.05
|
28
|
+
expect(runner).to_not be_alive
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#successful?' do
|
33
|
+
it 'is true on zero exit status' do
|
34
|
+
runner = described_class.new(cl: 'exit 0', chdir: test_dir)
|
35
|
+
expect(runner).to be_successful
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'is false on nonzero exit status' do
|
39
|
+
runner = described_class.new(cl: 'exit 1', chdir: test_dir)
|
40
|
+
expect(runner).to_not be_successful
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#count_lines' do
|
45
|
+
let!(:lns) { <<~SHELL }
|
46
|
+
lns () { i=1; while [ "$i" -le $1 ]; do echo $i; i=$(( i + 1 )); done };
|
47
|
+
SHELL
|
48
|
+
|
49
|
+
context 'dead process' do
|
50
|
+
subject { described_class.new(cl: "#{lns} lns 3", chdir: test_dir) }
|
51
|
+
|
52
|
+
before do
|
53
|
+
subject.successful? # waits on threads
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'plateaus immediately at log lines count' do
|
57
|
+
[3, 3].each { |n| expect(subject.count_lines).to eq(n) }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'alive process' do
|
62
|
+
subject { described_class.new(cl: "#{lns} lns 3; sleep 0.20", chdir: test_dir) }
|
63
|
+
|
64
|
+
before do
|
65
|
+
subject
|
66
|
+
sleep 0.02
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'unitarily increases from zero then plateaus at current line count' do
|
70
|
+
[0, 1, 2, 3, 3, 3].each { |n| expect(subject.count_lines).to eq(n) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
require 'mnogootex/log/processor'
|
8
|
+
|
9
|
+
describe Mnogootex::Log::Processor do
|
10
|
+
describe '.strings_to_lines!' do
|
11
|
+
it 'converts strings into lines' do
|
12
|
+
strings = "foo\nbar\n".lines
|
13
|
+
|
14
|
+
described_class.strings_to_lines! strings
|
15
|
+
|
16
|
+
expect(strings).to eq [
|
17
|
+
Mnogootex::Log::Line.new('foo'),
|
18
|
+
Mnogootex::Log::Line.new('bar')
|
19
|
+
]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '.tag_lines!' do
|
24
|
+
it 'tags using short matchers' do
|
25
|
+
lines = [Mnogootex::Log::Line.new,
|
26
|
+
Mnogootex::Log::Line.new('foo'),
|
27
|
+
Mnogootex::Log::Line.new]
|
28
|
+
|
29
|
+
matchers = [Mnogootex::Log::Matcher.new(/foo/, :foo)]
|
30
|
+
|
31
|
+
described_class.tag_lines! lines, matchers: matchers
|
32
|
+
|
33
|
+
expect(lines.map(&:level)).to eq([nil, :foo, nil])
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'tags using long matchers' do
|
37
|
+
lines = [Mnogootex::Log::Line.new,
|
38
|
+
Mnogootex::Log::Line.new,
|
39
|
+
Mnogootex::Log::Line.new('foo'),
|
40
|
+
Mnogootex::Log::Line.new,
|
41
|
+
Mnogootex::Log::Line.new,
|
42
|
+
Mnogootex::Log::Line.new,
|
43
|
+
Mnogootex::Log::Line.new]
|
44
|
+
|
45
|
+
matchers = [Mnogootex::Log::Matcher.new(/foo/, :foo, 3)]
|
46
|
+
|
47
|
+
described_class.tag_lines! lines, matchers: matchers
|
48
|
+
|
49
|
+
expect(lines.map(&:level)).to eq([nil, nil, :foo, :foo, :foo, nil, nil])
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'tags abiding to matchers order' do
|
53
|
+
lines = [Mnogootex::Log::Line.new('foo'),
|
54
|
+
Mnogootex::Log::Line.new('something')]
|
55
|
+
|
56
|
+
matchers = [Mnogootex::Log::Matcher.new(/foo/, :winner, 1),
|
57
|
+
Mnogootex::Log::Matcher.new(/foo/, :loser, 1),
|
58
|
+
Mnogootex::Log::Matcher.new(//, :anything, 1)]
|
59
|
+
|
60
|
+
described_class.tag_lines! lines, matchers: matchers
|
61
|
+
|
62
|
+
expect(lines.map(&:level)).to eq(%i[winner anything])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '.filter_lines!' do
|
67
|
+
it 'raises on line with unknown level' do
|
68
|
+
lines = [Mnogootex::Log::Line.new('foo', :bar)]
|
69
|
+
|
70
|
+
levels = { foo: Mnogootex::Log::Level.new(0) }
|
71
|
+
|
72
|
+
expect do
|
73
|
+
described_class.filter_lines! lines,
|
74
|
+
levels: levels,
|
75
|
+
min_level: :foo
|
76
|
+
end.to raise_exception KeyError
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'raises on unknown min level' do
|
80
|
+
lines = [Mnogootex::Log::Line.new('foo', :foo)]
|
81
|
+
|
82
|
+
levels = { foo: Mnogootex::Log::Level.new(0) }
|
83
|
+
|
84
|
+
expect do
|
85
|
+
described_class.filter_lines! lines,
|
86
|
+
levels: levels,
|
87
|
+
min_level: :bar
|
88
|
+
end.to raise_exception KeyError
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'filters tagged lines by minimum level' do
|
92
|
+
lines = [Mnogootex::Log::Line.new('foo', :foo),
|
93
|
+
Mnogootex::Log::Line.new('bar', :bar),
|
94
|
+
Mnogootex::Log::Line.new('baz', :baz)]
|
95
|
+
|
96
|
+
levels = { foo: Mnogootex::Log::Level.new(0),
|
97
|
+
bar: Mnogootex::Log::Level.new(1),
|
98
|
+
baz: Mnogootex::Log::Level.new(2) }
|
99
|
+
|
100
|
+
described_class.filter_lines! lines,
|
101
|
+
levels: levels,
|
102
|
+
min_level: :bar
|
103
|
+
|
104
|
+
expect(lines.map(&:text)).to eq(%w[bar baz])
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '.colorize_lines!' do
|
109
|
+
it 'raises on unknown min level' do
|
110
|
+
lines = [Mnogootex::Log::Line.new('foo', :foo)]
|
111
|
+
|
112
|
+
levels = { bar: Mnogootex::Log::Level.new(0) }
|
113
|
+
|
114
|
+
expect do
|
115
|
+
described_class.colorize_lines! lines, levels: levels
|
116
|
+
end.to raise_exception KeyError
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'colorizes tagged lines' do
|
120
|
+
lines = [Mnogootex::Log::Line.new('foo', :foo),
|
121
|
+
Mnogootex::Log::Line.new('bar', :bar),
|
122
|
+
Mnogootex::Log::Line.new('baz', :baz)]
|
123
|
+
|
124
|
+
levels = { foo: Mnogootex::Log::Level.new(0, :foo, :red),
|
125
|
+
bar: Mnogootex::Log::Level.new(1, :bar, :green),
|
126
|
+
baz: Mnogootex::Log::Level.new(2, :baz, :blue) }
|
127
|
+
|
128
|
+
described_class.colorize_lines! lines, levels: levels
|
129
|
+
|
130
|
+
expect(lines.map(&:text)).to eq %W{\e[0;31;49mfoo\e[0m
|
131
|
+
\e[0;32;49mbar\e[0m
|
132
|
+
\e[0;34;49mbaz\e[0m}
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe '.render_lines!' do
|
137
|
+
it 'renders lines to indented terminated strings' do
|
138
|
+
lines = [Mnogootex::Log::Line.new('foo'),
|
139
|
+
Mnogootex::Log::Line.new('bar'),
|
140
|
+
Mnogootex::Log::Line.new('baz')]
|
141
|
+
|
142
|
+
described_class.render_lines! lines, indent_width: 2
|
143
|
+
|
144
|
+
expect(lines).to eq [" foo\n", " bar\n", " baz\n"]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe '#run' do
|
149
|
+
log = <<~LOG
|
150
|
+
This is generic irrelevant information.
|
151
|
+
Hey, I'm warning you, dude. Stuff is gonna get bad.
|
152
|
+
This is also a known irrelevant information flood...
|
153
|
+
... telling you that you'd better pay attention to warnings.
|
154
|
+
I warned you, dude. Here's an ERROR. :(
|
155
|
+
LOG
|
156
|
+
|
157
|
+
levels = { trace: Mnogootex::Log::Level.new(0, :trace),
|
158
|
+
warning: Mnogootex::Log::Level.new(1, :warning, :yellow),
|
159
|
+
error: Mnogootex::Log::Level.new(2, :error, :red) }
|
160
|
+
|
161
|
+
matchers = [Mnogootex::Log::Matcher.new(/error/i, :error, 1),
|
162
|
+
Mnogootex::Log::Matcher.new(/warning/i, :warning, 1),
|
163
|
+
Mnogootex::Log::Matcher.new(/flood/, :trace, 2),
|
164
|
+
Mnogootex::Log::Matcher.new(//, :trace, 1)]
|
165
|
+
|
166
|
+
it 'can be initilized and run' do
|
167
|
+
my_processor = described_class.new matchers: matchers,
|
168
|
+
levels: levels,
|
169
|
+
min_level: :warning,
|
170
|
+
colorize: true,
|
171
|
+
indent_width: 4
|
172
|
+
|
173
|
+
expect(my_processor.run(log.lines)).to eq(
|
174
|
+
[" \e[0;33;49mHey, I'm warning you, dude. Stuff is gonna get bad.\e[0m\n",
|
175
|
+
" \e[0;31;49mI warned you, dude. Here's an ERROR. :(\e[0m\n"]
|
176
|
+
)
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'can disable colorization' do
|
180
|
+
my_processor = described_class.new matchers: matchers,
|
181
|
+
levels: levels,
|
182
|
+
min_level: :warning,
|
183
|
+
colorize: false,
|
184
|
+
indent_width: 4
|
185
|
+
|
186
|
+
expect(my_processor.run(log.lines)).to eq(
|
187
|
+
[" Hey, I'm warning you, dude. Stuff is gonna get bad.\n",
|
188
|
+
" I warned you, dude. Here's an ERROR. :(\n"]
|
189
|
+
)
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'does not mutate given lines' do
|
193
|
+
my_processor = described_class.new matchers: matchers,
|
194
|
+
levels: levels,
|
195
|
+
min_level: :warning,
|
196
|
+
colorize: true,
|
197
|
+
indent_width: 4
|
198
|
+
|
199
|
+
log_lines = log.lines
|
200
|
+
expect { my_processor.run(log_lines) }.to_not(change { log_lines })
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
require 'mnogootex/utils'
|
8
|
+
|
9
|
+
describe Mnogootex::Utils do
|
10
|
+
describe '.short_md5' do
|
11
|
+
it 'gives expected hash for empty string' do
|
12
|
+
expect(described_class.short_md5('')).to eq('1B2M2Y8AsgTpgAmY7PhCfg')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'gives an url/path-safe hash' do
|
16
|
+
expect(described_class.short_md5('Knuth')).to eq('KyIs0ZIec5GkG7_G-clv6Q')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '.humanize_bytes' do
|
21
|
+
it 'rounds to smaller unit' do
|
22
|
+
expect(described_class.humanize_bytes(1023 * 1024)).to eq('1023Kb')
|
23
|
+
expect(described_class.humanize_bytes(1024 * 1024)).to eq('1Mb')
|
24
|
+
expect(described_class.humanize_bytes(1025 * 1024)).to eq('1Mb')
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'covers a reasonable scale' do
|
28
|
+
%w[b Kb Mb Gb Tb Pb Eb Zb Yb].each_with_index do |unit, index|
|
29
|
+
expect(described_class.humanize_bytes((2**10)**index)).to eq("1#{unit}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '.dir_size' do
|
35
|
+
let(:tmpdir) { Pathname.new(Dir.mktmpdir) }
|
36
|
+
before { tmpdir.mkpath }
|
37
|
+
after { tmpdir.rmtree }
|
38
|
+
|
39
|
+
it 'measures an empty dir' do
|
40
|
+
expect(described_class.dir_size(tmpdir)).to eq(0)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'measures a subtree' do
|
44
|
+
tmpdir.join('foo').write('foo' * 100)
|
45
|
+
tmpdir.join('bar').write('bar' * 200)
|
46
|
+
tmpdir.join('baz').mkpath
|
47
|
+
tmpdir.join('baz', 'qux').write('qux' * 300)
|
48
|
+
# NOTE: dir size is fs dependent, so let's not care about that
|
49
|
+
expect(described_class.dir_size(tmpdir.to_s)).to eq(300 + 600 + tmpdir.join('baz').size + 900)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|