baleen 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +29 -0
  6. data/Rakefile +1 -0
  7. data/baleen.gemspec +33 -0
  8. data/bin/baleen +17 -0
  9. data/lib/baleen/client.rb +45 -0
  10. data/lib/baleen/container.rb +49 -0
  11. data/lib/baleen/job.rb +19 -0
  12. data/lib/baleen/messages/messages.rb +48 -0
  13. data/lib/baleen/messages/request.rb +31 -0
  14. data/lib/baleen/messages/response.rb +29 -0
  15. data/lib/baleen/runner.rb +119 -0
  16. data/lib/baleen/server.rb +75 -0
  17. data/lib/baleen/task.rb +44 -0
  18. data/lib/baleen/utils/colored_puts.rb +17 -0
  19. data/lib/baleen/version.rb +3 -0
  20. data/lib/baleen.rb +5 -0
  21. data/poc/.gitignore +15 -0
  22. data/poc/Gemfile +49 -0
  23. data/poc/README.rdoc +261 -0
  24. data/poc/Rakefile +7 -0
  25. data/poc/app/assets/images/rails.png +0 -0
  26. data/poc/app/assets/javascripts/application.js +15 -0
  27. data/poc/app/assets/stylesheets/application.css +13 -0
  28. data/poc/app/controllers/application_controller.rb +3 -0
  29. data/poc/app/helpers/application_helper.rb +2 -0
  30. data/poc/app/mailers/.gitkeep +0 -0
  31. data/poc/app/models/.gitkeep +0 -0
  32. data/poc/app/models/ar_profile.rb +3 -0
  33. data/poc/app/views/layouts/application.html.erb +14 -0
  34. data/poc/config/application.rb +62 -0
  35. data/poc/config/boot.rb +6 -0
  36. data/poc/config/cucumber.yml +8 -0
  37. data/poc/config/database.yml +28 -0
  38. data/poc/config/environment.rb +5 -0
  39. data/poc/config/environments/development.rb +37 -0
  40. data/poc/config/environments/production.rb +67 -0
  41. data/poc/config/environments/test.rb +37 -0
  42. data/poc/config/initializers/backtrace_silencers.rb +7 -0
  43. data/poc/config/initializers/inflections.rb +15 -0
  44. data/poc/config/initializers/mime_types.rb +5 -0
  45. data/poc/config/initializers/secret_token.rb +7 -0
  46. data/poc/config/initializers/session_store.rb +8 -0
  47. data/poc/config/initializers/wrap_parameters.rb +14 -0
  48. data/poc/config/locales/en.yml +5 -0
  49. data/poc/config/routes.rb +58 -0
  50. data/poc/config.ru +4 -0
  51. data/poc/db/.gitkeep +0 -0
  52. data/poc/db/migrate/20130914144710_create_ar_profiles.rb +11 -0
  53. data/poc/db/schema.rb +24 -0
  54. data/poc/db/seeds.rb +7 -0
  55. data/poc/features/cpu_bound.feature +3 -0
  56. data/poc/features/io_bound.feature +3 -0
  57. data/poc/features/step_definitions/fake_test_steps.rb +25 -0
  58. data/poc/features/support/env.rb +60 -0
  59. data/poc/features/support/ruby_prof_cucumber.rb +15 -0
  60. data/poc/features/t1.feature +12 -0
  61. data/poc/features/t10.feature +12 -0
  62. data/poc/features/t2.feature +12 -0
  63. data/poc/features/t3.feature +12 -0
  64. data/poc/features/t4.feature +12 -0
  65. data/poc/features/t5.feature +12 -0
  66. data/poc/features/t6.feature +12 -0
  67. data/poc/features/t7.feature +12 -0
  68. data/poc/features/t8.feature +12 -0
  69. data/poc/features/t9.feature +12 -0
  70. data/poc/lib/assets/.gitkeep +0 -0
  71. data/poc/lib/tasks/.gitkeep +0 -0
  72. data/poc/lib/tasks/cucumber.rake +65 -0
  73. data/poc/public/404.html +26 -0
  74. data/poc/public/422.html +26 -0
  75. data/poc/public/500.html +25 -0
  76. data/poc/public/favicon.ico +0 -0
  77. data/poc/public/index.html +241 -0
  78. data/poc/public/robots.txt +5 -0
  79. data/poc/script/cucumber +10 -0
  80. data/poc/script/rails +6 -0
  81. data/poc/test/fixtures/.gitkeep +0 -0
  82. data/poc/test/fixtures/ar_profiles.yml +11 -0
  83. data/poc/test/functional/.gitkeep +0 -0
  84. data/poc/test/integration/.gitkeep +0 -0
  85. data/poc/test/performance/browsing_test.rb +12 -0
  86. data/poc/test/test_helper.rb +13 -0
  87. data/poc/test/unit/.gitkeep +0 -0
  88. data/poc/test/unit/ar_profile_test.rb +7 -0
  89. data/poc/vendor/assets/javascripts/.gitkeep +0 -0
  90. data/poc/vendor/assets/stylesheets/.gitkeep +0 -0
  91. data/poc/vendor/plugins/.gitkeep +0 -0
  92. metadata +262 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fc7921b0867d0a73836d50b562b35e7cf9692d7f
