config location ruby and using rake for unit test - ruby

Learning Ruby, my Ruby app directory structure follows the convention
with lib/ and test/
in my root directory I have a authentication config file, that I read from one of the classes in lib/. It is read as File.open('../myconf').
When testing with Rake, the file open doesn't work since the working directory is the root, not lib/ or test/.
In order to solve this, I have two questions:
is it possible, and should I specify rake working directory to test/ ?
should I use different file discovery method? Although I prefer convention over config.
lib/A.rb
class A
def openFile
if File.exists?('../auth.conf')
f = File.open('../auth.conf','r')
...
else
at_exit { puts "Missing auth.conf file" }
exit
end
end
test/testopenfile.rb
require_relative '../lib/A'
require 'test/unit'
class TestSetup < Test::Unit::TestCase
def test_credentials
a = A.new
a.openFile #error
...
end
end
Trying to invoke with Rake. I did setup a task to copy the auth.conf to the test directory, but turns out that the working dir is above test/.
> rake
cp auth.conf test/
/.../.rvm/rubies/ruby-1.9.3-p448/bin/ruby test/testsetup.rb
Missing auth.conf file
Rakefile
task :default => [:copyauth,:test]
desc "Copy auth.conf to test dir"
task :copyauth do
sh "cp auth.conf test/"
end
desc "Test"
task :test do
ruby "test/testsetup.rb"
end

You're probably getting that error because you're running rake from the projects root directory, which means that the current working directory will be set to that directory. This probably means that the call to File.open("../auth.conf") will start looking one directory up from your current working directory.
Try specifying the absolute path to the config file, for example something like this:
class A
def open_file
path = File.join(File.dirname(__FILE__), "..", "auth.conf")
if File.exists?(path)
f = File.open(path,'r')
# do stuff...
else
at_exit { puts "Missing auth.conf file" }
exit
end
end
Btw, I took the liberty of changing openFile -> open_file, since that's more consistent with ruby coding conventions.

I recommend using File.expand_path method for that. You can evaluate auth.conf file location based on __FILE__(current file - lib/a.rb in your case) or Rails.root depending on what you need.
def open_file
filename = File.expand_path("../auth.conf", __FILE__) # => 'lib/auth.conf'
if File.exists?(filename)
f = File.open(filename,'r')
...
else
at_exit { puts "Missing auth.conf file" }
exit
end
end

Related

Accessing the PWD inside a thor task

In my main thor file, I call this code
script.rb
# this works
current_dir = Dir.getwd
# this changes directory into the tasks
Dir.chdir(#{pwd}/tasks) {
IO.popen("thor #{ARGV * ' '}") do |io|
while (line = io.gets) do
puts line
end
io.close
end
}
tasks/example.rb
require 'thor'
class Git < Thor
include Thor::Actions
desc 'test', 'test'
def test
puts Dir.getwd # this is showing my tasks folder
end
end
Inside example.rb How can I get access to the Dir.getwd value of the script.rb and not of the example.rb (this is wrong since it is running inside the Dir.chdir).
I tried global variables and such but it doesn't seem to be working.

How to include code from a file in Ruby?

I'm new to Ruby and I'm figuring out how to include a file in a script. I tried require and include but it doesn't work.
Here's the file I'd like to include, worth noting that it's not a module.
# file1.rb
if Constants.elementExist(driver, 'Allow') == true
allowElement = driver.find_element(:id, 'Allow')
allowElement.click()
sleep 1
wait.until {
driver.find_element(:id, 'Ok').click()
}
username = driver.find_element(:id, 'UsernameInput')
username.clear
username.send_keys Constants.itech_email
password = driver.find_element(:id, 'PasswordInput')
password.clear
password.send_keys Constants.itechPass
driver.find_element(:id, 'Login').click
else
username = driver.find_element(:id, 'UsernameInput')
username.clear
username.send_keys Constants.itech_email
password = driver.find_element(:id, 'PasswordInput')
password.clear
password.send_keys Constants.itechPass
driver.find_element(:id, 'Login').click
end
That file contains several lines of codes that are reusable or repeatable in my case. It's not inside a class or a module. It's a straightforward Ruby script, and I'd like to use this on my second script inside a module.
# file2.rb
module File2
module IOS
# include file1.rb
end
end
This way, it should just run the code of file1.rb inside file2.rb.
How can I do this in Ruby?
According to the Ruby docs, require has the following behavior:
If the filename does not resolve to an absolute path, it will be
searched for in the directories listed in $LOAD_PATH ($:).
Which means that in order to run file1.rb's code inside file2.rb, where both files are in the exact same folder, you'd have to do the following:
# file2.rb
module File2
module IOS
# Absolute path to file1.rb, adding '.rb' is optional
require './file1'
end
end
use require_relative:
require_relative 'file1.rb'

Opening relative paths from gem

