Class: BareTest::Persistence

Inherits:
Object
  • Object
show all
Defined in:
lib/baretest/persistence.rb

Overview

A simple file based storage. This is used to persist data between runs of baretest (caching data, keeping the last run’s states for filtering, etc.) The data is stored in ~/.baretest, per project. A file with the name pattern

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project_dir = nil, storage_dir = nil) ⇒ Persistence

Arguments:

project_dir

The directory of the project

storage_dir

The directory where this Persistence instance should store its data



66
67
68
69
70
71
72
73
74
75
76
# File 'lib/baretest/persistence.rb', line 66

def initialize(project_dir=nil, storage_dir=nil)
  @storage_dir = File.expand_path(storage_dir || self.class.storage_path)
  @project_dir = File.expand_path(project_dir || ".")
  @project_id  = self.class.determine_project_id(@project_dir)
  stat         = File.stat(@project_dir)
  store('project', {
    :project_directory        => @project_dir,
    :project_directory_inode  => stat.ino,
    :project_directory_device => stat.dev
  })
end

Instance Attribute Details

#project_dirObject (readonly)

The directory of the project this Persistence instance is attached to



57
58
59
# File 'lib/baretest/persistence.rb', line 57

def project_dir
  @project_dir
end

#project_idObject (readonly)

The id of the project this Persistence instance is attached to



60
61
62
# File 'lib/baretest/persistence.rb', line 60

def project_id
  @project_id
end

#storage_dirObject (readonly)

The directory this Persistence instance stores its data



54
55
56
# File 'lib/baretest/persistence.rb', line 54

def storage_dir
  @storage_dir
end

Class Method Details

.determine_project_id(project_dir) ⇒ Object

BareTest uses a file of the form ‘.baretest_id_*’ (where * is a 32 digits long hex) to uniquely identify a project. This ID is then used to associate stored data with the project.



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/baretest/persistence.rb', line 32

def self.determine_project_id(project_dir)
  found = Dir.glob("#{project_dir}/.baretest_id_*") { |path|
    break $1 if File.file?(path) && path =~ /id_([A-Fa-f0-9]{32})$/
  }
  unless found then
    found = UID.hex_uid
    File.open(".baretest_id_#{found}", "w") { |fh|
      # The content of this file is irrelevant, only its name. So lets
      # add a little bit of explaining text in case somebody wonders about
      # the purpose of this file.
      fh.write(
        "This file is used by baretest to find the persisted data in your ~/.baretest directory.\n" \
        "Deleting this file will result in orphaned persistence data.\n" \
        "See `baretest help reset`."
      )
    }
  end

  found
end

.storage_pathObject

The default storage path base (~/.baretest)



25
26
27
# File 'lib/baretest/persistence.rb', line 25

def self.storage_path
  File.expand_path('~/.baretest')
end

Instance Method Details

#clearObject

Remove all files that store state, cache things etc.



137
138
139
# File 'lib/baretest/persistence.rb', line 137

def clear
  delete('final_states')
end

#delete(filename) ⇒ Object

Deletes the file for the given filename.

filename

A relative path. Empty directories are recursively deleted up to (but without) Persistence#storage_dir. The path is relative to Persistence#storage_dir



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/baretest/persistence.rb', line 118

def delete(filename)
  raise "Invalid filename: #{filename}" if filename =~ %r{\A\.\./|/\.\./\z}
  project_storage_dir = "#{@storage_dir}/#{@project_id}"
  path                = "#{project_storage_dir}/#{filename}.yaml"

  File.delete(path)
  container = File.dirname(path)
  while container != project_storage_dir
    begin
      Dir.delete(container)
    rescue Errno::ENOTEMPTY
      break
    else
      container = File.dirname(container)
    end
  end
end

#read(filename, default = nil) ⇒ Object

Reads and deserializes the data in a given filename.

filename

A relative path. Directories are created on the fly if necessary. Must not be an absolute path. The path is relative to Persistence#storage_dir

default

The value to return in case the file does not exist. Alternatively you can pass a block that calculates the default.



101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/baretest/persistence.rb', line 101

def read(filename, default=nil)
  raise "Invalid filename: #{filename}" if filename =~ %r{\A\.\./|/\.\./\z}
  path = "#{@storage_dir}/#{@project_id}/#{filename}.yaml"

  if File.exist?(path)
    YAML.load_file(path)
  elsif block_given?
    yield
  else
    default
  end
end

#store(filename, data) ⇒ Object

Stores data to a file.

Arguments

filename

A relative path. Directories are created on the fly if necessary. Must not be an absolute path. The path is relative to Persistence#storage_dir

data

The data to store. Anything that can be serialized by YAML. This excludes IOs and Procs.



86
87
88
89
90
91
92
93
# File 'lib/baretest/persistence.rb', line 86

def store(filename, data)
  raise "Invalid filename: #{filename}" unless filename =~ %r{\A[A-Za-z0-9_-][A-Za-z0-9_-]*\z}
  dir = "#{@storage_dir}/#{@project_id}"
  FileUtils.mkdir_p(dir)
  File.open("#{dir}/#{filename}.yaml", "w") do |fh|
    fh.write(data.to_yaml)
  end
end