opticon 0.0.1 → 0.0.2

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.
data/CHANGELOG.txt CHANGED
@@ -0,0 +1,13 @@
1
+ === 0.0.2 :: 2007-11-15
2
+
3
+ * A series of tests on a service can now be specified as a block to the
4
+ Service#test method.
5
+ * All failures generated inside a test block are all batched together
6
+ into one notification.
7
+ * Opticon test scripts can now be run with the "-v" switch to verbosely
8
+ display what the script is doing.
9
+ * Failure messages are now a bit more readable for non-techies.
10
+
11
+ === 0.0.1 :: 2007-02-21
12
+
13
+ * First public release.
data/Manifest.txt CHANGED
@@ -1,7 +1,6 @@
1
1
  Rakefile
2
2
  README.txt
3
3
  CHANGELOG.txt
4
- History.txt
5
4
  Manifest.txt
6
5
  README.txt
7
6
  Rakefile
@@ -17,8 +16,10 @@ lib/opticon/service.rb
17
16
  lib/opticon/tester.rb
18
17
  lib/opticon/version.rb
19
18
  setup.rb
19
+ test/opticon/email_notifier_test.rb
20
20
  test/opticon/http_test.rb
21
21
  test/opticon/mailer_test.rb
22
+ test/opticon/service_test.rb
22
23
  test/opticon/tester_test.rb
23
24
  test/opticon_test.rb
24
- test/test_helper.rb
25
+ test/test_helper.rb
data/README.txt CHANGED
@@ -1,4 +1,4 @@
1
- Opticon is a no-nonsense utility for monitoring HTTP-based services (websites).
1
+ Opticon is a no-nonsense utility for monitoring HTTP-based services (i.e. websites).
2
2
 
3
3
  When triggered, opticon connects to a list of URIs and checks to make sure that
4
4
  they pass some arbitrary test, such as responding with a non-error HTTP response
data/examples/sample.rb CHANGED
@@ -1,24 +1,38 @@
1
1
  #!/usr/bin/env ruby
2
- require 'rubygems'; gem 'opticon'; require 'opticon'
2
+ require 'rubygems'
3
3
 
4
- # Where notifications will be emailed
5
- SEND_TO = "sample@nowhere.foo"
4
+ begin
5
+ gem 'opticon'
6
+ rescue Gem::LoadError
7
+ # try to load local copy if gem is not available
8
+ $: << 'lib'
9
+ end
10
+
11
+ require 'opticon'
6
12
 
13
+ # SEND_TO determines where notifications will be emailed.
14
+ #
7
15
  # If you want to send to multiple mailboxes, you can specify an array of
8
16
  # addresses like so:
9
17
  #
10
18
  # SEND_TO = ['robert@nowhere.foo', 'sally@nowhere.foo']
11
19
 
12
- # Email notifications will show up as coming from this address.
13
- FROM = "opticon@nowhere.foo"
20
+ SEND_TO = "sample@nowhere.foo"
14
21
 
22
+
23
+ # Email notifications will show up as coming from the FROM address.
24
+ #
15
25
  # You may have to set the FROM value to be a real email address (e.g. your
16
26
  # email address). Otherwise your mail server may decide to drop Opticon's
17
27
  # notification messages.
18
28
 
29
+ FROM = "opticon@nowhere.foo"
30
+
31
+
19
32
  # You don't have to change anything on this next line. This configures the
20
33
  # default notification behaviour for Opticon, which is to email failures to the
21
34
  # addresses you configured above.
35
+
22
36
  Opticon.default_notifiers = Opticon::Notifier::Email.new(SEND_TO, :from => FROM)
23
37
 
24
38
  # You may need to configure the Mailer with your mail server info. Here's how to
@@ -34,6 +48,7 @@ Opticon.default_notifiers = Opticon::Notifier::Email.new(SEND_TO, :from => FROM)
34
48
  # See http://api.rubyonrails.com/classes/ActionMailer/Base.html for more details
35
49
  # on setting up the mailer.
36
50
 
51
+
37
52
  # Now you can set up some tests. Note that this is just plain Ruby code, formatted
38
53
  # a bit strangely (note the dot at the end of each line -- we are calling the test()
