Commit Tracing In Pry (Part 1)
- How Done Is This Post?
- The Backstory
- The Technique
- The Technique (Take 2)
- Picking a Commit:
Add Pry::Env
- Reset repo to prior commit
- Rspec conventions
- A little digression on ENV, test 1:
when ENV contains the passed key
- test 2:
when ENV doesn't contain the passed key
- Test 3:
when ENV contains the passed key but its value is nil
- From the top,
git reset --hard 17bdfd70
- Checks for understanding
How Done Is This Post? #
Anything I write ranges from “super rough notes-as-i-go, don’t even expect full sentences” to “this post is done and proven to deliver what it promises to deliver”
- Scratch-pad, take-notes-as-I-go <– we are here
- Review post, clean up for first ‘share’
- I
mkdir
new directory, follow my own steps, clarify - Integrate feedback/answer questions from others
- Final cleanup, post is considered
done
unless someone reaches out with further questions
I’m working through Leveling Up Skill #6: Commit Tracing, as applied to the pry
gem.
The Backstory #
Iuliu Pop and I are working to maximize learning, targeting “top 10% software developer”, perhaps scoped down to things like a specific programming language, business domain, etc.
For now, top 10% software developer.
The question we both think about are “what are the specific and tactical tools one can apply to improving one’s skills?”
What And Why To Trace Commits #
Read Chelsea Troy’s post: Leveling Up Skill #6: Commit Tracing.
Why To examine the Pry Gem #
Pry is a ubiquitous tool in the Ruby/Rails community, seemed like a good place to start.
The Technique #
Open the gem in your editor:
$ EDITOR=atom bundle open griddler
Could not find gem 'pry'
Hm. I check to see that the gem is installed locally:
$ gem list pry
*** LOCAL GEMS ***
pry (0.14.0, 0.13.1)
pry-byebug (3.9.0)
Ah, of course, we’re calling “bundle open”, so it wants to look at our Gemfile
or Gemfile.lock
So, I’m cd
ing into a directory that has a Gemfile
and Gemfile.lock
containing pry
.
If you’d like to follow along, I’m using my intermediate ruby obstacle course, and you can follow along with:
git clone git@github.com:josh-works/intermediate_ruby_obstacle_course.git
cd intermediate_ruby_obstacle_course
bundle install
EDITOR=atom bundle open pry
Here’s what it looked like for me:
You’ll see pry open in the specified editor
Success! 1
…
Arg. Not success, duh. As soon as I spent 3 seconds thinking about it, I realized that of course the code included in the Gemfiles doesn’t include version control info. We’ll need to change tacts, clone down the actual pry gem (with the .git
folder included) and try again.
The Technique (Take 2) #
1. Create a place for the Pry source code #
Make a new directory, clone down the pry gem
$ mkdir /me/warmup/commit-tracing/
$ cd commit-tracing
$ git clone git@github.com:pry/pry.git
$ cd pry
Now we can work through this together!
2. Find a promising commit #
Chelsea Troy says she looks for:
A fairly complex code base that exemplifies my topic of study and has clearly named, well-circumscribed commits.
So, I’m going to start browsing Pry commits
I’m skimming through a handful, I’m going to find 5 “promising” commits before moving to step 3. (I expect that my first few attempts at commit tracing will not be precisely the right size/complexity, so I’m not getting held up on this step)
Here’s some interesting commits that I might use for the next step:
- config: return
nil
on unknown option instead of raising - pry-backtrace frozen bug
- Display all syntax error messages when catching SyntaxException
- Add Pry::Env
- Add Pry::Warning
- Push edit contents to history
Picking a Commit: Add Pry::Env
#
I decided to start with Add Pry::Warning
First, I’m going to check out the repo to right before the commit that adds Pry::Warning
Here’s how to find that commit:
Next, use git diff <sha-of-the-next-commit>
to see the contents of this pull request, represented inside of your editor:
git diff 7ce5ca
Here’s how I grabbed the commit sha:
Now, if you do the above git diff
command, it’s comparing the current commit against the future commit, which shows what the current commit is missing.
Here’s the output:
This isn’t exactly what I want - I want to see the code as what’s added, not what’s deleted.
Alternatively, I’ll check out the commit that adds the feature:
git checkout 7ce5ca
And now I’ll diff this feature commit against the prior commit, which we could either hardcode the commit to diff, or use a shortcut.
Run both of these in your editor:
git co 7ce5ca # co is aliased to 'check out'
git diff 17bdfd7 # diffs against the prior commit
git diff HEAD~1 # HEAD is "wherever the repo is set right now"
# ~1 means "one commit earlier", you could
# also drop the 1, because if no number is
# provided, it assumes you mean 1
git diff HEAD~ # these last two commands are the same. Actually, these are all ways of saying the same thing
Whatever method you use, here’s what you’ll see:
Write Out All Added Code, Including what file it’s in #
Now I’m going to copy this code down on paper. I’ll time how long this takes me: It’s Time.now
when I start.
[music plays while Josh puts pen to paper]
OK! Finished. It took 9 minutes for me to write out that commit, by hand, on paper:
TODO: Add photos of paper, they’re on my phone, 02/23/21
Open the affected files in my editor: #
Now, I’m going to diverge a little from Chelsea’s path. I’m going to open each changed file in my editor:
$ git diff HEAD~1 --name-only # shows the name of modified files
$ git diff HEAD~1 --name-only | xargs atom
You’ll end up with something like this:
(I could have done atom .
and used the fuzzy finder to open these individual files up in sequence, but that would have taken longer)
I’m going to run the tests for just the affected spec. In atom, shift-ctrl-c
copies the relative path for the selected pane to my clipboard, and I can paste that in my terminal, and do rspec ctrl-v
, or rspec spec/env_spec.rb
josh, that seems like a lot of work for just running a single test file
Yeah, when I skip around a project, running many different test files, especially deeply-tested test files, I use the shift-ctrl-c
thing all the dang time.
Tests pass!
Now lets reset the repo to the prior commit, and manually rebuild this commit, from paper.
Reset repo to prior commit #
Boom. Here’s what I just did:
Running just the specs, without the actual code to make it run…
When I add require pry/env
and:
# lib/env.rb
class Pry
module Env
end
end
The tests all pass.
Siiiiiiiiigh this is annoying. The tests shouldn’t pass. Why are they passing???
Rspec conventions #
Time to dig into RSPEC a bit.
First, what’s this specify
thing? Hashrocket with the explanation:
I wanted to be able to put a pry in that line of code and be able to call key
, or described_class[key]
or Pry::Env[key]
.
I’ll have to “deconstruct” the specify
block to get a pry closer to this line of code.
We’ll go from this:
context "when ENV contains the passed key" do
before { ENV[key] = 'val' }
after { ENV.delete(key) }
specify { expect(described_class[key]).to eq('val') }
end
to this:
context "when ENV contains the passed key" do
before { ENV[key] = 'val' }
after { ENV.delete(key) }
it "should equal 'val'" do
expect(described_class[key]).to eq('val')
end
end
I don’t have access to key
here:
But I do have it now, after refactoring from specify {}
to it/do
:
A little digression on ENV, test 1: when ENV contains the passed key
#
Time to learn a little about your env
, if you’ve not already. In Pry (or your terminal session), type ENV
(it’s case sensitive)
You’ll get a bunch of results back. When I run ENV.count
I get 87 results. I’ve had to search through my ENV
before to find matching results, and I use a little ruby method like so:
ENV.select { |k| k =~ /pry/i }
This selects all my ENV
entries that have the string pry
in them, in a case-insensitive way. (that’s what the trailing i
specifies.)
I can see that as the code executes, the first spec should pass. It sets ENV['PRYTESTKEY']
to val
, and then asserts that… well, that it did this. Cool. On to test 2.
test 2: when ENV doesn't contain the passed key
#
By the way, when running the tests like so:
rspec spec/env_spec.rb
You can add :line-number
to the end, and it’ll run the spec associated with the should
block in that line of code. I do this to run tests one-at-a-time:
rspec spec/env_spec.rb:17
So, we’re testing ENV behavior when there’s no assigned key.
Instead of ENV['PRYTESTKEY'] = 'val'
having been run, now we’re just calling a bare, non-assigned env
variable, like ENV['totallyfake']
What happens when you call a hash key that doesn’t exist? You usually get nil
, so this test also doesn’t really tell us new things:
ENV['PRYTESTKEY']
=> nil
So this also passes.
Test 3: when ENV contains the passed key but its value is nil
#
I converted the specify
to a it/do
block, added the pry, and am visually/manually reproducing the assertion:
expect(described_class[key]).to be_nil
In pry:
ENV['PRYTESTKEY'].nil?
=> false
Seems like it should fail, but it doesn’t.
Sigh. Here’s how rspec
handles true
, false
, truthy
, falsey
, and nil
. This still doesn’t adhere to my expectations:
Truthiness and existentialism (rspec cheatsheet)
Arg. I feel disconent messing with code when the tests don’t even fail.
AAAAAAH it’s because I created the new file in the wrong directory. Once I move env.rb
to the lib/pry
directory (or something like that) the tests are failing. Huzzah.
From the top, git reset --hard 17bdfd70
#
OK, I deleted the lib code, rebuilt it using the tests. No problem, ended up with a close copy to what was in the PR. It didn’t take very long. Enjoyed it.
Now to rebuild the whole thing, including the tests.
I ran git reset --hard 17bdfd70
, to check out the whole repo to right before this feature that I want to add.
I added a “shell” of the test file to spec/env_spec.rb
, like so:
RSpec.describe Pry::Env do
describe "#[]" do
context "when ENV contains the passed key" do
ENV['testkey'] = 'val'
it "should equal 'val'" do
expect(ENV['testkey']).to eq('val')
end
end
end
end
and when I was running it, I kept getting uninitialized constant RSpec
errors. No idea why - I matched what exists in other files, so I did a bundle exec rspec spec/env_spec.rb
, got the same error. bundle installed
, re-ran it, and all worked. no idea why.
Now I’m getting uninitalized constant 'Pry::Env'
, which is reasonable, because… well, I’ve not added the constant.
So, added:
class Pry
module Env
end
end
and the test passes. Upgraded the test to include the 2nd assertion:
RSpec.describe Pry::Env do
describe "#[]" do
context "when ENV contains the passed key" do
ENV['testkey'] = 'val'
it "should equal 'val'" do
expect(ENV['testkey']).to eq('val')
end
end
context "when ENV does not contain the passed key" do
it "should be nil" do
expect(ENV['testkey']).to be_nil
end
end
end
end
And now the ENV
value is persisting between test runs, so I need to clean it up.
In the describe
I added:
RSpec.describe Pry::Env do
describe "#[]" do
after { ENV.delete('testkey') }
# ^^ these deletes the key I'm creating in various tests
Now all the tests pass again. So far so good. The last test case is where the real work happens, I think.
Here’s the last test I added:
context "when ENV contains the passed key but its value is '' " do
ENV['testkey'] = ''
it "should return nil" do
expect(ENV['testkey']).to be_nil
end
end
and the test output:
rspec spec/env_spec.rb
Pry::Env
#[]
when ENV contains the passed key
should equal 'val' (FAILED - 1)
when ENV does not contain the passed key
should be nil
when ENV contains the passed key but its value is ''
should return nil
Failures:
1) Pry::Env#[] when ENV contains the passed key should equal 'val'
Failure/Error: expect(ENV['testkey']).to eq('val')
expected: "val"
got: ""
(compared using ==)
# ./spec/env_spec.rb:9:in `block (4 levels) in <top (required)>'
Finished in 0.01615 seconds (files took 0.3045 seconds to load)
3 examples, 1 failure
Failed examples:
rspec ./spec/env_spec.rb:8 # Pry::Env#[] when ENV contains the passed key should equal 'val'
colorized:
Lets make it pass.
Checks for understanding #
how to open gem in specific editor #
- How do you open a gem with a specific editor? (
EDITOR=
… )
VS Code:
EDITOR=code bundle open pry
git stuff #
git checkout
What’s a commit sha
?
git log
git log -p
How to find file to open to look at tests and code and such? #
Notes, further reading #
- Tacit Knowledge is a Real Thing
- How To Open A Specific Gem In A Specific Editor (github.com/josh-works/til)
-
Here’s my today-i-learned for the above topic. ↩