I'm writing a simple gem that can load from and save data to text files and zip archives. So, it has four methods: load_from_file, load_from_zip, save_to_file and save_to_zip respectfully. The problem is that I can't figure out how to specify relative paths for loading and saving for these methods. Here they go:
def load_from_file(filename)
File.open(filename) do |f|
f.each { |line| add(line) } # `add` is my another class method
end
end
def load_from_zip(filename)
Zip::File.open("#{filename}.zip") do |zipfile|
zipfile.each { |entry| read_zipped_file(zipfile, entry) } # my private method
end
end
def save_to_file(filename)
File.write("#{filename}.txt", data)
end
def save_to_zip(filename)
Zip::File.open("#{filename}.zip", Zip::File::CREATE) do |zipfile|
zipfile.get_output_stream('output.txt') { |f| f.print data }
end
end
private
def read_zipped_file(zipfile, entry)
zipfile.read(entry).lines.each { |line| add(line) }
end
So what I want basically is to allow this gem to load and save files by relative paths whereever it is used in system, e.g. I have an app located in /home/user/my_app with two files - app.rb and data.txt, and I could be able to read the file from this directory without specifying absolute path.
Example:
# app.rb
require 'my_gem'
my_gem = MyGem.new
my_gem.load_from_file('data.txt')
(Sorry for bad English)
UPD: This is not Rails gem and I'm not using Rails. All this is only pure Ruby.
Short answer
If I understand it correctly, you don't need to change anything.
Inside app.rb and your gem, relative paths will be understood relatively to Dir.pwd.
If you run ruby app.rb from inside /home/user/my_app :
Dir.pwd will be /home/user/my_app
both app.rb and my_gem will look for 'data.txt' inside /home/user/my_app.
Useful methods, just in case
Dir.chdir
If for some reason Dir.pwd isn't the desired folder, you could change directory :
Dir.chdir('/home/user/my_app') do
# relative paths will be based from /home/user/my_app
# call your gem from here
end
Get the directory of current file :
__dir__ will help you get the directory :
Returns the canonicalized absolute path of the directory of the file
from which this method is called.
Get the current file :
__FILE__ will return the current file. (Note : uppercase.)
Concatenate file paths :
If you need to concatenate file paths, use File.expand_path or File.join. Please don't concatenate strings.
If you don't trust that the relative path will be correctly resolved, you could send an absolute path to your method :
my_gem.load_from_file(File.expand_path('data.txt'))

Testing input/output with rspec and plain ruby

I am trying to create a test for a FileProcessor that reads from a text file, passes it to another class and then writes output. I made a test file and am able to access but it feels bulky. I'm also going to need to test that it writes the output in a new file and I am not sure how to set this up. I've seen a lot of tutorials but they are be rails centric. My goal is to get rid of writing the path in the test and to clean up the generated output files after each test.
describe FileProcessor do
test_file = File.dirname(__FILE__) + '/fixtures/test_input.txt'
output_file = File.dirname(__FILE__) + '/fixtures/test_output.txt'
subject {FileProcessor.new(test_file, output_file)}
describe '#read_file' do
it 'reads a file' do
expect(subject.read_file).to eq('This is a test.')
end
end
def write_file(str)
File.open("#{output_file}", "w+") { |file| file.write(str) }
end
end
How about using StringIO:
require 'stringio'
class FileProcessor
def initialize(infile, outfile)
#infile = infile
#outfile = outfile
#content = nil
end
def read_file
#content ||= #infile.read
end
def write_file(text)
#outfile.write(text)
end
end
describe FileProcessor do
let(:outfile) { StringIO.new }
subject(:file_processor) do
infile = StringIO.new('This is a test')
FileProcessor.new(infile, outfile)
end
describe '#read_file' do
it "returns correct text" do
expect(file_processor.read_file).to eq("This is a test")
end
end
describe '#write_file' do
it "writes correct text" do
file_processor.write_file("Hello world")
outfile.rewind
expect(outfile.read).to eq("Hello world")
end
end
end
There's not a great way to avoid writing the path of your input file. You could move that into a helper method, but on the other hand having the path in the test has the benefit that someone else (or you six months from now) looking at the code will know immediately where the test data comes from.
As for the output file, the simplest solution is to use Ruby's built-in Tempfile class. Tempfile.new is like File.new, except that it automatically puts the file in /tmp (or wherever your OS's temporary file directory is) and gives it a unique name. This way you don't have to worry about cleaning it up, because the next time you run the test it'll use a file with a different name (and your OS will automatically delete the file). For example:
require 'tempfile'
describe FileProcessor do
let(:test_file_path) { File.dirname(__FILE__) + '/fixtures/test_input.txt' }
let(:output_file) { Tempfile.new('test_output.txt').path }
subject { FileProcessor.new(test_file_path, output_file.path) }
describe '#read_file' do
it 'reads a file' do
expect(subject.read_file).to eq('This is a test.')
end
end
end
Using let (instead of just assigning a local variable) ensures that each example will use its own unique output file. In RSpec you should almost always prefer let.
If you want to get really serious, you could instead use the FakeFS gem, which mocks all of Ruby's built-in file-related classes (File, Pathname, etc.) so you're never writing to your actual filesystem. Here's a quick tutorial on using FakeFS: http://www.bignerdranch.com/blog/fake-it/

Creating a common method for Capistrano tasks

Let's say in my standard deploy.rb file I have a set of namespaces. I have a common task that lists RPM packages based on a variable I pass to it. When I run this as is, it complains about capture being an undefined method. If I include that method inside the deploy.rb file, it works just fine.
Mind you, I'm new to ruby and to OOP so I'm sure I'm doing this the wrong way. :-)
deploy.rb
load 'config/module'
namespace :lp_app do
desc "LP tasks"
co = Tasks::Commands.new()
task :list do
co.list_pkg("LP")
end
end
module.rb
module Tasks
class Commands
def list_pkg(component)
File.open("#{component}.file.list", "r").each_line do |line|
pkg_name = "#{line}".chomp
set :server_pkg, capture("rpm -q #{pkg_name}")
puts "#{server_pkg}"
end
end
end
end
You are trying to use Capistano specific commands outside of Capistrano. If you want to set a variable to the result of something you run on the command line, try the backtick (`).
module Tasks
class Commands
def list_pkg(component)
File.open("#{component}.file.list", "r").each_line do |line|
pkg_name = "#{line}".chomp
server_pkg = `rpm -q #{pkg_name}`
puts server_pkg
end
end
end
end

Resources