39
54
  # method on the URI string, and then on the result of each successive test).
@@ -47,11 +62,22 @@ Opticon.default_notifiers = Opticon::Notifier::Email.new(SEND_TO, :from => FROM)
47
62
 
48
63
  "http://google.com".
49
64
  # Google will try to redirect to http://www.google.com/
50
- test(:responds_with_code, 302)
65
+ test(:responds_with_code, 301)
51
66
  # Note that currently there is no test for checking where you are being
52
67
  # redirected to. This functionality will be added in the future.
53
68
 
54
69
 
70
+ # Alternatively, you can pack your tests into a a block (this is now the
71
+ # preferred method):
72
+
73
+ "http://www.yahoo.com/".test do
74
+ # check that the HTTP response code is in the 200 range
75
+ test(:responds_with_code, :success)
76
+ # check that the page includes the string "Privacy Policy"
77
+ test(:response_body_contains, "Privacy Policy")
78
+ end
79
+
80
+
55
81
  # For a comprehensive guide on configuring and using Opticon, please see:
56
82
  #
57
83
  # http://code.google.com/p/opticon/wiki/ConfiguringOpticon
@@ -23,7 +23,7 @@ module Opticon
23
23
 
24
24
  class ResponseCodeTestFailure < Base
25
25
  def failure_message
26
- "Expected response code #{condition.inspect} but got '#{response.message}' (#{response.code})"
26
+ "The service did not respond as expected; expected response code was #{condition.inspect} but got #{response.code.inspect} (#{response.message.inspect})"
27
27
  end
28
28
  end
29
29
 
@@ -32,7 +32,7 @@ module Opticon
32
32
  if exception
33
33
  return exception.message
34
34
  else
35
- "Content did not include the expected #{condition.class} #{condition.inspect}"
35
+ "Page content did not include the expected #{condition.class} #{condition.inspect}"
36
36
  end
37
37
  end
38
38
  end
@@ -6,15 +6,26 @@ require 'action_mailer'
6
6
  module Opticon
7
7
  class Mailer < ::ActionMailer::Base
8
8
  # we use sendmail by default since it might just work out of the box
9
- # (:smtp, which is the normal default, definetly won't work without manual configuration)
9
+ # (:smtp which is the normal default definitely won't work without manual configuration)
10
10
  self.delivery_method = :sendmail
11
11
  self.template_root = File.dirname(File.expand_path(__FILE__))+'/..'
12
12
 
13
- def failure_notification(failure, recipients, from)
13
+ def failure_notification(service, message, recipients, from)
14
+ subject = "HTTP Service Failure: #{service}"
15
+
16
+ if ARGV.include?("-v")
17
+ puts "Mailing notification:"
18
+ puts " TO: #{recipients.inspect}"
19
+ puts " SUBJECT: #{subject}"
20
+ puts " FROM: #{from}"
21
+ puts " SERVICE: #{service}"
22
+ puts " MESSAGE: #{message}"
23
+ end
24
+
14
25
  self.recipients recipients
15
26
  self.from from
16
- subject "HTTP Service Failure: #{failure.uri}"
17
- body :failure => failure
27
+ self.subject subject
28
+ self.body :service => service, :message => message
18
29
  end
19
30
  end
20
31
  end
@@ -1,5 +1,5 @@
1
- Service at '<%= @failure.uri %>' failed:
1
+ Service at '<%= @service %>' failed one or more Opticon tests:
2
2
 
3
- <%= @failure.failure_message %>
3
+ <%= @message %>
4
4
 
5
5
  (Generated by Opticon on host '<%= ENV['HOSTNAME'] %>' at <%= Time.now %>)
@@ -1,17 +1,70 @@
1
1
  require 'opticon/mailer'
2
+ require 'pstore'
2
3
 
3
4
  module Opticon
4
- module Notifier
5
+ module Notifier
6
+ # Send failure notifications via email to the given list of recipients.
7
+ #
8
+ # To set options, configure Opticon::Mailer the same way you
9
+ # would any other ActionMailer. For example, to send via SMTP:
10
+ #
11
+ # Opticon::Mailer.delivery_method = :smtp
12
+ #
13
+ # Opticon::Mailer.server_settings = {
14
+ # :address => "mail.nowhere.foo",
15
+ # :domain => "nowhere.foo",
16
+ # :authentication => :login,
17
+ # :user_name => "mail_user",
18
+ # :password => "topsecret"
19
+ # }
20
+ #
5
21
  class Email
