Skip to content

Commit 2d14562

Browse files
committed
fix: correctly handle reading locked pact file on windows
The yielded file from Filelock must be read, rather than using File.read, otherwise "Permission denied @ io_fread" is raised. Fixes: pact-foundation/pact-js#150
1 parent 1856f21 commit 2d14562

File tree

3 files changed

+39
-29
lines changed

3 files changed

+39
-29
lines changed

lib/pact/consumer_contract/consumer_contract_writer.rb

+13-5
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,15 @@ def update_pactfile_if_needed
6060
def update_pactfile
6161
logger.info log_message
6262
FileUtils.mkdir_p File.dirname(pactfile_path)
63-
Filelock pactfile_path do | file |
63+
Filelock(pactfile_path) do | pact_file |
64+
# must be read after obtaining the lock, and must be read from the yielded file object, otherwise windows freaks out
65+
@existing_contents = pact_file.read
6466
new_contents = pact_json
65-
file.truncate 0
66-
file.write new_contents
67+
pact_file.rewind
68+
pact_file.truncate 0
69+
pact_file.write new_contents
70+
pact_file.flush
71+
pact_file.truncate(pact_file.pos)
6772
end
6873
end
6974

@@ -109,9 +114,12 @@ def pactfile_has_contents?
109114
File.size(pactfile_path) != 0
110115
end
111116

117+
def existing_contents
118+
@existing_contents
119+
end
120+
112121
def existing_consumer_contract
113-
# This must only be read after the file has been locked
114-
Pact::ConsumerContract.from_uri(pactfile_path)
122+
@existing_consumer_contract ||= Pact::ConsumerContract.from_json(existing_contents)
115123
end
116124

117125
def warn_and_stderr msg

spec/lib/pact/consumer_contract/consumer_contract_writer_spec.rb

+25-23
Original file line numberDiff line numberDiff line change
@@ -36,31 +36,36 @@ module Pact
3636

3737
let(:consumer_contract_writer) { ConsumerContractWriter.new(consumer_contract_details, logger) }
3838

39-
describe "consumer_contract" do
40-
41-
let(:subject) { consumer_contract_writer.consumer_contract }
39+
describe "#write" do
40+
it "writes the pact file to the pact_dir" do
41+
FileUtils.rm_rf target_pact_file_location
42+
consumer_contract_writer.write
43+
expect(File.exist?(target_pact_file_location)).to be true
44+
end
4245

4346
context "when overwriting pact" do
4447

4548
it "it uses only the interactions from the current test run" do
46-
expect(consumer_contract_writer.consumer_contract.interactions).to eq new_interactions
49+
consumer_contract_writer.write
50+
expect(Pact::ConsumerContract.from_json(File.read(target_pact_file_location)).interactions.collect(&:to_hash)).to eq new_interactions.collect(&:to_hash)
4751
end
4852

4953
end
5054

5155
context "when updating pact" do
5256

53-
let(:pactfile_write_mode) {:update}
57+
let(:pactfile_write_mode) { :update }
5458

5559
it "merges the interactions from the current test run with the interactions from the existing file" do
5660
allow_any_instance_of(ConsumerContractWriter).to receive(:info_and_puts)
57-
expect(consumer_contract_writer.consumer_contract.interactions).to eq existing_interactions + new_interactions
61+
consumer_contract_writer.write
62+
expect(Pact::ConsumerContract.from_uri(target_pact_file_location).interactions.size).to eq 3
5863
end
5964

60-
let(:line0) { /\*/ }
61-
let(:line1) { /Updating existing file/ }
62-
let(:line2) { /Only interactions defined in this test run will be updated/ }
63-
let(:line3) { /As interactions are identified by description and provider state/ }
65+
let(:line0) { "*" * 77 }
66+
let(:line1) { %r{Updating existing file} }
67+
let(:line2) { %r{Only interactions defined in this test run will be updated} }
68+
let(:line3) { %r{As interactions are identified by description and provider state} }
6469

6570
it "logs a description message" do
6671
expect($stdout).to receive(:puts).with(line0).twice
@@ -71,40 +76,37 @@ module Pact
7176
expect(logger).to receive(:info).with(line1)
7277
expect(logger).to receive(:info).with(line2)
7378
expect(logger).to receive(:info).with(line3)
74-
consumer_contract_writer.consumer_contract
79+
consumer_contract_writer.write
7580
end
7681
end
7782

7883
context "when an error occurs deserializing the existing pactfile" do
7984

80-
let(:pactfile_write_mode) {:update}
85+
let(:pactfile_write_mode) { :update }
8186
let(:error) { RuntimeError.new('some error')}
82-
let(:line1) { /Could not load existing consumer contract from .* due to some error/ }
87+
let(:line1) { %r{Could not load existing consumer contract from .* due to} }
8388

8489
before do
85-
allow(ConsumerContract).to receive(:from_json).and_raise(error)
90+
File.open(target_pact_file_location, "w") { |file| file << "wiffle" }
91+
8692
allow($stderr).to receive(:puts)
8793
allow(logger).to receive(:puts)
8894
end
8995

96+
after do
97+
FileUtils.rm(target_pact_file_location)
98+
end
99+
90100
it "logs the error" do
91101
expect($stderr).to receive(:puts).with(line1)
92102
expect(logger).to receive(:warn).with(line1)
93-
consumer_contract_writer.consumer_contract
103+
consumer_contract_writer.write
94104
end
95105

96106
it "uses the new interactions" do
97107
expect(consumer_contract_writer.consumer_contract.interactions).to eq new_interactions
98108
end
99109
end
100-
end
101-
102-
describe "#write" do
103-
it "writes the pact file to the pact_dir" do
104-
FileUtils.rm_rf target_pact_file_location
105-
consumer_contract_writer.write
106-
expect(File.exist?(target_pact_file_location)).to be true
107-
end
108110

109111
context "when pactfile_write_mode is update" do
110112
let(:pactfile_write_mode) { :update }

spec/support/factories.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def self.create hash = {}
3737
},
3838
'response' => {
3939
'status' => 200,
40-
'body' => {a: 'response body'}
40+
'body' => {'a' => 'response body'}
4141
}
4242
}
4343
Pact::Interaction.from_hash(stringify_keys(deep_merge(defaults, stringify_keys(hash))))

0 commit comments

Comments
 (0)