4
+ data.tar.gz: 43d2f2f70271efdd4407247e55e0341ffeb689ae
5
+ SHA512:
6
+ metadata.gz: 2c9f0dab84c29a08f66fe481fa110c26ec6a8bf06b20826077c29cc2333696d7693209ea569d0a587a899144fa5bcefb42be29b4ff2824d4b06be2e68e6d8b78
7
+ data.tar.gz: 8ec1a88eac0e64c7402f9a2414c88bb9352a08fda0e68b8fdd3c0d90064e311a331c573f1bdfe4e3550d849f133fc5fe46a80949709e718e20c811e337b7c67a
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ poc/db/.*sqlite3
19
+ poc/log/*
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in baleen.gemspec
4
+ gemspec
5
+
6
+ gem 'docker-client', path: File.expand_path("/Users/kimh/git/docker-ruby", __FILE__)
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Kim, Hirokuni
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Baleen
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'baleen'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install baleen
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/baleen.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'baleen/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "baleen"
8
+ spec.version = Baleen::VERSION
9
+ spec.authors = ["Kim, Hirokuni"]
10
+ spec.email = ["kimh@kvh.co.jp"]
11
+ spec.description = %q{Ballen allows you to run standard ruby tests in parallel and isolated environment}
12
+ spec.summary = %q{Parallel and container-based test runner powered by Docker}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "pry"
24
+ spec.add_development_dependency "awesome_print"
25
+ spec.add_development_dependency "interactive_editor"
26
+
27
+ spec.add_runtime_dependency 'celluloid'
28
+ spec.add_runtime_dependency 'celluloid-io'
29
+ spec.add_runtime_dependency 'colorize'
30
+ spec.add_runtime_dependency 'thor'
31
+
32
+ end
33
+
data/bin/baleen ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "thor"
4
+
5
+ class BaleenCommand < Thor
6
+
7
+ desc "cucumber", "Running cucumber features"
8
+ option :image, :required => true
9
+ option :work_dir, :default => "./"
10
+ option :files, :default => "features"
11
+ option :before_command, :default => nil
12
+ def cucumber
13
+ puts options[:image]
14
+ end
15
+ end
16
+
17
+ BaleenCommand.start
@@ -0,0 +1,45 @@
1
+ require 'celluloid/io'
2
+ require 'celluloid/autostart'
3
+
4
+ module Baleen
5
+ class Client
6
+ include Celluloid::IO
7
+ finalizer :close
8
+
9
+ def initialize(host, port=12345)
10
+ @socket = TCPSocket.open(host, port)
11
+ @result = "running"
12
+ async.response
13
+ end
14
+
15
+ def request(request)
16
+ @socket.puts(request.to_json)
17
+ end
18
+
19
+ def response
20
+ loop { break if handle_response(@socket.gets) }
21
+ end
22
+
23
+ def close
24
+ @socket.close if @socket
25
+ info "connection closed"
26
+
27
+ rescue IOError; nil
28
+ end
29
+
30
+ def handle_response(response)
31
+ if response.nil?
32
+ raise RuntimeError, 'Connection closed by server'
33
+ end
34
+
35
+ info "Got response"
36
+ @result = Baleen::Message::Decoder.new(response).decode
37
+ true
38
+ end
39
+
40
+ def result
41
+ @result
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,49 @@
1
+ module Baleen
2
+ module Container
3
+
4
+ class DockerClient
5
+ Result = Struct.new("Result", :status_code, :container_id, :log)
6
+
7
+ def initialize(containers)
8
+ @containers = containers
9
+ end
10
+
11
+ def create_container(params)
12
+ @container = @containers.create([params.shell, params.opt, params.commands], params.image)
13
+ end
14
+
15
+ def start_container
16
+ id = @container["Id"]
17
+
18
+ info "Start container #{id}"
19
+ @containers.start(id)
20
+ @containers.wait(id)
21
+ info "Finish container #{id}"
22
+ end
23
+
24
+ def result
25
+ id = @container["Id"]
26
+ rst = @containers.show(id)
27
+ log = @containers.logs(id)
28
+
29
+ Result.new(
30
+ rst["State"]["ExitCode"],
31
+ rst["ID"],
32
+ log
33
+ )
34
+ end
35
+
36
+ def kill_all
37
+ @containers.list.each do |container|
38
+ @containers.kill(container["Id"])
39
+ end
40
+ end
41
+
42
+ def status
43
+ info @containers.list
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
data/lib/baleen/job.rb ADDED
@@ -0,0 +1,19 @@
1
+ module Baleen
2
+ class Job
3
+
4
+ def initialize(client, msg)
5
+ @client = client
6
+ @msg = msg
7
+ end
8
+
9
+ def start
10
+ @client.request(@msg.params)
11
+ end
12
+
13
+ def result
14
+ @client.result
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,48 @@
1
+ require "json"
2
+
3
+ module Baleen
4
+ module Message
5
+
6
+ class Decoder
7
+
8
+ def initialize(json_string)
9
+ @params = JSON.parse(json_string)
10
+ end
11
+
12
+ def decode
13
+ klass = @params.delete "klass"
14
+ Object.const_get(klass).new(symbolize_keys(@params))
15
+ end
16
+
17
+ private
18
+
19
+ def symbolize_keys(hash)
20
+ hash.inject({}){|new_hash, key_value|
21
+ key, value = key_value
22
+ value = symbolize_keys(value) if value.is_a?(Hash)
23
+ new_hash[key.to_sym] = value
24
+ new_hash
25
+ }
26
+ end
27
+ end
28
+
29
+ class Base
30
+ attr_accessor :json_msg
31
+
32
+ def initialize
33
+ @params = {}
34
+ @params[:klass] = self.class.to_s
35
+ end
36
+
37
+ def params
38
+ @params
39
+ end
40
+
41
+ def to_json
42
+ @params.to_json
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,31 @@
1
+ require "json"
2
+
3
+ module Baleen
4
+ module Message
5
+ module Request
6
+
7
+ class ClientDisconnect < Base
8
+ def initialize(opt = {}); super() end
9
+ end
10
+
11
+ class PingPong < Base
12
+ def initialize(opt = {}); super() end
13
+ end
14
+
15
+ class Cucumber < Base
16
+ def initialize(image: "kimh/baleen-poc", work_dir: "./", files: "./features", shell: "/bin/bash", opt: "-c", exe: "bundle exec cucumber", before_command: nil, command: nil)
17
+ super()
18
+ @params[:image] = image
19
+ @params[:shell] = shell
20
+ @params[:opt] = opt
21
+ @params[:work_dir] = work_dir
22
+ @params[:files] = files
23
+ @params[:exe] = exe
24
+ @params[:before_command] = before_command
25
+ @params[:command] = command
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ module Baleen
2
+ module Message
3
+ module Response
4
+
5
+ class JobComplete < Base
6
+ def initialize(opt = {})
7
+ super()
8
+ @params[:status_code] = opt[:status_code]
9
+ @params[:log ] = opt[:log]
10
+ @params[:container_id ] = opt[:log]
11
+ end
12
+
13
+ def status_code; @params[:status_code] end
14
+ def log; @params[:log] end
15
+ def container_id; @params[:container_id] end
16
+ end
17
+
18
+ class RunnerFinish < Base
19
+ def initialize(opt = {})
20
+ super()
21
+ @params[:status] = opt[:status]
22
+ end
23
+
24
+ def status; @params[:status] end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,119 @@
1
+ module Baleen
2
+
3
+ class DockerParam
4
+ def initialize(params)
5
+ @params = params
6
+ end
7
+
8
+ def method_missing(name, *args)
9
+ DockerParam.class_eval{
10
+ define_method "#{name}" do
11
+ @params[name.to_sym]
12
+ end
13
+
14
+ define_method "#{name}=" do |*args|
15
+ @params[name.to_sym] = args.first
16
+ end
17
+ }
18
+ send(name)
19
+ end
20
+
21
+ def commands
22
+ %{
23
+ #{@params[:before_command]}
24
+ cd #{@params[:work_dir]}
25
+ #{command}
26
+ }
27
+ end
28
+
29
+ def command
30
+ @params[:command] ||= %{#{@params[:exe]} #{@params[:files]}}
31
+ end
32
+
33
+ def command=(c)
34
+ @params[:command] = c
35
+ end
36
+
37
+ def dup
38
+ copy_params = @params.dup
39
+ Object.const_get(self.class.to_s).new(copy_params)
40
+ end
41
+ end
42
+
43
+ class RunnerManager
44
+ include Celluloid::IO
45
+
46
+ CONCURRENCY=2
47
+
48
+ def initialize(containers, socket, msg)
49
+ @containers = containers
50
+ @socket = socket
51
+ @queue = []
52
+ @params = DockerParam.new(msg.params)
53
+ end
54
+
55
+ def run
56
+ create_runners.each do |runners|
57
+ @queue = runners
58
+ @queue.each do |runner|
59
+ runner.async.run
60
+ end
61
+ loop {break if monitor_runners}
62
+ end
63
+
64
+ msg = Message::Response::RunnerFinish.new(status: "done")
65
+ @socket.puts(msg.to_json)
66
+ end
67
+
68
+ private
69
+
70
+ def create_runners
71
+ target_files.map {|file|
72
+ @params.files = file
73
+ Runner.new(@containers, @params)
74
+ }.each_slice(CONCURRENCY).map {|r| r}
75
+ end
76
+
77
+ def target_files
78
+ params = @params.dup
79
+ params.command = %{find #{params.files} | grep "\\.feature"}
80
+ runner = Runner.new(@containers, params)
81
+ runner.run
82
+ runner.status.log.split("\n")
83
+ end
84
+
85
+ def monitor_runners
86
+ @queue.all?{ |r| r.status }
87
+ end
88
+ end
89
+
90
+ class Runner
91
+ include Celluloid::IO
92
+
93
+ attr_reader :status
94
+
95
+ def initialize(containers, params)
96
+ @docker_client = Container::DockerClient.new(containers)
97
+ @status = nil
98
+ @params = params
99
+ end
100
+
101
+ def run
102
+ start_runner do |result|
103
+ @status = Message::Response::JobComplete.new(
104
+ status_code: result.status_code,
105
+ container_id: result.container_id,
106
+ log: result.log
107
+ )
108
+ end
109
+ sleep 0.1 # Stop a moment until RunnerManager checks the status
110
+ end
111
+
112
+ def start_runner
113
+ @docker_client.create_container(@params)
114
+ @docker_client.start_container
115
+ yield( @docker_client.result )
116
+ end
117
+
118
+ end
119
+ end
@@ -0,0 +1,75 @@
1
+ require 'docker'
2
+ require 'awesome_print'
3
+ require 'socket'
4
+ require 'celluloid/io'
5
+ require 'celluloid/autostart'
6
+ require 'json'
7
+
8
+ module Baleen
9
+
10
+ class Server
11
+ include Celluloid::IO
12
+ finalizer :shutdown
13
+
14
+ attr_accessor :containers
15
+
16
+ def initialize(host, port: 4243)
17
+ @base_url = "http://#{host}:#{port}"
18
+ @docker = Docker::API.new(base_url: @base_url)
19
+ @containers = @docker.containers
20
+ @server = TCPServer.new("127.0.0.1", 12345)
21
+ async.run
22
+ end
23
+
24
+ def run
25
+ loop { async.handle_connection @server.accept }
26
+ end
27
+
28
+ def shutdown
29
+ @server.close if @server
30
+ end
31
+
32
+ def handle_connection(socket)
33
+ loop { handle_request(socket) }
34
+
35
+ rescue Exception => ex
36
+ case ex
37
+ when IOError; nil # when trying to close already closed socket
38
+ else
39
+ warn "Unknown exception occured"
40
+ ap ex
41
+ raise ex
42
+ end
43
+ end
44
+
45
+ def handle_request(socket)
46
+ message = socket.gets
47
+
48
+ if message.nil?
49
+ socket.close
50
+ return
51
+ end
52
+ msg = parse_request(message)
53
+
54
+ case msg
55
+ when Message::Request::PingPong
56
+ socket.puts "pong"
57
+ when Message::Request::ClientDisconnect
58
+ socket.close
59
+ when Message::Request::Cucumber
60
+ manager = RunnerManager.new(@containers, socket, msg)
61
+ manager.run
62
+ else
63
+ warn "Received unknown request"
64
+ ap msg
65
+ end
66
+ end
67
+
68
+ def parse_request(message)
69
+ #info "Got request from client"
70
+ #ap message
71
+ Baleen::Message::Decoder.new(message).decode
72
+ end
73
+ end
74
+
75
+ end
@@ -0,0 +1,44 @@
1
+ module Baleen
2
+ module Task
3
+
4
+ class Command
5
+ def initialize(work_dir, files, bundler)
6
+ @work_dir = work_dir
7
+ @files = files
8
+ @bundler = bundler
9
+ @before = []
10
+ build_default_arg
11
+ end
12
+
13
+ def before(commands=nil)
14
+ commands = sanitize_and_tokenize(commands) if commands
15
+ commands ? @before = commands : @before
16
+ end
17
+
18
+ def commands
19
+ @before ? @before + @args : @args
20
+ end
21
+
22
+ def <<(arg)
23
+ @args << arg
24
+ end
25
+
26
+ private
27
+
28
+ def build_default_arg
29
+ exe = @bundler ? "bundle exec cucumber" : "cucumber"
30
+ @args = ["cd #{@work_dir}", "#{exe} #{@files}"]
31
+ end
32
+
33
+ def sanitize_and_tokenize(arg)
34
+ # sanitize
35
+ arg = arg.strip.gsub(/^ */, '').gsub(/\n+/, "\n")
36
+ # tokenize
37
+ arg = arg.gsub(";", "\n")
38
+ arg.split("\n")
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+
@@ -0,0 +1,17 @@
1
+ require "colorize"
2
+
3
+ module Kernel
4
+ private
5
+
6
+ def info(msg)
7
+ puts msg.green
8
+ end
9
+
10
+ def warn(msg)
11
+ puts msg.yellow
12
+ end
13
+
14
+ def error(msg)
15
+ puts msg.red
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module Baleen
2
+ VERSION = "0.0.2"
3
+ end
data/lib/baleen.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "baleen/version"
2
+
3
+ module Baleen
4
+ # Your code goes here...
5
+ end
data/poc/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ # See http://help.github.com/ignore-files/ for more about ignoring files.
2
+ #
3
+ # If you find yourself ignoring temporary files generated by your text editor
4
+ # or operating system, you probably want to add a global ignore instead:
5
+ # git config --global core.excludesfile ~/.gitignore_global
6
+
7
+ # Ignore bundler config
8
+ /.bundle
9
+
10
+ # Ignore the default SQLite database.
11
+ /db/*.sqlite3
12
+
13
+ # Ignore all logfiles and tempfiles.
14
+ /log/*.log
15
+ /tmp