6
22
  attr_accessor :recipients, :from
7
23
 
24
+ # The same notification will not be resent over and over again unless
25
+ # this is set to true.
26
+ @@resend = false
27
+
8
28
  def initialize(recipients, options = {:from => "opticon@#{ENV['HOSTNAME']}"})
9
29
  @recipients = recipients
10
30
  @from = options[:from]
11
31
  end
12
32
 
13
- def notify(failure)
14
- Opticon::Mailer.deliver_failure_notification(failure, recipients, from)
33
+ def notify(failures)
34
+ failures = [failures] unless failures.kind_of? Array
35
+
36
+ failures_by_uri = {}
37
+ failures.each do |f|
38
+ failures_by_uri[f.uri] ||= []
39
+ failures_by_uri[f.uri] << f
40
+ end
41
+
42
+ if ARGV.include?("-v")
43
+ puts "Notifying #{recipients.inspect} about #{failures.size} failures:"
44
+ failures.each{|f| puts " #{f.failure_message}"}
45
+ end
46
+
47
+ failures_by_uri.each do |uri, failures|
48
+ Opticon::Mailer.deliver_failure_notification(
49
+ uri,
50
+ failures.collect{|f| f.failure_message}.join("\n"),
51
+ recipients, from
52
+ )
53
+ end
54
+ end
55
+ end
56
+
57
+ # Dummy notifier used in testing. It doesn't do anything other than store
58
+ # the error_messages given to notify in the object's notifications attribute.
59
+ class Dummy
60
+ attr_accessor :notifications
61
+
62
+ def initialize()
63
+ @notifications = []
64
+ end
65
+
66
+ def notify(failures)
67
+ @notifications << failures
15
68
  end
16
69
  end
17
70
  end
@@ -21,47 +21,82 @@ module Opticon
21
21
  @uri.to_s
22
22
  end
23
23
 
24
- def test(tester, *args)
25
- case tester
26
- when :responds_with_code, 'responds_with_code'
27
- tester = Opticon::Tester::ResponseCodeTester.new
28
- when :response_body_contains, 'response_body_contains'
29
- tester = Opticon::Tester::ContentTester.new
30
- when Class
31
- tester = tester.new
32
- when Tester::Base
33
- tester = tester
24
+ def test(*args, &block)
25
+ if block
26
+ @prev_batch_notify = @batch_notify
27
+ @batch_notify = true
28
+ instance_eval(&block)
29
+ send_batch_notification
30
+ @batch_notify = @prev_batch_notify
34
31
  else
35
- begin
36
- tester_class = "Opitcon::Tester::#{tester.to_s}".constantize
37
- tester = tester_class.new
38
- rescue NameError
39
- bad_tester = true
32
+ tester_type = args[0]
33
+ condition = args[1]
34
+ options = args[2]
35
+
36
+ case tester_type
37
+ when :responds_with_code, 'responds_with_code'
38
+ tester = Opticon::Tester::ResponseCodeTester.new
39
+ when :response_body_contains, 'response_body_contains'
40
+ tester = Opticon::Tester::ContentTester.new
41
+ when Class
42
+ tester = tester.new
43
+ when Tester::Base
44
+ tester = tester
45
+ else
46
+ begin
47
+ tester_class = "Opitcon::Tester::#{tester.to_s}".constantize
48
+ tester = tester_class.new
49
+ rescue NameError
50
+ bad_tester = true
51
+ end
52
+
53
+ if bad_tester or !tester.respond_to? :test
54
+ raise ArgumentError, "'#{tester}' is not a valid tester. The parameter must be: 'responds_with_code', 'response_body_contains', or"+
55
+ " an Object or Class that responds to a 'test' method."
56
+ end
40
57
  end
41
58
 
42
- if bad_tester or !tester.respond_to? :test
43
- raise ArgumentError, "'#{tester}' is not a valid tester. The parameter must be: 'responds_with_code', 'response_body_contains', or"+
44
- " an Object or Class that responds to a 'test' method."
59
+ tester.uri = @uri
60
+
61
+ @failures ||= []
62
+
63
+ debug = "#{@uri} #{tester_type} #{condition.inspect}?"
64
+
65
+ if tester.test(condition)
66
+ puts "#{debug} ==> PASSED!" if ARGV.include?("-v")
67
+ else
68
+ puts "#{debug} ==> FAILED!" if ARGV.include?("-v")
69
+ notifiers.each {|n| n.notify(tester.failure)} unless @batch_notify
70
+ @failures << tester.failure
45
71
  end
72
+
73
+ # return self to allow for chaining test calls
74
+ return self
46
75
  end
47
-
48
- tester.uri = @uri
49
-
50
- @failures = []
51
- unless tester.test(*args)
52
- notifiers.each {|n| n.notify(tester.failure)}
53
- @failures << tester.failure
76
+ end
77
+
78
+ def send_batch_notification
79
+ notifiers.each {|n| n.notify(@failures)} unless @failures.empty?
80
+ end
81
+
82
+ def failures?
83
+ if @failures.nil?
84
+ raise "Test has not yet been run."
85
+ else
86
+ not @failures.empty?
54
87
  end
55
-
56
- # return self to allow for test chaining
57
- return self
58
88
  end
59
89
  end
60
90
  end
61
91
 
62
- # this makes it possible to treat strings as Service objects
92
+ # this makes it possible to use Strings like Service objects
63
93
  class String
64
- def test(tester, *args)
65
- Opticon::Service.new(to_s).test(tester, *args)
94
+ def method_missing(method, *args, &block)
95
+ service.send(method, *args, &block)
96
+ end
97
+
98
+ private
99
+ def service
100
+ @service ||= Opticon::Service.new(to_s)
66
101
  end
67
102
  end
@@ -9,19 +9,22 @@ module Opticon
9
9
 
10
10
  include Opticon::HTTP
11
11
 
12
- def test(*args)
12
+ # Wrapper for calling the test method.
13
+ # Generally you'll want to invoke this instead of invoking test directly.
14
+ def run(condition)
13
15
  begin
14
- test_without_exception_handling(*args)
16
+ test(condition)
15
17
  rescue SocketError, TimeoutError, Net::HTTPError, Errno::ECONNREFUSED
16
- @failure = Opticon::Failure::ConnectionFailure.new(uri, args, nil)
18
+ @failure = Opticon::Failure::ConnectionFailure.new(uri, condition, nil)
17
19
  @failure.exception = $!
18
20
  false
19
21
  end
20
22
  end
21
23
  end
22
-
24
+
25
+ # Tests that the service responds with some given HTTP status code.
23
26
  class ResponseCodeTester < Base
24
- def test_without_exception_handling(condition)
27
+ def test(condition)
25
28
  case condition
26
29
  when :ok
27
30
  responds_with_code(200)
@@ -79,8 +82,10 @@ module Opticon
79
82
  end
80
83
  end
81
84
 
85
+ # Tests that the service responds with content that matches some given
86
+ # string or regular expression.
82
87
  class ContentTester < Base
83
- def test_without_exception_handling(condition)
88
+ def test(condition)
84
89
  unless (200..206).include?(response.code.to_i)
85
90
  @failure = Opticon::Failure::ContentTestFailure.new(uri, condition, response)
86
91
  if (300..307).include?(response.code.to_i)
@@ -2,7 +2,7 @@ module Opticon #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- TINY = 1
5
+ TINY = 2
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -0,0 +1,8 @@
1
+ require File.dirname(__FILE__) + '/../test_helper.rb'
2
+
3
+ class Opticon::ServiceTest < Test::Unit::TestCase
4
+
5
+ def test
6
+ end
7
+
8
+ end
@@ -17,7 +17,7 @@ class Opticon::HTTPTest < Test::Unit::TestCase
17
17
  end
18
18
 
19
19
  def test_failure_notification
20
- email = Opticon::Mailer.create_failure_notification(@failure, "test@test.ruby", "opticon@test.ruby")
20
+ email = Opticon::Mailer.create_failure_notification(@failure.uri, @failure.failure_message, "test@test.ruby", "opticon@test.ruby")
21
21
  assert_match(@failure.uri, email.subject)
22
22
  assert_equal("test@test.ruby", email.to.first)
23
23
  assert_match(@failure.uri, email.body)
@@ -0,0 +1,62 @@
1
+ require File.dirname(__FILE__) + '/../test_helper.rb'
2
+
3
+ class Opticon::ServiceTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ Opticon.default_notifiers = Opticon::Notifier::Dummy.new
7
+ end
8
+
9
+ def test_initialize
10
+ uri = "http://www.yahoo.com/"
11
+ s = Opticon::Service.new(uri)
12
+
13
+ assert_equal Opticon.default_notifiers, s.notifiers
14
+ assert_equal uri, s.to_s
15
+ end
16
+
17
+ def test_test
18
+ uri = "http://www.yahoo.com/"
19
+ s = Opticon::Service.new(uri)
20
+
21
+ assert !s.test(:responds_with_code, 200).failures?
22
+ assert s.test(:responds_with_code, 400).failures?
23
+ assert_equal 1, s.notifiers.first.notifications.size
24
+ assert_kind_of Opticon::Failure::ResponseCodeTestFailure, s.notifiers.first.notifications.first
25
+ end
26
+
27
+ def test_string_convenience
28
+ uri = "http://www.yahoo.com/"
29
+
30
+ assert !uri.test(:responds_with_code, 200).failures?
31
+ assert uri.test(:responds_with_code, 400).failures?
32
+ end
33
+
34
+ def test_test_block
35
+ uri = "http://www.yahoo.com/"
36
+ s = Opticon::Service.new(uri)
37
+
38
+ s.test do
39
+ test(:responds_with_code, 200)
40
+ test(:responds_with_code, 400)
41
+ end
42
+
43
+ assert s.failures?
44
+ assert_equal 1, s.notifiers.first.notifications.size
45
+ assert_kind_of Opticon::Failure::ResponseCodeTestFailure, s.notifiers.first.notifications.first.first
46
+ end
47
+
48
+ def test_test_block_on_string
49
+ uri = "http://www.yahoo.com/"
50
+
51
+ uri.test do
52
+ test(:responds_with_code, 200)
53
+ test(:responds_with_code, 400)
54
+ end
55
+
56
+ assert uri.failures?
57
+ assert_equal 1, uri.notifiers.first.notifications.size
58
+
59
+ assert_kind_of Opticon::Failure::ResponseCodeTestFailure, uri.notifiers.first.notifications.first.first
60
+ end
61
+
62
+ end
@@ -9,18 +9,18 @@ class Opticon::HTTPTest < Test::Unit::TestCase
9
9
  tester = Opticon::Tester::ResponseCodeTester.new
10
10
  tester.uri = "http://www.yahoo.com"
11
11
 
12
- assert tester.test(:success)
13
- assert tester.test(200)
14
- assert tester.test([200, 302])
15
- assert !tester.test(:server_error)
12
+ assert tester.run(:success)
13
+ assert tester.run(200)
14
+ assert tester.run([200, 302])
15
+ assert !tester.run(:server_error)
16
16
  end
17
17
 
18
18
  def test_response_code_tester_failure
19
19
  tester = Opticon::Tester::ResponseCodeTester.new
20
20
  tester.uri = "http://www.yahoo.com/alskjdflaksjflkasjfklsTHIS_PAGE_DOESNT_EXIST"
21
21
 
22
- assert tester.test(404)
23
- assert !tester.test(:success)
22
+ assert tester.run(404)
23
+ assert !tester.run(:success)
24
24
  assert_kind_of Opticon::Failure::ResponseCodeTestFailure, tester.failure
25
25
  assert_equal 404.to_s, tester.failure.response.code.to_s
26
26
  end
@@ -29,13 +29,13 @@ class Opticon::HTTPTest < Test::Unit::TestCase
29
29
  tester = Opticon::Tester::ResponseCodeTester.new
30
30
  tester.uri = "http://there.is.no.such.server.foobarblah/test"
31
31
 
32
- assert !tester.test(:success)
32
+ assert !tester.run(:success)
33
33
  assert_kind_of Opticon::Failure::ConnectionFailure, tester.failure
34
34
 
35
35
  tester = Opticon::Tester::ResponseCodeTester.new
36
36
  tester.uri = "http://localhost:65489789"
37
37
 
38
- assert !tester.test(:success)
38
+ assert !tester.run(:success)
39
39
  assert_kind_of Opticon::Failure::ConnectionFailure, tester.failure
40
40
  end
41
41
 
@@ -43,11 +43,11 @@ class Opticon::HTTPTest < Test::Unit::TestCase
43
43
  tester = Opticon::Tester::ContentTester.new
44
44
 
45
45
  tester.uri = "http://code.google.com/p/opticon/"
46
- assert tester.test("matt.zukowski"), tester.failure
46
+ assert tester.run("matt.zukowski"), tester.failure
47
47
  assert_nil tester.failure
48
48
 
49
49
  bad_string = "THIS STRING DOESNT EXIST IN OPTICON's GOOGLE CODE PAGE!!!"
50
- assert !tester.test(bad_string), tester.failure
50
+ assert !tester.run(bad_string), tester.failure
51
51
  assert_equal bad_string, tester.failure.condition
52
52
  end
53
53
 
@@ -55,11 +55,11 @@ class Opticon::HTTPTest < Test::Unit::TestCase
55
55
  tester = Opticon::Tester::ContentTester.new
56
56
 
57
57
  tester.uri = "http://code.google.com/p/opticon/"
58
- assert tester.test(/gnu general public license [23]\.[0-9]/i), tester.failure
58
+ assert tester.run(/gnu general public license v[0-9]/i), tester.failure
59
59
  assert_nil tester.failure
60
60
 
61
61
  bad_regexp = /klasjfkljasdlkfjasklfjslkdfj/
62
- assert !tester.test(bad_regexp), tester.failure
62
+ assert !tester.run(bad_regexp), tester.failure
63
63
  assert_equal bad_regexp, tester.failure.condition
64
64
  end
65
65
 
@@ -68,10 +68,9 @@ class Opticon::HTTPTest < Test::Unit::TestCase
68
68
 
69
69
  tester.uri = "http://google.com/"
70
70
  # google will try to redirect to http://www.google.com/
71
- assert_equal 302, tester.response.code.to_i
72
- assert !tester.test("Google Search"), tester.failure
71
+ assert_equal 301, tester.response.code.to_i
72
+ assert !tester.run("Google Search"), tester.failure
73
73
  assert_equal "http://www.google.com/", tester.failure.response['location']
74
74
  end
75
75
 
76
-
77
76
  end
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.0
2
+ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: opticon
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.1
7
- date: 2007-02-20 00:00:00 -05:00
6
+ version: 0.0.2
7
+ date: 2007-11-15 00:00:00 -05:00
8
8
  summary: Peace of mind through automated monitoring of your HTTP services.
9
9
  require_paths:
10
10
  - lib
@@ -32,7 +32,6 @@ files:
32
32
  - Rakefile
33
33
  - README.txt
34
34
  - CHANGELOG.txt
35
- - History.txt
36
35
  - Manifest.txt
37
36
  - examples/sample.rb
38
37
  - lib/opticon.rb
@@ -46,15 +45,19 @@ files:
46
45
  - lib/opticon/tester.rb
47
46
  - lib/opticon/version.rb
48
47
  - setup.rb
48
+ - test/opticon/email_notifier_test.rb
49
49
  - test/opticon/http_test.rb
50
50
  - test/opticon/mailer_test.rb
51
+ - test/opticon/service_test.rb
51
52
  - test/opticon/tester_test.rb
52
53
  - test/opticon_test.rb
53
54
  - test/test_helper.rb
54
55
  test_files:
55
56
  - test/opticon_test.rb
57
+ - test/opticon/service_test.rb
56
58
  - test/opticon/tester_test.rb
57
59
  - test/opticon/mailer_test.rb
60
+ - test/opticon/email_notifier_test.rb
58
61
  - test/opticon/http_test.rb
59
62
  rdoc_options: []
60
63
 
data/History.txt DELETED
File without changes